运算符重载

目录

+运算符的重载

=运算符的重载

重载<<与>>运算符

重载()(强制类型转换)

++或--运算符的重载


首先我们要知道普通的运算符+、-、*、/等,只能用于对基本类型的常量或变量进行运算,不能用于对象之间的运算。有时希望对象之间也能用这些运算符进行运算,以达到使程序更简洁、易懂的目的。运算符重载是一种C++多态。

那么我们如果想将两个对象进行运算,我们该怎么办?

C++ 提供的“运算符重载”机制,赋予运算符新的功能,就能解决用+将两个对象相加这样的问题。

运算符重载,就是对已有的运算符赋予多重含义,使同一运算符作用于不同类型的数据时产生不同的行为。运算符重载的目的是使得 C++ 中的运算符也能够用来操作对象。

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

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

+运算符的重载

class Complex
{
public:
	Complex(int x=0,int y=0):m_x(x),m_y(y) { }
	
	Complex operator + (const Complex & c)
	{
		return Complex(c.m_x + m_x, c.m_y + m_y);
	}

	void print()
	{
		cout << m_x << "\t" << m_y << endl;
	}

private:
	int m_x;
	int m_y;

};

int main()
{
	Complex a(2, 3);
	Complex b(4, 5);
	Complex c = a + b;
	c.print();
	return 0;
}

这样,我们通过重载+运算符可以让两个对象相加。

=运算符的重载

赋值运算符=要求左右两个操作数的类型是匹配的,或至少是兼容的。有时希望=两边的操作数的类型即使不兼容也能够成立,这就需要对=进行重载。C++ 规定,=只能重载为成员函数。来看下面的例子。要编写一个长度可变的字符串类 String,该类有一个 char* 类型的成员变量,用以指向动态分配的存储空间,该存储空间用来存放以\0结尾的字符串。String 类可以如下编写:
 

class String {
private:
	char * str;
public:
	String() :str(NULL) { }
	const char * c_str() const { return str; };
	String & operator = (const char * s);
	~String();
};

String & String::operator = (const char * s)
{
	if (str)
		delete[] str;
	if (s != NULL)
	{ //s不为NULL才会执行拷贝
		str = new char[strlen(s) + 1];
		strcpy(str, s);
	}
	else
		str = NULL;
	return *this;
}

String::~String()
{
	if (str)
		delete[] str;
};

int main()
{
	String s;
	s = "helloooo"; //等价于 s.operator=();
	cout << s.c_str() << endl;
}

构造函数将 str 初始化为 NULL,仅当执行了 operator= 成员函数后,str 才会指向动态分配的存储空间,并且从此后其值不可能再为 NULL。在 String 对象的生存期内,有可能从未执行过 operator= 成员函数,所以在析构函数中,在执行delete[] str之前,要先判断 str 是否为 NULL。

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

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

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

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

重载<<与>>运算符

<<与>>都是C++的输入输出流,实际上,<<本来输出这样的功能,之所以能和 cout 一起使用,是因为被重载了。

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

ostream operator<<(ostream &os,const A &a)
{
    os<<a.num;
    return os;
}

注意,函数的返回类型是ostream  &,这意味着函数返回ostream 对象的引用。函数开始执行时,程序传递了一个对象引用给他,最终函数的返回值就是传递给他的对象。

cout<<a;将被转换成operator<<(cout,a);

重载()(强制类型转换运算符)

类型强制转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。经过适当重载后,(类型名)对象这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名(),即变成对运算符函数的调用。
 

#include <iostream>
using namespace std;
class Complex
{
    double real, imag;
public:
    Complex(double r = 0, double i = 0) :real(r), imag(i) {};
    operator double() { return real; } //重载强制类型转换运算符 double
};
int main()
{
    Complex c(1.2, 3.4);
    cout << (double)c << endl; 
    double n = 2 + c; //等价于 double n = 2 + c. operator double()
    cout << n; 
}

++或--运算符的重载

我们都知道以++为例,假设 obj 是一个 类的对象,++objobj++本应该是不一样的,前者的返回值应该是 obj 被修改后的值,而后者的返回值应该是 obj 被修改前的值。如果重载++运算符,那么就会有两种方案,一个是重载前置++,另外一个是重载后置++。那么两种哪一种更好呢?

#include <iostream>
using namespace std;
class A {
private:
	int n;
public:
	A(int i = 0) :n(i) { }

	A  operator++() //用于前置形式
	{
		n++;
		return *this;
	}

	A operator++(int) //用于后置形式
	{
		A tmp(*this); //记录修改前的对象
		n++;
		return tmp; //返回修改前的对象
	}
	
	operator int() { return n; }
	friend A operator--(A &);
	friend A operator--(A &, int);
};


A  operator--(A & d)
{//前置--
	d.n--;
	return d;
}
A operator--(A & d, int)
{//后置--
	A tmp(d);
	d.n--;
	return tmp;
}
int main()
{
	A d(6);
	cout << (d++) << ","; //等价于 d.operator++(0);
	cout << d << ",";
	cout << (++d) << ","; //等价于 d.operator++();
	cout << d << endl;
	cout << (d--) << ","; //等价于 operator-(d,0);
	cout << d << ",";
	cout << (--d) << ","; //等价于 operator-(d);
	cout << d << endl;
	return 0;
}

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

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

重载后运算符的含义应该符合原有用法习惯。例如重载+运算符,完成的功能就应该类似于做加法,在重载的+运算符中做减法是不合适的。此外,重载应尽量保留运算符原有的特性。
C++ 规定,运算符重载不改变运算符的优先级。
以下运算符不能被重载:

  • 重载运算符()、[]、->、或者赋值运算符=时,只能将它们重载为成员函数,不能重载为全局函数。
  • 运算符重载的实质是将运算符重载为一个函数,使用运算符的表达式就被解释为对重载函数的调用。
  • 运算符可以重载为全局函数。此时函数的参数个数就是运算符的操作数个数,运算符的操作数就成为函数的实参。
  • 运算符也可以重载为成员函数。此时函数的参数个数就是运算符的操作数个数减一,运算符的操作数有一个成为函数作用的对象,其余的成为函数的实参。
  • 必要时需要重载赋值运算符=,以避免两个对象内部的指针指向同一片存储空间。
  • 运算符可以重载为全局函数,然后声明为类的友元。
  • <<和>>是在 iostream 中被重载,才成为所谓的“流插入运算符”和“流提取运算符”的。
  • 类型的名字可以作为强制类型转换运算符,也可以被重载为类的成员函数。它能使得对象被自动转换为某种类型。
  • 自增、自减运算符各有两种重载方式,用于区别前置用法和后置用法。运算符重载不改变运算符的优先级。重载运算符时,应该尽量保留运算符原本的特性。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值