深蓝学院C++基础与深度解析笔记 第 11 章 类

news/2025/1/13 6:10:26/

深蓝学院C++基础与深度解析笔记 第 11 章 类

1. 结构体与对象聚合
**● 结构体:**对基本数据结构进行扩展,将多个对象放置在一起视为一个整体

– 结构体的声明与定义(注意定义后面要跟分号来表示结束)
– 仅有声明的结构体是不完全类型( incomplete type )
– 结构体(以及类)的一处定义原则:翻译单元级别

● 数据成员(数据域)的声明与初始化

– ( C++11 )数据成员可以使用 decltype 来声明其类型,但不能使用 auto(除静态)
– 数据成员声明时可以引入 const 、引用等限定
– 数据成员会在构造类对象时定义
– ( C++11 )类内成员初始化
– 聚合初始化:从初始化列表到指派初始化器
#include <iostream>
struct Str 
{int x;int y;
}int main()
{Str m_str;m str.x = 3;std::cout < m str.x < std::endl; 
}

有声明即可定义指针,结构体内可以使用decltype(),不可以使用auto;
● mutable 限定符,只能在结构体内限定定义
● 静态数据成员 多个对象之间共享的数据成员

– 定义方式的衍化

在这里插入图片描述
● C++98 :类外定义, const 静态成员的类内初始化
● C++17 :内联静态成员的初始化

– 可以使用 auto 推导类型

● 静态数据成员的访问

– “.” 与“ ->” 操作符
– “::” 操作符

● 在类的内部声明相同类型的静态数据成员

2. 成员函数(方法)

C语言不可以在结构体中定义函数;C++可以在结构体中定义函数,作为其成员的一部分:对内操作数据成员,对外提供调用接口:

– 在结构体中将数据与相关的成员函数组合在一起将形成类,是 C++ 在 C 基础上引入的概念
– 关键字 class, struct在此类似于public:
– 类可视为一种抽象数据类型,通过相应的接口(成员函数)进行交互
– 类本身形成域,称为类域 

● 成员函数的声明与定义:

– 类内定义(隐式内联)
– 类内声明 + 类外定义, 类外显示内联:inline
– 类与编译期的两遍处理:但是只能处理内置类型后置,自定义类型还是需要桉顺序使用
– 成员函数与尾随返回类型( trail returning type )

● 成员函数与 this 指针: str *const this

– 使用 this 指针引用当前对象
– 基于 const 的成员函数重载,this本身不能修改,但是this->x可以

**● 成员函数的名称查找与隐藏关系:**先找最小范围的作用域,在递归的寻找外一层的作用域

– 函数内部(包括形参名称)隐藏函数外部
– 类内部名称隐藏类外部
– 使用 this 或域操作符引入依赖型名称查找

ps: 类名::变量/函数即为该类下的变量/函数, :: 变量/函数:中省略::前的作用域范围即为全局的变量/作用域
● 静态成员函数:被所有对象共享

– 在静态成员函数中返回静态数据成员

● 成员函数基于引用限定符的重载( C++11 )

3. 访问限定符与友元
访问限定符与友元
● 使用 public/private/protected 限定类成员的访问权限

– 限定目标 : 访问权限的引入使得可以对抽象数据类型进行封装
– 类与结构体缺省访问权限的区别: C++默认:private, C 默认类似于public

● 使用友元打破访问权限限制 关键字 —— friend

– 声明某个类或某个函数是当前类的友元 慎用! —— 改变了【封装】  特性
– 在类内首次声明友元类或友元函数:可以先声明freined ,再定义;声明friend和声明函数是两个概念● 注意使用限定名称引入友元并非友元类(友元函数)的声明: 假如使用了全局限定::则需要提前声明或定义函数
#include <iostream>
#include <vector>class Str2;
class str 
{friend Str2;  // str2可以访问str的私有和保护类型的数据和函数 ,单向的   
public:inline static int x; 
private:int y;
};class Str2 
{void fun() {std::cout<<Str::x<< std::endl; }
}; 
– 友元函数的类内外定义与类内定义

隐藏友元( hidden friend ):常规名称查找无法找到,在类内定义

● 好处:减轻编译器负担,防止误用
● 改变隐藏友元的缺省行为:在类外声明或定义函数

