hello,各位小伙伴,本篇文章跟大家一起学习《C++:运算符重载》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !
文章目录
- 赋值运算符重载
- 1. 运算符重载
- 2.赋值运算符重载
- 第一个点
- 第二个点:
- 第三个点:
- 前置++和后置++重载
- const成员
赋值运算符重载
1. 运算符重载
在《C++:函数重载和引用》里有讲到函数中在,那么和运算符重载有什么不一样呢?
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字 operator
后面接需要重载的运算符符号。
函数原型:返回值类型 operator
操作符(参数列表)
但是并不是所有符号都能重载,要注意:
- 不能通过连接其他符号来创建新的操作符:比如
operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型
+
,不 能改变其含义 - 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的
this
.
、*
、::
、sizeof
、?:
. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
举个例子:
class Date
{
public:Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}// 加const修饰是为了不让传参被改变bool operator==(const Date& d){return _year == d._year && _month == d._month&& _day == d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 5, 1);Date d2(d1);//拷贝构造cout << (d1 == d2) << endl;return 0;
}
输出结果是:1,也就是说d1和d2是相等的
// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d){return _year == d._year && _month == d._month&& _day == d._day;}
2.赋值运算符重载
第一个点
先来看格式,赋值运算符重载格式:
- 参数类型:const T&,传递引用可以提高传参效率 //
T
是类名 - 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回
*this
:要复合连续赋值的含义
Date& operator=(const Date& d){if(this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
1.先讲讲参数形式为什么是**const T&**和返回值类型:T&,在《C++:构造函数、析构函数、拷贝构造函数》讲到过在对象的传值传参时,会发生拷贝构造,返回临时对象时,也会发生拷贝构造,会降低效率。所以选择传引用传参和用引用做返回值
用引用做返回值还有一个原因是为了支持连续赋值,举个例子:
int main()
{Date d1(2024,5,1);Date d2(1,1,1);Date d3(1,1,1);d3 = d2 = d1;return 0;
}
在d2 = d1
时调用了函数Date& operator=(const Date& d)
,会返回一个值,这个值属于什么类型会影响d3
,所以返回*this
是为了将d2
的值赋值给d3
,从而实现了连续赋值
2.检测是否自己给自己赋值,自己给自己复制就没意思了,所以直接return *this
第二个点:
赋值运算符只能重载成类的成员函数不能重载成全局函数
原因:
赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
第三个点:
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}
在这里会调用Time
类的赋值运算符重载完成赋值
这里有小伙伴会说:既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。但是存在即合理,看以下例子:
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的_arry
的地址指向了s1的_arry
的地址 - 那么编译器为
s2的_arry
开辟的空间消失了 - 导致内存泄漏
- 在最后销毁时,将同一块空间释放了两次,程序崩溃
图解:
前置++和后置++重载
根据前面所讲,
函数名字为:关键字 operator
后面接需要重载的运算符符号。
函数原型:返回值类型 operator
操作符(参数列表)
那么怎么区分前置++和后置++重载的函数,来看:
在C++中,前置++和后置++运算符可以被重载为成员函数或友元函数。要区分重载的前置++和后置++函数,你需要注意两点:
-
函数参数列表:前置++和后置++函数的参数列表应该是不同的,以便编译器可以区分它们。
-
函数的返回类型:前置++函数应该返回引用,而后置++函数应该返回值。
下面是一个示例,展示了如何重载前置++和后置++运算符:
#include <iostream>class Counter {
public:Counter() : count(0) {}// 前置++运算符重载Counter& operator++() {++count;return *this;}// 后置++运算符重载Counter operator++(int) {Counter temp = *this;++(*this);return temp;}void display() {std::cout << "Count: " << count << std::endl;}
private:int count;
};int main() {Counter c1, c2;// 前置++++c1;c1.display(); // 输出: Count: 1// 后置++c2++;c2.display(); // 输出: Count: 1return 0;
}
在这个示例中,前置++运算符被重载为成员函数operator++()
,后置++运算符被重载为成员函数operator++(int)
。前置++函数返回引用,而后置++函数返回值。
注意:C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
只不过这个const
放的位置有点奇怪,因为在类的成员函数参数列表中this
指针已经是固定在第一位的,只是看不到,所以要这么做:
class Date
{
public:void Print() const{cout<<_year<<"-"<<_month<<"-"<<_day<<endl;}
private:int _year;int _month;int _day;
};
那么const
修饰后的对象调用函数,又与普通的对象调用函数有什么不一样呢?
来看下列代码:
#include<iostream>
using namespace std;class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022, 1, 13);d1.Print();const Date d2(2022, 1, 13);d2.Print();
}int main()
{Test();return 0;
}
运行结果:
很明显,d1
不能调用void Print() const
,d2
不能调用void Print()
问题又来了:
- const对象内可以调用其它的非const成员函数吗?
- 非const对象内可以调用其它的const成员函数吗?
总结:
-
const 对象可以调用非 const 成员函数吗?
- 是的,const 对象可以调用非 const 成员函数。因为 const 对象的调用不会改变对象的状态,只是限制了对成员变量的修改,所以它可以安全地调用非 const 成员函数。
-
非 const 对象可以调用 const 成员函数吗?
- 是的,非 const 对象可以调用 const 成员函数。因为非 const 对象的调用不会限制对成员变量的修改,所以它可以安全地调用 const 成员函数。
-
const 成员函数内可以调用其他的非 const 成员函数吗?
- 是的,const 成员函数内可以调用其他的非 const 成员函数。因为 const 成员函数承诺不会修改对象的状态,但它可以通过调用其他非 const 成员函数来实现某些功能。
-
非 const 成员函数内可以调用其他的 const 成员函数吗?
- 是的,非 const 成员函数内可以调用其他的 const 成员函数。因为非 const 成员函数没有限制对成员变量的修改,所以它可以安全地调用 const 成员函数。
总的来说,const 成员函数提供了对象只读的访问权限,并不限制调用其他成员函数的类型;而非 const 成员函数可以调用任何类型的成员函数。
在这个示例中,callNonConstFromConst()
函数是一个const成员函数,它调用了非const成员函数nonConstFunc()
,这是允许的。但是,如果你尝试在非const成员函数中调用const成员函数(例如callConstFromNonConst()
),编译器将会报错。
简单理解就是:权限问题,const
给非const
,权限放大,是肯定不行的;反过来权限缩小,是可以的。
好啦,这篇文章就到此结束了
所以你学会了吗?
好啦,本章对于《C++:运算符重载和“const”成员》的学习就先到这里,如果有什么问题,还请指教指教,希望本篇文章能够对你有所帮助,我们下一篇见!!!
如你喜欢,点点赞就是对我的支持,感谢感谢!!!