news 2026/7/29 7:50:31

C++类和对象:面向对象编程的核心(二)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++类和对象:面向对象编程的核心(二)

之前我们总结了C++类和对象的概念,也提到了构造函数与析构函数,本文我们来了解拷贝构造函数与赋值运算符重载:

一、拷贝构造函数:拷贝构造函数是一种特殊构造函数,使用同一个类的已有对象,去创建新对象。

特点:

1、函数名与类名相同;
2、唯一形参:同类的 const 引用(必须是引用!直接传值会无限递归调用);
3、没有返回值;
4、如果自己不写,编译器会自动提供默认拷贝构造函数。

(一)默认拷贝构造函数(浅拷贝)

逐成员拷贝(值拷贝 / 浅拷贝)对于普通成员变量直接复制;指针成员:只复制变量本身的值(地址),不会新开堆内存。

问题根源:两个对象指针指向同一块堆内存,析构重复释放。

(二)自写拷贝构造函数(深拷贝)

重新开辟堆内存,让新对象拥有独立资源,解决浅拷贝崩溃问题:

(三)什么时候调用拷贝构造函数

拷贝构造函数在以下几种情况下会被调用:

  1. 用一个对象初始化另一个对象时
    class MyClass { public: MyClass(const MyClass& other) { // 拷贝构造函数实现 } }; int main() { MyClass obj1; MyClass obj2 = obj1; // 调用拷贝构造函数 MyClass obj3(obj1); // 调用拷贝构造函数 return 0; }
  2. 函数参数传递对象时(按值传递)
    void processObject(MyClass obj) { // 函数体 } int main() { MyClass original; processObject(original); // 调用拷贝构造函数,创建形参obj return 0; }
  3. 函数返回对象时(按值返回)
    MyClass createObject() { MyClass obj; return obj; // 可能会调用拷贝构造函数(取决于编译器优化) } int main() { MyClass newObj = createObject(); // 调用拷贝构造函数 return 0; }
  4. 容器中插入对象时
    #include <vector> int main() { std::vector<MyClass> vec; MyClass obj; vec.push_back(obj); // 调用拷贝构造函数 return 0; }

注意:现代编译器会进行返回值优化(RVO)和命名返回值优化(NRVO),在某些情况下会避免不必要的拷贝构造函数调用,提高程序效率。

二、赋值运算符重载

赋值运算符重载是C++中允许我们自定义类对象之间赋值行为的运算符重载。与拷贝构造函数不同,赋值运算符用于已存在对象之间的赋值操作。

特点:

  1. 函数名固定为operator=
  2. 返回类型通常是类的引用(支持连续赋值)
  3. 参数通常是同类的 const 引用
  4. 需要处理自赋值情况
  5. 需要释放原有资源并分配新资源(深拷贝)

(一)默认赋值运算符(浅拷贝)

如果不自定义赋值运算符,编译器会生成默认的赋值运算符,执行逐成员浅拷贝

class ShallowCopyClass { private: int* data; int size; public: // 构造函数 ShallowCopyClass(int s) : size(s) { data = new int[size]; for (int i = 0; i < size; i++) { data[i] = i; } } // 析构函数 ~ShallowCopyClass() { delete[] data; } // 没有自定义赋值运算符,使用默认浅拷贝 }; int main() { ShallowCopyClass obj1(5); ShallowCopyClass obj2(3); obj2 = obj1; // 默认赋值运算符:浅拷贝! // obj1和obj2的data指针指向同一内存 return 0; // 程序结束时会双重释放,导致崩溃! }

问题:与默认拷贝构造函数类似,默认赋值运算符只复制指针值,导致两个对象指向同一块堆内存,析构时重复释放。

(二)自定义赋值运算符(深拷贝)

为了解决浅拷贝问题,我们需要自定义赋值运算符,实现深拷贝:

class DeepCopyClass { private: int* data; int size; public: // 构造函数 DeepCopyClass(int s) : size(s) { data = new int[size]; for (int i = 0; i < size; i++) { data[i] = i; } } // 拷贝构造函数(深拷贝) DeepCopyClass(const DeepCopyClass& other) : size(other.size) { data = new int[size]; for (int i = 0; i < size; i++) { data[i] = other.data[i]; } } // 赋值运算符重载(深拷贝) DeepCopyClass& operator=(const DeepCopyClass& other) { // 1. 检查自赋值 if (this == &other) { return *this; } // 2. 释放原有资源 delete[] data; // 3. 分配新资源并复制数据 size = other.size; data = new int[size]; for (int i = 0; i < size; i++) { data[i] = other.data[i]; } // 4. 返回当前对象的引用 return *this; } // 析构函数 ~DeepCopyClass() { delete[] data; } // 打印数据(用于测试) void print() const { for (int i = 0; i < size; i++) { std::cout << data[i] << " "; } std::cout << std::endl; } }; int main() { DeepCopyClass obj1(5); DeepCopyClass obj2(3); std::cout << "赋值前obj2: "; obj2.print(); // 输出: 0 1 2 obj2 = obj1; // 调用自定义赋值运算符(深拷贝) std::cout << "赋值后obj2: "; obj2.print(); // 输出: 0 1 2 3 4 // 修改obj1,不会影响obj2 // ...(假设有修改方法) return 0; // 正常析构,不会崩溃 }

(三)拷贝构造函数 vs 赋值运算符

对比项拷贝构造函数赋值运算符
调用时机创建新对象时已存在对象之间赋值时
函数签名ClassName(const ClassName&)ClassName& operator=(const ClassName&)
返回类型无(构造函数)类的引用(支持连续赋值)
资源管理分配新资源先释放旧资源,再分配新资源
自处理不需要(新对象)需要检查自赋值
示例MyClass obj2 = obj1;obj2 = obj1;

(四)现代C++的改进:拷贝并交换惯用法

使用"拷贝并交换"惯用法可以简化赋值运算符的实现,同时提供强异常安全性:

class SmartClass { private: int* data; int size; // 交换函数 friend void swap(SmartClass& first, SmartClass& second) noexcept { using std::swap; swap(first.data, second.data); swap(first.size, second.size); } public: // 构造函数 SmartClass(int s = 0) : size(s), data(size ? new int[size] : nullptr) { for (int i = 0; i < size; i++) { data[i] = i; } } // 拷贝构造函数 SmartClass(const SmartClass& other) : size(other.size), data(size ? new int[size] : nullptr) { for (int i = 0; i < size; i++) { data[i] = other.data[i]; } } // 赋值运算符(拷贝并交换) SmartClass& operator=(SmartClass other) { // 注意:按值传递! swap(*this, other); // 交换当前对象和临时对象 return *this; // other离开作用域时会释放旧资源 } // 移动构造函数(C++11) SmartClass(SmartClass&& other) noexcept : data(nullptr), size(0) { swap(*this, other); } // 移动赋值运算符(C++11) SmartClass& operator=(SmartClass&& other) noexcept { swap(*this, other); return *this; } // 析构函数 ~SmartClass() { delete[] data; } }; int main() { SmartClass obj1(5); SmartClass obj2; obj2 = obj1; // 调用拷贝并交换的赋值运算符 return 0; }

优点:

  1. 代码简洁,避免重复
  2. 自动处理自赋值(交换是安全的)
  3. 提供强异常安全性
  4. 同时支持拷贝和移动语义(C++11)

(五)什么时候需要自定义赋值运算符?

  • 类包含动态分配的内存(指针成员)
  • 类包含文件句柄、网络连接等资源
  • 需要实现引用计数写时复制
  • 需要特殊的赋值逻辑(如数据库记录复制)

总结:赋值运算符重载是C++资源管理的重要组成部分。对于管理资源的类,通常需要同时实现"拷贝构造函数、赋值运算符、析构函数"(Rule of Three),在C++11及以后还需要考虑"移动构造函数、移动赋值运算符"(Rule of Five)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 7:50:25

MATLAB IIR滤波器设计实战:从原理到硬件实现的全流程指南

1. 从“滤波器设计”到“系统实现”&#xff1a;IIR滤波器的MATLAB实战全景提到数字信号处理&#xff0c;滤波器是绕不开的核心工具。而在众多滤波器类型中&#xff0c;无限脉冲响应&#xff08;IIR&#xff09;滤波器以其在相同阶数下能实现更陡峭的过渡带和更窄的过渡带宽而著…

作者头像 李华
网站建设 2026/7/29 7:50:23

DNS劫持检测API参数详解与工程最佳实践

适用场景 DNS劫持是运营商、恶意软件或中间人通过篡改DNS解析结果将用户导向错误服务器的攻击手段。本API通过向阿里AliDNS、腾讯DNSPod、360安全DNS、Cloudflare四家公共DoH&#xff08;DNS over HTTPS&#xff09;服务商并发查询同一域名&#xff0c;对比IP与CNAME的一致性&…

作者头像 李华
网站建设 2026/7/29 7:47:40

LabVIEW频谱分析:实时信号处理与优化实践

1. 项目概述&#xff1a;LabVIEW频谱图生成的核心价值频谱分析是信号处理领域的基石技术&#xff0c;而LabVIEW作为图形化编程的标杆工具&#xff0c;其直观的数据流编程方式特别适合实时信号处理任务。我在工业测控领域使用LabVIEW开发频谱分析模块已有七年经验&#xff0c;这…

作者头像 李华
网站建设 2026/7/29 7:47:11

Graph Engineering 全面解析

过去两年&#xff0c;Agent 的单步能力明显变强了。 它可以读代码、查资料、调用工具、修改文件&#xff0c;也能在失败后重试。可当任务从十分钟延长到十小时&#xff0c;从一次模型调用变成几十个步骤&#xff0c;新的问题出现了&#xff1a; 规划已经完成&#xff0c;执行…

作者头像 李华
网站建设 2026/7/29 7:47:04

深入理解C++ std::list:从核心原理到模拟实现

1. 项目概述&#xff1a;为什么我们需要深入理解std::list&#xff1f;在C的STL&#xff08;标准模板库&#xff09;里&#xff0c;std::list是一个存在感很强&#xff0c;但又常常被初学者甚至一些有经验的开发者“用错”或“误解”的容器。很多人第一次接触它&#xff0c;是因…

作者头像 李华
网站建设 2026/7/29 7:31:23

ESP32-C3蓝牙自定义GATT服务开发实战:从架构到实现

1. 项目概述&#xff1a;从“能连上”到“能干活”的跨越玩过一阵子ESP32-C3蓝牙的朋友&#xff0c;估计都跑过官方的GATT Server例程&#xff0c;看着手机上的蓝牙调试助手能连上设备&#xff0c;能发现一堆服务&#xff08;Service&#xff09;和特征值&#xff08;Character…

作者头像 李华