当定义一个类后,它的对象在未来的操作中,总会不可避免的总会碰到如下的行为:创建、拷贝、赋值、移动、销毁。这些操作实际上是通过 6 种特殊的成员函数来控制的:构造函数、析构函数、拷贝构造函数、拷贝赋值函数、移动构造函数、移动赋值函数。默认情况下,编译器会为新创建的类添加这些函数(默认不会添加移动构造和移动赋值),以便它的对象在未来能够执行这些操作。
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
C++ 支持多种类型的构造函数,用于满足不同的初始化需求:
- 默认构造函数: 不带参数的构造函数,通常用于初始化对象的默认状态;
- 带参数构造函数: 允许传入参数来初始化对象的状态;
- 拷贝构造函数: 用于从另一个已存在的对象创建新对象;
- 移动构造函数: 用于从即将销毁的临时对象转移资源到新对象。
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 种方法也就是函数体内逐个赋值:
// 有参构造函数定义
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)实现以下功能:
- 记录账户余额;
- 支持存款和取款操作;
- 可以查询当前余额;
- 取款时检查余额是否充足。
为了满足以上需求,需要在类中定义以下属性和方法:
- 私有成员变量:
account_number(账号)、account_holder(账户持有人)、balance(余额)。 - 公有方法:
deposit(int amount):存钱,修改 balance 余额;withdraw(int amount):取款(需检查余额是否足够),同样修改 balance 余额;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 函数中创建了对象。但是程序执行完毕后,显示如下结果:
调用构造函数
调用析构函数
这是因为程序执行完毕之后,创建的对象就会被销毁掉,也就触发了析构函数。
析构函数没有参数,因此一个类只能有一个析构函数。