天天看點

C++學習之運算符重載

  • 運算符重載的基本概念

為什麼要重載運算符?

C++中預定義的運算符隻能用于基本資料類型的計算,但有時我們會希望對象也能通過運算符進行運算,(比如求兩個複數對象的和)這樣會使代碼更簡潔,容易了解。

運算符重載,就是對已有的運算符(C++中預定義的運算符)賦予多重的含義,使同一運算符作用于不同類型的資料時導緻不同類型的行為。

重載的目的是擴充C++中提供的運算符的适用範圍,使之能作用于對象。

同一個運算符,對不同類型的操作數,所發生的行為不同。

例如:

complex_a + complex_b  生成新的複數對象

5 +4  = 9

運算符重載的實質是函數的重載。

可以重載為普通函數,也可以重載為成員函數。

把含運算符的表達式轉換成對運算符函數的調用。

把運算符的操作數轉換成運算符函數的參數。

運算符被多次重載時,根據實參的類型決定調用哪個運算符函數。

運算符重載的形式:

傳回值類型   operator 運算符(形參表)

{

······

}
           

運算符重載的示例:

class Complex
{
    public:
        double real, imag;
        Complex (double r=0.0, double i=0.0):real(r),imag(i){   };
        Complex operator-(const Complex & c);
};

Complex operator+(const Complex & a, const Complex & b)
{
    return Complex(a.real+b.real, a.imag+b.imag);   //傳回一個臨時對象
}

Complex Complex::operator-(const Complex & c)
{
    return Complex(real-c.real, imag-c.imag);    //傳回一個臨時對象
}

int main(){
    Complex 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;
}
           

重載為成員函數時,參數個數為運算符目數減一;

重載為普通函數時,參數個數為運算符目數。

輸出結果為:

5,5

3,3

  • 指派運算符‘=’的重載

有時候希望指派運算符兩邊的類型可以不比對,比如:把一個int類型的變量指派給一個Complex對象,或把一個char*類型的字元串指派給一個字元串對象,此時就需要重載指派運算符“=”。

指派運算符隻能重載為成員函數。

class String {
private:
    char * str;
public:
    String ():str(new char[1]) { str[0] = 0;}
    const char * c_str() { return str; };
    String & operator = (const char * s);
    ~String( ) { delete [] str; }
};

String & String::operator = (const char * s)
{         //重載“=”以使得 obj = “hello”能夠成立
        delete [] str;
        str = new char[strlen(s)+1];
        strcpy( str, s);
        return * this;
}

int main()
{
    String s;
    s = "Good Luck," ;     //等價于 s.operator=("Good Luck,");
    cout << s.c_str() << endl;
// String s2 = "hello!";     //這條語句要是不注釋掉就會出錯
    s = "Shenzhou 8!";         //等價于 s.operator=("Shenzhou 8!");
    cout << s.c_str() << endl;
    return 0;
}
           
  • 運算符重載為友元函數

一般隻需将運算符重載為類的成員函數便是較好的選擇,但有時,重載為成員函數不能滿足使用要求,重載為普通函數,又不能通路類的私有成員,是以才将運算符重載為友元。

引出代碼:

class Complex
{
    double real,imag;
public:
    Complex( double r, double i):real(r),imag(i){ };
    Complex operator+( double r );
};

Complex Complex::operator+( double r )
{ //能解釋 c+5
    return Complex(real + r,imag);
}
           

經過上述重載後,能夠解釋c+5,但是不能解釋5+c

是以改動重載函數為:

Complex Complex::operator+(double r, const Complex & c)
{
    return Complex(c.real+r, c.imag);
}        
           

上述函數就可以解釋5+c的問題了,但是普通函數是不能通路私有成員的,需要将運算符+重載為友元。

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);
};
           
  • 可變長整型數組(運算符重載的執行個體)

#include <iostream>
#include <string>
#define _SCL_SECURE_NO_WARNINGS
using namespace std;

