首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++进阶】继承(下)——挖掘继承深处的奥秘!

【C++进阶】继承(下)——挖掘继承深处的奥秘!

作者头像
用户11960591
发布2025-12-23 15:54:41
发布2025-12-23 15:54:41
210
举报

一、派生类的默认成员函数

我们知道类和对象中存在6个默认成员函数,默认的意思就是指我们不写,编译器会变我们自动生成⼀个,那么在派生类中,这几个成员函数是如何生成的呢?

1.1派生类的构造函数

代码语言:javascript
复制
#include<iostream>
#include<string>
uisng namespace std;
class Person
{
public:
	Person(const char* name)
		:_name(name)
	{
		cout << "Person()基类构造" << endl;
	}
protected:
	string _name;
};

class Student :public Person
{
public:
	//===============================
	//派生类的构造 与类和对象的规则高度相似
	//但是要分开两部份
	//派生类中的 基类->调用父类的构造
	//派生类中的 特有成员->调用派生类自己的构造
	//====================================
	Student(const char* name,int num,int age)
		//:_name(name) 这里不能这样写 派生类的构造函数必须调用基类的构造函数来初始化基类的那一部分成员
		:Person(name)								
		,_num(num)
		,_age(age)
	{
		cout<<"Student(const char* name,int num,int age)派生类构造"<<endl;
	}
protected:
	int _num;
	int _age;
};

int main()
{
	Student s("张三", 2416010101, 18);
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:

  1. 派生类构造函数必须显式或隐式调用父类构造函数。若未显式调用,编译器会自动调用父类的默认构造函数。若父类没有默认构造函数,则必须显式调用某个特定的父类构造函数。
  2. 在派生类构造函数的初始化列表中,基类成员的构造总是最先完成,随后才会执行派生类自身成员的构造。

1.2派生类的拷贝拷贝构造

代码语言:javascript
复制
#include<iostream>
#include<string>
uisng namespace std;
class Person
{
public:
	Person(const char* name)
		:_name(name)
	{
		cout << "Person()基类构造" << endl;
	}
	 Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)基类的拷贝构造" << endl;
	}
protected:
	string _name;
};

class Student :public Person
{
public:
	Student(const char* name,int num,int age)
		:Person(name)								
		,_num(num)
		,_age(age)
	{
		cout<<"Student(const char* name,int num,int age)派生类构造"<<endl;
	}
	Student(const Student& s)
	//把派生类中的基类看作是一个整体 调用基类的拷贝构造
	:Person(s)//传的是一个Person类对象
	,_num(s._num)
	,_age(s._age)
{
	cout << "Student(const Student& s)派生类的拷贝构造" << endl;
}
protected:
	int _num;
	int _age;
};

int main()
{
	Student s("张三", 2416010101, 18);
	Student s2(s);
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:

  1. 派⽣类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  2. 在没有资源的情况下/不存在要深拷贝时派生类可以不写拷贝构造,让编译器自动生成。编译器自动生成的拷贝构造,对于派生类的基类部分会调用基类的拷贝构造对于派生类新增的部分会浅拷贝。

1.3派生类的赋值重载

代码语言:javascript
复制
class Person
{
public:
	Person(const char* name)
		:_name(name)
	{
		cout << "Person()基类构造" << endl;
	}
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)基类的拷贝构造" << endl;
	}
	//赋值重载
	Person& operator=(const Person& s)
	{
		if (&s != this)
		{
			_name = s._name;
		}
		return *this;
	}
protected:
	string _name;
};

class Student :public Person
{
public:
	Student(const char* name,int num,int age)
		:Person(name)								
		,_num(num)
		,_age(age)
	{
		cout << "Student(const char* name,int num,int age)派生类构造" << endl;
	}
	Student(const Student& s)
		:Person(s)
		,_num(s._num)
		,_age(s._age)
	{
		cout << "Student(const Student& s)派生类的拷贝构造" << endl;
	}
	//赋值重载
	Student& operator=(const Student& s)
	{
		if (&s != this)
		{
			//基类部分调用基类的赋值重载 而由于基类的operator=()与派生类的operator=()构成隐藏
			//所以在调用基类的operator=()时要指定类域
			Person::operator=(s);
			_num = s._num;
			_age = s._age;
		}
		return* this;
	}
protected:
	int _num;
	int _age;
};

int main()
{
	Student s("张三", 01, 18);
	cout << endl;
	Student s2(s);
	cout<<endl;
	Student s3("李四", 2, 25);
	s3 = s2;
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:

  1. 派生类的赋值运算符(operator=)必须显式调用基类的赋值运算符来完成基类部分的复制。需要注意的是,派生类operator=隐藏基类operator=,因此在调用时需通过基类作用域来明确指定。
  2. 派生类的赋值重载一般来说,编译器默认生成的就够用了,只有当存在深拷贝的时候才需要自己写。

1.4派生类的析构函数

代码语言:javascript
复制
class Person
{
public:
	Person(const char* name)
		:_name(name)
	{
		cout << "Person()基类构造" << endl;
	}
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)基类的拷贝构造" << endl;
	}

	Person& operator=(const Person& s)
	{
		if (&s != this)
		{
			_name = s._name;
		}
		return *this;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
};

class Student :public Person
{
public:
	Student(const char* name, int num, int age)
		:Person(name)
		, _num(num)
		, _age(age)
	{
		cout << "Student(const char* name,int num,int age)派生类构造" << endl;
	}

	Student(const Student& s)
		:Person(s)
		, _num(s._num)
		, _age(s._age)
	{
		cout << "Student(const Student& s)派生类的拷贝构造" << endl;
	}
	Student& operator=(const Student& s)
	{
		if (&s != this)
		{
			Person::operator=(s);
			_num = s._num;
			_age = s._age;
		}
		return*this;
	}
	//析构函数名字因为后续多态(重写)章节原因,会被处理成destructor
	//所以派生类和基类析构构成隐藏关系
	~Student()
	{
		 //Person::~Person();这行代码可以不用写,因为~Student本身会自动调用
	} // 自动调用父类析构, 才能保证先子后父的析构顺序

	// 派生类析构调用后,会自动调用父类析构,所以自己实现析构时不需要显示调用
	// 构造初始化,先父类后子。析构清理资源,先子后父。
protected:
	int _num;
	int _age;
};

int main()
{
	Student s("张三", 01, 18);
	cout << endl;
	Student s2(s);
	cout << endl;
	Student s3("李四", 2, 25);
	s3 = s2;
}

注意:

  1. 派生类对象在析构时会先调用派生类的析构函数,再自动调用 基类的析构函数完成清理工作。因为这样才能保证派生类对象先清理派生类成员清理基类成员的顺序
  2. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(这个我们多态章节介绍)。那么编译器会对析构函数名进⾏特殊处理处理成destructor(),所以基类析构函数不加virtual的情况下派生类析构函数和基类析构函数构成隐藏关系

派生类默认成员函数行为总结:

在这里插入图片描述
在这里插入图片描述

二、继承与友元

友元关系不能继承,也就是说基类友元不能访问派⽣类私有和保护成员 。

代码语言:javascript
复制
//前置声明
class Student;

//基类
class Person
{
public:
	//友元函数
	friend void Display(const Person& p, const Student& s);//这里用到了Student当参数
						//编译器向上找找不到会报错 所以要在上面加上前置声明
protected:
	string _name; // 姓名

private:
	int _a;
};

//派生类
class Student : public Person
{
public:
	//把Display函数也声明
	friend void Display(const Person& p, const Student& s);
protected:
	int _stuNum; // 学号
};

//全局函数
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}


int main()
{
	Person p;
	Student s;
	// 编译报错:error C2248: “Student::_stuNum”: ⽆法访问 protected 成员
	// 解决⽅案:Display也变成Student 的友元即可
	Display(p, s);
	return 0;
}
在这里插入图片描述
在这里插入图片描述

注意友元关系是不能继承的Display声明成Person的友元,则Display中可以访问Person中的私有成员子类Student 继承了父类Person 后,并不能使用Display函数,因为DisplayPerson的友元而不是Student的友元。就相当于你父亲的朋友不一定是你的朋友一样。解决方法:在Student里也声明友元。

三、继承与静态成员

基类中定义的静态成员在整个继承体系中是唯一的。无论派生出多少个子类,该静态成员始终只有一个static实例存在。

代码语言:javascript
复制
class Person
{
public:
	string _name;
	//静态成员在类里面定义
	static int _count;
};
//静态成员在类外面初始化
int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum;
	
};

int main()
{
	Person p;
	Student s;
	
	//==============================================
	// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的
	// 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份
	//==============================================
	cout << &p._name << endl;
	cout << &s._name << endl;
	
	//==============================================
	// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的
	// 说明派⽣类和基类共⽤同⼀份静态成员
	//==============================================
	cout << &p._count << endl;
	cout << &s._count << endl;
	
	//=============================================
	// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员
	//=============================================
	cout << Person::_count << endl;
	cout << Student::_count << endl;
	return 0;
}
在这里插入图片描述
在这里插入图片描述

四、继承与组合

继承(is-a)与组合(has-a)对比表格

特性

继承(is-a)

组合(has-a)

关系类型

is-a 关系,派生类是基类的一种类型

has-a 关系,对象包含另一个对象

复用类型

白箱复用:派生类可访问基类实现细节

黑箱复用:对象细节不可见,仅通过接口访问

耦合度

高,基类变化可能影响派生类

低,改变一个对象不会影响另一个对象

优点

代码复用,支持多态和功能扩展

类间关系松散,低耦合,易于维护和替换

缺点

破坏封装,强依赖性,基类变动风险

需定义清晰接口,可能增加代码量

使用场景

类间存在逻辑层级关系且需多态支持(如动物→狗)

类间需功能组合但无层级关系(如汽车→发动机)

五、总结

C++继承核心要点💡

📢继承类型公有继承保持基类访问权限,保护继承降级为保护成员,私有继承降级为私有成员。

📢构造与析构派生类构造函数隐式调用基类默认构造,显式调用需通过初始化列表;析构顺序与构造相反。

📢访问控制派生类可访问基类公有和保护成员,外部对象仅能访问公有成员(公有继承时)。

📢继承与组合继承体现“is-a”关系(特化),组合体现“has-a”关系(包含),需根据设计目标选择。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、派生类的默认成员函数
    • 1.1派生类的构造函数
    • 1.2派生类的拷贝拷贝构造
    • 1.3派生类的赋值重载
    • 1.4派生类的析构函数
  • 二、继承与友元
  • 三、继承与静态成员
  • 四、继承与组合
  • 五、总结
    • C++继承核心要点💡
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档