默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中并不是什么都没有,任何类即使在什么都不写时,编译器会自动生成以下6个默认成员
函数。
默认成员函数:用户如果没有显式实现,编译器会自动生成的成员函数(在类中,用户可以自己实现默认成员函数,编译器也就不会再自动生成)。
构造函数——初始化函数
注意:构造函数的任务并不是开空间创建对象,而是初始化对象
class Date
{
public:
void Init()
{
_year = 1;
_month = 1;
_day = 1;
}
Date()
{
_year = 1;
_month = 1;
_day = 1;
}//由用户实现的构造函数
private:
int _year;
int _month;
int _day;
};
上述代码中,Init()与Date()功能一样,那为什么要引入构造函数呢?
原因在于构造函数可以在对象实例化时被编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数的特点
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
带参构造函数的实例化使用
class Date
{
public:
//注意:构造函数支持函数重载
Date(int year = 1;int month = 1;int day = 1)//全缺省构造函数
{
_year = year;
_month = month;
_day = day;
}
Data()//不带参的构造函数
{
_year = 1;
_month = 1;
_day = 1;
}
//虽然全缺省构造函数和不带参构造函数构成函数重载,但是无参调用时存在调用歧义
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2,2,2);
//Date d2();
//Date d3;
return 0;
}
d1,d2,d2实例化时状况各有不同
Date d1(2,2,2):调用带参构造函数
Date d2():非正常使用(原因在于可能与函数声明混淆)
Date d3:调用默认构造函数(不带参构造函数 / 全缺省构造函数 / 编译器生成构造函数),本类中前两种构造函数重载实现,所以存在调用不明确的问题
由d3可以看出, 全缺省构造函数 可以覆盖 不带参构造函数 的功能,并且全缺省构造函数调用灵活便捷,所以一般情况下,类中都会实现一个全缺省构造函数。
编译器自动生成的构造函数
引入概念:
1. C++将数据分为内置类型(基本类型)和自定义类型
内置类型(基本类型):int / char / double / 指针 ......
自定义类型:class / struct / union ......
2. 默认构造函数:
默认构造函数:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数
总结:不传参就可以调用的构造函数就是默认构造函数,并且三种默认构造函数不能共存一个类中。
上文提到,若用户未显示实现构造函数,编译器会自动生成构造函数,那编译器生成的构造函数究竟会将对象初始化成什么样子呢?
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;
}
打印结果:-858993460--858993460--858993460
由此可以看出编译器自动生成构造函数实际上并未对对象进行初始化,为什么?(部分编译器可能会初始化成0)
编译器自动生成的构造函数,对于内置类型成员变量,没有规定要不要处理(有的编译器可能会处理);对于自定义类型成员变量,才会调用它自己的默认构造函数进行初始化(下一篇介绍没有默认构造函数时,使用 初始化列表 对 自定义类型成员变量 初始化)
注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给缺省值,如下:
class Date
{
private:
int _year = 1;
int _month = 1;
int _day = 1;
};//一开始就会直接根据 缺省值 进行初始化,如果遭遇构造函数,可能覆盖该初始化。
构造函数总结
1. 一般情况构造函数都需要我们自己显示得去实现
2. 只有少数情况下可以让编译器自动生成构造函数,如MyQueue
析构函数
注意:析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
class Stack
{
public:
void Destory()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;}
}
~Stack()
{
if (_array)//此处判断用处:支持用户显示调用(后续编译器自动调用不会进入if)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;}
}//由用户实现的析构函数
private:
int* _array;
int _top;
int _size;
};
析构函数的特点
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统自动调用析构函数(用户可自己调用析构函数)
析构函数用于完成资源清理,在一些不需要消耗资源的类中(如Date类),就不需要析构函数,即用户无需自己去实现析构函数。
编译器自动生成的析构函数
与构造函数类似:
1. 对内置类型,不做处理
2. 对自定义类型,调用它自己的析构函数
析构是否需要显示实现的总结:
需要显示实现:有资源需要显示处理,如Stack / List
不需要显示实现:a.没有内存需要清理,如Date
b.没有需要资源清理的内置成员,如MyQueue
拷贝构造函数
拷贝构造函数的特点
1. 拷贝构造函数是构造函数的一个重载形式
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用
class Data
{
public:
Data(int year = 1, int month = 1,int day = 1)
{
_year = year;
_month = month;
_day = day;
}Data(const Data* d)//普通构造函数
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}Data(const Data& d)//拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
Data d2(&d1);//下面两行等价,均调用拷贝构造函数
Data d3(d1);
Data d4 = d1;
return 0;
}
为什么不能使用传值调用?
//单个类类型形参,编译器将其视作错误的拷贝构造函数,而不是普通构造函数
Data(Data d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}//改变形参列表,可以将其变为 普通构造函数 / 拷贝构造函数
关键原因在于:自定义类型传值传参需要调用拷贝构造函数
由此引发无穷递归调用
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。
总结:
无需实现拷贝构造函数:
1. 无资源管理,如Date
2. 类成员为 自定义类型成员 和 无指向资源的内置类型成员,如MyQueue
3. 一般情况下,不需要写析构函数,就不需要写拷贝构造函数
需要实现拷贝构造函数:
1. 类成员存在内置成员(指针/一些值)指向资源,需要深拷贝进行拷贝,如Stack,Queue,List等