【C++】——继承

news2024/11/20 15:18:18
P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。
P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。

  

在这里插入图片描述

                                           博主主页:Yan. yan.
                                              C语言专栏
                                            数据结构专栏
                                         力扣牛客经典题目专栏
                                                     C++专栏

文章目录

  • 继承的概念与定义
    • 1、继承的概念
    • 2、继承的定义
      • 2.1、继承的语法形式
      • 2.2、继承中类的叫法
      • 2.3、继承后的子类成员访问权限
  • 基类与派生类的赋值转换
    • 1、派生类对象赋值给基类对象
    • 2、派生类对象的引用赋值给基类对象
    • 3、派生类对象的指针赋值给基类对象
    • 4、基类指针赋值给派生类指针
  • 继承的作用域
    • 1、同名变量
    • 2、同名函数
  • 派生类中的默认成员
    • 1、对象的构造和析构遵循特定的顺序
    • 2、派生类构造函数调用基类构造函数
    • 3、析构函数的特殊处理
    • 4、拷贝构造与赋值重载必须调用基类的拷贝构造与赋值重载完成对基类的初始化

继承的概念与定义

1、继承的概念

  继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。


举一个简单的例子

一个人,他具有年龄,姓名等个人信息,然后我们可以将这些信息整合为一个Person类,如果说我们还想要定义一个Student的类,这些学生当然也是人,因此我们可以复用Person这个类,然后再添加一些其他的信息,例如学号之类的。

2、继承的定义

2.1、继承的语法形式

  继承的定义时=是通过在子类的声明中使用基类, 并加上冒号“:” 和继承方式(public \ protected \ private)来实现的。

class Person
{
	//.....基类
};

//      子      继承方式  父
class Student : public Person
{
	//....派生类
};

例:

class Person
{
public:
	void print()
	{
		cout << _name << endl;
		cout << _address << endl;
		cout << _age << endl;
	}
private:
	string _name = "张三 ";  // 姓名
	string _address = "河北 ";// 地址
	int _age = 18 ;// 年龄
};

//      子      继承方式  父
class Student : public Person
{
private:
	string _tel = "189 ";// 电话
	int _id = 123321;
};

int main()
{
	Person p;
	Student s;

	return 0;
}

在这里插入图片描述
通过监视窗口可以看出,Student类继承了Person类的成员与函数。

2.2、继承中类的叫法

(1)子类(或派生类): 这是指继承其他类(即父类)的类。子类可以使用父类的所有非私有属性和方法,同时也可以添加自己的属性和方法或重写父类的方法。
(2)父类(或基类):这是指被其他类(即子类)继承的类。父类提供了通用的属性和方法,这些可以被子类继承和使用。

2.3、继承后的子类成员访问权限

不同的继承方式产生的继承效果自然也不一样。
如图:
在这里插入图片描述

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 最好显示的写出继承方式。
  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

基类与派生类的赋值转换

  在面向对象编程中,基类和派生类之间的赋值转换涉及到对象的类型兼容性和多态性。

1、派生类对象赋值给基类对象

  派生类对象可以赋值给基类的对象。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。

  相反,基类成员无法赋值给派生类成员,因为有些成员派生类有,而基类没有。

class Person
{
public:
    Person(string name = "Kana", int age = 18, double height = 1.50) :
        _name(name),
        _age(age),
        _height(height)
    {
        // ...
    }

    void Print() const
    {
        cout << _name << endl;
        cout << _age << endl;
        cout << _height << endl;
    }

private:
    string _name; // 姓名  
    int _age;     // 年龄  
    double _height; // 身高  
};

class Student : public Person
{
public:
    Student(string name = "Kana", int age = 18, double height = 1.50, int id = 233333, int grade = 10)
        : Person(name, age, height), _id(id), _grade(grade) {}

    void Print() const
    {
        // 首先调用基类的 Print 方法  
        Person::Print();
        // 然后打印学生特有的属性  
        cout << "ID: " << _id << endl;
        cout << "Grade: " << _grade << endl;
    }

private:
    int _id;    // 学号  
    int _grade; // 年级  
};

int main()
{
    Student s;
    s.Print();

    // 尝试将 Student 对象切片为 Person 对象(不推荐,因为会丢失信息)  
    Person p = s;
    p.Print(); // 仅打印 Person 的信息(姓名、年龄、身高)  

    return 0;
}

在这里插入图片描述

2、派生类对象的引用赋值给基类对象

  我们可以将一个派生类对象的引用赋值给一个基类类型的引用,而不需要const修饰符。

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

class Student :public Person
{
public:
	void Print()
	{
		cout << "Student Print()" << endl;
	}
};

int main()
{
	Student s;
	Person& p = s;
	p.Print();

	return 0;
}

在这里插入图片描述

3、派生类对象的指针赋值给基类对象

  派生类对象的指针可以赋值给基类对象的指针。

class Person
{
public:
    void Print() const
    {
        cout << "Person: Name = " << _name << ", Age = " << _age << endl;
    }
 
    Person(string name, int age) : _name(name), _age(age) {}
 
private:
    string _name;
    int _age;
};
 
class Student : public Person
{
public:
    void Print() const
    {
        cout << "Student: ID = " << _id << ", Grade = " << _grade << endl;
    }
 
    Student(string name, int age, int id, int grade) : Person(name, age), _id(id), _grade(grade) {}
 
private:
    int _id;    // 学号    
    int _grade; // 年级    
};
 
int main()
{
    Student student("Kana", 18, 12345, 10);
 
    // 将Student对象的指针赋值给Person对象的指针  
    Person* p = &student;
 
    p->Print();    
 
    // 如果要访问Student特有的成员,需要使用Student类型的指针或引用  
    Student* s = &student;
    s->Print();  
 
    return 0;
}

在这里插入图片描述

4、基类指针赋值给派生类指针

  在C++中,将基类指针直接强制转换为派生类指针是一种危险的做法,通常是不被推荐的,因为它违反了类型安全的原则,并且可能导致未定义行为,包括越界访问或访问无效内存。

	Person p;
	Student *s = (Student*) & p; // right

1.派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
2.基类对象不能赋值给派生类对象。
3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

继承的作用域

  继承的作用域决定了从基类继承到派生类的成员(包括变量和方法)的访问权限。
  在C++的继承体系中, 派生类和基类都各自拥有独立的作用域,当派生类和基类中定义了相同的变量(包括成员和函数)时,派生类的成员会“隐藏”或者“重定义”基类中的同名成员,这意味着在派生类的作用域内,直接访问该同名成员将引用派生类的成员,而不是基类的成员。

1、同名变量

class Person
{
protected:
	int _id = 123;
	int _age = 18;
};

class Student : public Person
{
public:
	void print()
	{
		cout << _id << endl;
		cout << _age << endl;
		cout << _num << endl;

	}
private:
	int _age = 20;
	int _num = 3;
};

int main()
{
	Student s;
	s.print();

	return 0;
}

派生类和基类中都含有名为_age的成员变量,打印结果如下:
在这里插入图片描述
如果想要打印基类中的_age,则需要使用 :: 限定符:
在这里插入图片描述

2、同名函数

class A
{
public:
	void fun()
	{
		cout << "fun()" << endl;
	}
};
 
class B : public A
{
public:
	void fun(int i)
	{
		A::fun();
		cout << "fun(int i)->" << i << endl;
	}
};
 
int main()
{
	B b;
	b.fun(1);
};

在这里插入图片描述
B 中的 fun 和 A 中的 fun 构成隐藏,成员函数满足函数名相同就构成隐藏。

由于函数重载针对的是同一个作用域的函数,而基类与派生类直接作用域不同。因此不是函数重载。

同样的,如果需要访问其他作用域的函数,我们需要使用 :: 操作符:

派生类中的默认成员

我们知道:在类中有6个默认成员函数,如果不显示定义,编译会自动生成。

那么在派生类中,这些成员函数如何生成?
在这里插入图片描述

1、对象的构造和析构遵循特定的顺序

对象的构造和析构遵循特定的顺序,以确保对象的正确初始化和清理。

构造函数调用顺序:

(1)创建派生类对象时,从最顶层的基类开始,逐层向下调用构造函数,直到派生类。
(2)接着,按照派生类中成员变量的声明顺序初始化成员变量(若成员是对象,则调用其构造函数)。
(3)最后,执行派生类构造函数体中的代码。

析构函数调用顺序:

(1)销毁派生类对象时,首先调用派生类的析构函数。
(2)然后,按照成员变量声明的逆序调用成员变量的析构函数(若成员是对象)。
(3)最后,从最顶层的基类开始,逐层向上调用析构函数,直到派生类的基类。

class Person
{
public:
	Person(string name = "Kana")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
private:
	string _name; // 姓名
};
class Student : public Person
{
public:
	Student()
	{
		cout << "Student()" << endl;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
private:
	int _id;
};

int main()
{
	Student s;
	return 0;
}

在这里插入图片描述

2、派生类构造函数调用基类构造函数

(1)派生类的构造函数必须调用基类的构造函数来初始化基类的成员。
(2)如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表中显式调用基类的一个构造函数。

class Person 
{
public:
    // Person类的构造函数,用于初始化名字
    Person(const char* name) : 
        _name(name) 
    {
        // ...
    }
    // Person类的拷贝构造函数
    Person(const Person& p) : 
        _name(p._name) 
    {
    
    }
private:
    string _name;
};
 
class Student : public Person 
{
public:
    // Student类的构造函数,接收学号和名字
    Student(int id, const char* name) : 
        _id(id), 
        Person(name) 
        {
        // ...
        }
    // Student类的默认构造函数
    Student() : 
        Person("Default Student Name"), 
        _id(0) 
        {
            // ...
        }
private:
    int _id;
};

3、析构函数的特殊处理

因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destructor()。

4、拷贝构造与赋值重载必须调用基类的拷贝构造与赋值重载完成对基类的初始化

class Person 
{
public:
    // 默认构造函数  
    Person(const string& name) : 
        _name(name) 
        {
        // ...
        }
 
