程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> PKU C++程序設計實習 學習筆記4 運算符重載

PKU C++程序設計實習 學習筆記4 運算符重載

編輯:C++入門知識

PKU C++程序設計實習 學習筆記4 運算符重載


第四章 運算符重載

4.1 運算符重載的基本概念

1. 運算符


2. 自定義數據類型與運算符重載

C++提供了數據抽象的手段:用戶自己定義數據類型 -- 類
? 調用類的成員函數—>操作它的對象

類的成員函數—>操作對象時,很不方便
? 在數學上,兩個復數可以直接進行+/-等運算 Vs. 在C++中,直接將+或-用於復數是不允許的

3. 運算符重載

對抽象數據類型也能夠直接使用C++提供的運算符
? 程序更簡潔
? 代碼更容易理解運算符重載
? 對已有的運算符賦予多重的含義
? 使同一運算符作用於不同類型的數據時—>不同類型的行為目的
? 擴展C++中提供的運算符的適用范圍,以用於類所表示的抽象數據類型同一個運算符,對不同類型的操作數,所發生的行為不同
? (5,10i) + (4,8i) = (9,18i)
? 5 + 4 = 9運算符重載的實質是函數重載
返回值類型 operator 運算符(形參表)
{
……
}
在程序編譯時:
? 把含 運算符的表達式 —> 對 運算符函數 的調用
? 把 運算符的操作數 —> 運算符函數的 參數
? 運算符被多次重載時, 根據 實參的類型 決定調用哪個運算符函數
? 運算符可以被重載成普通函數
? 也可以被重載成類的成員函數

4. 運算符重載為普通函數

class Complex {
  public:
    Complex( double r = 0.0, double i= 0.0 ){
      real = r;
      imaginary = i;
    }
  double real; // real part
  double imaginary; // imaginary part
};

Complex operator+ (const Complex & a, const Complex & b)
{
  return Complex( a.real+b.real, a.imaginary+b.imaginary);
} // “類名(參數表)” 就代表一個對象

Complex a(1,2), b(2,3), c;
c = a + b;// 相當於什麼?  operator+(a,b)
重載為普通函數時,參數個數為運算符目數

5. 運算符重載為成員函數

class Complex {
  public:
    Complex( double r= 0.0, double m = 0.0 ):real(r), imaginary(m) { } // constructor
    Complex operator+ ( const Complex & ); // addition
    Complex operator- ( const Complex & ); // subtraction
  private:
    double real; // real part
    double imaginary; // imaginary part
};
// Overloaded addition operator
Complex Complex::operator+(const Complex & operand2) {
  return Complex( real + operand2.real,imaginary + operand2.imaginary );
}
// Overloaded subtraction operator
Complex Complex::operator- (const Complex & operand2){
  return Complex( real - operand2.real,imaginary - operand2.imaginary );
}
int main(){
 Complex x, y(4.3, 8.2), z(3.3, 1.1);
 x = y + z;// 相當於什麼?  y.operator+(z)
 x = y - z;// 相當於什麼?  y.operator-(z)
 return 0;
}
重載為成員函數時,參數個數為運算符目數減一

4.2 賦值運算符的重載

1. 賦值運算符 ‘=’ 重載

賦值運算符 兩邊的類型 可以 不匹配
? 把一個 int類型變量 賦值給一個 Complex對象
? 把一個 char * 類型的字符串 賦值給一個 字符串對象

需要 重載賦值運算符 ‘=’

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

編寫一個長度可變的字符串類String
? 包含一個char * 類型的成員變量
—> 指向動態分配的存儲空間
? 該存儲空間用於存放 ‘\0’ 結尾的字符串

class String {
  private:
    char * str;
  public:
    String () : str(NULL) { } //構造函數, 初始化str為NULL
    const char * c_str() { return str; } //返回值為const類型,保證str不會被修改。比如char* p=str.c_str();則編譯器會報錯,類型不匹配。
    char * operator = (const char * s);
    ~String( );//需要考慮String對象是否指向了動態分配的存儲空間
}; 
//重載‘=’使得obj = “hello”能夠成立
char * String::operator = (const char * s){
  if(str) delete [] str;
  if(s) { //s不為NULL才會執行拷貝
    str = new char[strlen(s)+1];
    strcpy(str, s);
  }
  else
    str = NULL;
  return str;
}
String::~String( ) {
  if(str) delete [] str;
};
int main(){
  String s;
  s = “Good Luck,” ;
  cout << s.c_str() << endl;
  // String s2 = “hello!”; //這條語句要是不注釋掉就會出錯
  s = "Shenzhou 8!";
  cout << s.c_str() << endl;
  return 0;
}

2. 重載賦值運算符的意義 – 淺復制和深復制

S1 = S2;

 

淺復制/淺拷貝
執行逐個字節的復制工作

 

\
\

