7、类

7.1、类声明

前置声明:声明一个将稍后在此作用域定义的类类型。直到定义出现前,此类名具有不完整类型。当代码仅仅需要用到类的指针或引用时,就可以采用前置声明,无需包含完整的类定义。

前置声明有以下几个作用:

  • 降低编译时的依赖性,加快编译速度
  • 避免出现循环引用
// 前置声明
class MyClass;void func(MyClass* obj); // 可以使用指针
void func2(const MyClass& obj); // 可以使用引用
MyClass* createObj(); // 返回指针或引用是可行的

使用前置声明也会有一些限制,除了不能访问类成员、不能创建类对象、不能使用类的大小信息外,还不能继承自该类。

7.2、局部类

局部类(Local Class)是指定义在函数内部的类,其作用域仅限于该函数。它们无法被函数外部访问。局部类可以访问函数的静态变量、外部全局变量和枚举。局部类的非静态成员函数不能访问外部函数的非静态局部变量。

int globalVar = 100;void func() {static int staticVar = 200;int localVar = 300; // 局部变量(非静态)class Local {public:void print() {std::cout << globalVar << std::endl; // 合法:访问全局变量std::cout << staticVar << std::endl; // 合法:访问静态变量// std::cout << localVar << std::endl; // 错误:无法访问非静态局部变量}};Local().print();
}

局部类一般用于封装临时性算法,实现函数内部的策略模式。

7.3、联合体声明

联合体(Union)是C++中一种特殊的类类型,它允许在相同的内存位置存储不同类型的数据。与结构体(struct)和类(class)相比,联合体的主要特点是所有成员共享同一块内存,因此在同一时刻只能存储一个成员的值。

联合体有以下特性:

  • 默认成员访问是public。
  • 修改一个成员会覆盖其他成员的值。
  • 联合体的对齐方式通常由其最大成员的对齐要求决定
  • 联合体的大小至少要能容纳其最大的数据成员,并且可能因对齐需求而增加额外空间。
union Example {char c;       // 1字节int i;        // 4字节(假设int为4字节)double d;     // 8字节(假设double为8字节)
}; // 8字节

匿名联合体是没有名称的联合体,其成员直接成为包含它的作用域的成员:

struct Employee {enum class Type { MANAGER, ENGINEER };Type type;union {char* department;  // 当type为MANAGER时使用int engineerId;    // 当type为ENGINEER时使用}; // 匿名联合体// 访问方式:直接通过Employee对象访问department或engineerId
};// 使用示例
Employee e;
e.type = Employee::Type::MANAGER;
e.department = "HR";

7.4、this指针

this指针在特殊场景中的行为:

  • 在析构函数中使用this:析构函数执行时,对象的成员变量仍在内存中,但已进入销毁流程,析构函数执行完毕后,对象占用的内存会被回收。析构函数中不要访问已释放的成员变量,仅执行必要的资源释放操作,避免复杂逻辑,应避免调用其他成员函数。
class Resource {
public:~Resource() {// 合法:析构函数执行时对象尚未完全销毁std::cout << "Destroying resource at " << this << std::endl;// 但不能调用非析构的成员函数,可能访问已释放内存}
};
  • delete this的危险用法
class Dangerous {
public:void selfDestruct() {std::cout << "Before delete: " << this << std::endl;delete this; // 调用后当前对象被销毁// this->data = 42;  // 灾难!访问已释放的内存// std::cout << data; // 同样危险// 应该立即返回}
private:int data;
};Dangerous* obj = new Dangerous;
obj->selfDestruct();
// 从这里开始,obj已经成为悬空指针
// 任何对obj的使用都是未定义行为

这种模式有时用于引用计数对象的自我销毁

