引入
哈喽各位铁汁们好啊,我是博主鸽芷咕《C++干货基地》是由我的襄阳家乡零食基地有感而发,不知道各位的城市有没有这种实惠又全面的零食基地呢?C++ 本身作为一门篇底层的一种语言,世面的免费课程大多都没有教明白。所以本篇专栏的内容全是干货让大家从底层了解C++,把更多的知识由抽象到简单通俗易懂。
⛳️ 推荐
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
文章目录
一、运算符重载
C++ 中为了增加代码的可读性运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
🔥 注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.*
::
sizeof
? :
.
注意以上5个运算符不能重载。这个经常在笔试选择题中出现。**
1.1 == 运算符重载
下面我们就来实践一下再日期类中 ==
运算符如何重载, 以往我们在 内置类型 ==
运算符返回的都是bool 类型
- 这样的话返回类型就确定了
- 运算符重载 的关键字是 operator
- 函数原型:
返回值类型 operator操作符(参数列表)
🍸 代码演示:
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}bool operator==(const Date& d1){return _year == d1._year&& _month == d1._month&& _day == d1._day;}private:int _year;int _month;int _day;
};int main()
{Date d1(2022, 1, 13);Date d2(2022, 1, 14);Date d3(2022, 1, 13);cout << (d1 == d2) << endl;cout << (d1 == d3) << endl;return 0;
}
1.2 < 运算符重载
< 运算符重载就更加简单了,和 运算符 == 的规则一样只需要改变一下我们类成员的比较方法就可以了
🍸 代码演示:
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}bool operator==(const Date& d1){return _year == d1._year&& _month == d1._month&& _day == d1._day;}bool operator< (const Date& d1){if (_year < d1._year){return true;}else if (_year == d1._year){if (_month < d1._month){return true;}else{return _day < d1._day;}}return false;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2022, 1, 13);Date d2(2022, 1, 14);Date d3(2022, 1, 13);cout << (d1 < d2) << endl;cout << (d1 < d3) << endl;return 0;
}
二、赋值运算符重载
前面的几个赋值运算符重载比较简单,只是为了完成类是如何比较的而赋值运算符就是我们这里比较关键的了
赋值运算符重载
主要是赋值,把一个对象赋值给另一个对象- 而
拷贝构造函数
主要是,同类型的对象创建初始化时调用
2.1 赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
这里我们为什么要讲返回值类型设定为
&
引用类型呢?赋值运算不就简单赋值就好了:
- 那么我们就来看一下下面这段代码
class Date
{
public:Date(int year=2022, int month=10, int day=21){_year = year;_month = month;_day = day;}bool operator==(const Date& d1){return _year == d1._year&& _month == d1._month&& _day == d1._day;}bool operator< (const Date& d1){if (_year < d1._year){return true;}else if (_year == d1._year){if (_month < d1._month){return true;}else{return _day < d1._day;}}return false;}bool operator!=(const Date& d){return ~(*this == d);}void operator=(const Date& d){_year = d._year;_month = d._year;_day = d._day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2022, 1, 13);Date d2;Date d3;d2 = d3 = d1;int a, b;a = b = 10;return 0;
}
这里为什么内置类型可以连续赋值而自定类型不可以?内置类型我们都知道是从后往前连续赋值的
- 也就是 10 先赋值给 b 然后 b 在赋值给 a,而我们并没有给赋值运算符重载返回一个值所以
- 就提示我们
二元 “=”
运算符没有找到与它匹配的操作数
所以我们的返回值就必须是this 指针所指向的内容代码改进如下:
class Date
{
public:Date(int year=2022, int month=10, int day=21){_year = year;_month = month;_day = day;}bool operator==(const Date& d1){return _year == d1._year&& _month == d1._month&& _day == d1._day;}bool operator< (const Date& d1){if (_year < d1._year){return true;}else if (_year == d1._year){if (_month < d1._month){return true;}else{return _day < d1._day;}}return false;}bool operator!=(const Date& d){return ~(*this == d);}Date& operator=(const Date& d){if (*this != d){_year = d._year;_month = d._year;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2022, 1, 13);Date d2;Date d3;d2 = d3 = d1;return 0;
}
多加了一个 if 判断是为了避免
d1 = d1;
这样的写法去又重新赋值一遍
2.2 赋值运算符重载的行为
赋值运算符重载既然也是六大默认成员函数之一那么肯定也是我们不写自动生成一个默认的复制运算符重载 ,那么究竟有什么行为呢?
构造函数和析构函数他们的行为都是对内置类型不处理对自动定义类型调用他们的析构或者构造函数,而 赋值运算符重载是和 拷贝构造一样的行为,我们不写会自定生成一个默认函数,默认的赋值运算符重载 以值的方式逐字节拷贝。
- 🔥 注:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
2.4 有了自动生成的赋值重载我们还需要自己写吗?
答案是肯定的,自动生成的
默认赋值重载
只会完成浅拷贝,也就是值拷贝当遇到需要申请资源
的类时 就会出现问题!
- 默认生成的赋值重载只完成值拷贝会把俩个指针的指向都改为同一个方向就会导致程序崩溃
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}
这里s2 的指针被 s1 所指向的地址给以值的方式赋值了,所以 s2 s1 指向的是同一块空间当他们自动调用析构函数时就会对同一块地址释放俩次导致程序崩溃
三、前置++ 和 后置++
前面我们重载的都是二元的运算符,看似只有一个显示定义的参数其实还有一个this指针
- 例如d1 == d2 虽然我们是这样写的但是会转换为
- d1.operator==(d2) 这样的,那前置 ++ 和后置++ 都是一元操作符且运算符都是一样的如何进行重载呢?
下面我们就来尝试重载一下前置++,前置++ 我相信大家都用过返回的是++之后的值
🍸 代码演示:
Date& operator++(){_day++;return *this;}
而我们的后置++ 返回的是++之前的值先使用在++所以需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
后置++:
- 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
- C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
Date operator++(int){Date tmp(*this);_day += 1;return tmp;}