
图一
我也不想啊,只是他真的太像用来写博客的东西了
实验一内容展示

图二 实验一的内容
接下来我们以此为目录展开这篇文章思考实验要我们探究什么
目录
3.公民civil类(虽然后面才看到要用people,但也差不多)
6.Doctorhelpteacher类(这翻译下饭了,但问题不大)
实验任务1.(继承与成员类型设定要求)
(我也不到为啥老师不起标题,只说任务,没有说要达到什么)
1. 声明一个基类Animal,有私有类成员变量age,构造其派生类dog,在其成员函数SetAge(int n)中直接给age赋值,会出现什么问题,把age改为公有成员变量,问题还存在吗。通过编程实现。
这里直接上代码:

图三 按要求调取基类私有成员的报错
原因解释:基类的私有成员在被基类公有继承时会直接抛掉,派生类中不含有这个成员

图四 没有成员权限的解决方式
其实还有另一种解决方法,把age改成protected就能继承到了,这里没有连续继承多种子类,本例中只使用了公有继承,面对更复杂的类的关系时可以考虑其他继承方式以及protected的灵活运用。
实验任务2.(基类和派生类构造函数与析构函数调用情况观察)
2. 声明一个基类BaseClass,有整型成员变量Number,构造其派生类DerivedClass,观察构造函数和析构函数的执行情况,用cout输出提示信息。
直接上代码:

图五 基类和派生类声明与定义

图六 测试用例和代码运行情况

图七 测试代码目的说明

图八 测试代码详细说明
这里也可以看出些许栈区和堆区的些许区别,这里使用“=”赋值一下看看构造不构造也是受到了深度搜索中复制构造函数,以及浅拷贝深拷贝的概念,实践一下,值得修改的是同样的数据cout了两次导致结果有些冗余,两部分验证之间打印分割线会更清晰一点。
实验任务3.(虚基类的使用背景)
(注意这里才只是虚基类,菱形继承问题的来源,具体使用在下一篇探究)
(我也没法啊,老师这几个实验明明是连着的,按内容给整分开了)
3. 声明一个车(vehicle)基类,具有MaxSpeed、Weight等成员变量,Run、Stop等成员函数,由此派生出自行车(bicycle)类、汽车(motorcar)类。自行车(bicycle)类有高度(Height)等属性,汽车(motorcar)类有座位数(SeatNum)等属性。从bicycle和motorcar派生出摩托车(motorcycle)类,在继承过程中,注意把vehicle设置为虚基类。如果不把vehicle设置为虚基类,会出现什么问题。通过编程说明。

图九 未使用虚基类方式的情况

图十 具体报错信息
看到这里,就会想,什么是ambiguous呢,怎么就模棱两可了呢?我们画图分析分析(真想学会还得看语法书,这里更多是帮助理解)

图十一 画图分析这种奇怪的继承
那么怎么帮助编辑器明确,他俩都是由vehicle来的,直接用,这样的想法呢,这时就需要虚基类。

图十二 加入关键字virtual采用虚继承
报错信息没有了,那怎么初步理解虚基类这种继承方式呢

图十三 对虚基类的初步认识
至此本实验的要求就达到了,至于具体实现虚基类有哪些要注意的地方,请看下一部分。
实验任务4.(较复杂类关系的声明实现)
4. 类图如图1所示,实现图中类之间的关系,注意虚基类的使用,重载相应的成员函数,并测试这些类。

