C++ 大學MOOC 北大課程(郭煒老師)聽課整理 第四周(運算符重載)

運算符重載基本概念

1)目的是拓展原C程序運算符的作用範圍,使程序看起來更加簡潔
2)本質是函數,可以稱之爲運算符函數
3)可以定義爲普通函數,也可定義爲成員函數
4)把含運算符的表達式轉換成函數的調用
5)運算符操作數轉換爲函數的參數
6)運算符函數可以重載,調用時根據參數類型選擇
例如:

class complex{
public:
 double real, imag;
 complex(double r = 0.0, double i = 0.0) :real(r), imag(i){}
 complex operator - (const complex & r);
};
complex operator+(const complex &c1, const complex &c2){
 return complex(c1.real+c2.real,c1.imag+c2.imag);
}
complex complex::operator-(const complex &r){
 return complex(real-r.real,imag-r.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.operator-(b);
 return 0;
}

賦值運算符重載

1)考慮需要將兩個不同類型的變量相互賦值,比如將 char* 類型的字符串常量賦值給字符串對象 complex
2)只能是成員函數
如示例:實現 s=“hello" 功能

class String{
private:
 char * str;
public:
 String() :str(new char[1]){
  str[0] = 0;
 }
 ~String(){
  delete[] str;//析構函數 負責當對象消亡時將str指向的空間釋放
 }
 const char * c_str(){
  return str;
 }
 String& operator=(char * s);//聲明賦值運算符成員函數
};
String& String::operator=(char* s){//主義函數返回值爲對象的引用
 delete[]str;//先將成員指針str指向的空間delete掉
 str = new char[strlen(s) + 1];//再new出一片新的字符數組內存空間,空間大小爲 s指向的空間大小加一
 strcpy(str, s);//然後將S指向的空間複製到str指向的空間 完成賦值
 return *this;//最後返回被賦值的對象
}
int main(){
 String s1, s2;
 s1 = "hello";
 s2 = "nice to meet you";
 cout << s1.c_str() << endl << s2.c_str() << endl;
 return 0;
}

輸出:

hello
nice to meet you

3)如果需要實現 s1=s2 的賦值語句,則上訴代碼存在問題
4)我們希望將s2.str指向的空間賦值給s1.str指向的空間,而單純 s1=s2的結果是使得s1.str與s2.str相同,即s1.str與s2.str同指向同一片內存空間
5)這樣就會導致當s1消亡時,及時s2沒有消亡,但s2.str所指向的空間已經被析構函數釋放了,這樣不妥
6)改進方式:重新定義類string的賦值運算符函數
例如:

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

7)如果出現 s1=s1 的語句 則上訴代碼會出現問題
8)這個語句會使得 s1.str 所指向的空間先釋放掉 從而導致無法進行後面的賦值
所以改進:

String& String::operator=(const String & r){
 if (this==&r)//判斷賦值語句兩端的對象是否相同
  return *this;
 delete[] str;
 str = new char[strlen(r.str) + 1];
 strcpy(str, r.str);
 return *this;
}

9)如果出現調用複製構造函數的情況,就會出現如上訴 s1=s2 一樣的問題
10)需要重新定義新的複製構造函數
例如:

String(const String& e){
  str = new bar[strlen(e.str)+1];
  strcpy(str,e.str);
 }

11)爲了實現類似 (s1=s2)=s3(使得s1=s3) 的功能,賦值運算符函數的返回值需要是對象的引用(只有返回值是引用的函數可以放在等號的左側)

運算符重載爲友元函數

1)有時需要將運算符函數定義爲普通函數
例如:

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

2)如上的運算符函數operator+可以解釋類似於 c=c+5; 的代碼,但不能解釋類似於 c=5+c 的代碼
3)所以需要將運算符函數定義爲普通函數來實現上訴功能
例如:

complex operator+(double c,const complex & s){
 return complex(s.real+c,s.imag);
}

4)但爲了使得普通函數可以訪問對象的私有成員變量,需要將其聲明爲友元
例如:

