目录
一、构造函数
1.1 构造函数的引入
1.2 构造函数的定义和语法
1.2.1 无参构造函数:
1.2.2 带参构造函数
1.3 构造函数的特性
1.4 默认构造函数
二、析构函数
2.1 析构函数的概念
2.2 特性
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
class Date
{
};
一、构造函数
1.1 构造函数的引入
对于Date类,有如下程序:
#include<iostream>
using namespace std;
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(2025, 3, 5)
return 0;
}
对于Date类,可以通过Init公有方法给对象设置日期。但有时也会出现忘记Init初始化函数,而直接Push。为避免忘记,也为了方便程序撰写,所以,C++规定了构造函数。
1.2 构造函数的定义和语法
构造函数是一种特殊的成员函数。虽然叫构造,但是其任务不是开空间,而是初始化对象。其特征如下:
- 构造函数名与类名相同;
- 无返回值,不需要写void,void是空返回值;
- 对象实例化时编译器自动调用对应的构造函数;
- 构造函数可以重载。
所以Date类构造函数可写为:
1.2.1 无参构造函数:
#include<iostream>
using namespace std;
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
//Date d2();//err
//Date d1(2024, 1, 27);//err,没有与参数列表匹配的构造函数实例
return 0;
}
运行结果为:
此外,C++规定,无参构造函数变量名后不能加小括号。Date d2();有可能是函数的声明,返回类型是Date,因为这个地方要写函数声明的话,小括号中是要加参数类型的。
1.2.2 带参构造函数
#include<iostream>
using namespace std;
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
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 d1(2025, 3, 5);
return 0;
}
构造函数可以函数重载,也可以改写为全缺省。
#include<iostream>
using namespace std;
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2025, 3, 5);
return 0;
}
语法上,上述两个函数也可以同时存在,因为函数重载,函数名虽然相同,但参数不同。但是在调用Date d1;时,会产生歧义,编译器不知道要调用谁,是要调用无参的还是要调用全缺省的。所以一般情况下,我们不这样写。
1.3 构造函数的特性
1.C++规定,对象定义(实例化)的时候,必须调用构造函数。
2.构造函数是默认成员函数,默认成员函数的特征是:我们没有显示定义,编译器会自动生成一个无参的;如果写了,编译器就不会生成。
但是,当我们这样写如下代码以后,编译器调用了默认生成的构造函数,但是什么也没干,没有初始化。
#include<iostream>
using namespace std;
class Date
{
public:
void func()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 1;
int _month = 1;
int _day;
};
int main()
{
Date d1;
d1.Print();
//A _aa;
return 0;
}
运行过程为:
运行结果为:
说明了使用编译器实现的默认构造函数出来初始化的数据是随机值,不是0。
C++98规定,默认生成的构造函数,对于内置类型不做处理;对于自定义类型会直接调用他的默认构造函数。即编译器对生成默认了构造函数,对int不做处理,但对于自定义成员类型A aa;要调用A的构造函数。
内置类型/基本类型 - int/char/double/指针
自定义类型 - struct/class
C++11对这个语法进行补丁,在声明的位置给缺省值。即不给缺省值,编译器什么都不管;给缺省值,就默认生成了构造函数,用缺省值去完成初始化。在类中,默认了Date(){int _year = 1;int _month = 1;};
1.4 默认构造函数
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 1;
int _month = 1;
int _day;
};
int main()
{
Date d1;
return 0;
}
当我们运行时发现上述代码报错:此处编译报错,提示没有默认构造函数可以用。
很多人经常理解默认构造函数就是编译器生成的那一个。编译器默认生成的构造函数是默认构造函数,但只是其中之一。
无参的构造函数和全缺省构造函数也被称为默认构造函数,且默认构造函数有且只能有一个。 一般情况下,建议优选全缺省构造函数。
总结:不需要传参就可以调用的构造函数,都可以叫默认构造函数。
所以,上述代码中,没有无参,也没有全缺省的默认构造函数,此时就需要编译器就要生成一个默认构造函数。
但是编译器默认生成的构造函数是有条件的,基于默认成员函数的特性:即没有写显示定义的构造函数,编译器才会自动生成一个无参的默认构造函数;一旦用户显示定义,编译器就不会再生成。
为了使上述程序能够通过,需要函数重载,显式定义一个默认构造函数。
#include<iostream>
using namespace std;
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 1;
int _month = 1;
int _day;
};
int main()
{
Date d1;
return 0;
}
二、析构函数
2.1 析构函数的概念
内存泄漏是不会报错的,所以经常会忘记destory,所以C++就有了析构函数。
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。相当于destory,把动态开辟的数组空间free掉,不清理的话会出现内存泄漏。所以:构造函数完成的不是创建,析构函数完成的也不是销毁。
在函数名前加~,在C语言中,~表示按位取反,所以选用这个符号就表示和构造函数的功能是相反的。
2.2 特性
析构函数是特殊的成员函数,其特性如下:
- 析构函数名是类名前加字符“~”;
- 没有参数也没有返回值;
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载;
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
第4点类似于构造函数,构造函数在实例化的时候会自动调用。
#include<iostream>
using namespace std;
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类生成的默认析构函数。
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数。
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity = 3)
{
cout << "Stack(int capacity = 3)" << endl;
_arry = (int*)malloc(capacity * sizeof(4));
if(_arry == NULL)
{
perror("malloc");
}
_capacity = capacity;
}
void Push(int x)
{
_arry[_size] = x;
_size++;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_arry);
_arry = NULL;
_size = 0;
_capacity = 0;
}
private:
int* _arry;
int _capacity = 0;
int _size = 0;
};//注意必须要有分号
class MyQueue
{
private:
Stack st1;
Stack st2;
int _size = 0;
};//注意必须要有分号
int main()
{
MyQueue q;
return 0;
}
运行结果为:
2.3 析构顺序
销毁顺序为:局部对象(后定义的先析构)->局部的静态->全局对象(后定义的先析构)。程序证明如下:
class Date
{
public:
Date(int year = 1)
{
_year = year;
}
~Date()
{
cout << "~Date()->" << _year << endl;
}
private:
int _year ;
int _month;
int _day;
};
void func()
{
Date d3(3);
static Date d4(4);
}
Date d5(5);
static Date d6(6);
int main()
{
Date d1(1);
Date d2(2);
func();
return 0;
}
static Date d3(3);为局部静态,和上边两个的存储区域不同,第3是存在静态区的,虽然定义在了局部,但是生命周期是全局的。在main函数结束以后才会销毁。所以在这之前要先把main函数中的局部变量先销毁。