1.前言
本章重点
本篇文章着重讲解类中的两个默认函数,分别为:
构造函数,析构函数
这是c++六个默认成员函数中的两个
(其他四个在后面章节讲解)
我们平时在写基础的数据机构时,例如栈和队列
如果自己没有注意没有进行初始化,就有可能导致出错,同理,在写完代码后,忘记销毁开辟好的空间,这样容易导致内存泄漏,长期内存泄漏,就会造成不可能避免的损失。对于一些小白来说用起来是非常困难的。
这些都是c语言里面残留的一些问题,c++为了解决这两类问题,就提出了构造函数和析构函数的概念。
2.构造函数
构造函数:顾名思义就是当你自己不初始化时,这个函数会帮你进行初始化。
特性:
- 数名与类名相同
- 无返回值
- 对象实例化时自动调用对应的构造函数
- 构造函数可以重载
需要注意的点是:
1.构造函数并不是普通的成员函数,他是一个特殊的成员函数
2.他的任务只是对已经实例化出来的对象进行初始化,不是开辟空间实例化出对象。
举例说明:
class Date
{
public:
Date(int year, int month, int day)//构造函数
{
_year = year;
_month = month;
_day = day;
}
Date()//无参的构造函数
{
_year = 1900;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 调用无参构造函数
Date d2(2023, 7, 24);//调用含参的构造
//这里要注意:如果调用的是无参的构造函数,后面不能加(),否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
Date d3();
}
3.构造函数的特性
如果使用者没有写构造函数,那么编译器会自动生成一个。
例:
class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date(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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
Date d1;
return 0;
}
对上述代码的解释:
屏蔽掉自己写的构造函数时编译器会自动生成一个,d1在实例化时就会去调用编译器生成的
然而当放开自己写的构造函数后会报错,因为自己实现的构造函数没有缺省值,并且d1实例化时没有传参。
4. 对默认构造函数的理解
可能童鞋们会疑惑:既然编译器会自己生成构造函数,那我是不是写不写构造函数都可以了?
带着此疑问引出一个新概念:内置类型和自定义类型
内置类型是C++语言提供的类型。比如: int/char类型
自定义类型是用户使用class/struct/union类定义出来的类型,如:Date类(日期类)
这个新概念有什么用?
编译器自动生成的构造函数,不会处理内置类型,它们是随机值
然而自动生成的构造会处理自定义类型,它会去调用自定义类型的默认构造
举例说明一下:
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;
return 0;
}
对上述代码进行解释:
发现编译器生成默认的构造函数会对自定类型成员 _t 调用的它的默认成员 函数。此时_year,_month,_day任然是随机值,而_t中的会调用它的默认构造函数进行初始化,所以_t中的成员都进行了初始化
5.对默认构造函数的补充
1.注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在 类中声明时可以给默认值 。 举例说明: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 = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
对上述代码进行解释:
2.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且 默认构造函数只能有一个 。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数。当你实例化一个Date对象时,你没有写默认构造函数,他会自己生成一个默认构造函数,按照之前的逻辑,Date中的int类型都是随机值,但是在c++11中内置类型成员变量在 ,类中声明时可以给默认值。因此此时int类型都不是随机值,而被默认初始化为给的默认值。
举例说明:
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;
};
// 以下测试函数能通过编译吗?
int main()
{
Date d1();
return 0;
}
对上述代码进行解释
这样是无法通过编译的,因为上面两种写法都是默认构造函数!但是它们不能同时存在
具体来说就是当实例化对象时没有传参,系统不知道是调用全缺省函数还是无参的函数
6. 析构函数
现在我们知道一个对象是怎么被初始化的
那么一个对象又是怎么被销毁的呢?
概念:
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作 。
特性:
1. 析构函数名是在类名前加上字符 ~ 。 2. 无参数无返回值类型。 3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载(重载概念后面再谈) 4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。
PS:析构函数和构造函数一样是特殊的函数,不能将它与普通函数相比
7.对析构函数的理解
有了前面构造函数的铺垫
析构函数就容易理解了,和我们想的一样
编译器自动生成的默认析构函数
只处理自定义类型,而内置类型不会管
那你可能会产生一个问题:
既然默认析构函数不会处理内置类型
那么内置类型是不是不会销毁?
答案是: 不!
内置类型会在对象生命周期结束时,将它在栈区的空间还给操作系统
所以析构函数不处理在栈区的变量,也没有问题但是有些变量的指针指向堆区,有由动态开辟出来的空间,这份空间不会主动还给操作系统
需要我们手动写析构函数来释放!
举个例子:
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;
}
~Stack()//析构函数
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
void TestStack()
{
Stack s;
}
对上述代码进行解释:这段代码中,存在在堆区申请的空间,所以不能使用编译器默认生成的析构而是要用自己写的析构函数去free掉,
这块堆区的空间
7.1 对默认析构函数的理解
先上代码:
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
对上述代码的解释:
// 程序运行结束后输出: ~Time() // 在 main 方法中根本没有直接创建 Time 类的对象,为什么最后会调用 Time 类的析构函数? // 因为: main 方法中创建了 Date 对象 d ,而 d 中包含 4 个成员变量,其中 _year, _month, _day 三个是 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而 _t 是 Time类对 象,所以在 d 销毁时,要将其内部包含的 Time 类的 _t 对象销毁,所以要调用 Time 类的析构函数。但是: main 函数 中不能直接调用Time 类的析构函数,实际要释放的是 Date 类对象,所以编译器会调用 Date 类的析构函 数,而 Date 没有显式提供,则编译器会给 Date 类生成一个默认的析构函数,目的是在其内部 调用Time 类的析构函数,即当Date 对象销毁时,要保证其内部每个自定义对象都可以正确销毁 // main 函数中并没有直接调用 Time 类析构函数,而是显式调用编译器为 Date 类生成的默认析 构函数
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
8.总结与拓展
总结:构造函数是析构函数是对立的,一个用于初始化,一个用于销毁对象调用
掌握它们对后面类和对象的学习很重要。
拓展:
类的六个默认函数:
现在已经学了前两个了,后续在学其他几个。
标签:函数,--,c++,int,默认,析构,Date,和析构,构造函数 From: https://blog.csdn.net/weixin_62196764/article/details/139235012下期预告:拷贝构造和运算符重载