【C++】继承的介绍

继承

  • 1.继承的概念及定义
    • 1.1继承的概念:
    • 1.2 继承定义
    • 1.3继承类模板
  • 2.继承中的函数隐藏
  • 3.派生类的默认成员函数
  • 4.继承中的切割
  • 5.多继承及其菱形继承问题
    • 5.1继承模型
    • 5.2解决菱形继承问题的方法(虚继承)
  • 6.继承和组合

1.继承的概念及定义

1.1继承的概念:

在C++中,继承是面向对象编程的一个重要特性。
从概念上讲,继承允许你创建一个新类(派生类/子类),这个新类从一个已存在的类(基类/父类)那里获取成员变量和成员函数。继承的主要目的是代码重用和建立类之间的层次结构

class Student
{
public:
// 进校园/图书馆/实验室刷二维码进行身份认证
	void identity()
	{
	// ...
	}
	// 学习
	void study()
	{
	// ...
	}
protected:
	string _name = "peter"; // 姓名
	string _address; // 地址
	string _tel; // 电话
	int _age = 18; // 年龄
	int _stuid; // 学号
};
class Teacher
{
public:
	void identity()
	{
	// ...
	}
	// 授课
	void teaching()
	{
	//...
	}
protected:
	string _name = "张三"; // 姓名
	int _age = 30; // 年龄
	string _address; // 地址
	string _tel; // 电话
	string _title; // 职称
};

上面我们看到没有继承之前我们设计了两个类Student和Teacher,Student和Teacher都有姓名/地址/
电话/年龄等成员变量,都有identity⾝份认证的成员函数,设计到两个类里面就是冗余的。当然他们
也有⼀些不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣
的独有成员函数是学习,⽼师的独有成员函数是授课。
下⾯我们公共的成员都放到Person类中,Student和teacher都继承Person,就可以复⽤这些成员,就不需要重复定义了,省去了很多麻烦。

class Person
{
public:
// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证
void identity()
{
cout << "void identity()" <<_name<< endl;
}
protected:
	string _name = "张三"; // 姓名
	string _address; // 地址
	string _tel; // 电话
	int _age = 18; // 年龄
};

用Student和teacher都继承Person

class Student : public Person
{
public:
// 学习
	void study()
	{
	// ...
	}
protected:
	int _stuid; // 学号
};
class Teacher : public Person
{
public:
	// 授课
	void teaching()
	{
	//...
	}
protected:
	string title; // 职称
};

int main()
{
	Student s;
	Teacher t;
	s.identity();
	t.identity();
return 0;
}

1.2 继承定义

定义格式
下面我们看到Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。(因为翻译的原因,所以
既叫基类/派⽣类,也叫⽗类/子类)
在这里插入图片描述
继承基类成员访问方式的变化:
在这里插入图片描述
1.基类private成员在派⽣类中⽆论以什么⽅式继承都是不可见的。这⾥的不可见是指基类的私有成员
还是被继承到了派⽣类对象中,但是语法上限制派⽣类对象不管在类⾥⾯还是类外⾯都不能去访问
它。
2.基类private成员在派⽣类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派⽣类
中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.基类的私有成员在派⽣类都是不可见,public > protected >private。
4.使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显
⽰的写出继承方式。在实际运⽤中⼀般使⽤都是public继承,几乎很少使⽤protetced/private继承。

#include <iostream>
using namespace std;
// 基类
class Base {
public:
    int public_var;
    void public_func() {
        cout << "这是基类的公有函数" << endl;
    }
protected:
    int protected_var;
    void protected_func() {
        cout << "这是基类的保护函数" << endl;
    }
private:
    int private_var;
    void private_func() {
        cout << "这是基类的私有函数" << endl;
    }
};

// 公有继承的派生类
class PublicDerived : public Base {
public:
    void accessMembers() {
        public_var = 10;  // 可以访问基类的公有成员
        protected_var = 20;  // 可以访问基类的保护成员
        public_func();  // 可以调用基类的公有函数
        protected_func();  // 可以调用基类的保护函数
    }
};

// 私有继承的派生类
class PrivateDerived : private Base {
public:
    void accessMembers() {
        public_var = 30;  // 可以访问,但在派生类中变成私有成员了
        protected_var = 40;  // 可以访问,但在派生类中变成私有成员了
        public_func();  // 可以调用,但在派生类中变成私有函数了
        protected_func();  // 可以调用,但在派生类中变成私有函数了
    }
};

