C++构造函数、拷贝构造函数、析构函数的含义与用法

1. 构造函数的基本定义

构造函数是一种特殊的成员函数,它在对象创建时自动调用,用于初始化对象的成员变量。其名称必须与类名相同,且没有返回类型(即使是 void 也不能)。

2.构造函数的类型

2.1默认构造函数

当我们在创建类时,如果没有构建构造函数,那么编译器会自动给我们构建一个默认的构造函数。

class MyClass {

    MyClass() {} // 类的构造函数

};

注意:当你使用类时 未定义构造函数 则编译器会自动实现

默认构造函数:Simple() {}(空实现)

拷贝构造函数:Simple(const Simple& other)

析构函数:~Simple() {}

2.2带参数的构造函数(自定义的构造函数)

class BankAccount {

private:

    double balance;  // 成员变量

    string password; // 成员变量

public:

    // 构造函数:类名作为函数名

    BankAccount(double initBalance, string pwd)

        : balance(initBalance), password(pwd) // 初始化列表

}

关键理解:构造函数是类的特殊成员函数

为什么类名能当函数名

这是C++的语法规定:构造函数必须与类同名

编译器通过这种特殊命名识别构造函数

当创建对象时,会自动调用这个"同名函数"

2.3构造函数的工作流程

当你这样创建对象时:

BankAccount myAccount(1000.0, "secret123");

1.内存分配:为对象分配内存空间

2.调用构造函数:

myAccount.BankAccount(1000.0, "secret123");

3.执行初始化列表:

balance(initBalance) → 将参数值赋给成员变量

password(pwd) → 同上

执行构造函数体 {}(本例中为空)

2.4 初始化列表详解

: balance(initBalance), password(pwd)

部分

含义

:

初始化列表开始标志

balance(initBalance)

将参数initBalance的值赋给成员变量balance

,

分隔多个初始化

password(pwd)

将参数pwd的值赋给成员变量password

2.5为什么函数体{}可以为空?

不是必须写内容

如果只需要初始化成员变量

不需要额外操作时,函数体可以为空

2.6 Demo

#include <iostream>
#include <string>
using namespace std;

class BankAccount {
private:
    double balance;  // 成员变量
    string password; // 成员变量

public:
    // 构造函数:类名作为函数名
    BankAccount(double initBalance, string pwd) 
        : balance(initBalance), password(pwd) // 初始化列表
    {
        // 函数体:可以添加额外逻辑
        cout << "账户创建成功!初始余额:" << balance << endl;
    }
    
    void showBalance(string inputPwd) {
        if(inputPwd == password) {
            cout << "当前余额:" << balance << endl;
        } else {
            cout << "密码错误!" << endl;
        }
    }
};

int main() {
    // 创建对象:实际上是调用构造函数
    BankAccount aliceAccount(500.0, "alice123");
    
    // 使用对象
    aliceAccount.showBalance("wrong"); // 输出:密码错误!
    aliceAccount.showBalance("alice123"); // 输出:当前余额:500
}

具体来说:1. 在类中,如果你没有定义任何构造函数,编译器会自动生成一个默认构造函数(无参构造函数)。2. 但是一旦你定义了任何构造函数(比如你定义了一个带两个参数的构造函数),编译器就不会再自动生成默认构造函数了

因此你可以在class中再添加一个默认构造函数,如下方实例

using namespace std;

class Student{

private:
    int couts;
    string password;

public:
    Student() : couts(0), password("") {}  // 添加默认构造函数
    Student(int mun,string word):couts(mun),password(word){
        cout<<"count create successful!! have"<< couts << "dllors"<<"passworld is"<< password <<"\n";
    }

   void showpassworld(string inputstring)
   {
    if(inputstring == password)
    {
        cout << "success" << endl;
    }
    else {
       cout << "fail" << endl;
    }

   }
};

int main (int argc ,char * argv[])
{
    Student account (500,"123456");
    Student account1;
    account.showpassworld("123456");
    account.showpassworld("1234");
    account1 = account;
    account1.showpassworld("123456");
    return 0;
}

概念    说明    必要性
构造函数ClassName(...){...} 必须:创建对象时自动调用
private私有成员,外部不可见强烈推荐:保护数据安全
public公共接口,外部可访问 必须:提供操作对象的方式
封装性 隐藏实现细节,暴露接口 面向对象核心特性

3.拷贝构造函数

3.1 拷贝构造函数的定义和识别

class BankAccount {
public:
    // 普通构造函数
    BankAccount(double initBalance, string pwd) 
        : balance(initBalance), password(pwd) {}
    