class CArray {
	int size;//	數組元素的個數
	int *ptr;//指向動态配置設定的數組
public:
	CArray(int s = 0);//s代表數組元素的個數
	CArray(CArray & a);
	~CArray();
	void push_back(int v);//用于在數組尾部添加一個元素
	CArray &operator=(const CArray & a);//用于數組對象間的指派
	int length() { return size; }//傳回數組元素的個數
	int & operator[](int i)//傳回值為int是不行的,不支援a[i]=4.
	{
		return ptr[i];  //用于支援根據下标通路數組元素,如n=a[i]和a[i]=4這樣的語句。
	}
};

CArray::CArray(int s) :size(s) {
	if (s == 0) {
		ptr = NULL;
	}
	else {
		ptr = new int[s];
	}
}

CArray::CArray(CArray & a) {
	if (!a.ptr) {
		ptr = NULL;
		size = 0;
		return;
	}
	ptr = new int[a.size];
	memcpy(ptr, a.ptr, sizeof(int)*a.size);
	size = a.size;
}

CArray::~CArray() {
	if (ptr)
		delete[] ptr;
}
CArray & CArray::operator=(const CArray & a) {
	//指派号重載的作用是使“=”左邊的對象裡存放的數組,大小和内容都和右邊的對象一樣
	if (ptr == a.ptr) { //防止a = a這樣的指派導緻出錯
		return *this;
	}
	if (a.ptr == NULL) {   //a裡面的數組是空的
		if (ptr) {
			delete[] ptr;
		}
		ptr = NULL;
		size = 0;
		return *this;
	}
	if (size < a.size) {//如果原有空間夠大,就不用配置設定新的空間
		if (ptr) {
			delete[] ptr;
		}
		ptr = new int[a.size];
	}

	memcpy(ptr, a.ptr, sizeof(int)*a.size);
	size = a.size;
	return *this;
}

void CArray::push_back(int v) {
	if (ptr) {
		int *temptr = new int[size + 1];//重新配置設定空間
		memcpy(temptr, ptr, sizeof(int)*size);//拷貝原數組的内容
		delete[] ptr;
		ptr = temptr;
	}
	else//數組本來是空的
		ptr = new int[1];
	ptr[size++] = v;		//加入新的數組元素
}

int main() {
	CArray a;		//開始的數組内是空的
	for (int i = 0; i < 5; ++i) {
		a.push_back(i);
	}
	CArray a2, a3;
	a2 = a;
	for (int i = 0; i < a.length(); ++i) {
		cout << a2[i] << " ";
	}
	a2 = a3;	//a2是空的
	for (int i = 0; i < a2.length(); ++i) {		//a2.length()傳回0
		cout << a2[i] << " ";
	}
	cout << endl;
	a[3] = 100;
	CArray a4(a);
	for (int i = 0; i < a4.length(); ++i) {
		cout << a4[i] << " ";
	}
	return 0;
}
           
  • 流插入運算符和 流提取運算符的重載

問題的引入:

cout << 5 << “this”; 為什麼能夠成立?

cout是什麼? “<<” 為什麼能用在 cout上?

cout 是在 iostream 中定義的,ostream 類 的對象。

“<<” 能用在cout 上是因為,在iostream 裡對 “<<” 進行了重載。

怎麼重載才能使得 cout << 5 << “this” ; 成立?

重載的形式:

ostream & ostream::operator<<(int n)
{
    …… //輸出n的代碼
    return * this;
}


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

問題:假定下面程式輸出為5hello,該補寫些什麼?

class CStudent{
public: 
    int nAge;
};

int main(){
    CStudent s ;
    s.nAge = 5;
    cout << s <<"hello";
    return 0;
}
           

可以分析得到上面的程式缺少對流插入運算符的重載,

即:

ostream & operator<<( ostream & o,const CStudent & s){
    o << s.nAge ;
    return o;
}
           

