《21天学通C++》(第十章)实现继承(2)

2024-04-30 02:20

本文主要是介绍《21天学通C++》(第十章)实现继承(2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.构造顺序

提问一: 当创建派生类的对象时,是先调用派生类的构造函数?还是先调用基类的构造函数呢?
提问二: 当创建派生类的对象后,派生类对象的成员属性是在调用函数前实例化?还是调用后实例化呢?

当创建一个派生类的对象时,构造函数的调用顺序遵循特定的规则,以确保所有基类成员和成员对象都被正确地初始化。以下是构造顺序:
①基类构造: 若存在多个直接基类,它们将按照继承列表中的顺序被构造。
②成员对象: 在所有直接基类构造函数执行完毕后,派生类中的成员对象(非静态)按照它们在类定义中声明的顺序被构造。
③派生类构造 派生类自己的构造函数体执行。

示例如下:

#include <iostream>
using namespace std;class Base1 {
public:Base1() { cout << "Base1 constructor called." << endl; }
};class Base2 {
public:Base2() { cout << "Base2 constructor called." << endl; }
};class Derived : public Base1, public Base2 {
public:Derived() { cout << "Derived constructor called." << endl; }
};int main() {Derived d; // 构造Derived对象system("pause");return 0;
}

输出结果是:

Base1 constructor called.
Base2 constructor called.
Derived constructor called.

这个例子中,Derived类有两个直接基类Base1和Base2。构造顺序如下:

1. Base1的构造函数首先被调用。
2. Base2的构造函数。
3. Derived类中的成员对象(如果有的话)。
4. Derived自己的构造函数体。

2.析构顺序

析构顺序即资源被释放的顺序,刚好和构造顺序相反
①派生类析构 ②成员对象析构 ③基类析构
示例代码如下:

#include <iostream>
using namespace std;class Base1 {
public:Base1() { cout << "Base1 constructor called." << endl; }~Base1() { cout << "Base1 destructor called." << endl; }
};class Base2 {
public:Base2() { cout << "Base2 constructor called." << endl; }~Base2() { cout << "Base2 destructor called." << endl; }
};class Derived : public Base1, public Base2 {
public:Derived() { cout << "Derived constructor called." << endl; }~Derived() { cout << "Derived destructor called." << endl; }
};int main() {{Derived d; // 构造Derived对象} // d对象作用域结束,开始析构system("pause");return 0;
}

输出结果为:

Base1 constructor called.
Base2 constructor called.
Derived constructor called.
Derived destructor called.
Base2 destructor called.
Base1 destructor called.

在这个例子中,析构函数的调用顺序如下:

1. Derived的析构函数首先被调用。
2. Derived中的成员对象(如果有的话)的析构函数,按照它们声明的逆序。
3. Base2的析构函数,因为它在继承列表中位于Base1的右边。
4. Base1的析构函数。

PS:右边是指派生类继承列表的顺序

class Derived : public Base1, public Base2{}

Base2就是在右边

3.私有继承

私有继承是一种继承方式,它使得基类中的公有成员(public)和保护成员(protected)在派生类中变为私有成员(private),继承时使用关键字private即:

1. 基类的公有和保护成员在派生类中不可直接访问,但可以在派生类的成员函数中使用。
2. 私有继承通常用于实现细节的隐藏,将基类作为辅助实现,而不是作为接口的一部分。

示例代码如下:

#include <iostream>
using namespace std;class Base {
public:Base() { cout << "Base constructor called." << endl; }void publicFunc() { cout << "Public function of Base." << endl; }
};class Derived : private Base {//私有继承
public:// 可以提供一个公有函数来调用基类的公有函数void accessPublicFunc() { publicFunc(); }// 派生类的构造和析构Derived() { cout << "Derived constructor called." << endl; }
};int main() {Derived d;d.accessPublicFunc(); // 正确:通过派生类提供的接口访问基类公有函数// d.publicFunc(); // 错误:在派生类中,基类的公有成员是私有的system("pause");return 0;
}

3.保护继承

保护继承是一种继承方式,当一个类使用保护继承时,基类中的公有成员(public)和保护成员(protected),继承时使用关键字protected,即:

1. 不能被派生类的对象直接访问调用
2. 可以在派生类成员函数友元函数中访问。
3. 派生类的派生类(即基类的更远派生类)也可以将这些保护成员视为保护成员。

示例代码如下:

#include <iostream>
using namespace std;class Base {
public:int publicVar;
protected:int protectedVar;
};class Derived : protected Base {// 基类中的publicVar和protectedVar现在都是保护成员
};int main() {Derived d;// d.publicVar = 10; // 错误:publicVar现在是保护成员,不能在Derived对象中直接访问// d.protectedVar = 20; // 错误:同样,不能直接访问system("pause");return 0;
}

4.保护继承和私有继承的异同点

相同点:

1.实现细节隐藏:保护继承和私有继承都用于隐藏基类的实现细节,防止派生类外部直接访问基类的成员。
2.基类成员不可直接访问:无论是保护继承还是私有继承,基类中的公有成员都不能在派生类的对象中直接访问。
3.派生类成员函数访问权限:在派生类中,无论是保护继承还是私有继承,派生类的成员函数都可以访问从基类继承来的成员。

不同点:

访问权限:

  • 保护继承:基类的公有成员变为派生类的保护成员,保护成员保持为保护。
  • 私有继承:基类的公有成员和保护成员都变为派生类的私有成员。

对派生类派生类的可见性:

  • 保护继承:在派生类的派生类中,基类的公有和保护成员都是保护成员,可以被派生类的派生类访问。
  • 私有继承:在派生类的派生类中,基类的公有和保护成员都是私有成员,不能被派生类的派生类直接访问,除非派生类提供了访问这些成员的公有或保护接口。

设计意图:

  • 保护继承:通常用于表示“我可以被继承,但我的公有成员不应该被直接用作你的接口的一部分”。
  • 私有继承:通常用于实现细节的隐藏,表示“我被继承仅用于实现,我的接口不应该被暴露”。

友元关系:

  • 保护继承:基类的友元在派生类中可能仍然保持友元关系,这取决于具体的实现和友元声明的上下文。
  • 私有继承:基类的友元在派生类中不再是友元,因为所有继承的成员都变为私有。

构造和析构:

无论是保护继承还是私有继承,基类的构造函数和析构函数仍然可以被派生类的对象调用,以完成对象的构造和析构。

5.切除问题

切除问题(Pruning)指的是当通过基类指针引用访问派生类对象时,无法访问派生类中新增的成员或重写的方法。

示例代码如下:

#include <iostream>
using namespace std;class Base {
public:virtual void show() { cout << "Base show" << endl; }
};class Derived : public Base {
public:void show() override { cout << "Derived show" << endl; }void derivedOnly() { cout << "Only in Derived" << endl; }// 新增的方法
};void someFunction(Base& baseRef) {baseRef.show(); // 可正确调用// baseRef.derivedOnly(); // 错误:无法通过基类引用访问 Derived 的特有成员
}int main() {Derived d;someFunction(d); // 通过基类引用传递派生类对象system("pause");return 0;
}

在这个例子中,尽管someFunction接收的是Base类型的指针,实际上传入的是Derived类型的对象。但是,由于基类指针的限制,我们无法通过它来访问Derived类特有的成员derivedOnly,这就是切除问题。

6.多继承

指一个派生类可以同时从多个基类继承属性和方法的能力,这使得派生类可以组合来自多个基类的特性。

示例代码如下:

#include <iostream>
using namespace std;// 基类:动物(Animal)
class Animal {
public:virtual void makeSound() { cout << "Animal sound" << endl; }
};// 基类:鸟类(Bird)
class Bird : public Animal {
public:void makeSound() override { cout << "Bird chirp" << endl; } // 鸟类特有的叫声void fly() { cout << "Bird is flying" << endl; } // 鸟类特有的飞行行为
};// 基类:狗类(Dog)
class Dog : public Animal {
public:void makeSound() override { cout << "Dog bark" << endl; } // 狗类特有的叫声void run() { cout << "Dog is running" << endl; } // 狗类特有的奔跑行为
};// 派生类:鸡类(Chicken),它同时继承自动物(Animal)和鸟类(Bird)
class Chicken : public Bird, public Dog {
public:// 鸡类可以重写或添加新的行为void makeSound() override { cout << "Chicken cluck" << endl; } // 鸡类特有的叫声void layEgg() { cout << "Chicken is laying an egg" << endl; } // 鸡类特有的下蛋行为
};int main() {Chicken myChicken;myChicken.makeSound(); // 调用鸡类的makeSound,输出 "Chicken cluck"myChicken.fly();        // 调用鸟类的fly,输出 "Bird is flying"myChicken.run();        // 调用狗类的run,输出 "Dog is running"myChicken.layEgg();     // 调用鸡类特有的layEgg,输出 "Chicken is laying an egg"system("pause");return 0;
}

7.使用final禁止继承

class FinalClass final {// code
};

这篇关于《21天学通C++》(第十章)实现继承(2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

基于Redisson实现分布式系统下的接口限流

《基于Redisson实现分布式系统下的接口限流》在高并发场景下,接口限流是保障系统稳定性的重要手段,本文将介绍利用Redisson结合Redis实现分布式环境下的接口限流,具有一定的参考价值,感兴趣... 目录分布式限流的核心挑战基于 Redisson 的分布式限流设计思路实现步骤引入依赖定义限流注解实现

SpringBoot实现虚拟线程的方案

《SpringBoot实现虚拟线程的方案》Java19引入虚拟线程,本文就来介绍一下SpringBoot实现虚拟线程的方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录什么是虚拟线程虚拟线程和普通线程的区别SpringBoot使用虚拟线程配置@Async性能对比H

基于Python实现进阶版PDF合并/拆分工具

《基于Python实现进阶版PDF合并/拆分工具》在数字化时代,PDF文件已成为日常工作和学习中不可或缺的一部分,本文将详细介绍一款简单易用的PDF工具,帮助用户轻松完成PDF文件的合并与拆分操作... 目录工具概述环境准备界面说明合并PDF文件拆分PDF文件高级技巧常见问题完整源代码总结在数字化时代,PD

Python实现Word转PDF全攻略(从入门到实战)

《Python实现Word转PDF全攻略(从入门到实战)》在数字化办公场景中,Word文档的跨平台兼容性始终是个难题,而PDF格式凭借所见即所得的特性,已成为文档分发和归档的标准格式,下面小编就来和大... 目录一、为什么需要python处理Word转PDF?二、主流转换方案对比三、五套实战方案详解方案1:

SpringBoot集成EasyExcel实现百万级别的数据导入导出实践指南

《SpringBoot集成EasyExcel实现百万级别的数据导入导出实践指南》本文将基于开源项目springboot-easyexcel-batch进行解析与扩展,手把手教大家如何在SpringBo... 目录项目结构概览核心依赖百万级导出实战场景核心代码效果百万级导入实战场景监听器和Service(核心

C# async await 异步编程实现机制详解

《C#asyncawait异步编程实现机制详解》async/await是C#5.0引入的语法糖,它基于**状态机(StateMachine)**模式实现,将异步方法转换为编译器生成的状态机类,本... 目录一、async/await 异步编程实现机制1.1 核心概念1.2 编译器转换过程1.3 关键组件解析

基于Python Playwright进行前端性能测试的脚本实现

《基于PythonPlaywright进行前端性能测试的脚本实现》在当今Web应用开发中,性能优化是提升用户体验的关键因素之一,本文将介绍如何使用Playwright构建一个自动化性能测试工具,希望... 目录引言工具概述整体架构核心实现解析1. 浏览器初始化2. 性能数据收集3. 资源分析4. 关键性能指

使用Redis快速实现共享Session登录的详细步骤

《使用Redis快速实现共享Session登录的详细步骤》在Web开发中,Session通常用于存储用户的会话信息,允许用户在多个页面之间保持登录状态,Redis是一个开源的高性能键值数据库,广泛用于... 目录前言实现原理:步骤:使用Redis实现共享Session登录1. 引入Redis依赖2. 配置R

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1

在Java中实现线程之间的数据共享的几种方式总结

《在Java中实现线程之间的数据共享的几种方式总结》在Java中实现线程间数据共享是并发编程的核心需求,但需要谨慎处理同步问题以避免竞态条件,本文通过代码示例给大家介绍了几种主要实现方式及其最佳实践,... 目录1. 共享变量与同步机制2. 轻量级通信机制3. 线程安全容器4. 线程局部变量(ThreadL