C++的算符重載

來源:互聯網
上載者:User
算符重載的作用是什嗎?它允許你為類的使用者提供一個直覺的介面。 算符重載允許C/C++的運算子在使用者定義型別(類)上擁有一個使用者定義的意義。重載的算符是函數調用的文法修飾:  
class Fred {
 public:    // …
};

#if 0           // 沒有算符重載:
Fred add(Fred, Fred);
Fred mul(Fred, Fred);

Fred f(Fred a, Fred b, Fred c)
{
 return add(add(mul(a,b), mul(b,c)), mul(c,a)); // 哈哈,多可笑…
}

#else   // 有算符重載:
Fred operator+ (Fred, Fred);
Fred operator* (Fred, Fred);

Fred f(Fred a, Fred b, Fred c)
{
 return a*b + b*c + c*a;
}

#endif

  算符重載的好處是什嗎?

  通過重載類上的標準算符,你可以發掘類的使用者的直覺。使得使用者程式所用的語言是面向問題的,而不是面向機器的。 最終目標是降低學習曲線並減少錯誤率。

  有什麼算符重載的執行個體?這裡有一些算符重載的執行個體:

myString + yourString 可以串連兩個 std::string 對象

myDate++ 可以增加一個 Date 對象

a * b 可以將兩個 Number 對象相乘

a[i] 可以訪問 Array 對象的某個元素

x = *p 可以反引用一個實際“指向”一個磁碟記錄的 "smart pointer" —— 它實際上在磁碟上定位到 p 所指向的記錄並返回給x。

  但是算符重載使得我的類很醜陋;難道它不是應該使我的類更清晰嗎?算符重載使得類的使用者的工作更簡易,而不是為類的開發人員服務的! 考慮一下如下的例子:
class Array {
 public:
int& operator[] (unsigned i);
};

inline
int& Array::operator[] (unsigned i)
{   // …
}
  有些人不喜歡operator關鍵字或類體內的有些古怪的文法。但是算符重載文法不是被期望用來使得類的開發人員的工作更簡易。它被期望用來使得類的使用者的工作更簡易:  
int main()
{
 Array a;
 a[3] = 4; // 使用者代碼應該明顯而且易懂…
}
  記住:在一個面向重用的世界中,使用你的類的人有很多,而建造它的人只有一個(你自己);因此你做任何事都應該照顧多數而不是少數。

  什麼算符能/不能被重載?大多數都可以被重載。C的算符中只有 . 和 ? :(以及sizeof,技術上可以看作一個算符)。C++增加了一些自己的算符,除了::和.*,大多數都可以被重載。 這是一個下標算符的樣本(它返回一個引用)。先沒有算符重載:
class Array {
public:
int& elem(unsigned i) {  if (I > 99) error(); return data[i]; }
 private:
 int data[100];
};
int main()
{
 Array a;
 a.elem(10) = 42;
 a.elem(12) += a.elem(13);
}
  現在用算符重載給出同樣的邏輯:
class Array {
public:
int& operator[] (unsigned i)  {  if (I > 99) error(); return data[i]; }
 private:
 int data[100];
};
int main()
{
 Array a;
 a[10] = 42;
 a[12] += a[13];
}

  我能重載 operator== 以便比較兩個 char[] 來進行字串比較嗎?不行:被重載的算符,至少一個運算元必須是使用者定義型別(大多數時候是類)。 但即使C++允許,也不要這樣做。因為在此處你應該使用類似 std::string的類而不是字元數組,因為數組是有害的。因此無論如何你都不會想那樣做的。

  我能為“冪”運算建立一個 operator** 嗎?不行。 運算子的名稱、優先順序、結合性以及元數都是由語言固定的。在C++中沒有operator**,因此你不能為類類型建立它。

  如果還有疑問,考慮一下x ** y與x * (*y)等同(換句話說,編譯器假定 y 是一個指標)。此外,算符重載只不過是函數調用的文法修飾。雖然這種特殊的文法修飾非常美妙,但它沒有增加任何本質的東西。我建議你重載pow(base,exponent)(雙精確度版本在中)。

  順便提一下,operator^可以成為冪運算,只是優先順序和結合性是錯誤的。

  如何為Matrix(矩陣)類建立下標運算子? [Recently changed so it uses new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]

用 operator()而不是operator[]。

  當有多個下標時,最清晰的方式是使用operator()而不是operator[]。原因是operator[]總是帶一個參數,而operator()可以帶任何數目的參數(在矩形的矩陣情況下,需要兩個參數)。

  如:
class Matrix {
 public:
 Matrix(unsigned rows, unsigned cols);
 double& operator() (unsigned row, unsigned col);
 double operator() (unsigned row, unsigned col) const; // …
 Matrix(); // 解構函式
 Matrix(const Matrix& m); // 拷貝建構函式
 Matrix& operator= (const Matrix& m); // 賦值算符   // …
 private:
 unsigned rows_, cols_;
 double* data_;
};
inline
Matrix::Matrix(unsigned rows, unsigned cols)
: rows_ (rows),
cols_ (cols),
data_ (new double[rows * cols])
{
 if (rows == 0 || cols == 0)
 throw BadIndex("Matrix constructor has 0 size");
}

inline
Matrix::~Matrix()
{
 delete[] data_;
}

inline
double& Matrix::operator() (unsigned row, unsigned col)
{
 if (row >= rows_ || col >= cols_)
 throw BadIndex("Matrix subscript out of bounds");
 return data_[cols_*row + col];
}

inline
double Matrix::operator() (unsigned row, unsigned col) const
{
 if (row >= rows_ || col >= cols_)
 throw BadIndex("const Matrix subscript out of bounds");
 return data_[cols_*row + col];
}
  然後,你可以使用m(I,j)來訪問Matrix m 的元素,而不是m[i][j]:

int main()
{
 Matrix m(10,10);
 m(5,8) = 106.15;
 std::cout << m(5,8);  // …
}

  為什麼Matrix(矩陣)類的介面不應該象數組的數組?本FAQ其實是關於:某些人建立的Matrix 類,帶有一個返回 Array 對象的引用的operator[]。而該Array 對象也帶有一個 operator[] ,它返回Matrix的一個元素(例如,一個double的引用)。因此,他們使用類似m[i][j]的文法來訪問矩陣的元素,而不是象m(I,j)的文法。

  數組的數組方案顯然可以工作,但相對於operator()方法來說,缺乏靈活性。尤其是,用[][]方法很難表現的時候,用operator()方法可以很簡單的完成,因此[][]方法很可能導致差勁的表現,至少某些情況細是這樣的。

  例如,實現[][]方法的最簡單途徑就是使用作為密集矩陣的,以以行為主的形式儲存(或以列為主,我記不清了)的物理布局。相反,operator() 方法完全隱藏了矩陣的物理布局,在這種情況下,它可能帶來更好的表現。

  可以這麼認為:operator()方法永遠不比[][]方法差,有時更好。

  operator() 永遠不差,是因為用operator()方法實現以行為主的密集矩陣的物理布局非常容易。因此,當從效能觀點出發,那樣的結構正好是最佳布局時,operator()方法也和[][]方法一樣簡單(也許operator()方法更容易一點點,但我不想誇大其詞)。 Operator() 方法有時更好,是因為當對於給定的應用,有其它比以行為主的密集矩陣更好的布局時,用 operator() 方法比[][]方法實現會容易得多。 作為一個物理布局使得實現困難的例子,最近的項目發生在以列訪問矩陣元素(也就是,演算法訪問一列中的所有元素,然後是另一列等),如果物理布局是以行為主的,對矩陣的訪問可能會“cache失效”。例如,如果行的大小几乎和處理器的cache大小相當,那麼對每個元素的訪問,都會發生“cache不命中”。在這個特殊的項目中,我們通過將映射從邏輯布局(行,列)變為物理布局(列,行),效能得到了20%的提升。

  當然,還有很多這類事情的例子,而疏鬆陣列在這個問題中則是又一類例子。通常,使用operator()方法實現一個疏鬆陣列或交換行/列順序更容易,operator()方法不會損失什麼,而可能獲得一些東西——它不會更差,卻可能更好。

使用 operator() 方法。

  該從外(介面優先)還是從內(資料優先)設計類?從外部! 良好的介面提供了一個簡化的,以使用者詞彙表達的視圖。在物件導向軟體的情況下,介面通常是單個類或一組緊密結合的類的public方法的集合. 首先考慮對象的邏輯特徵是什麼,而不是打算如何建立它。例如,假設要建立一個Stack(棧)類,其包含一個 LinkedList:  
class Stack {
 public:  // …
 private:
 LinkedList list_;
};

  Stack是否應該有一個返回LinkedList的get()方法?或者一個帶有LinkedList的set()方法?或者一個帶有LinkedList的建構函式?顯然,答案是“不”,因為應該從外向裡設計介面。也就是說,Stack對象的使用者並不關心 LinkedList;他們只關心 pushing 和 popping。

  現在看另一個更微妙的例子。假設 LinkedList類使用Node對象的鏈表來建立,每一個Node對象有一個指向下一個Node的指標:  
class Node { /*…*/ };