class complex{
 double real, imag;
public:
 complex(double r, double i) :real(r), imag(i){}
 void c_print(){
  cout << real << ',' << imag << endl;
 }
 complex operator+(double r){
  return complex(real+r,imag);
 }
 friend complex operator+(double c, const complex & s);
};

賦值運算符重載應用 可變長數組實現

1)在應用中,如何能使得一個數組的長度可以改變,例如可以實現如下整型數組類的功能

int main(){
 CArray a;//定義一個類CArray對象,其代表一個空數組(需要定義構造函數)
 for (int i = 0; i < 5;++i)
  a.push_back(i);//可以通過函數爲數組向後增加一個整型元素 i(需要定義push_back函數)
 CArray a2, a3;
 a2 = a;//可以通過賦運算符 = 將 a 數組內容賦值給 a2 數組內容(需要賦值運算符重載函數)
 for (int i = 0; i < a2.length(); ++i)//可以用函數返回數組長度(需要定義length函數)
  cout << a2[i] << " ";//可以通過[]運算符返回數組對應元素的值(需要定義[]運算符重載函數)
 a2 = a3;
 for (int i = 0; i < a2.length(); ++i)
  cout << a2[i] << " ";
 cout << endl;
 a[3] = 100;//可以通過[]運算符進行對數組對應元素賦值的工作
 CArray a4(a);//可以通過構造函數完成兩數組內容的複製(需要定義複製構造函數)
 for (int i = 0; i < a4.length(); ++i)
  cout << a4[i] << " ";
 return 0;
}

2)類 CArray 定義如下

class CArray{
private:
 int size;//用於儲存數組元素的個數
 int * p;//用於指向整型數組空間
public:
 CArray(int s = 0){//構造函數可以根據所給參數完成數組空間的分配
  size = s;//儲存數組元素的個數
  if (s == 0)//當參數爲零 則代表數組爲空
   p = NULL;//指針置空
  else//否則 數組元素爲 s 
   p = new int[s];//動態分配一片大小爲sizeof(int)*s的空間 將其地址賦值給指針
 }
 CArray(CArray& p1){//複製構造函數
  if (!p1.p){//如果要複製的數組爲空
   p = NULL;//則指針置空
   size = 0;//元素數爲零
   return;//返回
  }
  p = new int[p1.size];//否則 重新分配空間
  memcpy(p, p1.p, sizeof(int)*p1.size);//並完成內存字節的複製
  size = p1.size;//儲存元素數
 }
 ~CArray(){//析構函數
  if (p)//如果指針不爲空
   delete[] p;//則將所指空間釋放
 }
 void push_back(int i);//push_back函數聲明
 int length();//length()函數聲明
 CArray & operator=(const CArray p1);//賦值運算符重載函數聲明
 int & operator[](int i){//[]運算符重載函數 返回是int &(爲使得a[1]=3合法)
  return p[i];//返回相應數組元素 引用使得返回值和數組元素一回事
 }
};

3)push_back函數

void CArray::push_back(int i){//再數組後再增加一個元素 i
 if (p){//判斷原數組不爲空
  int * tmp;//定義一箇中間指針
  tmp = new int[size + 1];//使中間指針指向一個大小比原數組大一個元素的數組空間
  memcpy(tmp, p, sizeof(int)*size);//將原數組內容複製到中間指針指向的數組空間
  delete[]p;//將原數組空間釋放
  p = tmp;//將中間指針賦值給作用數組 此時原數組的空間多了一個元素空間的大小
 }
 else//原數組爲空
  p = new int[1];//則直接動態分配一個元素大小的空間
 p[size++] = i;//將size自加 並將最後的元素賦值爲 i
}

4)length()函數

int CArray::length(){
 return size;//直接返回size
}

5)賦值運算符重載函數

CArray & CArray::operator=(const CArray p1){
 if (p == p1.p)//判斷賦值運算符兩端爲同一個對象
  return *this;//則直接返回
 if (p1.p == NULL){//判斷賦值右端數組爲空數組 則將被賦值數組置空
  if (p)//判斷被賦值數組不是空
   delete[] p;//則將所指空間釋放
  p = NULL;//再將指針置空
  size = 0;//然後將元素數置零
  return *this;//最後返回
 }
 if (size < p1.size){//判斷原數組空間大小比賦值數組小
  if (p)//原數組不爲空
   delete[] p;//則將所指空間釋放
  p = new int[p1.size];//並重新分配一個與賦值數組空間一樣大小的空間
 }
 memcpy(p, p1.p, sizeof(int)*p1.size);//空間分配後 或空間夠用 則完成兩數組空間內容的複製
 size = p1.size;//修改數組元素數
 return *this;//最後返回
}

左移右移運算符重載

1)cout是 iostream 文件中定義的 ostream類的對象
2)cin是istream類的對象
3)運算符"<<",">>“分別在ostream類與istream類中進行了重載
4)故"cout<<5"的調用形式是"cout.operator(5)”
5)爲了能夠實現類似於"cout<<5<<“hello”"的語句,重載函數ostream::operator()的返回值類型應該是ostream類的引用:ostream &
例1:實現如下代碼 使能夠輸出 5hello

class student{
public:
 int age;
};
ostream & operator<<(ostream & o, const student & s){//需要定義爲普通函數 函數參數等於運算符操作數(2) 爲了減小開銷函數參數使用對象的引用
 o << s.age;
 return o;
}
int main(){
 student s;
 s.age = 5;
 cout << s << "hello";
 return 0;
}

輸出:

5hello

例2:假定c是complex複數類的對象,現在希望寫"cout<<c",就能以"a+bi"的形式輸出c的值,寫"cin>>c",就能從鍵盤中讀入"a+bi"並賦值給c,使得c.real=a,c.imag=b。

class complex{
private:
 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'  ;
 return os;
}
istream& operator>>(istream & is, complex& c){
 string s;
 is >> s;
 int pos = s.find('+',0);  //find()函數 找到字符'+'在字符串中的位置
 string stmp = s.substr(0, pos);  //將'+'前的字符串剝離出
 c.real = atof(stmp.c_str());  //將剝離出的字符串轉換成flaot類型
 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;
}

輸入:

123+13.1i 456

輸出:

123+13.1i 456

重載類型轉換運算符

1)重載的類型轉換運算符有顯性和隱性
2)聲明定義時不用寫返回值類型 因爲返回值類型和運算符相同
3)單目運算符 操作數爲一 聲明成員函數時不用寫參數

class complex{
private:
 double real, imag;
public:
 complex(double r = 0, double i = 0) :real(r), imag(i){}
 operator double(){
  return real;
 }
};
int main(){
 complex c(1.2, 3.2);
 cout << (double)c << endl;//顯性作用 輸出1.2
 double n = 2 + c;//隱性作用 等價於2+c.operator double ();
 cout << n << endl;//輸出3.2
 return 0;
}

輸出:

1.2
3.2

自增自減運算符重載

1)自增自減運算符有前置和後置之分
2)C++規定 前置時爲一元運算符 後置時爲二元運算符
3)前置運算符重載函數返回引用
例如:

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 i){//後置++
 CDemo tmp(*this);//保存修改前的值
 n++;
 return tmp;//返回修改前的值
}//s++等價於s.operator(0)
CDemo & operator--(CDemo & r){//前置-- 參數得是引用
 r.n--;
 return r;//返回修改後的對象
}//--s等價於s.operator(s)
CDemo operator--(CDemo & r, int k){//後置--
 CDemo tmp(r);//保存修改前的值
 r.n--;//修改
 return tmp;//返回修改前的值
}//s++等價於s.operator(s,0)
int main(){
 CDemo d(5);
 cout << d++ << ',';
 cout << d << ',';
 cout << ++d << ',';
 cout << d << endl;
 cout << d-- << ',';
 cout << d << ',';
 cout << --d << ',';
 cout << d << endl;
 return 0;
}

輸出:

5,6,7,7
7,6,5,5
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章