构造函数/析构函数/拷贝构造

1. 构造函数(Constructor)

  • 定义与作用:构造函数是一种特殊的成员函数,其名称与类名相同,没有返回类型(包括 void 也没有)。它的主要作用是在创建对象时初始化对象的数据成员。构造函数可以有参数,通过参数传递来为对象的成员变量赋初值。
#include <iostream>
#include <string>class Student {
public:// 带参数的构造函数Student(const std::string& name, int age) : m_name(name), m_age(age) {std::cout << "Constructor called for " << m_name << std::endl;}private:std::string m_name;int m_age;
};int main() {Student student("Alice", 20);return 0;
}
  • 在上述代码中,Student 类有一个带参数的构造函数 Student(const std::string& name, int age)。在 main 函数中创建 Student 对象 student 时,调用了这个构造函数,并传入了姓名 “Alice” 和年龄 20,构造函数将这些值赋给对象的成员变量 m_namem_age,同时输出一条消息表示构造函数被调用。
  • 默认构造函数:如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数,它不接受参数,并且会对类的成员变量进行默认初始化(例如,对于内置类型,如 int,不会初始化;对于类类型,会调用其默认构造函数)。但一旦定义了其他构造函数(带参数的构造函数),编译器就不会再自动生成默认构造函数,除非显式定义。例如:
class MyClass {
public:MyClass(int value) : m_value(value) {}private:int m_value;
};// MyClass obj; // 错误,没有默认构造函数
MyClass obj(10); // 正确,调用带参数的构造函数
  • 初始化列表:在构造函数中,使用初始化列表(如 : m_name(name), m_age(age))来初始化成员变量是一种推荐的方式。它比在构造函数体内赋值更高效,特别是对于类类型的成员变量,因为初始化列表直接调用成员变量的构造函数,而在构造函数体内赋值会先调用成员变量的默认构造函数,然后再进行赋值操作。

2. 析构函数(Destructor)

  • 定义与作用:析构函数也是一种特殊的成员函数,其名称为类名前加波浪号(~),同样没有返回类型。它的作用是在对象生命周期结束时释放对象所占用的资源,例如动态分配的内存、打开的文件句柄等。析构函数在以下几种情况下会被调用:对象离开其作用域时、对象被 delete 时(如果是通过 new 动态分配的对象)。
#include <iostream>
#include <string>class Resource {
public:Resource() {m_data = new int[10];std::cout << "Resource constructed" << std::endl;}~Resource() {delete[] m_data;std::cout << "Resource destructed" << std::endl;}private:int* m_data;
};int main() {{Resource res;}std::cout << "After res goes out of scope" << std::endl;return 0;
}
  • 在上述代码中,Resource 类在构造函数中动态分配了一个包含 10 个 int 的数组 m_data。在析构函数中,通过 delete[] 释放了这块内存。当 res 对象离开其作用域(main 函数中的内层花括号结束)时,析构函数被调用,输出 “Resource destructed”,并且动态分配的内存被正确释放。
  • 注意事项:如果类中包含指针成员变量,并且在构造函数中动态分配了内存,一定要在析构函数中释放这些内存,以避免内存泄漏。此外,基类的析构函数通常应该声明为虚函数,这样当通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,确保资源正确释放。

3. 拷贝构造函数(Copy Constructor)

  • 定义与作用:拷贝构造函数是一种特殊的构造函数,用于创建一个新对象,该对象是另一个已有对象的副本。它的参数是本类对象的引用(通常是 const 引用)。拷贝构造函数在以下几种情况下会被调用:当用一个对象初始化另一个对象时、函数按值传递对象时、函数返回对象时。
#include <iostream>
#include <string>class Person {
public:Person(const std::string& name) : m_name(name) {std::cout << "Constructor called for " << m_name << std::endl;}// 拷贝构造函数Person(const Person& other) : m_name(other.m_name) {std::cout << "Copy constructor called for " << m_name << std::endl;}private:std::string m_name;
};Person createPerson() {Person temp("Bob");return temp;
}int main() {Person person1("Alice");Person person2 = person1; // 调用拷贝构造函数Person person3(createPerson()); // 调用拷贝构造函数return 0;
}
  • 在上述代码中,Person 类有一个拷贝构造函数 Person(const Person& other)。在 main 函数中,Person person2 = person1; 这行代码用 person1 初始化 person2,调用了拷贝构造函数。Person person3(createPerson()); 这行代码中,createPerson 函数返回一个 Person 对象,在初始化 person3 时也调用了拷贝构造函数。
  • 浅拷贝与深拷贝:默认情况下,如果没有定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数,它执行的是浅拷贝,即简单地按位复制对象的成员变量。对于包含指针成员变量的类,浅拷贝会导致多个对象共享同一块内存,当其中一个对象析构释放内存时,其他对象的指针就会变成野指针,从而引发未定义行为。为了避免这种情况,需要定义深拷贝的拷贝构造函数,即重新分配内存并复制指针所指向的内容。例如:
