修改记录
2020/12/27 : 修改了一下排版,还有几处程序和测试结果不匹配的问题也改正了,删除了虚继承的空间布局,因为我准备再写一篇文章,都写到一起就太长了。
继承的概念
继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有类的基础上做一些修改。新类也叫子类 或 派生类 原来的类叫做 父类 或 基类。派生类是基类的具体化。
派生类对基类的3种继承方式
这里要注意一下,区分继承方式和派生类成员访问权限属性。前者会修改派生类成员访问权限属性,后者规定了成员被访问的作用域。
派生类成员访问权限属性了成员可以使用的范围
public权限可以在本类的内部和外部使用,类的内部调用,类的外部调用。protected权限在派生类中是可访问的,意思是在子类内部可以调用父类中protected属性的成员。但是此属性成员在外部不能被调用,比如在main函数中使用派生类对象调用父类的protected成员。private权限只能在本类的内部被访问。
继承方式会修改子类继承父类成员的属性
在上面的图中已经有说明,需要注意的是,这里的改变并不会修改父类中成员的访问权限属性,只是修改了父类成员在子类中的访问权限属性
public : 父类的成员的属性在子类中不变
protected : 父类的成员属性在子类中会变成 protected,但是private不会改变
private : 父类的成员属性在子类中会变成 private
举个例子
class Father
{
public: int a;
protected: int b;
private: int c;
};
class Son : private Father
{
public: int d;
protected: int e;
private: int f;
// int a private属性
// int b private属性
// int c 不能访问
};
class Son2 : public Father
{
public: int d;
protected: int e;
private: int f;
// int a public属性
// int b protected属性
// int c 不能访问
};
继承的种类
单继承 : 一个子类只继承一个父类。
多继承 : 一个子类同时继承多个父类。
多重继承 : 举例说明: A继承B(A是子类,B是父类),C继承A(A是父类,C是子类),祖孙三代。
虚继承 : 虚继承是多重继承中特有的概念。虚继承是为解决多重继承(钻石继承)的路径二义性。(下文有解释)。
类型兼容原则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、解析函数之外的所有成员。兼容规则中所指的替代包括以下情况:
(1)子类对象可以当作父类对象使用
(2)子类对象可以直接赋值给父类对象
(3)子类对象可以直接初始化父类对象
(4)父类指针可以直接指向子类对象
(5)父类引用可以直接引用子类对象
两层含义 可以把父类的指针和引用指向子类对象,子类对象可以初始化父类对象。
继承中的构造和析构顺序
#include <iostream>
using namespace std;
class father
{
public:
father(int m,int n): a(m),b(n) //另一种赋值的方法 初始化列表
{
cout<<"调用父类的构造函数"<<endl;
}
~father()
{
cout<<"调用父类的析构函数"<<endl;
}
private:
int a;
int b;
};
class son : public father
{
public:
//继承情况下构造子类要先构造父类,这时候用构造函数初始化列表
son(int m,int n,int z,int x): father(m,n),c(z),d(x)
{
cout<<"调用子类的构造函数"<<endl;
}
~son()
{
cout<<"调用子类的析构函数"<<endl;
}
protected:
private:
int c;
int d;
};
int main()
{
//先构造父类在构造子类
son(1,2,3,4);
//先析构子类在析构父类
return 0;
}
多继承中构造和析构的顺序
#include <iostream>
using namespace std;
class father1
{
public:
father1(int a)
{
this->a = a;
cout << "father1的构造函数" << endl;
}
~father1()
{
cout << "father1的析构函数" << endl;
}
int a;
};
class father2
{
public:
father2(int a)
{
this->a = a;
cout << "father2的构造函数" << endl;
}
~father2()
{
cout << "father2的析构函数" << endl;
}
int a;
};
class father3
{
public:
father3(int a)
{
this->a = a;
cout << "father3的构造函数" << endl;
}
~father3()
{
cout << "father3的析构函数" << endl;
}
int a;
};
//子类先构造父类,这里有三个父类,构造父类的顺序是根据下面继承的顺序
class son : public father3, public father2,public father1
{
public:
//构造父类的顺序 与 初始化列表的顺序没有关系
son(int a, int b, int c): father1(a),father2(b),father3(c)
{
cout << "son的构造函数" << endl;
}
~son()
{
cout << "son的析构函数" << endl;
}
};
int main()
{
son s1(1,2,3);
return 0;
}
测试结果如图:
继承和组合混搭下的构造和析构顺序
构造顺序: 先构造父类(父类还有爷爷类,就构造爷爷类),接着构造组合类,最后构造子类。
析构顺序: 和构造的相反
#include <iostream>
using namespace std;
class Object //爷爷类
{
public:
Object(int m, int n) : a(m), b(n)
{
cout << "进行Object的构造函数" << endl;
}
~Object()
{
cout << "进行Object的析构函数" << endl;
}
int a;
int b;
};
class father : public Object //这个父类继承了爷爷类
{
public:
father(int a, int b) : Object(2, 3)//为父类赋值
{
this->a = a;
this->b = b;
cout << "进行father的构造函数" << endl;
}
~father()
{
cout << "进行father的析构函数" << endl;
}
int a;
int b;
};
class son : public father // 子类继承了父类
{
public:
//初始化列表 这里直接给父类和爷爷类赋了固定值
son(int m, int n) : c(m), d(n), father(1, 2),f1(7,8), o1(9, 10)
{
cout << "进行son的构造函数" << endl;
}
~son()
{
cout << "进行son的析构函数" << endl;
}
int c;
int d;
father f1; //这里是组合类
Object o1; //这里是组合类
};
int main()
{
son S1(3, 4);
return 0;
}
测试结果如图
测试结果的解读
第一行和第二行是构造son类的父类father产生的,第三行和第四行是构造子类中的 father f1(构造father类就得先构造Object类),第五行是构造子类中的Object o1类(不需要再构造其他了),第六行是构造子类,析构顺序和构造顺序相反。
含有虚继承的构造和析构顺序
在下列程序中,本应该是先构造B类然后再构造B2类,但是子类C是虚继承B2类,从结果上看是优先构造虚基类,然后再构造普通基类。析构顺序和构造顺序是相反的。
#include <iostream>
using namespace std;
class B
{
public:
B() { cout << "基类构造函数" << endl; }
~B(){ cout << "基类的析构函数" << endl; }
int b;
int d;
};
class B2
{
public:
B2() { cout << "虚基类B2构造函数" << endl; }
~B2(){ cout << "虚基类B2的析构函数" << endl; }
};
class C : public B , virtual public B2 //如果都是虚继承,那么构造顺序还是按照这里顺序进行。
{
public:
C() { cout << "子类C构造函数" << endl; }
~C(){ cout << "子类C析构函数" << endl; }
int c;
};
int main()
{
C c1;
return 0;
}
测试结果
虚基类和普通基类初始化的不同
派生类构造调用了虚基类的构造函数之后,中间的类对虚基类构造函数的调用被忽略了,这也是初始化虚基类
和初始化非虚基类不同的地方。也就是说虚基类只会被最底层类调用一次。
虚基类的初始化
/*
A
B C
D
*/
#include <iostream>
using namespace std;
class A
{
public:
A(int a_) : a(a_)
{
cout << "虚基类构造函数" << endl;
}
int a;
};
class B : virtual public A
{
public:
B() :A(3)
{
cout << "父类B构造函数" << endl;
}
};
class C : virtual public A
{
public:
C() :A(4)
{
cout << "父类C构造函数" << endl;
}
};
class D :public C, public B
{
public:
D() : A(6)
{
}
};
int main()
{
D D1;
cout << "虚基类中 a = " << D1.C::A::a << endl;
cout << "虚基类中 a = " << D1.B::A::a << endl;
return 0;
}
测试结果
创建子类D的过程中,只调用了一次虚基类的构造函数,虽然两个父类也写了虚基类的构造函数(这里是必须要写的),但是被忽略了。所以从不同的路径访问a的结果都是一样的。
普通基类的初始化
这个在g++ 下编译不通过,但是vs2010下可以。
#include <iostream>
using namespace std;
class A
{
public:
A(int a_) : a(a_){ cout << "基类构造函数" << endl; }
int a;
};
class B: public A
{
public:
B():A(3) { cout << "父类B构造函数" << endl; }
};
class C : public A
{
public:
C():A(4) { cout << "父类C构造函数" << endl; }
};
class D:public C,public B
{
public:
D(){ }
};
int main()
{
D D1;
//这里这么写是为了避免多重继承的二义性,因为这么写完全指明了继承的路径,但是不建议这么使用,此处只是试验
cout << "D1.C::A::a = " <<D1.C::A::a << endl;
cout << "D1.B::A::a = " <<D1.B::A::a << endl;
return 0;
}
测试结果
创建子类D的过程中,调用了两次基类的构造函数,并且从不同路径上去访问a得到的值还不同(因为两个父类的赋值不同,实际上也没这么用的)。
继承中会存在的问题
同名二义性
父类和子类的成员函数和成员变量都重名了怎么办
#include <iostream>
using namespace std;
class father
{
public:
int a;
void _print()
{
cout << "I am father" << endl;
cout << "a =" << a << endl;
}
};
class son: public father
{
public:
int a;
void _print()
{
cout << "I am son" << endl;
cout << "a =" << a << endl;
}
};
int main()
{
son s1;
s1.a = 1; //子类调用重名的成员函数,默认是子类的
s1._print();
cout << endl;
father f1; //父类调用重名的成员函数,默认是父类的
f1.a = 2;
f1._print();
cout << endl;
s1.father::a = 100; //如果想通过子类来调用父类的成员函数,需要在变量前面增加域名符号
s1.father::_print();
cout << endl;
return 0;
}
路径二义性
什么是继承的路径二义性?
最基类B(爷爷类)被 b1(父类1)和 b2(父类2)继承 ,而C类(子类)又继承了 b1和b2,那么当C继承B类的时候,是从b1类继承的还是从b2类继承的?如果不做处理就这样继承,程序会报错。
解决方法 使用虚继承,b1和b2虚继承B,此时B叫做虚基类。
/*先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,
它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),
则只会打出指定类XXX的内存布局。近期的VS版本都支持这样配置。
B
b1 b2
C
*/
#include <iostream>
using namespace std;
class B
{
public:
B()
{
cout<<"B的构造函数"<<endl;
}
int b;
int d;
};
class b1 : virtual public B //需要加上virtual关键字 变成虚继承
{
public:
b1()
{
cout<<"b1的构造函数"<<endl;
}
int B1;
};
class b2 : virtual public B
{
public:
b2()
{
cout<<"b2的构造函数"<<endl;
}
int B2;
};
//C是从b1还是b2继承的B类的?
class C : public b1, public b2
{
public:
C()
{
cout<<"C的构造函数"<<endl;
}
int c;
};
int main()
{
B old;
cout << endl;
b1 f1;
b2 f2;
cout << endl;
C c1;
c1.B1 = 100; //父类的
c1.B2 = 200; //父类的
c1.c = 300; //子类的
c1.b = 400; //爷爷类的 只有访问公共基类的时候需要虚继承
return 0;
}
测试结果
可以看到构造C类对象时,基类只进行了一次构造,这就是通过虚继承实现的。