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段中
未初始化的全局变量或静态全局对象存储于 .bss 段中

  静态局部对象   已初始化的静态局部变量存储于 .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++ 对象创建
类型一、 三个默认函数方式创建类对象

在C++中,对于一个类,C++的编译器都会为这个类提供四个默认函数,分别是
A()                                      // 默认构造函数
~A()                                    // 默认析构函数
A(const A&)                     // 默认拷贝构造函数
A & operator = (const A &) // 默认赋值操作函数。

 
1."默认构造函数"创建类对象

Person ps1;                                            // 隐式
Person ps2    = Person();                       // 显式
Person *pPs1 = new Person();              // new式-无临时对象

 实例化
2."拷贝构造函数"创建类对象

Person ps3(ps1); 

Person ps4 = ps1;

 实例化
3."赋值操作符"拷贝类对象

Person ps5

ps5 = ps1; 

 实例化
     
类型二、 "自定义构造函数"创建类对象 4."自定义构造函数"创建类对象

Person ps5("李敖", 83);                            // 隐式
Person ps6 = Person("李敖", 83);            // 显式

Person *pPs2 = new Person("李敖", 83); // new式-无临时对象

 实例化+初始化
       
类型三、 C++新特性创建类对象 5.使用"移动构造函数"创建类对象 Person ps7(std::move(ps5));  
6.使用"委托构造函数"创建类对象    
7.使用" std::initializer_list "统一初始化/列表初始化语法创建类对象

Person ps8 = {"李敖", 83};                         // 显式
Person ps9{"李敖", 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)

C++ 单例模式_c++单例模式_伐尘的博客-CSDN博客

 

posted @ 2023-08-16 15:09  suntroop  阅读(63)  评论(0)    收藏  举报