运算符重载

1.c++运算符重载的定义

运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

如果不做处理的话,在c++中+、-、*、/等运算符只能对基本的常量或变量进行运算,不能在对象之间进行运算。

如果希望对象之间也可以进行运算,于是就利用c++的“运算符重载”机制,赋予运算符新机制,就能解决像对象之间+这样的问题。

运算符重载的实质是编写以运算符作为名称的函数。不妨把这样的函数称为运算符函数。运算符函数的格式如下:

返回值类型 operator(运算符) 形参表
{
	.........
}

包含被重载的运算符的表达式会被编译成运算符函数的调用,运算符的操作数称为函数调用时的实参,运算结果就是函数的返回值。运算符可以被多次重载。

运算符可以被重载为全局函数,也可以被重载为成员函数。一般来说,倾向于将运算符重载为成员函数,这样较好的体现运算符和类的关系,例如:

#include <iostream>
using namespace std;
class C
{
public:
    double real, imag;
    C(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
    C operator - (const C& c);
};
C operator + (const C& a, const C& b)
{
    return C(a.real + b.real, a.imag + b.imag); //返回一个临时对象
}
C C::operator - (const C& c)
{
    return C(real - c.real, imag - c.imag); //返回一个临时对象
}
int main()
{
    C a(4, 4), b(1, 1), c;
    c = a + b; //等价于 c = operator + (a,b);
    cout << c.real << "," << c.imag << endl;
    cout << (a - b).real << "," << (a - b).imag << endl; //a-b等价于a.operator - (b)
    return 0;
}

结果为:

程序将+重载为一个全局函数,将-重载为一个成员函数。

运算符重载作为全局函数时,参数的个数等于运算符的目数(即操作数的个数);运算符重载作为成员函数时,参数的个数等于运算符的目数减一。

如果+没有被重载,则第21行就会出错,因为c++编译器不知道如何将两个对象进行+运算,有了运算符重载,编译器就理解为对运算符函数的调用,即,operator+(a,b)。

//也可以写为
c=operator+(a,b);

由于-被重载为成员函数,因此a-b就被编译器理解为

a.operator-(b);

2.C++重载=(C++重载赋值运算符)

赋值运算符要求=两边类型是匹配的,至少是兼容的。如果希望两边不兼容也能成立的话,就需要进行运算符重载。c++规定,=只能重载为成员函数。

赋值运算符重载格式
  • 参数类型const T&,传递引用可以提高传参效率
  • 返回值类型T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
#include <iostream>
using namespace std;
class Date
{ 
public :
 Date(int year = 1900, int month = 1, int day = 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;
 }
private:
 int _year ;
 int _month ;
 int _day ;
};
int main()
{
	Date a(2023, 4, 3);
	Date b,c;
	b = a;
	c = b = a;
	return 0;
}

结果为:

假定 a、b、c 都是 Date对象,则c=b=a的语句等价于下面的嵌套函数调用:

a.operator=( b.operator=(c) );

如果 operator= 函数的返回值类型为 void,显然上面这个嵌套函数调用就不能成立。将返回值类型改为 Date并且返回 *this 可以解决问题,但是还不够好。因为,假设 a、b、c 是基本类型的变量,则

(c =b) = a;

这条语句执行的效果会使得 a 的值和 c 相等,即a = b这个表达式的值其实是 a 的引用。为了保持=的这个特性,operator= 函数也应该返回其所作用的对象的引用。因此,返回值类型为 Date& 才是风格最好的写法。在 a、b、c 都是Date对象时,(a=b)=c;等价于

( c.operator=(b) ).operator=(a);

c.operator=(b) 返回对 c的引用后,通过该引用继续调用 operator=(a),就会改变 c 的值。

3.C++运算符重载为友元函数

一般情况下,将运算符重载为类的成员函数是较好的选择。但有时,重载为成员函数不能满足使用要求,重载为全局函数又不能访问类的私有成员,因此需要将运算符重载为友元。

例如,对于复数类 Complex 的对象,希望它能够和整型以及实数型数据做四则运算,假设 c 是 Complex 对象,希望c+55+c这两个表达式都能解释得通。

将+重载为 Complex 类的成员函数能解释c+5,但是无法解释5+c。要让5+c有意义,则应对+进行再次重载,将其重载为一个全局函数。为了使该全局函数能访问 Complex 对象的私有成员,就应该将其声明为 Complex 类的友元。具体写法如下:

class Complex
{
    double real, imag;
public:
    Complex(double r, double i):real(r), imag(i){};
    Complex operator + (double r);
    friend Complex operator + (double r, const Complex & c);
};
Complex Complex::operator + (double r)
{ //能解释c+5
    return Complex(real+r, imag);
}
Complex operator + (double r, const Complex & c)
{ //能解释5+c
    return Complex (c.real+r, c.imag);
}

