运算符重载(Operator Overloading)是指在C++等编程语言中,允许程序员自定义或重定义运算符的行为。通过运算符重载,我们可以为类的成员函数赋予特定的运算符意义,以实现更加直观和易于理解的代码。
接上一章节第九章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人 – 菜鸟资源 (xiciw.com)继续讲解
运算符重载的主要应用场景包括:
- 自定义数据类型:当我们定义自己的数据类型(如类)时,可能希望这些类型支持常见的运算符,如加法、减法、乘法等。通过运算符重载,我们可以为这些自定义类型提供自然的运算行为。
- 复合赋值运算符:在C++中,我们可以通过重载复合赋值运算符(如+=、-=、*=等)来简化代码。例如,如果我们有一个类代表向量,我们可以重载+=运算符来方便地实现向量的加法。
- 输入/输出运算符:在处理用户输入和输出时,我们可以重载<<和>>运算符,以便更方便地使用这些操作符来输入和输出自定义类型的数据。
运算符重载的规则包括:
- 只能重载C++中已经存在的运算符。
- 不能改变运算符的优先级和结合性。
- 运算符重载应该具有意义。也就是说,重载后的运算符应该能对程序中的数据类型产生有意义的操作。
- 不能重载C++中的关键字,如new、delete、sizeof等。
- 不能改变运算符的参数个数。
- 不能改变运算符的语法结构。例如,不能把“+”(加法)变成“+=”(加等于)。
通过合理地使用运算符重载,我们可以编写更加简洁、易读和直观的代码,提高代码的可读性和可维护性。
6. 运算符重载(Operator OverLoad)
运算符重载的本质是函数重载。
6.1. 重载入门
6.1.1. 语法格式
重载函数的一般格式如下:
返值类型 operator 运算符名称(形参表列)
{
重载实体;
}
operator 运算符名称 在一起构成了新的函数名。比如
const Complex operator+(const Complex &c1,const Complex &c2);
我们会说,operator+ 重载了重载了运算符+。
6.1.2. 友元重载
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
friend const Complex operator+(const Complex &c1,const Complex &c
2);
private:
float _x;
float _y;
};
const Complex operator+(const Complex &c1,const Complex &c2)
{
return Complex(c1._x + c2._x,c1._y + c2._y);
}
int main()
{
Complex c1(2,3);
Complex c2(3,4);
c1.dis();
c2.dis();
// Complex c3 = c1+c2;
Complex c3 = operator+(c1,c2);
c3.dis();
return 0;
}
6.1.3. 成员重载
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
friend const Complex operator+(const Complex &c1,const Complex &c
2);
const Complex operator+(const Complex &another);
private:
float _x;
float _y;
};
const Complex operator+(const Complex &c1,const Complex &c2)
{
cout<<"友元函数重载"<<endl;
return Complex(c1._x + c2._x,c1._y + c2._y);
}
const Complex Complex::operator+(const Complex & another)
{
cout<<"成员函数重载"<<endl;
return Complex(this->_x + another._x,this->_y + another._y);
}
int main()
{
Complex c1(2,3);
Complex c2(3,4);
c1.dis();
c2.dis();
// Complex c3 = c1+c2;
// Complex c3 = operator+(c1,c2);
Complex c3 = c1+c2; //优先调用成员函数重载??
c3.dis();
return 0;
}
备注:
关于返回值:
int a = 3;
int b = 4;
(a+b) = 100; 这种语法是错的,所以重载函数的返回值必须是 const
关于重载全局和成员
6.1.4. 重载规则
(1)C++不允许用户自己定义新的运算符,只能对已有的 C++运算符进行重载。
例如,有人觉得 BASIC 中用“* *”作为幂运算符很方便,也想在 C++中将“* *”定义为幂运算符,用“3* *5”表示 3^5,这是不行的。
(2)C++允许重载的运算符
C++中绝大部分运算符都是可以被重载的。
不能重载的运算符只有 4 个:72
前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符合 sizeof 运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征。
(3)重载不能改变运算符运算对象(即操作数)的个数。
如,关系运算符“>”和“<”等是双目运算符,重载后仍为双目运算符,需要两个参数。运算符”+“,”-“,”*“,”&“等既可以作为单目运算符,也可以作为双目运算符,可以分别将它们重载为单目运算符或双目运算符。
(4)重载不能改变运算符的优先级别。
例如”*“和”/“优先级高于”+“和”-“,不论怎样进行重载,各运算符之间的优先级不会改变。有时在程序中希望改变某运算符的优先级,也只能使用加括号的方法强制改变重载运算符的运算顺序。
(5)重载不能改变运算符的结合性。
如,复制运算符”=“是右结合性(自右至左),重载后仍为右结合性。
(6)重载运算符的函数不能有默认的参数
否则就改变了运算符参数的个数,与前面第(3)点矛盾。
(7)重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。
也就是说,参数不能全部是 C++的标准类型,以防止用户修改用于标准类型数据成员的运算符的性质,如下面这样是不对的:
复制代码 代码如下:
int operator + (int a,int b)
{
return(a-b);
}
原来运算符+的作用是对两个数相加,现在企图通过重载使它的作用改为两个数相减。如果允许这样重载的话,如果有表达式 4+3,它的结果是 7 还是 1 呢?显然,这是绝对要禁止的。
(8)用于类对象的运算符一般必须重载,但有两个例外,运算符”=“和运算符”&“不必用户重载。
复制运算符”=“可以用于每一个类对象,可以用它在同类对象之间相互赋值。因为系统已为每一个新声明的类重载了一个赋值运算符,它的作用是逐个复制类中的数据成员地址运算符&也不必重载,它能返回类对象在内存中的起始地址。
(9)应当使重载运算符的功能类似于该运算符作用于标准类型数据时候时所实现的功 能。
例如,我们会去重载”+“以实现对象的相加,而不会去重载”+“以实现对象相减的功能,因为这样不符合我们对”+“原来的认知。
(10)运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数
6.2. 重载例举
6.2.1. 双目运算符例举
形式
L#R
全局函数
operator#(L,R);
成员函数
L.operator#(R)
operator+=
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
Complex& operator+=(const Complex &c)
{
this->_x += c._x;
this->_y += c._y;
return * this;
}
private:
float _x;
float _y;
};
int main()
{
// int a = 10, b = 20,c = 30;
// a += b;
// b += c;
// cout<<"a = "<<a<<endl;
// cout<<"b = "<<b<<endl;
// cout<<"c = "<<c<<endl;
// Complex a1(10,0),b1(20,0), c1(30,0);
// //(1)此时的+=重载函数返回 void
// a1 += b1;
// b1 += c1;
// a1.dis();
// b1.dis();
// c1.dis();
//----------------------------------------------------
// int a = 10, b = 20,c = 30;
// a += b += c;
// cout<<"a = "<<a<<endl;
// cout<<"b = "<<b<<endl;
// cout<<"c = "<<c<<endl;
// Complex a1(10,0),b1(20,0), c1(30,0);
// //(2)此时重载函数+=返回的是 Complex
// a1 += b1 += c1;
// a1.dis();
// b1.dis();
// c1.dis();
//----------------------------------------------------
int a = 10, b = 20,c = 30;
(a += b) += c;
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
Complex a1(10,0),b1(20,0), c1(30,0);
// //(3)此时重载函数+=返回的是 Complex &
// 一定要注意在连等式中,返回引用和返回对象的区别
(a1 += b1) += c1;
a1.dis();
b1.dis();
c1.dis();
retur
operator-=
friend Complex& operator-=(Complex &c1, const Complex & c2)
{
}
6.2.2. 单目运算符例举
形式
#M 或 M#
全局函数75
operator#(M)
成员函数
M.operator#()
operator-
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
const Complex operator-(void) const
{
return Complex(-_x,-_y);
}
private:
float _x;
float _y;
};
int main()
{
int n = 10;
cout<<n<<endl;
cout<<-n<<endl;
cout<<n<<endl;
cout<<-(-n)<<endl;
// -n = 100;
Complex c(1,2);
Complex c2 = -c;
c2 = -(-c);
-c = Complex(3,4); //编译通不过
c.dis();
c2.dis();
c.dis();
return 0;
}
operator++ 前加加
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
// Complex & operator++(void)
// {
// _x++;
// _y++;
// return *this;
// }
friend Complex & operator++(Complex& c);
private:
float _x;
float _y;
};
Complex & operator++(Complex& c)
{
c._x++;
c._y++;
return c;
}
int main()
{
int n = 10;
cout<<n<<endl; //10
cout<<++n<<endl; //11
cout<<n<<endl; //11
cout<<++++n<<endl; //13
cout<<n<<endl; //13
Complex c(10,10);
c.dis(); //10 10
Complex c2=++c;
c2.dis(); //11 11
c.dis(); //11 11
c2 = ++++c;
c2.dis(); //13 13
c.dis(); //13 13
return 0;
}
operator++ 后加加
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
// const Complex operator++(int) //哑元
// {
// Complex t = *this;
// _x++;
// _y++;
// return t;
// }
friend const Complex operator++(Complex &c,int);
private:
float _x;
float _y;
};
const Complex operator++(Complex &c,int)
{
Complex t(c._x,c._y);
c._x++;
c._y++;
return t;
}
int main()
{
int n = 10;
cout<<n<<endl; //10
cout<<n++<<endl; //10
cout<<n<<endl; //11
// cout<<n++++<<endl; //13 后++表达式不能连用
cout<<n<<endl; //11
Complex c(10);
c.dis();;
Complex c2 = c++;
c2.dis();
c.dis();;
// c2 = c++++;
// c2.dis();
c.dis();
return 0;
}
6.2.3. 流输入输出运算符重载
函数形式
istream & operator>>(istream &,自定义类&);
ostream & operator<<(ostream &,自定义类&)
通过友元来实现,避免修改 c++的标准库。
operator<< vs operator>>
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
friend ostream & operator<<(ostream &os, const Complex & c);
friend istream & operator>>(istream &is, Complex &c);
private:
float _x;
float _y;
};
ostream & operator<<(ostream &os, const Complex & c)
{
os<<"("<<c._x<<","<<c._y<<")";
return os;
}
istream & operator>>(istream &is, Complex &c)
{
is>>c._x>>c._y;
return is;
}
int main()
{
Complex c(2,3);
cout<<c<<endl;
cin>>c;
cout<<c<<endl;
return 0;
}
补充 MyString 类的流输入与输出
friend ostream & operator<<(ostream & os,const MyString &outStr);
friend istream & operator>>(istream &in, MyString & inStr);
ostream & operator<<(ostream & os,const MyString &outStr)
{
os<<outStr._str;
return os;
}
istream & operator>>(istream &in, MyString & inStr)
{
delete [] inStr._str;
char buf[1024];
scanf(“%s”,buf); //scanf fget sin.getline(buf,1024);
int len;
len = strlen(buf);
inStr._str = new char[len+1];
strcpy(inStr._str,buf);
return in;
}
6.3. 运算符重载小结
6.3.1. 不可重载的运算符
. (成员访问运算符)
.* (成员指针访问运算符)
:: (域运算符)
sizeof (长度运算符)
?: (条件运算符)
6.3.2. 只能重载为成员函数的运算符
= 赋值运算符
[] 下标运算符
() 函数运算符
-> 间接成员访问
6.3.3. 常规建议
6.3.4. 友元还是成员?
引例
假设,我们有类 Sender 类和 Mail 类,实现发送邮件的功能。
Sender sender; Mail mail;
sender<< mail;
sender 左操作数,决定了 operator<<为 Sender 的成员函数,而 mail 决定了 operator<<要作 Mail 类的友员。
#include <iostream>
using namespace std;
class Mail;
class Sender
{
public:
Sender(string s):_addr(s){}
Sender& operator<<(const Mail & mail); //成员
private:
string _addr;
};
class Mail
{
public:
Mail(string _t,string _c ):_title(_t),_content(_c){}
friend Sender& Sender::operator<<(const Mail & mail);
//友元
private:
string _title;
string _content;
};
Sender& Sender::operator<<(const Mail & mail)
{
cout<<"Address:"<<_addr<<endl;
cout<<"Title :"<<mail._title<<endl;
cout<<"Content:"<<mail._content<<endl;
return *this;
}
int main()
{
Sender sender("guilin_wang@163.com");
Mail mail("note","meeting at 3:00 pm");
Mail mail2("tour","One night in beijing");
sender<<mail<<mail2;
return 0;
}
结论:
1,一个操作符的左右操作数不一定是相同类型的对象,这就涉及到将该操作符函 数定义为谁的友元,谁的成员问题。
2,一个操作符函数,被声明为哪个类的成员,取决于该函数的调用对象(通常是左操作数)。
3,一个操作符函数,被声明为哪个类的友员,取决于该函数的参数对象(通常是右操作数)。
6.4. 类型转换
6.4.1. 标准类型间转换
隐式类型转换
5/8 5.0/8
显式类型转换
float(5)/8
对于上述两类,我们不再展开赘述,系统有章可循,而对于用户自定义的类型。编译
系统则不知道怎么进行转化。解决这个问题的办法是,自定义专门的函数。
如下文:
6.4.2. 用类型转换构造函数进行类型转换
实现其它类型到本类类型的转化。
转换构造函数格式
class 目标类
{
目标类(const 源类 & 源类对象引用){
根据需求完成从源类型到目标类型的转换
}
}
代码
#include <iostream>
using namespace std;
class Point3D;
class Point2D
{
public:
Point2D(int x,int y)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
friend Point3D;
private:
int _x;
int _y;
};
class Point3D
{
public:
Point3D(int x,int y,int z)
:_x(x),_y(y),_z(z){}
Point3D(Point2D &p)
{
this->_x = p._x;
this->_y = p._y;
this->_z = 0;
}
void dis()
{
cout<<"("<<_x<<","<<_y<<","<<_z<<")"<<endl;
}
private:
int _x;
int _y;
int _z;
};
void func(Point3D d3)
{
}
int main()
{
Point2D p2(1,2);
p2.dis();
Point3D p3(3,4,5);
p3.dis();
Point3D p3a = p2;
p3a.dis();
func(d2)
return 0;
}
explicit 关键字的意义
以显示的方式完成转化 static_cast<目标类> (源类对象)。否则会报错。
explicit Point3D(Point2D &p)
{
this->_x = p._x;
this->_y = p._y;
this->_z = 0;
}
Point2D p2(1,2);
p2.dis();
Point3D p3a = static_cast<Point3D> (p2);
p3a.dis();
6.4.3. 用类型转换操作符函数进行转换
类型转化函数格式
class 源类{
operator 转化目类型(void){
根据需求完成从源类型到目标类型的转换
}
}
代码
#include <iostream>
using namespace std;
class Point3D;
class Point2D
{
public:
Point2D(int x,int y)
:_x(x),_y(y){}
operator Point3D();
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
private:
int _x;
int _y;
};
class Point3D
{
public:
Point3D(int x,int y,int z)
:_x(x),_y(y),_z(z){}
void dis()
{
cout<<"("<<_x<<","<<_y<<","<<_z<<")"<<endl;
}
private:
int _x;
int _y;
int _z;
};
Point2D::operator Point3D()
{
return Point3D(_x,_y,0);
}
int main()
{
Point2D p2(1,2);
p2.dis();
Point3D p3(3,4,5);
p3.dis();
Point3D p3a = p2;
p3a.dis();
return 0;
}
小结
1,转化函数定义在源对象(待转化对象中),转化源的成员函数。
2,一旦为转换源类型提供了到目标类型的转化操作符函数,就可以将源类型对象以隐式转化的方式得的目标类型的对象。
3,应用于构造及初始化,赋值,传参,返回等等场合
6.4.4. 作业
实现如下功能:
Complex c (1,2);
Complex c2 = c + 2; //Complex(double real)
Complex c (1,2);
double a = 2 + c; //operator double()
本章完,下一章讲解运算符重载提高篇(菜鸟资源整理原创首发)感觉讲解的很好请支持本站