C++ 对象实例化、对象初始化、程序 与 内存关系透析
参考资料:C++和STL参考手册:
学习交流网站 菜鸟教程 https://www.runoob.com/cplusplus/cpp-tutorial.html https://zh.cppreference.com/w/首页 https://www.cplusplus.com https://www.tutorialspoint.com/cplusplus https://www.learncpp.com https://github.com/fffaraz/awesomecpp https://stackoverflow.com https://hackingcpp.com/ https://learn.microsoft.com/zh-cn/cpp/standard-library/cpp-standard-library-overview?view=msvc-170 刷题网站 https://www.studycpp.cn/ 力扣:https://leetcode.cn/
牛客网:https://www.nowcoder.com/exam/oj/ta?tpId=37
https://hackingcpp.com
一、 对象、内存、程序三者关系
1.1 程序运行时(进程)虚拟内存分布:

1.2 程序存储时与运行时的关系:

1.3 依据内存分类对象
| 栈对象 | 隐含调用构造函数(程序中没有显式调用) | ||
| 堆对象 | new | 隐含调用构造函数(程序中没有显式调用),要显式释放 | |
| 全局对象、静态全局对象 |
全局对象的构造先于 main 函数 已初始化的全局变量或静态全局对象存储于.data段中 |
||
| 静态局部对象 | 已初始化的静态局部变量存储于 .data 段中 未初始化的静态局部变量存储于 .bss 段中 |
||
1 #include <iostream> 2 using namespace std; 3 4 class Trace 5 { 6 public: 7 Trace(int n) : m_val(n) 8 { 9 cout << "Trace " << m_val << " ..." << endl; 10 } 11 ~Trace() 12 { 13 cout << "~Trace " << m_val << " ..." << endl; 14 } 15 private: 16 int m_val; 17 }; 18 19 int var1; // 未初始化的全局变量,初始值为0。var1存储于.bss段中。(block started by symbol) 20 int var2 = 10; // 已初始化的全局变量,初始值为10。var2存储于.data段中。 21 Trace g_obj1(100); // 全局对象 的构造先于main函数, 22 static Trace g_obj2(200); // 静态全局对象 的构造先于main函数 23 24 int main(void) 25 { 26 std::cout << "\nEntering main ..." << std::endl; 27 28 Trace obj1(1000); // 栈上创建的对象,在生存期结束的时候自动释放 29 { 30 Trace obj2(2000); 31 } 32 33 { 34 Trace *obj3 = new Trace(3000); // 堆上创建的对象,要显式释放 35 delete obj3; 36 } 37 38 { 39 static int var3; // var3存储于.bss段中 (编译期初始化) 40 static int var4 = 20; // var4存储于.data段中 (编译期初始化) 41 42 static Trace obj4(3300); // 静态局部对象 运行期初始化 .data段 43 } 44 std::cout << "Exiting main ...\n" << std::endl; 45 return 0; 46 }
打印输出如下:

