共计 2340 个字符,预计需要花费 6 分钟才能阅读完成。
核心概念
基本类型赋值 vs 类对象赋值
基本类型的赋值是最简单的操作,直接复制值:

int a = 10;
int b = a; // 简单的值复制
但对于类对象,赋值操作会调用拷贝构造函数(如果定义了):
class MyClass {
public:
int* data;
MyClass(int size) {data = new int[size]; }
~MyClass() { delete[] data; }
// 拷贝构造函数
MyClass(const MyClass& other) {data = new int[/*size*/];
std::copy(other.data, other.data + /*size*/, data);
}
};
MyClass obj1(10);
MyClass obj2 = obj1; // 调用拷贝构造函数
函数调用的参数传递方式
- 值传递 :创建参数的完整副本
void func(MyClass obj) {/* 操作副本 */}
func(obj1); // 调用拷贝构造函数
- 引用传递 :操作原始对象
void func(MyClass& obj) {/* 操作原对象 */}
func(obj1); // 无拷贝
- 指针传递 :传递对象地址
void func(MyClass* obj) {/* 通过指针操作 */}
func(&obj1); // 传递地址
痛点分析
浅拷贝问题
没有定义拷贝构造函数时的默认行为:
class BadClass {
public:
int* data;
BadClass(int size) {data = new int[size]; }
~BadClass() { delete[] data; }
// 无拷贝构造函数!};
BadClass b1(10);
BadClass b2 = b1; // 浅拷贝
// 析构时会导致同一内存被释放两次
临时对象生命周期
void process(const std::string& str) {/*...*/}
process("temporary"); // 临时 string 对象在函数调用后销毁
返回局部变量引用
int& badFunc() {
int local = 42;
return local; // 错误!局部变量即将销毁
}
解决方案
Rule of Three/Five
class SafeClass {
public:
int* data;
size_t size;
// 构造函数
SafeClass(size_t s) : size(s), data(new int[s]) {}
// 1. 析构函数
~SafeClass() { delete[] data; }
// 2. 拷贝构造函数
SafeClass(const SafeClass& other) : size(other.size), data(new int[other.size]) {std::copy(other.data, other.data + size, data);
}
// 3. 拷贝赋值运算符
SafeClass& operator=(const SafeClass& other) {if (this != &other) {delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
// 4. 移动构造函数 (C++11)
SafeClass(SafeClass&& other) noexcept : data(nullptr), size(0) {swap(*this, other);
}
// 5. 移动赋值运算符 (C++11)
SafeClass& operator=(SafeClass&& other) noexcept {swap(*this, other);
return *this;
}
};
使用 std::move
void processBigData(SafeClass&& data) {/* 高效接管资源 */}
SafeClass bigData(10000);
processBigData(std::move(bigData)); // 转移所有权
参数传递决策树
- 小类型(int 等):值传递
- 只读大对象:const 引用
- 需要修改的原始对象:非 const 引用
- 需要接管资源的对象:右值引用
- 可选参数:指针(可以传递 nullptr)
避坑指南
- explicit 关键字
class MyString {
public:
explicit MyString(int size) {/*...*/} // 禁止隐式转换
};
// MyString s = 10; // 错误!必须显式调用构造函数
MyString s(10); // 正确
- 返回值优化(NRVO)
SafeClass createSafe() {SafeClass obj(100);
return obj; // 编译器可能优化掉拷贝
}
-
线程安全
-
值传递:线程安全(每个线程有自己的副本)
- 引用 / 指针传递:需要同步机制
自测题
- 下列哪种参数传递方式会产生对象拷贝?
A. 值传递
B. const 引用
C. 右值引用
D. 指针传递
答案:A
- 哪个 C ++ 规则要求在定义析构函数时通常也需要定义拷贝操作?
A. SOLID 原则
B. Rule of Three
C. RAII
D. DRY 原则
答案:B
- 对于一个大对象,如果函数只需要读取它的内容,最优的参数声明方式是:
A. void func(Object obj)
B. void func(Object& obj)
C. void func(const Object& obj)
D. void func(Object* obj)
答案:C
总结
C++ 的赋值和函数调用看似简单,但背后隐藏着许多需要注意的细节。理解拷贝语义、参数传递方式和对象生命周期是写出健壮代码的关键。随着 C ++ 标准的演进,移动语义等新特性为我们提供了更多优化选择,但也带来了新的学习曲线。建议新手从基本规则开始,逐步掌握更高级的特性,并在实际项目中多加练习。
正文完