// 保护继承的派生类
class ProtectedDerived : protected Base {
public:
    void accessMembers() {
        public_var = 50;  // 可以访问,在派生类中变成保护成员
        protected_var = 60;  // 可以访问,在派生类中变成保护成员
        public_func();  // 可以调用,在派生类中变成保护函数
        protected_func();  // 可以调用,在派生类中变成保护函数
    }
};

int main() {
    PublicDerived pub_derived;
    pub_derived.public_var = 5;
    pub_derived.public_func();

    PrivateDerived pri_derived;
    // pri_derived.public_var = 6;  // 错误,在外部不能访问私有继承后变成私有的成员了
    // pri_derived.public_func();  // 错误,在外部不能访问私有继承后变成私有的函数了

    ProtectedDerived pro_derived;
    // pro_derived.public_var = 7;  // 错误,在外部不能访问保护继承后变成保护的成员了
    // pro_derived.public_func();  // 错误,在外部不能访问保护继承后变成保护的函数了

    return 0;
}

1.3继承类模板

namespace stack
{
	template<class T>
class stack : public std::vector<T>
{
public:
	void push(const T& x)// 基类是类模板时,需要指定⼀下类域,
	// 否则编译报错:error C3861: “push_back”: 找不到标识符
	// 因为stack<int>实例化时,也实例化vector<int>了
	// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
	vector<T>::push_back(x);
     //push_back(x);
	 void pop()
	{
		vector<T>::pop_back();
	}
	const T& top()
	{
		return vector<T>::back();
	}
	bool empty()
	{
		return vector<T>::empty();
	}
  };
}

2.继承中的函数隐藏

隐藏规则:
1.在继承体系中基类和派生类都有独立的作用域。
2.派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。
(在派⽣类成员函数中,可以使用基类::基类成员显示访问)

//Base是基类,Derived继承Base
//Base和Derived都有printf这个函数
Derived d;
d.printf()//调用派生类printf函数
d.Base::printf();//使用作用域解析运算符访问基类被隐藏的print函数

3.需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4.注意在实际中在继承体系⾥⾯最好不要定义同名的成员。
重载和隐藏的区别:
重载(Overloading):
重载发生在同一个类中,是指函数名相同但参数列表不同的多个函数。例如,在一个类中可以有 int add(int a, int b) 和 double add(double a, double b) ,它们通过参数的类型、个数或者顺序的不同来区分,编译器会根据调用时的实际参数来决定调用哪个重载函数。
隐藏(Hiding):
隐藏发生在基类和派生类之间,是因为函数名相同而产生的现象。 并且隐藏不像重载那样要求参数列表不同,只要函数名相同就会隐藏基类中的同名函数。而且,派生类对象调用同名函数时,默认调用的是派生类中定义的函数,而不是基类中的函数。

3.派生类的默认成员函数

默认成员函数,默认的意思就是指我们不写,编译器会变我们⾃动生成⼀个。
在这里插入图片描述
1.派生类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。如果基类没有默认的构造
函数,则必须在派⽣类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调⽤基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。需要注意的是派生类的
operator=隐藏了基类的operator=,所以显示调用基类的
operator=
,需要指定基类作用域。
4.派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。因为这样才能保证派
生类对象先清理派⽣类成员再清理基类成员的顺序。
4. 派生类对象初始化先调⽤基类构造再调派生类构造。
5. 派生类对象析构清理先调⽤派⽣类析构再调基类的析构。
6. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派生类析构函数和基类析构函数构成隐藏关系。
在这里插入图片描述
以下代码来说明上面的默认成员函数调用的规则。

class Person
{
public :
	 Person(const char* name = "peter")
	 : _name(name )
	{
		cout<<"Person()" <<endl;
	}
	Person(const Person& p)
	: _name(p._name)
	{
		cout<<"Person(const Person& p)" <<endl;
	}
	Person& operator=(const Person& p )
	{
		cout<<"Person operator=(const Person& p)"<< endl;
	if (this != &p)
	_name = p ._name;
	return *this ;
	}
	~Person()
	{
		cout<<"~Person()" <<endl;
	}
protected :
	string _name ; // 姓名
};
class Student : public Person
{
public :
	Student(const char* name, int num)
	: Person(name)
	, _num(num )
	{
		cout<<"Student()" <<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;
	}
		return *this ;
	}
	~Student()
	{
		cout<<"~Student()" <<endl;
	}
protected :
		int _num ; //学号
	};
int main()
{
	Student s1 ("jack", 18);
	Student s2 (s1);
	Student s3 ("rose", 17);
	s1 = s3 ;
	return 0;
}

实现⼀个不能被继承的类
⽅法1:基类的构造函数私有,派⽣类的构成必须调⽤基类的构造函数,但是基类的构成函数私有化以后,派⽣类看不见就不能调用了,那么派⽣类就⽆法实例化出对象。
⽅法2:C++11新增了⼀个final关键字,final修改基类,派生类就不能继承了。

// C++11的用方法
class Base final
{
public:
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
private:
// C++98的方法
/*Base()
{}*/
};
class Derive :public Base
{
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}

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

4.继承中的切割

在继承关系中,“切割”(也称为对象切割)是指当把派生类对象赋值给基类对象或者将派生类对象作为参数传递给以基类对象为参数的函数时,派生类对象会被“切割”,只保留基类部分的信息。这是因为基类对象没有足够的空间来存储派生类对象中的所有成员。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分,会导致数据丢失

class Derived : public Base {
public:
    int derivedData;
    Derived(int base, int derived)
	: Base(base)
	, derivedData(derived)
	{}
};

void func(Base b) {
    cout << "Base类对象中的数据: " << b.baseData << endl;
}
int main() {
    Derived d(10, 20);
    func(d);  // 派生类对象d传递给Base类型的参数,发生对象切割
    return 0;
}

在这个例子中, Derived 类继承自 Base 类。 func 函数的参数是 Base 类型。当把 Derived 类的对象 d 传递给 func 函数时,对象 d 会被切割,只传递和处理 Base 类部分的数据(也就是 baseData ), Derived 类特有的 derivedData 成员在这个过程中被忽略了。
解决方法:
1.可以使用基类指针或基类引用作为函数参数,这样就不会发生对象切割,并且能够利用多态性来正确地处理派生类对象。
例如

void func(Base& b) {
    cout << "Base类对象中的数据: " << b.baseData << endl;
}

int main() {
    Derived d(10, 20);
    func(d);  // 此时不会发生切割,因为是引用传递
    return 0;
}

或者使用指针:

void func(Base* b) {
    cout << "Base类对象中的数据: " << b->baseData << endl;
}
int main() {
    Derived d(10, 20);
    func(&d);  // 此时不会发生切割,因为是指针传递
    return 0;
}

这样,通过引用或指针传递,能够在函数内部正确地访问派生类对象(如果需要访问派生类特有的成员,可能需要进行类型转换),同时避免了对象切割的问题。

5.多继承及其菱形继承问题

5.1继承模型

单继承:⼀个派生类只有⼀个直接基类时称这个继承关系为单继承。
在这里插入图片描述

多继承:⼀个派生类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型
是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后面。

在这里插入图片描述
菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以
看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就
⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议
设计出菱形继承这样的模型的。
在这里插入图片描述

5.2解决菱形继承问题的方法(虚继承)

虚继承:在菱形继承结构中,当两个或多个派生类共同继承一个基类,并且又有一个类继承这些派生类时,为了确保基类在最终派生类中只有一个实例,使用虚继承。这样,在内存布局和对象访问上会更加合理

class Animal {
public:
    int animalData;
};

class Mammal : virtual public Animal {
};
//在继承方式public前面加virtual这个关键字,就变成了虚继承
class Bird : virtual public Animal {
};

class Bat : public Mammal, public Bird {
};

通过虚继承, Bat 类对象中 Animal 类的成员只有一份,解决了数据冗余和二义性问题。在类图表示中,虚继承可以用虚箭头(比如虚线箭头)来表示从 Mammal 和 Bird 类到 Animal 类的继承关系,以突出与普通继承的区别。

6.继承和组合

•public继承是⼀种is-a的关系。也就是说每个派⽣类对象都是一个基类对象。
• 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
•在继承方式中,基类的内部细节对派⽣类可见 。继承⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很大的影响。派⽣类和基类间的依赖关系很强,耦合度高。
•优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好。不过也不太
那么绝对,类之间的关系就适合继承(is-a)那就⽤继承,另外要实现多态,也必须要继承。类之间的
关系既适合⽤继承(is-a)也适合组合(has-a),就用组合