二、对象创建:实例化与初始化
注意:实例化、初始化 和 赋值是完全不一样的。
2.1 对象的实例化与对象初始化的区别
实例化 = 创建对象:分配内存空间,使对象从概念变为实体
初始化 = 设置初值:赋予对象确定的初始状态,避免未定义行为
2.1.1 实例化(Instantiation)
含义:根据类定义创建具体对象的过程。
核心行为:
- 内存分配:编译器为对象分配内存空间(栈或堆)
- 调用构造函数:触发对象的构造流程
- 对象诞生:完成此步骤后,对象实体正式存在
示例:
class Student { std::string name; int age; int No; public: Student() {} // 默认构造函数
Student(string _name,int _no):name(_name),No(_no){} //自定义构造函数 }; Student s_g1; // BSS段实例化
Student s_g2("张三","2025178"); // data段实例化 + 初始化 int main() { Student s_a; // 栈上实例化 Student* s_b = new Student(); // 堆上实例化 }
P.S. : 此时 s_a 和 s_b 已存在,但成员name/age值未定义(随机值)
2.1.2 初始化(Initialization)
含义:为对象成员变量赋予初始值的过程。
核心行为:
- 设置初始状态:确保对象处于可预测的安全状态
- 发生在构造阶段:通常通过构造函数实现
- 避免未定义行为:消除成员变量的随机值风险
|
方式 |
描述 |
代码示例 | 特点 | |
| 构造函数初始化 | 构造函数{内=赋值} | Student() { name=""; age=0; } | 实际是赋值操作,非初始化 | |
| 初始化列表 | 构造函数 : 成员()初始化列表 | Student() : name(""), age(0) { } | 真正初始化(推荐) | |
| 类声明时(就地)初始化 | C++11 - 就地初始化 使用等号= 或者 花括号{} 进行就地的非静态成员变量,声明初始化 |
std::string name = "Unknown"; int No{34030220251021}; |
“类内”直接初始化 |
三种方法的顺序为:声明时初始化 -> 初始化列表 -> 构造函数初始化
先进行声明时初始化,然后进行初始化列表初始化,最后进行构造函数初始化。因此初始化列表初始化的变量值会覆盖掉声明时初始化的值,而构造函数中初始化的值又会覆盖掉初始化列表的。见下例子:
#include <iostream> using namespace std; class A { public: int a = 1; // 声明时"就地"初始化 A(int a_) :a(2) // 初始化列表 { a = a_; // 构造函数初始化 } }; int main() { A t(3); cout << "t.a=" << t.a << endl; // t.a=3 return 0; }
注意:典型错误
Student s; // 已实例化,但未初始化! cout << s.age; // 危险!访问随机值
extern Student global; // 声明(未实例化)
Student global("Tom", 20); // 实例化+初始化
创建类对象方式如下:C++类对象的创建方式_c++创建对象-CSDN博客
| 类型一、 三个默认函数方式创建类对象
在C++中,对于一个类,C++的编译器都会为这个类提供四个默认函数,分别是 |
1."默认构造函数"创建类对象 |
Person ps1; // 隐式 |
实例化 |
| 2."拷贝构造函数"创建类对象 |
Person ps3(ps1); Person ps4 = ps1; |
实例化 | |
| 3."赋值操作符"拷贝类对象 |
Person ps5 ps5 = ps1; |
实例化 | |
| 类型二、 "自定义构造函数"创建类对象 | 4."自定义构造函数"创建类对象 |
Person ps5("李敖", 83); // 隐式 Person *pPs2 = new Person("李敖", 83); // new式-无临时对象 |
实例化+初始化 |
| 类型三、 C++新特性创建类对象 | 5.使用"移动构造函数"创建类对象 | Person ps7(std::move(ps5)); | |
| 6.使用"委托构造函数"创建类对象 | |||
| 7.使用" std::initializer_list "统一初始化/列表初始化语法创建类对象 |
Person ps8 = {"李敖", 83}; // 显式 Person *pPs3 = new Person{"李敖", 83}; // new式-无临时对象 |
实例化+初始化 | |
| 类型四、 使用智能指针创建类对象 | 8. 使用shared_ptr创建类对象 | ||
| 9. 使用unique_ptr创建类对象 | |||
| 10. 使用weak_ptr创建类对象 | |||
| *、std::make_shared或std::make_unique为什么代替new? | |||
2.1."=":拷贝初始化(copy initialization)
这种初始化形式是从C继承的。此种方式的初始化在现代C+中已不再受欢迎,因为对于某些复杂类型来说,此种方式初始化的效率低于其它形式的初始化。
每当隐式拷贝或转换值时,也会使用拷贝初始化,例如按值将参数传递给函数、按值从函数返回或按值捕获异常时。
2.2."()":构造函数/直接初始化(direct initialization)
通过括号(parentheses)提供值来声明变量。构造函数初始化和旧的普通初始化方式(=)的区别在于,它总是返回括号中的最后一个值,无论它的大小或符号是什么。
就像拷贝初始化一样,直接初始化在现代C++中已经不再受欢迎,很大程度上是因为被列表初始化所取代。然而,我们现在知道列表初始化有其自身的一些怪癖,因此直接初始化在某些情况下再次得到使用。
当值显式转换为另一种类型时,也会使用直接初始化。
直接初始化不受欢迎的原因之一是它使得很难区分变量和函数。
2.2.1 默认"无参"构造函数 Class A () = default; 实例化: 创建+初始化
static ClassA objA; //等价于 static ClassA objA( ); // 类定义静态对象:static 类名 对象名; ClassA <Type> objA; //等价于 ClassA <Type> objA(); //类模板定义对象:类模板名 <真实类型参数表> 对象名;
2.2.2 有参数构造函数ClassA (...) ; 实例化: 创建+初始化
ClassA objA(a, b, c); //类名 对象名(构造函数实际参数表); ClassA <Type> objA(a, b, c); //类模板名<真实类型参数表> 对象名(构造函数实际参数表);
3.3."{}":统一初始化(list initialization or uniform initialization or brace initialization):
c++11标准中提出一种统一初始化现代方法(也叫大括号初始化)。用大括号代替括号(curly braces instead of parentheses)。与构造函数初始化不同,此赋值方法只能采用大括号中的一个值,提供多个值将返回编译错误。
列表初始化还有一个额外的好处:列表初始化中的"缩小转换(narrowing conversions,是隐式转换)"格式不正确。这意味着,如果你尝试使用变量无法安全保存的值来初始化变量,则编译器需要生成诊断信息(通常是错误)。
拷贝和直接初始化只会删除小数部分,从而导致值初始化为可变宽度。你的编译器可能会选择性地警告你这一点,因为很少希望丢失数据。但是,通过列表初始化,编译器需要在这种情况下生成诊断。
你可以在通常进行初始化的任何地方使用大括号初始化,例如,作为函数参数或返回值,或者与new关键字一起使用。
四、对象调用方式
4.1 C++11智能指针之std::shared_ptr 共享所有权
std::shared_ptr <Type> ptrObjet
std::shared_ptr是C++中用于实现共享所有权的智能指针·类模板。它提供了一种方便的方式来管理动态分配的资源,特别是在多线程环境下。具体用法如下
ptrObjet= std::make_shared<Type>(...);
3.2 C++11智能指针之std::unique_ptr 独享所有权
std::unique_ptr <Type> ptrObject;
uniqut_ptr 是一种对资源具有排他性拥有权(独享所有权)的智能指针类模板,即一个对象资源只能同时被一个unique_ptr指向
std::unique_ptr 在其生命周期结束时自动释放所管理的资源,无需手动释放;
注意:unique_ptr是独占所有权的智能指针,不能被复制或者拷贝操作,但可以通过 std::move()转移所有权
//1.定义 std::unique_ptr <Type> ptrObject; //创建一个空的智能指针 //2.初始化 //2.1 new std::unique_ptr<Type> ptrObject(new Type()); //2.2 make_unique std::unique_ptr<Type> ptrObject(std::make_unique<Type>(...)); std::unique_ptr<Type> ptrObject = std::make_unique<Type>(...); 3 转移所有权 3.1 move std::unique_ptr <Type> ptrObjectB = std::move(ptrObjectA); 3.1 reset ptrObject.reset(new Type(...)); //智能指针"绑定”(重置)动态对象 ptrObjectB.reset(ptrObjectA.release()); //智能指针"绑定”(重置)释放对象
四、开发模式
单例模式、静态类和线程内唯一对象:
Refer:谈一谈单例模式、静态类和线程内唯一对象有什么区别_懒得勤快的博客_互联网分享精神 (masuit.com)
浙公网安备 33010602011771号