C++类和对象(六):继承


继承是面向对象三大特性之一,可提高代码复用性
子类/派生类既具有父类/基类/超类共性,又具有自身的特性


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:protectedprivate的主要区别在于继承方面,即子类是否可以访问父类的相关成员: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;
}

猜你喜欢

转载自blog.csdn.net/newson92/article/details/113778173