11.一个对象=另一个对象会发生什么(赋值构造函数)
🔬 对象赋值的内部机制:赋值运算符解析
📖 内容概览
- 理解C++中对象赋值操作的本质
- 区分拷贝构造函数和拷贝赋值运算符
- 掌握浅拷贝与深拷贝的区别及应用场景
- 学习赋值运算符的实现要点和最佳实践
🎯 核心概念
🧩 赋值操作的本质
当执行对象A = 对象B时,实际上调用的是拷贝赋值运算符(而非构造函数)。如果类没有显式定义该运算符,编译器会自动生成合成的拷贝赋值运算符。
🔄 浅拷贝与深拷贝
- 浅拷贝:直接复制对象的所有成员值,包括指针地址。对于包含指针成员的类,会导致多个对象共享同一内存。
- 深拷贝:为指针成员重新分配内存,并复制内存中的内容。每个对象拥有独立的内存空间。
📋 合成的拷贝赋值运算符
- 编译器自动生成的拷贝赋值运算符执行浅拷贝
- 对于无指针成员的类,浅拷贝通常是安全的
- 对于有指针成员的类,浅拷贝会导致悬挂指针和多次释放内存的问题
🛠️ 赋值运算符的实现
🔧 基本语法
// 声明Human& operator=(const Human& other);// 实现Human& Human::operator=(const Human& other) { // 自赋值检查 if (this == &other) { return *this; // 自赋值时直接返回 }
// 拷贝成员变量 this->name = other.name; this->age = other.age; this->sex = other.sex; // 拷贝指针成员(浅拷贝) strcpy_s(this->addr, ADDR_LEN, other.addr); // 返回*this,支持链式赋值 return *this;}🔧 调用时机
// 对象赋值时自动调用Human zhangsan("张三", 18, "男");Human lisi;lisi = zhangsan; // 调用拷贝赋值运算符💻 完整代码示例
📝 Human.h
#pragma once#include <string>#include <iostream>#include <Windows.h>using namespace std;class Human {public: Human(); ~Human(); Human(string name, int age, string sex);
// 拷贝赋值运算符(注意:不是赋值构造函数) Human& operator=(const Human& other); // 成员函数 string getName() const; string getSex() const; int getAge() const; const char* getAddr() const; void setAddr(char* addr); void description() const;private: string name; // 姓名(string自动管理内存) string sex; // 性别(string自动管理内存) int age; // 年龄(基本类型) char* addr; // 地址(指针,需要手动管理内存)};📝 Human.cpp
#include "Human.h"#define ADDR_LEN 64Human::Human() { name = "无名"; sex = "未知"; age = 18; const char* addr_s = "China"; addr = new char[ADDR_LEN]; strcpy_s(addr, ADDR_LEN, addr_s);}Human::Human(string name, int age, string sex) { this->age = age; this->name = name; this->sex = sex;}Human& Human::operator=(const Human& other) { // 自赋值检查 if (this == &other) { return *this; } // 拷贝非指针成员 this->name = other.name; this->age = other.age; this->sex = other.sex; // 拷贝指针成员(浅拷贝) strcpy_s(this->addr, ADDR_LEN, other.addr); // 返回*this,支持链式赋值 return *this;}// 成员函数实现string Human::getName() const { return name; }string Human::getSex() const { return sex; }int Human::getAge() const { return age; }const char* Human::getAddr() const { return addr;}void Human::setAddr(char* addr) { if (!addr) return; strcpy_s(this->addr, ADDR_LEN, addr);}Human::~Human() { delete[] addr; // 析构函数释放动态内存}void Human::description() const { cout << "姓名: " << name << ", 年龄: " << age << ", 性别: " << sex << endl;}main.cpp
#include "Human.h"
void showMsg(const Human& man1, const Human& man2) { cout << "张三地址: " << man1.getAddr() << endl; cout << "李四地址: " << man2.getAddr() << endl; cout << "------------------------" << endl;}
int main(void) { Human zhangsan("张三", 18, "男"); Human lisi; cout << "=== 初始化状态 ===" << endl; zhangsan.description(); lisi.description(); // 调用拷贝赋值运算符 lisi = zhangsan; cout << "=== 执行 lisi = zhangsan 后 ===" << endl; showMsg(zhangsan, lisi); // 修改张三的地址 zhangsan.setAddr((char*)"新加坡"); cout << "=== 张三修改地址后 ===" << endl; showMsg(zhangsan, lisi); system("pause"); return 0;}📊 执行结果分析
=== 初始化状态 ===姓名: 张三, 年龄: 18, 性别: 男姓名: 无名, 年龄: 18, 性别: 未知------------------------=== 执行 lisi = zhangsan 后 ===张三地址: China李四地址: China=== 张三修改地址后 ===张三地址: 新加坡⚠️ 代码问题与优化
现有代码的问题
- 注释错误:将”拷贝赋值运算符”错误地称为”赋值构造函数”
- 浅拷贝风险:对于指针成员addr,当前实现是浅拷贝,可能导致多个对象共享同一内存
- 缺少内存释放:在赋值运算符中没有释放当前对象的旧内存
优化建议
Human& Human::operator=(const Human& other) { // 1. 自赋值检查 if (this == &other) { return *this; }
// 2. 释放当前对象的旧内存 delete[] this->addr; // 3. 重新分配内存 this->addr = new char[ADDR_LEN]; // 4. 拷贝数据 this->name = other.name; this->age = other.age; this->sex = other.sex; strcpy_s(this->addr, ADDR_LEN, other.addr); // 5. 返回*this return *this;}💡 实现要点总结
- 自赋值检查:避免自赋值时的错误操作
- 资源管理:先释放旧资源,再分配新资源
- 深拷贝:对于指针成员,必须进行深拷贝
- 返回引用:支持链式赋值(如
a = b = c) - 异常安全:确保在异常发生时,对象状态依然有效
🎯 核心概念回顾
| 概念 | 关键点 |
|---|---|
| 拷贝赋值运算符 | 用于已有对象间的赋值,语法:Class& operator=(const Class& other) |
| 浅拷贝 | 直接复制指针值,多个对象共享同一内存,不安全 |
| 深拷贝 | 重新分配内存并复制内容,每个对象拥有独立内存,安全 |
| 自赋值检查 | 避免自赋值时的资源释放错误 |
| 返回引用 | 支持链式赋值,提高性能 |
| 理解拷贝赋值运算符的工作原理,是编写安全、高效C++代码的重要基础。通过正确实现赋值运算符,可以避免内存泄漏、悬挂指针等常见问题,提高程序的可靠性和稳定性。 |