class MyClass {
public:MyClass(int size) : m_size(size) {m_data = new int[m_size];}// 深拷贝的拷贝构造函数MyClass(const MyClass& other) : m_size(other.m_size) {m_data = new int[m_size];for (int i = 0; i < m_size; ++i) {m_data[i] = other.m_data[i];}}~MyClass() {delete[] m_data;}private:int* m_data;int m_size;
};

MyClass 类的拷贝构造函数执行深拷贝,为新对象分配了独立的内存,并复制了原对象 m_data 数组中的内容,确保每个对象都有自己独立的内存空间。

构造函数、析构函数和拷贝构造函数是 C++ 中用于对象生命周期管理和对象复制的重要机制,它们之间存在紧密的关系:

4. 构造函数与析构函数的关系

  • 生命周期的起止:构造函数和析构函数分别标志着对象生命周期的开始和结束。构造函数负责在对象创建时初始化其成员变量和分配必要的资源,而析构函数则在对象销毁时释放这些资源,确保资源的正确管理,防止内存泄漏等问题。例如,在一个管理动态分配内存的类中:
class Resource {
public:Resource() {data = new int[10];}~Resource() {delete[] data;}
private:int* data;
};

在这个 Resource 类中,构造函数分配内存,析构函数释放内存,二者相互配合完成对象生命周期内资源的合理使用。

  • 调用顺序:在继承体系中,构造函数和析构函数的调用顺序是固定的。当创建一个派生类对象时,首先调用基类的构造函数,然后调用派生类的构造函数;而在销毁对象时,顺序相反,先调用派生类的析构函数,再调用基类的析构函数。这确保了对象的初始化和清理工作按照正确的层次结构进行。例如:
class Base {
public:Base() { std::cout << "Base constructor" << std::endl; }~Base() { std::cout << "Base destructor" << std::endl; }
};class Derived : public Base {
public:Derived() { std::cout << "Derived constructor" << std::endl; }~Derived() { std::cout << "Derived destructor" << std::endl; }
};int main() {Derived d;return 0;
}

输出结果为:

Base constructor
Derived constructor
Derived destructor
Base destructor

5. 构造函数与拷贝构造函数的关系

  • 初始化方式:拷贝构造函数是一种特殊的构造函数,用于基于已存在的对象创建一个新对象,是对象初始化的一种方式。普通构造函数用于创建全新的对象并初始化其成员,而拷贝构造函数则是根据已有的对象进行初始化。例如:
class Point {
public:Point(int x, int y) : m_x(x), m_y(y) {}Point(const Point& other) : m_x(other.m_x), m_y(other.m_y) {}
private:int m_x;int m_y;
};Point p1(1, 2);
Point p2(p1); // 使用拷贝构造函数初始化 p2
  • 隐式调用与默认行为:如果没有显式定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数。这个默认的拷贝构造函数执行浅拷贝,即按位复制对象的成员变量。在很多情况下,特别是当对象包含指针成员变量时,浅拷贝可能会导致问题,因为多个对象会共享同一块内存。此时,就需要显式定义拷贝构造函数来执行深拷贝,以确保每个对象都有自己独立的资源。例如:
class String {
public:String(const char* str) {m_length = strlen(str);m_data = new char[m_length + 1];strcpy(m_data, str);}// 如果不定义拷贝构造函数,默认的浅拷贝会导致问题String(const String& other) {m_length = other.m_length;m_data = new char[m_length + 1];strcpy(m_data, other.m_data);}~String() {delete[] m_data;}
private:char* m_data;size_t m_length;
};

