如果一个类中什么成员都没有,则就是空类。但是空类中什么都没有吗?其实并不是这样的,任何类在我们不写的情况下,都会自动生成下面六个默认成员函数。
class Date{};
初始化和清理
-
构造函数
- 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
- 特性:
- 1、构造函数的名字跟类名相同
- 2、没有返回值
- 3、对象实例化时候,编译器自动调用对应的构造参数
- 4、构造函数可以重载
#include<iostream> using namespace std; class date { public: //从下面可以看到构造函数的名字跟类名相同,并且没有返回值 data() {}//无参的构造函数 date(int year, int month, int day) { _year = year; _month = month; _day = day; }//有参的构造函数 //这两个构造函数构成了函数的重载,因为它们的参数不同 void PrintDate() { cout<< _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { date d1;//调用无参构造函数 date d2(2019,07, 11);//调用带参的构造函数 d2.PrintDate(); return 0; }
- 5、如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
class Date { public: void PrintDate() { cout<< _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; void Test(){ Date d;//没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数 }
-
6、无参构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
- 无参构造函数:Date(){}
- 全缺省构造函数:Date(int year=2019,int month=7,int day=11){}
-
7、如果用户自己没有定义构造函数,则C++系统会自动生成一个构造函数,称作默认构造函数或者缺省构造函数。只是这个构造函数是空的,不执行任何操作。它好像看起来没有什么用处,但是为什么还要存在呢?
- 因为C++将类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如 int/char…,自定义类型就是我们使用class/union/struct自己定义的类型。看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_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; system("pause"); return 0; }
接下来我们看一下它创建对象时的汇编语句
从图上我们看到我们并没有显式的在类中定义构造函数,编译器自动的生成了一个默认的构造函数,此时这个默认的构造函数就有用了,它将会对自定义类型的成员_t调用它的默认成员函数
-
析构函数
- 析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
- 特征:
-
析构函数名是在类名前加上字符~
-
无参数无返回值
~SeqList(){}
-
一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
-
对象生命周期结束时,C++编译系统会自动调用析构函数
-
关于编译器自动生成的析构函数,会对自定义类型成员调用它的析构函数
-
拷贝复制
-
拷贝构造函数
- 概念:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
- 为什么一般常用const修饰?
- 因为你在拷贝复制的时候可能不小心会把被拷贝的对象给修改了,这样程序的结果可能就不符合预期,如果你加上const的时候,就会出现下面的情况。
- 因为你在拷贝复制的时候可能不小心会把被拷贝的对象给修改了,这样程序的结果可能就不符合预期,如果你加上const的时候,就会出现下面的情况。
- 为什么一般常用const修饰?
- 概念:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
- 特征
-
拷贝构造函数是构造函数的一个重载形式(因为它们在同一个作用域,函数名相同,参数列表不同)
-
拷贝构造函数的参数只有一个且必须使用引用实参,使用传值方式会引发无穷递归调用
Date(const Date date){}
当我们用一个对象去拷贝构造另一个对象的时候,如果我们显式定义了拷贝构造函数,则在拷贝构造的时候,编译器则会调用我们自己显式定义的拷贝构造函数。而如果我们使用传值的方式进行拷贝构造的时候,则在调用拷贝构造函数时,编译器会生成一个临时对象。而这个临时对象也会调用拷贝构造函数来完成临时对象的创建,所以就会不断的调用拷贝构造函数,,从而引发无穷递归调用。
-
若未显式定义,系统会默认生成一个拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝是浅拷贝,或者叫值拷贝
上面我们给出的String类,我们使用s2拷贝构造s1,并且我们使用的是系统给出的默认拷贝构造函数,使用的是浅拷贝。当我们构造完成要释放资源时,则会对同一份内存空间释放两次,所以程序就会崩溃。因此当一个类管理资源的时候,将来这个类我们一定要自己显式的实现它,否则就是浅拷贝,则会引起程序崩溃。 -
如果我们自己没有实现拷贝构造函数,则编译器也会为我们实现一个默认的拷贝构造函数,那么我们什么时候使用编译器给出的,什么时候我们自己去实现呢?
- 如果一个类管理资源的时候,就需要我们自己去实现这个拷贝构造函数,如果没有管理资源,如我们前面写的日期类,就可以使用编译器给出的默认拷贝构造函数。
-
-
赋值运算符重载
-
运算符重载
- 运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
- 函数名字为:关键字operator后面接需要重载的运算符符号
- 函数原型:返回值类型operator操作符(参数列表)
- 注意:
- 不能通过连接其他符号来创建新的操作符:如operator@,并且 .* :: sizeof ?: .这五个运算符不能重载。
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变
- 例如:内置类型的+,不能改变其含义,什么意思?
int main(){ int a = 1; int b = 2; a+b;//这个+号加完之后的结果并没有改变a和b的值,而如果改为a+=b,则结果就会改变a的值,作为内置类型的操作符就会出错 }
- 作为类成员的重载函数时,其形参看起来比操作数数目少一
- 因为操作符有一个默认的形参this,限定为第一个形参
-
赋值运算符重载
- 赋值运算符主要有四点
- 参数类型:一个类类型对象
- 返回值:有返回值
- d1 = d2 = d3;对于这种连续赋值的时候,要有返回值
- 检测是否自己给自己赋值
- 返回*this
- 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的拷贝
- 赋值运算符主要有四点
-
取地址重载
-
const成员
- const修饰类的成员函数:将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际上修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
- const对象不允许调用非const成员函数,而非const对象可以调用const成员函数
- const成员函数内不可以调用其它的非const成员函数,而非const成员函数可以调用其他的const成员函数
- 取地址及const取地址操作符重载
- 这两个默认成员函数一般不用重新定义,编译器会默认生成
class Date { public: Date* operator&() { return this; } const Date* operator&()const { return this; } private: int _year; int _month; int _day; };