首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【c++】继承(一)

【c++】继承(一)

作者头像
mosheng
发布2026-01-14 18:33:36
发布2026-01-14 18:33:36
70
举报
文章被收录于专栏:c++c++

hello~ 很高兴见到大家! 这次带来的是C++中关于继承这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢? 个 人 主 页: 默|笙

一、继承的概念及定义

1.1 什么是继承?

  1. 继承是关于类设计层次的复用,假设有两个类为A类和B类,且B类继承了A类,那么我们把A类这样被继承的类叫做基类或父类,B类这样继承基类的类叫做派生类或子类
  2. 继承基类的派生类会拥有所有基类的成员函数和成员变量。

下面举一个例子:

  1. 假设有两种身份,一种是学生Student,一种是老师Teacher,可以将其看作两个类,它们都有姓名/地址/电话/年龄成员变量,不同之处在于:学生有成员变量学号,有成员函数study,而老师没有;老师有成员函数teaching,而学生没有。
  2. 我们就可以把共同的部分提取出来成为一个新的类Person,Student类和Teacher类继承Person类,实现类的复用,提高效率。
代码语言:javascript
复制
class Person
{
public:

protected:
	string _name = "小明";//名字
	int _age = 18;//年龄
	string _address;//地址
	string _tel;//电话
};

class Student : public Person
{
public:
	void study()
	{
		cout << "study()" << endl;
	}
private:
	string _stuid;//学号
};

class Teacher : public Person
{
public:
	void teaching()
	{
		cout << "teaching()" << endl;
	}
};

二、继承格式与继承方式

2.1 继承格式

代码语言:javascript
复制
class/struct 派生类 : 继承方式 基类
在这里插入图片描述
在这里插入图片描述

2.2 继承方式

  1. 继承方式有三类:分别是public继承、protected继承和private继承。我们知道,访问限定符也有对应的三种:public访问·、protected访问与private访问。它们可以组成9种情况如下:
在这里插入图片描述
在这里插入图片描述
  1. 基类private成员无论是哪种继承方式在派生类中都是不可见的。不可见指的是基类的private成员还是被继承到了派生类中,只是无论在类里类外都是无法访问的。
  2. 虽然基类private成员在派生类中是不可见的,但是我们可以在通过在基类里设置public或protected成员函数来间接在派生类里拿到private成员。
  3. 在学习继承之前,private与protected两个访问限定符的作用似乎一样,现在,我们能够找到区别:如果想让基类成员不想在类外被直接拿到却可以被派生类拿到的话,我们就可以使用protected访问限定符 – protected即是为了继承才出现的访问限定符。
  4. 总结以上表格,我们将private、protected与public的权限做个大小比较:public > protected > public,基类成员在派生类里的访问方式 == Min(成员在基类的访问限定符,继承方式)。
  5. 继承方式可以省略不写:使用关键字class时默认的继承方式是私有继承,使用关键字struct时默认的继承方式是公有继承。不过最好是显示写出继承方式。
  6. 一般情况下,都是使用public继承。

三、继承类模板

  1. 我们知道,模板是按需实例化的:一个类模板,只有在要使用里面的某个成员函数时,才会将需要使用的成员函数实例化
  2. 若基类是类模板,就需要指定一下类域,否则会编译报错:因为基类里的成员函数未实例化,编译器不知道它们的具体类型。
代码语言:javascript
复制
template<class T>
class stack : public vector<T>
{
public:
	void push(const T& x)
	{	
		vector<T>::push_back(x);//指定类域:vector<T>
	}

	void pop()
	{
		vector<T>::pop_back();
	}

	const T& top()
	{
		return vector<T>::back();
	}

	bool empty()
	{
		return vector<T>::empty();
	}
};

四、基类和派生类之间的转换

  1. 基类指针和派生类对象之间:通常在我们将一个类型的对象赋值给另一个类型的指针(强制转换)或引用时,存在类型转化,中间会产生临时对象,需要添加const(让权限对等)。而在public继承中:编译器会做特殊处理,使得派生类对象可以赋值给基类的引用和指针而不需要添加const
代码语言:javascript
复制
int d1 = 1;
double d2 = d1;//内置类型之间的隐式类型转换,值拷贝,临时值随后被销毁
const double& d3 = d1;//引用需要添加const,绑定临时对象,延长临时对象的生命周期

string s1 = "aaa";//构造(临时对象) + 拷贝构造(临时对象拷贝构造到s1),编译器优化为直接构造
const string& s2 = "aaa";//引用会省略拷贝构造,需要添加const来绑定临时对象
在这里插入图片描述
在这里插入图片描述

2.派生类对象和基类对象之间: 派生类对象可以赋值给基类对象,这通过基类的拷贝构造函数或赋值重载函数完成,这个过程就像是派生类里的基类之外的一部分被切掉了一样,只留下基类的一部分,所以也被叫做切割或切片。

在这里插入图片描述
在这里插入图片描述
  1. 基类对象不能赋值给派生类对象
  2. 基类指针或引用与派生类指针或引用之间:基类指针或者引用必须通过强制类型转换赋值给派生类的指针或引用。但是必须是基类的指针是指向派生类才是安全的。

五、继承中的作用域与隐藏

  1. 基类的作用域与派生类都拥有独立的作用域
  2. 若派生类和基类之中拥有同名成员,则会优先访问派生类里的成员,而基类里的同名成员将会被隐藏,无法对其直接访问,如果需要访问基类同名成员,需要指定类域
  3. 如果是成员函数的隐藏,只需要函数名相同就会构成隐藏,参数是否相同不是必要
  4. 在使用中最好不要定义同名成员,容易混淆。

观察以下代码:

代码语言:javascript
复制
class A
{
public:
 void fun()
 {
 cout << "func()" << endl;
 }
};
class B : public A
{
public:
 void fun(int i)
 {
 cout << "func(int i)" <<i<<endl;
 }
};
int main()
{
 B b;
 b.fun(10);
 b.fun();
 return 0;
};
  1. 程序的编译运行结果是编译报错,即语法问题。b.fun()会调用派生类里的而不会调用基类里的,派生类里面的fun函数需要传递参数,基类里面的则不需要。这里我们没有传递参数,编译报错。
  • 关于运行报错:一般是空指针,野指针。

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

1. 4个常见默认成员函数

  1. 默认构造函数,它分为两个部分:基类成员(它们是一个整体,需要调用基类构造)和派生类成员构造(跟类和对象的规则一样)。如果基类没有对应的默认的构造函数,则必须在派生类构造函数初始化列表阶段显示调用–对应初始化列表规则。
  2. 关于派生类拷贝构造函数,必须调用基类的拷贝构造函数完成基类成员的拷贝初始化。
  3. 派生类赋值重载函数,必须调用基类的赋值重载函数完成基类成员的赋值,由于派生类函数名与基类函数名都是operator=,构成隐藏,所以在调用基类的赋值重载函数时,需要指定类域
  4. 派生类的析构函数会在被调用完成之后自动调用基类的析构函数,这样是为了保证基类对象在还需要被使用的时候不会被清理掉,即先清理派生类成员,再清理基类成员。
  5. 派生类对象初始化时会先初始化基类成员,再初始化派生类成员,先父后子
  6. 派生类对象被清理时会先清理派生类成员,再清理基类成员,先子后父
  7. 因为多态中的一些场景析构函数需要构成重写,而重写需要函数名相同。编译器会对函数名做特殊处理,所有析构函数的名字都会被处理成destructor(),会构成隐藏,所以在派生类里面调用基类析构函数的时候需要指定类域(其实不需要调用基类析构函数,会自动调用的)。
代码语言:javascript
复制
class Person
{
public:
	Person(const char* name = "小明")
	{
		cout << "Person(const char* name) " << endl;
	}

	Person(const Person& p)
	{
		cout << "Person(const char& name)" << endl;
		_name = p._name;
	}

	Person& operator=(const Person& p)
	{
		cout << "operator=(const Person& p)" << endl;
		if (&p != this)
		{
			_name = p._name;
		}
		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
public:
	Student(const char* name = "xiaofang", int num = 1)
		:Person(name)
		,_num(num)
	{
		cout << "Student(const char* name, int num)" << endl;
	}

	Student(const Student& s)
		:Person(s)//派生类对象赋值给基类引用
		,_num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}
	}

	~Student()
	{
		cout << "~Student()" << endl;
	}
	//派生类析构调用之后,会自动调用基类析构--当自己实现析构时不用显式调用基类析构
private:
	int _num;

};


int main()
{
	Student s;
	return 0;
}
在这里插入图片描述
在这里插入图片描述

2. 实现一个无法被继承的类

  1. 可以将基类的构造函数私有,派生类的构造必须调用基类的构造函数,一旦基类的构造函数私有化在派生类里不可见之后,派生类就无法调用,自然也就无法实例化出对象。
  2. c++11新增了一个关键字–final,final修改基类,派生类就不能被调用了。

格式:

代码语言:javascript
复制
class/struct 类名称 final//在定义的时候

七、继承与友元

  1. 基类的成员都能够被派生类所继承,但是基类的友元关系是不能被继承的。也就是说,基类友元是不能够访问派生类保护和私有成员的。就好比,关于继承,父母的财产是能够被继承的,但是父母的人际关系是无法被继承的。
  2. 如果需要让基类友元能够访问派生类保护和私有成员,则需要在派生类里重新声明友元关系。

今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~ 让我们共同努力, 一起走下去!

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、继承的概念及定义
    • 1.1 什么是继承?
  • 二、继承格式与继承方式
    • 2.1 继承格式
    • 2.2 继承方式
  • 三、继承类模板
  • 四、基类和派生类之间的转换
  • 五、继承中的作用域与隐藏
  • 六、派生类的默认成员函数
    • 1. 4个常见默认成员函数
    • 2. 实现一个无法被继承的类
  • 七、继承与友元
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档