class LinkedList {
 public:  // …
 private:
 Node* first_;
};

  LinkedList類是否應該有一個讓使用者訪問第一個Node的get()方法?Node 對象是否應該有一個讓使用者訪問鏈中下一個 Node 的 get()方法?換句話說,從外部看,LinkedList應該是什麼樣的?LinkedList 是否實際上就是一個 Node 對象的鏈?或者這些只是實現的細節?如果只是實現的細節,LinkedList 將如何讓使用者在某時刻訪問 LinkedList 中的每一個元素?

  某人的回答:LinkedList 不是的 Node 鏈。它可能的確是用 Node 建立的,但這不是本質。它的本質是元素的序列。因此,LinkedList 象應該提供一個“LinkedListIterator”,並且“LinkedListIterator”應該有一個operator++ 來訪問下一個元素,並且有一對get()/set()來訪問儲存於Node 的值(Node 元素中的值只由LinkedList使用者負責,因此有一對get()/set()以允許使用者自由地維護該值)。

  從使用者的觀點出發,我們可能希望 LinkedList類支援看上去類似使用指標演算法訪問數組的算符:  
void userCode(LinkedList& a)
{
 for (LinkedListIterator p = a.begin(); p != a.end(); ++p)
 std::cout << *p << '/n';
}

  實現這個介面,LinkedList需要一個begin()方法和end()方法。它們返回一個“LinkedListIterator”對象。該“LinkedListIterator”需要一個前進的方法,++p ;訪問當前元素的方法,*p;和一個比較算符,p != a.end()。

  如下的代碼,關鍵在於 LinkedList 類沒有任何讓使用者訪問 Node 的方法。Node 作為實現技術被完全地隱藏了。LinkedList內部可能用雙重鏈表取代,甚至是一個數組,區別僅僅在於一些諸如
prepend(elem) 和 append(elem)方法的效能上。

#include // Poor man's exception handling
class LinkedListIterator;
class LinkedList;

class Node {   // No public members; this is a "private class"
 friend LinkedListIterator; // 友員類
 friend LinkedList;
 Node* next_;
 int elem_;
};

class LinkedListIterator {
public:
bool operator== (LinkedListIterator i) const;
bool operator!= (LinkedListIterator i) const;
void operator++ (); // Go to the next element
int& operator* (); // Access the current element
private:
LinkedListIterator(Node* p);
Node* p_;
friend LinkedList; // so LinkedList can construct a LinkedListIterator
};

class LinkedList {
 public:
 void append(int elem); // Adds elem after the end
 void prepend(int elem); // Adds elem before the beginning  // …
 LinkedListIterator begin();
 LinkedListIterator end();  // …
 private:
 Node* first_;
};

  這些是顯然可以內聯的方法(可能在同一個標頭檔中):  
inline bool LinkedListIterator::operator== (LinkedListIterator i) const
{
 return p_ == i.p_;
}

vinline bool LinkedListIterator::operator!= (LinkedListIterator i) const
{
 return p_ != i.p_;
}

 inline void LinkedListIterator::operator++()
{
 assert(p_ != NULL); // or if (p_==NULL) throw …
 p_ = p_->next_;
}

inline int& LinkedListIterator::operator*()
{
 assert(p_ != NULL); // or if (p_==NULL) throw …
 return p_->elem_;
}

inline LinkedListIterator::LinkedListIterator(Node* p)
: p_(p)
{ }

inline LinkedListIterator LinkedList::begin()
{
 return first_;
}

inline LinkedListIterator LinkedList::end()
{
 return NULL;
}

  結論:鏈表有兩種不同的資料。儲存於鏈表中的元素的值由鏈表的使用者負責(並且只有使用者負責,鏈表本身不阻止使用者將第三個元素變成第五個),而鏈表底層結構的資料(如 next 指標等)值由鏈表負責(並且只有鏈表負責,也就是說鏈表不讓使用者改變(甚至看到!)可變的next 指標)。

  因此 get()/set() 方法只擷取和設定鏈表的元素,而不是鏈表的底層結構。由於鏈表隱藏了底層的指標等結構,因此它能夠作非常嚴格的承諾(例如,如果它是雙重鏈表,它可以保證每一個後向指標都被下一個 Node 的前向指標匹配)。

  我們看了這個例子,類的一些資料的值由使用者負責(這種情況下需要有針對資料的get()/set()方法),但對於類所控制的資料則不必有get()/set()方法。

  注意:這個例子的目的不是為了告訴你如何寫一個鏈表類。實際上不要自己做鏈表類,而應該使用編譯器所提供的“容器類”的一種。理論上來說,要使用標準容器類之一,如:std::list 模板。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.