4. 构造、析构与复制成员函数

● 构造函数: 构造对象时调用的函数,无返回

– 名称与类名相同,无返回值,可以包含多个版本(可重载)
– 代理构造函数( C++11 ):使用别的函数完成构造,然后再执行本构造函数的内容

代理构造:
在这里插入图片描述
● 初始化列表: 区分数据成员的初始化与赋值,尽量和初始化顺序一致
在这里插入图片描述

– 通常情况下可以提升系统性能
– 一些情况下必须使用初始化列表(如类中包含引用成员)
– 注意元素的初始化顺序与其声明顺序相关,与初始化列表中的顺序无关(初始化列表可能顺序不一致,但是声明顺序是固定的,为了满足先构造的后销毁的原则)
– 使用初始化列表覆盖类内成员初始化的行为

先构造的后销毁,后构造的对象先销毁!
**● 缺省构造函数:**不需要提供实际参数就可以调用的构造函数

– 如果类中没有提供任何构造函数,那么在条件允许的情况下,编译器会自动合成一个缺省构造函数
– 合成的缺省构造函数会使用缺省初始化来初始化其数据成员
– 调用缺省构造函数时避免 most vexing parse
classname m();      //会把m认为是函数而不是对象

使用 = default 关键字定义缺省构造函数,只有编译器能合成的函数它才会去合成。

● 单一参数构造函数

– 可以视为一种类型转换函数
– 可以使用 explicit 关键字避免求值过程中的隐式转换

● 拷贝构造函数: 接收一个当前类对象的构造函数

– 会在涉及到拷贝初始化的场景被调用,比如:参数传递。因此要注意拷贝构造函数的形参类型
– 如果未显式提供,那么编译器会自动合成一个,合成的版本会依次对每个数据成员调用拷贝构造

● 移动构造函数 (C++11) :
将旧的对象转移给新对象后,旧的对象自动销毁。接收一个当前类右值引用对象的构造函数,进一步提升系统性能。

– 可以从输入对象中“  偷窃”  资源,只要确保传入对象处于合法状态即可
– 当某些特殊成员函数(如拷贝构造)未定义时,编译器可以合成一个,有移动调移动,没有移动调拷贝 (C++17)
– 通常声明为不可抛出异常的函数: noexcept,一旦抛出就会崩溃
– 注意右值引用对象用做表达式时是左值! 

PS:&& 代表右值引用

语义移动:
在C++中,移动语义是一种优化技术,用于在对象之间转移资源的所有权,而不是进行复制。 当你移动一个对象时,源对象的资源所有权转移到目标对象,源对象不再拥有该资源。

移动操作通常使用移动构造函数和移动赋值运算符来实现。这些特殊的成员函数允许你有效地将资源(如堆上的内存、文件句柄等)从一个对象转移到另一个对象,而无需进行代价昂贵的复制操作。

在进行移动操作时,原始对象的内存空间仍然存在,但它的状态可能会变为有效但不确定的状态。 是因为移动操作不对原始对象进行显式的清理或重置,这意味着你不能再对原始对象进行任何有意义的操作,因为它的资源已被移动到其他对象。对于移动后的原始对象,你可以选择销毁它或重新赋值为其他有效值。(为了避免误用移动操作导致不确定状态,建议清理!)

移动操作的主要好处是避免了不必要的复制开销,特别是在涉及大型对象或资源密集型对象时。通过移动对象,可以更高效地管理资源并提高程序的性能。

需要注意的是,只有具有可移动语义的对象才能被移动。这包括具有移动构造函数和移动赋值运算符的类,或者具有可移动成员的类(如std::unique_ptr、std::vector等)。

总结起来,移动操作将资源的所有权从一个对象转移到另一个对象,原始对象进入有效但不确定的状态,而移动后的对象获得资源的所有权。移动操作的主要目的是避免不必要的复制并提高程序性能。

● 拷贝赋值与移动赋值函数( operator = )

Str m;        //缺省构造函数  无参
Str m2 = m;   //拷贝赋值
Str m3(m2);   //移动构造函数
m3 = m;       //赋值运算符,会被翻译成 m3.opreator = (m);

