一、继承作用:进行代码复用,将重复的部分作为公共部分来使用,从而不必在构建每一个类的时候都将相同的部分在写一遍。通过继承,可以获得父类的所有功能,并且可以重写已有功能,添加新功能。
二、继承的规则与特点:
1. 子类对象在创建时调用父类的构造函数,然后在调用自己的构造函数;先基类构造再派生构造。
2. 子类可以直接使用父类的成员函数,也就是子类拥有父类的所有属性和行为。子类可以当做父类对象使用。
3. 子类中可以添加父类没有的方法和属性。
4. 子类对象可以直接赋值给父类对象(public继承),也就是说子类对象可以直接初始化父类对象。
eg: Base base;//基类
Drive drive;//派生类
base=drive;
5.当派生类中存在与基类同名的成员的时候(成员函数是否同名与参数无关),通过派生类的对象进行访问的时候,只能访问派生类中的成员,若要访问基类中的成员,必须加上作用域限定符(同名隐藏);
6.在基类中是虚函数,则派生类中同名且参数相同的函数也是虚函数。
7.基类对象的引用或指针可以指向派生类的对象,反之不可;(public继承)
Base *pb=new Drive(10); V
Drive* pd=new Base(10); X
8.友元函数是不能被继承的(友元函数不是成员函数);
9.基类中的静态成员可以被继承,但无论有多少派生类,该静态成员仅有一份,也就是说基类与派生类共用一份该静态成员;
三、基类不同的访问限定符的成员以不同的继承方式继承,在派生类中的访问限定
四、构造函数和析构函数
基类的构造,析构函数派生类无法继承下来,必须在派生类构造函数的初始化列表上表明基类的构造方式。
析构:要将基类的析构设置为虚函数,才能防止内存泄露,举个例子:
class Base
{
public:
Base(int a) :ma(a)
{
cout << "Base::Base()" << endl;
}
void show()
{
cout << "Derive::show()" << endl;
}
virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
protected:
int ma;
};
class Derive : public Base
{
public:
Derive(int b) :mb(b), Base(b)
{
cout << "Derive::Derive()" << endl;
}
virtual void show()
{
cout << "Derive::show()" << endl;
}
~Derive()
{
cout << "Derive::~Derive()" << endl;
}
private:
int mb;
};
int main()
{
Base* pb = new Derive(10);
pb->Show();
delete pb;//会崩溃
return 0;
}
如果基类析构不设置成虚函数,则派生内存布局为下图,造成内存泄露
如果基类析构设置成虚函数,(动态绑定)则派生内存布局为下图,(把派生类的vfptr合并到基类的vfptr中)
指针调用虚函数是动多态,发生动态绑定,运行时确定函数的调用;
编译期间确定虚函数表,从表里拿到虚函数入口地址,存放到eax中(call eax),虚表存放在.rodata只读数据段,(运行时会将.data和.text段加载到内存中),所以运行时会通过两个logal加载器把虚表加载到内存中,运行时就可以知道虚函数的入口地址。这样,就会先析构派生类,后析构基类。
五、同名函数关系
1、重载(overlode):
相同作用域;函数名相同;参数列表不同(参数类型不同,或者参数个数不同,或者参数个数和参数类型都不相同);返回类型随意。
产生原因:主要是因为在C++中,编译器在编译.cpp文件中当前作用域的同名函数时,函数生成的符号由返回值类型(不起决定作用)+形参类型和顺序(起决定作用)的组成
作用:用同一个函数名命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。
2、隐藏(overhide)/ 就是重定义:(存在,看不见)
作用域不同(分别在父类和子类中);函数名相同;除过覆盖的同名函数都是隐藏关系。
派生类的同名函数隐藏了基类中所有的同名函数。
3、覆盖(override)/ 就是重写:(不存在)
覆盖的作用:实现动多态。
发生条件:
1)作用域不同;函数名相同;参数列表相同;返回类型相同;
2)发生在虚表中,基类函数的同名函数是虚函数;
3)派生类中的同名且参数列表相同的函数覆盖了基类同名,参数列表相同的函数
在子类中定义了一个与父类完全相同的函数时,称子类这个函数覆盖了父类的这个虚函数。
完全相同代表着两个函数的函数名、参数个数、参数类型、返回值类型都相同;也有特殊例子(协变)。
**协变:子类的虚函数和父类中的虚函数的函数名、参数个数、参数类型都相同,只是返回值类型不同,
父类的虚函数返回的是父类的指针或者引用,Base: virtual Base& show();
子类的虚函数返回的是子类的指针或者引用,Drive: virtnal Drive& show();
这种情况下也会产生子类的虚函数覆盖父类的虚函数(返回值类型不同)
注:派生类的析构会覆盖掉基类的析构
六、赋值兼容规则
子类对象可以直接赋值给父类对象(public继承),也就是说子类对象可以直接初始化父类对象。
eg: Base base;//基类
Drive drive;//派生类
base=drive;
七、has_a / is_a的关系
组合关系:has_a <---------> a part of
节点和链表的关系,节点是链表的一部分
继承关系:is_a<--------> a kind of
学生和人的关系,学生是人的一种