继承是面向对象三大特性之一,可提高代码复用性。
子类/派生类既具有父类/基类/超类的共性,又具有自身的特性。
1 继承的基本语法
语法:class 子类/派生类 : 继承方式 父类/基类/超类 {...};
作用:减少重复代码,提高代码的复用性。
注:子类的成员包括:①从父类继承的共性;②子类新增的特性。
2 继承方式
语法:class 子类 : 继承方式 父类 {...};
继承方式:
(1)公共继承public
:
父类中的公共成员public
、保护成员protected
,在子类中其访问权限不变;
父类中私有成员private
,在子类中不可访问。
(2)保护继承protected
:
父类中的公共成员public
、保护成员protected
,在子类中其访问权限均变为protected
;
父类中私有成员private
,在子类中不可访问。
(3)私有继承private
:
父类中的公共成员public
、保护成员protected
,在子类中其访问权限均变为private
;
父类中私有成员private
,在子类中不可访问。
注:父类中的私有成员,无论采用何种继承方式,子类均无法访问。
访问权限包括3种:
public
:公共权限——成员在类中和类外均可访问。
protected
:保护权限——成员在类中可访问,类外不可访问;子类可访问父类的保护成员。
private
:私有权限——成员在类中可访问,类外不可访问;子类不可访问父类的私有成员。
注1:
protected
和private
的主要区别在于继承方面,即子类是否可以访问父类的相关成员:protected
子类可访问父类的保护成员;private
子类不可访问父类的私有成员。
注2:类中的函数内部,仍属于类中,可访问任意权限的类成员。
注3:类class的默认权限为私有private
。
注4:类的作用域
属于类内
,如类型::成员
,可访问类中的私有成员或私有构造函数。
示例:继承方式
#include <iostream>
using namespace std;
class Father {
public:
int a;
protected:
int b;
private:
int c;
};
/* 公共继承 */
class Son1 : public Father {
void func() {
a = 1; //父类public成员,子类中是public
b = 2; //父类protected成员,子类中是protected
//报错:不可访问
//c = 3; //父类private成员,子类无法访问
}
};
void test1() {
Son1 s;
s.a = 1; //类内可访问,类外可访问→public
/* protected、private成员在类外无法访问 */
//报错:不可访问
//s.b = 2; //类内可访问,类外无法访问→protected
//报错:不可访问
//s.c = 3; //父类private成员,子类无法访问
}
/* 保护继承 */
class Son2 : protected Father {
void func() {
a = 1; //父类public成员,子类中是protected
b = 2; //父类protected成员,子类中是protected
//报错:不可访问
//c = 3; //父类private成员,子类无法访问
}
};
void test2() {
Son2 s;
/* protected、private成员在类外无法访问 */
//报错:不可访问
//s.a = 1; //类内可访问,类外无法访问→a可能为protected或private,需继续验证其子类
//报错:不可访问
//s.b = 2; //类内可访问,类外无法访问→b可能为protected或private,需继续验证其子类
//报错:不可访问
//s.c = 3; //父类private成员,子类无法访问
}
class Grandson2 : public Son2 {
void func() {
a = 10; //在Son2的子类可访问→a为Son2的保护成员(protected)
b = 20; //在Son2的子类可访问→b为Son2的保护成员(protected)
}
};
/* 私有继承 */
class Son3 : private Father {
void func() {
a = 1; //父类public成员,子类中是private
b = 2; //父类protected成员,子类中是private
//报错:不可访问
//c = 3; //父类private成员,子类无法访问
}
};
void test3() {
Son3 s;
/* protected、private成员在类外无法访问 */
//报错:不可访问
//s.a = 1; //类内可访问,类外无法访问→a可能为protected或private,需继续验证其子类
//报错:不可访问
//s.b = 2; //类内可访问,类外无法访问→b可能为protected或private,需继续验证其子类
//报错:不可访问
//s.c = 3; //父类private成员,子类无法访问
}
class Grandson3 : public Son3 {
void function() {
//报错:不可访问
//a = 10; //在Son3的子类无法访问→a为Son3的私有成员(private)
//报错:不可访问
//b = 20; //在Son3的子类无法访问→b为Son3的私有成员(private)
}
};
3 继承中的对象模型
父类中的全部非静态成员属性(public
/protected
/private
等各种权限)均会被子类继承。
父类的私有成员会被子类继承,但编译器内部会隐藏父类私有成员,使其无法被子类对象访问。
注:使用Visual Studio自带工具
Developer Command Prompt for VS 2019
(VS 2019的开发人员命令提示符)查看子类对象中继承自父类的私有成员。
使用方法:
①在该命令提示符中,切换至当前.cpp
源文件所在目录;
②cl /d1 reportSingleClassLayout查询类名 源文件名
如:E:\Microsoft Visual Studio>cl /d1 reportSingleClassLayoutSon test.cpp
。
示例:子类对象占用的内存空间
#include <iostream>
using namespace std;
class Father {
public:
int fieldA;
protected:
int fieldB;
private:
int fieldC;
};
class Son : public Father {
public:
int fieldD;
};
int main() {
//父类的全部非静态成员均会被子类继承,编译器内部会隐藏父类私有成员使其无法被子类对象访问
cout << "子类对象占用的内存大小:" << sizeof(Son) << endl; //16
return 0;
}
4 继承中构造和析构顺序
当创建子类对象时,会调用父类的构造函数。
创建子类对象:先调用父类构造函数;再调用子类构造函数。
释放子类对象:先调用子类析构函数;再调用父类析构函数。
注1:继承中,先调用父类构造函数,再调用子类构造函数;析构顺序与构造相反。
注2:Java中,创建子类对象时,会先访问父类默认无参构造方法。
示例:创建和释放子类对象时,调用父类构造函数和析构函数
#include <iostream>
using namespace std;
class Father {
public:
Father() {
cout << "父类构造函数" << endl;
}
~Father() {
cout << "父类析构函数" << endl;
}
};
class Son : public Father {
public:
Son() {
cout << "子类构造函数" << endl;
}
~Son() {
cout << "子类析构函数" << endl;
}
};
int main() {
Son s; //创建子类对象
return 0;
}
输出结果:
父类构造函数
子类构造函数
子类析构函数
父类析构函数
5 继承中同名成员的访问方式
通过子类对象访问同名成员:
访问子类同名成员(属性或函数):直接访问,即子类对象.成员属性/函数
,如obj.field;
或obj.func();
。
访问父类同名成员(属性或函数):添加作用域,即子类对象.父类名::成员属性/函数
,如obj.Father::field;
或obj.Father::func();
。
注:若子类存在与父类同名的成员函数,则编译器会隐藏父类所有的同名成员函数(包括重载的同名函数),添加父类作用域后可访问父类中的同名成员函数。
示例:继承中同名成员的访问
#include <iostream>
using namespace std;
class Father {
public:
//父类同名成员属性
int field;
Father() {
field = 1;
cout << "父类构造函数" << endl;
}
//父类同名成员函数(重载)-无参
void func() {
cout << "父类的成员函数:func()" << endl;
}
//父类同名成员函数(重载)-1个参数
void func(int a) {
cout << "父类的成员函数:func(int a)" << endl;
}
//父类同名成员函数(重载)-2个参数
void func(int a, int b) {
cout << "父类的成员函数:func(int a, int b)" << endl;
}
};
class Son : public Father {
public:
//子类同名成员属性
int field;
Son() {
field = 2;
cout << "子类构造函数" << endl;
}
//子类同名成员函数
void func() {
cout << "子类的成员函数:func()" << endl;
}
};
int main() {
Son s;
/* 同名成员属性 */
//访问子类同名成员属性
cout << "访问子类同名成员属性:" << s.field << endl; //2
//访问父类同名成员属性
cout << "访问父类同名成员属性:" << s.Father::field << endl; //1
/* 同名成员函数 */
//访问子类同名成员函数
s.func(); //子类的成员函数:func()
//访问父类同名成员函数
s.Father::func(); //父类的成员函数:func()
//若子类存在与父类同名的成员函数,则编译器会隐藏父类所有的同名成员函数(包括重载的同名函数)
//s.func(10); //报错。无法直接访问父类的所有同名函数
//s.func(10, 20); //报错。无法直接访问父类的所有同名函数
s.Father::func(10); //父类的成员函数:func(int a)
s.Father::func(10, 20); //父类的成员函数:func(int a, int b)
return 0;
}
6 继承中同名静态成员的访问方式
(1)通过子类对象访问同名静态成员,与非静态成员的访问方式一致:
访问子类同名静态成员(属性或函数):直接访问,即子类对象.静态成员属性/函数
,如obj.field;
或obj.func();
。
访问父类同名静态成员(属性或函数):添加父类作用域,即子类对象.父类名::成员属性/函数
,如obj.Father::field;
或obj.Father::func();
。
(2)通过子类类名访问同名静态成员:
访问子类同名静态成员(属性或函数):直接访问,即子类名::静态成员属性/函数
,如Son::field;
或Son::func();
。
访问父类同名静态成员(属性或函数):添加父类作用域,即子类名::父类名::静态成员属性/函数
,如Son::Father::field;
或Son::Father::func();
。
注:第1个
::
表示通过类名访问;第2个::
表示访问父类作用域。
注:若子类存在与父类同名的静态成员函数,则编译器会隐藏父类所有的同名静态成员函数(包括重载的同名函数),添加父类作用域后可访问父类中的同名静态成员函数。
示例:继承中同名静态成员的访问
#include <iostream>
using namespace std;
class Father {
public:
//父类同名静态成员属性
static int s_field;
//父类同名静态成员函数(重载)-无参
static void s_func() {
cout << "父类的静态成员函数:s_func()" << endl;
}
//父类同名静态成员函数(重载)-1个参数
static void s_func(int a) {
cout << "父类的静态成员函数:s_func(int a)" << endl;
}
//父类同名静态成员函数(重载)-2个参数
static void s_func(int a, int b) {
cout << "父类的静态成员函数:s_func(int a, int b)" << endl;
}
};
//初始化父类静态成员属性
int Father::s_field = 1;
class Son : public Father {
public:
//子类同名静态成员属性
static int s_field;
//子类同名静态成员函数
static void s_func() {
cout << "子类的静态成员函数:s_func()" << endl;
}
};
//初始化子类静态成员属性
int Son::s_field = 2;
int main() {
Son s;
/* 同名静态成员属性 */
//1.通过子类对象访问
//访问子类同名成员属性
cout << "访问子类同名静态成员属性:" << s.s_field << endl; //2
//访问父类同名成员属性
cout << "访问父类同名静态成员属性:" << s.Father::s_field << endl; //1
//2.通过子类类名访问
//访问子类同名成员属性
cout << "访问子类同名静态成员属性:" << Son::s_field << endl; //2
//访问父类同名成员属性
cout << "访问父类同名静态成员属性:" << Son::Father::s_field << endl; //1
/* 同名静态成员函数 */
//1.通过子类对象访问
//访问子类同名成员函数
s.s_func(); //子类的静态成员函数:s_func()
//访问父类同名成员函数
s.Father::s_func(); //父类的静态成员函数:s_func()
//2.通过子类类名访问
//访问子类同名成员函数
Son::s_func(); //子类的静态成员函数:s_func()
//访问父类同名成员函数
Son::Father::s_func(); //父类的静态成员函数:s_func()
//若子类存在与父类同名的静态成员函数,则编译器会隐藏父类所有的同名成员函数(包括重载的同名函数)
//s.s_func(10); //报错。无法直接访问父类的所有同名函数
//s.s_func(10, 20); //报错。无法直接访问父类的所有同名函数
s.Father::s_func(10); //父类的静态成员函数:s_func(int a)
s.Father::s_func(10, 20); //父类的静态成员函数:s_func(int a, int b)
return 0;
}
7 多继承语法
C++允许一个类继承多个类。
语法:class 子类 : 继承方式 父类1, 继承方式 父类2, ... {...};
注1:多继承可能导致不同父类中存在同名成员,即存在二义性,需使用作用域进行区分,否则编译器报错:
子类::同名成员 不明确
。
注2:C++实际开发中不建议用多继承。
示例:C++多继承
#include <iostream>
using namespace std;
class Father1 {
public:
int fieldCommon;
int fieldA;
Father1(){
fieldCommon = 1;
}
void func() {
cout << "Father1类的func()" << endl;
}
};
class Father2 {
public:
int fieldCommon;
int fieldB;
Father2() {
fieldCommon = 2;
}
void func() {
cout << "Father2类的func()" << endl;
}
};
class Son : public Father1, public Father2 {
public:
int fieldC;
};
int main() {
Son s;
cout << "Father1类中的同名属性:" << s.Father1::fieldCommon << endl; //1
cout << "Father2类中的同名属性:" << s.Father2::fieldCommon << endl; //2
//报错:Son::fieldCommon不明确
//cout << "Father2类中的同名属性:" << s.fieldCommon << endl;
s.Father1::func(); //Father1类的func()
s.Father2::func(); //Father2类的func()
//报错:Son::func不明确
//s.func();
return 0;
}
8 菱形继承
菱形继承/钻石继承:两个子类继承同一个父类,且某个类同时继承两个子类。
菱形继承的问题:
(1)孙子类(Grandson)访问成员时,可能产生二义性;
(2)孙子类(Grandson)从父类(Father)继承了两份数据,导致内存资源浪费,且不符合实际意义。
解决方法:
使用虚继承(virtual关键字
)可解决菱形继承问题,父类/基类称为虚父类/虚基类
。
语法:class 子类 : virtual 继承方式 虚基类{...};
原理:使用虚继承后,孙子类(Grandson)不再从两个子类中继承两份数据,而通过继承两个指针,分别根据偏移量查找到唯一的数据。
虚基类指针vbptr
指向虚基类表vbtable
,可根据表中记录的偏移量查找到唯一的数据。
示例:动物→马、驴→骡子
#include <iostream>
using namespace std;
//动物类
class Animal {
public:
int age;
};
//马类
class Horse : virtual public Animal {};
//驴类
class Donkey : virtual public Animal {};
//骡类
class Mule : public Horse, public Donkey {};
int main() {
Mule m;
/*
//1.存在二义性
//m.age = 0; //不可访问。报错:Mule::age不明确
//2.两份数据导致资源浪费,可使用作用域区分,但不符合实际意义
m.Horse::age = 1;
m.Donkey::age = 2;
*/
/* 加入虚继承,使用virtual关键字 */
m.age = 0; //可访问。
m.Horse::age = 1;
m.Donkey::age = 2;
//孙子类仅保存一份唯一数据
cout << m.age << endl; //2
cout << m.Horse::age << endl; //2
cout << m.Donkey::age << endl; //2
return 0;
}