 4.C++重载<<和>>(C++重载输出运算符和输入运算符)

在 C++ 中,左移运算符<<可以和 cout 一起用于输出,因此也常被称为“流插入运算符”或者“输出运算符”。实际上,<<本来没有这样的功能,之所以能和 cout 一起使用,是因为被重载了。

cout 是 ostream 类的对象。ostream 类和 cout 都是在头文件 <iostream> 中声明的。ostream 类将<<重载为成员函数,而且重载了多次。为了使cout<<"Star War"能够成立,ostream 类需要将<<进行如下重载:

ostream & ostream::operator << (const char* s)
{
    //输出s的代码
    return * this;
}

为了使cout<<5;能够成立,ostream 类还需要将<<进行如下重载:


ostream & ostream::operator << (int n)
{
    //输出n的代码
    return *this;
}

重载函数的返回值类型为 ostream 的引用,并且函数返回 *this,就使得cout<<"Star War"<<5能够成立。有了上面的重载,cout<<"Star War"<<5;就等价于:

( cout.operator<<("Star War") ).operator<<(5);

重载函数返回 *this,使得cout<<"Star War"这个表达式的值依然是 cout(说得更准确一点就是 cout 的引用,等价于 cout),所以能够和<<5继续进行运算。

cin 是 istream 类的对象,是在头文件 <iostream> 中声明的。istream 类将>>重载为成员函数,因此 cin 才能和>>连用以输入数据。一般也将>>称为“流提取运算符”或者“输入运算符”。

例题:假定 c 是 Complex 复数类的对象,现在希望写cout<<c;就能以 a+bi 的形式输出 c 的值;写cin>>c;就能从键盘接受 a+bi 形式的输入,并且使得 c.real = a, c.imag = b。

显然,要对<<>>进行重载,程序如下:

#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Complex
{
	double real,imag;
public:
	Complex( double r=0, double i=0):real(r),imag(i){ };
	friend ostream & operator<<( ostream & os,const Complex & c);
	friend istream & operator>>( istream & is,Complex & c);
};
ostream & operator<<( ostream & os,const Complex & c)
{
	os << c.real << "+" << c.imag << "i"; //以"a+bi"的形式输出
	return os;
}
istream & operator>>( istream & is,Complex & c)
{
	string s;
	is >> s; //将"a+bi"作为字符串读入, "a+bi" 中间不能有空格
	int pos = s.find("+",0);
	string sTmp = s.substr(0,pos); //分离出代表实部的字符串
	c.real = atof(sTmp.c_str());//atof库函数能将const char*指针指向的内容转换成 float
	sTmp = s.substr(pos+1, s.length()-pos-2); //分离出代表虚部的字符串
	c.imag = atof(sTmp.c_str());
	return is;
}
int main()
{
	Complex c;
	int n;
	cin >> c >> n;
	cout << c << "," << n;
	return 0;
}

程序的运行结果:

13.2+133i 87
13.2+133i,87

因为没有办法修改 ostream 类和 istream 类,所以只能将<<>>重载为全局函数的形式。由于这两个函数需要访问 Complex 类的私有成员,因此在 Complex 类定义中将它们声明为友元。

cout<<c会被解释成operator<<(cout, c),因此编写 operator<< 函数时,它的两个参数就不难确定了。

5.C++重载++和--(自增和自减运算符)

自增运算符++、自减运算符--都可以被重载,但是它们有前置、后置之分。

++为例,假设 obj 是一个 Demo 类的对象,++objobj++本应该是不一样的,前者的返回值应该是 obj 被修改后的值,而后者的返回值应该是 obj 被修改前的值。如果如下重载++运算符:

Demo & Demo::operator ++ ()
{
    //...
    return * this;
}

那么不论obj++还是++obj,都等价于obj.operator++()无法体现出差别。

为了解决这个问题,C++ 规定,在重载++或--时,允许写一个增加了无用 int 类型形参的版本,编译器处理++或--前置的表达式时,调用参数个数正常的重载函数;处理后置表达式时,调用多出一个参数的重载函数。来看下面的例子:
 

#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1);
	void Print();
	int GetMonthDay(int year, int month);

