构造函数

当定义一个类后,它的对象在未来的操作中,总会不可避免的总会碰到如下的行为:创建、拷贝、赋值、移动、销毁。这些操作实际上是通过 6 种特殊的成员函数来控制的:构造函数析构函数拷贝构造函数拷贝赋值函数移动构造函数移动赋值函数。默认情况下,编译器会为新创建的类添加这些函数(默认不会添加移动构造和移动赋值),以便它的对象在未来能够执行这些操作。

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

C++ 支持多种类型的构造函数,用于满足不同的初始化需求:

  1. 默认构造函数: 不带参数的构造函数,通常用于初始化对象的默认状态;
  2. 带参数构造函数: 允许传入参数来初始化对象的状态;
  3. 拷贝构造函数: 用于从另一个已存在的对象创建新对象;
  4. 移动构造函数: 用于从即将销毁的临时对象转移资源到新对象。

1. 默认构造函数

默认构造函数 就是无参数的构造函数。既可以是自己定义的,也可以是编译系统自动生成的。

当没有为一个类定义任何构造函数的情况下,编译系统就会自动生成一个无参数、空函数体的默认构造函数。

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。语法格式如下:

类名::类名() {          // 注意:小括号中无任何参数
    // 函数体
}

代码如下:

#include <iostream>
using namespace std;


class Student {
public:
    // 默认构造函数声明
    Student();
};

// 默认构造函数定义
Student::Student() {
    cout << "创建对象时调用默认构造参数" << endl;
}


int main() {
    // 创建对象时触发默认构造函数
    Student student;

    return 0;
}

2. 带参数构造函数

带参数构造函数允许传入参数来初始化对象的状态。参数可以是任何有效的 C++ 数据类型,包括基本类型、自定义类对象和指针。

#include <iostream>
using namespace std;


class Student {
private:
    int _age;
    string _name;
public:
    // 默认构造函数声明
    Student();
    // 有参构造函数声明
    Student(string name, int age);
    int get_age();
    string get_name();
};

// 默认构造函数定义
Student::Student() {
    cout << "创建对象时调用默认构造参数" << endl;
}

// 有参构造函数定义
Student::Student(string name, int age) {
    _name = name;
    _age = age;
    cout << "调用有参构造函数" << endl;
}

// get 方法
int Student::get_age() {
    return _age;
}

string Student::get_name() {
    return _name;
}

int main() {
    // 创建对象时触发默认构造函数
    Student stu_1;

    // 创建对象时触发有参构造函数
    Student stu_2("AAA建材罗总", 31);
    cout << "姓名:" << stu_2.get_name() << ",年龄:" << stu_2.get_age() << endl;

    Student stu_3("不吃香菜", 18);
    cout << "姓名:" << stu_3.get_name() << ",年龄:" << stu_3.get_age() << endl;

    return 0;
}

因为类的构造函数也可以使用函数重载,所以我们既可以不传递参数创建对象,也可以传递参数创建对象。

还有一点需要注意的是,string 作为函数参数时,通常会使用常量引用 const string& 的形式传参。string 实际上是字符串的对象,而常量引用并不拥有自己的存储空间,而是指向其他已经存在的 string 对象。当你 const string& 作为函数参数或者在代码中使用时,不会发生字符串内容的拷贝,而是直接引用已经存在的 string 对象,因此可以提高性能,特别是在函数调用时避免不必要的拷贝。

初始化列表

构造函数的一项重要功能是对属性进行初始化。为了对成员变量进行初始化,构造函数主要通过两种方式实现:

  1. 在函数体内逐个赋值
  2. 使用初始化列表

在上述代码中,我们使用了第 1 种方法也就是函数体内逐个赋值:

// 有参构造函数定义
Student::Student(string name, int age) {
    _name = name;
    _age = age;
    cout << "调用有参构造函数" << endl;
}

如果使用初始化列表,就不需要在函数体中对属性进行赋值,而是在函数首部与函数体之间添加了一个冒号 :,后面紧跟 _age(age), _name(name) 语句,这个语句的意思相当于函数体内部的 _age = age; _name = name;,也是赋值的意思,代码如下:

