【c++】继承学习(二):探索 C++ 中派生类的默认机制与静态成员共享

2024-05-04 23:04

本文主要是介绍【c++】继承学习(二):探索 C++ 中派生类的默认机制与静态成员共享,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

目录

  • `1.派生类的默认成员函数`
  • `2.继承与友元`
  • `3.继承与静态成员`

朋友们大家好,本篇文章我们来学习继承的第二部分

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

在这里插入图片描述
来看下面的类:

class Person
{
public:Person(const char* name = "jason"): _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:
protected:int _num; //学号
};
  1. Student对象生成的默认构造函数,对内置类型不做处理,对自定义类型调用它的默认构造函数,规则和以前一样
    在这里插入图片描述
    在这里插入图片描述

派生类里面,把父类成员当做一个整体

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

比如的父类的构造函数修改成这种:

Person(const char* name): _name(name)
{cout << "Person()" << endl;
}

现在父类没有默认构造,会编译报错

在这里插入图片描述

这时候需要子类来完成构造函数:

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

这段代码功能:

基类初始化:
Person(name) 调用了基类 Person 的构造函数,并传递了 name 参数,这确保了 Person 类的成员 _name 被正确初始化。写成 Person(name) 就是指示编译器使用 Person 类的接受 const char* 参数的构造函数。如果不这样做,基类成员 _nameStudent 对象构建过程中不会被初始化。

注意

这里不能这样初始化:

Student(const char* name, int num): _name(name), _num(num)
{cout << "Student()" << endl;
}

父类成员需要当做一个整体的一个自定义类型的成员,不能单独对它的成员处理

_name 是基类 Person 的一部分,派生类 Student 没有直接的权限去初始化它。应该使用基类构造函数来初始化

确保基类的构造函数被调用是继承中非常重要的一部分,因为只有基类的构造函数知道如何正确初始化基类定义的成员。上面的修改确保当创建Student 类的对象时,它会首先调用 Person 类的构造函数初始化 _name,然后初始化派生类 Student 的 _num 成员

派生类这里分成了两个部分:父类和自己,父类的调用父类构造函数初始化

成员变量的初始化顺序是根据它们在类定义中出现的顺序,而不是初始化列表中的顺序。因此,基类的构造函数总是首先被调用,再是派生类中定义的成员变量

  1. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化一般情况下默认生成的就够用,如果涉及到深拷贝,就需要自己显示实现
Student(const Student& s): Person(s), _num(s._num)
{cout << "Student(const Student& s)" << endl;
}

在这里插入图片描述

  1. 派生类的operator=必须要调用基类的operator=完成基类的复制
Student& operator = (const Student& s)
{cout << "Student& operator= (const Student& s)" << endl;if (this != &s){Person::operator =(s);_num = s._num;}return *this;
}

这里同名函数构成了隐藏

  1. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
~Student()
{cout << "~Student()" << endl;
}

上面的函数我们都进行了显示调用,但是析构函数不可以
在这里插入图片描述
Student 的析构函数被调用并完成执行后,Person 的析构函数将随即被自动而且默认地调用。这样的设计可以防止基类成员被重复释放或者提前释放,从而导致潜在的错误和资源泄漏

  1. 派生类对象初始化:先调用基类构造再调派生类构造

  2. 派生类对象析构清理:先调用派生类析构再调基类的析构。

  3. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

所以我们想显示调用就需要这样写:

~Student()
{Person::~Person();cout << "~Student()" << endl;
}

但是这里会导致一个问题,析构多调用了一次,就是因为析构函数先调用子类再调用父类的,子类析构函数结束后会自动调用父类析构

2.继承与友元

友元关系不能继承,基类友元不能访问子类私有和保护成员

class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}

这里会编译错误:

在这里插入图片描述

基类将某些函数或类声明为友元,这个友元关系不会自动传递给从派生)。派生类需要自己明确声明哪些函数或类是它的友元

如何解决编译错误:

要解决 Display 函数不能访问 Student 类的 _stuNum 成员的问题,可以在 Student 类中也声明 Display 为友元:

class Student : public Person
{
public:friend void Display(const Person& p, const Student& s);
protected:int _stuNum; // 学号
};

现在,Display 函数是 PersonStudent 两个类的友元,可以访问两个类的保护成员

3.继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

