c++ 类的继承(二)

1. 初始化列表

初始化列表在三种情况下必须使用:

  1. 继承关系下,父类没有无参构造函数情况
#include <iostream>using namespace std;class Base{
public:string name;int num;Base(){cout<< "基类的无参构造..." <<endl;}Base(string name){cout<< "基类的有参构造1..." <<endl;}Base(string name,int num){cout<< "基类的有参构造2..." <<endl;}};class Derived : public Base {
public:Derived(){cout<< "派生类的无参构造..." <<endl;}Derived(string name):Base(name){cout<< "派生类的有参构造1..." <<endl;}Derived(string name,int num):Base(name,num){cout<< "派生类的有参构造2..." <<endl;}
};int main() {Derived d;cout << "------------------------" << endl;Derived d0("zhangfei");Derived d1("libai",24);return 0;
}

运行结果:

基类的无参构造...
派生类的无参构造...
------------------------
基类的有参构造1...
派生类的有参构造1...
基类的有参构造2...
派生类的有参构造2...
  1. 需要初始化const修饰的类成员或初始化引用成员数据
#include <iostream>using namespace std;class person{public:const int ID;int & age;person(int no , int age):ID(no),age(age){cout << "执行构造函数..." <<endl;}
};int main(){person p(78 , 28);cout << p.ID << " , " << p.age << endl;return 0 ;
}
  1. 需要初始化的数据成员是对象,并且对应的类没有无参构造函数
#include <iostream>using namespace std;class Base{
public:string name;int num;Base(){cout<< "基类的无参构造..." <<endl;}Base(string name){cout<< "基类的有参构造1..." <<endl;}Base(string name,int num){cout<< "基类的有参构造2..." <<endl;}};class Derived{
public:int no;Base b;
//    Derived(){
//        cout<< "派生类的无参构造..." <<endl;
//    }Derived(string name):b(name){cout<< "派生类的有参构造1..." <<endl;}Derived(string name,int num):b(name,num){cout<< "派生类的有参构造2..." <<endl;}
};int main() {cout << "------------------------" << endl;Derived d0("zhangfei");cout << "------------------------" << endl;Derived d1("libai",24);return 0;
}

运行结果:

------------------------
基类的有参构造1...
派生类的有参构造1...
------------------------
基类的有参构造2...
派生类的有参构造2...

or

#include <iostream>using namespace std;class Base{
public:string name;int num;Base(){cout<< "基类的无参构造..." <<endl;}Base(string name){cout<< "基类的有参构造1..." <<endl;}Base(string name,int num){cout<< "基类的有参构造2..." <<endl;}};class Derived{
public:int no;Base b;Derived():b(){cout<< "派生类的无参构造..." <<endl;}Derived(string name):b(name){cout<< "派生类的有参构造1..." <<endl;}Derived(string name,int num):b(name,num){cout<< "派生类的有参构造2..." <<endl;}
};int main() {Derived d;cout << "------------------------" << endl;Derived d0("zhangfei");cout << "------------------------" << endl;Derived d1("libai",24);return 0;
}

运行结果:

基类的无参构造...
派生类的无参构造...
------------------------
基类的有参构造1...
派生类的有参构造1...
------------------------
基类的有参构造2...
派生类的有参构造2...

2. 重写父类同名函数

在C++中,子类可以重写(override)父类的同名函数。这被称为函数的覆盖(function overriding)。当子类重写父类的函数时,它必须具有相同的名称、参数列表和返回类型。

  • 特点
  1. 多态性:通过重写父类函数,子类对象可以根据实际类型来调用不同的函数实现,实现多态性。
  2. 继承性:子类继承了父类的函数,并且可以对其进行更改或增加新的功能。
  • 使用场景
  1. 扩展功能:子类可以通过重写父类函数来添加额外的行为或修改原有行为,从而实现功能扩展。
  2. 自定义实现:子类可以根据自己的需求提供不同于父类的实现逻辑。
  3. 适应特定情境:根据特定场景需要,在子类中针对某些特殊情况重新定义父类方法。
    代码:
