(1)继承
<1>语法
- 原则 :
父类/子类 基类/派生类
是否可以是继承:【子类 is a 父类】 - 语法
class 派生类:访问限定符 基类{
结构体
};
如果不写访问限定符,默认是
private
继承。
(2)访问权限
- 1.子类继承了父类所有的成元变量和成员函数。与访问限定符无关。访问限定符只是限制了访问。
- 2.子类内部要想访问父类的private成员,可以把
private
改为public
或者protected
(常用)
<1>子类内部的访问权限
- 1.无论那种继承方式,子类内部可以访问除了
private
的所有成员。 - 2.子类继承父类,成员访问权限的变化:
继承方式\父类成员 | public | protected | private |
---|---|---|---|
public继承 | public | protected | 不可见 |
protected继承 | protected | protected | 不可见 |
private继承 | private | private | 不可见 |
子类内部访问父类成员,只能访问
public
和protected
成员。
<2>子类对象的访问权限
- 子类对象只能访问其父类的
public
成员,不能访问其他成员。
注意:
(1)子类只有public继承父类的时候,才能访问父类的public成员,其他都不能访问。通常子类使用public继承父类。
(2)public继承,可以访问,不能直接修改基类里的private成员,可以在父类中用接口来修改其私有变量的值。
(3)继承关系的构造顺序
<1>派生类的构造顺序
- 派生类的构造顺序:父类---->子类成员变量------>子类
- 派生类的析构顺序:和构造顺序相反。
注意:没有默认构造函数的基类在派生类的初始化,必须在初始化列表中初始化。比如在三角形实例中。
class Member{
public:
Member(int i){ cout << __func__ << endl;}
~Member(){ cout << __func__ << endl;}
};
class Father{
public:
Father(int i){ cout << __func__ << endl;}
~Father(){ cout << __func__ << endl;}
};
class Son:public Father{
Member m;
public:
//没有默认构造函数的父类在子类中初始化,必须使用初始化列表的方式
Son():m(1),Father(1){ cout << __func__ << endl;}
~Son(){ cout << __func__ << endl;}
};
int main(){
Son s;
}
结果:
Father
Member
Son
~Son
~Member
~Father
(4)同名隐藏规则
<1>概念
如果子类与父类成员函数同名,无论参数是否相同,子类会隐藏父类的成员函数。
<2>访问同名隐藏的父类
- 1.在子类中添加【using 父类名::同名函数名;】,只能解决同名但不同参的情况;
- 2.使用【子类对象.父类名::父类函数名;】:可以解决任何情况。
class Father{
public:
void Test(){
cout << "Father::Test()" << endl;
}
void Test(int i){
cout << "Father::Test(" <<i <<")" <<endl;
}
};
class Son:public Father{
public:
using Father::Test;//(1)只能解决参数不同的同名隐藏
void Test(){
cout << "Son::Test()" << endl;
}
};
int main(){
//不采用任何情况
Son s;
s.Test();//访问的是子类的成员函数
s.Test(3)//会报错!
//如果要调用父类的方法:
//方法1:可以完全解决同名隐藏
s.Father::Test(2);
s.Father::Test();
//法(2):使用using 。。。,只能解决同名不同参的问题
s.Test(3);
//s.Test();//不能调用父类的Test()
}
<3>对象查找函数的规则
对象首先在子类中查找,如果没有在子类找到,会到父类中查找;如果子类中找到了同名的函数,并且参数相同就会执行该函数,如果参数不同就会报错,并不会再往父类中查找了。
注意:指针访问和.访问的规则一样。
(5)赋值兼容
<1>前提
必须是公有继承,才能谈赋值兼容。
<2>概念
- 在任何需要基类对象的地方都可以使用公有的派生类对象来代替。反之,不可。
- 对象切割(Object Slicing):在赋值时舍弃派生类自己的成员,只进行基类数据成员的赋值。
- 赋值兼容可能会产生对象切割的代价。
<3>赋值兼容有三种情况:
-
1. 派生类的对象可以赋值给基类对象
会生成一个新的父类对象。
Base base;
Derive derive;
base = derive;
- 注意:
(1) 子类特有的成员变量不能被父类访问。
(2)子类给父类赋值以后,父类的大小是其本身成员变量的大小,子类的大小是继承于父类的大小加上子类本身的大小。
(3)子类对象是可以初始化父类对象。 - 例子:
class Base{
public:
int n;
};
class Derive:public Base{
public:
int m;
};
void Func(Base b){}
Base Func(){
Derive d;
return d;
}
int main(){
Base b;
Derive d;
d.n=10;
d.m=100;
b=d;//调用Base的赋值运算符重载
//d=b;//错误,不可以这样赋值
cout << b.n<<endl;//4
//cout << b.m<<endl;//Base对象不能访问到子类的任何成员(对象切割)
cout << sizeof(b)<<endl;//4
cout << sizeof(d)<<endl;//8
Base b2(d);//子类对象初始化父类对象(调用Base的拷贝构造函数)
子类对象给父类对象赋值的应用:
//1.函数参数对象传值
Func(d);//调用Base的拷贝构造函数
//2.函数返回对象
Func();////调用Base的拷贝构造函数
-
2. 派生类的对象可以初始化基类的引用
没有生成新的对象,只是起了一个别名。
Derive d;
Base& fb = d;//子类对象初始化父类的引用,类型决定大小和能否访问的成员
1.d的地址和fb的地址是一样的!
cout << &d <<endl;
cout << &fb <<endl;
2.初始化父类的对象是不能访问子类成员的。
cout << sizeof(d) <<endl;//8
cout << sizeof(fb)<<endl;//4
d.Test();//子类对象可以访问子类的成员
fb.Test();//父类引用虽然指向子类对象,但是不能访问子类成员,只能访问父类的成员
3. 应用:
void Func(Base& b){}//定义
Func(d)//调用.(这里是引用不会调用拷贝构造函数,值传才会调用拷贝构造)
-
3. 派生类的对象地址可以初始化基类的指针
没有生成一个新的对象,只是父类指针指向父类特有的那一部分。
Base* pb=&d;
1.d的地址和fb的地址是一样的!
cout << pb <<endl;
cout << &d <<endl;
2.初始化父类的对象是不能访问子类成员的。
pb->Test();//只能访问父类特有的成员
pb->m;
3. 应用;
void Func(Base* b){}//定义
Func(&d);//调用
注意:
1.父类引用子类对象与父类指针指向子类对象,都只能访问子类所继承的那部分成员
2.即使父类和子类函数名和参数完全相同,也不能访问到子类的成员,要想访问与其同名的子类成员,就要实现多态
(6)多重继承
一个类可以同时继承多个父类的行为和特征功能。
<1>格式
class 类名 : public 基类1,public 基类2{};
<2>大小
多重继承的大小,等于继承的所有父类大小之和。
<3>多重继承基类构造顺序
父类在子类的继承顺序就是子类对象的初始化顺序,如果父类有继承先会初始化父类的继承。
class Base{
public:
Base(){cout << __func__ <<endl;}
~Base(){cout << __func__ <<endl;}
int n;
};
class A:public Base{
public:
A(){cout << __func__ <<endl;}
~A(){cout << __func__ <<endl;}
};
class B:public Base{
public:
B(){cout << __func__ <<endl;}
~B(){cout << __func__ <<endl;}
};
class C:public A,public B{
public:
C(){cout << __func__ <<endl;}
~C(){cout << __func__ <<endl;}
};
int main(){
C c;
cout << "Base size:" << sizeof(Base) << endl;//4
cout << "A size:" << sizeof(A) << endl;//4
cout << "B size:" << sizeof(B) << endl;//4
cout << "C size:" << sizeof(C) << endl;//4+4=8
执行结果:Base--->A--->Base--->B---->C---->~C---->~B---->~Base---->~A----->~Base
}
<4>多重继承的危害
钻石继承/菱形继承。(有两套相同的成员函数和成员变量,造成成员冲突)
(7)钻石继承/菱形继承
<1>概念
两个子类B,C继承同一个父类A,而又有子类D同时继承这两个子类B,C,继承关系画成一个图刚好是菱形。
<2>产生的问题
- (1)成员冲突:子类D有两套名字完全相同的成员变量和成员函数来自B和C。要想访问某个成员必须在其类D中指明是那个类的成员。
- (2)多次构造和析构:见上面的多重继承。在构造父类时,由于父类都虚继承了祖父类,所以在构造是都会调用祖父类的构造函数,并且会多次释放。
void Print()const{
//成员变量
cout << "a:" << EqualTriangle::a <<endl;//指明成员变量属于那个类。
cout << "b:" << EqualTriangle::b <<endl;
cout << "c:" << EqualTriangle::c <<endl;
cout << "a:" << RightTriangle::a <<endl;
cout << "b:" << RightTriangle::b <<endl;
cout << "c:" << RightTriangle::c <<endl;
//成员函数
cout << er.EqualTriangle::Getlength() << endl;
cout << er.RightTriangle::Getlength() << endl;
说明:等腰直角三角形同时继承了等腰三角形和直角三角形,当调用Getlength时,不知道调用的是等腰三角形的还是直角三角形的。
<3>虚继承
- 解决成员冲突有两种方法:
(1)在访问时加上类型(类名)来限制访问的是那个成员。
(2)虚继承 - 定义
虚继承:在继承定义中包含了virtual关键字的继承关系。(A虚继承于Base)
虚基类:在虚继承体系中的通过virtual继承而来的基类。(Base是A的虚基类)
虚基类是一个相对概念,在虚继承关系中,父类相对与子类是虚基类
- 格式
class 类名:public virtual 基类{
}
- 注意:
1.格式的写法
2.关于孙子类的构造函数要加上前面父类和祖父类
3.sizeof的变化
4.孙子类对象成员函数的调用(调用的是父类的,不是祖父类的)
5.初始化祖父类的次数的变化,之前是两次构造和析构,加上虚继承后只构造一次、析构一次。
class EqualTriangle:public virtual Triangle{//添加虚继承
class RightTriangle:public virtual Triangle{//添加虚继承
class EqualRightTriangle:public EqualTriangle,public RightTriangle{
EqualRightTriangle(floatright):EqualTriangle(sqrt(right*right*2),right),
RightTriangle(right,right),Triangle(right,right,sqrt(right*right*2)){}//所有的构造函数都必须初始化,但是如果父类和祖父类是默认的构造函数,则不用写。
void Print()const{
cout << "a:" << a <<endl;//虚继承后可以直接访问祖父类的成员变量
cout << "b:" << b <<endl;
cout << "c:" << c <<endl;
}
};
int main(){
cout << sizeof(EqualTriangle) <<endl;//12(3*4) ---->24(8(一个指向虚继承的指针)+3*4+4(补齐))
cout << sizeof(RightTriangle) << endl;//12 ---->24
cout << sizeof(EqualRightTriangle) <<endl;//24(12+12)---->32(8*2(加了两个指针)+3*4+4(补齐))
cout << er.GetArea() << endl;//调用的是父类(RightTriangle)函数,不是祖父类的。
}
虚继承的构造和析构顺序:
class Base{
public:
Base(){cout<<__func__<<endl;}
~Base(){cout<<__func__<<endl;}
};
class A:public virtual Base{
public:
A(){cout<<__func__<<endl;}
~A(){cout<<__func__<<endl;}
};
class B:public virtual Base{
public:
B(){cout<<__func__<<endl;}
~B(){cout<<__func__<<endl;}
};
class C:public A,public B{ // 父类在子类中的继承顺序就是子对象中父类的构造顺序
public:
C(){cout<<__func__<<endl;}
~C(){cout<<__func__<<endl;}
};
class Emply{};
int main(){
C c;
cout << sizeof(Base) << endl;
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
// 空类
cout << sizeof(Emply) << endl;
}
结果:
Base
A
B
C
1
8
8
16
1
~C
~B
~A
~Base
<4>关于多重继承总结
(1)什么是多重继承?同时继承多个父类。
(2)多重继承有什么危害?菱形继承/钻石继承。
(3)什么是菱形继承/钻石继承?多重继承的两个或多个父类具有相同的祖先类。
(4)菱形继承/钻石继承有什么危害?因为多重继承的两个或多个父类具有相同的祖先类。所以会有完全相同的属性和方法。因此当前多重继承类有两份相同的属性和方法。使用时会出现冲突。
(5)如何解决菱形继承/钻石继承导致的冲突?使用虚继承
(6)什么是虚继承?父类在继承具有相同的祖先类时,加上virtual.
(8)对象构造顺序总结
- 先父后子(继承)
- 从左到右(多重继承)
- 先虚后实(虚继承 )
- 从上到下 (成员变量)
- 由内及外 (组合)
网友评论