8-C++远征之继承篇-学习笔记
C++远征之继承篇
开篇介绍
整个C++远征计划: 起航->离港->封装->继承
- 为什么要用继承?
- 为什么要有继承?
- 如何来定义基类 <----> 派生类?
- 基类到派生类的三种继承关系: 公有继承,保护继承,私有继承
- IS-a & Has a
- 多重继承 & 多继承 & 虚继承(解决多继承中的一些问题)
为什么继承?
现实生活中: 儿子继承父亲财产
- 生活中继承 不等于 c++中的继承
为什么要有继承?
从下面的例子说起
人类:
class Person { public: void eat(); string m_strName; // 名字 int m_iAge; // 年龄 }
工人(人类的一种):
class Worker { public: void eat(); void work(); // 可以工作 string m_strName; int m_iAge; int m_iSalary; // 会发工资 }
代码重用,前提条件是发生关系的这两个类是超集子集的关系。
class Worker: public Person { public: void work(); int m_iSalary; }
此时工人类是人类的派生类。人类是工人类的基类。只需要写自己特有的。
这里有一个配对的概念,提到基类的时候,对应的是派生类。提到父类时,与之对应的是子类。
内存中的对象:
人类如果实例化一个对象,就有两个数据成员。虽然在工人类定义姓名年龄,但因为继承,工人类也包含姓名和年龄。
代码演示
要求:
- 实例化时先实例化子类,再实例化父类; 而析构函数正好相反。
- 子类已经同时具有父类的数据成员和成员函数
2-2-InheritWorkerPerson
Person.h:
#include <string> using namespace std; class Person { public: Person(); ~ Person(); void eat(); string m_strName; int m_iAge; };
Person.cpp:
#include "Person.h" #include <iostream> using namespace std; Person::Person() { cout << "Person()" << endl; } Person::~Person() { cout << "~Person()" << endl; } void Person::eat() { cout << "eat()" << endl; }
worker.h:
#include "Person.h" // 要求采用公有继承 class Worker:public Person { public: Worker(); ~Worker(); void work(); int m_iSalary; };
worker.cpp:
#include "Worker.h" #include <iostream> using namespace std; Worker::Worker() { cout << "Worker()" << endl; } Worker::~Worker() { cout << "~Worker()" << endl; } void Worker::work() { cout << "Work" << endl; }
main.cpp:
#include "Worker.h" #include <stdlib.h> #include <iostream> using namespace std; int main(void) { // 堆中申请内存 Worker *p = new Worker(); p->m_strName = "mtianyan"; p->m_iAge = 21; p->eat(); p->m_iSalary = 10000; p->work(); delete p; p = NULL; system("pause"); return 0; }
可以看到实例化worker的时候,先实例化出爸爸Person,然后生出儿子Worker。
销毁时,先销毁儿子Worker,再销毁掉了Person。
main.cpp
#include "Worker.h" #include <stdlib.h> #include <iostream> using namespace std; int main(void) { // 栈中申请内存 Worker worker; worker.m_strName = "Jim"; worker.m_iAge = 10; worker.eat(); worker.m_iSalary = 1000; worker.work(); system("pause"); return 0; }
栈中申请内存,也是先有爸爸,然后生儿子。 销毁时,儿子先走,爸爸后走。
C++继承方式
- 公有继承:
class A:public B
- 保护继承:
class A:protected B
- 私有继承:
class A:private B
公有继承
class Person { public: void eat(); string m_strName; int m_iAge; };
class Worker:public Person { public: // void eat(); void work(); // int m_iAge; // string m_strName; int m_iSalary; };
注释的是不用写,但是已经包含在Worker中。
int main(void) { Worker worker; worker.m_strName = "Merry"; worker.eat(); return 0; }
使用时,Worker可以调用父类的数据成员和成员函数。
Protected 和 private 不涉及继承时,他们两是一样的。
private和protected在继承时的区别:
- 子类通过protect继承了父类的时候,子类可以通过自己来访问父类的数据成员。
- 子类通过private继承了父类的时候,子类不能通过自己来访问父类的数据成员,父类的数据成员只有通过自己才能访问。
将刚才的例子进行修改:
class Person { public: void eat(); protected: int m_iAge; private: string m_strName; };
int main() { Person person; person.eat(); //Public, 必须正确 person.m_iAge = 20; //Protected,禁止访问 person.m_strName = "jim"; //private,禁止访问 return 0; }
但是实现成员函数时。我们的私有,保护数据成员也是可以正常访问的。
void Person::eat() { m_strName = "jim"; m_iAge = 10; }
但当protected遇到继承
class Person { public: void eat(); protected: string m_strName; int m_iAge; }; class Worker:public Person { public: // void eat(); void work(){m_iAge = 20;}; //继承下来之后可以通过work访问到age protected: // int m_iAge; // string m_strName; int m_iSalary; };
公有继承时,public 的继承到public。 protected的继承到protected。
class Person { public: void eat(); private: string m_strName; int m_iAge; }; class Worker:public Person { public: // void eat(); void work(){m_iAge = 20;}; private: int m_iSalary; };
public的继承到public。父类中的private被继承到了不可见位置。
而不是private部分。所以,此时通过work操作父类的m_iage会出错。
公有继承代码示例
要求:
3-2-PublicInheritWorkerPerson
初始化时代码与上次示例代码保持一致,下面开始进行修改。
- 将堆中实例化的对象改用栈中实例化方式。
main.cpp:
#include "Worker.h" #include <stdlib.h> #include <iostream> using namespace std; int main(void) { // 栈中申请内存 Worker worker; worker.m_strName = "Jim"; worker.m_iAge = 10; worker.eat(); worker.m_iSalary = 1000; worker.work(); system("pause"); return 0; }
可以看到运行结果没有发生变化,依然可以从子类调用父类的成员函数,数据成员。
- 实验二: 修改Person.h中数据成员
protected: string m_strName; private: int m_iAge;
Person.h:
#include <string> using namespace std; class Person { public: Person(); ~ Person(); void eat(); protected: string m_strName; private: int m_iAge; };
Person.cpp中修改eat方法,让其访问protected和private下的数据成员。
#include "Person.h" #include <iostream> using namespace std; Person::Person() { cout << "Person()" << endl; } Person::~Person() { cout << "~Person()" << endl; } void Person::eat() { m_strName = "mtianyan"; m_iAge = 21; cout << "eat()" << endl; }
main.cpp将Person.h引入。
#include <stdlib.h> #include <iostream> #include "Person.h" using namespace std; int main(void) { Person person; person.eat(); // 可以正常访问 // person.m_strName = "mtianyan666"; //无法访问 protected 成员 // person.m_iAge = 21; //无法访问 private 成员 system("pause"); return 0; }
类自己的public成员函数,函数内部可以访问到类自身的保护以及私有成员变量。
而类内受保护的私有成员,保护成员;无法在类外直接被访问。
公有继承之后父类的私有数据成员被子类继承到不可见位置,无法正常使用。
父类中的protected成员,子类也可以正常访问。被放在了子类的protected部分。
将m_strName,m_iAge放在protected下。
class Person { public: Person(); ~ Person(); void eat(); protected: string m_strName; int m_iAge; };
在子类中访问父类的protected数据成员。
void Worker::work() { m_iAge = 21; m_strName = "mtianyan"; cout << "Work" << endl; }
int main(void) { Worker worker; worker.work(); system("pause"); return 0; }
结果:正常运行。
- 父类的private数据成员父类是否可以正常访问:不可以
将上面代码中person下的protected改为private
则会报错:
Person::m_iAge”: 无法访问 private 成员(在“Person”类中声明)
c++保护继承,私有继承
- 公有继承:
class A:public B
- 保护继承:
class A:protected B
- 私有继承:
class A:private B
Private继承过来,会将父类的public和protected都降级为子类的private
例子:
class Line{ public: Line(int x1,int y1, int x2,int y2); private: Coordinate m_coorA; //线段只能访问到m_coorA的公有数据成员和公有成员函数 Coordinate m_coorB; }
在私有继承中,子类也只能访问父类的公有数据成员和公有成员函数。
- 线段和坐标的关系: has-a(包含关系)
- 私有继承(包含)
因为子类对象包含父类: 只能访问父类当中公有数据成员和成员函数。
保护与私有继承代码演示
要求:
protected继承:
public成员可以一直protected继承下去,被继承后属于protected,不可以被对象直接访问;可以被类自身的成员函数访问。
protected成员可以一直protected继承下去,被继承后属于protected,不可以被对象直接访问;
private成员不能被protected继承。
3-4-ProtectedPrivateInherit
人类派生出军人,军人派生出步兵。
Person.h:
#include <string> using namespace std; class Person { public: Person(); void play(); protected: string m_strName; };
Person.cpp:
#include <iostream> #include "Person.h" using namespace std; Person::Person() { m_strName = "merry"; } void Person::play() { cout << "person - play" << endl; cout << m_strName << endl; }
Soldier.h:
#include "Person.h" class Soldier:public Person { public: Soldier(); void work(); private: int m_iAge; };
Soldier.cpp:
#include "Soldier.h" #include <iostream> using namespace std; Soldier::Soldier() { } void Soldier::work() { // 访问基类(人类)的m_strName m_strName = "JIm"; m_iAge = 20; cout << m_strName << endl; cout << m_iAge << endl; cout << "soldier - work" << endl; }
Infantry.h:
#include "Soldier.h" class Infantry:public Soldier { public: void attack(); };
Infantry.cpp:
#include <iostream> #include "Infantry.h" using namespace std; void Infantry::attack() { cout << "Infantry --attack" << endl; }
main.cpp:
#include "Soldier.h" #include <stdlib.h> #include <iostream> int main() { Soldier soldier; // work会间接的访问到基类person中的m_strname soldier.work(); // 进行了公有继承,父类的protected数据成员会被继承到子类的protected下面 // 父类的public也会继承到子类的public下面 soldier.play();//调用父类的成员函数 system("pause"); return 0; }
运行结果:
将soldier继承Person的方式改为protected继承:
class Soldier :protected Person { //略 };
如果slodier保护继承了person,那么person下的public和protected数据成员和成员函数都会被继承到solider的protect部分。
这意味着通过子类对象只能直接访问到自己的public下的成员函数与数据成员。
main.cpp:
int main() { Soldier soldier; soldier.work(); // work是自己public下的,正常访问 soldier.play(); // play继承过来被放在了protected,无法直接访问了。 system("pause"); return 0; }
报错:
错误 C2247 “Person::play”不可访问,因为“Soldier”使用“protected”从“Person”继承
因此此时无法直接访问继承过来的protected下的play(),只有两种可能,一种根本没有继承过来,,一种是继承过来无法访问。如何证明继承过来了呢?
此时通过一个孙子类(Infantry)来公共继承soldier则可以在它自身的成员函数attack()中访问到继承来的Person的数据成员以及成员函数。
其实这时候在儿子类(Soldier)中新增一个成员函数,只要能使用protected中的父类数据成员和成员函数也是可以证明的。
验证方法1:(步兵类中attack()调用Person下数据成员)
Infantry.cpp:
#include <iostream> #include "Infantry.h" using namespace std; void Infantry::attack() { m_strName = "Mtianyan"; cout << m_strName << endl; cout << "Infantry --attack" << endl; }
main.cpp:
#include <stdlib.h> #include <iostream> #include "Infantry.h" int main() { Infantry infantry; infantry.attack(); system("pause"); return 0; }
可以看到attack可以访问到继承过来的protected的数据成员。
验证方法2(不严谨),Soldier自身的成员函数来访问Person的Protected数据成员
因为继承到private,也可以被自身成员函数访问。
// Person.h protected: string m_strName; // Soldier.cpp(自身的work方法) void Soldier::work() { m_strName = "JIm"; cout << m_strName << endl; cout << "soldier - work" << endl; }
private继承方式实验
Solder.h:
class Soldier :private Person {};
Person中的public和protected都会被继承到Soldier的private下。
这时在Soldier中可以直接访问这些数据成员。但孙子就拿不到爸爸从爷爷那私有继承的数据成员了。
实验证明:
错误 C2248 “Person::m_strName”了: 无法访问 无法访问的 成员(在“Person”类中声明)
在孙子辈的步兵类中已经无法访问到爷爷的m_strName”了
mtianyan: B类从A类派生,那么B类中含有A类的所有数据成员(私有的也被继承过来了,只不过继承到了不可见位置,无法访问)
B类从A类公共派生,那么可以在B类中直接使用A的公共及保护限定符的数据成员,不能使用私有成员。
B类从A类公共派生,那么A类的私有成员函数不能被B类继承并使用。(会继承下来,但是不能使用,所以正确)
B类从A类私有派生,那么A类的公共成员函数成为B类的私有成员函数。
巩固练习
定义两个类,人类中含有数据成员姓名(m_strName)及成员函数eat()
士兵类从人类派生,含有数据成员编号(m_strCode)及成员函数attack()
在main函数通过对数据的访问,体会公有继承的语法特点。
#include <iostream> #include <stdlib.h> #include <string> using namespace std; /** * 定义人的类: Person * 数据成员姓名: m_strName * 成员函数: eat() */ class Person { public: string m_strName; void eat() { cout << "eat" << endl; } }; /** * 定义士兵类: Soldier * 士兵类公有继承人类: public * 数据成员编号: m_strCode * 成员函数: attack() */ class Soldier:public Person { public: string m_strCode; void attack() { cout << "fire!!!" << endl; } }; int main(void) { // 创建Soldier对象 Soldier soldier; // 给对象属性赋值 soldier.m_strName = "mtianyan"; soldier.m_strCode = "273"; // 打印对象属性值 cout << soldier.m_strName << endl; cout << soldier.m_strCode << endl; // 调用对象方法 soldier.attack(); soldier.eat(); return 0; }
c++隐藏
覆盖 <-> 隐藏,很容易混淆.
本次课程重点介绍隐藏。
什么是隐藏?
- 父子两代(子类公有继承父类),当都有同名函数。子类会将父类的函数隐藏。
表现: 实例化B类对象时,只能直接访问到b中的abc方法。
- 但是实际上父类的abc方法只是隐藏了起来,因为确实是被继承过来了,通过特殊手段还可以访问到。
同名数据成员和成员函数都具有隐藏性质。
父子关系 & 成员同名 & 隐藏
代码:
class Person { public: void play(); protected: string m_strName; }; // 父子关系 class Soldier:public Person { public: void play(); // 同名成员函数 void work(); protected: int m_iCode; }
调用示例代码:
int main() { Soldier soldier; soldier.play(); //调用到soldier自己的play soldier.Person::play(); //可以调用到父类人的play return 0; }
数据成员同名:
void Soldier::work() { code = 1234; Person::code = "5678";//访问到的是父类的数据成员 }
可以通过比较好的命名方法(m_strCode,m_iCode)是可以避免重名的。
隐藏编码实例(一)
4-2-HideMemberFunctionVariable
程序代码
Person.h
#include <string> using namespace std; class Person { public: Person(); void play(); protected: string m_strName; };
Person.cpp:
#include "Person.h" #include <iostream> using namespace std; Person::Person() { m_strName = "mtianyan"; } void Person::play() { cout << "person - play()" << endl; cout << m_strName << endl; }
Soldier.h
#include "Person.h" class Soldier:public Person { public: Soldier(); void play(); void work(); protected: };
Soldier.cpp
#include "Soldier.h" #include <iostream> using namespace std; Soldier::Soldier() {} void Soldier::play() { cout << "soldier - play()" << endl; } void Soldier::work() { cout << "soldier - work()" << endl; }
main.cpp:
#include <iostream> #include <stdlib.h> #include "Soldier.h" int main() { Soldier soldier; soldier.play(); soldier.work(); soldier.Person::play(); system("pause"); return 0; }
使用方法名直接调用的是soldier自己的。
-
""
在程序目录下找 -
<>
在程序默认库查找 - 右键打开文档可以打开 - 使用string类型必须
#include<string>
时,需要配套使用using namespace std;
才可正常使用string,否则会出错,也就是string类型也是在std命名空间的。 - 继承只有一个冒号。而指明类的方法有两个。
- 声明类的最后要加分号。
隐藏编码二
如果想要打印Person的。则需要soldier.Person::play();
可以调用到person下的play方法。
当前不仅同名而且参数相同。如果参数不同,还会隐藏吗?
是的即使参数不同,也不会形成重载,只是隐藏。
// 不修改 void Person::play() { cout << "person - play()" << endl; cout << m_strName << endl; } // Soldier.h添加参数 void play(int x); // Soldier.cpp中添加参数 void Soldier::play(int x) { cout << "soldier - play()" << endl; } // 调用时 int main() { Soldier soldier; soldier.play(7); // 调用Soldier类自己的有参play soldier.work(); soldier.play(); //虽然与父类的参数要求一致,但当前父类方法被隐藏,无法调用。 //soldier.Person::play(); system("pause"); return 0; }
报错:
error C2660: “Soldier::play”: 函数不接受 0 个参数
说明即使参数不同。父类和子类的同名函数也不形成重载。而是隐藏。
数据成员重名
给Soldier.h protected下添加一个参数m_strName,使得它拥有一个和Person同名的数据成员
#include "Person.h" class Soldier:public Person { public: Soldier(); void play(int x); void work(); protected: string m_strName; };
Soldier.cpp:
#include "Soldier.h" #include <iostream> using namespace std; Soldier::Soldier() { } // 添加两行打印 void Soldier::play(int x) { cout << m_strName << endl;//打印出的是soldier下的 cout << Person::m_strName << endl;//打印父类的 cout << "soldier - play()" << endl; } // 添加两行数据成员的访问 void Soldier::work() { m_strName = "solider"; //只能赋值给soldier下的m_strname Person::m_strName = "Person"; cout << "soldier - work()" << endl; }
父子之间的同名数据成员。work中直接访问,只能访问到soldier下的m_strname。
#include <iostream> #include <stdlib.h> #include "Soldier.h" int main() { Soldier soldier; soldier.work(); soldier.play(7); soldier.Person::play(); system("pause"); return 0; }
可以看到数据成员被隐藏。
isA
隐形眼镜也是眼镜 - Is-a
- 眼镜: 基类
- 隐形眼镜: 派生类
基于上面的结论,示例程序
int main() { Soldier s1; Person p1 = s1; //用s1区实例化p1.这样做在语法上是正确的。士兵也是人。 Person *p2 = &s1; //正确 s1 = p1; //人赋值给士兵,错误。 Soldier *s2 = &p1; //士兵指针指向人对象,错误 return 0; }
子类的对象可以赋值给父类。也可以用基类的指针指向派生类的对象。
将基类的指针或者引用作为函数参数来使它可以接收传入的子类对象,并且也可以传入基类的对象。
fun1和fun2都可以传入Person 或 Soldier的对象。
- fun1要求传入的参数是指针,所以传入
&p1
对象p1的地址。 而fun2要求传入的是对象p的引用, 所以可以直接传入。
- 基类指针指向派生类对象:
Person *p = &soldier;
派生类对象初始化基类对象:
Person p1 = soldier;
存储结构:
子类中有父类继承下来的数据成员.也有它自身的数据成员。
当通过子类初始化父类时,会将继承下来的数据成员复制。其他子类自有的截断丢弃。
父类指针指向子类对象,父类也只能访问到自己遗传下去的,无法访问到子类自有的。
Is-a 编码
4-5-SoldierIsAPerson
Person.h:
#include <string> using namespace std; class Person { public: Person(string name = "Person_jim"); virtual ~Person();// 虚析构函数,可继承。soldier内的也会是虚的。 void play(); protected: string m_strName; };
Person.cpp:
#include "Person.h" #include <iostream> #include <string> using namespace std; Person::Person(string name) { m_strName = name; cout << "person()" << endl; } Person::~Person() { cout << "~person()" << endl; } void Person::play() { cout << "person - play()" << endl; cout << m_strName << endl; }
Soldier.h:
#include "Person.h" #include <string> using namespace std; class Soldier:public Person { public: Soldier(string name = "Soldier_james",int age =20); virtual ~Soldier(); void work(); protected: string m_iAge; };
Soldier.cpp:
#include "Soldier.h" #include <iostream> using namespace std; Soldier::Soldier(string name,int age) { m_strName = name; m_iAge = age; cout << "Soldier()" << endl; } Soldier::~Soldier() { cout << "~Soldier()" << endl; } void Soldier::work() { cout << m_strName << endl; cout << m_iAge << endl; cout << "Soldier -- work" << endl; }
main.cpp:
#include <iostream> #include <stdlib.h> #include "Soldier.h" int main() { Soldier soldier; Person p = soldier; p.play(); system("pause"); return 0; }
可以看到此时调用Person的play(),打印出的m_strName是Solder的数据成员。
int main() { Soldier soldier; Person p; p.play(); system("pause"); return 0; }
因为Person是有默认构造函数的,因此此时打印出来的必定似乎Person中数据成员。
刚才我们是使用soldier来初始化p,现在我们尝试使用赋值方式。
int main() { Soldier soldier; Person p; p = soldier; p.play(); system("pause"); return 0; }
无论是初始化,还是赋值,都可以实现。下面我们试试指针方式。
int main() { Soldier soldier; Person *p; p = &soldier; p->play(); system("pause"); return 0; }
使用父类的指针,调用子类独有的函数
p->work();
会提示错误:
错误 C2039 “work”: 不是“Person”的成员
- 关于销毁时,析构函数是如何执行的。
int main() { Person *p = new Soldier; p->play(); delete p; p=NULL; system("pause"); return 0; }
可以看到:1. 实例化时先执行父类的构造,再执行子类的构造。 父类指针指向子类对象,访问到的是子类的数据成员。 2. 销毁时先执行子类析构函数,再执行父类析构函数。
执行结果如上图所示,是因为我们的Person.h中已经将析构函数添加了virtual
关键字
virtual ~Person();// 虚析构函数,可继承。soldier内的也会是虚的。
如果没有virtual,那么在销毁时会造成只执行了父类的析构函数,没有执行子类的。
新知识点: 虚析构函数
什么时候会用到:
虚析构函数是为了解决基类的指针指向堆中的派生类对象,希望使用基类的指针释放内存。
这个关键字是会被继承下去的,即使在Soldier中不写,也会是一个虚析构函数
is-A 编码二
4-6-SoldierIsAPerson2:
main.cpp:
#include <iostream> #include <stdlib.h> #include "Soldier.h" void test1(Person p) { p.play(); } void test2(Person &p) { p.play(); } void test3(Person *p) { p->play(); } int main() { Person p; Soldier s; test1(p); test1(s); system("pause"); return 0; }
运行结果前三行是实例化了Person和Soldier的输出。
test1中调用了传入的Person的play方法,打印处理Person_jim。
test1传入s,调用了Person的play方法,数据成员是Soldier的。
销毁了两次Person,是因为调用时,有一个临时的对象person
两次析构函数是在两个test1执行完之后自动执行的。因为此时传入一个临时变量p。用完就要销毁掉。
test2(p); test2(s);
运行结果:
因为传入的是引用。所以里面调用的仍是传入的对象本身。没有实例化临时变量。
test3(&p); test3(&s);
与test2实验结果完全一致。p分别调用基类和派生类的play
结论:test2 和 test3 不会产生新的临时变量,效率更高。
巩固练习
定义两个类,基类是人类,定义数据成员姓名(name),及成员函数void attack()。
士兵类从人类派生,定义与人类同名的数据成员姓名(name)和成员函数void attack()。
通过对同名数据成员及成员函数的访问理解成员隐藏的概念及访问数据的方法。
#include <iostream> #include <stdlib.h> #include <string> using namespace std; /** * 定义人类: Person * 数据成员: m_strName * 成员函数: attack() */ class Person { public: string m_strName; void attack() { cout << "attack" << endl; } }; /** * 定义士兵类: Soldier * 士兵类公有继承人类 * 数据成员: m_strName * 成员函数: attack() */ class Soldier:public Person { public: string m_strName; void attack() { cout << "fire!!!" << endl; } }; int main(void) { // 实例士兵对象 Soldier soldier; // 向士兵属性赋值"tomato" soldier.m_strName = "tomato"; // 通过士兵对象向人类属性赋值"Jim" soldier.Person::m_strName = "Jim"; // 打印士兵对象的属性值 cout << soldier.m_strName << endl; // 通过士兵对象打印人类属性值 cout << soldier.Person::m_strName << endl; // 调用士兵对象方法 soldier.attack(); // 通过士兵对象调用人类方法 soldier.Person::attack(); return 0; }
多继承与多重继承
多重继承:
上述关系具体到代码上可以这样写:
class Person { }; class Soldier: public Person { }; class Infantryman: public Soldier { };
多继承:
具体到代码层面,如下:
class Worker { }; class Farmer { }; class MigrantWorker: public Worker,public Farmer { };
多继承时中间要加逗号,并且要写清楚继承方式。
如果不写,那么系统默认为private继承
多重继承代码演示
Person -> Soldier -> Infantryman
- 孙子实例化时先实例化爷爷,然后实例化爸爸。最后才能实例化孙子。
- 孙子先死。然后爸爸死。最后爷爷死。
附录代码 5-2-Multi-Inherit:
Person.h
#include <string> using namespace std; class Person { public: Person(string name = "jim"); virtual ~Person();// 虚析构函数,可继承。soldier内的也会是虚的。 void play(); protected: string m_strName; };
Person.cpp
#include "Person.h" #include <iostream> #include <string> using namespace std; Person::Person(string name) { m_strName = name; cout << "person()" << endl; } Person::~Person() { cout << "~person()" << endl; } void Person::play() { cout << "person - play()" << endl; cout << m_strName << endl; }
Soldier.h:
#include "Person.h" #include <string> using namespace std; class Soldier :public Person { public: Soldier(string name = "james", int age = 20); virtual ~Soldier(); void work(); protected: string m_iAge; };
Soldier.cpp:
#include "Soldier.h" #include <iostream> using namespace std; Soldier::Soldier(string name, int age) { m_strName = name; m_iAge = age; cout << "Soldier()" << endl; } Soldier::~Soldier() { cout << "~Soldier()" << endl; } void Soldier::work() { cout << m_strName << endl; cout << m_iAge << endl; cout << "Soldier -- work" << endl; }
Infantry.h:
#include "Soldier.h" class Infantry:public Soldier { public: Infantry(string name = "jack", int age = 30); ~Infantry(); void attack(); };
Infantry.cpp
#include <iostream> #include "Infantry.h" using namespace std; Infantry::Infantry(string name /* = "jack" */, int age /* = 30 */) { m_strName = name; m_iAge = age; cout << "Infantry()" << endl; } Infantry::~Infantry() { cout << "~Infantry()" << endl; } void Infantry::attack() { cout << m_iAge << endl; cout << m_strName<< endl; cout << "Infantry --attack" << endl; }
main.cpp:
#include <iostream> #include <stdlib.h> #include "Infantry.h" void test1(Person p) { p.play(); } void test2(Person &p) { p.play(); } void test3(Person *p) { p->play(); } int main() { Infantry infantry; system("pause"); return 0; }
可以看到,如我们预期的,先生爷爷,再生爸爸,最后生儿子。
步兵 IsA
军人 IsA
人类
具有传递关系。
int main() { Infantry infantry; test1(infantry); test2(infantry); test3(&infantry); system("pause"); return 0; }
test1传入的是对象。所以会有临时生成的对象。然后销毁。
c++多继承
实例化两个父类的顺序与继承时冒号后顺序一致而与初始化列表顺序无关。
class MigrantWorker:public Worker, public Farmer
- 是按继承的声明顺序来构造超类的 不是按初始化列表的顺序
- 函数参数默认值最好在声明时设置而不是在定义时。是因为定义出现在调用后 导致编译其无法识别 然后报错
完整代码 5-3-Multi-TwoInherit:
Worker.h:
#include <string> using namespace std; class Worker { public: Worker(string code ="001"); virtual ~Worker(); void carry(); protected: string m_strCode; };
Worker.cpp
#include "Worker.h" #include <iostream> using namespace std; Worker::Worker(string code) { m_strCode = code; cout << "worker()" << endl; } Worker::~Worker() { cout << "~worker" << endl; } void Worker::carry() { cout << m_strCode << endl; cout << "worker -- carry()" << endl; }
Farmer.h
#include <string> using namespace std; class Farmer { public: Farmer(string name = "jack"); virtual ~Farmer(); void sow(); protected: string m_strName; };
Farmer.cpp
#include "Farmer.h" #include <iostream> using namespace std; Farmer::Farmer(string name) { m_strName = name; cout << "Farmer()" << endl; } Farmer::~Farmer() { cout << "~Farmer()" << endl; } void Farmer::sow() { cout << m_strName << endl; cout << "sow()" << endl; }
MigrantWorker.h
#include <string> #include "Farmer.h" #include "Worker.h" using namespace std; class MigrantWorker:public Worker, public Farmer//此处顺序决定实例化顺序。 { public: MigrantWorker(string name,string code); ~ MigrantWorker(); private: };
MigrantWorker.cpp
#include "MigrantWorker.h" #include <iostream> using namespace std; MigrantWorker::MigrantWorker(string name,string code): Farmer(name), Worker(code) { cout <<":MigrantWorker()" << endl; } MigrantWorker::~MigrantWorker() { cout << "~MigrantWorker()" << endl; }
重点代码:
MigrantWorker::MigrantWorker(string name,string code): Farmer(name), Worker(code)
将其中的name传给Farmer code传给Worker
main.cpp
#include <iostream> #include "MigrantWorker.h" #include <stdlib.h> int main() { // 堆方式实例化农民工对象 MigrantWorker *p = new MigrantWorker("jim","100"); p->carry(); // 工人成员函数 p->sow(); // 农民成员函数 delete p; p = NULL; system("pause"); return 0; }
- 可以用指向子类的指针p,调用他两个爸爸的方法。
- 实例化顺序与声明顺序一致,声明时先写的Worker。
- 销毁顺序与实例化正好相反
基类不需要定义虚析构函数,虚析构函数是在父类指针指向子类对象的时候使用的。 这里只是简单的实例化子类对象而已,销毁的时候会执行父类和子类的析构函数的
- 虚析构函数是为了解决基类的指针指向堆中的派生类对象,并用基类的指针删除派生类对象。
巩固练习
- 定义worker工人类及children儿童类
- worker类中定义数据成员m_strName姓名
- children类中定义成员m_iAge年龄
- 定义ChildLabourer童工类,公有继承工人类和儿童类
- 在main函数中通过new实例化ChildLabourer类的对象,并通过该对象调用worker及children类中的成员函数,最后销毁该对象,体会多继承的继承特性及构造函数及析构函数的执行顺序。
#include <iostream> #include <stdlib.h> #include <string> using namespace std; /** * 定义工人类: Worker * 数据成员: m_strName * 成员函数: work() */ class Worker { public: Worker(string name) { m_strName = name; cout << "Worker" << endl; } ~Worker() { cout << "~Worker" << endl; } void work() { cout << m_strName << endl; cout << "work" << endl; } protected: string m_strName; }; /** * 定义儿童类: Children * 数据成员: m_iAge * 成员函数: play() */ class Children { public: Children(int age) { m_iAge = age; cout << "Children" << endl; } ~Children() { cout << "~Children" << endl; } void play() { cout << m_iAge << endl; cout << "play" << endl; } protected: int m_iAge; }; /** * 定义童工类: ChildLabourer * 公有继承工人类和儿童类 */ class ChildLabourer : public Children, public Worker { public: ChildLabourer(string name, int age):Worker(name),Children(age) { cout << "ChildLabourer" << endl; } ~ChildLabourer() { cout << "~ChildLabourer" << endl; } }; int main(void) { // 使用new关键字创建童工类对象 ChildLabourer *p = new ChildLabourer("jim",12); // 通过童工对象调用父类的work()和play()方法 p-> work(); p->play(); // 释放 delete p; p = NULL; return 0; }
c++虚继承(理论)
多继承 & 多重继承的烦恼
- 多继承和多重继承会出现问题呢?
菱形继承中既有多继承也有多重继承。D中将含有两个完全一样的A的数据。
如图,假设类a是父类,b类和c类都继承了a类,而d类又继承了b和c,那么由于d类进行了两次多重继承a类,就会出现两份相同的a的数据成员或成员函数,就会出现代码冗余。
人 ->
农民 | 工人->
农民工
如何避免该情况的发生,就可以使用虚继承
虚继承是一种继承方式,关键字是virtual
class Worker: virtual public Person // 等价于 public virtual Person {}; class Farmer: virtual public Person // 等价于 public virtual Person {}; class MigrantWorker: public Worker,public Farmer {};
使用虚继承。那么农民工类将只有一份继承到的Person成员。
虚继承编码(一)
我们在Farmer中和worker中都引入了Person.h,当MigrantWorker继承自他们两。会引入两个Person的数据成员等。
- 通过宏定义解决重定义:
我们在被公共继承的类中应该这样写:
#ifndef PERSON_H//假如没有定义 #define PERSON_H//定义 //代码 #endif //结束符
附录完整代码: 6-2-VirtualInherit
Person.h:
#ifndef PERSON_H//假如没有定义 #define PERSON_H//定义 #include <string> using namespace std; class Person { public: Person(string color = "blue"); virtual ~Person(); void printColor(); protected: string m_strColor; }; #endif //结束符
Person.cpp:
#include <iostream> #include "Person.h" using namespace std; Person::Person(string color) { m_strColor = color; cout << "person()" << endl; } Person::~Person() { cout << "~Person()" << endl; } void Person::printColor() { cout << m_strColor << endl; cout << "Person -- printColor" << endl; }
Worker.h
#include <string> using namespace std; #include "Person.h" class Worker:public Person { public: Worker(string code ="001",string color ="red"); // 希望worker可以传入肤色给person virtual ~Worker(); void carry(); protected: string m_strCode; };
Worker.cpp
#include "Worker.h" #include <iostream> using namespace std; Worker::Worker(string code,string color):Person(color) { m_strCode = code; cout << "worker()" << endl; } Worker::~Worker() { cout << "~worker" << endl; } void Worker::carry() { cout << m_strCode << endl; cout << "worker -- carry()" << endl; }
Farmer.h
#include <string> using namespace std; #include "Person.h" class Farmer:public Person { public: Farmer(string name = "jack",string color = "blue"); virtual ~Farmer(); void sow(); protected: string m_strName; };
Farmer.cpp
#include "Farmer.h" #include <iostream> using namespace std; Farmer::Farmer(string name,string color):Person(color) { m_strName = name; cout << "Farmer()" << endl; } Farmer::~Farmer() { cout << "~Farmer()" << endl; } void Farmer::sow() { cout << m_strName << endl; cout << "sow()" << endl; }
MigrantWorker.h
#include <string> #include "Farmer.h" #include "Worker.h" using namespace std; class MigrantWorker:public Worker, public Farmer//此处顺序决定实例化顺序。 { public: MigrantWorker(string name,string code,string color); ~ MigrantWorker(); private: };
MigrantWorker.cpp
#include "MigrantWorker.h" #include <iostream> using namespace std; MigrantWorker::MigrantWorker(string name,string code,string color): Farmer(name,color), Worker(code,color) { cout <<":MigrantWorker()" << endl; } MigrantWorker::~MigrantWorker() { cout << "~MigrantWorker()" << endl; }
main.cpp
#include <iostream> #include "MigrantWorker.h" #include <stdlib.h> int main() { system("pause"); return 0; }
如果不加宏定义会报错,Person类被重定义了。
错误 C2011 “Person”:“class”类型重定义
因为我们在Worker和Farmer中都引入了Person.h,这是正常的,但是当MigrantWorker类继承上面两个类,就会引入两遍Person。
公共继承的类需要写上,不是公共继承的也最好写上,因为未来可能会被重定义。
推荐写文件的全称大写,但是其实这个是自定义的,只要能区分开其他文件就可以。
可以正常通过编译说明我们用宏定义成功的解决了菱形问题的重定义报错。
c++虚继承(编码二)
与上小节中其他代码相同。
main.cpp:
int main() { MigrantWorker *p = new MigrantWorker("merry", "200", "yellow"); delete p; p = NULL; system("pause"); return 0; }
可以看到Person的构造函数执行了两遍。
现在MigrantWorker存在两份Person的成员,下面我们来证明。
- 修改Worker.cpp和Farmer.cpp:
Worker::Worker(string code,string color):Person("Worker"+color) Farmer::Farmer(string name,string color):Person("Farmer"+color)
- 通过农民工的指针打印这两份值。
main.cpp
int main() { MigrantWorker *p = new MigrantWorker("merry", "200", "yellow"); p->Farmer::printColor(); p->Worker::printColor(); p = NULL; system("pause"); return 0; }
可以看到在农民工对象中确实是有两份数据成员color的。
使用虚继承,让农民工只有一份。
Worker.h & Farmer.h中修改
class Worker: virtual public Person//work是虚基类。 {}; class Farmer: virtual public Person {};
可以看到Person的构造函数只被执行了一次。blue的原因是,既然两个儿子不知道哪个对孙子好,爷爷隔代亲传。
巩固练习
- 定义Person人类,worker工人类及children儿童类,
- worker类中定义数据成员m_strName姓名,
- children类中定义成员m_iAge年龄,
- worker类及children类均虚公有继承Person类,
- 定义ChildLabourer童工类,公有继承工人类和儿童类,从而形成菱形继承关系
- 在main函数中通过new实例化ChildLabourer类的对象,并通过该对象调用Person,Worker及Children类中的成员函数,最后销毁该对象,掌握多重继承,多继承,虚继承的定义方法。
#include <iostream> #include <stdlib.h> #include <string> using namespace std; /** * 定义人类: Person */ class Person { public: Person() { cout << "Person" << endl; } ~Person() { cout << "~Person" << endl; } void eat() { cout << "eat" << endl; } }; /** * 定义工人类: Worker * 虚继承人类 */ class Worker : virtual public Person { public: Worker(string name) { m_strName = name; cout << "Worker" << endl; } ~Worker() { cout << "~Worker" << endl; } void work() { cout << m_strName << endl; cout << "work" << endl; } protected: string m_strName; }; /** * 定义儿童类:Children * 虚继承人类 */ class Children : virtual public Person { public: Children(int age) { m_iAge = age; cout << "Children" << endl; } ~Children() { cout << "~Children" << endl; } void play() { cout << m_iAge << endl; cout << "play" << endl; } protected: int m_iAge; }; /** * 定义童工类:ChildLabourer * 公有继承工人类和儿童类 */ class ChildLabourer: public Children,public Worker { public: ChildLabourer(string name, int age):Worker(name),Children(age) { cout << "ChildLabourer" << endl; } ~ChildLabourer() { cout << "~ChildLabourer" << endl; } }; int main(void) { // 用new关键字实例化童工类对象 ChildLabourer *p = new ChildLabourer("11",12); // 调用童工类对象各方法。 p->eat(); p->work(); p->play(); delete p; p = NULL; return 0; }
运行结果:
可以看到多继承中实例化顺序,先声明的哪个先实例化哪个,与初始化列表顺序无关。
上一篇: 怎样用PHP实现文件上传
下一篇: 社群运营怎么做?这些点需要注意