《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

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount