一. 类的6个默认成员函数
默认的意思就是,我们不实现时,编译器自动会帮我们实现一份
二. 构造函数
1. 概念
构造函数是一个特殊的成员函数,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次
2. 特性
无返回值
函数名与类名相同
构造函数可以重载
c
lass Date { public: // 1.无参构造函数 Date() {} // 2.带参构造函数(两个可以并存,因为它们构成了重载) Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
3. 调用方式
如果显示定义的构造函数不带参或者是全缺省,在类实例化不想传参时,直接定义就行,后面不用加空括号。
int main() { //1. 调用无参的构造函数 //PS:不能写成Date d1(),这样就不能体现构造函数的默认性了 Date d1; //2. 调用带参的构造函数 Date d2(2021, 4, 15); return 0; }
4. 默认的构造函数(不传参就可以调用的)
4.1 分类
自己显示定义的无参的构造函数
自己显示定义的全缺省的构造函数
我们没写编译器默认生成的构造函数
PS:一个类中默认构造函数只能有一个
4.2 编译器默认生成的构造函数
一旦显示定义了构造函数(无论是默认的还是非默认的)编译器就不会再生成默认的构造函数。
作用:对内置类型(int,char,double等等)的成员变量不做处理,它们依然是随机值;对自定义类型的成员变量会调用它们的构造函数来初始化它们。
private: //编译器生成的默认构造函数 //1. 对内置类型成员变量不做处理 int _year; int _month; int _day; //2. 对自定义类型的成员变量调用它们的构造函数来初始化它们 Time t1;//(这里的Time是我们定义的另外一个类的类型)
5. 成员变量的命名风格
在给成员变量命名时,建议加上个前缀或者后缀以标识,不然在成员函数中可能会与形参名发生歧义。
class Date { public: //构造函数,初始化成员变量 Date(int year) { // 1. 这里的year到底是成员变量,还是函数形参?(按照就近原则,这里左值year是形参year,也就是右值) year = year; // 2. 这样就不会引起歧义 _year = year; } private: //1. 第一种成员变量命名方法 int year; //2. 第二种成员变量命名方法 int _year; };
三. 析构函数
1. 概念
对象在销毁时会自动调用析构函数,完成类对象的一些资源清理工作。
2. 特性
无返回值
名称: ~ + 类名
参数列表为空,不可重载
class Stack { public: //析构函数完成清理工作,比如堆空间的释放等 ~Stack() { free(_a); _a = nullptr; _top = 0; _capacty = 0; } private: int* _a; int _top; int _capacty; };
3. 调用方式
对象生命周期结束后,系统自动调用。
4.编译器默认生成的析构函数
与编译器生成的默认构造函数类似。对内置类型成员变量不做处理,对自定义类型的成员变量会调用它的析构函数完成完成这个成员变量的清理工作。
四. 拷贝构造
1. 概念
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
2. 调用方式
作为成员函数,它有两种调用方式
3. 特性
拷贝构造函数是构造函数的一个重载形式
拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
形参加最好加上const的原因:1.可以接收const修饰的实参。 2.对于拷贝构造的形参我们一般只读取他的值,不会去修改它,加const起到保护作用。
4. 编译器默认生成的拷贝构造函数
默认的拷贝构造函数按被拷贝对象的内容一个字节一个字节的拷贝过去,这种拷贝我们叫做浅拷贝,或者值拷贝。
int main() { //构造一个d1对象 Date d1(2021, 4, 15); //没有显示定义拷贝构造函数,利用编译器默认生成的拷贝构造来实例化d2 Date d2 = d1; return 0; }
5. 默认拷贝构造带来的浅拷贝问题
如果成员变量有动态开辟的空间的话,为了防止内存泄漏,我们最后要释放(一般通过析构函数来完成),如果使用默认拷贝构造函数构造同类对象,拷贝对象的成员变量也指向同一块动态开辟的空间,最后在析构函数清理时,会造成同一块空间多次释放而使程序崩溃。
#include<iostream> #include<stdlib.h> //下面的程序会崩溃掉 //因为浅拷贝带来的同一块空间空间多次 class Stack { public: Stack(int n=10) { _a = (int*)malloc(sizeof(int)*n); _top = 0; _capacty = n; } //析构函数完成空间释放 ~Stack() { free(_a); _a = nullptr; _top = 0; _capacty = 0; } private: int* _a; int _top; int _capacty; }; int main() { Stack s1; Stack s2(s1); return 0; }
五. 赋值运算符重载
1. 运算符重载
1.1 概念
自定义类型是不能用运算符的要实现就的实现运算符重载。运算符重载是具有特殊函数名的函数,函数名为关键字operator后面接需要重载的运算符符号,其返回值类型与参数列表与普通的函数类似。
函数原型:返回值类型 operator操作符(参数列表)
1.2 特性
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
操作符有一个默认的形参this,限定为第一个形参
.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载
1.3调用方式
全局的运算符重载(运算符重载成全局的就需要成员变量是共有的,为了保证封装性可以把全局的运算符重载设为类的友元函数)
PS:全局的运算符重载只有一种调用方式(因为它不是成员函数,就不会自动地传this指针)
定义在类内部的作为成员函数的运算符重载
PS:在类内定义的话有两种调用方式(因为它不是成员函数,就不会自动地传this指针)
2. 赋值运算符重载
2.1 概念
重载的赋值运算符必须是成员函数,也就是说重载赋值运算符必须定义在类内部的作为类的成员函数。
2.2 特性
返回值
返回左值的引用(也就是*this)
参数类型
建议传对象的引用并且加上const修饰,前者为了减少一次拷贝构造,提高效率;后者为了保护对象(右值)的内容不被修改,并且右值也可以传const和非const对象。
检测是否自己给自己赋值
如果自己给自己赋值的话没有必要,用if判断直接跳过
2.3 调用方式
作为成员函数,赋值重载也有两种调用方式
2.4 编译器默认生成的赋值重载函数
如果没有显示定义赋值重载,编译器会自动生成默认的赋值重载函数,和默认的拷贝构造函数一样,两个对象直接的数据是按一个字节一个字节的拷贝过去的(浅拷贝),也会带来浅拷贝问题。
2.5 区别的拷贝构造和赋值重载的调用方式
两者区别的关键在于构造和赋值。构造的话是两个对象一个已经实例化出来了,一个还没实例化;赋值的话两个操作对象都已经被实例化出来了。
六. const修饰类的成员函数
1.概念
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数的*this,表明在该成员函数中不能对类的任何成员进行修改。
用法:在参数列表后面加上const
class Date { public: void Print() const//等价于void Print(const Date* this); { //const修饰了this指针,所以不能修改this对象的成员变量 //_year = 10;(非法) cout << _year << "-" << _month << "-" << _day << endl;//只是读取,合法 } private: int _year; int _month; int _day; };
2.有关const权限的问题
在引用取别名和传指针时权限可以缩小不能放大:const修饰的对象只可读不可写;非const对象可读可写。
const对象可以调用非const成员函数吗?(不行)
非const对象可以调用const成员函数吗?(行)
const成员函数内可以调用其它的非const成员函数吗?(不行)
非const成员函数内可以调用其它的const成员函数吗?(行)
七. 取地址及const取地址操作符重载
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可。
class Date { public: //1. 取地址操作符重载 //const对象不可调用(权限放大) //返回值可读可写 Date* operator&() { return this; } //2. const取地址操作符重载 //const和非const对象都可调用 //返回值只读不可写 const Date* operator&()const { return this; } private: int _year; // 年 int _month; // 月 int _day; // 日 };
只有特殊情况,才需要重载,比如不想让别人获取到对象的地址,重载取地址操作符返回nullptr。
class Date { public: Date* operator&() { return nullptr; } const Date* operator&()const { return nullptr; } private: int _year; // 年 int _month; // 月 int _day; // 日 };