#include <iostream>// 父类
class Shape {
public:virtual void draw() {std::cout << "绘制形状" << std::endl;}
};// 子类 Circle
class Circle : public Shape {
public:void draw() override { // 使用 override 关键字表示重写std::cout << "绘制圆形" << std::endl;}
};// 子类 Rectangle
class Rectangle : public Shape {
public:void draw() override {std::cout << "绘制矩形" << std::endl;}
};int main() {Shape* shape1 = new Circle();shape1->draw(); // 输出:绘制圆形Shape* shape2 = new Rectangle();shape2->draw(); // 输出:绘制矩形delete shape1;delete shape2;return 0;
}

3. 多继承

在C++中,子类多继承是指一个派生类从多个基类继承特性和行为。这意味着一个子类可以同时拥有多个父类的成员和方法。

  • 特点
  1. 子类可以获得多个基类的属性和方法,增强了代码复用性。
  2. 多继承可以构建更复杂的继承关系,允许在一个子类中结合不同的功能。
  3. 多继承提供了更大的灵活性,使得对象之间可以共享接口和实现。
  • 使用场景
  1. 当存在一种逻辑上属于不同概念、但具有共同行为的情况时,可以使用多继承来实现。
  2. 当需要通过组合不同功能或角色来创建一个新的对象时,也可以考虑使用多继承。
  • 代码实现
#include <iostream>// 基类A
class A {
public:void funcA() {std::cout << "This is function A." << std::endl;}
};// 基类B
class B {
public:void funcB() {std::cout << "This is function B." << std::endl;}
};// 子类C从基类A和基类B进行多继承
class C : public A, public B {
public:void funcC() {std::cout << "This is function C." << std::endl;}
};int main() {C c;c.funcA();  // 调用基类A的函数c.funcB();  // 调用基类B的函数c.funcC();  // 调用子类C自身的函数return 0;
}

3.1 多继承的构造函数

多继承形式下的构造函数和单继承形式基本相同,只是要在子类的构造函数中调用多个父类的构造函数 。 他们调用的顺序由定义子类时,继承顺序 决定。

#include <iostream>using namespace std;// 基类A
class A {
public:A(){cout<< "A类的无参构造..." <<endl;}~A(){cout<< "A类的析构构造..." <<endl;}
};// 基类B
class B {
public:B(){cout<< "B类的无参构造..." <<endl;}~B(){cout<< "B类的析构构造..." <<endl;}
};// 子类C从基类A和基类B进行多继承
class C : public A, public B {
public:C(){cout<< "C类的无参构造..." <<endl;}~C(){cout<< "C类的析构构造..." <<endl;}
};int main() {C c;return 0;
}

运行结果:

A类的无参构造...
B类的无参构造...
C类的无参构造...
C类的析构构造...
B类的析构构造...
A类的析构构造...

4. 类的前置声明

C++中的类前置声明是指在使用类之前提前声明类的存在,而不需要完整地定义类。它可以在某些场景下用于解决循环依赖或减少编译时间的问题。

  • 特点
  1. 允许在程序中引用尚未完整定义的类。
  2. 只需要提供类名和分号即可进行声明,无需包含类的详细实现。
  3. 前置声明通常用于解决循环依赖或减少编译时间。
  • 使用场景
  1. 解决循环依赖
    当两个或多个类相互引用时,可以使用前置声明来解决循环依赖的问题。通过提前声明类的存在,可以在类定义之前使用该类的指针或引用。
  • 注意

