《一》单继承:派生类的直接基类只有一个
<1>继承的方式
class Base /*------------->基类/ 父类 */
{
public:
int ma;
};
class Derive:public Base /* ------------>派生类/ 子类 */
{
prviate:
int mb;
};
<2>派生类继承了什么?
除了构造函数,析构函数,赋值运算符和友元关系无法继承以外,其他所有的东西都可以继承(包括把作用域也继承了下来)
<3>派生类的内存布局
基类数据 |
派生类数据 |
<4>继承方式(public private protected)
public | private | protected | |
public | public | 不可访问的 | protected |
private | private | 不可访问的 | private |
protected | protected | 不可访问的 | protected |
<5>类和类的关系
1:组合关系 2:继承关系 3:代理关系
<6>派生类对象的构造方式
先构造基类(系统调用默认的构造函数),然后再构造派生类。《若基类中无默认的构造函数,需要在基类的初始话列表中指明基类的构造方式》
class Base
{
public:
Base(int a):ma(a){}
int ma;
};
class Derive:public Base
{
public:
Derive(int b=0):Base(b),mb(b){} ///在派生类中指明基类的构造方式
prviate:
int mb;
};
<7>基类和派生类的指针和引用
class Base
{
public:
Base(int a):ma(a){}
int ma;
};
class Derive:public Base
{
public:
Derive(int b=0):Base(b),mb(b){} ///在派生类中指明基类的构造方式
prviate:
int mb;
};
void main()
{
Base b(20);
Derive d(30);
Base* pbase = &d; //基类指针指向派生类
Derive* pderive = &b; //派生类指针指向基类 (错误)
Base &rbase = d; //基类引用指向派生类
Derive &rderive = b; //派生类引用指向基类 (错误)
}
指针和引用相同,都不可用派生类的指针(或引用)指向基类。
<8>派生类中如果有基类中的同名函数(虚函数)派生类中的同名函数会自动+virtual(变成虚函数),如果派生类中无同名虚函数,则继承基类中的虚函数。<基类中有虚函数,派生类中必有虚函数>
《二》多重继承:派生类的直接基类有多个
多重继承中基类的构造顺序:谁先继承,谁先构造
class A
{
public:
A(int a) :ma(a){}
void fun()
{
cout << "Base::fun" << endl;
}
protected:
int ma;
};
class B: public A
{
public:
B(int b) :A(b),mb(b){}
protected:
int mb;
};
class C : public A
{
public:
C(int c) : A(c), mc(c){}
protected:
int mc;
};
class D:public B,public C
{
public:
D(int d):B(d),C(d),md(d){}
private:
int md;
}
int main()
{
//D d;
//d.fun();//编译器报错:调用不明确
cout<<"A="<<sizeof(A)<<endl;
cout<<"B="<<sizeof(B)<<endl;
cout<<"C="<<sizeof(C)<<endl;
cout<<"D="<<sizeof(D)<<endl;
return 0;
}
代码输出:
在上面的代码中:基类的构造顺序是:A->B->C->D
此时类C的内存布局如下图:
此时就构成了我们所说的菱形继承:
出现的问题:当我们用对象d调用fun函数时;编译器报错误,因为编译器不知道该调用哪一个类中的fun函数,并且会造成数据冗余的问题,明明可以只要一份就好,而我们却保存了两份。会造成内存重复,浪费内存(就如我们D的内存布局中就有两份类A中的成员变量,造成内存重复)
解决这个问题的第一个方法就是:调用fun函数是加作用域,明确的说明我要调用哪一个类种的fun函数,不过这个做法很累赘
为了很好的解决这个问题,c++中引入了虚继承:
虚继承即让A和B在继承Base时加上virtural关键字,这里需要记住不是D使用虚继承
有虚基类继承的构造顺序:虚基类的优先级最高,(先构造虚基类,再看谁先继承,谁先构造)
class A
{
public:
A(int a) :ma(a){}
protected:
int ma;
};
class B:virtual public A
{
public:
B(int b) :A(b),mb(b){}
protected:
int mb;
};
class C :virtual public A
{
public:
C(int c) : A(c), mc(c){}
protected:
int mc;
};
class D:public B,public C
{
public:
D(int d):B(d),C(d),A(d),md(d){} //此时B和C不对A进行构造,所以要在D中指明A的构造方式
private:
int md;
}
int main()
{
cout<<"A="<<sizeof(A)<<endl;
cout<<"B="<<sizeof(B)<<endl;
cout<<"C="<<sizeof(C)<<endl;
cout<<"D="<<sizeof(D)<<endl;
return 0;
}
代码输出:
此时加了虚继承的B,C,D的内存均必没加虚继承的B,C,D的内存大了4字节,所以我们可以得出虚基类指针的大小为4字节
D继承了B,C,在D的内存布局中虚基类在最下面,
加上虚基类的继承的构造顺序:A->B->C->D
此时D的内存布局:
我们可看见在C和B中不再保存A中的内容,保存了一份偏移地址,然后将A的数据保存在一个公共位置处这样保证了数据冗余性的降低,
总结:虚继承解决的问题是:1,二异性 2,内存重叠
菱形继承实例《二》:代码如下
class A
{
public:
A(int a) :ma(a){}
protected:
int ma;
};
class B:virtual public A
{
public:
B(int b) :A(b),mb(b){}
protected:
int mb;
};
class C :virtual public A
{
public:
C(int c) : A(c), mc(c){}
protected:
int mc;
};
class E
{
public:
E(int e) : me(e){}
protected:
int me;
};
class D:public E,public C,virtual public B
{
public:
D(int d):B(d),A(d),E(d),C(d),md(d){}
private:
int md;
};
int main()
{
cout<<"A="<<sizeof(A)<<endl;
cout<<"B="<<sizeof(B)<<endl;
cout<<"C="<<sizeof(C)<<endl;
cout<<"D="<<sizeof(D)<<endl;
return 0;
}
代码输出:
构造顺序:A->B->C->E->D
D的内存布局:
如果虚基类指针不是平行层次要进行合并(这里所说的是否是平行层次的意思是虚继承的继承关系是不是平行的)
合并是派生类合并到基类中,
合并之后D的内存布局:
《三》c++中的同名函数
1:隐藏:隐藏发生在继承中,(隐藏是“看似无实际有”)构成隐藏的两个条件:
- 不同作用域(并且有继承关系)
- 派生类中的同名函数隐藏了基类中的所有的同名函数,
所以我们要访问被隐藏的函数时加作用域访问就可以了
2:覆盖:覆盖发生在虚函数表中,(覆盖是“看似无实际也无”)派生类中的同名同参虚函数覆盖了基类中的虚函数
3:重载:重载的条件:1,同作用域 2,同名 3,不同参数列表