1.C++运算符重载的概念和语法
所谓重载,就是赋予新的含义。函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载(Operator Overloading)也是一个道理,同一个运算符可以有不同的功能。
实际上,我们已经在不知不觉中使用了运算符重载。例如,"+"号可以对不同类型(int、float 等)的数据进行加法操作;"<<"既是位移运算符,又可以配合 cout 向控制台输出数据。C++已经对这些运算符进行了重载。
C++ 也允许程序员自己重载运算符,这给我们带来了很大的便利。
下面的代码定义了一个复数类,通过运算符重载,用"+"号实现了复数的加法运算:
- #include <iostream>
- using namespace std;
- class complex{
- private:
- double real; //实部
- double imag; //虚部
- public:
- complex(): real(0.0), imag(0.0){ }
- complex(double a, double b): real(a), imag(b){ }
- complex operator+(const complex & A)const;
- void display()const;
- };
- //运算符重载
- complex complex::operator+(const complex & A)const{
- complex B;
- B.real = real + A.real;
- B.imag = imag + A.imag;
- return B;
- }
- void complex::display()const{
- cout<<real<<" + "<<imag<<"i"<<endl;
- }
- int main(){
- complex c1(4.3, 5.8);
- complex c2(2.4, 3.7);
- complex c3;
- c3 = c1 + c2;
- c3.display();
- return 0;
- }
- complex operator+(const complex &A, const complex &B){
- complex C;
- C.real = A.real + B.real;
- C.imag = A.imag + B.imag;
- return C;
- }
c3 = c1 + c2;
语句时,编译器检测到"+"号左边("+"号具有左结合性)是一个 Complex 对象,就会调用运算符重载函数,该语句会被转换为:
c1.operator+(c2);很明显是一个函数调用。
- complex complex::operator+(const complex & A)const{
- return complex(real+A.real, imag+A.imag);
- }
complex(real+A.real, imag+A.imag)
会创建一个临时对象,它没有对象名,是一个匿名对象。在创建临时对象过程中调用构造函数,return 语句将该临时对象作为函数返回值。
- int main(){
- complex c1,c2,c3,c4;
- c4 = c1 + c2 * c3;
- return 0;
- }
c4 = c1 + c2 * c3;
语句等同于
c4 = c1 + ( c2 * c3 ) ;
,乘法的优先级仍然高于加法,而且它们也仍然是二元运算符。
2.C++用全局函数重载运算符
运算符重载函数既可以声明为类的成员函数,也可以声明为所有类之外的全局函数。
complex operator+(const complex & A)const;当执行:
c3 = c1 + c2;会被转换为:
c3 = c1.operator+(c2);通过 this 指针隐式的访问 c1 的成员变量。
- int operator + (int a,int b){
- return (a-b);
- }
- complex operator+(int a, complex &c){
- return complex(a+c.real, c.imag);
- }
- #include <iostream>
- using namespace std;
- class complex{
- private:
- double real; //实部
- double imag; //虚部
- public:
- complex(): real(0.0), imag(0.0){ }
- complex(double a, double b): real(a), imag(b){ }
- void display()const{ cout<<real<<" + "<<imag<<"i"<<endl; }
- friend complex operator+(const complex &A, const complex &B); //友元函数
- };
- //全局运算符重载
- complex operator+(const complex &A, const complex &B){
- complex C;
- C.real = A.real + B.real;
- C.imag = A.imag + B.imag;
- return C;
- }
- int main(){
- complex c1(4.3, 5.8);
- complex c2(2.4, 3.7);
- complex c3;
- c3 = c1 + c2;
- c3.display();
- return 0;
- }
c3 = c1 + c2;会被转换为:
c3 = operator+(c1, c2);最后要说明两点:
3.C++重载>>和<<(输入输出运算符)
在C++中,系统已经对左移运算符“<<”和右移运算符“>>”分别进行了重载,使其能够用于输入输出,但是输入输出的处理对象只能是系统内建的数据类型。系统重载这两个运算符是以系统类成员函数的形式进行的,因此cout<< var
语句可以理解为:
cout.operator<<( var )如果我们自己定义了一种新的数据类型,需要用输入输出运算符去处理,那么就要重载。本节以前面的 complex 类为例说明输入输出运算符的重载。
- istream & operator>>(istream & in, complex & A){
- in >> A.real >> A.imag;
- return in;
- }
friend istream & operator>>(istream & in , complex & a);该函数可以按照如下方式使用:
complex c; cin>> c;当输入
1.45 2.34↙
后,这两个小数就分别成为 complex 对象 c 的实部和虚部了。
cin>> c;
这一语句其实可以理解为:
operator<<(cin , c);在重载输入运算符时,采用引用的方式进行参数传递:输入的参数里面包含一个 istream 类的引用,返回值仍然为该引用。 这样做的一个明显好处就是可以采用链式输入(也就是连续输入),如下所示:
complex c1, c2, c3; cin>> c1 >> c2 >> c3;
rriend ostream &(ostream & out, complex & A);全局函数的实现如下:
- ostream & operator<<(ostream & out, complex & A){
- out << A.real <<" + "<< A.imag <<" i ";
- return out;
- }
- #include <iostream>
- using namespace std;
- class complex{
- private:
- double real; //复数的实部
- double imag; //复数的虚部
- public:
- complex(): real(0.0), imag(0.0){ };
- complex(double a, double b): real(a), imag(b){ };
- friend complex operator+(const complex & A, const complex & B);
- friend complex operator-(const complex & A, const complex & B);
- friend complex operator*(const complex & A, const complex & B);
- friend complex operator/(const complex & A, const complex & B);
- friend istream & operator>>(istream & in, complex & A);
- friend ostream & operator<<(ostream & out, complex & A);
- };
- //重载加法运算符
- complex operator+(const complex & A, const complex &B){
- complex C;
- C.real = A.real + B.real;
- C.imag = A.imag + B.imag;
- return C;
- }
- //重载减法运算符
- complex operator-(const complex & A, const complex &B){
- complex C;
- C.real = A.real - B.real;
- C.imag = A.imag - B.imag;
- return C;
- }
- //重载乘法运算符
- complex operator*(const complex & A, const complex &B){
- complex C;
- C.real = A.real * B.real - A.imag * B.imag;
- C.imag = A.imag * B.real + A.real * B.imag;
- return C;
- }
- //重载除法运算符
- complex operator/(const complex & A, const complex & B){
- complex C;
- double square = A.real * A.real + A.imag * A.imag;
- C.real = (A.real * B.real + A.imag * B.imag)/square;
- C.imag = (A.imag * B.real - A.real * B.imag)/square;
- return C;
- }
- //重载输入运算符
- istream & operator>>(istream & in, complex & A){
- in >> A.real >> A.imag;
- return in;
- }
- //重载输出运算符
- ostream & operator<<(ostream & out, complex & A){
- out << A.real <<" + "<< A.imag <<" i ";;
- return out;
- }
- int main(){
- complex c1, c2, c3;
- cin>>c1>>c2;
- c3 = c1 + c2;
- cout<<"c1 + c2 = "<<c3<<endl;
- c3 = c1 - c2;
- cout<<"c1 - c2 = "<<c3<<endl;
- c3 = c1 * c2;
- cout<<"c1 * c2 = "<<c3<<endl;
- c3 = c1 / c2;
- cout<<"c1 / c2 = "<<c3<<endl;
- return 0;
- }
4.C++重载[](下标运算符)
前面已经提到,下标操作符[]
必须以类的成员函数的形式进行重载。在类中的声明格式如下:
返回值类型 & operator[] (参数)
或const 返回值类型 & operator[] (参数)
使用第一种声明方式,操作符重载函数不仅可以访问对象,同时还可以修改对象。使用第二种声明方式,操作符重载函数只能访问而不能修改对象。
- #include<iostream>
- #include<string>
- using namespace std;
- class Array{
- private:
- int length;
- int * num;
- public:
- Array(): length(0), num(NULL){ }
- Array(int n);
- int & operator[](int);
- const int & operator[]( int )const;
- int getlength() const{ return length; }
- };
- Array::Array(int n){
- num = new int[n];
- length = n;
- }
- int& Array::operator[](int i){
- if(i < 0 || i >= length)
- throw string("out of bounds");
- return num[i];
- }
- const int & Array::operator[](int i) const{
- if(i < 0 || i >= length)
- throw string("out of bounds");
- return num[i];
- }
- int main(){
- Array A(5);
- int i;
- try{
- for(i = 0; i < A.getlength(); i++)
- A[i] = i;
- for(i = 0 ;i < 6; i++ )
- cout<< A[i] <<endl;
- }catch(string s){
- cerr<< s <<", i = "<< i <<endl;
- }
- return 0;
- }
本例用到了C++的错误处理机制以及 string 类,后续将会讲解。本例提供了两个版本的下标运算符重载函数:
- int & operator[]( int );
- const int & operator[]( int )const;
arr.operator[]( 5 );最后需要说明的是:即使没有定义 const 版本的重载函数,这段代码也是可以正确运行的,但是非 const 成员函数不能处理 const 对象,所以在编程时通常会提供两个版本的运算符重载函数。
5.C++重载++和--(自增自减运算符)
自增“++”和自减“--”都是一元运算符,它的前置形式和后置形式都可以被重载。请看下面的例子:
- #include <iostream>
- #include <iomanip>
- using namespace std;
- class stopwatch{ //秒表
- private:
- int min; //分钟
- int sec; //秒钟
- public:
- stopwatch(): min(0), sec(0){ }
- void setzero(){ min = 0; sec = 0; }
- stopwatch run(); // 运行
- stopwatch operator++(); //++i,前置形式
- stopwatch operator++(int); //i++,后置形式
- friend ostream & operator<<( ostream &, const stopwatch &);
- };
- stopwatch stopwatch::run(){
- ++ sec;
- if( sec == 60 ){
- min ++;
- sec = 0;
- }
- return *this;
- }
- stopwatch stopwatch::operator++(){
- return run();
- }
- stopwatch stopwatch::operator++(int n){
- stopwatch s = *this;
- run();
- return s;
- }
- ostream & operator<<( ostream & out, const stopwatch & s){
- out<<setfill('0')<<setw(2)<<s.min<<":"<<setw(2)<<s.sec;
- return out;
- }
- int main(){
- stopwatch s1, s2;
- s1 = s2 ++;
- cout << "s1: "<< s1 <<endl;
- cout << "s2: "<< s2 <<endl;
- s1.setzero();
- s2.setzero();
- s1 = ++ s2;
- cout << "s1: "<< s1 <<endl;
- cout << "s2: "<< s2 <<endl;
- return 0;
- }
6.C++重载=(赋值运算符)
和普通变量一样,对象之间也可以相互赋值。赋值运算符“=”可以用来将一个对象拷贝给另一个已经存在的对象。对象之间的赋值是将成员变量依次拷贝,而不是将整个对象的内存按位拷贝。
[举例] 对象之间的赋值:
- #include <iostream>
- using namespace std;
- class Demo{
- private:
- int a;
- int b;
- public:
- Demo(): a(0), b(0){ }
- Demo(int a, int b): a(a), b(b){ }
- void display(){ cout<<a<<", "<<b<<endl; }
- };
- int main(){
- Demo obj1, obj2(10, 20);
- obj1 = obj2; //对象之间的赋值
- obj1.display();
- return 0;
- }
- #include <iostream>
- using namespace std;
- class Book{
- private:
- double price; //书的价格
- int *bookmark; //书签
- int num; //书签的数量
- public:
- Book(): price(0.0), bookmark(NULL), num(0){}
- Book(double, int*, int);
- void setBookmark(int, int); //修改书签
- void display();
- };
- Book::Book(double price, int *bm, int num): price(price), num(num){
- int *bmTemp = new int[num];
- for(int i=0; i<num; i++){
- bmTemp[i] = bm[i];
- }
- this->bookmark = bmTemp;
- }
- void Book::setBookmark(int page, int index){
- if(index>=num-1){
- cout<<"Out of bound!"<<endl;
- }else{
- bookmark[index] = page;
- }
- }
- void Book::display(){
- cout<<"price: "<<price<<endl;
- cout<<"bookmarks: ";
- for(int i=0; i<num; i++){
- if(i==num-1){
- cout<<bookmark[i]<<endl;
- }else{
- cout<<bookmark[i]<<", ";
- }
- }
- }
- int main(){
- int bookmark[] = { 1, 49, 56, 290 };
- Book java, cpp(68.5, bookmark, 4);
- cpp.display();
- java = cpp; //对象之间赋值
- java.setBookmark(100, 2);
- cpp.display();
- return 0;
- }
java = cpp;
语句时会将 cpp.bookmark 的值复制给 java.bookmark,不同对象的成员变量指向同一个数组,当然会相互影响。
- Book & Book::operator=(const Book &b){
- if( this != &b){
- this->price = b.price;
- this->num = b.num;
- //为bookmark赋值
- int *bmTemp = new int[b.num];
- for(int i=0; i<b.num; i++){
- bmTemp[i] = b.bookmark[i];
- }
- this->bookmark = bmTemp;
- }
- return *this;
- }
java = cpp;
语句时,会转换为:
java.operator=(cpp);在函数体中,this 就指向 java 对象。这样 java 对象也会拥有属于自己的数组,两个对象之间不会再相会影响。
Book & operator=( Book &b ); Book & operator=( const Book &b );返回值和参数都是 Book 类对象的引用。下面一种原型则规定在赋值时不能修改原来的对象。
Book & operator=(const Book &b, a = 10);
7.C++重载new和delete运算符
内存管理运算符 new、new[]、delete 和 delete[] 也可以进行重载,其重载形式既可以是类的成员函数,也可以是全局函数。一般情况下,内建的内存管理运算符就够用了,只有在需要自己管理内存时才会重载。
重载 new 有两种形式:
//以类的成员函数的形式进行重载 void * 类名::operator new ( size_t size ){ //TODO: }和
//以全局函数的形式进行重载 void * operator new ( size_t size ){ //TODO: }两种重载形式的返回值相同,都是
void *
类型,并且都有一个参数,为
size_t
类型。
在重载 new 或 new[] 时,无论是作为成员函数还是作为全局函数,它的第一个参数必须是 size_t 类型。size_t 表示的是要分配空间的大小,对于 new[] 的重载函数而言,size_t 则表示所需要分配的所有空间的总和。
size_t 在头文件 <cstdio> 中被定义为
typedef unsigned int size_t;
,也就是无符号整型。
当然,重载函数也可以有其他参数,但都必须有默认值,并且第一个参数的类型必须是 size_t。
//以类的成员函数的形式进行重载 void 类名:: operator delete ( void *ptr){ //TODO: }和
//以全局函数的形式进行重载 void 类名:: operator delete ( void *ptr){ //TODO: }两种重载形式的返回值都是 void 类型,并且都必须有一个 void 类型的指针作为参数,该指针指向需要释放的内存空间。
- C * c = new C; //分配内存空间
- //
- delete c; //释放内存空间