二十一、继承
1.继承的概念:通过一种机制表达类型之间共性和特性的方式,利用已有的数据类型来定义新的数据类型,这种机制就是继承
人类:姓名、年龄、吃饭、睡觉
学生类:姓名、年龄、吃饭、睡觉、学号、学习
教师类:姓名、年龄、吃饭、睡觉、工资、讲课
-------------------------------------------
人类:姓名、年龄、吃饭、睡觉
学生类继承人类:学号、学习
教师类继承人类:工资、讲课
人类(基类/父类)
/ \
学生类 教师类(派生类/子类)
基类(父类)--派生--》子类(派生类)
子类(派生类)--继承--》基类(父类)
2 继承的语法
class 子类:继承方式 基类,...{
...
};
继承方式:
1)public,公有继承
2)protected,保护继承
3)private,私有继承
参考代码:
#include <iostream>
using namespace std;
//人类(基类)
class Human{
public:
Human(const string& name,int age)
:m_name(name),m_age(age),m_id(123){}
void eat(const string& food){
cout << "我在吃" << food << endl;
}
void sleep(int hour){
cout << "我睡了" << hour << "小时"
<< endl;
}
//保护成员可以类的内部和子类中访问
protected:
string m_name;
int m_age;
const int& getID(void)const{
return m_id;
}
private:
int m_id;
};
//学生类(人类派生的一个子类)
class Student:public Human{
public:
//Human(..):指明基类部分的初始化方式
Student(const string& name,int age,
int no):Human(name,age),m_no(no){}
void learn(const string& course){
cout << "我在学" << course << endl;
}
void who(void){
cout << "我叫" << m_name << ",今年"
<< m_age << "岁,学号是" << m_no
<< endl;
cout <<"身份证号是:"<<getID()<<endl;
}
private:
int m_no;
};
//教师类(人类派生的另一个子类)
class Teacher:public Human{
public:
Teacher(const string& name,int age,
int salary):Human(name,age),
m_salary(salary){}
void teach(const string& course){
cout << "我在教" << course << endl;
}
void who(void){
cout << "我叫" << m_name << ",今年"
<< m_age << "岁,工资是" <<
m_salary << endl;
}
private:
int m_salary;
};
int main(void)
{
Student s("悟空",30,10001);
cout << "size=" << sizeof(s) << endl;
s.who();
s.eat("桃子");
s.sleep(8);
s.learn("unix系统编程");
Teacher t("王建立",48,8800);
t.who();
t.eat("饺子");
t.sleep(7);
t.teach("unix系统编程");
//Student*-->Human*:向上造型
Human* ph = &s;//ok
ph->eat("面条");
ph->sleep(10);
//ph->learn("C++");
//Human*-->Student*:向下造型(合理)
Student* ps = static_cast<Student*>(ph);
ps->who();
Human h("老王",48);
//Human*-->Teacher*:向下造型(不合理)
Teacher* ps2=static_cast<Teacher*>(&h);
ps2->who();
return 0;
}
3.公有继承的特性(public)
1)子类对象会继承基类的属性和行为,通过子类对象可以访问基类中的成员,就如同是基类对象在访问它们一样
注:子类对象中包含基类的部分称为“基类子对象”
2)向上造型(upcast)//重点
将子类类型的指针或引用转换为基类类型的指针或引用,这种操作性缩小的类型转换,在编译器看来是安全的,可以隐式转换。
class A{};
class B:public A{};
class C:public A{};
...
void func(A* pa){}
B b;
func(&b);//向上造型
C c;
func(&c);//向上造型
/*
student* ---> Human*:向上造型
Human* ph=&s;
*/
3)向下造型(downcast)//了解
将基类类型的指针或引用转换为子类类型的指针或引用。这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转换,可以显示转换(推荐使用static_cast);
4)子类继承基类的成员
①在子类中,可以直接访问基类中的公有和保护成员,就如同他们是子类自己的成员一样
②基类中的私有成员也可以继承过来,但是会受到访问属性的限值,不能直接访问,但是可以让基类提供公有或保护的成员函数来间接访问。
5)子类隐藏基类的成员
①子类和基类定中定义同名的成员函数,因为作用域不同,不能构成重载关系,而是一种隐藏关系,这时通过子类对象将优先访问子类自己的成员,这时如果还希望访问到基类中被隐藏的成员,可以显示使用“类名::”来指明
②如果形成隐藏关系的成员是同名不同参的成员函数,也可以通过using声明,让他们在子类形成重载,通过重载匹配来解决
#include <iostream>
using namespace std;
class Base{
public:
void func(void){
cout << "Base::func(void)" << endl;
}
};
class Derived:public Base{
public:
void func(int i){
cout <<"Derived::func(int)"<< endl;
}
//将基类中func引入到当前子类作用域,让其
//在子类中形成重载
//using Base::func;
};
int main(void)
{
Derived d;
d.Base::func();
d.func(10);
return 0;
}
隐藏三大条件:
①不同作用域(一个派生类,一个基类)
②函数签名相同,基类不被virtual修饰
③函数名相同,参数或常属性不同,不受virtual影响
4.继承方式和访问属性
1)访问控制限定符:影响访问该类成员的位置
访问控制限定符 | 访问控制属性 | 内部访问 | 子类访问 | 外部访问 | 友元访问 |
---|---|---|---|---|---|
public | 公有成员 | ok | ok | ok | ok |
protected | 保护成员 | ok | ok | NO | ok |
private | 私有成员 | ok | NO | NO | ok |
2)继承方式:影响通过子类访问基类中成员的可访问性
基类中 | 在公有子类中变成 | 在保护子类中变成 | 在私有子类中变成 |
---|---|---|---|
公有成员 | 公有成员 | 保护成员 | 私有成员 |
保护成员 | 保护成员 | 保护成员 | 私有成员 |
私有成员 | 私有成员 | 私有成员 | 私有成员 |
注:在私有继承或保护继承中向上造型的语法不适用
参考代码:
#include <iostream>
using namespace std;
class A{
public:
int m_public;
protected:
int m_protected;
private:
int m_private;
};
class B:public A{};//公有继承
class C:protected A{};//保护继承
class D:private A{};//私有继承
class X:public B{
void func(void){
m_public = 123;//ok
m_protected = 123;//ok
//m_private = 123;//no
}
};
class Y:public C{
void func(void){
m_public = 123;//ok
m_protected = 123;//ok
//m_private = 123;//no
}
};
class Z:public D{
void func(void){
//m_public = 123;//no
//m_protected = 123;//no
//m_private = 123;//no
}
};
int main(void)
{
B b;
b.m_public = 123;//ok
//b.m_protected = 123;//no
//b.m_private = 123;//no
C c;
//c.m_public = 123;//no
//c.m_protected = 123;//no
//c.m_private = 123;//no
D d;
//d.m_public = 123;//no
//d.m_protected = 123;//no
//d.m_private = 123;//no
}
5.子类的构造函数
1)如果子类的构造函数没有显示指明基类部分(基类子对象)的初始化方式,那么编译器将会自动调用基类的无参构造函数来初始化基类子对象
2)如果希望基类子对象以有参的方式进行初始化,必须使用初始化表来显示指明
class Base{};
class Derived:public Base{
//Base(...):指明基类子对象初始化方式
Derived(...):Base(...){}
};
参考代码:
#include <iostream>
using namespace std;
class Member{
public:
Member(void):m_j(0){
cout << "Member(void)" << endl;
}
Member(int j):m_j(j){
cout << "Member(int)" << endl;
}
int m_j;
};
class Base{
public:
Base(void):m_i(0){
cout << "Base(void)" << endl;
}
Base(int i):m_i(i){
cout << "Base(int)" << endl;
}
int m_i;
};
class Derived:public Base{
public:
Derived(void){
cout << "Derived(void)" << endl;
}
//Base(..):指明基类子对象的初始化方式
//m_mem(..):指明成员子对象的初始化方式
Derived(int i,int j):Base(i),m_mem(j){
cout << "Derived(int)" << endl;
}
Member m_mem;//成员子对象
};
int main(void)
{
Derived d;
cout << d.m_i << ',' << d.m_mem.m_j
<< endl;//0,0
Derived d2(123,321);
cout << d2.m_i << ',' << d2.m_mem.m_j
<< endl;//123,321
}
3)子类对象的创建过程
①分配内存
②构造基类子对象(按继承的顺序)
③构造成员子对象(按声明的顺序)
④执行子类构造函数
6.子类析构函数
1)子类的析构函数,无论是自定义的还是编译器缺省提供的,都会自动调用基类的析构函数,完成基类子对象的销毁
2)子类对象的销毁过程
①执行子类析构函数代码
②析构成员子对象(按声明的逆序)
③析构基类子对象(按继承的逆序)
④释放内存
参考代码:
#include <iostream>
using namespace std;
class Member{
public:
Member(void):m_j(0){
cout << "Member(void)" << endl;
}
Member(int j):m_j(j){
cout << "Member(int)" << endl;
}
~Member(void){
cout << "~Member(void)" << endl;
}
int m_j;
};
class Base{
public:
Base(void):m_i(0){
cout << "Base(void)" << endl;
}
Base(int i):m_i(i){
cout << "Base(int)" << endl;
}
~Base(void){
cout << "~Base(void)" << endl;
}
int m_i;
};
class Derived:public Base{
public:
Derived(void){
cout << "Derived(void)" << endl;
}
//Base(..):指明基类子对象的初始化方式
//m_mem(..):指明成员子对象的初始化方式
Derived(int i,int j):Base(i),m_mem(j){
cout << "Derived(int)" << endl;
}
~Derived(void){
cout << "~Derived(void)" << endl;
}
Member m_mem;//成员子对象
};
int main(void)
{
/*
Derived d;
cout << d.m_i << ',' << d.m_mem.m_j
<< endl;//0,0
Derived d2(123,321);
cout << d2.m_i << ',' << d2.m_mem.m_j
<< endl;//123,321*/
Base* pb = new Derived;
//...
//pb->析构函数
delete pb;//内存泄露
}
3)基类的析构函数不会自动调用子类的析构函数,如果delete一个指向子类的基类指针,实际被调用的仅是基类的析构函数,子类的析构函数不会被执行,有内存泄露的风险。
解决方法:虚析构函数
class Base{...};
class Derived:public Base{...};
Base* pb = new Derived; //pb指向子类对象的基类指针
delete pb; //有内存泄露的风险
7.子类的拷贝构造和拷贝赋值
1)子类拷贝构造
①如果自己没有定义拷贝构造函数,那么编译器会提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成基类子对象的初始化
②如果定义了拷贝构造函数,那么编译器不再提供缺省的拷贝构造函数,这时需要使用初始化表,显式指明基类子对象也要以拷贝方式进行初始化
class Base{...};
class Derived:public Base{
// Base(that):显式指明基类子对象拷贝方式初始化
Derived(const Derived& that):Base(that){...}
};
2)子类的拷贝赋值
①如果子类没有定义拷贝赋值函数,编译器会为子类提供缺省的拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的复制
②如果自己写了拷贝赋值函数,编译器不会为子类提供缺省的拷贝赋值函数,这是需要显式的调用基类的拷贝赋值函数,完成基类子对象的复制
参考代码:
#include <iostream>
using namespace std;
class Base{
public:
Base(int i=0):m_i(i){}
Base(const Base& that):m_i(that.m_i){
cout << "Base的拷贝构造" << endl;
}
Base& operator=(const Base& that){
cout << "Base的拷贝赋值" << endl;
if(&that != this)
{
m_i = that.m_i;
}
return *this;
}
int m_i;
};
class Derived:public Base{
public:
Derived(int i1=0,int i2=0):Base(i1),m_i(i2){}
//Base(that):显示指明基类子对象以拷贝方式进行初始化
Derived(const Derived& that):m_i(that.m_i),Base(that){}
Dericed& operator=(const Derived& taht)
{
if(&that != this)
{
//显示调用基类的拷贝赋值函数,完成基类子对象的赋值
Base::operator=(that);
m_i = that.m_i;
}
return *this;
}
int m_i;
};
int main()
{
Derived d1(10,20);
Derived d2=d1; //拷贝构造
cout << d1.Base::m_i << "," << d1.m_i << endl;//10,20
cout << d2.Base::m_i << ',' << d2.m_i << endl;//10,20
Derived d3;
//d3.operator=(d1)
d3 = d1;//拷贝赋值
cout << d3.Base::m_i << ',' << d3.m_i << endl;//10,20
return 0;
}
8.多重继承
1)概念:一个子类可以同时继承多个基类,这样的继承方式称为多重继承
#include <iostream>
using namespace std;
//电话基类
class Phone{
public:
Phone(const string& num):m_num(num){}
void call(const string& num){
cout << m_num << "打给" << num <<
endl;
}
private:
string m_num;
};
//播放器基类
class Player{
public:
Player(const string& media)
:m_media(media){}
void play(const string& music){
cout << m_media << "播放音乐" <<
music << endl;
}
private:
string m_media;
};
//计算机基类
class Computer{
public:
Computer(const string& os):m_os(os){}
void run(const string& app){
cout << "在" << m_os << "系统上运行"
<< app << endl;
}
private:
string m_os;
};
//智能手机子类
class SmartPhone:public Phone,
public Player,public Computer{
public:
SmartPhone(const string& num,
const string& media,const string& os)
:Phone(num),Player(media),
Computer(os){}
};
int main(void)
{
SmartPhone iphoneXs(
"13688886666","MP4","Android");
iphoneXs.call("12315");
iphoneXs.play("最炫小苹果");
iphoneXs.run("Angry Bird");
SmartPhone* p1 = &iphoneXs;
Phone* p2 = p1;
Player* p3 = p1;
Computer* p4 = p1;
cout << "p1=" << p1 << endl;
cout << "p2=" << p2 << endl;
cout << "p3=" << p3 << endl;
cout << "p4=" << p4 << endl;
return 0;
}
2)向上造型时,编译器会根据基类子对象的内部布局,进行适当的偏移计算,保证指针的类型和其所指向的目标基类子对象类型一致
3)名字冲突问题:
①如果子类的多个基类存在相同的名字,当通过子类访问这些名字时,编译器会报歧义错误——名字冲突
②解决名字冲突问题的常规做法就是显式使用“类名::”说明所访问的名字属于哪个基类//常规做法
③如果产生名字冲突的是成员函数,并满足不同参的重载条件,也可以通过using声明,让他们在子类中形成重载,通过重载匹配来解决。//非常规做法
#include <iostream>
using namespace std;
class Base1{
public:
void func(void){
cout << "Base1::func" << endl;
}
int m_i;
};
class Base2{
public:
void func(int i){
cout << "Base2::func" << endl;
}
typedef int m_i;
};
class Derived:public Base1,public Base2{
public:
//通过using声明,让它们在子类中形成重载
//using Base1::func;
//using Base2::func;
};
int main(void)
{
Derived d;
d.Base1::func();
d.Base2::func(123);
d.Base1::m_i = 123;
cout << d.Base1::m_i << endl;//123
//int i = 100;
Derived::Base2::m_i i = 100;
cout << i << endl;//100
return 0;
}
9.多重继承——钻石继承
1)概念:一个子类的多个继承源自共同的基类祖先,,这样的继承结构称为钻石继承。
参考代码:
/* 钻石继承
* A
* / \
* B C
* \ /
* D
* */
#include <iostream>
using namespace std;
class A{
public:
A(int data):m_data(data){
cout << "A:" << this << "," <<
sizeof(A) << endl;
}
protected:
int m_data;
};
class B:public A{
public:
B(int data):A(data){
cout << "B:" << this << "," <<
sizeof(B) << endl;
}
void set(int data){
m_data = data;
}
};
class C:public A{
public:
C(int data):A(data){
cout << "C:" << this << "," <<
sizeof(C) << endl;
}
int get(void){
return m_data;
}
};
class D:public B,public C{
public:
D(int data):B(data),C(data){
cout << "D:" << this << "," <<
sizeof(D) << endl;
}
};
int main(void)
{
D d(100);
cout << sizeof(d) << endl;//8
cout << d.get() << endl;//100
d.set(200);
cout << d.get() << endl;//200?100
return 0;
}
2)在创建末端子类对象时,公共基类(A)子对象会存在多个实例,在通过末端子类对象访问公共基类中的成员,会因为继承路径不同,导致结果不一致。
3)通过虚继承可以让公共基类子对象实例唯一,并可以为所有的中间类共享,这样即使沿着不同的继承路径,所访问到的公共基类中的成员一定是一致的。
参考代码:
/* 钻石继承(虚继承)
* A
* / \
* B C
* \ /
* D
* */
#include <iostream>
using namespace std;
class A{
public:
A(int data):m_data(data){
cout << "A:" << this << "," <<
sizeof(A) << endl;
}
protected:
int m_data;
};
class B:virtual public A{//虚继承
public:
B(int data):A(data){
cout << "B:" << this << "," <<
sizeof(B) << endl;
}
void set(int data){
m_data = data;
}
};
class C:virtual public A{//虚继承
public:
C(int data):A(data){
cout << "C:" << this << "," <<
sizeof(C) << endl;
}
int get(void){
return m_data;
}
};
class D:public B,public C{
public:
//虚继承时,由末端子类负责构造公共基类
//子对象
D(int data):B(data),C(data),A(data){
cout << "D:" << this << "," <<
sizeof(D) << endl;
}
};
int main(void)
{
D d(100);
cout << sizeof(d) << endl;//8
cout << d.get() << endl;//100
d.set(200);
cout << d.get() << endl;//200?100
return 0;
}
4)虚继承的语法
①在继承表中使用virtual关键字修饰
②由末端子类负责构造公共基类子对象
5)虚继承实现原理(了解)
注:使用虚继承语法时,创建D对象,它的继承结构会变成类似如下: