C++远征之继承篇
开篇介绍
整个C++远征计划: 起航->离港->封装->继承
要求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;
}
公有继承
公有继承代码示例
要求:
要求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;
}
公有继承
保护继承
私有继承
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;
}
运行结果:
隐藏-
父子两代(子类公有继承父类),当都有同名函数。子类会将父类的函数隐藏。
-
表现: 实例化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;
}
工人士兵都是人的对象
基于上面的结论,示例程序
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;
}
要求
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;
}
两次析构函数
两次析构函数是在两个test1执行完之后自动执行的。因为此时传入一个临时变量p。用完就要销毁掉。
test2(p);
test2(s);
运行结果:
test2:引用中间没有中间变量的销毁因为传入的是引用。所以里面调用的仍是传入的对象本身。没有实例化临时变量。
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;
}
多重继承
多重继承-Is-a关系
上述关系具体到代码上可以这样写:
class Person
{
};
class Soldier: public Person
{
};
class Infantryman: public Soldier
{
};
多继承:
多继承-一个儿子有两个爸爸 多继承-isa-但是农民和工人没什么关系具体到代码层面,如下:
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;
}
多继承的要求
实例化两个父类的顺序与继承时冒号后顺序一致而与初始化列表顺序无关。
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;
}
多继承与多重继续的钻石问题(菱形继承)
钻石菱形继承中既有多继承也有多重继承。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;
}
mark
可以看到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;
}
mark
可以看到在农民工对象中确实是有两份数据成员color的。
使用虚继承,让农民工只有一份。
Worker.h & Farmer.h中修改
class Worker: virtual public Person//work是虚基类。
{};
class Farmer: virtual public Person
{};
mark
可以看到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;
}
运行结果:
mark可以看到多继承中实例化顺序,先声明的哪个先实例化哪个,与初始化列表顺序无关。
网友评论