    B b // 报错,是因为不知道前面的声明的B类有没有无参构造
    B* b; // 这是指针,它只是一个地址而已,不会执行无参构造。
    B& b; // 这是别名,不创建对象,所以不会执行无参构造。

#include <iostream>class B; // 前置声明class A {
private:B* b;
public:void setB(B* obj) {b = obj;}void doSomething();
};class B {
private:A* a;
public:void setA(A* obj) {a = obj;}void doSomething();
};void A::doSomething() {std::cout << "a->doSomething()" << std::endl;if (b) {b->doSomething();}
}void B::doSomething() {std::cout << "b->doSomething()" << std::endl;if (a) {a->doSomething();}
}int main() {A a;B b;a.setB(&b);b.setA(&a);std::cout << "-----------------------------" << std::endl;a.doSomething();b.doSomething();return 0;
}

类A和类B相互引用,并使用了前置声明。在类A中,成员变量B* b是一个指针类型,不会执行B类的无参构造函数,因此不需要知道B类是否有无参构造函数。而如果将成员变量声明为B& b,则是一个引用类型,也不会执行B类的无参构造函数。
在类定义之后,我们定义了类A和类B的成员函数doSomething()。在A::doSomething()中,调用了b->doSomething(),即调用了B类的成员函数。同样,在B::doSomething()中,调用了a->doSomething(),即调用了A类的成员函数。
在main()函数中,我们创建了类A和类B的对象,并通过setB()和setA()方法设置了它们之间的循环依赖关系。最后,调用了a.doSomething()和b.doSomething()来触发成员函数的调用。

  1. 提高编译速度
    在一些情况下,完整的类定义可能不是必需的,例如在函数声明中只需要使用类的指针或引用,而不需要访问类的成员。这时,使用前置声明可以减少编译时间,因为编译器不需要包含和处理完整的类定义。
#include <iostream>// 前置声明
class SomeClass;void useSomeClass(SomeClass* obj);int main() {SomeClass* obj = new SomeClass();useSomeClass(obj);delete obj;return 0;
}// 完整类定义
class SomeClass {
public:void doSomething();
};void useSomeClass(SomeClass* obj) {obj->doSomething();
}void SomeClass::doSomething() {std::cout << "Doing something..." << std::endl;
}

5. 继承的使用场景

  1. 代码重用和封装:当多个类具有相似的属性和行为时,可以将这些共同特征提取到一个基类中,派生类继承基类以获得这些共同特征,并在派生类中添加额外的特定功能。
class Animal {
public:void eat() {std::cout << "Animal is eating." << std::endl;}
};class Dog : public Animal {
public:void bark() {std::cout << "Dog is barking." << std::endl;}
};int main() {Dog dog;dog.eat();  // 继承自Animal类dog.bark(); // Dog类自身的函数return 0;
}
  1. 实现多态性:通过基类的指针或引用,可以以统一的方式操作不同的派生类对象,实现多态性。这样可以在运行时动态地选择调用不同派生类的特定函数。
class Shape {
public:virtual void draw() {std::cout << "Drawing a shape." << std::endl;}
};class Circle : public Shape {
public:void draw() override {std::cout << "Drawing a circle." << std::endl;}
};class Rectangle : public Shape {
public:void draw() override {std::cout << "Drawing a rectangle." << std::endl;}
};int main() {Shape* shape1 = new Circle();Shape* shape2 = new Rectangle();shape1->draw(); // 动态调用Circle类的draw函数shape2->draw(); // 动态调用Rectangle类的draw函数delete shape1;delete shape2;return 0;
}
  1. 扩展和特化功能:通过继承,可以在派生类中添加额外的成员变量和成员函数,以实现对基类功能的扩展和特化。
