前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >C++ 继承

C++ 继承

作者头像
发布2025-01-21 13:03:50
发布2025-01-21 13:03:50
4600
代码可运行
举报
文章被收录于专栏:转自CSDN转自CSDN
运行总次数:0
代码可运行

继承概念及其定义

概念

C++继承是一种面向对象编程的特性,允许一个类(称为子类或派生类)继承另一个类(称为基类或父类)的属性和方法。通过继承,子类可以重用和扩展基类的代码,实现代码复用和层次化设计。

例如:

对于两个类 Studen 和 Teacher 它们都有相同的成员变量,但是又各自又不同的成员变量,把它们设计到两个类里面就是冗余的,但是它们各自的不同的成员变量是“学号”,“职称”,它们各自的功能也不同,是“学习”与“授课”

代码语言:javascript
代码运行次数:0
复制
class Student
{
public:
	void study(){
		//..
	}
protected:
	string _name = "A";//重复的
	string _student_id = "1231312312";//不重复的
};

class Teacher
{
public:
	void teach() {
		//..
	}
protected:
	string _name = "A";//重复的
	string _teacher_id = "1231312312";//不重复的
};

为了解决这问题,我们使用它俩继承People的行为

代码语言:javascript
代码运行次数:0
复制
class People
{
public:
	void A()
	{

	}
protected:
	string _name;
};

class Student :public People
{
public:
	void study()
	{

	}
private:
	string _student_id;
};

class Teacher :public People
{
public:
	void teach()
	{

	}
private:
	string _teacher_id;
};

定义

C++继承是指一个类(子类)能够获取另一个类(基类)的属性和方法,从而实现代码复用和扩展的一种机制。

代码语言:javascript
代码运行次数:0
复制
//	   派生类名      继承方式    基类名
class   Student  :   public      People
{
	//扩展内容
public:
	void study()
	{

	}
private:
	string _student_id;
};

继承方式

public继承 protected继承 private继承

虽然关键字和访问限定符 相同 但是 不同 于访问限定符

它们遵循这样的规则进行继承

基类类成员//继承方式

public

protected

private

public

public

protected

private

protected

protected

protected

private

private

不可见

不可见

不可见

总结:基类类成员是private继承后全不可见 剩下的会按照继承方式 public不变 其它的相同与继承方式

注意

基类成员的private实际上是被继承了,只是无法在派生类中操作 基类中的“private”建议以protected的形式写 class的默认继承方式是priavre,struct的默认继承方式是public public的使用更多

代码语言:javascript
代码运行次数:0
复制
class People
{
public:
	void C()
	{
		_C = 0;
	}
	int _A;
protected:
	int _B;
private:
	int _C;
};

class Man :public People
{
	//这里可以调用 还是public的 A
	//还是protected的B 
	void TestA()
	{
		_A = 0;
		_B = 0;
		People::C();
	}
	//但是没有C 除非找他爹不然无法调用C
};

继承类模板

例如继承已有的vector的类模板时 要实例化一下

代码语言:javascript
代码运行次数:0
复制
template<class T>
class A :public vector<T>//继承vector的模板 
{
public:
	void push(const T& x)
	{
		// 这里要实例化不然找不到pushback
		vector<T>::push_back(x);
	}
};

基类于派生类之间的转换

  • public继承的派生类 可以 基类的指针 或 引用
  • 派生类的指针或引用不可以指向基类
  • 基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针是指向派⽣类对象时才是安全的。(基类只能指向自己儿子)

解释一下

可以认为基类的扩展为派生类,派生类的空间更大,基类指向自己派生类的时候,总会指向源自于基类的位置,不会指向派生类产出的额外的位置,对派生类产生了“切割”,不会越界访问所以可以,反之会越界所以不可以。

代码语言:javascript
代码运行次数:0
复制
class People
{
public:
	void A()
	{

	}
protected:
	string _name;
};

class   Student : public      People
{
	//扩展内容
public:
	void study()
	{

	}
private:
	string _student_id;
};

int main()
{
	Student s;
	
	People* p1 = &s;
	People& p2 = s;

	//而且派生类可以赋值给基类 但是基类不能赋值给派生类
	People b = s;
	return 0;
}

继承中的作用域

  • 继承体系中,基类和派生类拥有各自独立的作用域
  • 当基类和派生类有同名变量或函数的时候,基类的会隐藏,不被直接访问,基类需要 基类::成员 来访问
  • 只要名字相同就能隐藏 不建议隐藏
代码语言:javascript
代码运行次数:0
复制
class A
{
public:
	int a(){}
	int b;
};

class B :public A
{
public:
	int a(){/*我和上面不是同一个a*/}
	int b;//我和上面不是同一个B
	void test()
	{
		a();
		A::a();
		b = 0;
		A::b = 0;
	}
};

派生类型的默认成函数