拷贝赋值返回值是:类名&

– 注意赋值函数不能使用初始化列表
– 通常来说返回当前类型的引用
– 注意处理给自身赋值的情况
– 在一些情况下编译器会自动合成

● 析构函数

– 函数名:“ ” ~ 加当前类型,无参数,无返回值
– 用于销毁对象以及扫尾工作,执行完最后一句才会跳出执行内存回收。
– 注意内存回收是在调用完析构函数时才进行
– 除非显式声明,否则编译器会自动合成一个,其内部逻辑为平凡的
– 析构函数通常不能抛出异常

● 通常来说,一个类:

– 如果需要定义析构函数,那么也需要定义拷贝构造与拷贝赋值函数(显式地)
– 如果需要定义拷贝构造函数,那么也需要定义拷贝赋值函数
– 如果需要定义拷贝构造(赋值)函数,那么也要考虑定义移动构造(赋值)函数

没有定义在使用时就会造成二次释放或者多次释放
ps:

1. Rule of Three (三法则):
如果需要定义析构函数、拷贝构造函数或拷贝赋值函数中的任何一个,就需要显式定义这三个函数。这个原则确保了资源管理的正确性和一致性。2. Rule of Five (五法则):
在C++11及更高版本中,如果需要定义拷贝构造函数或拷贝赋值函数,就需要同时定义析构函数、拷贝构造函数和拷贝赋值函数,并且还要考虑定义移动构造函数和移动赋值函数。这个原则兼容了移动语义,提高了对象的性能和效率。

请注意,这些名称是广为接受的通用术语,用于描述C++编程中的最佳实践。
● 示例:包含指针的类:

● default 关键字
– 只对特殊成员函数有效

● delete 关键字
=delete 表示禁用该函数

– 对所有函数都有效
– 注意其与未声明的区别
– 注意不要为移动构造(移动赋值)函数引入 delete 限定符

在这里插入图片描述

● 如果只需要拷贝行为,那么引入拷贝构造即可
● 如果不需要拷贝行为,那么将拷贝构造声明为 delete 函数即可
● 注意 delete 移动构造(移动赋值)对 C++17 的新影响

如果一个类有用户自定义的拷贝赋值运算符或拷贝构造函数,或者有用户自定义的析构函数,那么该类的相关默认函数的隐式定义会被弃用,并且在未来的C++版本中可能会被删除,需要显式定义相关函数来替代。

5. 字面值类,成员指针与 bind 交互

● 字面值类:可以构造编译期常量的类型

– 其数据成员需要是字面值类型
– 提供 constexpr / consteval 构造函数 (小心使用 consteval )
– 平凡的析构函数(没有自定义行为的析构函数)
– 提供 constexpr / consteval 成员函数 (小心使用 consteval )
– 注意:从 C++14constexpr / consteval 成员函数非 const 成员函数

constexprconsteval区别:

constexprconsteval是C++中的两个关键字,用于指定编译时常量表达式和编译时求值函数。

  1. constexpr

    • constexpr是C++11引入的关键字,用于声明一个编译时常量表达式。它可以应用于变量、函数、构造函数等。
    • constexpr声明的变量必须在编译时求值并产生常量结果,可以用于编译时常量的计算和编译时优化。
    • constexpr函数是在编译时进行求值的函数,它的参数和返回值必须是可编译时求值的表达式,函数体内只能包含可编译时求值的语句。
    • 在C++14之前,constexpr函数只能包含一些简单的计算和控制流程,而在C++14及以后的版本中,constexpr函数可以包含更复杂的逻辑。
  2. consteval

    • constevalC++20引入的关键字,用于声明一个编译时求值函数,要求在编译时执行。
    • consteval函数是在编译时强制执行的函数,它的参数和返回值必须是可编译时求值的表达式,函数体内只能包含可编译时求值的语句。
    • constexpr函数不同,consteval函数在编译时强制要求函数的调用结果能在编译期间确定,否则会引发编译错误。
    • consteval函数适用于需要在编译时执行的严格要求,可以用于生成更高效的代码,但使用场景相对较少。

总结:
constexpr用于声明编译时常量表达式和函数,而consteval用于声明在编译时强制求值的函数。它们都提供了在编译期间进行求值和优化的能力,但consteval更加严格,要求函数在编译时一定能求值,否则会导致编译错误。

