天天看點

C++:12---運算符重載

一、概念

  • 對已有的運算符重新進行定義,賦予其另一種功能,以适應不同的資料類型
  • 重載的運算符是具有特殊名字的函數,該函數也有傳回值、參數清單、函數體

二、運算符重載的3種實作方式

  • 成員函數:私有、公有、保護都可以
  • 友元函數:同上
  • 全局函數:隻能通路公有的

三、運算符重載的規則

         C++98,C++0x,C++11對“哪些運算符重載能夠重載”有一緻的規定。詳細例如以下:

  • 以下運算符不支援重載:.(類屬運算符),::(作用域運算符),sizeof,?:(三目運算符),#(預處理運算符)
  • 隻能用成員函數重載的運算符:=(指派運算符)、()(強制類型轉換)、[]、new、delete、->
  • 隻能用友元、全局函數重載的運算符:<<、>>
  • +=、-=、=運算符傳回值為引用類型(&):函數執行完之後傳回的是*this(對象本身)。如果不加&,則傳回的是臨時對象
  • 承接上一注意事項:前置++、--,傳回值為引用。後置++、--,傳回值不為引用

四、+、-、*、/、+=、-=的重載

  • //隻實作+、*,+=。其它原理都相同
class Cperson
{
private:
int data;
public:
Cperson operator+(Cperson const& other)const;//類成員函數實作
friend Cperson operator*(Cperson const& p1,Cperson const& p2);//友元函數方式實作
Cperson& operator+=(Cperson const& other);
};
Cperson Cperson::operator+(Cperson const& other)const
{
Cperson temp;
temp.data=this->data+other.data;
return temp;
}


Cperson operator*(Cperson const& p1,Cperson const& p2)
{
Cperson temp;
temp.data=p1.data*p2.data;
return temp;
}


Cperson& Cperson::operator+=(Cperson const& other)
{
Cperson temp=*this=other;//因為前面已經實作了+運算符,此處直接使用
return *this=temp;
}      

五、 指派運算符(=)的重載​

  • 指派運算符的重載,傳回值應該為運算符左側對象的一個引用,否則傳回的是一個臨時對象
  • 如果沒有寫指派重載運算符,編譯器自動存在一個預設的,就是拷貝構造中所用到的預設拷貝構造,但是如果類成員變量中含有動态記憶體的變量時,需要重載指派運算符
class Cperson
{
private:
char* name;
public:
Cperson& operator=(Cperson const& other);
};
Cperson& Cperson::operator=(Cperson const& other)
{
if(this->name)//先釋放原先的資料
delete[] name;
if(other)//如果傳入的參數不為空
{
int len=strlen(other.name);
this->name=new char[len+1];
strcpy(this->name,other.name);
}
else//如果傳入的參數為空
{
other.name=nullptr;
}
};      

拷貝構造函數與拷貝指派運算符的關系​

  • 拷貝構造函數是用另一個對象來初始化一塊記憶體區域,這塊記憶體就是新對象的記憶體區
  • 指派函數是對于一個已經被初始化的對象來進行operator=操作。例如:
class A;
A a;
A b = a; // 調用拷貝構造函數, 因為b是第一次初始化
A c(a);  // 調用拷貝構造函數, 因為c是第一次初始化
b = c;   // 調用指派運算符, 因為b已經初始化過了      

六、 ++、--運算符的重載​

  • 為了差別前置和後置:後置++的參數為int,前置++無參數
  • 前置++、--,傳回值為引用。後置++、--,傳回值不為引用
  • ++、--(前置後置),下面隻示範++的操作
class Cperson
{
private:
int data;
public:
Cperson& operator++();//前置++,傳回值為引用
Cperson operator++(int);//後置++,傳回值不為引用
};
Cperson& Cperson::operator++()
{
data++;
return *this;
}
Cperson Cperson::operator++(int)
{
Cperson temp=*this;
data++;
return temp;
}      

七、==、!=運算符的重載

  • 用途:這兩個運算符用來判斷兩個類對象中資料成員的值是否相等/不相等(可以在重載中判斷單/多個成員是否相等)

設計規則:

  • 通常情況下,==運算符應該具有傳遞性,例如:如果a==b和b==c,那麼a==c應該也為真
  • 如果實作了==運算符,則!=運算符可以直接在return語句中應用剛才實作的==運算符來簡化書寫
  • 如果用成員函數實作隻能有一個參數,用友元、全局函數實作是兩個參數
class Cperson
{
private:
int id;
int age;
public:
Cperson(int Id, int Age) :id(Id), age(Age) {}
int getId()const;
int getAge()const;
friend bool operator==(const Cperson &p1, const Cperson &p2)
{
return ((p1.getId() == p2.getId()) && (p1.getAge() == p2.getAge()));
}
friend bool operator!=(const Cperson &p1, const Cperson &p2)
{
return !(p1 == p2);
}
};
int Cperson::getId()const { return id; }
int Cperson::getAge()const { return age; }      

八、 >>、<<運算符的重載

  • 輸入輸出運算符的重載不能用成員函數實作,一般用友元實作

重載輸出運算符<<