S1和S2指向了同一塊動態分配內存區域,那麼當S1和S2同時消亡的時候,這一塊內存空間就會被先後釋放兩次。這樣就會導致嚴重的內存錯誤,甚至可能引發程序意外的中止。
所以我們看到,這樣的一個淺拷貝,或者淺復制的工作本身並不能實現我們所希望實現的像S2中間的這個str字串復制給S1本身指向的那一塊空間。

 

深復制/深拷貝
將一個對象中指針變量指向的內容 —> 復制到另一個對象中指針成員對象指向的地方

 

\

\

在 class MyString 裡添加成員函數:

String & operator = (const String & s) {
  if(str) delete [] str;
  str = new char[strlen(s.str)+1];
  strcpy(str, s.str);
  return * this;
}

 

3. 思考

 

考慮下面語句,是否會有問題?
MyString s; s = “Hello”; s = s;

正確寫法:

String & String::operator = (const String & s){
  if(str == s.str) return * this;//增加此行
  if(str) delete [] str;
  if(s.str) { //s.str不為NULL才會執行拷貝
    str = new char[strlen(s.str)+1];
    strcpy( str,s.str);
  }
  else
    str = NULL;
  return * this;
}

 

4. 對 operator = 返回值類型的討論

void 好不好?
考慮: a = b = c;
//等價於a.operator=(b.operator=(c));String 好不好?為什麼是 String &
運算符重載時, 好的風格 -- 盡量保留運算符原本的特性
考慮: (a=b)=c; //會修改a的值
分別等價於:(a.operator=(b)).operator=(c);

5. 上面的String類是否就沒有問題了?

 

為 String類編寫 復制構造函數 時,會面臨和 ‘=’ 同樣的問題,用同樣的方法處理

String::String(String & s)
{
  if(s.str) {
    str = new char[strlen(s.str)+1];
    strcpy(str, s.str);
  }
  else
    str = NULL;
}

4.3 運算符重載為友元函數

1. 運算符重載為友元

通常,將運算符重載為類的成員函數重載為友元函數的情況:
? 成員函數不能滿足使用要求
? 普通函數,又不能訪問類的私有成員
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);
}
經過上述重載後:
Complex c ;
c = c + 5; //有定義,相當於 c = c.operator +(5);
但是:
c = 5 + c; //編譯出錯
為了使得上述表達式能成立, 需要將+重載為普通函數
Complex operator+ (double r, const Complex & c) {
  //能解釋 5+c
  return Complex( c.real + r, c.imag);
}
普通函數不能訪問私有成員 —> 將運算符+重載為友元函數
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);
};

4.4 實例 – 長度可變的整型數組類(可變長整型數組)

\
class CArray
{
    int size; //數組元素的個數
    int
    *ptr; //指向動態分配的數組
  public:
    CArray(int s = 0); //s代表數組元素的個數
    CArray(CArray & a);
    ~CArray();
    void push_back(int v); //用於在數組尾部添加一個元素v
    CArray & operator=( const CArray & a);
    //用於數組對象間的賦值
    int length() { return size; } //返回數組元素個數
    int & CArray::operator[](inti) //返回值為 int 不行!不支持 a[i] = 4
    {//用以支持根據下標訪問數組元素,
     //如n = a[i]和a[i] = 4; 這樣的語句
      return ptr[i];
    }
};

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

因為我們不寫賦值構造函數的話,編譯器自動生成的那個賦值構造函數會執行賦值的功能。這裡所說的賦值的工作,只是把a1的成員變量賦值到a2裡面去。那a1的成員變量ptr
被賦值到a2裡面去,那自然a2.ptr就等於a1.ptr。那也就是說,a2的ptr和a1的ptr都指向了同一片存儲空間。
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;
} // CArray & CArray::operator=( const CArray & a)

void CArray::push_back(int v)
{ //在數組尾部添加一個元素
 if( ptr) {
   int * tmpPtr = new int[size+1]; //重新分配空間
   memcpy(tmpPtr,ptr,sizeof(int)*size); //拷貝原數組內容
   delete [] ptr;
   ptr = tmpPtr;
 }
 else //數組本來是空的
   ptr = new int[1];
 ptr[size++] = v; //加入新的數組元素
}

4.5 流插入運算符和流提取運算符的重載

1. 問題

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

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

2. 流插入運算符的重載

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

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

考慮,怎麼重載才能使得cout << 5; 和 cout << “this”都能成立?

有可能按以下方式重載成 ostream類的成員函數:

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

 

因為ostream已經封裝好了,不可能把這個重載寫成成員函數,所以寫成全局函數。

cout << 5 ; 即 cout.operator<<(5);
cout << “this”; 即 cout.operator<<( “this” );

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

ostream & ostream::operator<<(int n)
{
  …… //輸出n的代碼
  return * this;
}
ostream & ostream::operator<<( const char * s )
{
  …… //輸出s的代碼
  return * this;
}
cout << 5 << “this”;本質上的函數調用的形式是什麼?
cout.operator<<(5).operator<<(“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;
}

 

3. 例題

 

(教材P218)例子。可略了。

 





						

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved