13x2类和对象-2-对象特性

admin 2025年2月21日22:39:34评论6 views字数 9567阅读31分53秒阅读模式

关注泷羽Sec-静安 公众号,后台回复 找书+ C++Primer 获取C++相关电子书

13x2类和对象-2-对象特性
image-20250211221401317

对象的初始化和清理

在C++中,对象的初始化和清理是确保资源安全管理和程序健壮性的核心机制。以下是详细解释和示例:

一、为什么需要初始化?

  1. 避免未定义行为

    classStudent {public:int score;  // 未初始化时值为随机数voidshow()cout << score; }};intmain(){    Student s;   // score是随机值    s.show();    // 输出不可预测的值}
  2. 资源正确获取

    classDatabaseConn {public:    DatabaseConn() {         connect(); // 构造函数中建立数据库连接    }voidconnect()/* 实际连接操作 */ }};// 使用对象时自动完成初始化DatabaseConn db; // 创建即连接

二、为什么需要清理?

  1. 防止内存泄漏

    classMemoryPool {private:int* data;public:    MemoryPool(int size) { data = newint[size]; }    ~MemoryPool() { delete[] data; } // 必须清理};voidfunc(){MemoryPool pool(100)// 离开作用域时自动调用析构函数释放内存}
  2. 确保资源释放

    classFileHandler {    FILE* file;public:    FileHandler(constchar* name) {         file = fopen(name, "r");     }    ~FileHandler() { if(file) fclose(file); // 保证文件关闭    }};// 即使后续代码抛出异常,文件也会关闭

三、初始化和清理的黄金组合:RAII

资源获取即初始化(Resource Acquisition Is Initialization):

classMutexLock {    mutex mtx;public:    MutexLock() { mtx.lock(); }   // 获取资源=初始化    ~MutexLock() { mtx.unlock(); } // 释放资源=清理};voidsafeOperation(){    MutexLock lock; // 构造时加锁// 临界区操作...// 离开作用域自动解锁

四、特殊场景说明

  1. 异常安全性

    classTransaction {    Database& db;public:    Transaction(Database& d) : db(d) {         db.begin();     }    ~Transaction() { if(!std::uncaught_exceptions()) { // 无异常时提交            db.commit();         } else {                          // 有异常时回滚            db.rollback();        }    }};
  2. 深拷贝问题

    classBadArray {int* arr;public:    BadArray(int size) { arr = newint[size]; }// 错误:没有拷贝构造函数和operator=    ~BadArray() { delete[] arr; }};voidproblem(){BadArray a1(10);    BadArray a2 = a1; // 浅拷贝,两个对象指向同一内存// 析构时同一内存被释放两次(崩溃!)

五、最佳实践建议

  1. 遵循3/5/0法则

    • 需要自定义析构函数时,通常也需要自定义拷贝/移动操作
  2. 使用智能指针

    classSafeResource {unique_ptr<Resource> res; // 自动管理资源public:    SafeResource() : res(new Resource) {}// 无需显式定义析构函数};
  3. 初始化列表优先

    classProperInit {constint id;string name;public:    ProperInit(int i, string n)         : id(i),        // const成员必须初始化列表          name(n) {}    // 比构造函数内赋值更高效};

总结必要性

场景
未正确初始化的后果
未正确清理的后果
内存管理
野指针/数据污染
内存泄漏
文件操作
读取到垃圾数据
文件句柄泄漏
多线程
竞争条件/数据竞争
死锁
网络连接
连接失败不处理
连接泄漏/端口耗尽
图形资源
渲染异常
GPU内存泄漏

通过构造函数和析构函数实现的RAII机制,是C++区别于其他语言的核心特性之一,它能有效保证:

  • 资源安全性:100%避免资源泄漏
  • 异常安全性:即使程序出错也能正确释放资源
  • 代码简洁性:自动管理替代手动new/delete

构造函数和析构函数

在C++中,构造函数(Constructor) 和 析构函数(Destructor) 是类的特殊成员函数,分别负责对象的初始化和清理工作。它们是面向对象编程中资源管理的核心机制。

一、构造函数(Constructor)

1. 核心作用

  • 对象初始化:在对象创建时自动调用,如果指定了就不再提供。创建函数默认就有默认构造函数,默认拷贝函数。
  • 分配资源:为对象的成员变量赋予初始值
  • 强制初始化:确保对象使用时处于有效状态

2. 关键特性

  • 函数名与类名完全相同
  • 没有返回类型(连 void 也没有)
  • 可以有多个重载版本(参数不同)
  • 可以包含初始化列表(更高效的初始化方式),自动调用且调用一次

3. 代码示例

#include<iostream>usingnamespacestd;classPerseon{public: Perseon() {cout << "Perseon()调用" << endl; };private:};intmain(){ Perseon p;return0;}---Perseon()调用

二、析构函数(Destructor)

1. 核心作用

  • 对象清理:在对象销毁时自动调用
  • 释放资源:回收动态分配的内存、关闭文件等
  • 防止泄漏:确保资源不会永久滞留

2. 关键特性

  • 函数名为 **~类名**(如 ~SmartArray
  • 没有参数和返回类型
  • 每个类只能有一个析构函数,默认函数用完会销毁
  • 通常声明为 virtual(基类必备)

3. 代码示例

classSmartArray {// ... (其他成员同前)public:// 析构函数    ~SmartArray() {delete[] data;      // 释放动态内存cout << "析构完成,释放" << size << "个int" << endl;    }};voidtest(){SmartArray temp(5);    // 构造//... 使用temp对象// 离开作用域时自动调用析构函数
13x2类和对象-2-对象特性
image-20250221103602221
13x2类和对象-2-对象特性
image-20250221103803207

但是函数没走完是不会释放的。

带参构造函数和拷贝构造函数

#include<iostream>usingnamespacestd;classPerson{public:int PubId;//默认构造函数 Person() {cout << "Person()调用" << endl; };//带参数的构造函数 Person(int id, string name, string phone, string address) {cout << "Person(int id, string name, string phone, string address)调用" << endl;  m_id = id; m_name = name; m_phone = phone; m_address = address;  PubId = id;cout << "id:" << id << " name:" << name << " phone:" << phone << " address:" << address << endl; };//拷贝构造函数 Person(const Person& p) {cout << "Person(const Person& p)调用" << endl;  PubId = p.PubId;cout << "id:" << p.m_id << " name:" << p.m_name << " phone:" << p.m_phone << " address:" << p.m_address << endl; };private:int m_id;string m_name;string m_phone;string m_address;};intmain(){ Person p1; //用默认构造时候不要加()Person p2(1"张三""123456""北京");  // 括号法表达//相当于Person p2=Person(1, "张三", "123456", "北京") 显式法cout << "p2.PubId:" << p2.PubId << endl;Person p3(p2)// 也可以写为 Person p3=p2 隐式法cout << "p3.PubId:" << p3.PubId << endl;return0;}___Person()调用Person(int id, string name, string phone, string address)调用id:1 name:张三 phone:123456 address:北京p2.PubId:1Person(const Person& p)调用id:1 name:张三 phone:123456 address:北京p3.PubId:1

单单一个Person(10)是匿名对象,当前行执行结束后自动回收。

三、调用时机对比

场景
构造函数调用
析构函数调用
局部对象
进入作用域时
离开作用域时
动态对象(new)
new

 操作时
delete

 操作时
全局对象
程序启动时(main之前)
程序终止时(main之后)
容器中的对象
元素被添加时(如vector.push_back
容器销毁或被清空时
#include<iostream>usingnamespacestd;//拷贝构造函数的调用时机//1.使用一个对象去初始化另一个对象//2.值传递的方式给函数参数传值//3.值方式返回局部对象classPerson{public: Person() {cout << "Person的默认构造函数调用" << endl; } Person(int age) {  m_Age = age;cout << "Person的有参构造函数调用" << endl; } Person(const Person& p) {  m_Age = p.m_Age;cout << "Person的拷贝构造函数调用" << endl;cout << "age" << m_Age << endl; } ~Person() {cout << "Person的析构函数调用" << endl; }int m_Age =10 ;};voidtest01()//1.使用一个对象去初始一个对象{Person p1(20);Person p2(p1);};voiddowork(Person p){cout << "dowork p age " << p.m_Age << endl;};voidtest02()//2.值传递的方式给函数参数传值{ Person p; dowork(p);};Person dowork2()//Person 类型的函数名{ Person p1;cout << (int*)&p1 << endl;return p1;};voidtest03()//3.值方式返回局部对象{ Person p = dowork2();cout << (int*)&p << endl;};intmain(){cout << "1.使用一个对象去初始一个对象" << endl; test01();cout << "----------------------" << endl;cout << "2.值传递的方式给函数参数传值" << endl; test02();cout << "----------------------" << endl;cout << "3.值方式返回局部对象" << endl; test03();cout << "----------------------" << endl;return0;};---1.使用一个对象去初始一个对象Person的有参构造函数调用Person的拷贝构造函数调用age20Person的析构函数调用Person的析构函数调用----------------------2.值传递的方式给函数参数传值Person的默认构造函数调用Person的拷贝构造函数调用age10dowork p age 10Person的析构函数调用Person的析构函数调用----------------------3.值方式返回局部对象Person的默认构造函数调用000000000014FC44000000000014FC44Person的析构函数调用----------------------

四、典型应用场景

1. 文件自动管理

classAutoFile {    FILE* file;public:    AutoFile(constchar* name) {         file = fopen(name, "r"); if(!file) throw runtime_error("文件打开失败");    }    ~AutoFile() { if(file) fclose(file);     }};// 使用示例:{AutoFile config("settings.cfg");  // 构造时打开文件// 使用文件...// 离开作用域自动关闭文件(即使发生异常也会调用析构)

2. 线程锁自动释放

classMutexGuard {    mutex& mtx;public:    MutexGuard(mutex& m) : mtx(m) { mtx.lock(); }  // 构造即加锁    ~MutexGuard() { mtx.unlock(); }                // 析构即解锁};// 使用示例:mutex criticalMutex;voidsafeOperation(){MutexGuard lock(criticalMutex);  // 进入作用域加锁// 临界区操作...// 离开作用域自动解锁

五、必须注意的陷阱

1. 异常安全

classProblemClass {int* res1;    FILE* res2;public:    ProblemClass() {        res1 = newint[100];     // 分配资源1        res2 = fopen("data");    // 分配资源2(可能失败)// 若此处抛出异常,res1内存泄漏!    }    ~ProblemClass() {delete[] res1;if(res2) fclose(res2);    }};

改进方案:使用成员类管理独立资源(RAII):

classSafeIntArray {/* 智能管理int数组 */ };classSafeFile {/* 智能管理文件 */ };classSafeClass {    SafeIntArray res1;  // 成员对象自动管理资源    SafeFile res2;public:    SafeClass() : res1(100), res2("data") {} // 异常安全};

2. 虚析构函数问题

classBase {public:    ~Base() { cout << "Base析构"; }  // 非虚析构函数};classDerived :public Base {int* data;public:    Derived(int size) { data = newint[size]; }    ~Derived() { delete[] data; cout << "Derived析构"    }};// 错误用法:Base* obj = new Derived(100);delete obj;  // 只调用Base的析构函数 → 内存泄漏!

正确做法

classBase {public:virtual ~Base() { ... }  // 虚析构函数};

六、深拷贝和浅拷贝

1. 核心概念对比

特性
浅拷贝 (Shallow Copy)
深拷贝 (Deep Copy)
复制内容
只复制指针地址
复制指针地址指向的实际数据
内存关系
新旧对象共享同一块内存
新旧对象拥有独立内存
修改影响
修改一个对象会影响另一个
对象间完全独立互不影响
资源释放
双重释放风险(程序崩溃)
安全释放
性能消耗
速度快(仅复制指针)
速度慢(需分配内存+数据复制)

2. 浅拷贝问题演示*

代码示例

#include<iostream>#include<cstring>usingnamespacestd;classMyArray {private:int* data;int size;public:// 构造函数    MyArray(int s) : size(s) {        data = newint[size];for (int i = 0; i < size; ++i) {            data[i] = i;        }    }// 浅拷贝构造函数    MyArray(const MyArray& other) : data(other.data), size(other.size) {// 浅拷贝:仅复制指针和大小    }// 深拷贝构造函数MyArray deepCopy()const{MyArray copy(size);for (int i = 0; i < size; ++i) {            copy.data[i] = data[i];        }return copy;    }// 析构函数    ~MyArray() {delete[] data;    }// 获取数据intgetData()const{return data;    }// 获取大小intgetSize()const{return size;    }// 打印数组voidprint()const{for (int i = 0; i < size; ++i) {cout << data[i] << " ";        }cout << endl;    }};voiddemonstrateShallowCopy(){MyArray arr1(5);    MyArray arr2 = arr1; // 浅拷贝cout << "Original array (arr1): ";    arr1.print();cout << "Shallow copy (arr2): ";    arr2.print();// 修改浅拷贝后的数组    arr2.getData()[0] = 100;cout << "After modification:" << endl;cout << "Original array (arr1): ";    arr1.print();cout << "Shallow copy (arr2): ";    arr2.print();}voiddemonstrateDeepCopy(){MyArray arr1(5);    MyArray arr2 = arr1.deepCopy(); // 深拷贝cout << "Original array (arr1): ";    arr1.print();cout << "Deep copy (arr2): ";    arr2.print();// 修改深拷贝后的数组    arr2.getData()[0] = 100;cout << "After modification:" << endl;cout << "Original array (arr1): ";    arr1.print();cout << "Deep copy (arr2): ";    arr2.print();}intmain(){cout << "Demonstrating Shallow Copy:" << endl;    demonstrateShallowCopy();cout << endl;cout << "Demonstrating Deep Copy:" << endl;    demonstrateDeepCopy();cout << endl;return0;}

问题分析

  • 内存共享arr1arr2data指针指向同一内存地址
  • 数据污染:修改任意对象的data会影响另一个对象
  • 崩溃风险:双重delete[]导致内存错误
13x2类和对象-2-对象特性
image-20250221153355045

3. 深拷贝解决方案

13x2类和对象-2-对象特性
关键实现点
  1. 拷贝构造函数new分配新内存 + 数据逐项复制

    // 拷贝构造函数(深拷贝)    MyArray(const MyArray& other) : size(other.size) {        data = newint[size];for (int i = 0; i < size; ++i) {            data[i] = other.data[i];        }    }
  2. 赋值运算符

    • 检查自赋值 (if(this != &other))
    • 先释放旧内存 (delete[] data)
    • 重新分配内存并复制数据
  3. 析构函数:安全释放对象自身持有的内存

4. 开发建议

  1. 三法则 (Rule of Three)如果类需要以下任一成员,通常需要全部显式定义:

    • 析构函数
    • 拷贝构造函数
    • 拷贝赋值运算符
  2. 使用智能指针

    #include<memory>classSafeArray {unique_ptr<int[]> data;  // 自动管理内存int size;public:    SafeArray(int sz) : size(sz), data(newint[sz]) {}// 不需要显式定义拷贝/析构函数!};
  3. 禁用拷贝对于不可复制的资源(如网络连接),明确禁止拷贝:

    classNonCopyable {    NonCopyable(const NonCopyable&) = delete;    NonCopyable& operator=(const NonCopyable&) = delete;};

5. 实际应用场景

场景
推荐拷贝方式
原因
小型结构数据
浅拷贝
性能优先,无资源管理需求
文件句柄
禁止拷贝
操作系统限制重复操作
数据库连接
深拷贝
需要独立连接会话
图像缓冲区
深拷贝
保证各对象数据独立性

七、总结:构造函数 vs 析构函数

构造函数
析构函数
调用时机
对象创建时
对象销毁时
主要任务
初始化成员/分配资源
清理成员/释放资源
重载
支持多个重载版本
不可重载
虚函数
不能是虚函数
基类必须声明为虚函数
默认生成
若无定义,编译器生成默认版本
若无定义,编译器生成默认版本

设计原则

  1. 每个类必须至少有一个构造函数(可由编译器隐式生成)
  2. 若类管理资源(内存/文件/网络连接等),必须显式定义析构函数
  3. 遵循RAII原则:资源获取即初始化,资源释放即析构

🔔 想要获取更多网络安全与编程技术干货?

关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻

扫描下方二维码,马上加入我们,共同成长!🌟

👉 长按或扫描二维码关注公众号

或者直接回复文章中的关键词,获取更多技术资料与书单推荐!📚

原文始发于微信公众号(泷羽Sec-静安):13x2类和对象-2-对象特性

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月21日22:39:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   13x2类和对象-2-对象特性https://cn-sec.com/archives/3767360.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息