一、对象的生命周期
在C++中,对象从创建到销毁经历两个关键阶段:
-
出生:构造函数负责创建对象并初始化成员变量
-
死亡:析构函数负责清理资源并进行善后工作
二、构造函数
1. 什么是构造函数?
构造函数是类中一种特殊的成员函数,在对象被创建时自动调用,用于初始化对象的成员变量。
cpp
class Student {
private:
string name;
int age;
public:
// 构造函数:函数名和类名相同,无返回类型
Student(string n, int a) {
name = n;
age = a;
}
};
int main() {
Student s1("张三", 18); // 自动调用构造函数
return 0;
}
2. 构造函数的特点
| 特点 | 说明 |
|---|---|
| 函数名 | 与类名相同 |
| 返回类型 | 无返回类型 (不是 void,是根本不写) |
| 调用时机 | 对象创建时自动调用,对象不能手动调用构造函数 |
| 调用次数 | 一个对象只能调用一次构造函数 |
| 重载 | 可以定义多个构造函数(参数列表不同) |
| 缺省构造函数 | 如果没写任何构造函数,编译器自动生成一个无参构造函数 |
3. 构造函数的三种写法
cpp
class Student {
private:
string name;
int age;
public:
// 1. 无参构造函数
Student() {
name = "未知";
age = 0;
}
// 2. 带参数的构造函数
Student(string n, int a) {
name = n;
age = a;
}
// 3. 初始化列表(推荐)
Student(string n, int a) : name(n), age(a) {
// 函数体可以为空
}
};
推荐使用初始化列表:效率更高,直接初始化成员变量,而不是先默认初始化再赋值。
4. 缺省构造函数
如果类中没有定义任何构造函数,编译器会自动生成一个缺省构造函数(无参数,函数体为空)。
cpp
class Point {
private:
int x, y;
// 编译器自动生成:Point() {}
};
int main() {
Point p; // 调用缺省构造函数,但 x 和 y 是随机值
}
注意:
-
一个类只有一个缺省构造函数
-
缺省构造函数是编译器自动生成的无参构造函数
-
自己写的无参构造函数不叫"缺省构造函数",叫"无参构造函数"
缺省函数 vs 缺省参数
这两个概念容易混淆:
| 术语 | 含义 | 示例 |
|---|---|---|
| 缺省函数 | 编译器自动生成的、无参数的函数 | 没写构造函数时,编译器生成 类名() {} |
| 缺省参数 | 函数参数有默认值 | void func(int a = 10); |
说明:
-
缺省函数 = 系统自动生成的、无参数的函数
-
缺省参数 = 函数参数有默认值
-
析构函数不能有参数,所以不存在"缺省参数"的说法
cpp
class Test {
public:
Test(int a = 10) {} // 这是缺省参数,不是缺省函数
};
5. 构造函数可以在类外定义
构造函数可以在类中声明,在类外定义。定义时需要用 类名:: 指明所属类。
cpp
class Student {
private:
string name;
int age;
public:
Student(string n, int a); // 声明
};
// 类外定义
Student::Student(string n, int a) : name(n), age(a) {}
说明 :将声明和定义分开,可以让类的接口更清晰,实现细节隐藏在 .cpp 文件中。
6. 构造函数可以是私有的吗?
可以。构造函数可以是 private、protected 或 public,但绝大多数情况设为 public,否则外部无法创建对象。
cpp
class Singleton {
private:
Singleton() {} // 私有构造函数,外部无法创建对象
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
};
三、存储空间与对象的关系
1. 有空间不一定有对象,有对象一定有空间
内存空间是客观存在的,但只有当我们在这块空间上构造对象时,它才成为一个"对象"。
2. 空类占多少字节?
cpp
class Empty { };
cout << sizeof(Empty) << endl; // 输出 1
为什么是1字节?
如果空类占0字节,两个对象在内存中就无法区分------它们的地址会相同。为了确保每个对象有唯一的地址,编译器给空类分配1字节作为占位符。这个字节只用来标识对象的存在,不存储任何数据。
四、构造函数完整示例
cpp
#include <iostream>
using namespace std;
class CDate {
private:
int year;
int month;
int day;
public:
// 无参构造函数
CDate() : year(1), month(1), day(1) {}
// 带参构造函数
CDate(int y, int m, int d) : year(y), month(m), day(d) {}
void PrintDate() const {
printf("%04d/%02d/%02d\n", year, month, day);
}
};
int main() {
CDate dt1; // 调用无参构造函数
CDate dt2(2025, 7, 24); // 调用带参构造函数
dt1.PrintDate(); // 输出 0001/01/01
dt2.PrintDate(); // 输出 2025/07/24
return 0;
}
说明 :dt1 使用无参构造函数,年月日被初始化为 (1,1,1);dt2 使用带参构造函数,用传入的值初始化。两种方式展示了构造函数的重载用法。
五、析构函数
1. 什么是析构函数?
析构函数是对象生命周期结束时自动调用 的成员函数,用于清理资源(如释放动态分配的内存、关闭文件等)。
cpp
class CGoods {
private:
char* name;
public:
// 构造函数:分配资源
CGoods(const char* n) {
name = new char[strlen(n) + 1];
strcpy(name, n);
}
// 析构函数:释放资源
~CGoods() {
delete[] name; // 释放动态分配的内存
}
};
2. 析构函数的特点
| 特点 | 说明 |
|---|---|
| 函数名 | ~类名,如 ~CGoods() |
| 返回类型 | 无返回类型(也不能是 void) |
| 参数 | 不带任何参数 |
| 个数 | 一个类有且仅有一个析构函数 |
| 调用时机 | 对象销毁时自动调用(作用域结束、delete等) |
| 缺省析构函数 | 如果没写,编译器自动生成一个空函数体的析构函数 |
3. 缺省析构函数
如果类中没有定义析构函数,编译器会自动生成一个缺省析构函数(函数体为空)。
cpp
class Point {
int x, y;
// 编译器自动生成:~Point() {}
};
注意:
-
缺省析构函数不释放动态分配的内存,如果类中有指针成员,必须自己写析构函数
-
析构函数不能有参数,所以不存在"缺省参数"的说法
4. 对象可以调用析构函数吗?
对象不能调用构造函数 ,但可以手动调用析构函数(一般不推荐)。
cpp
class Test {
public:
~Test() {
cout << "析构函数被调用" << endl;
}
};
int main() {
Test t;
t.~Test(); // ✅ 可以手动调用,但之后对象还在,再次销毁会出问题
return 0;
}
不建议手动调用:对象销毁时析构函数会被再次调用,导致重复释放等错误。
六、构造函数 vs 析构函数
| 对比项 | 构造函数 | 析构函数 |
|---|---|---|
| 函数名 | 类名 | ~类名 |
| 参数 | 可以有 | 无参数 |
| 返回类型 | 无 | 无 |
| 重载 | 可以(多个构造函数) | 不可以(只有一个) |
| 调用时机 | 对象创建时 | 对象销毁时 |
| 调用次数 | 每个对象一次 | 每个对象一次 |
| 手动调用 | ❌ 不能 | ✅ 可以(但不推荐) |
| 缺省版本 | 没写任何构造函数时生成 | 没写析构函数时生成 |
七、完整示例:Point 和 Line 类
下面的 Point 类演示了构造函数的两种写法:无参构造函数和带参构造函数。
cpp
class Point {
private:
float xpos;
float ypos;
public:
Point() : xpos(0.0), ypos(0.0) {}
Point(float x, float y) : xpos(x), ypos(y) {}
void setX(float x) { xpos = x; }
void setY(float y) { ypos = y; }
float getX() const { return xpos; }
float getY() const { return ypos; }
void Printpos() const {
printf("x:%.2f, y:%.2f\n", xpos, ypos);
}
};
Line 类演示了两种构造函数:无参构造函数和四点坐标初始化。
cpp
#include <cmath>
class Line {
private:
Point a;
Point b;
public:
// 无参构造函数:两个端点都是 (0,0)
Line() : a(0.0, 0.0), b(0.0, 0.0) {}
// 带参构造函数:用四个坐标初始化
Line(float a_x, float a_y, float b_x, float b_y)
: a(a_x, a_y), b(b_x, b_y) {}
const Point& getPointa() const { return a; }
const Point& getPointb() const { return b; }
float get_length() const {
float dx = a.getX() - b.getX();
float dy = a.getY() - b.getY();
return sqrt(dx * dx + dy * dy);
}
void PrintLine() const {
printf("端点A: ");
a.Printpos();
printf("端点B: ");
b.Printpos();
printf("长度: %.2f\n", get_length());
}
};
main 函数展示了两种构造方式的调用。
cpp
int main() {
Line l1; // 无参构造函数
printf("=== line1 ===\n");
l1.PrintLine();
Line l2(1.2, 2.3, 3.4, 4.5); // 四点初始化
printf("\n=== line2 ===\n");
l2.PrintLine();
return 0;
}
示例说明:
-
l1:无参构造函数,两个端点都初始化为 (0,0) -
l2:四点坐标初始化,用传入的四个坐标值初始化两个端点
八、总结
| 知识点 | 核心要点 |
|---|---|
| 构造函数作用 | 创建对象 + 初始化成员变量 |
| 构造函数特点 | 函数名=类名,无返回类型,可重载,自动调用,不能手动调用 |
| 构造函数定义位置 | 可在类内定义,也可在类外定义(类名::类名() {}) |
| 缺省构造函数 | 没写任何构造函数时编译器自动生成(无参,函数体为空) |
| 缺省函数 vs 缺省参数 | 缺省函数是编译器自动生成的无参函数;缺省参数是函数参数有默认值 |
| 私有构造函数 | 用于单例模式等场景,禁止外部直接创建对象 |
| 初始化列表 | 推荐写法,效率更高 |
| 空类大小 | 占1字节,确保每个对象有唯一地址 |
| 析构函数作用 | 清理资源(释放动态内存、关闭文件等) |
| 析构函数特点 | 函数名=~类名,无参数,无返回类型,不可重载,可手动调用 |
| 缺省析构函数 | 没写析构函数时编译器自动生成(空函数体) |