运算符重载(一)
- 运算符重载允许把标准运算符(如+、—、*、/、<、>等)应用于自定义数据类型的对象。
- 直观自然,可以提高程序的可读性。
- 体现了C++的可扩充性。
- 运算符重载仅仅只是语法上的方便,它是另一种函数调用的方式。
- 运算符重载,本质上是函数重载。
- 不要滥用重载、因为它只是语法上的方便,所以只有在涉及的代码更容易写、尤其是更易读时才有必要重载。
实际上对于运算符的重载在之前的学习中也已经使用到了,只是没系统的提到,下面来用代码进行说明:
先用普通的方式来实现一下复数的运算:
Complex.h:
#ifndef _COMPLEX_H_ #define _COMPLEX_H_ class Complex { public: Complex(int real, int imag); Complex(); ~Complex(); private: int real_;//复数的实部 int imag_;//复数的虚部 }; #endif //_COMPLEX_H_
Complex.cpp:
#include "Complex.h" Complex::Complex(int real, int imag) : real_(real), imag_(imag) { } Complex::Complex() { } Complex::~Complex() { }
如果要实现两个复数相加,则先定义一个方法:
Complex.h:
#ifndef _COMPLEX_H_ #define _COMPLEX_H_ class Complex { public: Complex(int real, int imag); Complex(); ~Complex(); Complex& add(const Complex& otherComplex);//复数加法运算 void display() const;//打印复数 private: int real_;//复数的实部 int imag_;//复数的虚部 }; #endif //_COMPLEX_H_
Complex.cpp:
#include "Complex.h" #include <iostream> using namespace std; Complex::Complex(int real, int imag) : real_(real), imag_(imag) { } Complex::Complex() { } Complex::~Complex() { } Complex& Complex::add(const Complex& otherComplex) { real_ += otherComplex.real_; imag_ += otherComplex.imag_; return *this; } void Complex::display() const { cout<<real_<<"+"<<imag_<<"i"<<endl; }
编写测试代码:
#include "Complex.h" int main(void) { Complex c1(3, 5); Complex c2(4, 6); c1.add(c2); c1.display(); return 0; }
编译运行,结果显而易见:
以上就是以成员函数的方式来实现两个复数的加法运算,但是,这种方式不太直观,而且有个毛病就是c1对象发生的改变,如果要实现下面这种运算呢?
这时运算符的重载就派上了用场了,上面的这种写法现在肯定是编译通不过的:
其中运算法函数的有两种方式:成员函数重载、非成员函数重载。下面分别来实现:
- 成员函数原型的格式:
函数类型 operator 运算符(参数表); - 成员函数定义的格式:
函数类型 类名::operator 运算符(参数表){函数体;
}
Complex.h:
#ifndef _COMPLEX_H_ #define _COMPLEX_H_ class Complex { public: Complex(int real, int imag); Complex(); ~Complex(); Complex& add(const Complex& otherComplex);//复数加法运算 void display() const;//打印复数 Complex operator+(const Complex& otherComplex);//+号运算符重载 private: int real_;//复数的实部 int imag_;//复数的虚部 }; #endif //_COMPLEX_H_
Complex.cpp:
编译运行:
实际上:
Complex c3 = c1 + c2; 等价于: Complex c3 = c1.operator + (c2);
下面来验证下:
编译运行:
可见这个加法运算实际上就是函数调用,但是这种等价写法不是那么直观,所以还是将代码还原。
- 友元函数原型的格式:
friend 函数类型 operator 运算符(参数表); - 友元函数定义的格式:
friend 函数类型 类名::operator 运算符(参数表){函数体;
}
在编译运行之前,先思考一个问题:成员函数重载和非成员函数成载能否共存呢?下面编译一下:
从结果来看是可以共存的,但是在vS6.0中是不允许的,所以平常最好只要写其中一个就成了,不要共存。
如果是以友元的方式重载,那下面这句的话等价于:
- 运算符重载不允许发明新的运算符。
- 不能改变运算符操作对象的个数。
- 运算符被重载后,其优先级和结合性不会改变。
- 不能重载的运算符:
- 关于选择成员函数重载还是友元函数重载,有下面一些规划:
①、一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。
②、以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。
③、类型转换运算符只能以成员函数方式重载。
④、流运算符只能以友元的方式重载。
这里用一个整数类来说明这个运算符的重载,如下:
Integer.h:
#ifndef _INTEGER_H #define _INTEGER_H class Integer { public: Integer(int n); ~Integer(); void display() const; private: int n_; }; #endif //_INTEGER_H
Integer.cpp:
#include "Integer.h" #include <iostream> using namespace std; Integer::Integer(int n) : n_(n) { } Integer::~Integer() { } void Integer::display() const{ cout<<n_<<endl; }
编写测试代码:
#include "Integer.h" #include <iostream> using namespace std; int main(void) { Integer n(100); n.display(); }
编译运行:
接下来实现它的++运算,先将其测试代码写出来,当然现在是编译通不过滴:
下面需要重载++运算符,先学习一下基本概念:
①、前置++运算符重载:
- 成员函数的方式重载,原型为:
函数类型 & operator++(); - 友元函数的方式重载,原型为:
friend 函数类型 & operator++(类类型 &);
②、后置自增和后置自减的重载:
-
成员函数的方式重载,原型为:
函数类型 & operator++(int); - 友元函数的方式重载,原型为:
friend函数类型 & operator++(类类型 &,int);
下面来具体实现一下:
编译运行:
而如果以友元的方式重载,需要多加一个参数,如其它重载一样:
编译:
所以将之前的重载方式注释掉:
编译运行其结果跟之前的第一种方式是一样的。
下面来看下后置++运算符的重载:
编写测试代码:
根据后置运算符的语法可以推断n++的整个表示式是没有+1的,也就是n3应该等于101,而对于n自身来讲是要+1的,所以n应该等于102,那结果是102、101么?下面运行一下:
其主要原因还是这样写有问题:
那如何解决这个问题,那就是用临时对象来解决,具体如下:
由于函数原形变了,则在头文件中的声明也得变化一下:
再次编译运行:
这时就正确了,而它的友远方式实现跟前置++类似,如下:
其运行结果跟成员函数的重载一样。
【注意】:能用成员函数重载解决的就尽量用成员函数的方式,否则才用友员的方式。
关于这个运算符重载用之前我们已经写过的字符串类来说明:
String.h:
#ifndef _STRING_H #define _STRING_H class String { public: String(const char* str=""); ~String(); private: char* str_; }; #endif // _STRING_H
String.cpp:
#include "String.h" String::String(const char* str) { int len = sizeof(str) + 1; str_ = new char[len]; memset(str_, 0, len); strcpy(str_, str); } String::~String() { delete[] str_; }
另外还需要实现深拷贝,关于为什么要实现深拷贝,可以参考博文:http://www.cnblogs.com/webor2006/p/5084247.html,具体代码如下:
编译运行:
再次编译:
再次编译:
\
下面来实现赋值运算符,因为默认情况下:
String s1("aaa");
String s2 = s1;等价于s2.str_=s1.str_实现的也是浅拷贝,而当两个对象释放时,都会释放同一个str_就会出问题,所以需要实现赋值运算符实现深拷贝,具体如下:
String.cpp:
#pragma warning(disable:4996) #include "String.h" #include <string.h> #include <iostream> using namespace std; String::String(const char* str) { str_ = allocAndCpy(str); } String::String(const String& other) { str_ = allocAndCpy(other.str_); } String& String::operator = (const String& other) { if(this == &other) return *this; delete[] str_; str_ = allocAndCpy(other.str_); return *this; } char* String::allocAndCpy(const char* str) { int len = sizeof(str) + 1; char* newStr = new char[len]; memset(newStr, 0, len); strcpy(newStr, str); return newStr; } String::~String() { delete[] str_; } void String::display() const { cout<<str_<<endl; }
测试代码:
编译运行:
如果是这样编写测试代码,还能编译通过么?
编译运行:
这是由于当执行s3="xxx"时会调用转换构造函数:
如果不让其转换构造,那还能支持这种写法么?
见证奇迹:
其实可以通过重载运算赋值运算符来解决,如下:
再次编译运行:
先来编写测试代码再来去实现非运算符重载:
当然目前的代码是无法编译通过的:
下面来实现非运算符重载:
编译运行: