文章目录
继承
继承的概念
通过一种机制表达类型之间的共性和特性的方式,利用已有的数据类型定义新的数据类型,这种机制就是继承。
人类:姓名 年龄 吃饭 睡觉
学生类:姓名 年龄 吃饭 睡觉 学习 学号
教师类:姓名 年龄 吃放 睡觉 讲课 工资
---------------------------------------
人类:姓名 年龄 吃饭 睡觉
学生类继承人类:学习 学号
教师类继承人类:讲课 工资
人类(基类/父类)
/ \
学生 教师(派生类/子类)
基类--派生-->子类
子类--继承-->基类
继承的语法
class 子类:继承方式 基类,...{
...
};
继承方式:
- public(公有继承)
- protected(保护继承)
- private(私有继承)
公有继承的特性
- 子类对象会继承基类的属性和行为,通过子类对象可以访问基类中的成员,就如同是基类对象在访问它们一样。
注:子类对象中包含了基类部分称为“基类子对象” - 向上造型(重点)
将子类类型的指针或引用,转换为基类类型的指针或引用。这种操作性缩小的类型转换,在编译器看来是安全,可以直接隐式转换。
基类
↑
子类
eg:
class A{};
class B:public A{};
class C:public A{};
void func(A* pa){...}
B b;
func(&b);//向上造型
C c;
func(&c);//向上造型
- 向下造型
将基类类型指针或引用,转换为子类类型的指针或引用。这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转换,但是可以显示转换。 - 子类集成基类的成员
- 在子类中可以直接访问基类中公有或保护成员,就如同它们是子类自己的成员一样。
- 基类中私有成员可以集成过来,但是会受访问控制属性的限制,无法直接访问;如果需要在子类中访问基类的私有成员,需要通过基类中的公有或保护的接口函数间接访问。
- 子类隐藏基类的成员
- 子类和基类中定义同名的成员函数,因为作用域不同,不能构成重载关系,而是一种隐藏关系,这时通过子类对象将优先访问子类自己的成员,无法直接访问基类中的成员。
- 如果需要通过子类访问基类中被隐藏的成员,可以借助“类名::”显式指明。
- 通过using声明可以将基类中的成员引入子类的作用域,形成重载,通过重载匹配解决。
代码示例
- inherit.cpp
#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;
public:
//私有成员子类中无法直接访问,但是可以
//通过基类提供的接口函数间接访问
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("关羽",29,10001);
s.who();
s.eat("红烧牛肉面");
s.sleep(8);
s.learn("孙武兵法");
Teacher t("悟空",30,50000);
t.who();
t.eat("水蜜桃");
t.sleep(6);
t.teach("Unix C编程");
//Student*-->Human*:向上造型
Human* ph = &s;
ph->eat("平谷大桃");
ph->sleep(2);
//ph->who();
//Human*-->Student*:向下造型(安全)
Student* ps = static_cast<Student*>(ph);
ps->who();
Human h("林黛玉",28);
//Human*-->Student*:向下造型(危险)
Student* ps2 =static_cast<Student*>(&h);
ps2->who();
return 0;
}
- 执行结果
- 02inherit.cpp
#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函数声明到当前子类,可以
//让其和子类中的func形成重载
//using Base::func;
};
int main(void)
{
Derived d;
d.Base::func();
d.func(10);
return 0;
}
- 执行结果
继承方式和访问属性
- 访问控制熟悉:影响访问该类成员的位置
访问控制 | 访问控制 | 内部 | 子类 | 外部 | 友元 |
---|---|---|---|---|---|
限定符 | 属性 | 访问 | 访问 | 访问 | 访问 |
public | 公有成员 | Ok | Ok | Ok | Ok |
protected | 保护成员 | Ok | Ok | No | Ok |
private | 私有成员 | Ok | No | No | Ok |
- 集成方式:影响通过子类访问基类中成员的可访问性
基类中的 | 在公有集成子类中变成 | 在保护集成子类中变成 | 在私有继承中子类变成 |
---|---|---|---|
公有成员 | 公有成员 | 保护成员 | 私有成员 |
保护成员 | 保护成员 | 保护成员 | 私有成员 |
私有成员 | 私有成员 | 私有成员 | 私有成员 |
子类的构造函数
- 如果子类的构造函数没有指明基类部分(基类子对象)的初始化方式,那么编译器将自动调用基类的无参构造函数来初始化基类子对象。
- 如果希望基类子对象以有参方式进行初始化,必须要使用初始化表来显式指明。
class 子类:public 基类{
//基类(...):显式指明基类子对象初始化方式
子类(...):基类(...){}
};
- 子类对象构造过程
- 内存分配
- 构造基类子对象(按继承表顺序)
- 构造成员子对象(按声明顺序)
- 执行子类构造函数代码是
代码示例
- 03inherit.cpp
#include <iostream>
using namespace std;
class Member{
public:
Member(void):m_i(0){
cout << "Member(void)" << endl;
}
Member(int i):m_i(i){
cout << "Member(int)" << endl;
}
int m_i;
};
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(i):指明基类子对象的初始化方式
//m_mem(i):指明成员子对象初始化方式
Derived(int i):Base(i),m_mem(i){
cout << "Derived(int)" << endl;
}
Member m_mem;//成员子对象
};
int main(void)
{
Derived d;
cout << d.m_i << ',' << d.m_mem.m_i
<< endl;//0,0
Derived d2(123);
cout << d2.m_i << ',' << d2.m_mem.m_i
<< endl;//123,123
return 0;
}
- 执行结果
子类的析构函数
- 子类的析构函数,无论自己定义的还是编译器缺省提供的,都会自动调用基类的析构函数,析构基类子对象。
- 子类对象的析构过程
- 执行子类析构函数代码
- 析构成员子对象(按声明逆序)
- 析构基类子对象(按集成表逆序)
- 释放内存
- 基类的析构函数不会调用子类的析构函数,对一个指向子类对象的基类指针使用delete操作符,实际被调用的仅是基类的析构函数,子类的析构函数不会被调用,有内存泄漏风险。
解决:虚析构函数
class A{};
class B:public A{};
A* pa = new B;//pa:指向子类对象的基类指针
delete pa;//有内存泄漏的风险
代码示例
- inherit.cpp
#include <iostream>
using namespace std;
class Member{
public:
Member(void):m_i(0){
cout << "Member(void)" << endl;
}
Member(int i):m_i(i){
cout << "Member(int)" << endl;
}
~Member(void){
cout << "~Member(void)" << endl;
}
int m_i;
};
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(i):指明基类子对象的初始化方式
//m_mem(i):指明成员子对象初始化方式
Derived(int i):Base(i),m_mem(i){
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_i
<< endl;//0,0
Derived d2(123);
cout << d2.m_i << ',' << d2.m_mem.m_i
<< endl;//123,123
*/
Base* pb = new Derived;
//...
//只调用基类的析构函数,有内存泄露风险
delete pb;
return 0;
}
- 执行结果
子类的拷贝构造和拷贝赋值
- 拷贝构造
- 如果子类没用定义拷贝构造函数,编译器会为子类提供一个缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成对基类子对象的拷贝。
- 如果自己定义了拷贝构造函数,需要使用初始化表,显式的指明基类子对象也要以拷贝的方式来初始化。
class Base{};
class Derived:public Base{
//Base(that):显式的指明基类子对象也要以拷贝的方式来初始化
Derived(const Derived& that)
:Base(that),...{}
};
- 拷贝赋值
- 如果子类没用定义拷贝赋值操作符函数,那么编译器会为子类提供缺省的拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的复制操作。
- 如果自己定义了拷贝赋值函数,需要显式调用基类的拷贝赋值函数,完成基类子对象的赋值操作。
class Base{};
class Derived:public Base{
Derived& operator=(const Derived& that){
//显式调用基类的拷贝赋值函数
Base::operator=(that);
......
}
};
代码示例
- 02inherit.cpp
#include <iostream>
using namespace std;
class Base{
public:
Base(void):m_i(0){}
Base(int i):m_i(i){}
Base(const Base& that):m_i(that.m_i){
cout << "Base(const Base&)" << endl;
}
Base& operator=(const Base& that){
cout << "Base::operator=" << endl;
if(&that != this){
m_i = that.m_i;
}
return *this;
}
int m_i;
};
class Derived:public Base{
public:
Derived(void):m_i(0){}
Derived(int i):Base(i),m_i(i){}
//Base(that):指明基类部分也要以拷贝方式
//来初始化
Derived(const Derived& that)
:m_i(that.m_i),Base(that){}
Derived& operator=(const Derived& that){
if(&that != this){
//显式调用基类的拷贝赋值函数,
//完成基类子对象的复制
Base::operator=(that);
m_i = that.m_i;
}
return *this;
}
int m_i;
};
int main(void)
{
Derived d1(123);
Derived d2(d1);//拷贝构造
cout << d1.m_i << ',' << d1.Base::m_i
<< endl;//123 123
cout << d2.m_i << ',' << d2.Base::m_i
<< endl;//123 123
Derived d3;
d3 = d1;//拷贝赋值
cout << d3.m_i << ',' << d3.Base::m_i
<< endl;//123 123
}
- 执行结果
多重继承
- 概念
一个子类同时继承多个基类,这样继承结构称为多重继承。
电话 播放器 计算机
\ | /
智能手机
- 多重继承在向上造型时,编译器会根据基类子对象的内存布局,自动进行偏移计算,保证指针的类型与所指向的目标对象类型一致。
- 名字冲突问题
一个子类的多个基类中如果存在相同名字的成员,当通过子类访问它们时,编译器会包歧义错误——名字冲突。
解决名字冲突的一般做法是显式调用“类名::”,指明所访问的成员属于哪个基类。
如果产生名字冲突的成员是成员函数并满足不同参数重载条件,也可以通过 using 声明使它们在子类中形成重载,通过重载解析来解决。
代码示例
- mul_inherit.cpp
#include <iostream>
using namespace std;
//电话基类
class Phone{
public:
Phone(const string& number):
m_number(number){}
void call(const string& number){
cout << m_number << "打给" <<
number << endl;
}
private:
string m_number;
};
//播放器基类
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& number,
const string& media,
const string& os):
Phone(number),
Player(media),
Computer(os){}
};
int main(void)
{
SmartPhone iphoneX(
"13866668888","MP4","Android");
iphoneX.call("010-110");
iphoneX.play("最炫小苹果.mp3");
iphoneX.run("Angry Brid");
SmartPhone* p1 = &iphoneX;
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;
}
- 执行结果
钻石继承
- 一个子类的多个基类源于共同的基类祖先,这样的继承结构称为钻石继承。
A
/ \
B C
\ /
D
- 公共基类(A)子对象在汇聚子类(D)对象中存在多个实例。在汇聚子类中或者通过汇聚子类对象去访问公共基类中的成员,会因为继承路径不同而导致结果不一致。
- 通过虚继承可以让公共基类(A)子对象在汇聚子类(D)对象中实例位移,并为多余中间(B、C)类共享,这样即使沿着不同的继承路径,所访问到公共基类中的成员也一定一致。
代码实例
- mul_inherit.cpp
#include <iostream>
using namespace std;
class Base1{
public:
void func(void){
cout << "Base1::func(void)" << endl;
}
};
class Base2{
public:
void func(int i){
cout << "Base2::func(int)" << endl;
}
};
class Derived:public Base1,public Base2{
public:
//using Base1::func;
//using Base2::func;
};
int main(void)
{
Derived d;
d.Base1::func();
d.Base2::func(10);
return 0;
}
- 执行结果
- diamond.cpp
#include <iostream>
using namespace std;
/* 钻石继承
* A(int m_data)
* / \
* B C
* \ /
* D(汇聚子类)
* */
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 << d.get() << endl;//100
d.set(200);
cout << d.get() << endl;//200
return 0;
}
- 执行结果
虚继承
- 1)在继承表中使用virtual关键字修饰
- 2)位于继承链的最末端子类负责构造公共(虚)基类子对象。
A(int m_data)
/ \
B C//:virtual public A
\ /
D//负责构造公共基类(A)子对象