    // 拷贝构造函数  
    Person(const Person& p) : 
        _name(p._name) 
        {
            cout << "Copy Person(" << _name << ")" << endl;
        }
 
    // 赋值操作符重载  
    Person& operator=(const Person& p) 
    {
        if (this != &p) 
        {
            _name = p._name;
            cout << "Assign Person(" << _name << ")" << endl;
        }
        return *this;
    }
 
    // 析构函数  
    ~Person() 
    {
        cout << "~Person(" << _name << ")" << endl;
    }
 
    string _name;
};
 
class Student : public Person 
{
public:
    // 构造函数  
    Student(int num, const string& name) : Person(name), _num(num) {
        cout << "Student(" << _num << ", " << _name << ")" << endl;
    }
 
    // 拷贝构造函数  
    Student(const Student& s) : Person(s), _num(s._num) {
        cout << "Copy Student(" << _num << ", " << _name << ")" << endl;
    }
 
    // 赋值操作符重载  
    Student& operator=(const Student& s) 
    {
        if (this != &s) 
        {
            Person::operator=(s); // 调用基类的赋值操作符  
            _num = s._num;
        }
        return *this;
    }
 
    // 析构函数  
    ~Student() 
    {
        cout << "~Student(" << _num << ", " << _name << ")" << endl;
    }
    int _num;
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入解析MySQL事务管理:ACID特性与基本操作

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

自动猫砂盆是养猫新型智商税吗?测评2024年热门款智能猫砂盆分享

铲屎官们只要一察觉到猫主子拉屎&#xff0c;就要马上去铲掉&#xff0c;这不仅是为了猫砂盆中其他干净的猫砂&#xff0c;更是为了防止猫屎残留发臭&#xff0c;特别是便便这种东西&#xff0c;一旦放久了就很招虫子&#xff0c;家里出现这些虫子又要大扫除消杀&#xff0c;特…

2024 年顶级智能文档处理解决方案

在当今的数字时代&#xff0c;智能文档处理(IDP) 对于提高业务效率和降低成本至关重要。IDP 可实现文档处理的自动化&#xff0c;最大限度地减少人工劳动和错误。由于有众多 IDP 解决方案可供选择&#xff0c;因此选择合适的解决方案可能具有挑战性。 本指南回顾了 10 款最…

Android Handler消息机制完全解析-同步屏障(三)

Android 消息机制Handler完全解析(一) Android 消息机制Handler完全解析(二) 前面两篇我们主要讲了Handler消息机制的一些基础&#xff0c;今天来看下消息屏障&#xff0c;通过本篇文章你将学到如下知识点 (1)什么是同步屏障 (2)为什么要有同步屏障 (3)同步屏障的原理 (4…

获取时隔半个钟的三天

摘要&#xff1a; 今天遇到需求是配送时间&#xff0c;时隔半个钟的排线&#xff01;所以需要拼接时间&#xff01;例如2024-10-08 14&#xff1a;30&#xff0c;2024-10-08 15&#xff1a;00&#xff0c;2024-10-08 15&#xff1a;30 <el-form-item label"配送时间&a…

24下软考中级系统集成项目管理工程师怎么备考?

备考资料&#xff1a; 1.教材 教材可以准备由清华大学出版社出版的系统集成项目管理工程师教材&#xff0c;这也是官方所推荐的教材&#xff0c;准备这本书是绝对没错的。 2.真题 真题也是在备考过程中少不了的资料之一&#xff0c;而且系统集成项目管理工程师考试就是需要多…

初始项目托管到gitee教程,开箱即用

0.本地仓库与远程仓库关联&#xff08;需先在gitee创建仓库&#xff09; ①打开powershell生成ssh key ssh-keygen -t ed25519 -C "Gitee SSH Key"-t key 类型-C 注释 生成成功如下&#xff0c;并按下三次回车 ②查看公私钥文件 ls ~/.ssh/输出&#xff1a; id_…

华为---Super VLAN简介及示例配置

目录 1. Super VLAN技术产生背景 2. Super VLAN概念 3. Super VLAN应用场景 4. Super VLAN工作原理 5. Super-VLAN主要配置命令 6. Super-VLAN主要配置步骤 7. 示例配置 7.1 示例场景 7.2 网络拓扑 7.3 配置代码 7.4 代码解析 7.5 测试验证 1. Super VLAN技术产生背…

虹软人脸 报错 Can‘t find dependent libraries

系列文章目录 文章目录 系列文章目录一、虹软人脸 报错 Can‘t find dependent libraries 一、虹软人脸 报错 Can‘t find dependent libraries 在项目中使用了 虹软 人脸识别SDK&#xff0c;环境一直出错。 错误&#xff1a; Can’t find dependent libraries 从错误信息来…

项目启动 | 盘古信息赋能奥尼视讯数字化转型升级,实现全面数智化发展

随着信息技术的飞速发展与全球市场竞争的日益激烈&#xff0c;传统制造业正面临生存和发展的危机&#xff0c;制造企业为谋求发展&#xff0c;纷纷开启数字化转型之路&#xff0c;深度融入数字技术&#xff0c;实现生产流程的智能化、管理模式的精细化以及产品服务的个性化&…

[面试] java开发面经-1

前言 目录 1.看到你的简历里说使用Redis缓存高频数据&#xff0c;说一下Redis的操作 2.说一下Redis的缓存击穿、缓存穿透、缓存雪崩 3.你的项目中使用了ThreadLocal&#xff0c;那么当有两个请求同时发出时&#xff0c;会怎么处理&#xff0c;可以同时处理两个请求吗 4.使用…

CUDA、Pytorch、Pycharm的安装与配置

文章目录 一、CUDA安装1.检查英伟达驱动支持的最高CUDA版本 二、Pytorch的安装与环境配置1.选择是下载CPU版本还是GPU版本2.上Pytorch官网找到安装命令3.运行指令(1)CPU版本(2)GPU版本 4.验证5.安装其他所需模块(0)安装torch(1)安装Matplotlib(2)安装 pillow&#xff08;可能an…

高效稳压,YB2411 DCDC降压芯片助力高电压功率转换系统

在现代的科技发展中&#xff0c;高电压功率转换系统的需求越来越多。为满足市场需求&#xff0c;我们推出了一款高输入电压DCDC降压芯片——YB2411。 YB2411系列 1>昱灿 YB2411R SOT23-6 DC-DC高压降压 36V 0.6A 2>昱灿 YB2411SR SOT23-6 DC-DC高压降压 60V 0.8A YB241…

【simulink仿真模型】Buck变换器闭环控制,电力电子仿真模型

摘要 本文介绍了基于Simulink的Buck变换器闭环控制系统的设计与仿真。通过对Buck变换器的数学模型进行建模&#xff0c;并引入PI控制器对输出电压进行实时调节&#xff0c;实现了系统的稳态控制。仿真结果显示&#xff0c;该闭环控制系统能够快速响应负载变化&#xff0c;保持…

头戴式耳机性价比推荐有哪些?头戴式耳机性价比之王推荐

这不是马上就要双十一了&#xff1f;对于环境比较吵的人来说&#xff0c;趁着最近双11开始&#xff0c;是购买耳机的好时机。即将入冬&#xff0c;佩戴头戴式耳机频率越来越多&#xff0c;包裹着耳朵很舒适保暖。有的人入耳式已经非常多了&#xff0c;而且同样的价格&#xff0…

地图箭头方向检测系统源码分享

地图箭头方向检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

哈佛大学俩学生开发出一种 AI 眼镜,看你一眼就能扒光所有个人信息

最近&#xff0c;科幻场景中的一个设想变成了现实&#xff1a;一副眼镜能够一瞥便获取人的全部信息&#xff01; 两位来自哈佛大学的学生开发了一个名为I-XRAY的项目&#xff0c;该项目利用智能眼镜结合面部识别技术&#xff0c;能够实时分析并获取被捕捉者的个人信息。这些信…

ChatTTS使用demo示例(包含长文本生成语音、固定音色pt文件)

ChatTTS使用demo示例&#xff08;包含长文本生成语音、固定音色pt文件&#xff09; 一、配置开发环境 安装anaconda&#xff0c;安装参考文章&#xff1a;https://blog.csdn.net/Q_fairy/article/details/129158178 建议anaconda最新版&#xff1a;https://mirrors.tuna.tsi…

windows11下面使用Pyinstaller打包python程序

文章目录 一、安装Python二、安装pip三、通过pip安装pyinstaller四、使用pyinstaller打包python为二进制程序参考 一、安装Python 我这里直接下载的是Python的可执行程序包&#xff0c;打开即用的版本&#xff0c; 也可以按照以下的教程安装python工具到windows上面 &#…

海洋鱼类图像分类分割系统源码&数据集分享

海洋鱼类图像分类分割系统源码&#xff06;数据集分享 [yolov8-seg-slimneck&#xff06;yolov8-seg-attention等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Globa…