class Person
{
public:Person() { ++_count; }
protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count = 0;class Student : public Person
{
protected:int _stuNum; // 学号
};class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
void TestPerson()
{Student s1;Student s2;Student s3;Graduate s4;cout << " 人数 :" << Person::_count << endl;Student::_count = 0;cout << " 人数 :" << Person::_count << endl;
}

静态成员是与类本身关联的,而不是与类的单个实例相关联。静态成员变量在所有实例中共享,而静态成员函数可以在没有类实例的情况下直接通过类名调用。当静态成员被继承时,派生类共享同一个静态成员副本,因为静态成员是属于类的,不属于类的任何具体对象

在上面代码中,Person 类有一个静态成员 _count,它被用来统计该类的实例数量。每当创建一个 Person 类的实例或者它的派生类的实例时,构造函数都会递增 _count,因此 StudentGraduate 的示例也会递增 _count

分析:

  1. Person::_count 是静态成员变量,并且初始化为 0。它统计 Person 及其派生类 StudentGraduate 的对象个数。

  2. Student 类继承自 Person,没有定义新的静态成员变量,因此它共享 Person 类的静态成员 _count

  3. Graduate 类继承自 Student,也没有定义新的静态成员变量,因此它同样共享 Person 类的静态成员 _count

void TestPerson()
{Student s1; // 在构造时, Person::_count 变为 1Student s2; // Person::_count 变为 2Student s3; // Person::_count 变为 3Graduate s4; // Person::_count 变为 4cout << " 人数 :" << Person::_count << endl; // 输出 " 人数 :4"Student::_count = 0; // 重置 Person::_count 为 0cout << " 人数 :" << Person::_count << endl; // 输出 " 人数 :0"
}

TestPerson 函数中创建了三个 Student 对象和一个 Graduate 对象,每次构造函数调用都会递增 _count,因此打印 _count 的结果为 4。

然后,将静态成员 _count 通过 Student 类重置为 0。注意,这里使用 Student::_count 访问的实际上还是 Person 类的静态成员 _count,因为 Student 并没有重新定义它。这表明无论通过类 Person 还是它的任何派生类访问静态成员 _count,结果都是相同的。因此,第二次打印 _count 的结果是 0

静态成员的继承性质:静态成员在基类及其派生类之间是共享的,而不是每个派生类都有独立的静态成员副本。因此,无论是在基类还是派生类中访问静态成员,访问的都是同一个数据。在设计类层次结构时,这一点非常重要,因为静态成员的行为可能会影响整个类族

这篇关于【c++】继承学习(二):探索 C++ 中派生类的默认机制与静态成员共享的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/960291

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

使用Nginx来共享文件的详细教程

《使用Nginx来共享文件的详细教程》有时我们想共享电脑上的某些文件,一个比较方便的做法是,开一个HTTP服务,指向文件所在的目录,这次我们用nginx来实现这个需求,本文将通过代码示例一步步教你使用... 在本教程中,我们将向您展示如何使用开源 Web 服务器 Nginx 设置文件共享服务器步骤 0 —

更改docker默认数据目录的方法步骤

《更改docker默认数据目录的方法步骤》本文主要介绍了更改docker默认数据目录的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1.查看docker是否存在并停止该服务2.挂载镜像并安装rsync便于备份3.取消挂载备份和迁

Spring使用@Retryable实现自动重试机制

《Spring使用@Retryable实现自动重试机制》在微服务架构中,服务之间的调用可能会因为一些暂时性的错误而失败,例如网络波动、数据库连接超时或第三方服务不可用等,在本文中,我们将介绍如何在Sp... 目录引言1. 什么是 @Retryable?2. 如何在 Spring 中使用 @Retryable

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

Python使用pysmb库访问Windows共享文件夹的详细教程

《Python使用pysmb库访问Windows共享文件夹的详细教程》本教程旨在帮助您使用pysmb库,通过SMB(ServerMessageBlock)协议,轻松连接到Windows共享文件夹,并列... 目录前置条件步骤一:导入必要的模块步骤二:配置连接参数步骤三:实例化SMB连接对象并尝试连接步骤四:

Linux使用粘滞位 (t-bit)共享文件的方法教程

《Linux使用粘滞位(t-bit)共享文件的方法教程》在Linux系统中,共享文件是日常管理和协作中的常见任务,而粘滞位(StickyBit或t-bit)是实现共享目录安全性的重要工具之一,本文将... 目录文件共享的常见场景基础概念linux 文件权限粘滞位 (Sticky Bit)设置共享目录并配置粘

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用