參數:

  • 參數1:一個非常量ostream對象的引用(ostream是非常量是因為向流寫入内容會改變其狀态)
  • 參數2:一般來說是一個常量的引用,該常量是我們想要列印的類類型(使用引用的原因是我們希望避免複制實參。使用常量是因為不會改變對象的内容)

傳回值:傳回它的ostream形參

重載輸入運算符>>

參數:

  • 參數1:是運算符将要讀取的流的引用
  • 參數2:将要讀入到的(非常量)的引用(使用非常量是因為輸入運算符本身的目的就是将資料讀入到這個對象中)

傳回值:某個給定流的引用

輸入時可能産生的錯誤:

  • 輸入的類型不符
  • 當讀取操作達到檔案末尾或者遇到輸入流的其它錯誤時也會失敗
class Cpoint
{
private:
int x;
int y;
public:
friend ostream& operator<<(ostream& os, const CMyPoint const& pt);//輸出流
friend istream& operator>>(ostream& is, const CMyPoint const& pt);//輸入流
}
ostream& operator<<(ostream& os, const CMyPoint const& pt)
{
os<<pt.x<<pt.y;
return os;
}
istream& operator>>(ostream& is,const CMyPoint const& pt)
{
is>>pt.x>>pt.y;
return is;
}      

九、[]下标運算符的重載

下标操作符 [] 通常用于通路數組元素。重載該運算符用于增強操作 C++ 數組的功能。

下面的執行個體示範了如何重載下标運算符 []。

#include <iostream>
using namespace std;
const int SIZE = 10;


class Safearray
{
   private:
      int arr[SIZE];
   public:
      Safearray() 
      {
         register int i;
         for(i = 0; i < SIZE; i++)
         {
           arr[i] = i;
         }
      }
      int& operator[](int i)
      {
          if( i > SIZE )
          {
              cout << "索引超過最大值" <<endl; 
              // 傳回第一個元素
              return arr[0];
          }
          return arr[i];
      }
};
int main()
{
   Safearray A; 
   cout << "A[2] 的值為 : " << A[2] <<endl;
   cout << "A[5] 的值為 : " << A[5]<<endl;
   cout << "A[12] 的值為 : " << A[12]<<endl; 
   return 0;
}      

十、->成員通路運算符的重載

類成員通路運算符( -> )可以被重載,但它較為麻煩。它被定義用于為一個類賦予"指針"行為。運算符 -> 必須是一個成員函數。如果使用了 -> 運算符,傳回類型必須是指針或者是類的對象。

運算符 -> 通常與指針引用運算符 * 結合使用,用于實作"智能指針"的功能。這些指針是行為與正常指針相似的對象,唯一不同的是,當您通過指針通路對象時,它們會執行其他的任務。比如,當指針銷毀時,或者當指針指向另一個對象時,會自動删除對象。

間接引用運算符 -> 可被定義為一個一進制字尾運算符。也就是說,給出一個類:

classPtr{
   //...
   X *operator->();};      

類 Ptr 的對象可用于通路類 X 的成員,使用方式與指針的用法十分相似。例如:

void f(Ptr p ){
   p->m =10;// (p.operator->())->m = 10}      

語句 p->m 被解釋為 (p.operator->())->m。同樣地,下面的執行個體示範了如何重載類成員通路運算符 ->。

#include <iostream>
#include <vector>
using namespace std;
 
// 假設一個實際的類
class Obj {
   static int i, j;
public:
   void f() const { cout << i++ << endl; }
   void g() const { cout << j++ << endl; }
};
 
// 靜态成員定義
int Obj::i = 10;
int Obj::j = 12;
 
// 為上面的類實作一個容器
class ObjContainer {
   vector<Obj*> a;
public:
   void add(Obj* obj)
{ 
      a.push_back(obj);  // 調用向量的标準方法
   }
   friend class SmartPointer;
};
 
// 實作智能指針,用于通路類 Obj 的成員
class SmartPointer {
   ObjContainer oc;
   int index;
public:
   SmartPointer(ObjContainer& objc)
   { 
       oc = objc;
       index = 0;
   }
   // 傳回值表示清單結束
   bool operator++() // 字首版本
   { 
     if(index >= oc.a.size() - 1) return false;
     if(oc.a[++index] == 0) return false;
     return true;
   }
   bool operator++(int) // 字尾版本
   { 
      return operator++();
   }
   // 重載運算符 ->
   Obj* operator->() const 
   {
     if(!oc.a[index])
     {
        cout << "Zero value";
        return (Obj*)0;
     }
     return oc.a[index];
   }
};
 
int main() {
   const int sz = 10;
   Obj o[sz];
   ObjContainer oc;
   for(int i = 0; i < sz; i++)
   {
       oc.add(&o[i]);
   }
   SmartPointer sp(oc); // 建立一個疊代器
   do {
      sp->f(); // 智能指針調用
      sp->g();
   } while(sp++);
   return 0;
}      

十一、()函數調用運算符的重載

  • 如果類重載了函數調用運算符,那麼我們在使用該對象時就如同調用一個函數一樣
  • 注意:()運算符與對象初始化時調用構造函數不是一個東西、是以()函數調用運算符不能再類初始化時使用,會與構造函數沖突
struct absInt {
bool operator()(int value) {
return value < 0 ? true : false;
}
};
int main()
{
absInt a;
printf("%d\n", a(-1));//列印1
return 0;
}