6. 拷贝构造函数与析构函数的关系

  • 资源复制与释放:拷贝构造函数创建的新对象拥有与原对象相似的资源状态,析构函数则负责释放这些资源。如果拷贝构造函数执行浅拷贝,多个对象共享资源,那么在析构时可能会出现多次释放同一资源的错误。而深拷贝确保每个对象都有自己独立的资源,每个对象析构时释放自己的资源,不会相互干扰。例如,在前面的 String 类中,如果使用默认的浅拷贝,当一个对象析构释放 m_data 后,其他共享该资源的对象的 m_data 就会变成野指针,再次析构时会导致程序崩溃。

  • 异常安全:拷贝构造函数和析构函数都需要考虑异常安全。如果拷贝构造函数在复制资源时抛出异常,对象可能处于部分构造的状态,析构函数需要能够正确处理这种情况,确保已分配的资源被正确释放,避免内存泄漏。例如,在实现拷贝构造函数时,如果在分配新内存时抛出异常,析构函数应该能够清理已经分配的部分资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93444.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93444.shtml
英文地址,请注明出处:http://en.pswp.cn/web/93444.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【P14 3-6 】OpenCV Python——视频加载、摄像头调用、视频基本信息获取(宽、高、帧率、总帧数),视频保存在指定位置

文章目录1 读取本地视频1.1 绝对路径 6种方式1.2 相对路径 4种方式1.3 读取本地视频2 视频基本信息3 调用摄像头 并将视频保存在指定位置P14 3-6 1 读取本地视频 现在要读取本地视频“video.mp4”&#xff0c; 视频文件“video.mp4”和playVideo.py脚本文件&#xff0c;都在…

【DL学习笔记】常用数据集总结

一、如何找数据集 paperswithcode&#xff0c;但好像没了 AutoDL Roboflow Kaggle Hungging Face 百度飞浆PP AIStudio 二、目标检测数据集格式 常用数据集坐标格式 MSCOCO &#xff1a; 坐标格式&#xff08;x&#xff0c;y&#xff0c;w&#xff0c;h&#xff…

19.3 Transformers量化模型极速加载指南:4倍推理加速+75%显存节省实战

Transformers量化模型极速加载指南:4倍推理加速+75%显存节省实战 实战项目:模型量化 Transformers 兼容性配置 量化模型加载核心配置逻辑 #mermaid-svg-rDjfMigtxckLYWp3 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#merm…

Android 终端接入 GB28181 国标视频平台的完整解决方案解析

1. 引言&#xff1a;让 Android 终端无缝融入国标视频网络在公安、交通、应急、工业、教育等领域&#xff0c;GB/T 28181 国标协议早已成为视频监控与指挥调度的事实标准。传统国标视频网络通常由固定部署的 IPC 摄像机、NVR、视频管理平台构成&#xff0c;设备形态单一。随着一…

Docker目录的迁移

# 迁移 docker 目录 &#xff08;无论容器与镜像占用空间大小&#xff0c;哪怕只占用1G&#xff0c;也需用此方式&#xff0c;否则可能迁移不成功&#xff09;service docker stopcd /var/lib/docker# 一个一个复制除 overlay2 外的其他所有文件夹cp -R builder /home/docker/l…

IOS APP 前端存储

UserDefaults优点简单易用提供简单的键值对存储接口无需复杂配置&#xff0c;开箱即用适合存储少量简单数据轻量级专门为存储小量数据设计内存占用小性能开销低自动持久化数据自动保存到磁盘应用重启后数据仍然可用通过synchronize()方法可以强制立即写入&#xff08;iOS 12已自…

在前端js中使用jsPDF或react-to-pdf生成pdf文件时,不使用默认下载,而是存储到服务器

开源地址&#xff1a; https://github.com/ivmarcos/react-to-pdf 主要就是这个方法&#xff0c;有三种可选&#xff1a; 默认是save&#xff0c;也就是会自动触发下载的方法&#xff0c;open方法是默认会打开一个pdf预览的tab页面&#xff0c;build方法就是在调用的函数gener…

会议征稿!IOP出版|第二届人工智能、光电子学与光学技术国际研讨会(AIOT2025)

往届已EI检索&#xff0c;欢迎投稿&#xff01; AIOT2024会后两个月实现见刊&#xff01; AIOT2025已通过IOP-JPCS出版申请&#xff0c;独立JPCS出版 AIOT2025已上线西安文理学院官网&#xff1a; 征文通知&#xff5c;第二届人工智能、光电子学与光学技术国际…

CPP多线程2:多线程竞争与死锁问题

在多线程编程中&#xff0c;多个线程协同工作能显著提升程序效率&#xff0c;但当它们需要共享和操作同一资源时&#xff0c;潜在的问题也随之而来&#xff1b;线程间的执行顺序不确定性可能导致资源竞争&#xff0c;可能引发死锁&#xff0c;让程序陷入停滞。 多线程竞争问题示…