● 成员指针

– 数据成员指针类型示例: int A::*;  即使A中没有任何东西
– 成员函数指针类型示例: int (A::*)(double);
– 成员指针对象赋值: auto ptr = &A::x;● 注意域操作符子表达式不能加小括号(否则 A::x 一定要有意义,例如静态)– 成员指针的使用:● 对象 .* 成员指针● 对象指针 ->* 成员指针

无法加减,会被认为不是同一类型的,下式报错:
在这里插入图片描述

● bind 交互: 需要引入类的对象

– 使用 bind + 成员指针构造可调用对象
– 注意这种方法也可以基于数据成员指针构造可调用对象

http://www.ppmy.cn/news/819008.html

相关文章

【华为OD机试】数字反转打印【2023 B卷|100分】

【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述: 小华是个很有对数字很敏感的小朋友,他觉得数字的不同排列方式有特殊美感。 某天,小华突发奇想,如果数字多行排列,第一行1个数,第二行2个,第三行3个, 即第n行有n个数字,并且奇…

代码记录2

1 select year(replace(replace(replace(2013年10月21日,年,-),月,-),日,)) 2-1 create database question2 on primary( Namequestion2_data.mdf, SIZE6MB, MAXSIZE12MB, fileNAMED:\A1\question2_data.mdf, FILEGROWTH5% ) log on( NAMEquestion2_log.1df, SIZE6MB, MAXSIZE1…

扫雷游戏代码(升级版)

代码&#xff1a; #include<bits/stdc.h> #include<windows.h> #include<conio.h> using namespace std; int x,y,l,l2; bool s,c[12][12],flag; char a[12][12],b[12][12],f; int statistics_1(int j,int i); int statistics_2(int j,int i); void around(…

【CE入门教程】使用Cheat Engine(CE)修改游戏“植物大战僵尸”之僵尸篇

目录 1.寻找僵尸位置基址 2.实现“秒杀”僵尸&#xff08;修改僵尸血量&#xff09; 上一期教程中&#xff0c;我们学习了修改植物大战僵尸的单卡片无CD、全卡片无CD、豌豆射手射速修改以及实现豌豆射手发射“玉米加农炮”。PS&#xff1a;上篇链接&#xff1a;【CE入门教程】…

使用Cheat Enginee(CE)找到“植物大战僵尸”金币的内存基址

使用Cheat Enginee(CE)找到“植物大战僵尸”金币的内存基址 在ce中首次扫描当前金币数的十分之一&#xff0c;捡金币后再次扫描当前金币数的十分之一。 双击添加到地址列表&#xff0c;修改数值后金币数值随之改变。 查看谁改写了这个地址 双击打开&#xff0c; 可以看出…

【杂项】 Cheat Engine抓游戏基址

记录一下CE偏移抓游戏数据基址的过程吧…… 一、说明二、操作1&#xff09;首先手动抓数据2&#xff09;接下来建立指针3&#xff09;然后建立第二个指针3&#xff09;保存与使用脚本 一、说明 CE(Cheat Engine)是一个强大的游戏修改器&#xff0c;可以读取和写入游戏数据。在…

植物大战僵尸源代码

最近下了植物大战僵尸的代码,却发现我什么也看不懂,呜呼哀哉。 分析数据: 游戏基址:6a9ec0 768阳光: 5560 768 6a9ec0 最多几个植物:6a9ec0 768 144 24准备了几个植物:d24 774 6a9ec0僵尸行走:52AFCA486C55 获取怪物:413108 ebx:6a9ec0 768 怪物属性偏移:+2C X坐标+30 Y…

【CE入门教程】使用Cheat Engine(CE)修改游戏“植物大战僵尸”之植物篇

目录 1.单卡片无CD 1.1 思路一 1.2 思路二 2.全卡片无CD 3.豌豆射手射速修改&#xff08;修改植物射速&#xff09; 4.实现豌豆射手发射“玉米加农炮”&#xff08;思路&#xff09; 上一期教程中&#xff0c;我们学习了修改植物大战僵尸的阳光数量、向日葵生产阳光速率以…