一、基本概念
- 代码复用:继承的基本功能,封装起来的基类稳定性、安全性和效率都较高
1.1派生类
- 派生类:继承了基类的数据成员和成员函数,派生类中只需写新增的数据成员和新增的成员函数即可
- 派生类构造函数:
- a、先调用基类构造函数,然后调用派生类构造函数
- b、若未显式调用基类构造函数,则隐式调用
- 派生类析构函数:先调用派生类的析构函数,然后调用基类的析构函数
1.2 继承关系
- is-a关系
- 全称:is-a-kind-of
- 单向性:person包含所有singer,但singer不包含所有person
- 扩展性:派生类可在基类基础上添加属性,但不可删减
- has-a关系
- 例如:meal包含fruit,但是不能从meal派生出fruit
- 处理:将fruit类作为meal的一个数据成员
二、常规写法
- 合并写法:可以把以下三个放到一个文件里
- 头文件写法:分成三个文件,头文件、类实现文件、主程序文件,三者找地方放
2.1 头文件
- person.h
// 这个PERSON_H_是自己命名的,不必跟文件名一致
#ifndef PERSON_H_
#define PERSON_H_
// 以下内容会被原封不懂的插入#include "person.h"语句处
#include <iostream>
#include <string>
using namespace std;
// 基类
class Person
{
private:
string _name;
int _age;
// 静态数据成员:见下注释,记录类创建了多少个
static int count;
public:
// const静态数据成员:见下注释,记录创建对象的基类名字
static const string title;
// 常规构造函数,接收0、1、2个参数
Person(const string &name = "未命名", int age = 0);
~Person();
void show() const;
};
// 派生类:指明基类是Person,继承方式为public,
// 可访问public,protected,不可访问private
class Singer : public Person
{
private:
string _style;
public:
Singer(const string &name = "未命名", int age = 0,
const string &style = "无风格");
void show() const;
};
#endif
- 静态数据成员:
- 功能:作为声明变量的类所创建的对象的共享变量
- 声明及初始化:类声明中声明,在类方法文件中初始化,防止多次引入头文件,导致的多次初始化
- 静态const数据成员
- 声明及初始化:同静态数据成员
- 功能:创建的常量生命周期跟类同长,避免频繁创建销毁
2.2 类实现
- person.cpp
// 注意路径的写法,同级目录可只写文件名
#include "./person.h"
// 基类中静态成员初始化
int Person::count = 0;
// 基类中const静态成员初始化
const string Person::title = "Person类";
// 基类的成员函数定义
// 类构造函数:分号后为成员初始化列表形式,只能用于构造函数
// 分号前括号内为形参,分号后_name(name),意义同_name=name;
Person::Person(const string &name,
int age) : _name(name), _age(age) { count += 1; }
~Person(){ count -= 1; };
// 基类的常规成员函数
void Person::show() const
{
cout << "姓名是:" << _name << endl;
cout << "年龄是:" << _age << endl;
cout << "当前Person个数:"<< count << endl;
}
// 派生类的成员函数定义
// 派生类构造函数:成员初始化列表,且调用了基类的构造函数
Singer::Singer(const string &name, int age,
const string &style) : Person(name, age), _style(style){};
// 派生类的常规成员函数
void Singer::show() const
{ // 派生类中调用基类的成员函数方法
Person::show();
cout << "风格为:" << _style << endl;
// 生成了15个*号
string s(15, '*');
cout << s << endl;
}
2.3 主程序
- main.cpp
// 注意路径的写法,同级目录可只写文件名
#include "./person.h"
int main()
{
// 类数组列表初始化
Singer singer[3] = {Singer("小明", 15, "hip-hop"),
Singer("小李", 13, "blue")};
singer[0].show();
singer[1].show();
// 这里调用的是基类Person的show函数
singer[2].Person::show();
// 调用默认构造函数,生成一个对象,count增加1
Singer temp;
temp.show();
}
2.4 编译及显示
- 编译命令
>> g++ main.cpp person.cpp person.h
>> ./a.out
- 显示
姓名是:小明
年龄是:15
当前Person个数:3
风格为:hip-hop
***************
姓名是:小李
年龄是:13
当前Person个数:3
风格为:blue
***************
姓名是:未命名
年龄是:0
当前Person个数:3
姓名是:未命名
年龄是:0
当前Person个数:4
风格为:无风格
三、多态公有继承
-
名词释义
名称 |
释义 |
多态 |
即具有多种形态,同一方法,在派生类和基类中的行为是不同的 |
多态用途 |
函数虚实参传递时可用这一特性,实参为派生类对象(singer)或地址(&singer),虚参为基类绑定(&rp)或指针(*pp) |
重载问题 |
若有基类有重载声明,派生类不可单独重载其中一个,需全部重写 |
派生类不可继承函数 |
构造、析构、=(用目标对象修改已初始化对象) 函数 |
能默认生成函数 |
构造(空的)、析构(空的)、=(默认逐数据成员项复制,遇到指针浅浅复制)、& 函数 |
-
多态调用指向(见2.3.1代码)
- 合法指向:基类的指针或绑定可以指向派生类,通过等号右侧(指针或绑定指向的对象)的对象决定调用哪个类的方法
- 限制指向:派生类指针或绑定不可以指向基类,除非显式的定义了构造函数
派生类(&基类)
3.1 虚方法
- 功能:若要在派生类中重新定义基类的方法,将它设置为虚方法,否则为非虚方法(效率高相对高)
- 头文件person.h
...
// 基类
class Person
{
private:
...
public:
...
// 只在基类增加virtual关键字即可
virtual void show() const;
...
// 虚析构函数:基类内声明,规范析构函数调用顺序,
// 先调用派生类析构函数,再调用基类析构函数
// 若无此句,在main.cpp中,a、b完成后将只调用基类的析构函数
virtual ~Person(){}
};
class Singer : public Person
{
...
}
...
#endif
- 代码实现main.cpp
#include "person.h"
int main()
{
Singer singer("小明", 15, "hip-hop");
// 以下三个都是调用派生类的show函数,若person.h中无virtual
// 以下前两个调用的是基类的show函数,最后个还是派生类的show函数
// 注意体会将其应用在函数虚实参传递中的效果
// a、基类引用可以绑定派生类
Person &rp = singer;
rp.show();
// b、基类指针可以指向派生类
Person *pp = &singer;
pp->show();
singer.show();
}
3.2 抽象基类
3.3 多重继承MI
-
定义:能够使用两个或多个基类派生出新的类,组合功能
-
虚基类:使得从多个类(他们基类相同)派生出的对象只继承一个基类对象,防止二义性
-
构造函数规则:对虚基类,除默认构造函数,否则必须显式调用虚基类的某个构造函数
-
继承关系示例
-
代码示例
#include <iostream>
using namespace std;
class B{
public:
void show(){cout<<"B"<<endl;};
};
// 指定B为虚基类
class C:virtual public B{
public:
void show(){cout<<"C"<<endl;};
void cal(){cout<<"C"<<endl;};
};
class D:public C{};
// 指定B为虚基类
class E:virtual public B{
public:
void cal(){cout<<"E"<<endl;};
};
class F:public D,public E{};
int main() {
F m;
// 用的是类C的
m.show();
// m.cal()会产生二义性,
// 最好在类F中重写此方法
m.D::cal();
return 0;
}
四、动态内存分配
- 静态内存分配:程序启动瞬间,变量即被分配了内存空间,直到程序退出销毁
- 动态内存分配:程序运行期间,根据需要,动态分配内存空间,期间动态分配、销毁
4.1 头文件
4.2 类实现
- person.cpp
#include "./person.h"
Person::Person()
{
// 赋值静态数据成员,见2.1节
static const char *s = "未命名";
// 字符串长度:传入字符串指针
len = strlen(s);
// new分配内存空间,此处用[],对应于析构函数
_name = new char[len + 1];
// 字符串深拷贝
strcpy(_name, s);
cout << "默认构造函数被调用" << endl;
};
Person::Person(const char *name)
{
// 同构造函数
len = strlen(name);
_name = new char[len + 1];
strcpy(_name, name);
cout << _name << "的构造函数被调用" << endl;
};
Person::Person(const Person &p)
{
this->len = p.len;
_name = new char[len + 1];
strcpy(_name, p._name);
cout << _name << "的复制构造函数被调用" << endl;
};
Person::~Person()
{
// 析构函数对应与构造函数
delete[] _name;
cout << _name << "的析构函数被调用" << endl;
};
Person &Person::operator=(const Person &p)
{
// 检查是否为自我复制,若是且无此判断语句
// 则delete语句会先清空内存导致无值可赋
if (this == &p)
return *this;
// 若非自我复制,清空_name指向的内存
delete[] _name;
// 以下同构造函数写法
len = strlen(p._name);
_name = new char[len + 1];
strcpy(_name, p._name);
cout << _name << "赋值运算符被调用" << endl;
return *this;
};
void Person::show() const
{
cout << "姓名是:" << _name << endl;
};
4.3 主程序
4.4 显示
上一篇:C++启蒙笔记(七)—类运算符重载
综合专题列表
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)