类与类之间的关系
:嵌套,代理,继承
组合,嵌套
:一个类中声明了另一个类,一个类是另一个类的一部分
代理
:一个类的接口是另一个类的接口的子集,一个类的功能需要依赖另一个类的功能实现
继承
:一个类是另一个类的一种
构造析构顺序
:先构造父类再构造子类,析构先子类后父类
赋值兼容规则:当派生类公有继承时,可以用派生类的对象对基类的对象赋值,这时会将派生类继承来的变量的值赋给基类。
父类的构造函数如果需要传参,就必须在子类的构造函数中初始化列表初始化
同名隐藏
:子类会隐藏父类的同名成员(与参数类型无关),对派生类的类外也隐藏,因此在类内类外访问都必须加::
作用域
父类的指针指向子类的对象
:Base* pb=new Derive();
#include<string.h>
class Fish
{
public:
string _name;
};
class GoldFish:public Fish
{
public:
string _color;
};
int main()
{
Fish fish;
fish._name="jinyu";
GoldFish gfish;gfish继承了父类,拥有父类的属性_name;
gfish._name="jinyu";
gfish._color="gold";
return 0;
}
public
:任何地方都可以访问
protected
:子类和自身类可以访问
private
:只有自身类可以访问
class Person
{
public:
Person(string name,int age,string sex,string wife=0)
{
_name=name;
_sex=sex;
_age=age;
_wife=wife;
}
void eat()
{
cout<<"eat"<<endl;
}
void work()
{
cout<<"work"<<endl;
}
protected:
string _name;
int age;
string _sex;
private:
string _wife;
};
class student:public Person
{
public:
student(string name,int age,string sex,string num)
:Person(name,age,sex,wife)必须初始化,因为父类先构造
{
_num=num;
}
void show()
{
cout<<"name"<<_name<<endl;
当_name为父类的私有成员时报错,子类无法访问父类的私有成员,共有与保护不报错
cout<<"age"<<_age<<endl;
}
protected:
private:
string _num;
};
int main()
{
student s;
}
多继承
class Base基类
{
public:
void fun()
{
cout<<"base void fun1()"<<endl;
}
void fun(int a)
{
cout<<"base fun(int a)"<<endl;
}
private:
int a;
};
class Derive:public Base派生类
{
public:
void fun()
{
cout<<"derive fun"<<endl;
}
void fun(int a,int b)
{
cout<<"derive void fun(int a)"<<endl;
}
private:
int _b;
};
int main()
{
Derive d;
d.fun();调用derive fun
d.base::fun();调用Base的fun
d.fun(2);报错
子类会隐藏父类的同名成员,只要名字一样就会隐藏,所以会报错,可以用::来调用
Base* bp=new Derive();父类的指针指向子类的对象,
bp->fun(10);没用作用域不报错,它认为bp指向的是父类的对象
delete bp;调用base的析构,然后子类的析构就无法实现,方法是在Base的析构函数前加(virtual)
}
动多态
动多态产生
:使用指针或者引用调用虚函数,就会产生动多态调用
动多态调用的过程
:
1.使用指针或者引用调用虚函数
2.在对象中找到vfptr
3.根据vfptr找到vftable
4.在vfptr中找到要调用的函数
5.调用
虚函数具有传递性
,当基类的函数加上virtual,子类的同函数不加virtual也会被编译器处理为虚函数,包括析构函数
同函数
:同返回值,同函数名,同参数列表
编译时期
如果类中具有虚函数就会对这个类生成vftable(虚函数表)
,将当前类虚函数的指针放到vftable当中
当对象进行构造时,如果发现该类有vftable,就会将vftable的地址写入到这个类对象之中(vfptr)
每一个类中有一个虚函数表(放在.rodata)只读数据段
覆盖(重写)
:发生在vftable,父类中的虚函数会被子类中的相同的函数覆盖(在子类的虚函数表中覆盖)
当基类的一个函数为虚函数,那么用基类的指针指向多个派生类,指向那个调用的就是那个派生类
继承权限
菱形继承
菱形继承:D中既有B继承的A的函数又有C继承A的函数,调用该函数时会报错,因为不知道调用的是哪个
解决方法是将B C继承时加一个关键字virtual(虚继承),加关键字后,B,C中就会存在一个虚基类指针(vbptr)指向vbtable中虚基类实例的位置(A).对于构造函数来说:A的构造函数必须在D类重新初始化列表构造,不能说D继承了BC(BC中已经构造了A)就不用构造,当BCvirtual继承时,那么A也相当于D的直接父类。
菱形继承的的内存分配:先总的开辟一个大空间,然后构造B中的A类,它们占一块空间,再构造B类,再构造c中的A类,最后构造本类。
为了解决菱形继承带来的数据冗余,在BC类继承A时加一个virtual,并且在D类的构造函数中(初始化列表参数)对A类进行构造,A(id,name)这样构造时内存分配是先开辟大空间,然后在底部构造A类,再构造B和C,B和C中有一块空间存放A的偏移地址。
菱形继承下若不是虚继承的虚函数产生的虚表,对于D来说有两份,先继承的B的虚表是真的虚表,对于继承的C的表在调用时会产生转换器直接调用继承B的。
纯虚函数
有纯虚函数的类称为抽象类,抽象类不能实例化对象
限制子类必须覆盖某一个接口
class A
{
public:
virtual void fun()=0;纯虚函数
}
class AA
{
public:
virtual void fun()
{
cout<<fun<<endl;
}
}