【C++】——继承【上】

news2024/10/9 21:06:51
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/2200019.html

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

相关文章

MPLS解决BGP路由黑洞问题

文章目录 MPLS应用实验实验配置 MPLS应用实验 实验目的&#xff1a;解决BGP中IBGP邻居之间存在的问题 MPLS解决BGP路由黑洞问题实验 配置完基本的MPLS和BGP操作之后&#xff0c;只有R2和R5上面有两个私网的路由&#xff0c;中间的设备没有私网路由&#xff0c;这时候默认还是走…

Linux源码阅读笔记-USB驱动分析

基础层次详解 通用串行总线&#xff08;USB&#xff09;主要用于连接主机和外部设备&#xff08;协调主机和设备之间的通讯&#xff09;&#xff0c;USB 设备不能主动向主机发送数据。USB 总线采用拓扑&#xff08;树形&#xff09;&#xff0c;主机侧和设备侧的 USB 控制器&a…

IT招聘乱象的全面分析

近年来&#xff0c;IT行业的招聘要求似乎越来越苛刻&#xff0c;甚至有些不切实际。许多企业在招聘时&#xff0c;不仅要求前端工程师具备UI设计能力&#xff0c;还希望后端工程师精通K8S服务器运维&#xff0c;更有甚至希望研发经理掌握所有前后端框架和最新开发技术。这种招聘…

RAG测评关键指标

解读RAG测评&#xff1a;关键指标与应用分析 ©作者|CodeDan 来源|神州问学 一、RAG介绍 1.1 简介 RAG&#xff08;Retrieval-Augmented Generation&#xff09;是一种结合信息检索与文本生成的技术&#xff0c;旨在提高大型语言模型&#xff08;LLM&#xff09;在回答复…

ROS理论与实践学习笔记——4 ROS的常用组件之TF坐标变换

tf:TransForm Frame,坐标变换 坐标系:ROS 中是通过坐标系统开标定物体的,确切的将是通过右手坐标系来标定的。 作用:在 ROS 中用于实现不同坐标系之间的点或向量的转换。 说明:在ROS中坐标变换最初对应的是tf,不过在 hydro 版本开始, tf 被弃用,迁移到 tf2,后者…

docker 搭建 vue3 + vite

vue3发布了,今天就分享一下我使用docker 搭建 vue3 vite 开发环境。至于为什么使用docker搭建&#xff0c;因为多版本可以快速切换&#xff0c;和本地环境避免冲突。好了话不多说我们开始吧。 1. 准备资料 Docker Desktop wsl2 ubuntu 下载地址 : https://www.docker.…

实验室认证需要准备哪些文件材料?

实验室认证需要准备的文件材料通常包括以下几类&#xff1a; 一、法律地位文件 实验室成立文件及营业执照&#xff1a;包括实验室的成立证明文件、单位营业执照等&#xff0c;以证明实验室的法律地位和合法性。 人员任命文件&#xff1a;最高管理者&#xff08;如总经理&…

QT 实现QMessageBox::about()信息自定义显示

这是我记录Qt学习过程的第四篇心得文章&#xff0c;主要是方便自己编写的应用程序显示“关于信息”&#xff0c;对QMessageBox::about()输入信息进行规范&#xff0c;可以设置应用程序名称&#xff0c;通过定义宏从pro文件获取应用程序版本号&#xff0c;以及编译程序的QT版本、…

关于C语⾔内存函数 memcpy memmove memset memcmp

memcpy使⽤和模拟实现 void * memcpy ( void * destination, const void * source, size_t num ); 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。 这个函数在遇到 \0 的时候并不会停下来。 如果source和destination有任何的重叠&am…

Linux环境通过APT 仓库安装版PostgreSQL 数据库实战

Linux环境通过APT 仓库安装版PostgreSQL 数据库是运维人员常见的需求之一&#xff0c;今天我们一步一步演示一下&#xff1a; 1、添加 PostgreSQL APT 仓库 确保你的系统更新&#xff0c;然后添加 PostgreSQL 的官方 APT 仓库。 sudo apt update sudo apt install -y wget w…