    // 拷贝构造函数
    BankAccount(const BankAccount& other)
        : balance(other.balance), password(other.password) {}
};

关键点:

签名固定ClassName(const ClassName&)

参数:必须是同类型的常量引用

调用时机:当使用同类型的现有对象初始化新对象时

3.2编译器根据初始化方式自动选择构造函数

创建方式构造函数类型示例
Type obj; 默认构造函数BankAccount acc;
Type obj(args);匹配参数的构造函数BankAccount acc(1000.0, "pwd");
Type obj = existingObj;拷贝构造函数BankAccount acc2 = acc1
Type obj(existingObj)拷贝构造函数BankAccount acc3(acc1);

3.3默认拷贝构造函数

如果你没有写拷贝构造函数,则系统会帮你生成

class SimpleAccount {
    double balance;
    string password;
public:
    SimpleAccount(double b, string p) : balance(b), password(p) {}
    // 没有声明拷贝构造函数
};

int main() {
    SimpleAccount acc1(1000.0, "pwd");
    SimpleAccount acc2 = acc1; // 使用编译器生成的默认拷贝构造
}

编译器会自动生成一个浅拷贝的拷贝构造函数

// 编译器生成的等价代码
SimpleAccount(const SimpleAccount& other)
    : balance(other.balance), password(other.password) {}

3.4浅拷贝与深拷贝

3.4.1浅拷贝

浅拷贝可由编译器主动生成,当我们使用时,可以不主动自定义直接在我们函数中使用;浅拷贝更方便我们使用,但是注意浅拷贝不能使用在 包含指针成员 、文件句柄/网络连接禁止拷贝、等使用!!!!

以下为错误实例!!

class DangerousAccount {
    int* securityCode; // 指针成员
public:
    DangerousAccount(int code) {
        securityCode = new int(code);
    }
    
    ~DangerousAccount() {
        delete securityCode; // 释放内存
    }
};

int main() {
    DangerousAccount acc1(12345);
    DangerousAccount acc2 = acc1; // 浅拷贝!
    
    // 问题1:两个对象共享同一内存
    // 问题2:析构时双重释放(崩溃!)
}

此问题我们可以使用自定义拷贝构建函数或者深拷贝解决。

3.4.2 深拷贝

class SafeAccount {
    int* securityCode;
public:
    // 普通构造
    SafeAccount(int code) : securityCode(new int(code)) {}
    
    // 拷贝构造(深拷贝)
    SafeAccount(const SafeAccount& other) 
        : securityCode(new int(*other.securityCode)) {}
    
    ~SafeAccount() {
        delete securityCode;
    }
};

使用深拷贝可解决浅拷贝会出现的问题。

3.4.3 何时需要自定义拷贝构造函数?

情况    示例    必要性
包含指针成员int* data必须(避免浅拷贝问题)
资源管理 文件句柄/网络连接必须
需要特殊日志记录拷贝操作可选
禁止拷贝单例类声明为delete

3.4.3.1 default:显式使用默认拷贝

BankAccount(const BankAccount&) = default; // 显式使用默认拷贝

3.4.3.1 delete 禁止拷贝

BankAccount(const BankAccount&) = delete; // 禁止拷贝

4.析构函数

构造析构函数的使用方法有很多很多 设计到的知识点也很多 这里只简单介绍其基本用法,其余内容需要在工作中,或者学习站内大佬代码和博客进行学习理解

4.1析构函数定义

析构函数(Destructor)是一种特殊的成员函数,在对象销毁时自动调用,用于:

1.释放对象占用的资源(内存、文件句柄、网络连接等)

2.执行清理操作

3.确保资源安全释放,避免泄漏

4.2基本特性

特性说明
命名规则~类名()
无参数不能带任何参数
无返回值不声明返回类型
自动调用对象销毁时自动执行
不可重载每个类只能有一个析构函数

4.3 默认析构函数

class MyClass {
public:
    // 构造函数
    MyClass() { /* 分配资源 */ }
    
    // 析构函数
    ~MyClass() { /* 释放资源 */ }
};

即使你不主动构建析构函数,编译器也会自动创建。

4.4 构造析构函数

4.4.1 动态内存管理(必须使用)

当类管理动态分配的内存时,必须使用析构函数释放内存

class DynamicArray {
    int* data;
    size_t size;
    
public:
    // 构造函数:分配内存
    DynamicArray(size_t sz) : size(sz), data(new int[sz]) {
        std::cout << "分配 " << sz << " 个整数\n";
    }
    
    // 析构函数:释放内存
    ~DynamicArray() {
        delete[] data;  // 关键:释放数组内存
        std::cout << "释放 " << size << " 个整数\n";
    }
    
