当前位置: 首页 > ai >正文

C++ <继承> 详解

目录

一、引言

二、基本继承语法与示例

2.1 基本语法

2.2 示例代码

三、访问控制与继承

3.1 不同访问控制符的影响

3.2 代码示例

四、子类的构造、拷贝构造、赋值重载和析构

4.1 构造函数

4.2 拷贝构造函数

4.3 赋值重载函数

4.4 析构函数

五、隐藏(重定义)

5.1.示例代码

六、不能被继承的类

6.1示例代码

七、友元与继承

八、静态成员与继承

8.1代码示例

九、多继承与菱形继承

9.1继承模型

 9.2代码示例

十、总结


一、引言

在面向对象编程中,继承是一个非常重要的概念,它允许我们创建一个新的类(子类),这个新类可以继承另一个已存在的类(父类)的属性和方法。C++ 作为一门强大的面向对象编程语言,提供了丰富的继承机制。本文将结合提供的代码,详细解析 C++ 继承的各种特性。

二、基本继承语法与示例

2.1 基本语法

在 C++ 中,使用 : 符号来实现继承,语法如下:

class 子类名 : 继承方式 父类名 {// 子类的成员
};

 

其中,继承方式可以是 publicprotected 或 private,默认情况下是 private

2.2 示例代码

class Person {
protected:string _name = "张三";string _address;string _tel;
private:int _age = 18;
};class Student : public Person {
protected:int _stuid;
};

在这个例子中,Student 类继承自 Person 类,使用了 public 继承方式。

三、访问控制与继承

3.1 不同访问控制符的影响

  • public 继承:父类的 public 成员在子类中仍然是 publicprotected 成员在子类中仍然是 protectedprivate 成员在子类中不可直接访问。
  • protected 继承:父类的 public 和 protected 成员在子类中都变为 protectedprivate 成员在子类中不可直接访问。
  • private 继承:父类的 public 和 protected 成员在子类中都变为 privateprivate 成员在子类中不可直接访问。

 

在实际运⽤中⼀般使⽤都是public继承,⼏乎很少使⽤protetced/private继承,也不提倡使⽤
protetced/private继承,因为protetced/private继承下来的成员都只能在派⽣类的类⾥⾯使⽤,实
际中扩展维护性不强。

3.2 代码示例

