个人主页 : zxctscl
文章封面来自:艺术家–贤海林
如有转载请先通知
文章目录
- 1. 类的6个默认成员函数
- 2. 构造函数
- 2.1 概念
- 2.2 构造函数特性
- 2.2.1 语法特性
- 2.2.2 其他特性
- 3. 析构函数
- 3.1 概念
- 3.2 特性
- 4. 构造与析构顺序
1. 类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
重点关注前面四个。
2. 构造函数
在写栈或者队列时可能会忘记初始化,就会开始其他操作,所以c++就提出构造函数。
2.1 概念
对于以下Date类:
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.Print();Date d2(2024, 2, 23);d2.Print();Date d3(2024);d3.Print();return 0;
}
对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
2.2 构造函数特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
2.2.1 语法特性
语法定义其特征如下:
-
函数名与类名相同。
-
无返回值。(这里并不是指void,而是直接不需要写)
-
对象实例化时编译器自动调用对应的构造函数。
-
构造函数可以重载。
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;// 调用无参构造函数d1.Print();Date d2(2024, 2, 23);// 调用带参的构造函数d2.Print();return 0;
}
还可以直接写成全缺省
class Date
{
public: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 d3(2024);d3.Print();return 0;
}
还能只传部分参数。
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;d1.Print();return 0;
}
无参和全缺省的能不能同时存在呢?
可以,他们构成函数重载。但是实践中不能同时存在,会出现对重载函数调用不明确。
一般情况下不会同时写出来。
2.2.2 其他特性
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。 - 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
我们理解构造函数是默认成员函数,我们不写编译器会生成一个。
所以会认为在不给值时,会自动初始化日期为0或者负一,但是并没有。
class Date
{
public: void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Print();return 0;
}
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
在c++中将数据分为内置类型也叫基本类型,像 int/char/double/指针 ,语言自己自身定义的类型。
还有自定义类型,像struct/class。
在c++98中规定了:默认生成的构造函数,对于内置类型不做处理,自定义类型回去调用他的默认构造。
在C++11 委员会对这个语法进行打补丁, 在声明的位置给缺省值,像这样。
所以分析一个类型成员和初始化需求
需要写构造函数我们就自己写;不需要时就用编译器自己生成的
结论:绝大多数场景下面都需要自己实现构造函数
如果我们没写任何一个构造函数,编译器才会自动生成的,如果我们写了就不会生成了
- 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。
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;
}
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
看看下面这个代码:
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=1 ;int _month=1;int _day;};
int main()
{Date d1;d1.Print();return 0;
}
默认构造函数有三个:1.编译器默认生成的构造函数,2.全缺省构造函数,3.无参构造函数。
总结一下就是,不需要传参就可以调用构造函数,都可以叫做默认构造函数。
在上面代码中已经有带参构造函数了,不会生成默认构造。
要编译通过代码,只需要加一个无参构造。
一般推荐写全缺省构造函数。
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=1 ;int _month=1;int _day;};
int main()
{Date d1;d1.Print();return 0;
}
3. 析构函数
析构函数完成的是清理资源,类似于之前栈的Destroy,不清理就会内存泄漏。
3.1 概念
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
3.2 特性
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
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;}~Date(){cout << this << endl;cout << "~Date()" << endl;}private:int _year = 1;int _month = 1;int _day;
};void func()
{Date d2;
}
int main()
{func();Date d1;d1.Print();return 0;
}
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;}~Date(){cout << this << endl;cout << "~Date()" << endl;}private:int _year = 1;int _month = 1;int _day;
};void func()
{Date d2;
}
class Stack
{
public:Stack(size_t capacity = 3){_array = (int*)malloc(sizeof(int) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(int data){// CheckCapacity();_array[_size] = data;_size++;}bool Empty(){return _size == 0;}int Top(){return _array[_size - 1];}void Pop(){//..}~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = nullptr;}_size = _capacity = 0;}private:int* _array;int _capacity;int _size;
};int main()
{func();Date d1;d1.Print();Stack st1;return 0;
}
在用C语言写栈或者队列时候可能会忘记释放销毁,但c++中提供默认析构函数就会解决这个问题。
- 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
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, 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁,main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
4. 构造与析构顺序
先来看看下面的代码
class Date
{
public:Date(int year = 1){_year = year;}~Date(){cout << "~Date()->"<<_year<< endl;}private:// 基本类型(内置类型)int _year;int _month;int _day;
};int main()
{Date d1(1);Date d2(2);return 0;
}
他们两个在同一个域中,满足后定义的先析构(后进先出),所以这里先析构d2再析构d1。
再加上一个局部静态
他们的存储区域是不一样的,d3虽然定义在局部,但生命周期是全局的。
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);
}int main()
{Date d1(1);Date d2(2);func();return 0;
}
定义1和2后,调用fun(),先定义d3,然后再定义d4,但d3是局部的,d4是全局的。所以fun()结束后,先销毁d3,d4不销毁。然后main函数结束后,先销毁2,再销毁1,最后是4。
先销毁局部的静态d4,再销毁全局的d5。
发现将static Date d6(6); Date d5(5);
进行交互以后,销毁的顺序就不一样了。
所以销毁顺序就是:先局部对象(后定义先析构)->局部的静态->全局对象(后定义先析构)
有问题请指出,大家一起进步!!!