  • 多态场景下的this指针,在虚函数中,this指针始终指向实际对象(而非静态类型)
class Base {
public:virtual void printAddress() {std::cout << "Base: " << this << std::endl;}
};class Derived : public Base {
public:void printAddress() override {Base::printAddress(); // 输出基类视角的this(与派生类相同)std::cout << "Derived: " << this << std::endl;}
};// 输出:Base和Derived的this指针地址相同,指向同一对象

7.5、static成员

在类定义中,关键词static声明不绑定到类实例的成员

static成员变量是具有静态存储期的独立变量

class X { static int n; }; // 声明(用 'static')
int X::n = 1;              // 定义(不用 'static')

static成员函数不依赖于对象实例(没有this指针),只能访问static成员变量,可以直接用类名调用。

7.6、嵌套类

嵌套类(Nested Class)是指在一个类(称为外围类/外部类)内部定义的另一个类。嵌套类的作用域受外围类限制,但可以访问外围类的成员(包括私有成员),嵌套类被视为外部类的 “朋友”。。

嵌套类可以访问外部类的所有成员(包括私有、受保护和公共成员)以及成员函数,不过访问非静态成员时,必须借助外部类的实例来实现。对于外部类的静态成员,嵌套类可以直接访问,无需外部类的实例。

外部类无法直接访问嵌套类的私有和受保护成员,如需访问要将将外部类声明为友元类。

外部类和嵌套类之间成员的相互使用无需理会声明顺序:

class enclose
{
public:void printNest() {nested1 ne;ne.num = 10;}private:class nested1 {public:void print() {enclose en;en.num = 10;}private:friend class enclose;int num;};int num;
};

嵌套类可以前置声明并在之后定义,在外围类的体内或体外均可:

class enclose
{class nested1;    // 前置声明class nested2;    // 前置声明class nested1 {}; // 嵌套类的定义
};class enclose::nested2 { }; // 嵌套类的定义

嵌套类不影响外围类的大小。

7.7、派生类

在基类子句中列出的类是直接基类,直接基类的基类被称为间接基类。同一个类不能多次被指定为直接基类,但是可以既是直接基类又是间接基类。

基类子对象的构造函数被派生类的构造函数所调用:可以在成员初始化器列表中向这些构造函数提供实参。

继承时,如果省略访问说明符,那么它对以类关键词struct声明的类默认为public,对以类关键词class声明的类为private。

虚基类:虚继承是为了解决菱形继承问题而引入的。当使用虚继承时,无论通过多少条路径继承同一个虚基类,最终派生对象中只会包含该虚基类的一个实例。

struct B { int n; };
class X : public virtual B {};
class Y : virtual public B {};
class Z : public B {};// 每个 AA 类型对象拥有一个 X,一个 Y,一个 Z 和两个 B:
// 一个是 Z 的基类,另一个由 X 与 Y 所共享
struct AA : X, Y, Z
{void f(){X::n = 1; // 修改虚 B 子对象的成员Y::n = 2; // 修改同一虚 B 子对象的成员Z::n = 3; // 修改非虚 B 子对象的成员std::cout << X::n << Y::n << Z::n << '\n'; // 打印 223}
};

上述例子虽然能运行,但是n的定义是不明确的!!

私有继承时,子类不对外表现出is-a的关系,但是可以在子类内部使用is-a,在类外部可以通过指针强转进行虚函数调用。

7.8、using 声明

在命名空间和块作用域中:using声明将另一命名空间的成员引入到当前命名空间或块作用域中。

在类定义中:using声明可以将别处定义的名字引入到此using声明所在的声明区中,例如将基类的受保护成员暴露为派生类的公开成员。这有几个优点:

7.8.1、实现接口扩展与兼容性

当你设计一个类的继承体系时,可能基类出于封装和安全性的考虑,将某些成员设置为受保护的。但在派生类的使用场景中,这些成员可能需要被外部更方便地访问,以满足特定的接口需求。通过 using 声明将基类的受保护成员提升为派生类的公开成员,可以在不破坏基类原有封装的前提下,为派生类提供更广泛的接口。

// 基类
class Base {
protected:void protectedFunction() {std::cout << "Base::protectedFunction() called" << std::endl;}
};// 派生类
class Derived : public Base {
public:using Base::protectedFunction;  // 将基类的受保护成员暴露为公开成员
};int main() {Derived d;d.protectedFunction();  // 可以直接调用,无需通过其他接口return 0;
}

引入有作用域枚举项:除了另一命名空间的成员和基类的成员,using声明也能将枚举的枚举项引入命名空间、块和类作用域。

7.8.2、继承构造函数

当在派生类中写下using Base::Base; 时,编译器会:

  • 隐式生成派生类的构造函数:这些构造函数与基类的构造函数具有相同的参数列表。
  • 将基类构造函数纳入重载决议:当创建派生类对象时,编译器会考虑基类的构造函数。
class Base {
public:Base(int a) { cout << "Base int " << endl; }Base(int a, double b) { cout << "Base int double " << endl; }Base(int a, double b, char c) { cout << "Base int double char " << endl; }
};class Derived : public Base {
public:Derived(int a, double b) : Base(a, b){cout << "Derived int double " << endl;}
private:using Base::Base;  // 让Base的构造函数在Derived中可见
};int main() {Derived a(1);Derived b(1, 1.1);Derived c(1, 2, 'c');return 0;
}

当创建派生类对象时,编译器会:

  • 优先考虑派生类自身定义的构造函数。
  • 如果没有匹配的构造函数,则考虑通过 using Base::Base; 继承的基类构造函数。
  • 继承的构造函数只会初始化基类部分,派生类的新增成员需通过默认初始化(如果有默认构造函数)或保持未初始化状态。

访问控制规则:

  • 基类构造函数的访问权限不变:如果基类的某个构造函数是 protected,继承后在派生类中仍然是 protected。
  • 派生类无法继承基类的私有构造函数:因为私有成员对派生类不可见。

构造函数继承的限制:

  • 无法初始化派生类成员
  • 无法继承默认 / 拷贝 / 移动构造函数
  • 冲突的构造函数会被删除:如果派生类已经定义了与基类构造函数参数列表相同的构造函数,继承的版本会被删除。

7.9、空基类优化

为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象的大小至少为1,即使该类型是空的类类型(即没有非静态数据成员的类或结构体),否则两个对象的大小为0,它们的内存地址可能相同,导致指针无法区分它们。

C++标准允许编译器将空基类子对象的大小优化为0字节,即使普通对象必须至少1字节

class Empty {};  // 空基类class Derived : public Empty {int x;  // 只有一个数据成员
};// 通常 sizeof(int) 为 4 字节
// 由于 EBCO,Empty 基类子对象的大小被优化为 0
// 因此 sizeof(Derived) == 4,而非 5!

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

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

相关文章

4-6WPS JS宏自定义函数变长参数函数(实例:自定义多功能数据统计函数)学习笔记

一、自定义函数:自定义多功能数据统计函数。示例1&#xff1a;function jia1(x,...arr){//自定义变长函数&#xff0c;X第一参数&#xff0c;...arr为变长参数可放入无数个参数&#xff0c;就像是数组return xWorksheetFunction.Sum(arr)//返回&#xff0c;X第一参数WorksheetF…

HDMI延长器 vs 分配器 vs KVM切换器 vs 矩阵:技术区别与应用场景

在音视频和计算机信号传输领域&#xff0c;延长器、分配器、切换器和矩阵是四种常见设备&#xff0c;它们的功能和应用场景有显著区别。以下是它们的核心差异对比&#xff1a; 1. 延长器&#xff08;Extender&#xff09; 功能&#xff1a; ▸ 将信号&#xff08;如HDMI、Displ…

从0到1解锁Element-Plus组件二次封装El-Dialog动态调用

技术难题初登场 家人们&#xff0c;最近在开发一个超复杂的后台管理系统项目&#xff0c;里面有各种数据展示、表单提交、权限控制等功能&#xff0c;在这个过程中&#xff0c;我频繁地使用到了element-plus组件库中的el-dialog组件 。它就像一个小弹窗&#xff0c;可以用来显示…

数据结构实验习题

codeblock F2是出控制台 1.1 /* by 1705 WYY */ #include <stdio.h> #include <stdlib.h> #define TRUE 1 #define FALSE 0 #define YES 1 #define NO 0 #define OK 1 #define ERROR 0 #define SUCCESS 1 #define UNSUCCESS 0 #define OVERFLOW -2 #define UNDERF…

PyTorch 2.7深度技术解析:新一代深度学习框架的革命性演进

引言:站在AI基础设施变革的历史节点 在2025年这个充满变革的年份,PyTorch团队于4月23日正式发布了2.7.0版本,随后在6月4日推出了2.7.1补丁版本,标志着这个深度学习领域最具影响力的框架再次迎来了重大突破。这不仅仅是一次常规的版本更新,而是一次面向未来计算架构和AI应…

LTspice仿真10——电容

电路1中电容下标m5&#xff0c;表示5个该电阻并联电路2中ic1.5v&#xff0c;表示电容初始自带电量&#xff0c;电压为1.5v

C#事件驱动编程:标准事件模式完全指南

事件驱动是GUI编程的核心逻辑。当程序被按钮点击、按键或定时器中断时&#xff0c;如何规范处理事件&#xff1f;.NET框架通过EventHandler委托给出了标准答案。 &#x1f50d; 一、EventHandler委托&#xff1a;事件处理的基石 public delegate void EventHandler(object se…

全面的 Spring Boot 整合 RabbitMQ 的 `application.yml` 配置示例

spring:rabbitmq:# 基础连接配置 host: localhost # RabbitMQ 服务器地址port: 5672 # 默认端口username: guest # 默认用户名password: guest # 默认密码virtual-host: / # 虚拟主机&#xff08;默认/&…

Win32 API实现串口辅助类

近期需要使用C++进行串口通讯,将Win32 API串口接口进行了下封装,可实现同步通讯,异步回调通讯 1、SerialportMy.h #pragma once #include <Windows.h> #include <thread> #include <atomic> #include <functional> #include <queue> #inclu…

Python-执行系统命令-subprocess

1 需求 2 接口 3 示例 4 参考资料 Python subprocess 模块 | 菜鸟教程

Web攻防-XMLXXE上传解析文件预览接口服务白盒审计应用功能SRC报告

知识点&#xff1a; 1、WEB攻防-XML&XXE-黑盒功能点挖掘 2、WEB攻防-XML&XXE-白盒函数点挖掘 3、WEB攻防-XML&XXE-SRC报告 一、演示案例-WEB攻防-XML&XXE-黑盒功能点挖掘 1、不安全的图像读取-SVG <?xml version"1.0" standalone"yes&qu…

浏览器工作原理37 [#] 浏览上下文组:如何计算Chrome中渲染进程的个数?

一、前言 在默认情况下&#xff0c;如果打开一个标签页&#xff0c;那么浏览器会默认为其创建一个渲染进程。 如果从一个标签页中打开了另一个新标签页&#xff0c;当新标签页和当前标签页属于同一站点&#xff08;相同协议、相同根域名&#xff09;的话&#xff0c;那么新标…

位置编码和RoPE

前言 关于位置编码和RoPE 应用广泛&#xff0c;是很多大模型使用的一种位置编码方式&#xff0c;包括且不限于LLaMA、baichuan、ChatGLM等等 第一部分 transformer原始论文中的标准位置编码 RNN的结构包含了序列的时序信息&#xff0c;而Transformer却完全把时序信息给丢掉了…

手动使用 Docker 启动 MinIO 分布式集群(推荐生产环境)

在生产环境中&#xff0c;MinIO 集群通常部署在多个物理机或虚拟机上&#xff0c;每个节点运行一个 MinIO 容器&#xff0c;并通过 Docker 暴露 API 和 Console 端口。 1. 准备工作 假设有 4 台服务器&#xff08;也可以是同一台服务器的不同端口模拟&#xff0c;但不推荐生产…

如何在IntelliJ IDEA中设置数据库连接全局共享

在现代软件开发中&#xff0c;数据库连接管理是开发过程中不可或缺的一部分。为了提高开发效率&#xff0c;减少配置错误&#xff0c;并方便管理&#xff0c;IntelliJ IDEA 提供了一个非常有用的功能&#xff1a;数据库连接全局共享。通过这个功能&#xff0c;你可以在多个项目…

【Python】文件应用: 查找读取的文件内容

查找读取的文件内容 from pathlib import Pathpath Path(pi_million_digits.txt) contents path.read_text()lines contents.splitlines() pi_string for line in lines:pi_string line.lstrip()birthday input("Enter your birthday, in the form mmddyy: "…

交互式剖腹产手术模拟系统开发方案

以下是为您设计的《交互式剖腹产手术模拟系统》开发方案框架,包含技术实现路径与详细内容结构建议。由于篇幅限制,这里呈现核心框架与关键模块说明: 交互式剖腹产手术模拟系统开发方案 一、项目背景与意义 1.1 传统医学教学痛点分析 尸体标本成本高昂(约$2000/例)活体训…

AWS WebRTC: 判断viewer端拉流是否稳定的算法

在使用sdk-c viewer端进行拉流的过程中&#xff0c;viewer端拉取的是视频帧和音频帧&#xff0c;不会在播放器中播放&#xff0c;所以要根据收到的流来判断拉流过程是否稳定流畅。 我这边采用的算法是&#xff1a;依据相邻帧之间的时间间隔是否落在期望值的 20% 范围内。 音频…

【Python】文件读取:逐行读取应用实例——从一个JSONL文件中逐行读取文件

从一个JSONL文件中逐行读取文件&#xff0c;并将这些问题保存到一个新的JSONL文件中 import json import argparse import os # 导入os模块用于检查文件是否存在def read_questions_from_jsonl(file_path, limit):"""从JSONL文件中读取指定数量的question部分…

百宝箱生成智能体

点击新建应用 工作流如下&#xff1a; 点击发布 点击Web服务&#xff0c;上架