例題:

假定c是Complex複數類的對象,現在希望 寫“cout >c;”,就能從鍵 盤接受“a+bi”形式的輸入,并且使得 c.real = a,c.imag = b。

int main() {
    Complex c;
    int n;
    cin >> c >> n;
    cout << c << "," << n;
    return 0;
}

//使得程式的輸出結果是
//      13.2+133i 87   (輸入)
//      13.2+133i, 87
           

顯然需要我們補充對<<和>>運算符的重載。

完整代碼如下:

#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;
}
           

選擇題:

重載“<<”用于将自定義的對象通過cout輸出時, 以下說法哪個是正确的?

A) 可以将"<<"重載為 ostream 類的成員函數, 傳回值類型是 ostream &

B) 可以将"<<"重載為全局函數,第一個參數以 及傳回值,類型都是 ostream

C) 可以将"<<"重載為全局函數,第一個參數以 及傳回值,類型都是 ostream &

D) 可以将"<<"重載為 ostream 類的成員函數, 傳回值類型是 ostream

正确答案:C。

  • 類型轉換運算符的重載

例如:

#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; //輸出 1.2
    double n = 2 + c; //等價于 double n=2+c.operator double()
    cout << n; //輸出 3.2
}
           

上面代碼中的double就是屬于類型轉換運算符的重載,可以使得類的對象顯示或隐式的發生轉換。

  • 自增,自減運算符的重載

自增運算符++、自減運算符--有前置/後置之分,為了區分所重載的是前 置運算符還是後置運算符,C++規定:

前置運算符作為一進制運算符重載

重載為成員函數: T & operator++();

                             T & operator--();

重載為全局函數: T1 & operator++(T2);

                             T1 & operator—(T2);

後置運算符作為二進制運算符重載,多寫一個沒用的參數:

重載為成員函數: T operator++(int);

                              T operator--(int);

重載為全局函數: T1 operator++(T2,int );

                             T1 operator—( T2,int);

在沒有後置運算符重載而有前置重載的情況下,在vs中,obj++也調用前置重載,而Dev則會編譯出錯。

例題:

下面的代碼,該如何編寫CDemo類?

int main()
{
    CDemo d(5);
    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;
}


//輸出結果為
5,6,7,7
7,6,5,5
           

正确的代碼補充為:

class CDemo {
private :
    int n;
public:
    CDemo(int i=0):n(i) { }
    CDemo & operator++(); //用于前置形式
    CDemo operator++( int ); //用于後置形式
    operator int ( ) { return n; }        //這裡是一個強制類型轉換的運算符被重載
    friend CDemo & operator--(CDemo & );
    friend CDemo operator--(CDemo & ,int);
};

    CDemo & CDemo::operator++()
{ //前置 ++
    n ++;
    return * this;
} // ++s即為: s.operator++();

    CDemo CDemo::operator++( int k )
{ //後置 ++
    CDemo tmp(*this); //記錄修改前的對象
    n ++;
    return tmp; //傳回修改前的對象
} // s++即為: s.operator++(0);

    CDemo & operator--(CDemo & d)
{//前置--
    d.n--;
    return d;
} //--s即為: operator--(s);

    CDemo operator--(CDemo & d,int)
{//後置--
    CDemo tmp(d);
    d.n --;
    return tmp;
} //s--即為: operator--(s, 0);
           
  • 運算符重載的注意事項

1. C++不允許定義新的運算符 ;

2. 重載後運算符的含義應該符合日常習慣;  complex_a + complex_b  word_a > word_b  date_b = date_a + n ‘’

3. 運算符重載不改變運算符的優先級;

4. 以下運算符不能被重載:“.”、“.*”、“::”、“?:”、sizeof;

5. 重載運算符()、[]、->或者指派運算符=時,運算符重載函數必須聲明為 類的成員函數

繼續閱讀