引言
在C++面向对象编程中,构造函数(Constructor)和析构函数(Destructor)是类的两个特殊成员函数,它们分别负责对象的初始化和清理工作,会被系统自动调用。我们可以在构造函数中给类分配资源,在类的析构函数中释放对应的资源。如果我们不提供构造函数与析构函数,编译器会自动提供两个函数的空实现。
构造函数和析构函数是一种特殊的公有成员函数,必须定义在public中。构造函数在类定义时由系统自动调用,析构函数在类被销毁时由系统自动调用。一个类可以有多个构造函数,只能有一个析构函数,不同的构造函数之间通过参数个数和参数类型来区分。
构造函数
构造函数是一个特殊的成员函数,用于在创建对象时初始化对象的数据成员。构造函数具有以下特点:
函数名与类名相同
没有返回类型(包括void)
在对象创建时自动调用
可以重载
可以有参数
无参(默认)构造函数(Default Constructor)
定义:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
作用:为对象提供默认的初始化方式。
示例:
class Person {
public:
Person() {
cout << "默认构造函数被调用" << endl;
}
};
Person p; // 自动调用默认构造函数注意:如果你没有为类定义任何构造函数,编译器会自动生成一个默认构造函数。
带参数构造函数(Parameterized Constructor)
定义:带有一个或多个参数的构造函数。
作用:允许在创建对象时,指定初始值。
示例:
class Person {
public:
string name;
int age;
Person(string n, int a) : name(n), age(a) {
cout << "带参数构造函数被调用" << endl;
}
};
Person p("talentq", 18); // 调用带参数构造函数拷贝构造函数(Copy Constructor)
定义:以本类对象的引用作为参数的构造函数。标准写法为:ClassName(const ClassName& other);
作用:用一个已有对象来初始化新对象,实现对象的“复制”。
示例:
class Person {
public:
string name;
int age;
Person(const Person& other) : name(other.name), age(other.age) {
cout << "拷贝构造函数被调用" << endl;
}
};
Person p1("李四", 30);
Person p2(p1); // 调用拷贝构造函数注意:如果没有自定义拷贝构造函数,编译器会自动生成一个“浅拷贝”版本。对于有动态资源管理的类,建议自定义“深拷贝”拷贝构造函数。
移动构造函数(Move Constructor)
定义:以右值引用(ClassName&&)作为参数的构造函数。
作用:实现资源的“移动”而不是“拷贝”,提升性能,避免不必要的资源分配和释放。
示例:
class Buffer {
public:
int* data;
size_t size;
Buffer(size_t n) : data(new int[n]), size(n) {}
// 移动构造函数
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // 资源转移
other.size = 0;
cout << "移动构造函数被调用" << endl;
}
};应用场景:适用于临时对象、返回局部对象等场合。
委托构造函数(Delegating Constructor)
定义:在一个构造函数中调用同类的另一个构造函数。
作用:减少代码重复,统一初始化逻辑。
示例:
class Person {
public:
string name;
int age;
Person() : Person("未知", 0) {
cout << "委托构造函数被调用" << endl;
}
Person(string n, int a) : name(n), age(a) {
cout << "主构造函数被调用" << endl;
}
};转换构造函数(Conversion Constructor)
定义:只有一个参数(且不是本类类型)或者有多个参数但除了第一个外都有默认值的构造函数。
作用:允许从其他类型隐式或显式地转换为本类对象。
示例:
class Fraction {
public:
int numerator, denominator;
// 转换构造函数
Fraction(int num) : numerator(num), denominator(1) {}
};
Fraction f = 5; // 隐式调用转换构造函数,将int转为Fraction注意:为避免隐式转换带来的意外,可以加explicit关键字:explicit Fraction(int num) : numerator(num), denominator(1) {} ,阻止隐式转换。
#include <iostream>
using namespace std;
class Fraction {
public:
int numerator;
int denominator;
explicit Fraction(int num) : numerator(num), denominator(1) {}
};
void printFraction(const Fraction& f) {
cout << f.numerator << "/" << f.denominator << endl;
}
int main() {
// Fraction f1 = 5; // 编译错误,不能隐式转换
Fraction f2(5); // 正确,显示构造
printFraction(f2); // 正确
// printFraction(10); // 编译错误,不能隐式转换
printFraction(Fraction(10)); // 正确,显示构造
return 0;
}析构函数
1 什么是析构函数?
析构函数(Destructor)是 C++ 类中的一种特殊成员函数,在对象生命周期结束时自动调用,用于执行清理工作,比如释放资源、关闭文件、断开网络连接等。
2 析构函数的语法与特点
析构函数的名称是在类名前加上波浪号(~),没有参数、没有返回值(连 void 都不能写)。
class ClassName {
public:
~ClassName();
};析构函数具有以下特点:
没有参数、返回值、返回类型,也不能重载(一个类只能有一个析构函数)
可以是虚函数(用于多态场景)
在对象销毁时自动调用,不需要手动调用(手动调用会导致未定义行为)
如果不定义,编译器会自动生成一个默认的析构函数(“浅析构”)
3 析构函数的调用时机
1 局部对象离开作用域时
void foo() {
MyClass obj; // foo 结束时 obj 自动析构
}2 动态分配的对象被delete时
MyClass* p = new MyClass;
delete p; // 调用析构函数3 程序结束时(全局对象和静态对象)
4 析构函数与多态(虚析构函数)
当用基类指针指向派生类对象时,基类析构函数强烈建议声明为 virtual,否则只会调用基类析构函数,派生类资源不会被释放,可能导致内存泄漏。
凡是要被继承的类,析构函数都强烈建议声明为 virtual。
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "Base构造" << endl; }
virtual ~Base() { cout << "Base析构" << endl; }
};
class Derived : public Base {
private:
int* p;
public:
Derived() : p(new int[10]) { cout << "Derived构造" << endl; }
~Derived() {
delete[] p;
cout << "Derived析构" << endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 会正确调用 Derived 和 Base 的析构函数
return 0;
}5 RAII 与析构函数
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中非常重要的资源管理思想。
核心思想是:资源的申请和释放分别放在构造函数和析构函数中。
class FileHandler {
private:
FILE* fp;
public:
FileHandler(const char* filename) {
fp = fopen(filename, "r");
}
~FileHandler() {
if (fp) fclose(fp);
}
};这样,FileHandler对象销毁时,文件会自动关闭,不会忘记释放资源。
C++11的“五法则”(Rule of Five)
在C++98/03中,三法则(Rule of Three)要求:如果你自定义了析构函数、拷贝构造函数、拷贝赋值运算符中的任何一个,通常都要自定义全部三个。
C++11 引入了移动语义,增加了移动构造函数和移动赋值运算符。因此,现代C++提出了五法则(Rule of Five)。
五法则(Rule of Five):如果类需要自定义析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符中的任何一个,通常都要自定义全部五个。这样可以确保资源的正确管理,避免内存泄漏、悬垂指针等问题。
现代C++(C++11及以后)推荐优先使用智能指针(如std::unique_ptr、std::shared_ptr)来自动管理资源,这样可以避免手写五法则。
五法则是C++11以后面向资源管理类的基本规范,避免资源泄漏和不安全的浅拷贝。
1 五个特殊成员函数
如果你的类管理资源(如动态内存、文件句柄、网络连接等),就要考虑如下五个函数:
析构函数(Destructor)
~ClassName();
负责释放资源。拷贝构造函数(Copy Constructor)
ClassName(const ClassName& other);
用于对象的“复制构造”。拷贝赋值运算符(Copy Assignment Operator)
ClassName& operator=(const ClassName& other);
用于对象的“复制赋值”。移动构造函数(Move Constructor, C++11)
ClassName(ClassName&& other) noexcept;
用于“窃取”资源,提高效率。移动赋值运算符(Move Assignment Operator, C++11)
ClassName& operator=(ClassName&& other) noexcept;
用于“窃取”资源并赋值。
2 为什么需要“五法则”?
如果你的类涉及资源管理,比如有原始指针成员变量,你自定义了其中一个函数,通常也要自定义其它四个,否则会出现浅拷贝、资源重复释放、悬垂指针等问题。
C++11引入移动语义,如果你没自定义,编译器可能生成默认的移动构造/赋值,可能不符合你的资源管理需求。
评论区