// Tire(轮胎)和Car(⻋)更符合has-a的关系
class Tire {
protected:
	string _brand = "Michelin"; // 品牌
	size_t _size = 17; // 尺⼨
};
class Car {
protected:
	string _colour = "⽩⾊"; // 颜⾊
	string _num = "陕ABIT00"; // ⻋牌号
	Tire _t1; // 轮胎
	Tire _t2; // 轮胎
	Tire _t3; // 轮胎
	Tire _t4; // 轮胎
};
class BMW : public Car {
public:
	void Drive() { cout << "好开-操控" << endl; }
};
// Car和BMW/Benz更符合is-a的关系
class Benz : public Car {
public:
	void Drive() { cout << "好坐-舒适" << endl; }
};
// stack和vector的关系,既符合is-a,也符合has-a
template<class T>
class stack : public vector<T>
{};
template<class T>
class stack
{
public:
	vector<T> _v;
};
int main()
{
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/934947.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

指令周期流程图

例题一 例题二 例题三

生成式AI概览与详解

1. 生成式AI概览&#xff1a;什么是大模型&#xff0c;大模型应用场景&#xff08;文生文&#xff0c;多模态&#xff09; 生成式AI&#xff08;Generative AI&#xff09;是指通过机器学习模型生成新的数据或内容的人工智能技术。生成式AI可以生成文本、图像、音频、视频等多种…

设计模式之原型模式:深入浅出讲解对象克隆

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 原型模式概述 在我们的日常生活中&#xff0c;经常会遇到"复制"这样的场景。比如我们在准备文件时&#xff0c;常常会复印一份原件&a…

集合ArrayList

黑马程序员Java的个人笔记 BV17F411T7Ao p111~p115 目录 集合存储数据类型的特点 创建对象 ArrayList 成员方法 .add 增加元素 .remove 删除元素 .set 修改元素 .get 查询元素 .size 获取长度 基本数据类型对应的包装类 Character 练习 返回多个数据 集合存储…

day10性能测试(2)——Jmeter安装环境+线程组+Jmeter参数化

【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、LoadRunner vs Jmeter 1.1 LoadRunner 1.2 Jmeter 1.3 对比小结 2、Jmeter 环境安装 2.1 安装jdk 2.2 安装Jmeter 2.3 小结 3、Jmeter 文件目录结构 4、Jmeter默认配置修改 5、Jmeter元件、组…

【全连接神经网络】核心步骤及其缺陷

前向传播 计算公式&#xff08;其中一种&#xff09; x1/x2&#xff1a;输入值&#xff0c;一般是神经网络上一层的输出或者输入数据本身&#xff0c;上图中表示两个节点w11 w13&#xff1a;权重&#xff0c;在神经网络中&#xff0c;权重是学习的参数&#xff0c;表示每个输入…

自荐一部IT方案架构师回忆录

作者本人毕业于一个不知名大专院校&#xff0c;所读专业计算机科学技术。2009年开始IT职业生涯&#xff0c;至今工作15年。擅长TSQL/Shell/linux等技术&#xff0c;曾经就职于超万人大型集团、国内顶级云厂商、央国企公司。参与过运营商大数据平台、大型智慧城市ICT、云计算、人…

【密码学】SM4算法

一、 SM4算法简介 SM4算法是中国国家密码管理局于2012发布的一种分组密码算法&#xff0c;其官方名称为SMS4&#xff08;SMS4.0&#xff09;&#xff0c;相关标准为GM/T 0002-2012《SM4分组密码算法》。SM4算法的分组长度和密钥长度均为128比特,采用非平衡Feistel结构。采用32…

番外篇 | 关于YOLOv8网络结构中添加注意力机制的常见方法 | Neck网络

前言:Hello大家好,我是小哥谈。注意力机制是一种神经网络模型,它通过赋予输入不同的权重的处理方式,来使得模型对输入信息的处理更加关注重要的部分。注意力机制在自然语言处理、计算机视觉等领域中得到了广泛的应用。🌈 目录 🚀1.基础概念 🚀2.案例说明 案例…

有序集合ZSET【Redis对象篇】

&#x1f3c6; 作者简介&#xff1a;席万里 ⚡ 个人网站&#xff1a;https://dahua.bloggo.chat/ ✍️ 一名后端开发小趴菜&#xff0c;同时略懂Vue与React前端技术&#xff0c;也了解一点微信小程序开发。 &#x1f37b; 对计算机充满兴趣&#xff0c;愿意并且希望学习更多的技…

JSON语法、序列化/反序列化、(JS、JSON、Java对象间转换)、fastjson库、JS内置对象JSON

目录 一、JSON基础。 &#xff08;1&#xff09;什么是JSON&#xff1f; &#xff08;2&#xff09;JSON对象语法。 1、数据结构。 2、键与值的格式。 3、JSON对象在线解析与格式验证网址。 4、JSON对象格式的完整示例。 二、序列化与反序列化。 &#xff08;1&#xff09;序列…

C#中的string操作详解-截取、分割、连接、替换等

在C#中&#xff0c;string 类提供了许多用于操作字符串的方法&#xff0c;包括截取、分隔和连接等。以下是一些常用字符串操作的介绍和实例&#xff1a; 1. 截取字符串 Substring 方法 用于从字符串中截取子字符串。 语法&#xff1a; //从startIndex开始截取&#xff0c;…

STOP: 0x0000007B

STOP: 0x0000007B 安装电脑&#xff0c;提示出错&#xff0c;硬盘模型错误&#xff0c;SATA模式3种&#xff1a;AHCI、IDE、RAID 高级这个图好像漏了&#xff0c;下次补吧&#xff0c;就在高级里面硬盘模式修改下

echarts自定义仪表盘样式及一些属性了解

目录 一、自定义仪表盘 1.仪表盘相关 2.常用属性 &#xff08;1&#xff09;series &#xff08;2&#xff09;graphic 二、自定义仪表盘 1.基本仪表盘绘制 2.分析结构&#xff0c;分别绘制 &#xff08;1&#xff09;自定义形状 &#xff08;2&#xff09;仪表盘各部…

图神经网络代码学习—基本使用与分类任务

初步接触图神经网络代码 环境配置 对于在多目标跟踪中应用图匹配网络&#xff0c;需要学习使用GNN图神经网络&#xff0c;对于图神经网络的实现需要学习使用一下库和项目来进行实践。 PyG&#xff08;PyTorch Geometric&#xff09;是一个建立在 PyTorch 基础上的库&#xf…

操作系统:死锁与饥饿

目录 死锁概念 饥饿与饿死概念 饥饿和死锁对比 死锁类型 死锁条件&#xff08;Coffman条件&#xff09; 死锁恢复方法 死锁避免 安全状态与安全进程序列&#xff1a; 银行家算法&#xff1a; 死锁检测时机&#xff08;了解&#xff09;&#xff1a; 死锁检测 死锁案…

SkyWalking Helm Chart 4.7.0 安装、配置

https://skywalking.apache.org/events/release-apache-skywalking-kubernetes-helm-chart-4.7.0/https://github.com/apache/skywalking-helm/tree/v4.7.0https://skywalking.apache.org/zh/2020-04-19-skywalking-quick-start/简介 skywalking 是分布式系统的 APM(Applicat…

electron 打包 webview 嵌入需要调用电脑摄像头拍摄失败问题

electron 打包 webview 嵌入需要调用电脑摄像头拍摄失败问题 这篇文章是接我cocos专栏的上一篇文章继续写的&#xff0c;我上一篇文章写的是 cocos 开发触摸屏项目&#xff0c;需要嵌入一个网页用来展示&#xff0c;最后通过 electron 打包成 exe 程序&#xff0c;而且网页里面…

嵌入式Linux应用开发中CAN通信实现

14.1 CAN介绍 14.1.1 CAN是什么? CAN,全称为“Controller Area Network”,即控制器局域网,是国际上应用最广泛的现场总线之一。最初,CAN 被设计作为汽车环境中的微控制器通讯,在车载各电子控制装置 ECU 之间交换信息,形成汽车电子控制网络。比如:发动机管理系统、变速…

Grafana功能菜单介绍

Grafana的功能菜单设计为侧边栏(sidebar)形式,可以折叠隐藏,便于我们更加专注数据的可视化。现将菜单栏各项功能进行编号讲解,如下图所示:① Grafana Logo 在这里插入图片描述 点击Grafana的logo,无论当前处于哪个页面,都会跳转回Home Page(主页)。② 新建与导入用于…