目录
前言
1. 类的默认成员函数
2. 构造函数
2.1 什么是构造函数
2.2 构造函数的特性
3. 析构函数
3.1 什么是析构函数
3.2 析构函数的特性
前言
前边我们已经了解了类和对像的基本概念,今天我们将继续深入了解类。类有6个默认成员函数,即使类中什么都不写(空类),编译器会自动生成6个默认成员函数。那么本期主要介绍的是构造函数和析构函数。
1. 类的默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
即使是类中什么都不写,编译器也会自动生成以下6个默认成员函数:
既然会默认生成那还有什么必要介绍呢?
虽然编译器会默认生成,但也并不是适用于任何场景(一些自定义类型不适用),这时就需要我们自己实现,所以深入了解类内部默认成员函数非常有必要的。
2. 构造函数
2.1 什么是构造函数
我们先看下面这个例子:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2023, 11, 10);
d1.Print();
Date d2;
d2.Init(2022, 11, 10);
d2.Print();
return 0;
}
这种写法其实很不便捷,比如:
- 每次创建对象需要手动调用
- 可能出现忘记调用的情况
为了使用便捷于是C++引入了新的玩法:
构造函数(用于初始化)
2.2 构造函数的特性
注意:
构造函数的主要任务并不是开空间创建对象,而是初始化对象。
构造函数有以下特性:
- 函数名与类名相同
- 无返回值
- 创建对象时编译器自动调用对应的构造函数
- 在对象生命周期中只调用一次
- 构造函数可以重载
class Data
{
public:
Data()
{
_year = 6;
_month = 6;
_day = 6;
}
Data(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
//上述两构造函数构成重载
void Print()
{
cout << _year << _month << _day << endl;
}
private:
int _year ;
int _month ;
int _day ;
};
int main()
{
Data d1;// 调用无参构造函数
//通过无参构造函数创建对象时,对象后面不用跟括号,会被识别成函数调用
Data d2(2023, 10, 20);// 调用带参的构造函数
return 0;
}
- 用户显式定义了构造函数,编译器将不再生成
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;//报错,没有合适的默认构造函数可用
//创建对象时没有参数,应该调用无参构造,并且上述构造没有缺省值
//编译器默认生成的构造初始化为随机值
return 0;
}
我们再来看看自定义类型:
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)、
//类里的成员变量只是声明
int _year;
int _month;
int _day;
// 自定义类型
Time _t;
};
int main()
{
Date d;//创建Date对象d,没有写构造函数也会生成默认构造
return 0;
}
默认生成的构造函数初始化分为两种:
- 内置类型
- 自定义类型
内置类型:编译器默认的构造函数会初始化为随机值(也可能是0,不同编译器之间有差别)
自定义类型:会调用它的默认构造
比如:
_t会调用Time类里边的默认构造
C++中将内置类型初始化为随机值是一个缺陷
C++11 针对这个缺陷,打了补丁:
- 内置类型成员变量在类中声明时可以给默认值
默认构造函数有三种:
- 无参的构造函数
- 全缺省的构造函数
- 我们没写编译器默认生成的构造函数
注意:默认构造函数只能有一个
比如:
class Date
{
public:
Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1;//报错,无法通过编译
//d1是无参对象,调用构造函数时编译器分不清要调用哪个
}
3. 析构函数
3.1 什么是析构函数
前边我们以经知道一个对象是怎么来的
那一个对象又是怎么没的呢?
析构函数:
完成对象中资源的清理工作
它的功能与构造函数相反
对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
注意:
- 析构函数不是完成对对象本身的销毁
- 局部对象销毁工作是由编译器完成的
3.2 析构函数的特性
析构函数有以下特性:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。
- 若未显式定义,系统会自动生成默认的析构函数。
- 析构函数不能重载
- 对象生命周期结束时,自动调用析构函数(C++)
析构函数的调用和构造函数很类似
对于类型处理也分为两种:
- 内置类型
- 自定义类型
对于内置类型,自己写析构函数并没有意义
内置类型出了作用域系统就自动回收了。
在自定义类型才有意义
比如:栈
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
void TestStack()
{
Stack s;
s.Push(1);
s.Push(2);
}
在栈使用结束后不需要手动调用
出了作用域自动销毁
它的行为和构造函数很相似:
- 默认生成析构函数,行为跟构造类似
- 内置类型成员不做处理
- 自定义类型成员会去调用他的析构
比如:
class MyQueue
{
private:
//MyQueue生成默认构造要清理_pushst和_popst两个成员变量时
//就会调用Stack类里的析构函数
Stack _pushst;
Stack _popst;
int _size = 1;
};
int main()
{
//main函数结束清理对象
//MyQueue类会自动生成默认析构函数行为如下:
//内置类型不做处理
//自定义类型调用它的析构函数
MyQueue mq;
return 0;
}
总结:
如果类中没有申请资源时(动态内存管理),析构函数可以不写,直接使用编译器生成的默认析构函数,有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
好了以上便是本期的全部内容,构造函数和析构函数的理解非常重要,关键也就总结为一句话:创建哪个类的对象就调用哪个类的构造函数,销毁哪个类的对象就调用哪个类的析构函数。希望对你有所帮助,感谢阅读!