class Person {
public:void identity() {cout << "void identity()" << _name << endl;cout << _age << endl;}
protected:string _name = "张三";
private:int _age = 18;
};class Student : public Person {void study() {identity(); // 可以调用父类的 public 方法// cout << _age << endl; // 错误,不能直接访问父类的 private 成员}
};

四、子类的构造、拷贝构造、赋值重载和析构

4.1 构造函数

子类的构造函数必须调用父类的构造函数来初始化父类的那部分成员。如果父类没有默认构造函数,则必须在子类的初始化列表中显式调用父类的构造函数。

class Person {
public:Person(const char* name = "xxx") : _name(name) {cout << "Person()" << endl;}
protected:string _name;
};class Student : public Person {
public:Student(const char* name, int nums, const char* address): Person(name), _nums(nums), _address(address) {}
protected:int _nums;string _address;
};

4.2 拷贝构造函数

子类的拷贝构造函数需要调用父类的拷贝构造函数来复制父类的成员。

class Student : public Person {
public:Student(const Student& s): Person(s), _nums(s._nums), _address(s._address) {}
};

4.3 赋值重载函数

子类的赋值重载函数需要显式调用父类的赋值重载函数。

class Student : public Person {
public:Student& operator=(const Student& s) {if (this != &s) {Person::operator=(s);_nums = s._nums;_address = s._address;}return *this;}
};

4.4 析构函数

子类的析构函数会在执行完自身的析构代码后,自动调用父类的析构函数,不需要显式调用。

class Student : public Person {
public:~Student() {// 不需要显式调用父类的析构函数//子类的析构与父类的虚构构成隐藏关系//规定:不需要显示调用父类的析构,子类析构之后会自动调用父类的析构//这样能保证析构顺序为先子后父//如果显示调用则取决于实现者的实现顺序,不能保证先子后父//Person::~Person();}
};

五、隐藏(重定义)

当子类和父类有同名的成员函数或成员变量时,子类的成员会隐藏父类的成员。

隐藏规则:
1. 在继承体系中基类和派⽣类都有独⽴的作⽤域。
2. 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。
(在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

4. 注意在实际中在继承体系⾥⾯最好不要定义同名的成员

5.1.示例代码

class A {
public:void func() {cout << "func()" << endl;}
};class B : public A {
public:void func(int i) {cout << "func(int i)" << endl;}
};int main() {B b;// b.func(); // 无法调用b.func(1);b.A::func(); // 要显示调用父类函数return 0;

六、不能被继承的类

在C++98中,想要实现不能被继承的类,可以把类的构造函数定义在private中设为私有,而在C++11中加入了新的关键字final,直接写在该类的后面。

6.1示例代码

//不能被继承的类
//C++98
class Base final //C++11关键字final
{private: //把构造函数私有Base(){}
};class Derive : public Base
{
public:Derive(){}
protected:int nums;
};

七、友元与继承

友元关系不能继承,也就是说基类友元不能访问派⽣类私有和保护成员。但是,如果在子类中再次声明同一个友元函数,那么该函数也会成为子类的友元。

class Student;
class Person {friend void Display(const Person& p, const Student& s);
protected:int _nums;
};class Student : public Person {friend void Display(const Person& p, const Student& s);
protected:string _name;
};void Display(const Person& p, const Student& s) {cout << p._nums << endl;cout << s._name << endl;
}

八、静态成员与继承

基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。

8.1代码示例

class Person
{
public:string _name;static int _count;
};
int Person::_count = 0;class Student : public Person
{
protected:int _stuNums;
};int main()
{Person p;Student s;// 这里的运行结果可以看到非静态成员_name的地址是不一样的// 说明子类继承下来了,父子类对象各有一份cout << &p._name << endl;cout << &s._name << endl;// 这里的运行结果可以看到静态成员_count的地址是一样的// 说明子类和父类共用同一份静态成员cout << &p._count << endl;cout << &s._count << endl;// 公有的情况下,父子类指定类域都可以访问静态成员cout << Person::_count << endl;cout << Student::_count << endl;Person::_count++;cout << p._count << endl;cout << s._count << endl;return 0;
}

九、多继承与菱形继承

9.1继承模型

1.单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
2.多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
3.菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤还是底层都会复杂很多。当然有多继承语法⽀持,就⼀定存在会设计出菱形继承,像Java是不⽀持多继承的,就避开了菱形继承。

 9.2代码示例

多继承:

class 子类名 : 继承方式 父类名1, 继承方式 父类名2, ... {// 子类的成员
};

菱形继承:

class Person
{
public:Person(const char* name):_name(name){}string _name;
};//虚继承 关键词virtual
class Student : virtual public Person
{
public:Student(const char* name, int nums = 0):Person(name),_nums(nums){}
protected:int _nums; //学号
};class Teacher : virtual public Person
{
public:Teacher(const char* name, int id = 1):Person(name),_id(id){}
protected:int _id; //职工号
};//不要用菱形继承 用的时候加上virtual关键字 底层复杂
//多继承先继承的在前面,后继承的在后面
class Assistant : public Student , public Teacher
{
public:    Assistant(const char* name1, const char* name2, const char* name3):Student(name1),Teacher(name2),Person(name3){}protected:string _majorCourse; //主修课程
};

十、总结

C++ 的继承机制提供了强大的功能,但也带来了一些复杂的问题,如访问控制、构造析构顺序、隐藏、友元、静态成员、多继承和菱形继承等。在使用继承时,需要仔细考虑这些问题,以确保代码的正确性和可维护性。

http://www.xdnf.cn/news/15918.html

相关文章:

  • Java IO流体系详解:字节流、字符流与NIO/BIO对比及文件拷贝实践
  • kafka 生产和消费 性能测试工具 kafka-producer-perf-test.sh kafka-consumer-perf-test.sh
  • 安装docker可视化工具 Portainer中文版(ubuntu上演示,所有docker通用) 支持控制各种容器,容器操作简单化 降低容器门槛
  • 2025最新版IntelliJ IDEA Ultimate for Mac专业版安装使用指南
  • C#最佳实践:为何应尽量减少静态类的使用
  • 【PTA数据结构 | C语言版】旅游规划
  • WSL如何安装docker?
  • 基于ArcFace损失函数训练的人脸特征提取模型
  • nextjs+react接口会请求两次?
  • UART串口
  • 【Git#6】多人协作 企业级开发模型
  • 多线程Python爬虫:加速大规模学术文献采集
  • 前端之学习后端java小白(一)之SDKMAN及helloword
  • 【深度学习新浪潮】如何系统性地学习扩散模型?
  • 影刀 RPA:批量修改 Word 文档格式,高效便捷省时省力
  • Elasticsearch X-Pack安全功能未启用的解决方案
  • MySQL索引失效常见场景总结
  • 线上问题排查之【CPU飙高100%】
  • [Ninja] 快速构建系统 | 字符串处理 | 0拷贝
  • JVM 类加载过程笔记
  • leetcode-sql-627变更性别
  • 计算机发展史:互联网时代的万物互联与全球变革
  • stm32mp157f-dk2安装镜像并且部署qt全流程
  • tcp的三次握手与四次挥手
  • Softhub软件下载站实战开发(二十):Docker部署全攻略
  • WPF——自定义ListBox
  • 注解 + AOP 的方式记录日志到 t_ops_sync_log 表
  • 使用相机不同曝光时间测试灯光闪烁频率及Ai解释
  • 宝塔访问lnmp项目,跳转不到项目根目录问题解决
  • 后训练(Post-training)语言模型