歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C++運算符重載

基本知識

重載的運算符是具有特殊名字的函數,他們的名字由關鍵字operator和其後要定義的運算符號共同組成。

運算符可以重載為成員函數和非成員函數。當一個重載的運算符是成員函數時,this綁定到左側運算對象。成員運算符函數的(顯式)參數比運算對象的數量少一個。

調用重載運算符函數

//非成員函數的等價調用
data1 + data2;//normal expression
operator+(data1,data2); // equal function call
//成員函數的等價調用
data1 += data2;//normal expression
data1.operator+(data2); // equal function call
 

可重載的運算符:+ - * / % ˆ & | ~ ! = < > += -= = /= %= ˆ= &= |= << >> >>= <<= == != <= >= && || ++ -- , -> -> ( ) [ ]

無法被重載的運算符有: :: .* . ?:

Restrictions

  • The operators :: (scope resolution), . (member access), .* (member access through pointer to member), and ?: (ternary conditional) cannot be overloaded
  • New operators such as **, <>, or &| cannot be created
  • The overloads of operators &&, ||, and , (comma) lose their special properties: short-circuit evaluation and sequencing.
  • The overload of operator -> must either return a raw pointer or return an object (by reference or by value), for which operator -> is in turn overloaded.
  • It is not possible to change the precedence, grouping, or number of operands of operators.

選擇作為成員或非成員的依據(引用C++ Primer)

  1. 賦值(=)、下標([])、調用(())和成員訪問箭頭(->)運算符必須是成員。
  2. 復合賦值運算符一般來說應該是成員,但並非必須。
  3. 改變對象狀態的運算符或者與給定類型密切相關的運算符,如++、--和解引用運算符,通常應為成員。
  4. 具有對稱性的運算符可能轉換任意一端的運算對象,例如算數、相等性、關系和位運算等,通常應為普通的非成員函數。便於自動類型轉換
  5. IO運算符必須是非成員函數(友元)。目的是為了與iostream標准庫的輸入輸出兼容,否則左值為類的對象。

運算符實例類

class Sales_data{
public:
    Sales_data(): units_sold(0), revenue(0.0) {}
    Sales_data(const std::string &s):
        bookNo(s), units_sold(0), revenue(0.0) {}
    Sales_data(const std::string &s, unsigned n, double p):
        bookNo(s), units_sold(n), revenue(p*n) {}
    std::string isbn() const{
        return bookNo;
    }
    Sales_data& combine(const Sales_data&);
    double avg_price() const;
 
    //運算符重載函數申明
    friend ostream& operator<<(ostream& os,const Sales_data& item);
    friend istream& operator>>(istream& is,Sales_data& item);
 
 
private:
    string bookNo;
    unsigned units_sold;
    double revenue;
};
inline double Sales_data:: avg_price() const{
    if(units_sold)
        return revenue / units_sold;
    else
        return 0;
}

輸入和輸出運算符

重載輸出運算符<<

通常情況下,第一個形參是一個非const的ostream對象的引用。ostream是非常量是因為寫入流會改變其狀態;而該形參是引用類型是因為無法直接復制一個ostream對象。

第二個形參一般是const&。因為我們希望避免復制實參,並且被打印的內用一般不會改變其內容。

為了與其他運算符保持一致,operator<<一般返回它的ostream形參。

ostream& operator<<(ostream& os,const Sales_data& item)
{
    os<<item.isbn()<<" "<<item.units_sold<<" "
        <<item.revenue<<" "<<item.avg_price();
    return os;
}

輸出運算符盡量減少格式化操作
輸出運算符主要負責打印對象的內容而非控制格式,輸出運算符不應該打印換行符。

輸入輸出運算符必須是非成員函數,一般申明為友元
與iostream標准庫兼容。

重載輸入運算符>>

通常,第一個形參是流的引用,第二個是將要讀到的(非const)對象引用。函數返回給定流的引用。

istream& operator>>(istream& is,Sales_data& item)
{
    double price;
    is>> item.bookNo >> item.units_sold >> price;
    if(is)    //檢查輸入是否成功
        item.revenue = item.units_sold * price;
    else
        item = Sales_data();    //輸入失敗,對象被賦予默認的狀態
}

當讀取操作發生錯誤時,輸入運算符應該負責從錯誤中恢復。

算術和關系運算符

通常情況下,我們把算術和關系運算符定義成非成員函數以允許對左側或者右側的運算對象進行轉換。因為這些運算符一般不需要改變運算對象的狀態,所以形參都是const&。