全国产飞腾d2000+复旦微690t信号处理模块

UD VPX-404是基于高速模拟/数字采集回放、FPGA信号实时处理、CPU主控、高速SSD实时存储架构开发的一款高度集成的信号处理组合模块&#xff0c;采用6U VPX架构&#xff0c;模块装上外壳即为独立整机&#xff0c;方便用户二次开发。 UD VPX-404模块的国产率可达到100%&#xff0…

物联网 (IoT) 的顶级硬件平台

物联网 &#xff08;IoT&#xff09; 的顶级硬件平台IoT&#xff08;物联网&#xff09;不再是一个流行词。随着每天出现几个鼓舞人心的用例&#xff0c;多家公司现在正在探索如何利用该技术实现业务增长。无论实施何种其他技术&#xff0c;基于物联网的新设备正迅速成为一项重…

TCP传输层协议(4)

TCP应用层协议&#xff08;4&#xff09; 流量控制 接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应. 因此 TCP 支持根据接收端的处理能力, 来决定发送端的发送速…

双向SSL认证之Apache实战配置

防御未授权访问&#xff0c;为企业级应用构筑双重身份验证防线 本文是关于Apache配置双向SSL认证的深度技术指南&#xff0c;包含全流程操作、调试技巧及企业级解决方案&#xff0c;适用于运维工程师和安全管理员。 1.为什么需要双向认证 &#xff1f; 核心价值 &#x1f51…

JavaScript 实用工具方法小全

1. 精确获取小数位数/*** 获取数字的小数位数&#xff08;支持科学计数法&#xff09;* param {number|string} num - 要检查的数字&#xff0c;可以是数字或字符串形式* returns {number} 返回小数部分的位数* * 实现原理&#xff1a;* 1. 处理科学计数法&#xff08;如1.23e-…

【易错题】C语言

今日遇到的易错题 #include <stdio.h> int i;//全局变量默认初始化是0 int main() {i--;//-1if (i > sizeof(i)){printf(">\n");}else{printf("<\n");}return 0; }易错点&#xff1a;sizeof的返回值类型实际为无符号整形&#xff0c;因此编…

第七十五章:AI的“思维操控师”:Prompt变动对潜在空间(Latent Space)的影响可视化——看懂AI的“微言大义”!

Prompt变动对潜在空间影响前言&#xff1a;AI的“思维操控师”——Prompt变动对潜在空间的影响可视化&#xff01;第一章&#xff1a;痛点直击——Prompt“难伺候”&#xff1f;改一个字就“面目全非”&#xff01;第二章&#xff1a;AI的“思维圣地”&#xff1a;潜在空间&…

【计算机视觉与深度学习实战】03基于Canny、Sobel和Laplacian算子的边缘检测系统设计与实现

第一章 引言 边缘检测作为计算机视觉和图像处理领域的核心技术之一,在现代数字图像分析中占据着举足轻重的地位。边缘是图像中亮度变化剧烈的区域,通常对应着物体的轮廓、表面方向的不连续性、材质变化或照明条件的改变。准确而高效的边缘检测不仅是图像分割、特征提取、模式…

【大语言模型 02】多头注意力深度剖析:为什么需要多个头

多头注意力深度剖析&#xff1a;为什么需要多个头 - 解密Transformer的核心升级 关键词&#xff1a;多头注意力、Multi-Head Attention、注意力头、并行计算、特征学习、Transformer架构、深度学习 摘要&#xff1a;在掌握了Self-Attention基础后&#xff0c;本文深入探讨多头注…

Python Condition对象wait方法使用与修复

在 Python 中&#xff0c;Condition 对象用于线程同步&#xff0c;其 wait() 方法用于释放锁并阻塞线程&#xff0c;直到被其他线程唤醒。使用不当可能导致死锁、虚假唤醒或逻辑错误。以下是常见问题及修复方案&#xff1a;常见问题与修复方案1. 未检查条件&#xff08;虚假唤醒…

嵌入式硬件——ARM

一、ARM体系结构程序编译的过程&#xff1a;预处理&#xff08;.c-.i&#xff09;&#xff1a;宏替换&#xff0c;头文件展开&#xff0c;去掉注释&#xff0c;特殊符号的处理编译&#xff08;.i-.s&#xff09;&#xff1a;C语言转换成汇编语言汇编&#xff08;.s-.o&#xff…