C++六大默认成员函数之二--构造函数与析构函数详解
目录
一、类的默认成员函数
在 C++ 中,即使一个类什么都不写,编译器也会自动帮我们生成一些函数,这些函数被称为默认成员函数。
所谓默认成员函数,就是:
用户没有显式实现,编译器自动生成的成员函数。
一个类在默认情况下,编译器会自动生成 6 个默认成员函数:
-
构造函数
-
析构函数
-
拷贝构造函数
-
赋值运算符重载
-
取地址重载
-
const 取地址重载
其中最核心的是:
-
构造函数
-
析构函数
-
拷贝构造
-
赋值重载
二、构造函数
1. 构造函数的概念
构造函数是一种特殊的成员函数。
很多初学者会误以为:
构造函数的作用是“创建对象”。
实际上并不是。
对象的空间在定义对象时就已经开好了,构造函数真正负责的是:
对对象进行初始化。
它相当于替代了 C 语言中的 Init 初始化函数。
例如:
STInit(&st);
DateInit(&d);
在 C++ 中可以通过构造函数自动完成初始化。
三、构造函数的特性
1. 函数名与类名相同
class Date
{
public:
Date()
{
}
};
构造函数名字必须与类名一致。
2. 没有返回值
构造函数不需要写返回值类型。
错误写法:
void Date()
{
}
正确写法:
Date()
{
}
这是 C++ 语法规定。
3. 对象创建时自动调用
class Date
{
public:
Date()
{
cout << "调用构造函数" << endl;
}
};
int main()
{
Date d;
return 0;
}
运行结果:
调用构造函数
对象创建后,构造函数会自动执行。
4. 构造函数可以重载
构造函数本质也是函数,因此支持函数重载。
class Date
{
public:
// 无参构造
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 带参构造
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
使用:
Date d1;
Date d2(2025, 1, 1);
四、this指针
在 C++ 中:
非静态成员函数内部都会隐藏一个 this 指针。
例如:
d1.Print();
编译器实际会处理成:
Print(&d1);
所以成员函数内部才能访问:
this->_year
本质上:
this
指向当前调用函数的对象。
例如:
class Date
{
public:
void Print()
{
cout << this->_year << endl;
}
private:
int _year;
};
这里的 this 指针本质就是当前对象地址。
空指针调用成员函数问题
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
这段代码不会报空指针解引用问题,会正常运行。
因为:
p->Print();
本质是:
Print(p);
函数地址存在代码段中,不依赖对象空间。
但是如果访问成员变量:
cout << p->_a;
程序就会崩溃。
因为空指针无法访问对象数据。
五、默认构造函数
1. 什么是默认构造函数?
只要:
不传参数就能调用的构造函数
都叫默认构造函数。
包括:
-
无参构造
-
全缺省构造
-
编译器自动生成的构造函数
2. 编译器自动生成默认构造
如果类中没有写构造函数:
class Date
{
private:
int _year;
int _month;
int _day;
};
编译器会自动生成默认构造函数。
3. 用户写了构造函数,编译器就不再生成
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
}
会报错:
没有默认构造函数
因为:
一旦用户自己写了构造函数,编译器就不会再自动生成默认构造。
六、默认构造函数的行为
很多同学会发现:
class Date
{
public:
void Print()
{
cout << _year << endl;
}
private:
int _year;
};
int main()
{
Date d;
d.Print();
}
输出的是随机值。
原因是:
编译器默认生成的构造函数,对内置类型不做处理。
1. 内置类型
例如:
-
int
-
char
-
double
-
指针
这些都属于内置类型。
默认构造不会初始化它们。
2. 自定义类型
如果成员变量是类对象:
class Time
{
public:
Time()
{
cout << "Time构造" << endl;
}
};
class Date
{
private:
Time _t;
};
创建 Date 对象时:
Date d;
会自动调用:
Time()
因为:
默认构造会自动调用自定义类型成员自己的构造函数。
七、C++11中的缺省值
C++11 后,可以直接给成员变量默认值:
class Date
{
private:
int _year = 2025;
int _month = 1;
int _day = 1;
};
这样即使不写构造函数,也能完成初始化。
八、无参构造与全缺省构造
无参构造
Date()
{
}
全缺省构造
Date(int year = 2025, int month = 1, int day = 1)
{
}
它们都属于默认构造函数。
默认构造函数只能有一个
下面代码会报错:
class Date
{
public:
Date()
{
}
Date(int year = 2025, int month = 1, int day = 1)
{
}
};
因为:
Date d;
调用时会产生歧义。
编译器不知道调用哪个构造函数。
九、空类大小问题
即使类中没有成员变量:
class C
{
};
它的大小也不是 0。
cout << sizeof(C) << endl;
结果:
1
原因是:
编译器需要给对象一个唯一地址进行标识。
所以空类至少占 1 个字节。
十、析构函数
1. 析构函数的概念
析构函数与构造函数相反。
它并不是负责销毁对象本身。
因为:
-
局部对象存在栈区
-
生命周期结束后系统自动回收空间
析构函数真正负责的是:
清理对象内部申请的资源。
例如:
-
malloc
-
new
-
fopen
等动态资源。
它类似于 C 语言中的:
Destroy()
函数。
十一、析构函数的特性
1. 函数名是 ~类名
~Date()
{
}
2. 无参数无返回值
~Date()
{
}
3. 一个类只能有一个析构函数
因为:
-
名字固定
-
参数固定
所以析构函数不能重载。
4. 生命周期结束自动调用
class Date
{
public:
~Date()
{
cout << "~Date()" << endl;
}
};
int main()
{
Date d;
return 0;
}
程序结束时:
~Date()
会自动执行。
十二、默认析构函数
如果用户不写析构函数:
class Date
{
};
编译器会自动生成默认析构函数。
默认析构函数会做什么?
对内置类型
什么都不做。
对自定义类型
自动调用它们自己的析构函数。
例如:
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
};
class Date
{
private:
Time _t;
};
Date 对象销毁时:
~Time()
也会自动调用。
十三、为什么需要析构函数?
1. 不需要写析构函数的情况
例如:
class Date
{
private:
int _year;
int _month;
int _day;
};
这里只是普通变量。
没有动态资源。
因此不需要写析构函数。
2. 必须写析构函数的情况
例如栈:
class Stack
{
public:
Stack(int n = 4)
{
_a = (int*)malloc(sizeof(int) * n);
}
~Stack()
{
free(_a);
_a = nullptr;
}
private:
int* _a;
};
这里使用了:
malloc
申请堆空间。
如果不释放:
free(_a);
就会造成内存泄漏。
十四、成员对象的构造与析构顺序
例如:
class MyQueue
{
private:
Stack pushst;
Stack popst;
};
构造顺序:
pushst
popst
析构顺序:
popst
pushst
原因是:
构造按成员声明顺序进行,析构顺序与构造相反。
这和栈的“后进先出”思想类似。
十五、对象析构顺序
对于局部对象:
后定义的对象先析构。
例如:
int main()
{
Date d1;
Date d2;
}
析构顺序:
d2
d1
因为栈本身就是后进先出的结构。
十六、RAII思想
在 C++ 中:
-
构造函数负责获取资源
-
析构函数负责释放资源
对象生命周期结束时自动释放资源。
这就是:
RAII思想
即:
Resource Acquisition Is Initialization
资源获取即初始化
它是 C++ 管理资源最核心的思想之一。
相比 C 语言:
Init();
Destroy();
需要手动调用。
C++ 自动调用构造和析构,大大降低了资源泄漏风险。
更多推荐

所有评论(0)