原来机器学习那么简单——决策树回归

引言&#xff1a; 在正文开始之前&#xff0c;首先给大家介绍一个不错的人工智能学习教程&#xff1a;https://www.captainbed.cn/bbs。其中包含了机器学习、深度学习、强化学习等系列教程&#xff0c;感兴趣的读者可以自行查阅。 一、算法介绍 回归树是决策树的一种&#xff…

[已完结] Authentication Lab —— 靶场笔记合集

Authentication Labhttps://authlab.digi.ninja/ 0x01&#xff1a;Authentication Lab 靶场简介 Authentication Lab 是由 DigiNinja 提供的&#xff0c;一个专注于身份验证和授权漏洞的实验平台。该网站旨在提供一个可以让用户探索和实践各种常见与不常见的身份验证与授权漏…

带你深入浅出设计模式:十、责任链模式:设计模式中的多米诺骨牌效应

此为设计模式第十谈&#xff01; 用总-分-总的结构和生活化的例子给你讲解设计模式&#xff01; 码农不易&#xff0c;各位学者学到东西请点赞收藏支持支持&#xff01; 开始部分&#xff1a; 总&#xff1a;责任链的本质是使多个对象都有机会处理请求&#xff0c;将这些对象…

指针和引用区别

目录 指针 指针类型 野指针 二级指针 Const修饰指针 引用 引用的作用 常引用 引用和指针的对比 引用能够完全替换指针吗&#xff1f; 指针 指针是C语言中的概念&#xff0c;它是指计算机储存内容的地址。指针它的值指向存在电脑储存器中另一个地方的值。通过地址能找…

玩机搞机基本常识-----如何在 Android 中实现默认开启某个功能 修改方法列举

我们有时候需要对安卓系统进行修改。实现其中的某些功能。让用户使用得心应手。节约时间。那么如果要实现系统中的有些功能选项开启或者关闭。就需要对系统有一定的了解。那么在 Android 中实现默认开启某个功能可以通过以下几种方式&#xff1a; 一、在应用的设置中添加选项 …

Chromium 中js Fetch API接口c++代码实现(二)

Chromium 中JavaScript Fetch API接口c代码实现&#xff08;一&#xff09;-CSDN博客 接着上一篇继续介绍调用&#xff0c;上函数堆栈。 1、打开http://192.168.8.1/chfs/shared/test/test02.html 此标签进程ID12484&#xff0c; 2、打开vs附加上此进程ID12484 3、点击页面测…

uni-app 开发的应用快速构建成鸿蒙原生应用

uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;它支持编译到 iOS、Android、小程序等多个平台。对于 HarmonyOS&#xff08;鸿蒙系统&#xff09;&#xff0c;uni-app 提供了特定的支持&#xff0c;允许开发者构建鸿蒙原生应用。 一、uni-app 对 HarmonyOS 的支…

分治算法(7)_归并排序_计算右侧小于当前元素的个数

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 分治算法(7)_归并排序_计算右侧小于当前元素的个数 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&…

公司防泄密软件哪个好?6款公司内部文件防泄密软件,2024超好用推荐!

企业的核心机密就如同生命之源&#xff0c;然而&#xff0c;数据泄露的风险也随之而来&#xff0c;让不少企业头疼不已。 面对这一挑战&#xff0c;选择一款高效、可靠的防泄密软件显得尤为重要。 那么&#xff0c;公司防泄密软件哪个好&#xff1f; 接下来&#xff0c;就让我…

攻防世界---->[简单] 初识RSA

做题笔记。 下载 是一个.py的文件。 用 Notepad打开瞅瞅。 分析&#xff1a; L (p-1)*(q-1) dgmpy2.invert(e,L) 求逆元快速算出来&#xff1a;invert(e,φ(N)) 求出d值。 n p*q pq p*(q-1) qp q*(p-1) L 【q*(p-1) * p*(q-1)】 // p*q >>> (p-1)*(…