继承语句
class 派生类 : 基类名表 {
};
其中基类名表格式是:
访问控制 基类名1 , 访问控制 基类名2 , ...
- 可以省略访问描述符,默认是私有继承;
访问控制
- public 继承:public -> public , private 不可访问 , protected -> protected
- protected 继承:public -> protected , private 不可访问 , protected -> protected
- private 继承:public -> private , private 不可访问 , protected -> private
继承和包含关系下的构造析构顺序
- 基类的属性
- 基类的构造方法
- 派生类的属性
- 派生类的构造方法
如果上面的属性是类对象,参照上一篇博文的构造顺序。如果继承多个基类,按照声明的顺序而不是构造函数初始式的顺序构造基类,每个基类分别定义属性,执行构造方法之后才到下一个基类。
#include<iostream>
using namespace std;
class Inter{
string name;
public:
Inter(){
name = "inter";
cout<<name<<" of Inter build!"<<endl;
}
Inter(string n){
name = n;
cout<<"Inter use "<<n<<" build!"<<endl;
}
~Inter(){
cout<<name<<" of Inter not build!"<<endl;
}
};
class InterPlus{
string name;
public:
InterPlus(){
name = "interplus";
cout<<name<<" of InterPlus build!"<<endl;
}
InterPlus(string n){
name = n;
cout<<"InterPlus use "<<n<<" build!"<<endl;
}
~InterPlus(){
cout<<name<<" of InterPlus not build!"<<endl;
}
};
class Base{
InterPlus itp;
public:
Base():itp("itp"){
cout<<"Base build!"<<endl;
}
~Base(){
cout<<"Base not build!"<<endl;
}
};
class BasePlus{
public:
BasePlus(){
cout<<"BasePlus build!"<<endl;
}
~BasePlus(){
cout<<"BasePlus not build!"<<endl;
}
};
class Detrived:Base,BasePlus{
Inter it;
public:
Detrived(){
cout<<"Detrived build!"<<endl;
}
~Detrived(){
cout<<"Detrived not build!"<<endl;
}
};
int main(){
Detrived d;
}
结果:
InterPlus use itp build!
Base build!
BasePlus build!
inter of Inter build!
Detrived build!
Detrived not build!
inter of Inter not build!
BasePlus not build!
Base not build!
itp of InterPlus not build!
分析:
- 想要创建 Detrived 对象,需要先创建 Base 和 BasePlus 对象,先 Base 再 BasePlus;
- 先创建 Base 对象,先定义 Base 的属性,是一个 InterPlus 对象,先创建 InterPlus 对象,看 Base 构造函数的初始式有 InterPlus(string n) 构造方法,调用这个构造方法,输出第 1 句;
- 然后执行 Base 的构造函数,输出第 2 句;
- 然后创建 BasePlus 对象,没有属性,直接执行构造函数,输出第 3 句;
- 然后创建 Detrived 对象,先定义属性,是一个 Inter 类对象,默认函数构造 Inter 对象,直接调用构造函数,输出第 4 句;
- 然后执行 Detrived 构造函数,输出第 5 句;
- 按照上面构造的逆序执行析构函数。
访问声明
继承关系使基类成员(属性和方法)在派生类中是固定的,如私有继承下,基类的 ptotected 到派生类变成了 private,为了更加灵活地控制访问权限(如将其改为 protected 权限),引入访问声明,其语法是:在派生类中想要将基类的成员改到目标的访问域中加上声明:
基类::成员;
- 访问声明仅调整名字的访问权限;
- 访问声明不允许在派生类中降低或升高可访问性,如上面的不能改为 public 和 private;
- 基类的私有成员在派生类根本不可见,也不能用于访问声明;
- 对基类重载函数名的访问声明会调整基类所有同名函数的访问域;但是基类的重载函数不在同一访问域不能进行访问声明,派生类和基类名字相同的成员不可调整访问权限。
在 C++ 11 中,访问声明已经被弃用。
重名成员
- 在派生类访问和基类同名的成员时,屏蔽基类成员;
- 使用作用域符显式指定基类成员进行访问。
- 在派生类内部访问基类同名成员使用
类名::成员属性
类名::成员函数(参数表)
在外部访问基类同名成员使用
派生类对象.类名::成员属性
派生类对象.类名::成员函数(参数表)
- 基类的成员的作用域从说明开始一直延伸到它的所有派生类,尽管基类的同名成员在派生类中被屏蔽,但是它们的作用域一直有效。
下面是一个例子:
#include<iostream>
using namespace std;
class Base{
public:
int b;
Base(){b = 2;c = 3;}
protected:
int c;
void show(string msg){cout<<msg<<endl;}
};
// 私有继承
class Detrived:public Base{
public:
int b;
int c;
Detrived(){b = 12;c = 13;}
void show(string msg){
cout<<"Base.show:";
Base::show(msg);
cout<<"Detrived.show:"<<msg<<endl;
}
void print(){
cout<<"b = "<<b<<",c = "<<c<<endl;
cout<<"Base.b = "<<Base::b<<",Base.c = "<<Base::c<<endl;
}
};
int main(){
Detrived d;
d.show("hello");
d.print();
cout<<"Base.b = "<<d.Base::b<<endl;
}
结果:
Base.show:hello
Detrived.show:hello
b = 12,c = 13
Base.b = 2,Base.c = 3
Base.b = 2
继承中的静态成员
- 基类的静态成员在整个类体系中共享;
- 基类的静态成员也是可以被派生类同名的静态成员覆盖的。其访问和普通成员变量是一致的。
#include<iostream>
using namespace std;
class Base{
public:
int b;
static int i;
Base(){b = 2;c = 3;}
protected:
int c;
void show(string msg){cout<<msg<<endl;}
};
int Base::i = 100;
// 私有继承
class Detrived:public Base{
public:
int b;
int c;
static int i;
Detrived(){b = 12;c = 13;}
void show(string msg){
cout<<"Base.show:";
Base::show(msg);
cout<<"Detrived.show:"<<msg<<endl;
}
void print(){
cout<<"b = "<<b<<",c = "<<c<<endl;
cout<<"Base.b = "<<Base::b<<",Base.c = "<<Base::c<<endl;
}
};
int Detrived::i = 200;
int main(){
Detrived d;
cout<<d.i<<endl;
cout<<Detrived::i<<endl;
cout<<d.Base::i<<endl;
cout<<Base::i<<endl;
}
结果:
200
200
100
100
虚继承
背景
C++ 允许多继承,如果有下面的继承关系:
类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A–>B–>D 这条路径,另一份来自 A–>C–>D 这条路径。在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B–>D 这条路径,还是来自 A–>C–>D 这条路径,需要使用作用域符显式指定。
#include<iostream>
using namespace std;
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: public A{
protected:
int m_b;
};
//直接基类C
class C: public A{
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名冲突
void seta(int a){ B::m_a = a; } //正确,指定 m_a 是谁的
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
定义
在继承方式前面加上 virtual 关键字就是虚继承:
class D : virtual 继承方式 B{
};
- 这种继承关系叫作虚继承;
- B 叫作 D 的虚基类。
虚继承下的构造顺序
- 不论声明继承的顺序如何,总是先创建直接基类的虚基类的对象,即使一些不是这些虚拟类的派生类的类写在继承顺序最前面;
- 首先如果直接基类是虚继承,先创建它们的虚基类的对象(定义属性,执行构造方法),创建所有虚基类的对象。
- 然后按照继承声明顺序创建直接基类的对象,如果有多个,按照声明继承的顺序,而不是构造方法初始式的顺序,并且在它们的构造方法中忽略对虚基类的构造,即不再执行虚基类的构造函数;
- 创建派生类对象;
- 也就是说,在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数(可以不再构造函数初始式写出,但是要保证有默认构造函数)。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。虚继承下派生类可以调用间接基类的构造函数,普通继承不能。
#include <iostream>
using namespace std;
//虚基类A
class A{
public:
A(int a):m_a(a){cout<<"A build!"<<endl;}
protected:
int m_a;
};
class E{
public:
E(){cout<<"E build!"<<endl;}
};
//直接派生类B
class B: virtual public A{
public:
B(int a, int b);
public:
void display();
protected:
int m_b;
};
B::B(int a, int b): A(a), m_b(b){
cout<<"B build!"<<endl;
}
void B::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl;
}
//直接派生类C
class C: virtual public A{
public:
C(int a, int c);
public:
void display();
protected:
int m_c;
};
C::C(int a, int c): A(a), m_c(c){
cout<<"C build!"<<endl;
}
void C::display(){
cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl;
}
//间接派生类D
class D: public E, public B, public C{
public:
D(int a, int b, int c, int d);
public:
void display();
private:
int m_d;
};
D::D(int a, int b, int c, int d):E(), A(a), B(90, b), C(100, c), m_d(d){
cout<<"D build!"<<endl;
}
void D::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
}
int main(){
D d(1,2,3,4);
d.display();
}
结果:
A build! // 总是先构造虚基类
E build!
B build!
C build!
D build!
m_a=1, m_b=2, m_c=3, m_d=4