	Date& operator++();
	// d1++
	// int参数 仅仅是为了占位,跟前置重载区分
	Date operator++(int);

	//d1-- ->d1.operator--()
	Date& operator--();
	//d1-- ->d1.operator--(0)
	Date operator--(int);
private:
	int _year;
	int _month;
	int _day;
};
Date& Date:: operator--()
{
	*this -= 1;
	return *this;
}

Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

Date& Date::operator++()
{
	*this += 1;
	return *this;
}

Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}
void TestDate2()
{
	Date d1(2023, 2, 4);
	d1.Print();
	/*d1 -= 100;
	d1.Print();*/
	Date ret1=++d1;
	d1.Print();
	ret1.Print();
	Date ret2=--d1;
	d1.Print();
	ret2.Print();
	Date ret3 = d1--;
	d1.Print();
	ret3.Print();
	Date ret4 = d1++;
	d1.Print();
	ret4.Print();

}
int main()
{
	TestDate2();
	
	return 0;
}

本程序将++重载为成员函数,将--重载为全局函数。其实都重载为成员函数更好,这里将--重载为全局函数只是为了说明可以这么做而已。

调用后置形式的重载函数时,对于那个没用的 int 类型形参,编译器自动以 0 作为实参。d++等价于d.operator++(0)。

对比前置++和后置++运算符的重载可以发现,后置++运算符的执行效率比前置的低。因为后置方式的重载函数中要多生成一个局部对象 tmp,而对象的生成会引发构造函数调用,需要耗费时间。同理,后置--运算符的执行效率也比前置的低。

前置++运算符的返回值类型是 Date &,而后置++运算符的返回值类型是 Date,这是因为运算符重载最好保持原运算符的用法。C++ 固有的前置++运算符的返回值本来就是操作数的引用,而后置++运算符的返回值则是操作数值修改前的复制品。

6.C++运算符重载注意事项以及汇总

在 C++ 中进行运算符重载时,有以下问题需要注意:

  • 重载后运算符的含义应该符合原有用法习惯。例如重载+运算符,完成的功能就应该类似于做加法,在重载的+运算符中做减法是不合适的。此外,重载应尽量保留运算符原有的特性。
  • C++ 规定,运算符重载不改变运算符的优先级。
  • 以下运算符不能被重载:.、.*、::、? :、sizeof。
  • 重载运算符()、[]、->、或者赋值运算符=时,只能将它们重载为成员函数,不能重载为全局函数。

运算符重载的实质是将运算符重载为一个函数,使用运算符的表达式就被解释为对重载函数的调用。

运算符可以重载为全局函数。此时函数的参数个数就是运算符的操作数个数,运算符的操作数就成为函数的实参。

运算符也可以重载为成员函数。此时函数的参数个数就是运算符的操作数个数减一,运算符的操作数有一个成为函数作用的对象,其余的成为函数的实参。

必要时需要重载赋值运算符=,以避免两个对象内部的指针指向同一片存储空间。

运算符可以重载为全局函数,然后声明为类的友元。

<<和>>是在 iostream 中被重载,才成为所谓的“流插入运算符”和“流提取运算符”的。

类型的名字可以作为强制类型转换运算符,也可以被重载为类的成员函数。它能使得对象被自动转换为某种类型。

自增、自减运算符各有两种重载方式,用于区别前置用法和后置用法。

运算符重载不改变运算符的优先级。重载运算符时,应该尽量保留运算符原本的特性
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值