###前言:此文主要介绍C++中的六种默认成员函数;默认的意思就是我们不写编译器会自动生成;这些函数在类里面自动生成;但是我们也可以自己写;学习这几种默认成员函数从两个方面入手:
(1)编译器自动生成时,这些函数会做什么?有哪些行为?这些行为是否满足我们的需求?
(2)我们自己写时,要怎么写才能满足我们的需求?
一、构造函数
构造函数名为构造,但是它的作用却不是去构造的;构造函数 , 的作用是初始化,可以把它想象成Init去理解;构造函数在我们用类实例化对象时发挥作用,去初始化对象里面的一些属性。
构造函数的几个性质:
(1)一个类的构造函数的名称和这个类一样;构造函数没有返回值,函数名前面也不要写void;
(2)构造函数支持重载,也就是说一个类里面可以有多个构造函数;
(3)构造函数开始作用时间是实例化对象的时候,并且自动调用;
(4)我们不定义构造函数时,编译器会自己在类里面生成一个无参的默认构造函数;当我们自己定义构造函数时,编译器就不会自动生成了;
(5)构造函数分为无参、带参、和缺省参数几种;默认构造函数是可以不传实参的构造函数,具体是:我们自己定义的无参、全缺省或者编译器自动定义的无参构造函数。默认构造函数在一个类中只能出现一种,原因:我们不定义时,自动生成无参的,那么就是一种;我们自己定义时,若是定义无参和全缺省两种,那么调用时不知道调用哪一个(不传参这两者都鞥被调用),只能写两者之一;(注意:我们自己定义的无参和编译器定义的无参是有区别的,最主要的区别在于给成员变量初始化的值不同);
(6)编译器自动生成的默认构造函数对内置类型的成员变量的初始化是未定义的;对自定义类型的成员变量的初始化则调用这个成员变量自己的构造函数;
###代码示例1:
#include<iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
//构造函数
//1、无参
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//2、带参
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date d1;//这里已经自动调用了
d1.Print();
Date d2(2020, 1, 1);//这是带参的写法
d2.Print();
}
首先看到我们自己定义的无参和带参两种构造函数;可以观察到:构造函数名称和类名一样,并且构造函数可以重载,写法也只是类名,没有返回值和void之类的;
调用构造函数的时间是实例化对象时,那么对于无参的来说,直接实例化的同时就自动调用了无参构造函数;对于带参的来说,要在实例化时在对象的后面加上括号,括号里面写置为的初始化的值;
具体观察调用构造函数的时间:
还未实例化:
实例化 d1:
实例化d2:
可以观察到构造函数是在实例化对象的同时调用的;
###代码示例2:
接下来看到我们不定义构造函数时,系统自动生成默认构造函数的过程:
class Date
{
private:
int _year;
int _month;
int _day;
public:
//不自己写构造函数
};
int main()
{
Date d1;
return 0;
}
未实例化:
实例化之后:
我们没有写构造函数,但是实例化之后d1的属性还是初始化了;初始化的是随机值;这证明了编译器自动生成的默认构造函数对对象的内置类型的成员变量的初始化是随机的。
###代码示例3:
这里主要看全缺省的默认构造函数(可以不传实参就是默认构造函数):
class Date
{
private:
int _year;
int _month;
int _day;
public:
//全缺省
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
};
int main()
{
//不传参时
Date d1;
d1.Print();
//传参时
Date d2(10);
d2.Print();
return 0;
}
###代码示例4:
当我们不写构造函数时,编译器自动生成的默认构造函数对内置类型成员变量的初始化是随机的,对自定义类型的成员变量初始化则是调用这个自定义类型的成员变量自己的构造函数,这里主要来观察系统自动生成的无参默认构造函数对自定义类型的成员变量初始化的过程:
简单示例:
class X
{
private:
int _a;
int _b;
public:
X()
{
_a = 10;
_b = 20;
}
};
class Y
{
//定义两个类的自定义类型
private:
X _A;
X _B;
int _c;
public:
//不写构造函数
};
int main()
{
Y y;
return 0;
}
这里定义了两个类:X、Y;X有自己的成员变量:_a _b,并且在X中定义了一个无参构造函数;Y的属性有 类X自定义类型的_A和_B,以及一个内置类型_c ;Y中不写构造函数;
在main函数中用一个Y类实例化对象y,那么此时也会调用编译器自动生成的无参默认构造函数;我们来观察y属性的初始化的情况:
可以观察到,编译器自动生成的默认构造函数对对象初始化时,y中自定义类型的成员变量 _A和_B会调用它们自己的构造函数也就是类X中的构造函数;对于内置类型的_c,那么它的初始化是随机的。
至于若是自定义类型的属性也没有构造函数,那么它的初始化的值就是随机的。
二、析构函数
析构函数和构造函数的作用相反,它的作用相当于Destroy,作用是对对象中申请了资源的成员变量进行释放;至于类似于上文示例中的Date类实例化的对象,则不需要用析构函数释放,因为这个类实例化出来的对象中的属性没有申请资源,它们都是局部变量,存于栈区中,在函数结束时会自动销毁。
那么我们主要来看析构函数;
析构函数的性质:
(1)析构函数的也没有返回值,前面也不用写void ;写法区别于构造函数的就是要在前面加上 ~ ;
(2)析构函数不支持重载;当我们不写析构函数时,编译器会将自动生成一个析构函数;
(3)析构函数调用时间是在对象的声明周期结束时,一般来说就是main函数走到return 0;时调用;
(4)跟构造函数相似:我们不写析构函数时,编译器自己生成的默认析构函数对于自定义类型的成员变量,析构时会调用这些成员变量自己的析构函数;对于内置类型的成员变量不做释放处理,这些成员变量随着函数栈帧的销毁而释放;
(5)无论我们写不写析构函数,对于自定义类型得到成员变量的销毁都会调用它们自己的析构函数。
###代码示例1:
我们自己写析构函数:先看只含有内置类型的对象:
class A
{
public:
A()
{
_b = 190;
}
~A()
{
_b = 0;
}
private:
int _b;
};
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
_a = (int*)malloc(sizeof(int));
}
//析构函数
~Date()
{
free(_a);
_a = nullptr;
_year = _month = _day = 0;
}
private:
int _year;
int _month;
int _day;
int* _a;
//A _c;
};
int main()
{
Date d1;
return 0;
}
实例化之后:
走玩return 0;对象生命周期结束,自动调用析构函数,进行释放,年月日的属性在栈区,函数栈帧销毁时才会真正释放。
我们自己定义的析构函数对自定义类型的成员变量的销毁:(取消注释自定义类型的成员变量,也就是多了一个成员变量A _c)
实例化之后:
销毁:
对于内置类型的成员变量的销毁调用的是它自己的析构函数;
###代码示例2:
我们不写析构函数,看编译器自己生成的析构函数对对象中成员变量的销毁过程:
class A
{
public:
A()
{
_b = 190;
}
~A()
{
_b = 0;
}
private:
int _b;
};
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
_a = (int*)malloc(sizeof(int));
}
private:
int _year;
int _month;
int _day;
int* _a;
A _c;
};
int main()
{
Date d1;
return 0;
}
可以得知,编译器自动生成的析构函数对内置类型的成员变量不做处理;对于自定义类型的成员变量还是调用它自己的析构函数,若是这个自定义类型的成员变量我们也没有自己写析构函数,那么对它也不做处理;
总结:一般来讲,构造函数和析构函数我们都需要自己写;少数情况,如自定义类型的成员变量写好了构造和析构函数时,我们不用再写。实际上,都是我们自己写的,只不过一个对象作为另一个对象的成员变量时,若是我们写好了这个作为成员变量的对象的构造和析构,我们不用再在真正为对象的对象所在的类中写了而已。