文章目录
8.重载运算符
简单的说,给原有的运算符赋予新功能,就是运算符重载
重载运算符的本质是函数重载,它是C++多态的一种体现,为用户提供了一个直观的接口,调用运算符操作自定义数据类型其实就是调用运算符函数。运算符重载增强了C++的可扩充性,使C++代码更加直观,易懂。
8.1重载运算符的定义
在C++中,我们可以重载或重定义大部分C++内置的操作符。这样,就可以自定义类型的运算符。
重载的运算符是带有特殊名称的函数,函数名是由关键字operator和其后要重载的运算符符号构成。与其他函数构成相同,重载运算符有一个返回类型和一个参数列表。
int operator+(const A &a);
实例:
#include <iostream>
using namespace std;
class A
{
public:
int operator+(const A& a) //重载操作符的定义
{
return m + a.m;
}
void set(int m_)
{
m = m_;
}
private:
int m;
};
int main()
{
A a1,a2;
a1.set(1);
a2.set(2);
cout << a1 + a2 << endl; //重载操作符的使用
return 0;
}
//输出:
//2
8.2可重载运算符/不可重载运算符
下面是可重载的运算符列表:
下面是不可重载的运算符列表:
- .:成员访问运算符
- . * , ->*:成员指针访问运算符
- :::域运算符
- sizeof:长度运算符
- ?::条件运算符
- #: 预处理符号
8.3运算符重载机制
重载的运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要重载的运算符共同组成。
语法结构:
返回类型 operator 运算符(参数列表)
{
函数体;
}
运算符重载规则:
- 只能重载C++已有的运算符,不能创建新的运算符。
- 重载之后的运算符不能改变其优先性和结合性,也不能改变其操作数的个数及语法结构。
- 避免没有目的的使用重载运算符。
8.4运算符重载规则(类成员函数、类友元函数)
重载为类的成员函数
重载为类的成员函数时,当函数无参时,编译器其实会自动添加一个this指针。
双目运算符
左操作数.运算符重载函数(右操作数);
定义二元运算符重载函数时,函数中只能定义一个形参。成员赋值操作符也只接受一个(显式的)形参。使用操作符时,一个指向左操作数的指针自动绑定到this,而右操作数限定为函数的唯一形参。
单目运算符
操作数.运算符重载函数();
重载为类的友元函数
语法格式:
friend 返回类型 operator 运算符(参数列表)
{
函数体;
}
返回类型为的类的对象的重载运算符
#include <iostream>
using namespace std;
class Box
{
public:
void set(int bb)
{
b = bb;
}
int get()
{
return b;
}
Box operator+(const Box& box) //重载运算符的定义
{
Box box_;
box_.b = this->b + box.b;
return box_; //返回类型为Box对象
}
private:
int b;
};
int main()
{
Box b1, b2, b3;
b1.set(1);
b2.set(2);
b3 = b1 + b2;
cout << b3.get() << endl;
return 0;
}
一元运算符重载
一元运算符只对一个操作数进行操作,下面是一元运算符的实例:
- 递增运算符(++)和递减运算符(–)
- 一元运算符,即负号(-)
- 逻辑非算符(!)
实例:
#include <iostream>
using namespace std;
class Data
{
public:
Data(int a):a(a)
{}
void set(int aa)
{
a = aa;
}
int get()
{
return a;
}
Data operator!()
{
a = !a;
return *this;
}
private:
int a;
};
int main()
{
Data data(1);
!data;
cout << "after !:" << data.get() << endl;
return 0;
}
-运算符的重载
注意:必须返回类的对象的引用,否则会出现错误
实例:
#include <iostream>
using namespace std;
class Data
{
public:
Data(int a) :a(a)
{}
void set(int aa)
{
a = aa;
}
int get()
{
return a;
}
//Data &operator-() //此处必须写上引用,否则会出现错误
//{
// a = -a;
// return *this;
//}
friend Data &operator-(Data &d) //此处同理,写引用
{
d.a = -d.a;
return d;
}
private:
int a;
};
int main()
{
Data data(1);
cout << data.get() <<endl;
-(-data); //若不写引用,此处会出现错误
cout << data.get() <<endl;
return 0;
}
++运算符的重载
首先注意几点:
- ++运算符有前缀和后缀,这两种用法不同,自然写法写不同
- ++前缀是得到自增后的值,同时自增,++后缀是得到自增前的值,再自增
实例:
#include <iostream>
using namespace std;
class Data
{
public:
Data(int a) :a(a)
{}
void set(int aa)
{
a = aa;
}
int get()
{
return a;
}
Data& operator++() //前缀++,此处加引用,因为得到的是自增后的值
{
a++;
return *this;
//return Data(a); //方法2
}
Data operator++(int) //后缀++,此处不加引用,因为得到的是自增前的值
{ //int标识作用,不传入任何值,即便是传入任何值也没有意义,因为我们不会去使用它
Data da(*this); //定义一个新的对象,将自增前的值保存
this->a++;
return da;
}
private:
int a;
};
int main()
{
Data data(1);
cout << data.get() << endl;
cout << (data++).get() << endl; //data.operator++(0); 系统默认传入0
cout << data.get() << endl;
return 0;
}
二元运算符
Coordinate operator+(Coordinate& c) //类的成员函数实现重载运算符+
{
Coordinate coor(0, 0);
coor.m_x = this->m_x + c.m_x;
coor.m_y = this->m_y + c.m_y;
return coor;
}
friend Coordinate operator+(Coordinate& a, Coordinate& b) //友元函数实现重载运算符+
{
Coordinate coor(0, 0);
coor.m_x = a.m_x + b.m_x;
coor.m_y = a.m_y + b.m_y;
return coor;
}
实例:
#include <iostream>
using namespace std;
class Data
{
public:
Data()
{}
Data(int a) :a(a)
{}
int get()
{
return a;
}
friend Data operator+(const Data& d1,const Data& d2) //重载符号+ 用于对象相加
{
Data da;
da.a = d1.a + d2.a;
return da;
}
friend Data operator+(const Data& d1,const int& a_) //重载符号+ 对象与数相加
{
Data da;
da.a = d1.a + a_;
return da;
}
friend Data operator+(const int& b, Data da);
private:
int a;
};
Data operator+(const int& b, Data da) //重载符号+ 数与对象相加
{
Data d;
d = da + b;
return d; //友元函数调用第二个重载+的成员函数,相当于 da.operator+(b)
}
int main()
{
Data d1(1), d2(2), d3;
int m = 6;
d3 = d1 + d2;
cout << "obj + obj:" << d3.get() << endl;
d3 = d1 + m;
cout << "obj + num:" << d3.get() << endl;
d3 = m + d1;
cout << "num + obj:" << d3.get() << endl;
return 0;
}
关系运算符重载
实例:
#include <iostream>
using namespace std;
class Data
{
public:
Data()
{}
Data(int a):a(a)
{}
int get()
{
return a;
}
bool operator<(const Data &da) //重载运算符 <
{
if (a < da.a)
return true;
else
return false;
}
private:
int a;
};
int main()
{
Data d1(1), d2(2);
if (d1 < d2)
cout << "d1 < d2" << endl;
return 0;
}
输入/输出 运算符重载
实例:
#include <iostream>
using namespace std;
class Data
{
public:
Data()
{
a = 0;
}
Data(int a):a(a)
{}
int get()
{
return a;
}
friend ostream& operator <<(ostream& output, const Data& da) //重载运算符 <<
{ //左操作数为输出流,右操作数为类的对象
output << da.a;
return output;
}
friend istream& operator >>(istream& input, Data& da) //重载运算符 >>
{ //左操作数为输入流,右操作数为类的对象
input >> da.a;
return input;
}
private:
int a;
};
int main()
{
Data d;
cin >> d; //operator>>(cin ,d)
cout << d; //operator<<(cout ,d)
return 0;
}
输入输出运算符重载的另类写法
一般的,IO操作符必须为非成员函数,否则,左操作数只能是该类型的对象
Data data;
data << cout;
实例:
//输入/输出 运算符重载只带有一个形参值
#include <iostream>
using namespace std;
class Data
{
public:
Data()
{
a = 0;
}
Data(int a):a(a)
{}
int get()
{
return a;
}
ostream& operator <<(ostream& output) //重载运算符 <<
{ //形参为输出流,因为友元函数不带有this指针,所以不能定义为友元函数
output << a;
return output;
}
istream& operator >>(istream& input) //重载运算符 >>
{ //形参为输入流,因为友元函数不带有this指针,所以不能定义为友元函数
input >> a;
return input;
}
private:
int a;
};
int main()
{
Data d;
d >> cin; //调用 >> 函数,形参为cin输入流
d << cout; 调用 << 函数,形参为cout输出流
return 0;
}
赋值运算符重载
赋值运算符可以重载。无论形参为何种类型,赋值操作符必须定义为成员函数,这一点与复合赋值操作符有所不同。
赋值必须返回*this的引用。
一般而言,赋值操作符和复合赋值操作符应返回左操作数的引用。
实例:
#include <iostream>
using namespace std;
class Data
{
public:
Data()
{
a = 0;
}
Data(int a):a(a)
{}
int get()
{
return a;
}
void operator +=(const int& m) //写法1
{
this->a += m;
}
/*Data operator +=(const int &m) //写法2
{
a += m;
return *this;
}*/
Data operator -=(const int& m)
{
a -= m;
return *this;
}
Data operator *=(const int& m)
{
a *= m;
return *this;
}
Data operator /=(const int& m)
{
a /= m;
return *this;
}
private:
int a;
};
int main()
{
Data d;
int m = 1;
d += m;
cout << d.get() << endl;
return 0;
}
自定义类型的隐式转换
当用户使用自定义类型变量向内置类型变量赋值时,可以使用自定义类型的隐式转换。
实例:
#include <iostream>
using namespace std;
class Int
{
private:
int n;
public:
Int(int i):n(i){}
operator int() //隐式转换声明,应该注意此处它与运算符重载的不同之处
{
return n;
}
};
int main()
{
Int a(5);
int c = a; //隐式调用转换函数
cout << c << endl;
cout << a << endl; //由于为重载Int的<<操作符,将隐式调用转换函数
return 0;
}
注意谨慎的使用隐式转换函数,因为当你不需要使用转换函数时,这些函数却可能会被调用运行,这些不正确的程序会做出咦嘻嘻额意想不到的事情,而你有很难判断出原因。
函数调用运算符()重载
函数调用运算符()可以被重载用于类的对象。当重载()时,这就创建了一种新的太哦用函数的方式,相反的,这是穿件一个可以传递任意数目参数的运算符函数。
实例:
#include <iostream>
using namespace std;
class Data
{
public:
Data()
{
a = 0;
}
Data(int a) :a(a)
{}
int get()
{
return a;
}
Data operator()(int b, int c) //重载函数调用运算符
{
a = b + c;
return *this;
}
private:
int a;
};
int main()
{
Data d1(1), d2;
d2 = d1(10, 10);
cout << d2.get() << endl;
return 0;
}
索引运算符[]重载
实例:
#include <iostream>
using namespace std;
class Coordinate
{
public:
Coordinate(int x,int y):m_x(x),m_y(y)
{}
int operator[](int index)
{
if (index == 0)
return m_x;
if (index == 1)
return m_y;
}
private:
int m_x;
int m_y;
};
int main()
{
Coordinate coor(3,4);
cout << coor[0] << endl; //coor.operator[](0)
cout << coor[1] << endl; //coor.operator[](1)
return 0;
}
箭头操作符(->)的重载
箭头操作符(->)的通常用法是,使用一个类对象的指针来调用该指针指向对象的成员。左操作数作为指针,右操作数为该对象的成员。定义重载箭头操作符后就比较特殊了,可以用类对象的指针来调用,也可以类的对象来调用。
重载箭头操作符,必须重载为类的成员函数。
箭头操作符可能看上去像一个二元操作符:接受一个对象名和一个成员名,对对象解引用获取成员。其实箭头操作符是一个一元操作符,没有显式形参,(唯一的隐式形参是this)。->的右操作数不是表达式,而是对象类成员的一个标识符,由编译器处理获取成员工作(编译器对重载箭头操作符所做的工作,比其他重载操作符要多,这也正是复杂的地方)
《C++ primer》操作符重载内容:
重载箭头操作符
重载运算符没有第二个形参,因为->的右操作数不是表达式,相反,是对应的类成员的一个标识符。没有明显可行的途径将一个标识符传递给形参,由编译器处理获取成员的工作。
当这样编写时:
point->action();
由于优先规则,所以等价于:
(point -> action) ();
换句话说,编译器想调用的是point->action()求值的结果。编译器这样对代码进行求值:
-
如果point是一个指针,指向具有名为action的成员的类对象,则编译器将代码编译为调用该对象的成员。
-
否则,如果point是定义了->重载操作符的类的一个对象,则point->action与point->()->action相同,即执行point的operator->(),然后使该结果重复三遍。
-
否则,代码出错
对重载箭头的返回值的约束
重载的箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。
如果返回类型是指针,则内置箭头操作符可用于该指针,编译器对该指针解引用并从结果对象获取指定成员。如果被指向的类型没有定义那个成员,则编译器产生一个错误。
如果返回类型是类类型的其他对象(或是这种对象的引用),则将递归应用该操作符。编译器检查返回对象所属类型是否具有成员箭头,如果有,就应用那个操作符;否则,编译器产生一个错误。这个过程继续下去,直到返回一个指向带有指定成员的的对象的指针,或者返回某些其他值,在后一种情况下,代码出错。
实例:
#include <iostream>
using namespace std;
class A
{
public:
void action()
{
cout << "action in class A" << endl;
}
};
class B
{
public:
A a;
void actoin()
{
cout << "action in class B" << endl;
}
A* operator->()
{
return &a;
}
};
class C
{
public:
B b;
void action()
{
cout << "action in class C" << endl;
}
B operator->()
{
return b;
}
};
int main()
{
C* pc = new C;
pc->action();
C c;
c->action();
return 0;
}
输出结果:
Action in class C
Action in class A
其中的代码
C* pc = new C;
pc->action();
输出的结果是
Action in class C!
这个结果比较好理解,pc是类对象指针,此时的箭头操作符使用的是内置含义,对pc解引用然后调用对象的成员函数action。
而下面的代码
C c;
c->action();
输出的结果是
Action in class A!
其实c->action();的含义与c.operator->().operator->()->action();相同。
c是对象,c后面的箭头操作符使用的是重载箭头操作符,即调用类C的operator->()成员函数。此时返回的是类B的对象,所以调用类B的operator->()成员函数,B的operator->()返回的是指针,所以现在可以使用内置箭头操作符了。对B的operator->()返回的指针进行解引用,然后调用解引用后的对象的成员函数action,此时调用的就是类A的action()。这里存在一个递归调用operator->()的过程,最后再使用一次内置含义的箭头操作符。
总结
- 输入输出运算符的重载必须是友元函数
- 索引运算符的重载必须是类的成员函数
- 单目运算符最好重载为类的成员函数(单目运算符改变操作对象),双目运算符最好重载为友元函数(双目运算符不改变左右操作数)
- 有4个运算符必须重载为类的成员函数:赋值(=)、下标([])、调用(())、成员访问箭头(->)