class Vehicle {
protected:int wheels;public:Vehicle(int numWheels) : wheels(numWheels) {}void printWheels() {std::cout << "Number of wheels: " << wheels << std::endl;}
};class Car : public Vehicle {
public:Car() : Vehicle(4) {}void startEngine() {std::cout << "Engine started." << std::endl;}
};int main() {Car car;car.printWheels();  // 继承自Vehicle类car.startEngine();  // Car类自身的函数return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/108966.shtml

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

相关文章

谈谈 Redis 分片集群模式

谈谈 Redis 分片集群模式 优点 ● 无中心架构&#xff0c;支持动态扩容 ● Cluster 自动具备哨兵监控和故障转移&#xff08;主从切换&#xff09;能力 ● 客户端连接集群内部地址可自动发现 ● 高性能、高可用&#xff0c;有效解决了 Redis 分布式需求 缺点 ● 运维复杂 ● 只…

MySQL远程连接

一、什么是mysq的远程连接? 1、本地连接 直接在本地使用mysqladmin命令登录 mysql -u root -p 解释如下: mysql:mysql 命令表示要启动 MySQL 客户端。-u root:-u 选项指定要使用的用户名。在这里,我们使用 root 用户名作为示例。-p:-p 选项需要用户输入密码。如果省…

Vue 父子组件传值有哪些方式

① Props 父组件向子组件传递数据是通过 props 传递的&#xff1b; 在父组件中通过 v-bind 绑定子组件的属性&#xff0c;子组件中通过 props 接收父组件传递的数据。 这是一种单向数据流的方式&#xff0c;父组件可以向子组件传递数据&#xff0c;但是子组件不能直接修改传…

比postman更好用的接口管理软件——Apifox

比postman更好用的接口管理软件——Apifox 官网安装和登录Apifox功能使用团队管理&项目管理接口管理接口文档 Apifox 帮助文档 最近使用了一个好用的中文版接口管理软件&#xff0c;Apifox&#xff0c;以下介绍一下它的使用方式及好处。 官网 Apifox的官方地址&#xff1a…

Python制作PDF转Word工具(Tkinter+pdf2docx)

一、效果样式 二、核心点 1. 使用pdf2docx完成PDF转换Word 安装pdf2docx可能会报错&#xff0c;安装完成引入from pdf2docx import Converter运行也可能报错&#xff0c;可以根据报错提示看缺少那些库&#xff0c;先卸载pip uninstall xxx,使用pip install python-docx -i htt…

Stm32_标准库_16_串口蓝牙模块_手机与蓝牙模块通信_手机传入信息能对芯片时间日期进行更改

实现了手机发送信息给蓝牙模块&#xff0c;程序对数据进行分析拆解&#xff0c;并更新自身数据 main.c: #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Serial.h" #include "Ti…

Docker仓库harbor私服搭建

Harbor和Registry都是Docker的镜像仓库&#xff0c;但是Harbor作为更多企业的选择&#xff0c;是因为相比较于Regisrty来说&#xff0c;它具有很多的优势。 提供分层传输机制&#xff0c;优化网络传输 Docker镜像是是分层的&#xff0c;而如果每次传输都使用全量文件(所以用FT…

特斯拉pre-test (Go)

特斯拉pre-test &#xff08;Go&#xff09; 1 Q12 Q23 Q3 1 Q1 原文&#xff1a; You are given an implementation of a function Solution that, given a positive integer N, prints to standard output another integer, which was formed by reversing a decimal repres…

前端成神之路-HTML

前端成神之路-HTML 目录 前端成神之路-HTML 认识网页 常见浏览器介绍 查看浏览器占有的市场份额&#xff08;知晓&#xff09; 浏览器内核&#xff08;理解&#xff09; Web标准&#xff08;重点&#xff09; Web 标准的好处 Web 标准构成 HTML 初识 HTML骨架格式 …

基于Lang-Chain(ChatGLM和ChatChat)知识库大语言模型的部署搭建

环境准备 阿里云个人认证后&#xff0c;可免费试用机器学习平台PAI&#xff0c;可提供适合大语言模型环境搭建的高配置服务器。 点击试用阿里云服务器 试用产品选择&#xff1a;选择交互式建模PAI-DSW 适合哪些场景 文章/知识库/帮助文档等的检索基于现有知识库实现问答… …

Java 字符串工具类

package com.lihaozhe.util.string;import java.util.UUID; import java.util.concurrent.ThreadLocalRandom;/*** 字符串工具类** author 李昊哲* version 1.0*/ public class StringUtils {/*** 去除字符串左边的的空格** param string 原始字符串* return 去除左边空格后的字…

2024得物校招面试真题汇总及其解答(一)

1.你对java什么方向比较了解,数据结构?jvm? 我对Java的各个方面都有一定了解,但我对数据结构和JVM比较熟悉。 在数据结构方面,我了解常见的数据结构的概念、实现和应用,例如链表、队列、栈、树、图等。我还了解一些高级数据结构,例如哈希表、B树、AVL树等。 在JVM方面…

centos清理日志和缓存

今天使用redmine修改密码&#xff0c;修改报错&#xff0c;再去试试创建用户&#xff0c;创建用户的页面直接报错显示不出来。然后看了一下服务器&#xff0c;发现服务器磁盘空间全部占满了。 CentOS系统也会在使用很长一段时间后出现硬盘空间开始不够的情况&#xff0c;而这并…

GPT4 Advanced data analysis Code Interpreter 做行业数据分析、可视化处理图像、视频、音频等

1. 跨境电商如何用ChatGPT选品 ChatGPT Jungle scout 案例&#xff1a;跨境电商如何用ChatGFT选品 ChatGPTJungle scout 素材和资料来自&#xff1a; Jungle ScoutEM, Michael Soltis 和 文韬武韬AIGC 1.1 从Jungle scout上下载数据 Date Range > Last 90 days Downlo…

uniapp(uncloud) 使用生态开发接口详情3(新增产品分类,产品列表,新闻列表)

我的想法是有产品分类,产品列表,新闻咨询,新闻列表 项目中, uniCloud > database 目录下新建 sy_product_nav.schema.json // 代码如下 {"bsonType": "object","required": ["classname"],"permission": {"read&…

rabbitMq (2)

RabbitMQ 消息应答与发布 文章目录 1. 消息应答1.2 自动应答1.2 手动应答1.3 代码案例 2. RabbitMQ 持久化2.1 队列持久化2.2 消息持久化 3. 不公平分发4. 预取值分发5. 发布确认5.1 发布确认逻辑5.2 开启发布确认的方法5.3 单个确认发布5.4 批量确认发布5.5 异步确认5.5.1 处理…

【LeetCode热题100】--31.下一个排列

31.下一个排列 思路&#xff1a; 方法&#xff1a;两遍扫描 注意到下一个排列总是比当前排列要大&#xff0c;除非该排列已经是最大的排列。我们希望找到一种方法&#xff0c;能够找到一个大于当前序列的新序列&#xff0c;且变大的幅度尽可能小。具体地&#xff1a; 我们需要…

实验室设备modbus小结

背景&#xff1a; 大概花1个月&#xff0c;后端代码量再1W行多点&#xff0c;不同厂商的指令不同需要定制化开发。参与了设备的数据采集工作&#xff0c;当然常规的设备管理、权限就不重点展开。 都是物联网相关&#xff0c;但是还是有所不同。 之前做过海尔的U home相关的项目…

使用Spring Boot构建稳定可靠的分布式爬虫系统

摘要&#xff1a;本文将介绍如何使用Spring Boot框架构建稳定可靠的分布式爬虫系统。我们将从系统设计、任务调度、数据存储以及容灾与故障恢复等方面进行详细讲解&#xff0c;帮助读者理解并实践构建高效的分布式爬虫系统。 1. 引言 随着互联网的快速发展&#xff0c;爬虫系…

右值引用+移动语义

目录 右值引用 引入 介绍 左值 左值引用 左值引用的缺陷 引入 缺陷 解决 右值 纯右值 将亡值 右值引用 move函数 介绍 底层实现 参数 -- 通用引用类型 引用折叠 折叠规则: 返回值 remove_reference 移动 引入 介绍 移动构造函数 介绍 是否抛出异常…