欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 社会 > C++总结01-类型相关

C++总结01-类型相关

2025/5/4 12:07:24 来源:https://blog.csdn.net/FairLikeSnow/article/details/147661127  浏览:    关键词:C++总结01-类型相关

一、数据存储

1.程序数据段

• 静态(全局)数据区:全局变量、静态变量
• 堆内存:程序员手动分配、手动释放
• 栈内存:编译器自动分配、自动释放
• 常量区:编译时大小、值确定不可修改

2.程序代码段

• 函数体
在这里插入图片描述

二、Stack 栈内存

• 栈内存属于执行期函数,编译时大小确定
• 函数执行时,栈空间自动分配
• 函数结束时,栈空间自动销毁
• 栈上对象线性分配,连续排列,没有内存碎片效应
• 栈内存具有函数本地性,不存在多线程竞争
• 栈内存有大小限制,可能会溢出,例如Linux默认为8MB,Windows默认为1MB
• 栈内存使用对象或引用直接使用,管理复杂度低

三、Heap 堆内存

• 堆内存属于具有全局共享特点,大小动态变化
• 对象分配时,手动分配堆内存(malloc/new)
• 对象释放时,手动释放堆内存(delete/free)
• 堆上对象链式分配,非连续排列
• 堆内存全局共享,存在多线程竞争可能性
• 堆内存大小没有栈内存严格限制,与机器内存总量和进程寻址空间相关
• 堆内存使用指针间接访问,管理复杂度高
• 堆内存有很高灵活性,虽性能较差,但可通过相关设施和编程技巧精细控制,从而获得改善。

四、堆-栈内存

• 栈内存分配快,布局连续,缓存友好,释放快
• 如果生存周期短,拷贝较少(传参、返回值),栈内存性能更好
• 堆内存有很高灵活性,但性能较差
• 堆内存在长运行程序有内存碎片效应,小块空闲内存得不到重用
• 堆分配需要寻找合适大小内存块,会花费更多时间
• 堆空间碎片化,容易降低缓存效率
• 编译器较难优化使用指针的代码
• 使用者需要确保申请释放成对,避免内存泄漏导致堆内存耗尽
• 使用者需要确保内存释放后不能访问(悬浮指针)
• 可以通过RAII 和指针移动操作避免拷贝代价。

五、值语义与引用语义

• 对内置类型和用户自定义类型提供同等支持。不存在特权类型或限定

5.1 值语义详解

对象以值的方式直接存储,传参、返回值、拷贝等。
1.行为特点
拷贝独立性:每次赋值或传参时,会生成一个完全独立的新对象(调用拷贝构造函数或移动构造函数)。

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1; // 值语义:v2是v1的深拷贝副本
v2.push_back(4);          // 修改v2不影响v1

2.底层原理
深拷贝:值语义的对象(如std::string、std::vector)会递归复制所有成员数据到新内存。
自动生命周期:对象超出作用域时自动调用析构函数释放内存(RAII机制)。

3.优点
安全性:数据隔离,避免意外的副作用。
确定性:生命周期清晰,无需担心悬空引用,没有内存泄漏,没有数据竞争

4.缺点
性能开销:拷贝大对象(如容器)成本高(可通过移动语义优化)。
不能支持虚函数多态

5.典型场景
需要独立修改的局部变量。
函数参数传递中不希望影响原数据时(如void func(std::string s))。

5.2 引用语义详解

对象以指针或引用的方式间接存储,参数、返回值、拷贝传递的是指针或引用。
1.行为特点
别名机制:引用或指针是原数据的“标签”,操作引用即操作原数据。

int a = 10;
int& ref = a;   // 引用语义:ref是a的别名
ref = 20;       // 直接修改a的值

2.底层原理
内存共享:引用本质是指针的语法糖(编译器自动解引用),指针直接存储内存地址。
手动管理:裸指针需注意生命周期(易悬空),引用需确保原对象存活。

3.优点
零拷贝:传递大对象时无性能开销。
共享修改:允许多个部分代码协同操作同一数据。

4.缺点
安全性风险:悬空引用、数据竞争(需配合const或智能指针)。
复杂性:需显式管理生命周期(如使用std::shared_ptr)。