Sales_data operator+(const Sales_data& lhs,const Sales_data& rhs)
{
    Sales_data sum = lhs;
    sum += rhs;
    return sum;
}

如果類同時定義了算術運算符和相關的復合賦值運算符,則通常情況下應該使用復合運算符來實現算術運算符。

相等運算符

用來檢驗兩個對象是否相等。

inline bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
    return lhs.isbn() == rhs.isbn() &&
        lhs.units_sold == rhs.units_sold &&
        lhs.revenue == rhs.revenue;
}
inline bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
    return !(lhs == rhs);
}

相等運算符設計准則:

  • 如果一個類包含判斷兩個對象是否相等的操作,顯然定義成operator==而非一個普通命名函數。這使得用戶不必記憶新的函數名,同時定義了==運算符之後也更容易使用標准庫容器和算法。
  • 如果類定義了operator==,則該運算符應該能判斷一組給定對象中是否含有重復數據。
  • 通常情況下,==運算符應該具有傳遞性。
  • 當類定義了==,應當也為類定義!=。
  • ==運算符和!=運算符中的一個應該把工作委托給另一個。意味著其中一個是負責實際比較對象的工作,另一個負責調用。

關系運算符

定義了相等運算符的類也常常(但不總是)包含關系運算符。特別的是,因為關聯容器和一些算法要用到<,所以定義operator<會比較有用。

通常情況下關系運算符應該

  1. 定義順序關系,令其與關聯容器對關鍵字的要求一致(與考試內容無關,詳情見C++ Primer);並且
  2. 如果類同時有==運算符時,則定義一種關系令其與==保持一致,特別的兩個對象!=時,那麼一個對象應該<另外一個。

如果存在唯一一種邏輯可靠的<定義,則應該考慮為類定義<運算符。如果類同時存在==運算符,當且僅當<的定義和==產生的結果一致時才定義<

此處的實例不存在邏輯可靠的關系運算,故引用其他例子。

關系運算符一般定義為非成員函數。
Typically, once operator< is provided, the other relational operators are implemented in terms of operator<。

inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return rhs < lhs;}
inline bool operator<=(const X& lhs, const X& rhs){return !(lhs > rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !(lhs < rhs);}

賦值運算符

T& operator=(const T& other) // copy assignment
{
    if (this != &other) { // self-assignment check expected
        if (/* storage cannot be reused (e.g. different sizes) */)
        {
            delete[] mArray;            // destroy storage in this
            /* reset size to zero and mArray to null, in case allocation throws */
            mArray = new int[/*size*/]; // create storage in this
        }
        /* copy data from other's storage to this storage */
    }
    return *this;
}

復合賦值運算
一般傾向於把包括復合賦值在內的所有賦值運算都定義在類的內部。為了與內置類型的復合賦值保持一致,一般返回左側對象的引用。

Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

下標運算符

表示容器的類通常可以通過元素在容器中的位置訪問元素,一般會定義operator[]。
下標運算符必須是成員函數

一般會定義兩個版本,一個返回普通引用,另一個是類的常成員並返回常量引用。

class  StrVec
{
public :
    string& operator[](size_t n)
    { return elements[n];}
    const string& operator[](size_t n) const
    { return elements[n];}
private :
    string *elements;
};

遞增和遞減運算符

遞增和遞減運算符改變的是操作對象的狀態,建議設定為成員函數。
應該同時定義前置和後置版本的遞增和遞減運算符

定義前置遞增/遞減運算符

template<class T>
class T{
public :
    T& operator++();
    T& operator--();
private:
        int x;
};
template<class T>
T& T::operator++()
{
    ++x;// actual increment takes place here
    return *this;
}
template<class T>
T& T::operator--()
{
    --x;// actual decrement takes place here
    return *this;
}

為了與內置版本保持一致,前置運算符應該返回遞增或遞減後對象的引用。

區分前置和後置運算符

後置版本接受一個額外的(不被使用)int類型的形參。

template<class T>
class T{
public :
    T operator++(int);
    T operator--(int);
private:
        int x;
};
template<class T>
T T::operator++()
{
    T tmp = *this; // copy
    ++(*this); // pre-increment
    return *tmp; // return old value
}
template<class T>
T T::operator--()
{
    T tmp = *this; // copy
    --(*this); // pre-increment
    return *tmp; // return old value
}

為了與內置版本保持一致,後置運算符應該返回對象的原值,返回的是一個值而非引用。

Copyright © Linux教程網 All Rights Reserved