week02-实践思考笔记

  1. 1. 值传递 vs 引用传递 实践思考笔记
    1. 1.1. 一、实践思考题标准答案
      1. 1.1.1. 1. 删掉11~16行拷贝构造,编译器会自动生成吗?是平凡拷贝吗?
      2. 1.1.2. 2. 删掉类的m_str变量,观察函数的调用差异?
      3. 1.1.3. 3. 对比test1(24行)和test2(28行)不同函数参数调用的区别?
    2. 1.2. 二、值传递(test1)vs 引用传递(test2)核心对比
    3. 1.3. 三、关键补充(结合汇编重点)
    4. 1.4. 四、总结(必记重点)
  2. 2. 关于初始化的实践思考笔记
    1. 2.1. 一、先看代码(你要分析的 5~7 行)
      1. 2.1.1. 核心结论(直接写答案)
    2. 2.2. 二、逐行解释作用(最标准)
      1. 2.2.1. 1. double correctionTime {};
      2. 2.2.2. 2. bool parseSuccess = true;
      3. 2.2.3. 3. int correctionSuccessNum {};
    3. 2.3. 三、这三行真正的目的
      1. 2.3.1. 1. 防止成员变量出现随机垃圾值
      2. 2.3.2. 2. 统一初始化风格,代码更安全
      3. 2.3.3. 3. 不管对象怎么创建,都有确定值
    4. 2.4. 四、最关键的区别
    5. 2.5. 五、最终总结
      1. 2.5.1. 第 5~7 行代码的作用:

值传递 vs 引用传递 实践思考笔记

核心代码基础(对应题干代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string>

class Test {
public:
Test() {}
~Test() {}
// 可手动删除拷贝构造,观察编译器行为
Test(const Test& ts) {
this->m_value1 = ts.m_value1;
this->m_value2 = ts.m_value2;
this->m_str = ts.m_str;
}
private:
int m_value1 = 1;
int m_value2 = 2;
std::string m_str = "hello"; // 可手动删除,观察差异
};

void test1(Test ts) {} // 值传递
void test2(Test& ts) {} // 引用传递

int main() {
Test temp;
test1(temp); // 调用test1(值传递)
test2(temp); // 调用test2(引用传递)
return 0;
}

一、实践思考题标准答案

1. 删掉11~16行拷贝构造,编译器会自动生成吗?是平凡拷贝吗?

✅ 会自动生成默认拷贝构造函数;属于逐成员浅拷贝(非平凡拷贝,仅当类为“平凡类型”时才是平凡拷贝)。

补充:默认拷贝构造的行为的是对所有成员变量逐一向拷贝——int类型直接复制值,std::string类型调用其自身的拷贝构造,指针类型仅复制地址(不复制指向内容)。

2. 删掉类的m_str变量,观察函数的调用差异?

删掉m_str后,Test类仅包含int类型成员,成为平凡类型(trivial type),编译器会优化调用逻辑:

  • 调用test1(值传递)时,不调用拷贝构造函数,直接通过memcpy复制对象内存(8字节,两个int),效率极高;

  • test2(引用传递)无变化,依旧仅传递对象地址,零开销;

  • 汇编中无“call 拷贝构造”指令,test1内部仅保留析构调用(清理栈上副本)。

3. 对比test1(24行)和test2(28行)不同函数参数调用的区别?

核心区别:值传递产生副本(有开销),引用传递仅传地址(零开销),具体对比如下:

二、值传递(test1)vs 引用传递(test2)核心对比

对比维度 test1(Test ts)值传递 test2(Test& ts)引用传递
参数本质 在栈上新建一个对象副本 传递原对象的内存地址(本质是指针)
构造/析构调用 调用1次拷贝构造(生成副本)+ 1次析构(销毁副本) 无任何构造、析构调用
有m_str(string)时 开销大幅增加(需拷贝string内容、分配内存) 依旧零开销(仅传地址)
汇编指令 指令多,包含call拷贝构造、call析构 指令极少,内部为空操作(push/pop ebp后直接ret)
适用场景 仅用于int等简单小类型(开销可忽略) 复杂类型(如带string、指针的类)永远优先使用

三、关键补充(结合汇编重点)

  • 值传递的核心开销:副本的拷贝与销毁,尤其是包含std::string、动态内存等资源时,开销会急剧增加;

  • 引用传递的核心优势:不产生副本,直接操作原对象(或仅传地址),无任何额外开销;

  • 平凡类型(仅含int、char等基础类型):编译器会优化拷贝行为,无需调用拷贝构造,直接内存复制,效率接近值传递的简单类型。

四、总结(必记重点)

  1. 未手动编写拷贝构造时,编译器自动生成默认拷贝构造,执行浅拷贝;

  2. 类无复杂成员(如string、指针)时,成为平凡类型,拷贝行为被编译器优化;

  3. 复杂类型的函数参数,优先使用引用传递,避免拷贝构造和析构的额外开销。

关于初始化的实践思考笔记


一、先看代码(你要分析的 5~7 行)

1
2
3
4
5
6
struct TestResult {
std::string errorPinyin; // 成员1
double correctionTime {}; // 第5行:【统一初始化】
bool parseSuccess = true; // 第6行:【类内初始化】
int correctionSuccessNum {};//第7行:【统一初始化】
};

核心结论(直接写答案)

第5~7行的作用:给成员变量做【类内初始化】,保证变量一定有初始值,避免未定义行为。


二、逐行解释作用(最标准)

1. double correctionTime {};

作用:值初始化(零初始化)

  • 空大括号 {} → 强制初始化为 0.0
  • 不管在哪里定义对象,这个变量永远不会是随机值

2. bool parseSuccess = true;

作用:类内默认初始化

  • 直接赋值 = true
  • 对象创建时,默认就是 true

3. int correctionSuccessNum {};

作用:值初始化(零初始化)

  • 空大括号 {} → 强制初始化为 0
  • 绝对不会出现随机垃圾值

三、这三行真正的目的

1. 防止成员变量出现随机垃圾值

如果不写初始化:

1
int correctionSuccessNum; // 不初始化

那么它的值是随机的、不确定的,会导致:

  • 程序逻辑错误
  • 崩溃
  • 未定义行为

2. 统一初始化风格,代码更安全

C++11 推荐写法:

  • {} → 零初始化(最安全)
  • = 值 → 默认初始化

3. 不管对象怎么创建,都有确定值

无论你怎么定义:

1
TestResult result;

成员变量一定有初始值,不会出现野值。


四、最关键的区别

写法 效果
int num; 随机值(危险)
int num{}; 0(安全)
bool b = true; true(安全)
double d{}; 0.0(安全)

五、最终总结

第 5~7 行代码的作用:

  1. 对结构体成员变量进行类内初始化,保证变量创建时就拥有确定的初始值;
  2. 避免未初始化带来的随机垃圾值、未定义行为、程序崩溃
  3. 使用 {} 进行值初始化(零初始化),使用 = true 进行默认初始化
  4. 让结构体无论在任何场景下创建对象,成员变量都保持安全、确定的状态。