上海网站关键排名,网站建设 上海网,东莞网页设计与制作公司,企业网站建设指标运算符重载
本章思维导图#xff1a; 注#xff1a;本章思维导图对应的xmind文件和.png文件都已同步导入至”资源“ 文章目录 运算符重载[toc] 1. 运算符重载的意义2. 函数的声明2.1 声明运算符重载的注意事项 3. 函数的调用4. const成员函数4.1 const成员函数的声明4.2 注意…运算符重载
本章思维导图 注本章思维导图对应的xmind文件和.png文件都已同步导入至”资源“ 文章目录 运算符重载[toc] 1. 运算符重载的意义2. 函数的声明2.1 声明运算符重载的注意事项 3. 函数的调用4. const成员函数4.1 const成员函数的声明4.2 注意点 5. 两个默认成员函数5.1 赋值运算符重载5.1.1 函数的声明5.1.2 函数的定义5.1.3 赋值运算符重载和拷贝构造的区别5.1.4 默认赋值运算符重载 5.2 和const 运算符重载 6. 流插入、流提取运算符重载6.1 函数的声明 7. Date类的实现7.1 对于部分代码的说明
1. 运算符重载的意义
我们都知道对于内置类型我们是可以直接用运算符直接对其进行操作的但是对于自定义类型这种做法是不被允许的。
例如对于Date类
Date d1;
Date d2(2023, 11, 3);d1 d2;
//会报错error C2676: 二进制“”:“Date”不定义该运算符或到预定义运算符可接收的类型的转换因此为了解决自定义类型不能使用操作符的问题C就有了运算符重载
2. 函数的声明 一般来说运算符重载的函数最好声明在类里面这样就可以使用被private修饰的成员变量
声明方式 operator 运算符 形参列表 函数的返回值应该和运算符的意义相对应。例如对于比较运算符、就应该返回bool值对于、-就应该返回当前类类型函数的形参个数应该和运算符的操作数相对应 例如我们要声明定义一个Date 整数的函数那我们就要重载运算符
//根据运算符的意义其返回值应该是操作数本身因此返回其引用
Date operator (int day)
{//仅作为运算符重载如何声明定义的演示//实现细节先不做说明return *this;
}2.1 声明运算符重载的注意事项
有小伙伴可能注意到为什么Date operator (int day)的形参只有一个day不是说形参个数要和运算符的操作数对应吗
我们不能忘记在类的成员函数中都有一个默认的形参this指针来指向当前的对象。
同时还应该注意以下几点 不能通过连接其他符号来实现重载例如operator ()是不被允许的不能修改内置类型运算符的意义.* . :? sizeof ::这五个运算符是不可以重载的对于二元运算符操作数为2的运算符形参列表的第一个参数为左操作数第二个参数为右操作数。因此对于声明在类里面的重载函数this指针永远是左操作数 3. 函数的调用 在声明定义好运算符重载后有两种调用方法
第一种——像内置类型一样直接使用运算符
d1 10;
//调用Date operator (int day)函数在对象d1的基础上加10第二种——像调用函数一样使用运算符重载
d1.operator(10);
//调用Date operator (int day)函数在对象d1的基础上加104. const成员函数 我们来看下面的代码
class Date
{
public:Date(int year 2023, int month 11, int day 3){//简单的值拷贝_year d._year;_month d._month;_day d._day;}void Print(){cout _year ; _month _day endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;const Date d2;d1.Print(); //Yesd2.Print(); //Notreturn 0;
}
//d2.Print()会报错不能将“this”指针从“const Date”转换为“Date ”这是因为 我们之前提到过引用不能涉及到权限的放大const Date d2对应的是const Date* this而函数Print里的默认的是Date* this这就涉及到了权限的放大。 因此为了解决被const修饰的对像不能调用成员函数的问题我们可以用const来修饰成员函数这样本质上成员函数的this指针就被const修饰了从而也就可以被const对象调用。
4.1 const成员函数的声明 将一个成员函数变为const成员函数只要在该函数声明的最后加上const修饰符即可 例如对于上面提到的Pinrt()成员函数将其变为const成员函数即
void Print() const
{cout _year ; _month _day endl;
}4.2 注意点
应该知道引用不能涉及权限的放大但是可以进行权限的平移和缩小。
因此
const对象可以调用const成员函数非const对象也可以调用const成员函数
但并不能说可以将所有的成员函数都声明为const成员函数因为对于有些成员函数它需要在函数内部修改对象的成员变量。
但是对于那些不需要修改对象成员变量的成员函数建议都将其声明为cosnt这样const对象和非const对象都能调用
5. 两个默认成员函数
运算符重载中也有两个成员函数分别是赋值运算符重载和取地址\const 运算符重载
5.1 赋值运算符重载 赋值运算符重载是类的默认成员函数 5.1.1 函数的声明
因为赋值运算符重载是类的默认成员函数因此必须声明在类的里面运算符的操作对象为2因此该函数有两个形参。第一个形参为被隐藏的this指针第二个形参就是要赋予值的对象且该对象最好被const修饰为了支持连续赋值函数的返回值应该是类类型的引用
例如声明一个Date类的赋值运算符重载
Date operator(const Date d)
{//
}5.1.2 函数的定义
应该清楚赋值实际上也是一种拷贝。而拷贝又分为深拷贝和浅拷贝 浅拷贝 浅拷贝又称值拷贝浅拷贝只是对成员变量值的简单复制而不是复制指向的动态分配的资源如堆内存原对象和拷贝对象将共享相同的资源。 深拷贝 深拷贝又称址拷贝相较于浅拷贝只是对成员变量值的简单赋值深拷贝会复制对象的成员变量以及指向的资源包括指针指向的数据这确保了原对象和拷贝对象拥有彼此独立但内容相同的资源副本。 关于深浅拷贝更为清楚的解释请移步C——拷贝构造 既然拷贝分为深浅拷贝那么我们的赋值运算符重载也应该分为值拷贝、址拷贝这两种情况 值拷贝 例如对于Date类里面没有指针指向空间资源因此只需要对其成员变量进行简单复制操作 Date operator(const Date d)
{//简单的值拷贝_year d._year;_month d._month;_day d._day;return *this;
}址拷贝 例如对于Stack类里面有一个指针用于指向栈存储的空间因此需要进行深拷贝来创建一个和源对象内容相同但地址不同的空间 Stack operator(const Stack st)
{_capacity st._capacity;_top st._top;//深拷贝_a (int*)malloc(sizeof(int) * _capacity);if (nullptr _a){perror(malloc);exit(-1);}memcpy(_a, st._a, sizeof(int) * _capacity);return *this;
}5.1.3 赋值运算符重载和拷贝构造的区别
看完上面函数的定义有小伙伴就肯定会有疑惑了
这看起来二者之间没有什么区别嘛为什么要做区分
我们来看下面的例子
class Date
{
public://构造Date(int year 2023, int month 11, int day 3){_year year;_month month;_day day;}//拷贝构造Date(const Date d){_year d._year;_month d._month;_day d._day;}//赋值运算符重载Date operator(const Date d){_year d._year;_month d._month;_day d._day;return *this;}
private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2 d1; //拷贝构造还是赋值运算符重载Date d3;d3 d2; //拷贝构造还是赋值运算符重载return 0;
}各位认为上面问题的答案是什么让我们来进行调试 可以得出结论赋值运算符重载和拷贝构造的不同之处在于 拷贝构造注重的是构造即用一个已经存在的对象来实例化构造一个新的对象赋值运算符重载注重的是赋值即对两个已经存在的对象进行赋值操作 5.1.4 默认赋值运算符重载
和其他默认成员函数一样如果自己没有声明和定义赋值运算符重载编译器会自动生成一个
和拷贝构造一样默认运算符重载只会对成员变量进行简单的值拷贝浅拷贝因此如果类的成员变量有指向内存空间的指针仅依赖系统默认生成的赋值运算符重载是不够的需要自己声明定义来实现深拷贝
5.2 和const 运算符重载 这两个函数都是类的默认成员函数 他们的作用仅用于返回对象的地址 Date* operator()
{return this;
}const Date* operator() const
{return this;
}这两个默认成员函数一般用编译器自动生成的默认运算符重载即可不需要自己声明和定义
6. 流插入、流提取运算符重载 通过查阅资料我们可以得知cout是ostream类的对象cin是istream类的对象
extern ostream cout;
extern istream cin;6.1 函数的声明
我们可以先看一个例子来推出这两个重载函数的返回值是什么 cout 10 20;这串代码实现的是向屏幕连续输出两个数字10和20运算符的运算顺序应该是从左到右而为了能够实现连续的cout我们可以假象在执行完cout 10后有生成了一个cout来完成cout 20 而为了能够实现执行完cout 10后又生成一个coutcout运算符重载的返回值就应该是ostream类的引用即ostream
同理cin运算符重载的返回值就应该是istream
接下来的问题是这两个运算符重载应该放在类里面还是类外面呢 如果声明在类里面 class Date
{
public://构造Date(int year 2023, int month 11, int day 3){_year year;_month month;_day day;}ostream operator(ostream _cout){_cout _year _month _day endl;return _cout;}
private:int _year;int _month;int _day;
};那当我们使用时 int main()
{Date d1;cout d1;return 0;
}
//就会报错二元“”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)这是因为我们在前面就说过对于二元运算符第一个形参就是左操作数第二个形参就是右操作数。而对于类的成员函数this指针就是第一个参数即左操作数。因此对于流插入运算符左操作数就是d1右操作数就是_cout。 正确的写法应该为 d1 cout;显然这种写法的可读性是极差的。我们**应该想办法使_cout成为左操作数this指针成为右操作数**但是对于类的成员函数形参的第一个都必定是指向对象的this指针所以为了达到要求我们应该将和运算符的重载声明定义在类的外面 即 class Date
{
public://构造Date(int year 2023, int month 11, int day 3){_year year;_month month;_day day;}private:int _year;int _month;int _day;
};ostream operator(ostream _cout, const Date d)
{_cout d._year d._month d._day endl;return _cout;
}我们将和运算符的重载声明在类外面可读性的问题是解决了但是这样就访问不了被private修饰的成员变量了呀。
为了解决这个问题我们可以使用友元函数friend 将非类成员函数的函数声明放在类里面再在声明前面加上修饰符friend这样这个函数就变成了友元函数。 友元函数不是类的成员函数但是可以直接访问类的私有成员 例如对于上面的代码
class Date
{
public://构造Date(int year 2023, int month 11, int day 3){_year year;_month month;_day day;}friend ostream operator(ostream _cout, const Date d);private:int _year;int _month;int _day;
};ostream operator(ostream _cout, const Date d)
{_cout d._year d._month d._day endl;return _cout;
}这样我们就可以正确使用和运算符重载了。
7. Date类的实现
class Date
{
public:// 获取某年某月的天数int GetMonthDay(int year, int month){int nums[13] { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (month 2 (year % 400 0 || (year % 4 0 year % 100 ! 0)))return nums[month] 1;elsereturn nums[month];}// 全缺省的构造函数Date(int year 1900, int month 1, int day 1){if (year 1 || month 1 || month 12 || day 1 || day GetMonthDay(year, month)){cout 输入非法 endl;exit(-1);}_year year;_month month;_day day;}// 拷贝构造函数Date(const Date d){_year d._year;_month d._month;_day d._day;}// 赋值运算符重载Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}// 日期天数Date operator(int day){if (day 0){return *this - (-day);}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 12){_month 1;_year;}}return *this;}// 日期天数Date operator(int day){Date tmp *this;tmp day;return tmp;}// 日期-天数Date operator-(int day){ Date tmp *this;tmp - day;return tmp;}// 日期-天数Date operator-(int day){if (day 0)return *this (-day);_day - day;while (_day 1){_month--;if (_month 0){_month 12;_year--;}_day GetMonthDay(_year, _month);}return *this;}// 前置Date operator(){*this 1;return *this;}// 后置Date operator(int){Date tmp *this;*this 1;return tmp;}// 后置--Date operator--(int){Date tmp *this;*this - 1;return tmp;}// 前置--Date operator--(){*this - 1;return *this;}// 运算符重载bool operator(const Date d){if (_year d._year)return true;else if (_year d._year _month d._month)return true;else if (_year d._year _month d._month _day d._day)return true;elsereturn false;}// 运算符重载bool operator(const Date d){return _year d._year _month d._month _day d._day;}// 运算符重载bool operator (const Date d){return (*this d) || (*this d);}// 运算符重载bool operator (const Date d){ return !(*this d);}// 运算符重载bool operator (const Date d){return (*this d) || (*this d);}// !运算符重载bool operator ! (const Date d){return !(*this d);}// 日期-日期 返回天数int operator-(const Date d){int flag 1;if (!(*this d))flag -1;Date cmp_big *this d ? *this : d;Date cmp_small *this d ? *this : d;int count 0;while (cmp_big ! cmp_small){count;cmp_small;}return flag * count;}// 析构函数~Date(){//cout ~Date() endl;}private:int _year;int _month;int _day;
};7.1 对于部分代码的说明 注意代码的复用这样可以少些许多代码提高效率。 例如定义好、-的运算符重载后-、运算符的重载就可以复用、-的代码定义好、的运算符重载后其他比较运算符就可以复用、的运算符重载来实现从而大幅减少了代码量。 注意前置和后置的运算符重载前置--和后置--同理 由于C规定要构成运算符重载运算符必须跟在operator后因此当碰到前置和后置这种运算符相同函数名相同但函数实现的功能不同的情况时就需要利用函数重载。我们可以在后置的运算符重载函数的形参列表里加入一个形参和前置构成函数重载这样就可以避免问题了。