运算符重载:
对于C++的运算符作用在用户自定义类型上确定其运算规则: 在自定义类型,定义对于该类型对象参与具体的运算的时候的运算操作模式,使得该类的对象不必要通过成员方法而直接使用运算符就可以按照设定的操作模式来进行运算
运算符重载是对已有的运算符赋予多重含义,同一个运算符作用于不同类型的数据导致不同类型的行为。运算符重载的实质就是函数重载。在实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型来确定需要调用的函数,这个过程是在编译过程中完成的。
运算符重载是一种在编译过程中的多态,所以称为静态多态或者编译时多态
C++中绝大多数运算符都可以重载,除了: 成员运算符., 域限定符::, 指针取值运算符*, 右箭头运算符->, 条件运算符?:, sizeof运算符
运算符重载的规则如下:
(1)C++中的运算符除了少数几个以外,全部可以重载,而且只能重载已有的这些运算符。
(2)重载之后运算符的优先级和结合性都不会改变。
(3)运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来讲,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。
运算符重载的实现方式: 重载为成员函数或者友元函数
重载为成员函数的时候,运算符的第一个操作数默认的(不需要体现在运算符重载函数中)是当前对象自己
重载为友元函数的时候,运算符的操作数的类型以及操作数的顺序都由友元函数的形参列表来确定
运算符重载的格式:
函数类型 operator 运算符(形参列表)
{
函数体
}
函数类型: 运算符运算结果的类型
形参列表: 指定了运算符的所有操作数(如果是成员函数,第一个操作数是当前对象自己,不要出现在形参列表中)以及操作数之间的顺序 -- 运算符的第一个操作数就是第一个形参,第二个操作数就是第二个形参
函数体: 指定当前类型的对象参与运算的时候的运算规则
运算符重载的实现:
a. 单目运算符: ++/--
#include <iostream>
using namespace std;
int main()
{
int a = 9;
int b = 8;
++a;
a++;
//a++ = b++; //9 = b++, a = 9 + 1;
++a = b++;
++a = ++b;
}
注意: 前缀运算符参加运算的是当前对象自己,而后缀运算符参加运算的是值等于当前变量的一个临时对象(以对象的值参加运算的)
运算符重载的形式类似如下:
Type operator ++()
{
}
#include <iostream>
using namespace std;
class Date
{
private:
int m_nDay;
int m_nMonth;
int m_nYear;
private:
void add_Days(int days) //在一个日期上加上指定的天数
{
m_nDay += days;
if(m_nDay > 30)
{
add_Months(m_nDay / 30);
m_nDay %= 30;
}
}
void add_Months(int months)
{
m_nMonth += months;
if(m_nMonth > 12)
{
add_Years(m_nMonth / 12);
m_nMonth %= 12;
}
}
void add_Years(int years)
{
m_nYear += years;
}
public:
Date() : m_nDay(1), m_nMonth(1), m_nYear(1970)
{
}
Date(int yy, int mm, int dd) : m_nDay(dd), m_nMonth(mm), m_nYear(yy)
{
}
//单目运算符重载 -- 前缀运算符
//前缀运算符的运算规则是先自增然后再以当前对象参加运算,所以前缀运算符的结果是当前对象自己
//如果将前缀运算符重载函数实现为成员方法的时候,函数的形参为当前对象自己(使用默认的第一个操作数)
Date &operator ++()
{
add_Days(1);
return *this;
}
void show_Date()
{
cout << m_nYear << " - " << m_nMonth << " - " << m_nDay << endl;
}
};
int main()
{
Date today(2023, 8, 4);
today.show_Date();
++today;
today.show_Date();
return 0;
}
后缀:
#include <iostream>
using namespace std;
class Date
{
private:
int m_nDay;
int m_nMonth;
int m_nYear;
private:
void add_Days(int days) //在一个日期上加上指定的天数
{
m_nDay += days;
if(m_nDay > 30)
{
add_Months(m_nDay / 30);
m_nDay %= 30;
}
}
void add_Months(int months)
{
m_nMonth += months;
if(m_nMonth > 12)
{
add_Years(m_nMonth / 12);
m_nMonth %= 12;
}
}
void add_Years(int years)
{
m_nYear += years;
}
public:
Date() : m_nDay(1), m_nMonth(1), m_nYear(1970)
{
}
Date(int yy, int mm, int dd) : m_nDay(dd), m_nMonth(mm), m_nYear(yy)
{
}
//单目运算符重载 -- 前缀运算符
//前缀运算符的运算规则是先自增然后再以当前对象参加运算,所以前缀运算符的结果是当前对象自己
//如果将前缀运算符重载函数实现为成员方法的时候,函数的形参为当前对象自己(使用默认的第一个操作数)
Date &operator ++() //前缀运算符必须返回引用
{
add_Days(1);
return *this;
}
//后缀运算符: 后缀运算是以当前对象的值参与运算,然后当前对象自己自增;所以后缀运算符的返回值是当前对象的值;对当前对象自己的影响是值会增加1
//后缀运算符和前缀运算符不能是一个版本,所以后缀运算符增加一个整数操作数
Date operator ++(int)
{
//需要创建一个用于返回的对象
Date tmp(m_nYear, m_nMonth, m_nDay); //Date tmp = *this;
//对于当前对象,需要自增1
add_Days(1);
return tmp; //返回的是自增之前的对象
}
void show_Date()
{
cout << m_nYear << " - " << m_nMonth << " - " << m_nDay << endl;
}
};
int main()
{
Date today(2023, 8, 4);
today.show_Date();
++today;
today.show_Date();
//用成员函数调用的方式进行运算
today.operator++();
today.show_Date();
Date t = today++;
today.show_Date();
t.show_Date();
Date theday(2023, 2, 2);
//today.operator++() = theday.operator++(2);
++theday = today++;
today.show_Date();
theday.show_Date();
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_06 运算符重载> g++ .\运算符重载.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_06 运算符重载> ./test
2023 - 8 - 4
2023 - 8 - 5
2023 - 8 - 6
2023 - 8 - 7
2023 - 8 - 6
2023 - 8 - 8
2023 - 8 - 7
注意: 一般情况下,单目运算符都会实现为类的成员函数
b. 双目运算符:双目运算符有两个操作数,如果重载为成员函数,运算符的第一个操作数一定是当前类型的对象自己;如果重载为友元函数,第一个操作数可以是其他类型
1) 实现两个日期相减,返回这两个日期之间的天数
//两个日期相减得到天数
int operator -(Date &theDay)
{
int n1 = m_nYear * 360 + (m_nMonth - 1) * 30 + m_nDay;
int n2 = theDay.m_nYear * 360 + (theDay.m_nMonth - 1) * 30 + theDay.m_nDay;
return abs(n1 - n2);
}
2) 一个日期加上一个天数,得到一个新的日期
//一个日期加上一个天数,实现为成员函数
Date operator + (int days)
{
Date t(m_nYear, m_nMonth, m_nDay);
t.add_Days(days);
return t;
}
//实现为友元函数
//将运算符重载为友元函数
friend Date operator + (int days, Date &theDay);
Date operator + (int days, Date &theDay)
{
Date t = theDay;
t.add_Days(days);
return t;
}
3) 赋值运算符: 类中会默认的实现以赋值运算符重载函数,实现方式和类中默认的拷贝构造函数是一样的(赋值运算符是将右值对象的数据成员依次赋值给当前对象的数据成员)
Date &operator = (Date &theDay)
{
m_nDay = theDay.m_nDay;
m_nMonth = theDay.m_nMonth;
m_nYear = theDay.m_nYear;
return *this;
}
如果类中有数据成员是指针类型并且在构造函数中动态分配了空间,这时必须实现赋值运算符重载函数
Student &operator = (Student &t)
{
s_no = t.s_no;
s_sc = t.s_sc;
//指针成员
delete[] s_name; //先释放原来的空间
s_name = new char[strlen(t.s_name) + 1]; //再按照需要的空间进行分配
strcpy(s_name, t.s_name); //再将数据进行复制
return *this;
}
4) 关系运算符: 用来指定两个对象的比较方式
实例代码:
#include <iostream>
using namespace std;
class Date
{
private:
int m_nDay;
int m_nMonth;
int m_nYear;
private:
void add_Days(int days) //在一个日期上加上指定的天数
{
m_nDay += days;
if(m_nDay > 30)
{
add_Months(m_nDay / 30);
m_nDay %= 30;
}
}
void add_Months(int months)
{
m_nMonth += months;
if(m_nMonth > 12)
{
add_Years(m_nMonth / 12);
m_nMonth %= 12;
}
}
void add_Years(int years)
{
m_nYear += years;
}
public:
Date() : m_nDay(1), m_nMonth(1), m_nYear(1970)
{
}
Date(int yy, int mm, int dd) : m_nDay(dd), m_nMonth(mm), m_nYear(yy)
{
}
//单目运算符重载 -- 前缀运算符
//前缀运算符的运算规则是先自增然后再以当前对象参加运算,所以前缀运算符的结果是当前对象自己
//如果将前缀运算符重载函数实现为成员方法的时候,函数的形参为当前对象自己(使用默认的第一个操作数)
Date &operator ++() //前缀运算符必须返回引用
{
add_Days(1);
return *this;
}
//后缀运算符: 后缀运算是以当前对象的值参与运算,然后当前对象自己自增;所以后缀运算符的返回值是当前对象的值;对当前对象自己的影响是值会增加1
//后缀运算符和前缀运算符不能是一个版本,所以后缀运算符增加一个整数操作数
Date operator ++(int)
{
//需要创建一个用于返回的对象
Date tmp(m_nYear, m_nMonth, m_nDay); //Date tmp = *this;
//对于当前对象,需要自增1
add_Days(1);
return tmp; //返回的是自增之前的对象
}
//两个日期相减得到天数
int operator -(Date &theDay)
{
int n1 = m_nYear * 360 + (m_nMonth - 1) * 30 + m_nDay;
int n2 = theDay.m_nYear * 360 + (theDay.m_nMonth - 1) * 30 + theDay.m_nDay;
return abs(n1 - n2);
}
//一个日期加上一个天数
Date operator + (int days)
{
Date t(m_nYear, m_nMonth, m_nDay);
t.add_Days(days);
return t;
}
Date &operator = (Date theDay)
{
m_nDay = theDay.m_nDay;
m_nMonth = theDay.m_nMonth;
m_nYear = theDay.m_nYear;
return *this;
}
void show_Date()
{
cout << m_nYear << " - " << m_nMonth << " - " << m_nDay << endl;
}
//将运算符重载为友元函数
friend Date operator + (int days, Date &theDay);
friend bool operator < (Date &day1, Date &day2);
};
Date operator + (int days, Date &theDay)
{
Date t = theDay;
t.add_Days(days);
return t;
}
//关系运算符重载函数用来指定两个对象进行比较的时候的依据
bool operator < (Date &day1, Date &day2)
{
int n1 = day1.m_nYear * 360 + (day1.m_nMonth - 1) * 30 + day1.m_nDay;
int n2 = day2.m_nYear * 360 + (day2.m_nMonth - 1) * 30 + day2.m_nDay;
return n1 < n2;
}
int main()
{
Date today(2023, 8, 4);
Date theday(2023, 9, 15);
cout << today - theday << endl;
theday = today + 100;
theday.show_Date();
theday = 100 + today;
theday.show_Date();
if(today < theday)
{
cout << "早" << endl;
}
/*
today.show_Date();
++today;
today.show_Date();
//用成员函数调用的方式进行运算
today.operator++();
today.show_Date();
Date t = today++;
today.show_Date();
t.show_Date();
Date theday(2023, 2, 2);
//today.operator++() = theday.operator++(2);
++theday = today++;
today.show_Date();
theday.show_Date();
*/
return 0;
}
作业:
1. 定义一个学生类,重载赋值运算符和关系运算符,定义一个对象数组,使用运算符进行排序
完成学生管理系统,要求能按学号降序排序、按姓名升序排序(姓名相同按学号升序)、按成绩升序排序。
2. 设计一个点类Point,实现点对象之间的各种运算。
完成:提供对点的偏移(整形),偏移量用Point类对象表示,判断两个对象是否相同,判断两个对象是否不相同,将两个点对象相加,将两个点对象相减功能;
3. 设计一个三角形类Triangle,包含三角形三条边长的私有数据成员,另有一个重载运算符“+”,以实现求两个三角形对象的面积之和。
4.习题3的重载运算符“+”友元函数只能返回两个三角形的面积之和,不能计算三个三角形的面积之和,改进一下,使之能计算任意多个三角形的面积之和。
5. 定义一个时间类用来表示某个具体的时间(时间用时分秒表示),在时间类中定义以下函数:
Time operator+(Time); 返回一时间加上另一时间得到的新时间
Time operator-(Time); 返回一时间减去另一时间得到的新时间
编写测试代码验证运算符重载的正确性