当前位置: 首页 > news >正文

【C++重要!!!】赋值与初始化的区别

在C++中,区分赋值(assignment)和初始化(initialization)是一个非常重要的问题,因为它们在语义、调用时机和涉及的函数上有着本质的区别。


1. 定义

  • 初始化(Initialization)

    • 指在对象创建时为其赋予初始值的过程。
    • 发生在对象生命周期的开始,通常由构造函数(包括默认构造函数、拷贝构造函数等)负责。
    • 语法上常见于声明时使用 = 或括号。
  • 赋值(Assignment)

    • 指在对象已经存在的情况下,修改其值的过程。
    • 发生在对象创建之后,通常由赋值运算符(operator=)负责。
    • 语法上表现为对已声明的对象使用 =

2. 主要区别

特性初始化(Initialization)赋值(Assignment)
时机对象创建时对象已存在后
涉及的函数构造函数(默认、拷贝、移动等)赋值运算符(operator=
资源管理直接构造,无需清理旧资源需要先清理旧资源,再赋值新值
语法示例T x = y;T x(y);x = y;
性能影响通常更高效(无需处理旧状态)可能涉及资源释放和重新分配

3. 如何区分:代码中的表现

在代码中,区分赋值和初始化主要看对象是否已经存在以及语法上下文

(1)初始化示例
Test t1;             // 默认初始化,调用默认构造函数
Test t2 = Test();    // 初始化,调用默认构造函数(可能优化为直接构造)
Test t3(t1);         // 初始化,调用拷贝构造函数
Test t4 = t1;        // 初始化,调用拷贝构造函数(不是赋值!)
Test t5 = getObj();  // 初始化,调用拷贝构造函数或移动构造函数
  • 这些都是在对象声明时进行的操作,触发构造函数。
  • 注意:Test t4 = t1; 看起来像赋值,但它是拷贝初始化,等价于 Test t4(t1);
(2)赋值示例
Test t1;        // 默认构造
t1 = Test();    // 赋值,调用赋值运算符
Test t2 = t1;   // 初始化(拷贝构造)
t2 = t1;        // 赋值,调用赋值运算符
t1 = getObj();  // 赋值,调用赋值运算符
  • 这些操作发生在对象已经构造之后,修改其状态,触发 operator=

4. 结合 Test 类分析

让我们用您提供的 Test 类来具体说明:

#include <iostream>
using namespace std;

class Test {
public:
    Test() : m_num(new int(100)) {
        cout << "construct: my name is jerry" << endl;
    }

    Test(const Test& a) : m_num(new int(*a.m_num)) {
        cout << "copy construct: my name is tom" << endl;
    }

    ~Test() {
        delete m_num;
        cout << "destruct Test class ..." << endl;
    }

    int* m_num;
};

Test getObj() {
    Test t;
    return t;
}
(1)初始化场景
int main() {
    Test t = getObj();  // 初始化
    cout << "t.m_num: " << *t.m_num << endl;
    return 0;
}
  • 行为Test t = getObj(); 是初始化。
  • 调用:触发拷贝构造函数(或因 RVO 优化直接构造)。
  • 输出(无 RVO):
construct: my name is jerry
copy construct: my name is tom     // 返回时的临时对象
destruct Test class ...            // getObj() 中的 t
copy construct: my name is tom     // main 中的 t
destruct Test class ...            // 临时对象
t.m_num: 100
destruct Test class ...            // main 中的 t  
  • 原因t 在声明时被构造,没有先存在再修改的过程。
(2)赋值场景
int main() {
    Test t;         // 默认构造
    t = getObj();   // 赋值
    cout << "t.m_num: " << *t.m_num << endl;
    return 0;
}
  • 行为
    • Test t; 是初始化,调用默认构造函数。
    • t = getObj(); 是赋值,调用默认赋值运算符(浅拷贝)。
  • 输出(假设未定义 operator=):
    construct: my name is jerry  // Test t
    construct: my name is jerry  // getObj() 中的 t
    copy construct: my name is tom  // 返回时的临时对象
    destruct Test class ...      // getObj() 中的 t 析构
    destruct Test class ...      // 临时对象析构
    // 此时 t.m_num 是野指针,访问未定义行为
    
  • 问题:默认赋值运算符执行浅拷贝,导致 t.m_num 指向已释放的内存。

如果定义赋值运算符:

Test& operator=(const Test& a) {
    if (this != &a) {
        delete m_num;
        m_num = new int(*a.m_num);
    }
    return *this;
}

输出变为:

construct: my name is jerry  // Test t
construct: my name is jerry  // getObj() 中的 t
copy construct: my name is tom  // 返回时的临时对象
destruct Test class ...      // getObj() 中的 t 析构
destruct Test class ...      // 临时对象析构
t.m_num: 100
destruct Test class ...      // t 析构

5. 区分的关键点

  • 语法位置
    • 声明时带 = 或括号(如 T x = y;):初始化。
    • 已声明对象后用 =(如 x = y;):赋值。
  • 对象状态
    • 初始化:对象尚未存在,直接构造。
    • 赋值:对象已有状态,需要清理旧资源。
  • 调用的函数
    • 初始化:构造函数(Test()Test(const Test&) 等)。
    • 赋值:operator=

6. 常见误区

  • T x = y; 不是赋值
    • 很多人看到 = 就认为是赋值,但这是拷贝初始化,调用拷贝构造函数。
    • 只有在 x 已存在时,x = y; 才是赋值。
  • 编译器优化
    • RVO 或拷贝省略可能让初始化看起来没有调用拷贝构造函数,但逻辑上仍是初始化。

7. 总结

  • 初始化:对象创建时,使用构造函数(Test t = getObj();)。
  • 赋值:对象存在后修改,使用赋值运算符(t = getObj();)。
  • 在您的原始代码中,只有初始化(Test t = getObj();),所以不需要赋值运算符也能正常运行。

相关文章:

  • Flutter三棵树是什么,为什么这么设计
  • 【5*】坐标规则类动态规划学习笔记
  • MindGYM:一个用于增强视觉-语言模型推理能力的合成数据集框架,通过生成自挑战问题来提升模型的多跳推理能力。
  • 【SpringMVC】常用注解:@PathVariable
  • vue2自定义指令实现 el-input 输入数字,小数点两位 最高10位,不满足则截取符合规则的值作为新值
  • 【Pytorch实战教程】拆解PyTorch中的多头注意力:原来Transformer的核心组件可以这样玩
  • 关于WPS的Excel点击单元格打开别的文档的两种方法的探究【为单元格添加超链接】
  • 【VS小知识】VS如何保存UTF8
  • Flutter Dart 面向对象编程全面解析
  • Day 2:基础知识巩固(HTML、CSS、JavaScript)
  • matlab 自适应模糊PID在反应釜温度控制中的应用
  • vue2用vscode调试打不上断点
  • Select 选择器选项位置偏移的解决方案
  • 出海行动派 | 全球服务新征程!Bonree ONE海外版正式发布
  • 使用 PaddlePaddle 官方提供的 Docker 镜像
  • Python个人学习笔记(15):模块(time,datetime,random)
  • ubuntu中使用ollama部署本地deepseek
  • 在Spring Boot项目中接入DeepSeek深度求索,感觉笨笨的呢
  • SpringMVC(五)拦截器
  • 深度学习中LayerNorm与RMSNorm对比
  • 秦洪看盘|指标股发力,A股渐有突破态势
  • 奥迪车加油时频繁“跳枪”维修两年未解决,4S店拒退换:可延长质保
  • 当代科技拟召开债券持有人会议 ,对“H20科技2”进行四展
  • 京东CEO许冉:外卖日单量接近2000万单,看到外卖对平台拉动和转化效应
  • 多家外资看好中国市场!野村建议“战术超配”,花旗上调恒指目标价
  • 权益类基金发行回暖,这些老将挂帅新基,谁值得买?