当前位置: 首页 > news >正文

第二十五天:构造函数/析构函数/拷贝构造

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

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.xdnf.cn/news/1310041.html

相关文章:

  • 开发一款多商户电商APP要多久?功能拆解与源码技术落地方案
  • 迭代器模式及优化
  • 模式匹配自动机全面理论分析
  • 【Web后端】Django、flask及其场景——以构建系统原型为例
  • AI 搜索时代:引领变革,重塑您的 SEO 战略
  • 基于uni-app+vue3实现的微信小程序地图范围限制与单点标记功能实现指南
  • Matplotlib直线绘制:从基础到三维空间的高级可视化
  • 数组名本质与指针运算揭秘
  • List容器:特性与操作使用指南
  • 零基础学习人工智能的完整路线规划
  • 民法学学习笔记(个人向) Part.5
  • 学习游戏制作记录(制作系统与物品掉落系统)8.16
  • MySQL查询性能慢时索引失效的排查与优化实践
  • Redis缓存
  • 【OpenGL】LearnOpenGL学习笔记09 - 材质、光照贴图
  • 登录与登录校验:Web安全核心解析
  • 【昇腾】单张48G Atlas 300I Duo推理卡MindIE+WebUI方式跑7B大语言模型_20250816
  • 如何在FastAPI中玩转APScheduler,实现动态定时任务的魔法?
  • 【wmi异常】关于taskkill命令提示“错误:找不到” 以及无法正常获取设备机器码的处理办法
  • pytorch例子计算两张图相似度
  • PHP反序列化的CTF题目环境和做题复现第2集_POP链构造
  • 利用Qwen大模型进行c++11并发库的学习,与时俱进!!!!
  • AI安全增强核心技术:提示词防火墙、置信度过滤与知识蒸馏防御
  • 第6问 数据分析领域主要的岗位有哪些?
  • Rust 入门 KV存储HashMap (十七)
  • pdf合并代码
  • 【C++】异常详解(万字解读)
  • FPGA串口通信实现方案
  • Qt QDateTime时间部分显示为全0,QTime赋值后显示无效问题【已解决】
  • 【C++】C++11