    // 禁止拷贝(避免浅拷贝问题)
    DynamicArray(const DynamicArray&) = delete;
    DynamicArray& operator=(const DynamicArray&) = delete;
};

// 使用示例
void testArray() {
    DynamicArray arr(100); // 构造时分配内存
    
    // 使用数组...
    
}

在函数结束后

自动调用:在对象销毁时自动执行析构函数。

4.4.2  文件资源管理(先了解)

确保文件在对象销毁时自动关闭

#include <fstream>

class LogFile {
    std::ofstream file;
    
public:
    // 构造函数:打开文件
    LogFile(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            throw std::runtime_error("无法打开日志文件");
        }
    }
    
    // 析构函数:关闭文件
    ~LogFile() {
        if (file.is_open()) {
            file.close();
            std::cout << "日志文件已关闭\n";
        }
    }
    
    void write(const std::string& message) {
        file << message << std::endl;
    }
};

// 使用示例
void logTest() {
    LogFile logger("app.log"); // 打开文件
    
    logger.write("程序启动");
    logger.write("执行操作...");
    
} // 离开作用域时自动关闭文件

4.4.3. 数据库连接管理(先了解)

确保数据库连接在对象销毁时自动关闭

class DatabaseConnection {
    // 伪代码,实际实现取决于数据库库
    void* dbHandle;
    
public:
    DatabaseConnection(const std::string& connStr) {
        dbHandle = connectToDatabase(connStr);
        if (!dbHandle) throw "连接失败";
    }
    
    ~DatabaseConnection() {
        if (dbHandle) {
            disconnect(dbHandle);
            std::cout << "数据库连接已关闭\n";
        }
    }
    
    void executeQuery(const std::string& sql) {
        // 执行SQL查询
    }
};

4.4.4 锁管理(RAII模式)(先了解)

#include <mutex>

class LockGuard {
    std::mutex& mtx;
    
public:
    // 构造函数:获取锁
    explicit LockGuard(std::mutex& m) : mtx(m) {
        mtx.lock();
        std::cout << "锁已获取\n";
    }
    
    // 析构函数:释放锁
    ~LockGuard() {
        mtx.unlock();
        std::cout << "锁已释放\n";
    }
    
    // 禁止拷贝
    LockGuard(const LockGuard&) = delete;
    LockGuard& operator=(const LockGuard&) = delete;
};

// 使用示例
std::mutex resourceMutex;

void safeAccess() {
    LockGuard lock(resourceMutex); // 获取锁
    
    // 安全访问共享资源...
    
} // 离开作用域时自动释放锁

 4.5 析构函数的高级应用 (先了解)

4.5.1 虚析构函数(多态基类必备)

当类被设计为基类时,必须声明虚析构函数

class Base {
public:
    virtual ~Base() {  // 虚析构函数
        std::cout << "Base 析构\n";
    }
};

class Derived : public Base {
    int* extraData;
    
public:
    Derived() : extraData(new int[100]) {}
    
    ~Derived() override {  // 覆盖基类虚析构函数
        delete[] extraData;
        std::cout << "Derived 析构\n";
    }
};

// 使用示例
void polymorphismTest() {
    Base* obj = new Derived();
    
    // 使用对象...
    
    delete obj; // 正确调用Derived的析构函数
}

 输出

Derived 析构
Base 析构

4.5.2继承体系中的析构顺序

class Grandparent {
public:
    Grandparent() { std::cout << "Grandparent 构造\n"; }
    virtual ~Grandparent() { std::cout << "Grandparent 析构\n"; }
};

class Parent : public Grandparent {
public:
    Parent() { std::cout << "Parent 构造\n"; }
    ~Parent() override { std::cout << "Parent 析构\n"; }
};

class Child : public Parent {
    DynamicArray arr;
public:
    Child() : arr(10) { std::cout << "Child 构造\n"; }
    ~Child() override { std::cout << "Child 析构\n"; }
};

// 使用示例
void inheritanceTest() {
    Child child;
}

输出

Grandparent 构造
Parent 构造
分配 10 个整数
Child 构造
Child 析构
释放 10 个整数
Parent 析构
Grandparent 析构

4.6析构函数的处理流程

当对象被销毁时,编译器会按照以下顺序处理:

  1. 执行析构函数体:运行用户定义的析构代码

  2. 销毁成员变量:按声明逆序调用成员的析构函数

  3. 销毁基类部分:调用基类析构函数(如果有)

  4. 释放内存:对象占用的内存被回收

注意:每当你在构造函数中分配资源时,立即在析构函数中添加对应的释放代码。这种"分配-释放"对称性是编写安全C++代码的黄金法则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值