1. 程式人生 > >新標準C++程序設計讀書筆記_運算符重載

新標準C++程序設計讀書筆記_運算符重載

函數 style 復制 cde span 參數 強制 sub 局限

形式

返回值類型 operator 運算符(形參表)
{
    ……
}

運算符重載

(1)運算符重載的實質是函數重載
(2)可以重載為普通函數,也可以重載為成員函數

 1 class Complex
 2 {
 3 public:
 4     double real,imag;
 5     Complex( double r = 0.0, double i= 0.0 ):real(r),imag(i) { }
 6     Complex operator-(const Complex & c);
 7 };
 8
9 Complex operator+( const Complex & a, const Complex & b) 10 { 11 return Complex( a.real + b.real, a.imag + b.imag); //返回一個臨時對象 12 } 13 14 Complex Complex::operator-(const Complex & c) 15 { 16 return Complex(real - c.real, imag - c.imag); //返回一個臨時對象 17 } 18 19 int main()
20 { 21 Complex a(4,4),b(1,1),c; 22 23 //等價於c=operator+(a,b); 24 c = a + b; 25 cout << c.real << "," << c.imag << endl; 26 27 //a-b等價於a.operator-(b) 28 cout << (a - b).real << "," << (a - b).imag << endl; 29 return 0;
30 }

(3)把含運算符的表達式轉換成對運算符函數的調用
(4)把運算符的操作數轉換成運算符函數的參數
(5)運算符被多次重載時,根據實參的類型決定調用哪個運算符函數
(6)重載為成員函數時, 參數個數為運算符目數減一;重載為普通函數時, 參數個數為運算符目數

賦值運算符 ‘ =’重載

賦值運算符“ =”只能重載為成員函數

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class String 
 6 {
 7 private:
 8     char * str;
 9 public:
10     String ():str(new char[1]) { str[0] = 0;}
11     const char * c_str() { return str; };
12     String & operator = (const char * s);
13     String::~String( ) { delete [] str; }
14 };
15 
16 String & String::operator = (const char * s)
17 { 
18     //重載“=”以使得 obj = “hello”能夠成立
19     delete [] str;
20     str = new char[strlen(s)+1];
21     strcpy( str, s);
22     return * this;
23 }
24 
25 int main()
26 {
27     String s;
28     s = "Good Luck," ; //等價於 s.operator=("Good Luck,");
29     cout << s.c_str() << endl;
30 
31     /* 這條語句要是不註釋掉就會出錯,因為這是一個初始化語句而並不是賦值語句 */
32     // String s2 = "hello!"; 
33     
34     s = "Shenzhou 8!"; //等價於 s.operator=("Shenzhou 8!");
35     cout << s.c_str() << endl;
36     return 0;
37 }

淺拷貝和深拷貝

考察下面的代碼

1 String S1, S2;
2 S1 = “this”;
3 S2 = “that”;
4 S1 = S2;

如果還用上面的運算符重載,那就會出現問題

技術分享

(1)如不定義自己的賦值運算符,那麽S1=S2實際上導致 S1.str和 S2.str指向同一地方。
(2)如果S1對象消亡,析構函數將釋放S1.str指向的空間,再訪問S2的時候就好玩兒了。
(3)另外,如果執行 S1 = "other";會導致S2.str指向的地方被delete

因此,要做如下修改:

 1 String & operator = (const String & s)
 2 {
 3     if( this == & s)
 4         return * this;
 5     
 6     delete [] str;
 7     str = new char[strlen(s.str)+1];
 8     strcpy( str,s.str);
 9     return * this;
10 }

ps:詳細完成一個完整的String類,後續再補充

運算符重載為友元函數

一般情況下,將運算符重載為類的成員函數,是較好的選擇。但有時,重載為成員函數不能滿足使用要求,重載為普通函數,又不能訪問類的私有成員,所以需要將運算符重載為友元。

 1 class Complex
 2 {
 3     double real,imag;
 4 public:
 5     Complex( double r, double i):real(r),imag(i){ };
 6     Complex operator+( double r );
 7 };
 8 
 9 Complex Complex::operator+( double r )
10 { 
11     //能解釋 c+5
12     return Complex(real + r,imag);
13 }

上面的類中重載的加號有局限性

Complex c ;
c = c + 5; //有定義,相當於 c = c.operator +(5);
但是:
c = 5 + c; //編譯出錯

為了使得上述的表達式能成立,需要將 + 重載為普通函數。但是普通函數又不能訪問私有成員,所以,需要將運算符 + 重載為友元。

 1 class Complex
 2 {
 3     double real,imag;
 4 public:
 5     Complex( double r, double i):real(r),imag(i){ };
 6     Complex operator+( double r );
 7     friend Complex operator + (double r,const Complex & c);
 8 };
 9 
10 Complex Complex::operator+( double r )
11 { 
12     //能解釋 c+5
13     return Complex(real + r,imag);
14 }
15 
16 Complex operator+ (double r,const Complex & c)
17 { 
18     //能解釋 5+c
19     return Complex( c.real + r, c.imag);
20 }

設計一個變長數組類

想要達到下面目的

 1 int main() 
 2 { 
 3     CArray a; //開始裏的數組是空的
 4     /*
 5         要用動態分配的內存來存放數組元素,需要一個指針成員變量
 6      */
 7     for( int i = 0;i < 5;++i)
 8         a.push_back(i);
 9 
10     CArray a2,a3;
11     /*
12         要重載“=”
13      */
14     a2 = a;
15     for( int i = 0; i < a.length(); ++i )
16         /*
17             要重載“[ ]”
18          */
19         cout << a2[i] << " " ;
20 
21     a2 = a3; //a2是空的
22     for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0
23         cout << a2[i] << " ";
24     cout << endl;
25 
26     a[3] = 100;
27     /*
28         要自己寫復制構造函數
29      */
30     CArray a4(a);
31     for( int i = 0; i < a4.length(); ++i )
32         cout << a4[i] << " ";
33 
34     return 0;
35 }

類的設計如下:

 1 class CArray {
 2     int size; //數組元素的個數
 3     int *ptr; //指向動態分配的數組
 4 public:
 5     CArray(int s = 0); //s代表數組元素的個數
 6     CArray(CArray & a);
 7     ~CArray();
 8     void push_back(int v); //用於在數組尾部添加一個元素v
 9     CArray & operator=( const CArray & a);
10     //用於數組對象間的賦值
11     int length() { return size; } //返回數組元素個數
12     int & CArray::operator[](int i) //返回值為 int 不行!不支持 a[i] = 4
13     {
14         //用以支持根據下標訪問數組元素,
15         // 如n = a[i] 和a[i] = 4; 這樣的語句
16         return ptr[i];
17     }
18 };
19 
20 CArray::CArray(int s):size(s)
21 {
22     if( s == 0)
23         ptr = NULL;
24     else
25         ptr = new int[s];
26 }
27 
28 CArray::CArray(CArray & a) 
29 {
30     if( !a.ptr) {
31         ptr = NULL;
32         size = 0;
33         return;
34     }
35     ptr = new int[a.size];
36     memcpy( ptr, a.ptr, sizeof(int ) * a.size);
37     size = a.size;
38 }
39 
40 CArray::~CArray()
41 {
42     if( ptr) delete [] ptr;
43 }
44 
45 CArray & CArray::operator=( const CArray & a)
46 { 
47     //賦值號的作用是使“=”左邊對象裏存放的數組,大小和內容都和右邊的對象一樣
48     if( ptr == a.ptr) //防止a=a這樣的賦值導致出錯
49         return * this;
50 
51     if( a.ptr == NULL) { //如果a裏面的數組是空的
52         if( ptr ) 
53             delete [] ptr;
54         ptr = NULL;
55         size = 0;
56         return * this;
57     }
58 
59     if( size < a.size) { //如果原有空間夠大,就不用分配新的空間
60         if(ptr)
61             delete [] ptr;
62         ptr = new int[a.size];
63     }
64 
65     memcpy( ptr, a.ptr, sizeof(int) * a.size);
66     size = a.size;
67     return * this;
68 } 
69 
70 void CArray::push_back(int v)
71 { 
72     //在數組尾部添加一個元素
73     if( ptr) {
74         int * tmpPtr = new int[size + 1]; //重新分配空間
75         memcpy(tmpPtr, ptr, sizeof(int) * size); //拷貝原數組內容
76         delete [] ptr;
77         ptr = tmpPtr;
78     }
79     else //數組本來是空的
80         ptr = new int[1];
81 
82     ptr[size++] = v; //加入新的數組元素
83 }

流運算符的重載

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

 1 class Complex 
 2 {
 3     double real,imag;
 4 public:
 5     Complex( double r=0, double i=0):real(r),imag(i){ };
 6     friend ostream & operator<<( ostream & os, const Complex & c);
 7     friend istream & operator>>( istream & is,Complex & c);
 8 };
 9 
10 ostream & operator<<( ostream & os,const Complex & c)
11 {
12     os << c.real << "+" << c.imag << "i"; //以"a+bi"的形式輸出
13     return os;
14 }
15 
16 istream & operator>>( istream & is,Complex & c)
17 {
18     string s;
19     is >> s; //將"a+bi"作為字符串讀入, “a+bi” 中間不能有空格
20     int pos = s.find("+", 0);
21     string sTmp = s.substr(0, pos); //分離出代表實部的字符串
22     c.real = atof(sTmp.c_str()); //atof庫函數能將const char*指針指向的內容轉換成 float
23     sTmp = s.substr(pos+1, s.length()-pos-2); //分離出代表虛部的字符串
24     c.imag = atof(sTmp.c_str());
25     return is;
26 }

類型轉換運算符

類型強制轉換運算符被重載時不能寫返回值類型,實際上其返回值類型就是該類型強制轉換運算符代表的類型

 1 #include <iostream>
 2 using namespace std;
 3 class Complex
 4 {
 5     double real,imag;
 6 public:
 7     Complex(double r=0,double i=0):real(r),imag(i) { };
 8     //重載強制類型轉換運算符 double
 9     operator double () { return real; }
10 
11 };
12 
13 int main()
14 {
15     Complex c(1.2,3.4);
16     cout << (double)c << endl; //輸出 1.2
17     double n = 2 + c; //等價於 double n=2+c.operator double()
18     cout << n; //輸出 3.2
19 }

自增,自減運算符的重載

1、前置運算符作為一元運算符重載
(1)重載為成員函數:
T & operator++();
T & operator--();
(2)重載為全局函數:
T1 & operator++(T2);
T1 & operator—(T2);

2、後置運算符作為二元運算符重載,多寫一個沒用的參數
(1)重載為成員函數:
T operator++(int);
T operator--(int);
(2)重載為全局函數:
T1 operator++(T2,int );
T1 operator—( T2,int);

 1 class CDemo 
 2 {
 3 private:
 4     int n;
 5 public:
 6     CDemo(int i=0):n(i) { }
 7     CDemo & operator++(); //用於前置形式
 8     CDemo operator++( int ); //用於後置形式
 9     operator int ( ) { return n; }
10     friend CDemo & operator--(CDemo & );
11     friend CDemo operator--(CDemo & ,int);
12 };
13 
14 CDemo & CDemo::operator++()
15 { 
16     //前置 ++
17     n++;
18     return * this;
19 } // ++s即為: s.operator++();
20 
21 CDemo CDemo::operator++( int k )
22 { 
23     //後置 ++
24     CDemo tmp(* this); //記錄修改前的對象
25     n++;
26     return tmp; //返回修改前的對象
27 } // s++即為: s.operator++(0);
28 
29 CDemo & operator--(CDemo & d)
30 {
31     //前置--
32     d.n--;
33     return d;
34 } //--s即為: operator--(s);
35 
36 CDemo operator--(CDemo & d,int)
37 {
38     //後置--
39     CDemo tmp(d);
40     d.n--;
41     return tmp;
42 } //s--即為: operator--(s, 0);
43 
44 int main()
45 {
46     CDemo d(5);
47     cout << (d++ ) << ","; //等價於 d.operator++(0);
48     cout << d << ",";
49     cout << (++d) << ","; //等價於 d.operator++();
50     cout << d << endl;
51     cout << (d-- ) << ","; //等價於 operator--(d,0);
52     cout << d << ",";
53     cout << (--d) << ","; //等價於 operator--(d);
54     cout << d << endl;
55     return 0;
56 }

運算符重載的註意事項
(1)C++不允許定義新的運算符 ;
(2)重載後運算符的含義應該符合日常習慣;
complex_a + complex_b
word_a > word_b
date_b = date_a + n
(3)運算符重載不改變運算符的優先級;
(4)以下運算符不能被重載:“.” 、“.*” 、“::” 、“?:” 、 sizeof;
(5)重載運算符()、 []、 ->或者賦值運算符=時,運算符重載函數必須聲明為類的成員函數。

新標準C++程序設計讀書筆記_運算符重載