c++实验代码及学习笔记(九)
你好! 这是一个高程实验课的代码记录及学习笔记。我将记录一些重要的知识点、易错点。但是作为大学生,水平很低,敬请指点教导、优化代码。
本节课我们学习了类的继承和派生,单继承、多重继承、多继承与虚基类等知识。
这道题实际上就是对知识的直接考察,故本次笔记侧重于对知识的罗列。
参考文章:C++ 类的继承与派生
C++中的继承(1) 继承方式
首先我们来讲讲基本概念。
继承是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一。
简单的说,继承是指一个对象直接使用另一对象的属性和方法。
C++中的继承关系就好比现实生活中的父子关系,继承一笔财产比白手起家要容易得多,原始类称为基类,继承类称为派生类,它们是类似于父亲和儿子的关系,所以也分别叫父类和子类。而子类又可以当成父类,被另外的类继承。
派生类的定义格式如下:
class <派生类名>:[继承方式]<基类名1>,[继承方式]<基类名2>,...[继承方式]<基类名n>]
{
<派生类新增的数据成员和成员函数定义>
};
a. :后面的被称为基类名表,基类名表的构成: 访问控制+基类名;
b. 访问控制表示派生类对基类的继承方式,使用关键字:
public : 公有继承;
private: 私有继承;
protected: 保护继承;
a. 子类拥有父类除了父类构造和析构函数,所有的成员函数和成员变量;
b. 子类就是一种特殊的父类;
c. 子类对象可以当做父类的对象使用;
d. 子类可以拥有父类没有的方法和属性(如图一的绿色❤)。
派生类构造函数定义格式如下:
<派生类名>(<总形式参数表>):<基类名1>(<参数表1>),
<基类名2>(<参数表2>),[...,<基类名n>(<参数表n>),其他初始化项>]
{
[<派生类自身数据成员的初始化>]
}
说明:
(1)总形式表给出派生类构造函数中所有的形式参数,作为调用基类带参构造函数的实际参数以及初始化本类数据成员的参数;
(2)一般情况下,基类名后面的参数表中的实际参数来自前面派生类构造函数形式参数总表,当然也可能是与前面形式参数无关的常量;
(3)在多层次继承中,每一个派生类只需要负责向直接基类的构造函数提供参数;如果一个基类有多个派生类,则每个派生类都要负责向该积累的构造函数提供参数。
值得注意的是,与单一继承不同:在多继承中,派生类有多个平行的基类,这些处于同一层次的基类构造函数的调用顺序,取决于声明派生类时所指定的各个基类的顺序,而与派生类构造函数的成员初始化列表中调用基类构造函数的顺序无关。
由于遵循栈的数据结构,构造顺序是先基类后派生,而析构则正好相反,先派生后基类。
后面我们将在代码中验证这一点。
多重继承又叫多层继承,很好理解,就是上图的爷-父-孙的关系。
要与之区别的是多继承,多继承则类似于父+母-子的关系,一个派生有多个基类。
(还可以加上隔壁老王、老李…)(没听说过!
多继承固然有好处,但是如果这些基类又由同一个基类派生,则会产生二义性。
如2个父类派生一个子类,但两个父类中都有同名的成员函数。派生出的子类产生二义性问题,编译时会报错
error: request for member 'show' is ambiguous
解决方法:
(1)利用作用域限定符(::),用来限定子类调用的是哪个父类的show()函数
son.Parent_f::show();
(2)在类中定义同名成员,覆盖掉父类中的相关成员
(1)使用作用域限定符,指明访问的是哪一个基类的成员。
注意:不能是Grandpa作用域下限定,因为Son直接基类的基类才是Grandpa,纵使指明了访问Grandpa的成员的话,对于Son来说,还是模糊的,还是具有二义性。
(2)在类中定义同名成员,覆盖掉父类中的相关成员。
(3)虚继承、使用虚基类
教科书上面对C++虚基类的描述玄而又玄,名曰“共享继承”,名曰“各派生类的对象共享基类的的一个拷贝”,其实说白了就是解决多重多级继承造成的二义性问题。
虚基类的原理则需要用到内存存储方式来说明。
正常的基类与派生类,都是存放到一个栈中的,如图所示,派生类和派生类之间是不相通的。这样子类在继承父类的时候,就会懵逼(我究竟有几个爷爷?)
而虚基类,则是创建了一个新的内存空间,并且用原基类和原派生类的指针构建。这样,派生类就可以共享基类。父类对祖父类的继承方式改为虚继承,那么子类访问自己从祖父类那里继承过来的成员就不会有二义性问题了。
值得一提的是,派生类在构造时,构造顺序永远是虚基类在前。
#include
using namespace std;
/*继承关系
A C
B AC
BAC
B1 B2*
D
*/
class A
{
protected:
int _x;
int _y;
public:
std::string name;
public:
A()
{}
A(int x,int y):_y(y),_x(x)
{
cout << "construct A" << endl;
}
~A()
{
cout << "desconstruct A" << endl;
}
};
class B //单继承
:public A
{
public:
B()
{}
B(int x,int y):A(x,y)
{
cout << "construct B" << endl;
}
~B()
{
cout << "desconstruct B" << endl;
}
};
class C
{
public:
C()
{
cout << "construct C" << endl;
}
~C()
{
cout << "desconstruct C" << endl;
}
};
class AC
:public A, public C
{
public:
A a;
C c;
public:
AC() :C(), A(1,1),a(1,0)
{
cout << "construct AC" << endl;
}
~AC()
{
cout << "desconstruct AC" << endl;
}
};
class BAC
:public AC, public B
{
public:
BAC() :B(1,0), AC()
{
cout << "construct BAC" << endl;
}
~BAC()
{
cout << "desconstruct BAC" << endl;
}
};
class B1
{
public:
B1()
{
cout << "construct B1" << endl;
}
~B1()
{
cout << "desconstruct B1" << endl;
}
};
class B2
{
public:
B2()
{
cout << "construct B2" << endl;
}
~B2()
{
cout << "desconstruct B2" << endl;
}
};
class D
:public B1,virtual public B2
{
public:
D():B1(),B2()
{
cout << "construct D" << endl;
}
~D()
{
cout << "desconstruct D" << endl;
}
};
int main()
{
cout << "单继承:" << endl;
B b(1,2);
cout << "类组合:" << endl;
AC ac;
cout << "多继承:" << endl;
BAC bac;
//bac.name = "abc_name";
bac.AC::name = "ac_name";
cout << "虚基类:" << endl;
D d;
getchar();
return 0;
}
附一段相声,给大家听听,也方便大家理解
A:今天给大家介绍一下类的继承
B:您讲讲
A:这个继承呢,就像父子这样的关系
B:怎么说呢
A:爸爸叫父类,又叫基类,儿子就是子类,又叫派生类。
B:嗯
A:子类呢,有时候也能继续当父类,子子孙孙无穷尽也
B:是这么个说法
A:就拿这位老师,驴鞭,举例吧,德高望重……
B:诶?
A:啊不是,是鱼鞭
B:怎么还越说越小了?
A:甭管是啥吧,就好比驴老师
B:还没改口呢!
A:他父亲,京城八大铁帽子王——绿帽子王
B:我们家还有这段呢
A:这就不多讲了,就拿继承来说,老爷子好像是于老师的父类
B:就是,还好像
A:老爷子的数据成员啊、属性啊他都一个不落的继承了
B:我是挺像我爸爸的
A:比如为人谦逊,友善,
B:您捧了
A:当然也有不好的
B:啊?
A:就是那个抽烟、喝酒、免费围观
B:没听说过!
A:意思就是,除了父类的构造和析构函数,所有成员函数和变量子类都能继承
B:那构造和析构怎么回事呢
A:那东西,自个身上的呗!能都继承了啊
B:构造和析构得自己写
A:那我问问你,你知道多继承子类构造函数调用顺序跟什么有关吗?
B:这哪跟哪呀,多继承又是什么呀
A:这好理解,一个子类有好多个父类
B:哦?
A:就拿于老师来说,一个父类是他爸爸
B:我有种不祥的预感
A:还有一个父类是隔壁老王
B:嗨!您瞧瞧这是人话吗!
A:还可以加上隔壁老李、老张……
B:去你的吧!
-END-