先说结论:建议你都要写 而且把基类的部分要单独看成一个内置类型 都要调用基类的玩意 析构时派生先析构 构造是基类先构造

派生类的构造函数必须调用基类构造函数的那一部分成员,如果基类没有默认构造函数,就必须在派生类构造函数的初始化列表显式调用。

代码语言:javascript
代码运行次数:0
复制
class A
{
public:
	A(int a):_a(a){}
	int _a;
};

class B :public A
{
public:
	B(int b, int a = 0) :_b(b), A(a) {};
	int _b;
};

派生类的拷贝构造函数必须调用基类的拷贝构造函数完成拷贝初始化 派生类的 = 也要调用基类的 = 而且要显示调用和指定类域

代码语言:javascript
代码运行次数:0
复制
class B :public A
{
public:
	B& operator=(const B& x)
	{
		if (this != &x)
		{
			_b = x._b;
			A::operator=(x);
		}
	}
	int _b;
};

派生类型的析构函数会被调用玩后自动带哦用基类的析构函数清理基类成员,要保证先清理派生类型后清理基类,所以不用再派生的析构里写基类的析构函数 派生类初始化会先调用基类构造再调用派生构造 派生对象析构清理会先调用派生后调用基类 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。

特殊点(不可继承类)

给基类的构造一个私有就不能继承 给基类一个final关键字也不能继承

代码语言:javascript
代码运行次数:0
复制
class base finall{};

继承与友元

友元关系不继承

继承与静态成员

静态成员继承,但是只指向同一个static成员实例

继承与组合

  • public继承是is-a关系 也就是派生对象都是一个基类对象
  • 组合是has-a的关系 B组合A,B对象中都有一个A
  • 继承是基于基类实现派生类,破坏了基类的封装,基类对派生类有影响,高耦合
  • 组合是一种复用选择,没有破坏“基类”的封装,是独立的一部分,低耦合
  • 组合的优先度高于继承

组合:

代码语言:javascript
代码运行次数:0
复制
class B
{
    class A
    {
        //
    };
    //
};

继承的关系类似于,A和B是绑定关系,白菜是菜,白菜就是菜 组合的关系类似于,A有B没B都不会破坏A或者B的存在,车有轮胎 车没轮胎

多继承与菱形继承

继承模型

单继承:一个派生继承一个类 A指向B

多继承:一个派生继承多个类 A指向B和C

菱形继承:一个派生类继承多个类,这多个类的继承源自于同一个类 例:A指向B和C,B和C指向D

问题

源自于多继承的菱形继承,会导致数据很多,而且对某些数据还有歧义,因为“隐藏”的原因,这个源于“原始基类”的数据被多次继承,每一个继承都有一个‘隐藏’,数据多而且乱七八糟,并不建议菱形继承,有些语言甚至没有多继承

代码语言:javascript
代码运行次数:0
复制
class A{};
class B:public A{};
class C:public A{};
class D:public A,public B{};

虚继承

c++提供了一个虚继承,底层复杂而且性能损失 虚继承采用virtual在腰部的继承的类上使用

代码语言:javascript
代码运行次数:0
复制
class A{};
class B:virtual public A{};
class C:virtual public A{};
class D:public A,public B{};
一、节省内存空间

在多重继承中,如果不使用虚继承,一个基类可能会因为被不同的派生类从不同路径继承而导致其数据成员在派生类中存在多份拷贝。这种情况会造成内存空间的浪费。通过虚继承,可以确保无论基类被继承多少次,其数据成员在内存中只占用一份空间。这是因为虚继承机制使得所有从同一基类派生的类共享该基类的一个实例。

二、避免二义性问题

当派生类从多个路径继承同一个基类时,如果不使用虚继承,在派生类中访问基类成员时可能会产生二义性。编译器在面对这种情况时,无法确定应该访问哪一份基类的拷贝,从而导致编译错误或不确定的行为。虚继承通过共享基类数据成员的方式消除了这种二义性,使得在派生类中访问基类成员变得明确且唯一。

三、实现原理

虚继承的底层实现原理通常与编译器相关,但一般通过虚基类指针(vbptr)和虚基类表(vtable)来实现。每个虚继承的子类都有一个虚基类指针,该指针指向一个虚基类表,虚表中记录了虚基类与本类的偏移地址。通过偏移地址,可以找到虚基类成员,从而实现了在多重派生子类中只保存一份共有基类的拷贝的功能。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 继承概念及其定义
    • 概念
    • 定义
    • 继承方式
    • 继承类模板
  • 基类于派生类之间的转换
  • 继承中的作用域
  • 派生类型的默认成函数
    • 特殊点(不可继承类)
  • 继承与友元
  • 继承与静态成员
  • 继承与组合
  • 多继承与菱形继承
    • 继承模型
    • 问题
    • 虚继承
      • 一、节省内存空间
      • 二、避免二义性问题
      • 三、实现原理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档