5.典型场景
避免拷贝大对象(如void process(const std::vector& data))。
需要跨作用域共享数据(如类成员、回调函数)。

六、变量的生命周期

• 全局变量,函数中的静态变量,类静态数据成员:程序启动后加载,程序结束释放。
• 局部变量、自动对象(栈):自声明开始,到声明语句所在块结尾 }释放
• 堆变量,自由存储对象:new开始,delete结束
• 临时对象: 和表达式周期一致,通常类似自动对象(绑定引用除外)
• 线程局部对象, thread_local:随线程创建而创建,随线程结束而释放。

在C++中,临时对象(Temporary Objects)是编译器在表达式求值过程中隐式创建的、生命周期短暂的匿名对象。它们通常出现在类型转换、函数返回值或运算符重载等场景中。

临时对象的常见产生场景

1.函数返回非引用类型的对象

std::string getString() { return "Hello"; // 返回时构造临时对象
}
// 临时对象用于初始化s
std::string s = getString(); 

2.类型转换(隐式或显式)

cpp
int a = 10;
double b = a + 3.14; // int转换为double时生成临时对象

3.运算符重载

class Complex {
public:Complex operator+(const Complex& rhs) {return Complex(this->real + rhs.real, this->imag + rhs.imag); // 返回临时对象}
};
Complex c1, c2;
Complex c3 = c1 + c2; // operator+返回的临时对象用于初始化c3

4.函数参数传递时的类型不匹配

void printString(const std::string& s) { /*...*/ }
printString("Hello"); // 从const char*隐式构造临时std::string

5.构造函数的隐式调用

class Widget { /*...*/ };
void processWidget(Widget w) { /*...*/ }
processWidget(Widget()); // 传递临时Widget对象
临时对象的生命周期规则

1.一般规则
临时对象在完整表达式结束时销毁(通常是分号处)。

std::string s = getString() + " World"; 
// 临时对象(getString()的返回值)在分号后销毁

2.绑定到引用时的扩展
若临时对象被绑定到const引用或右值引用,其生命周期延长至引用的作用域结束。

const std::string& ref = getString(); // 临时对象生命周期延长至ref作用域结束
std::string&& rref = getString();    // 同样延长(C++11起)

3.成员访问时的陷阱
临时对象的成员通过指针/引用访问时,需确保临时对象存活:

const std::string* p = &(getString().c_str()); // 错误:临时对象已销毁
std::string&& s = getString(); // 正确:生命周期延长

七、变量的初始化

• 统一初始化:int a1{100}; int a2={100};
• 赋值初始化:int a3=100;
• 构造初始化:int a4(100);
• 大多数情况推荐使用统一初始化,又叫列表初始化,特别是对象、容器;对于数值,可防止隐式窄化转型。空列表{}使用默认值初始化。
• 基本数值类型,以及auto自动推断类型声明,可以继续使用赋值初始化(除非需要避免数值窄化转型)。

八、指针是万恶之源:内存错误的罪与罚

• 所有权不清晰(谁分配,谁释放?)
• 对象类型不清晰(栈对象、堆对象、数组对象、资源句柄?)
• 错误百出的指针
1.内存泄漏——忘记delete之前new的内存
2.悬浮指针—— 使用已释放内存(读取、或写入)、返回栈对象地址
3.重复删除—— 对已经删除过的对象,进行二次删除
4.删除非堆对象指针——对栈对象、全局/静态对象地址进行删除
5.分配与删除错误匹配—— new和free搭配,malloc和delete搭配,new[]和delete搭配,new和delete[] 搭配
6.使用空指针
7.使用失效引用

九、基于对象编程

数据成员(字段)+函数成员
对象有什么?
    实例成员与this指针
    静态成员
•对象在哪里?——空间分析
•基本类型成员
•内嵌对象成员
•内嵌指针成员
•操作符重载

深入理解面向对象
向下:深入理解三大面向对象机制
• 封装,隐藏内部实现
• 继承,复用现有代码
• 多态,改写对象行为
向上:深刻把握面向对象机制所带来的抽象意义,理解如何使用这些机制来表达现实世界,掌握什么是“好的面向对象设计”

十、C++ 对象模型基础

• C++对象内存布局
         按照实例数据成员声明顺序从上到下排列(与C语言保持兼容)
         虚函数指针占用一个指针size
          静态数据成员不参与
• 内存对齐与填充——
          对象内存对齐是为了优化CPU存储数据效率、避免数据截断
         按对齐系数(4字节、8字节)整倍数
         可使用#pragma pack(4)控制
         简单优化:长字段放前,短字段置后(聚集)
• 对象有多大?sizeof
可参考我的另一篇文章:单一继承类成员布局

十一、特殊成员函数与三法则(Rule of Three)

• 四大特殊成员函数
         • 默认构造函数(无参) , 如果不定义任何拷贝构造,编译器自动生成
         • 析构函数/ 拷贝构造函数 / 赋值操作符,如果不定义,编译器自动生成
         • 使用 default 让编译器自动生成。
         • 使用 delete 让编译器不要自动生成。
• 三法则:析构函数、拷贝构造函数、赋值操作符 三者自定义其一,则需要同时定义另外两个(编译器自动生成的一般语义错误)。
• 编译器自动生成的拷贝/赋值是按字节拷贝,如不正确,则需要自定义拷贝/赋值/析构行为:
         • 赋值操作符中的 Copy & Swap 惯用法
         • 注意赋值操作中避免“自我赋值”。
• 需要自定义三大函数的类,通常包含指针指向的动态数据成员。
备注:c++11之后增加移动构造和移动赋值函数

十二、清楚对象的构造/析构点

• 构造器什么时候被调用?
         • 当对象(包括嵌套成员)被定义时(堆栈、堆或静态)。
         • 当对象的数组被定义时(堆栈、堆或静态)。
         • 当函数参数以值传递时
         • 当函数返回一个对象时
         • 甚至适用于编译器生成的临时对象。
• 析构器什么时候被调用?
         • 当命名的堆栈对象、数组或参数超出范围时(包括嵌套对象成员)
         • 当堆对象或数组被删除时
         • 对于静态对象,在程序结束时
         • 对于临时对象,在创建它们的 "完整表达式 "结束时调用

C++内嵌对象的构造和析构时机
在C++中,内嵌对象(作为类的成员变量)的构造和析构时机遵循特定的规则,理解这些规则对于正确管理对象生命周期非常重要。

构造顺序
构造时机:内嵌对象在包含它的类对象构造时被构造

构造顺序:

按照成员变量在类定义中声明的顺序依次构造(不是初始化列表中的顺序)

先构造所有内嵌对象,然后才执行包含类的构造函数体

class A {
public:A() { cout << "A constructed" << endl; }~A() { cout << "A destroyed" << endl; }
};class B {
public:B() { cout << "B constructed" << endl; }~B() { cout << "B destroyed" << endl; }
};class Container {A a;B b;
public:Container() : b(), a() {  // 初始化列表顺序不影响实际构造顺序cout << "Container constructed" << endl;}~Container() {cout << "Container destroyed" << endl;}
};// 使用:
Container c;
/* 输出顺序:
A constructed
B constructed
Container constructed
*/

析构顺序
析构时机:内嵌对象在包含它的类对象析构时被析构

析构顺序:

先执行包含类的析构函数体

然后按照成员变量声明顺序的逆序析构内嵌对象

// 接上面的例子,当Container对象离开作用域时:
/* 输出顺序:
Container destroyed
B destroyed
A destroyed
*/

十三、常用类型——数组最佳实践

• 尽量避免使用C风格数组,有很多安全隐患
         • 本质是指针指向的一块连续内存,引用语义
         • 不带长度信息,易错点:拷贝、传参、返回值
• 不要使用指针传递数组,传递指针仅代表单个对象
• 不要使用C风格数组承载多态对象(基类、子类)
• 使用抽象管理内存
         • 使用vector<T>实现变长数组,替代堆上的C风格数组
         • 使用array<T>实现定长数组,替代栈上的C风格数组
在C++中,使用C风格数组(如Base array[10])来存储多态对象会导致严重的对象切片问题,应该避免这种做法
请看我的另一篇文章不要以多态的方式处理数组

十四、常用类型——字符串最佳实践

• 尽量避免使用C风格字符串(“”默认字面常量)
         • 零结尾的字符数组,与字符数组有区别
         • 拥有一切C风格数组的安全隐患
• 使用string替代C风格字符串(“”字面常量以s结尾)
         • 能够正确分配资源,处理所有权、拷贝、扩容等操作
         • 但谨慎处理C风格字符串API和string的交互
• string内部实现了短字符串优化技术,拥有极高性能
         • 短字符串(<14字符)默认存在栈中,长字符串将分配于堆上。
• string_view 字符串的只读视图,表示为(指针,长度)
         • 不拥有,,不拷贝字符串,是字符串只读操作的性能之选

十五、其他类型——枚举类

• enum class 是一种限制了作用域的强类型枚举
         • 强类型,不能和整数类型进行隐式转换。
         • 可以显式指定枚举类的基础类型(存储类型),默认int
         • 可以使用整数常量初始化枚举值
         • 和整数之间转型要使用显式转型(static_cast)
• 普通enum类型继承自C语言
         • 弱类型,和基础类型存在隐式转换
         • 作用域位于enum本身所在作用域(名字空间污染)
• 建议使用enum class替换不安全的enum

十六、戒除C语言的“不良习惯”

• 辨析使用场景和对象所有权,谨慎使用裸指针
• 使用C++类型转换替换C风格的强制转换
• 使用模板和编译时计算等替换C风格的宏机制
• 尽量避免全局数据,谨慎使用全局函数
• 严格避免 malloc() 和 free()

场景需求推荐转换方式
基本类型安全转换static_cast
多态类型向下转型dynamic_cast
移除 const/volatileconst_cast
低级二进制重解释reinterpret_cast

十七、性能指南——类型与成员(1)

• 如果函数非常小,并时间敏感,将其声明为 inline
• 如果函数可能在编译期进行求值,就将其声明为 constexpr
• 类定义中禁止不期望的复制
• 使用成员初始化式来对类内数据成员进行默认值初始化
• 如果定义或者 =delete 了任何复制、移动或析构函数,请定义或者 =delete 它们全部(五法则)
• 将单参数的构造函数声明为 explicit,复制和移动构造函数则不• 采用 union 用以节省内存

十八、性能指南——类型与成员(2)

• 通过常量引用传递只读参数,而不是通过值。
• 尽可能地推迟对象的定义:晚加载,早释放
• 优先在构造函数中进行初始化而不是赋值。
• 考虑重载以避免隐式类型转换。
• 理解返回值优化(RVO)

返回值优化(Return Value Optimization,RVO)是 C++ 编译器的一项重要优化技术,用于消除函数返回对象时的临时对象构造和复制/移动操作。
RVO 的类型
1.命名返回值优化(NRVO, Named Return Value Optimization):
         • 优化命名局部变量的返回
         • 不是强制性的,但大多数现代编译器都支持
2.纯 RVO:
         • 优化临时对象的返回
触发 RVO 的条件
1.返回的类型与函数返回类型完全一致
2.返回的是一个局部对象(对于 NRVO)或临时对象(对于纯 RVO)
3.所有返回路径返回同一个对象(对于 NRVO)
示例:

#include <iostream>class Test {
public:Test() { std::cout << "Constructor\n"; }Test(const Test&) { std::cout << "Copy Constructor\n"; }Test(Test&&) { std::cout << "Move Constructor\n"; }~Test() { std::cout << "Destructor\n"; }
};// 可能应用 NRVO
Test createTest() {Test t;return t;  // 可能被优化,不调用拷贝/移动构造函数
}// 纯 RVO
Test createTest2() {return Test();  // 从 C++17 开始,保证不调用移动/拷贝构造函数
}int main() {std::cout << "Creating test1:\n";Test test1 = createTest();  // 通常只有一次构造std::cout << "\nCreating test2:\n";Test test2 = createTest2();  // 保证只有一次构造return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词