Student::Student(int age, string name): _age(age), _name(name) {
    cout << "使用初始化列表" << endl;
}

使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简单明了。

练习题 - 银行账户管理系统

设计一个简单的银行账户管理系统,要求使用面向对象编程(OOP)实现以下功能:

  1. 记录账户余额;
  2. 支持存款和取款操作;
  3. 可以查询当前余额;
  4. 取款时检查余额是否充足。

为了满足以上需求,需要在类中定义以下属性和方法:

  1. 私有成员变量:account_number(账号)account_holder(账户持有人)balance(余额)
  2. 公有方法:
  3. deposit(int amount):存钱,修改 balance 余额;
  4. withdraw(int amount):取款(需检查余额是否足够),同样修改 balance 余额;
  5. display():显示账户信息(账号、持有人、余额)

在 main 函数中,创建至少两个 Bank_Account 对象,模拟存款、取款操作。每次操作后调用 display() 方法,输出账户最新状态。

要求: 1. 使用 构造函数 初始化账户信息。 2. 使用 封装(private/public) 保护数据安全。 3. 在 withdraw() 方法中检查余额,如果不足则提示错误。

代码如下:

#include <iostream>
using namespace std;

class Bank_Account {
private:
    string _account_number;
    string _account_holder;
    int _balance;

public:
    Bank_Account(string number, string holder, int balance);
    // 存款
    void deposit(int amount);
    // 取款
    void withdraw(int amount);
    // 展示余额
    void display();
};

Bank_Account::Bank_Account(std::string number, std::string holder, int balance): _account_holder(holder), _account_number(number), _balance(balance) {}

void Bank_Account::deposit(int amount) {
    if (amount > 0) {
        _balance += amount;
        cout << "存款成功,当前余额:" << _balance << "元" << endl;
    } else {
        cout << "存款金额必须大于 0" << endl;
    }
    cout << "===================" << endl;
}

void Bank_Account::withdraw(int amount) {
    if (_balance < amount || amount < 0) {
        cout << "余额不足,取款失败" << endl;
    } else {
        _balance -= amount;
        cout << "取款成功,当前余额:" << _balance << "元" << endl;
    }
    cout << "===================" << endl;
}

void Bank_Account::display() {
    cout << "账号:" << _account_number << endl;
    cout << "持有人:" << _account_holder << endl;
    cout << "余额:" << _balance << endl;
    cout << "===================" << endl;
}


int main() {
    // 创建两个银行账户
    Bank_Account acc_1("123456", "张三", 10000);
    Bank_Account acc_2("654321", "李四", 1200);

    // 模拟存取款操作
    acc_1.withdraw(8000);
    acc_1.deposit(15000);
    acc_1.display();

    acc_2.deposit(1200);
    acc_2.withdraw(3000);
    acc_2.display();

    return 0;
}

析构函数

创建对象时系统会自动调用 构造函数 进行初始化工作,销毁对象时系统也会自动调用 析构函数 来进行清理工作,例如释放分配的内存、关闭打开的文件等。

析构函数(Destructor)是一种特殊的成员函数,它的名字是在类名前面加一个 ~ 符号,没有返回值,没有参数,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。

还是拿 Student 类来举例子,类名为 Student,构造函数名就是 Student(),析构函数名是 ~Student(),代码如下:

#include <iostream>
using namespace std;

class Student {
public:
    Student() {
        cout << "调用构造函数" << endl;
    }

    ~Student() {
        cout << "调用析构函数" << endl;
    }
};


int main() {
    // 创建对象
    Student stu_1;
    return 0;
}

以上代码中,仅在 Student 类中定义了无参构造函数与析构函数,main 函数中创建了对象。但是程序执行完毕后,显示如下结果:

调用构造函数
调用析构函数

这是因为程序执行完毕之后,创建的对象就会被销毁掉,也就触发了析构函数。

析构函数没有参数,因此一个类只能有一个析构函数。