图十四 要求的类图关系
(注意,教师的那个箭头应该指向研究生的导师)
这次就上代码:
#include <string>
#include <iostream>
using namespace std;
typedef enum {
MAN,
WOMAN,
Ambigous
}Sextype;
class Date {
private:
int Year;
int Month;
int Day;
public:
Date(int y = 2000, int m = 1, int d = 1) :Year(y), Month(m), Day(d) {
//如果这里不给出默认值,后面civil构造必须传入这个参数
}
~Date() {
}
void modify(int y = 2000, int m = 1, int d = 1) {
this->Year = y;
this->Month = m;
this->Day = d;
}
void formatPrint() {
cout << Year << "/";
if (Month < 10) {
cout << 0;
}
cout << Month << "/";
if (Day < 10) {
cout << 0;
}
cout << Day << "/"<<endl;
}
};
class civil {
public:
string name;
int num;
string identifiednumber;
Sextype sex;
Date birth;
public:
civil(string name="", int num=0, string identifiednumber="", Sextype sex=Ambigous) :name(name), num(num), identifiednumber(identifiednumber), sex(sex) {
}
~civil() {
}
void printinfo() {
cout << "name" << name << endl<<
"num" << num << endl<<
"identifiednumbed" << identifiednumber <<endl<<
"sex" << sex<<endl;
birth.formatPrint();
}
};
class Student
:virtual public civil
{
private:
string classnum;
public:
Student(string name="", int num=0, string identifiednumber="", Sextype sex=Ambigous) :civil(name, num, identifiednumber, sex) {
}
~Student() {
}
void YourMethodOrFunction() {
}
};
class Teacher
:virtual public civil
{
private:
string group;
public:
Teacher(string name, int num, string identifiednumber, Sextype sex) :civil(name, num, identifiednumber, sex) {
}
~Teacher() {
}
void YourMethodOrFunction() {
}
};
class Doctor
:virtual public Student
{
private:
string studydirection;
Teacher* master;
public:
Doctor(string name, int num, string identifiednumber, Sextype sex)
:civil(name, num, identifiednumber, sex),Student(name, num, identifiednumber, sex) {
}
~Doctor() {
}
};
class DoctorHelpTeacher
:virtual public Doctor,
virtual public Teacher
{
private:
string studydirection;
public:
// 注意这里的参数列表,需要包含所有需要的参数
DoctorHelpTeacher(string name, int num, string identifiednumber, Sextype sex)
:civil(name, num, identifiednumber, sex),
Doctor(name, num, identifiednumber, sex), // 调用 Doctor 的构造函数
Teacher(name, num, identifiednumber, sex)// 调用 Teacher 的构造函数
{
}
~DoctorHelpTeacher() {
}
void YourMethodOrFunction() {
}
};
下面分段说说每段代码的大致思路:
1.性别枚举
将性别用枚举和typedef定义成一种类型,自带类型检查,增强严谨性
2.日期类
构造函数设定初始时间为2000年1月1日,三个数据(年月日),对外隐藏,只能通过modify函数修改,规定了打印函数,进行显示。
3.公民civil类(虽然后面才看到要用people,但也差不多)
构造函数传入基本信息参数,规定打印信息函数,这里可以再写个修改信息的函数的,懒惰了。
4.student类和teacher类
以civil为虚基类派生,构造函数参数数量大于等于基类构造函数,保证基类函数调用能有足够参数(这里也要给出默认参数),同样的也应该写修改的函数,重载,多态都可以用。
5.doctor类
这里本来写法上直接写的虚基类公有继承student,因为他也是只能继承一次的“属性”,以后doctor也可能·与新的类别发生菱形继承,这里通过“身份”的含义选择使用虚基类,并非没有意义。(偏个人观点)
6.Doctorhelpteacher类(这翻译下饭了,但问题不大)
继承了doctor和teacher,菱形继承的最后一角。
接下来是一些细节上的东西
体会与理解
1.doctor类存储导师类对象的解决方式
doctor初始化时必须借助给出的方法将导师成员初始化,那怎么办呢?难道说导入两排参数,把他导师信息也给出来?如果这样做,就会发现他很可能不止两行,比如这个导师同时也是个doctor,难道要写一行又一行吗,这样的话我们就会发现,我们开始一定保证doctor的导师是可以有一个默认值的,我们可以给出teacher的默认构造函数,这样每个doctor在创建时都有了一个默认的导师对象——“虚空导师”(名字字符串也空,序号为0),我们只要给给出两种类型的相应构造函数,再写一个可以指定导师信息的成员函数即可,于是又会出现两个问题:
1助教类不能被接受为“成为一名研究生的导师”
2每次导师信息都是重新录入的,这个人甚至可以不存在
那么有什么办法呢,能够保持我虚空导师的特性呢?我们不妨从头来看,为什么我们要写一个教师类成员呢,因为类图,汉语叫“每个研究生配备一个导师”。可是导师也是人啊,怎么能被你一个人霸占呢?啊啊啊啊啊,我的导师可以是这个人,你的导师也可以是这个人,可以师出同门,对,当你想到“这个”,并且意识到存储的实际上不是导师的信息,而是指明一种“关系”,导师和学生的关系,这种关系应该用什么存储的,还有可以虚无的特点,没错,就是指针,存储的是"Teacher*"默认值就是NULL空指针。
优点与应用细节
这样的处理方式实际上应用了多态,当子类“助教”成为导师时,也能将其转化为基类指针,当成老师处理,对关系的存储采用指针,也极大的节省了存储空间,而不是存一个class对应的内存,此外,由于是指针,传入参数时,必须得是一个能取地址的内容,一定程度上保证了“确有其人”。
2.菱形继承构造函数写法问题
以上代码已经是相对完善的版本,开始的代码没有注意太多细节内容,比如子类构造函数对基类构造函数的调用,这里其实很有说道,刚开始构造函数多次报错,(事实上这里在dev没看懂这个报错,复制在vs上,才看懂意识到哪里出来问题)实际上一定要在子类分支上都把基类构造函数调用一下才行。(而且还必须放在最前面调用)
(这里批判一下,很多网上的例子都是class A,class B,Class C,构造函数内没有参数或参数很少,在菱形继承上暴露问题不明显)
那我们学一下怎么个事儿吧:
(把人家的关键信息提炼出来)
虚拟继承中,子类新增在最上面,顶层基类元素在最下面,且只有一份。
实现:子类多出字节(那4字节指向一块空间,那块空间存放的是顶层基类中的元素位置离虚基表指针所在的位置相差多少字节。我们把这个记录距离长短的量叫做偏移量。那块空间叫做偏移量表格或者虚基表,那4个字节叫做虚基表指针。)
(这个我们研讨课涉及了)更多的还是看人家专门介绍这个的博客吧。
如果有什么理解错误或者其他各种各样的错误请多指出,毕竟这是孩子要改到实验报告里。
引用
http://t.csdnimg.cn/LZQLa (菱形继承的一种典型错误)
http://t.csdnimg.cn/RYIwD (菱形继承写法规则)
还有课本也写了(虽然没有说明为什么这样做),这里不详细提及
866

被折叠的 条评论
为什么被折叠?



