<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    PS,1880后程序員

    看不完的牙,寫不完的程序,跑不完的步。
    隨筆 - 97, 文章 - 34, 評論 - 10, 引用 - 0
    數(shù)據(jù)加載中……

    C++ Primer 之 讀書筆記 第十四章

     

    Overloaded Operations and Conversions

    (重載操作和轉(zhuǎn)換)

    14.1 定義重載操作符(Defining an Overload Operators)

    重載操作符必須至少有一個(gè)操作數(shù)的類型是類。

    重載操作符必須至少有一個(gè)操作數(shù)是類類型和枚舉類型。(An overloaded operator must have at least one operand of class or enumeration type.

    重載操作符不會改變原來的優(yōu)先級和關(guān)聯(lián)關(guān)系。(Precedence and Associativity Are Fixed)

    短路徑求值不再保留(Short-Ciruit Evaluation Is Not Preserved)

    這是和內(nèi)置數(shù)據(jù)類型不同的。if expression1 && expression2, 對于內(nèi)置數(shù)據(jù)類型如果expression1false,那么expression2就不再判斷。但是這個(gè)短路徑求值方法在操作符重載時(shí)不再保留,就是說無論expression1是否為FALSE,都要判斷expression1

    類成員對非類成員(Class Member versus Nonmember)

    這個(gè)命題來自于操作符重載即可以是類成員也可以是非類成員

    // member binary operator: left-hand operand bound to implicit this pointer

    Sales_item& Sales_item::operator+=(const Sales_item&);

    // nonmember binary operator: must declare a parameter for each operand

    Sales_item operator+(const Sales_item&, const Sales_item&);

    它們的區(qū)別在于:

    類成員返回的是引用,而非類成員返回的是對象。

    重載操作符的設(shè)計(jì)原則

    對于類類型對象來說大多數(shù)的操作都是沒有實(shí)際意義的,除非這個(gè)類提供了重載操作符。

    但是在我們設(shè)計(jì)類的過程中,不能為了操作符重載而操作符重載。應(yīng)該先去定義類的接口,然后根據(jù)接口提供的操作,把對應(yīng)的操作轉(zhuǎn)換為操作符重載。(The best way to design operators for a class is first to design the class' public interface. Once the interface is defined, it is possible to think about which operations should be defined as overloaded operators. Those operations with a logical mapping to an operator are good candidates. For example,

    復(fù)合賦值操作符(Compound Assignment Operators

    首先說一下子的是啥是符合賦值操作符,Google說,這些都是哈:

    +=

    -=

    *=

    /=

    %=

    &=

    ^=

    |=

    <<=

    >>=

    復(fù)合賦值操作符(加法)

    復(fù)合賦值操作符(減法)

    復(fù)合賦值操作符(乘法)

    復(fù)合賦值操作符(除法)

    復(fù)合賦值操作符(取余)

    復(fù)合賦值操作符(按位與)

    復(fù)合賦值操作符(按位異或)

    復(fù)合賦值操作符(按位或)

    復(fù)合賦值操作符(按位左移)

    復(fù)合賦值操作符(按位右移)

    大師給出的原則是:既然你重載了+,那么+=你也要負(fù)責(zé)哦。(If a class has an arithmetic operator, then it is usually a good idea to provide the corresponding compound-assignment operator as well.

    相等和邏輯關(guān)系操作符

    1. 用作關(guān)聯(lián)容器的鍵(key)的類應(yīng)該定義’<’操作符(less than operator)。(Classes that will be used as the key type of an associative container should define the < operator.
    2. 如果類的對象要保存在序列容器里,那么一定要為這個(gè)類定義等于和小于操作符(’==’, ‘<’)。(Even if the type will be stored only in a sequential container, the class ordinarily should define the equality (==) and less-than (<) operators.
    3. 如果類定義了等于操作符,那么還應(yīng)該定義不等于操作符。(If the class defines the equality operator, it should also define !=.
    4. 同樣,如果類定義了小于操作符(<),那么它應(yīng)該定義所有的四種關(guān)系操作符(>, >=, <, and <=)。( If the class defines <, then it probably should define all four relational operators (>, >=, <, and <=).

    14.2 輸入輸出操作符(Input and Output Operators )

    重載輸出操作符<<

    基本的框架結(jié)構(gòu)應(yīng)該是:

    // general skeleton of the overloaded output operator

     ostream&

     operator <<(ostream& os, const ClassType &object)

     {

          // any special logic to prepare object

          // actual output of members

          os << // ...

          // return ostream object

          return os;

     }

    另外有幾點(diǎn)注意事項(xiàng):

    1.      IO操作符重載必須是非成員函數(shù)。

    2.      輸出重載應(yīng)該以最小的方式打印對象的內(nèi)容。

    3.      第一個(gè)參數(shù)ostream引用,是第二個(gè)參數(shù)必須是const引用,返回值是ostream引用。

    For example

    ostream&

     operator<<(ostream& out, const Sales_item& s)

     {

         out << s.isbn << ""t" << s.units_sold << ""t"

             << s.revenue << ""t" << s.avg_price();

         return out;

     }

    其實(shí),我覺得這很像Java中重載tostring()函數(shù)。

    重載輸入操作符>>

    For example

    istream&

       operator>>(istream& in, Sales_item& s)

       {

           double price;

           in >> s.isbn >> s.units_sold >> price;

           // check that the inputs succeeded

           if (in)

              s.revenue = s.units_sold * price;

           else

              s = Sales_item(); // input failed: reset object to default state

           return in;

       }

    有幾點(diǎn)注意事項(xiàng):

    1. 返回值是istream的引用。
    2. 第二個(gè)參數(shù)不能是const,因?yàn)樽x入的數(shù)據(jù)就是寫入到這個(gè)對象里的。
    3. 對輸入有效性的判斷是在使用讀入的數(shù)據(jù)之前進(jìn)行的:if (in)

    進(jìn)一步說,在讀入數(shù)據(jù)時(shí),我們還有可能需要進(jìn)行數(shù)據(jù)有效性的判斷。那么How to do?簡單地說,就是要在重載的輸入操作符中增加有效性的判斷,如果輸入的數(shù)據(jù)不滿足有效性,那么就對istreamfailbit置位。同樣的道理,也可以處理badbiteofbit。(Usually an input operator needs to set only the failbit. Setting eofbit would imply that the file was exhausted, and setting badbit would indicate that the stream was corrupted. These errors are best left to the IO library itself to indicate.

    14.3 Arithmetic and Relational Operators

    算術(shù)操作符

    1. 為了和內(nèi)置操作保持一致,重載的加法操作返回的右值對象。(Note that to be consistent with the built-in operator, addition returns an rvalue, not a reference.
    2. 定義算術(shù)運(yùn)算符和相對應(yīng)的復(fù)合操作符的類應(yīng)該通過使用復(fù)合操作符來實(shí)現(xiàn)算數(shù)操作符。(Classes that define both an arithmetic operator and the related compound assignment ordinarily ought to implement the arithmetic operator by using the compound assignment.)注意這個(gè)順序是不能反的,也就是不能用算術(shù)運(yùn)算符去實(shí)現(xiàn)復(fù)合操作符。很容易理解,因?yàn)樗阈g(shù)操作符返回的是對象,而復(fù)合操作符返回的*this

    相等操作符

    1. 定義等于操作符(==)就要同時(shí)定義不等于操作符(!=)。二者之間一個(gè)完成實(shí)際的工作,另一個(gè)僅僅是調(diào)用前者。
    2. 如果類定義了等于操作符(==),那么就更加容易利用標(biāo)準(zhǔn)庫的算法了。

    關(guān)系操作符

    關(guān)系操作符是指:<, >

    要注意如果定義關(guān)系操作符,要保證它們和等于操作符不沖突。如果沖突,那就要有取舍,大師給的Sales_item的例子,就無需定義關(guān)系操作符,為了避免和等于操作符沖突。P520的說明仔細(xì)琢磨后,有點(diǎn)意思。

    14.4 Assignment Operators

    賦值操作符必須是成員函數(shù)。(every assignment operator, regardless of parameter type, must be defined as a member function.

    賦值操作符可以重載

    class string {

     public:

         string& operator=(const string &);      // s1 = s2;

         string& operator=(const char *);        // s1 = "str";

         string& operator=(char);                // s1 = 'c';

         // ....

     };

    正是因?yàn)?/span>string對賦值操作符進(jìn)行了重載,下面的賦值才都是有效的:

    string car ("Volks");

    car = "Studebaker"; // string = const char*

    string model;

    model = 'T'; // string = char

    賦值應(yīng)該返回*this的引用。

    14.5 下標(biāo)操作符(Subscript Operator )

    stringvector是最好的例子。)

    下標(biāo)操作符必須是類成員函數(shù)。

    需要定義下標(biāo)操作符的類要定義兩個(gè)版本的下標(biāo)操作,一個(gè)是非const成員,返回值是引用;另一個(gè)是const成員,返回值是const引用。(a class that defines subscript needs to define two versions: one that is a nonconst member and returns a reference and one that is a const member and returns a const reference.

    14.6 MemberAccess Operators

    是指:解引用(*)和箭頭( ->)操作符。它們主要是用在實(shí)現(xiàn)智能指針(smart pointer)。

    先說說這兩個(gè)抓狂的操作符

    我是這么掰持的:

    箭頭實(shí)際上是*.操作。什么意思?(*ptr).fun()對應(yīng)的就是ptr->fun(),由于’.’操作符的優(yōu)先級高于解引用*,所以->的優(yōu)先級也應(yīng)高于解引用。所以*ptr->sp實(shí)際上就是*(ptr->sp)

    再看看大師給的這個(gè)經(jīng)典的smart pointer的例子

    糾結(jié)。

    class ScrPtr {

          friend class ScreenPtr;

          Screen *sp;

          size_t use;

          ScrPtr(Screen *p): sp(p), use(1) { }

          ~ScrPtr() { delete sp; }

     };

    /*

     * smart pointer: Users pass to a pointer to a dynamically allocated Screen, which

     *                   is automatically destroyed when the last ScreenPtr goes away

     */

    class ScreenPtr {

    public:

        // no default constructor: ScreenPtrs must be bound to an object

        ScreenPtr(Screen *p): ptr(new ScrPtr(p)) { }

        // copy members and increment the use count

        ScreenPtr(const ScreenPtr &orig):

           ptr(orig.ptr) { ++ptr->use; }

        ScreenPtr& operator=(const ScreenPtr&);

        // if use count goes to zero, delete the ScrPtr object

    ~ScreenPtr() { if (--ptr->use == 0) delete ptr; }

    // 以下是定義的解引用和箭頭操作

    Screen &operator*() { return *ptr->sp; } //返回的是Screen對象的引用

    Screen *operator->() { return ptr->sp; } //返回的是指向Screen對象的指針

    const Screen &operator*() const { return *ptr->sp; }

    const Screen *operator->() const { return ptr->sp; }

    private:

        ScrPtr *ptr;    // points to use-counted ScrPtr class

    };

    對于解引用操作,那是好理解滴。

    糾結(jié)在箭頭操作上了L

    箭頭可以看做是二元操作符。它的兩個(gè)操作數(shù)是對象以及函數(shù)名稱。為了獲得成員接引用對象。即使這樣,箭頭操作符不需要顯式的形參。(Operator arrow is unusual. It may appear to be a binary operator that takes an object and a member name, dereferencing the object in order to fetch the member. Despite appearances, the arrow operator takes no explicit parameter.

    我們可以把右邊的操作數(shù)看做是是標(biāo)識符,它對于類的一個(gè)成員。(the right-hand operand is an identifier that corresponds to a member of a class.

    KAO,那怎么理解

    point->action();

    1. 首先根據(jù)優(yōu)先級,要把它看做是:(point->action) ();
    2. point是對象,可以看做是point.operator->()->action()。(這個(gè)問題讓我想的很糾結(jié),人笨起來,擋都擋不住。以至于中午在SOGO都還在想這個(gè)問題,然后忽的一下醒悟,幸福。)operator->()可以簡單的看做是對象成員,返回值是指針,然后執(zhí)行->action()部分,這里的->則是地地道道的箭頭操作符了。

    對箭頭重載返回值的約束條件

    返回值要么是類類型的指針,或者是類類型的對象,不過這個(gè)類要定義了它自己的箭頭操作符。(The overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow.

    14.7 自增和自減操作符Increment and Decrement Operators

    1. 首先這兩個(gè)操作符要是類的成員。
    2. 其次為了和內(nèi)置的操作保持一致。前綴操作符應(yīng)該返回的是引用,這個(gè)引用是對應(yīng)到增加或減少后的對象。
    3. 為了區(qū)分前綴和后綴操作,后綴操作符重載包含一個(gè)額外的int類型的形參。

    定義后自增/自減操作符

    要區(qū)分前自增/自減和后自增/自減。區(qū)分的方法是后自增/自減要包含有一個(gè)int類型的形參。但是這個(gè)int類型的形參并不會被使用。僅僅是為了和前自增/自減加以區(qū)分。

    // postfix: increment/decrement object but return unchanged value

     CheckedPtr CheckedPtr::operator++(int)

     {

         // no check needed here, the call to prefix increment will do the check

         CheckedPtr ret(*this);        // save current value

         ++*this;          // advance one element, checking the increment

         return ret;                   // return saved state

     }

    返回值

    對比前自增的返回值是自增后的對象的引用,后自增返回值是沒有自增的對象,而不是引用。

    顯式調(diào)用自增

    CheckedPtr parr(ia, ia + size);        // iapoints to an array of ints

    parr.operator++(0);                    // call postfix operator++

    parr.operator++();                     // call prefix operator++

    14.8 Call Operator and Function Objects

    調(diào)用操作符是指:operator()

    基本定義方法:

    struct absInt {

        int operator() (int val) {

            return val < 0 ? -val : val;

        }

    };

    調(diào)用:

    int i = -42;

    absInt absObj; // object that defines function call operator

    unsigned int ui = absObj(i);     // calls absInt::operator(int)

    函數(shù)調(diào)用操作符必須定義為成員函數(shù)。一個(gè)類可以定義多個(gè)版本的調(diào)用操作符,每個(gè)調(diào)用操作符定義的形參的類型和數(shù)量不同。

    如果看上面的這個(gè)用法的例子,實(shí)在看不出調(diào)用操作符有啥優(yōu)點(diǎn),但是如果結(jié)合類庫算法來使用,就能夠看出調(diào)用操作符的靈活性。

    定義:

    // determine whether a length of a given word is longer than a stored bound

    class GT_cls {

    public:

        GT_cls(size_t val = 0): bound(val) { }

        bool operator()(const string &s)

                           { return s.size() >= bound; }

    private:

        std::string::size_type bound;

    };

    調(diào)用:

    for (size_t i = 0; i != 11; ++i)

     cout << count_if(words.begin(), words.end(),GT_cls (i))

           << " words " << i

           << " characters or longer" << endl;

    這,要是用函數(shù)定義來寫,就不得不寫11個(gè)不同的函數(shù),而這些函數(shù)僅僅是要判斷的長度不同。冗余!如果這樣寫,簡直就是紡織女工寫的程序。

    標(biāo)準(zhǔn)庫定義的函數(shù)對象

    每個(gè)標(biāo)準(zhǔn)庫函數(shù)對象實(shí)際上都代表了一種操作,并且它們還是模板:o 模板類型代表了操作數(shù)的類型。(The Template Type Represents the Operand(s) Type

    plus<Type>

    minus<Type>

    multiplies<Type>

    divides<Type>

    modulus<Type>

    negate<Type>

    applies +

    applies -

    applies *

    applies /

    applies %

    applies -

    equal_to<Type>

    not_equal_to<Type>

    greater<Type>

    greater_equal<Type>

    less<Type>

    less_equal<Type>

    applies ==

    applies !=

    applies >

    applies >=

    applies <

    applies <=

    logical_and<Type>

    logical_or<Type>

    logical_not<Type>

    applies &&

    applies |

    applies !

    一般的運(yùn)算

    plus<int> intAdd;         // function object that can add two int values

    negate<int> intNegate;   // function object that can negate an int value

    // uses intAdd::operator(int, int) to add 10 and 20

    int sum = intAdd(10, 20);          // sum = 30

    // uses intNegate::operator(int) to generate -10 as second parameter

    // to intAdd::operator(int, int)

    sum = intAdd(10, intNegate(10));    // sum = 0

    除了一般的運(yùn)算,這些標(biāo)準(zhǔn)庫定義的庫函數(shù)還可以用在算法調(diào)用中使用。

    // passes temporary function object that applies > operator to two strings

    sort(svec.begin(), svec.end(), greater<string>());

    函數(shù)對象的函數(shù)適配器

    為啥要用適配器?

    適配器有兩類:

    1)    Binders:綁定器。把二元函數(shù)對象轉(zhuǎn)換為一元的函數(shù)對象

    又分成:bind1st, bind2nd.

    2)    Negators:取反器。函數(shù)對象的真值取反

    又分成:not1 not2.

    14.9 Conversions and Class Types

    為什么轉(zhuǎn)換有用?

    知了J

    問題的提出是這樣的,如果我們需要定義一個(gè)SmartInt類,這個(gè)類的值被限制在0-255之間,也就是無符號字符(unsigned char),同時(shí)這個(gè)類還要支持所有的算術(shù)運(yùn)算。如果沒有類型轉(zhuǎn)換,意味著我們要定義48個(gè)操作符。L

    但是很幸運(yùn),C++提供了類型轉(zhuǎn)換機(jī)制,類定義應(yīng)用在自己對象上的轉(zhuǎn)換規(guī)則。

    SmallInt si(3);

    si + 3.14159;         // 先把si轉(zhuǎn)換成int,再由int轉(zhuǎn)換成double,前者是類類型轉(zhuǎn)換,后者是標(biāo)準(zhǔn)類型轉(zhuǎn)換

    轉(zhuǎn)換操作符

    定義

    轉(zhuǎn)換操作符必須是成員函數(shù)。它定義的是從一種類類型值轉(zhuǎn)換成其它類型的值。(A conversion operator is a special kind of class member function. It defines a conversion that converts a value of a class type to a value of some other type.

    class SmallInt {

    public:

        SmallInt(int i = 0): val(i)

        { if (i < 0 || i > 255)

           throw std::out_of_range("Bad SmallInt initializer");

        }

        operator int() const { return val; }

    private:

        std::size_t val;

    };

    一般形式:

    operator type();

    可以看出轉(zhuǎn)換操作符是沒有形參的,另外也不定義返回值的類型。(The function may not specify a return type, and the parameter list must be empty.

    這里的type即可是內(nèi)置類型,也可以是類類型,甚至還可以是由typedef定義的名稱。(type represents the name of a built-in type, a class type, or a name defined by a typedef.

    另外轉(zhuǎn)換操作符強(qiáng)調(diào)的是轉(zhuǎn)換,它并不改變對象的值,因此轉(zhuǎn)換操作符一般都定義成const成員。例如上面的定義:operator int() const { return val; }

    轉(zhuǎn)換類型

    但是轉(zhuǎn)換類型也是有限制的,

    首先void是不行的,數(shù)組(array)或者函數(shù)類型(function type)也是不可以的。

    其次指針是可以的,無論是指向數(shù)據(jù)的還是指向函數(shù)的,都是可以的。

    第三引用類型也是可以的。

    類類型轉(zhuǎn)換只能應(yīng)用一次(Class-Type Conversions and Standard Conversions

    類類型轉(zhuǎn)換后面只能跟標(biāo)準(zhǔn)類型轉(zhuǎn)換,而不能再跟類類型轉(zhuǎn)換了。(A class-type conversion may not be followed by another class-type conversion.

    糾結(jié)了L :

    大師給了這個(gè)定義,注意si這個(gè)對象的創(chuàng)建過程是先把intVal通過類類型轉(zhuǎn)換為SmallInt,再調(diào)用拷貝構(gòu)造函數(shù)而生成的。

    但是如果SmallInt里定義了形參是Integral的構(gòu)造函數(shù),那SmallInt si(intVal);調(diào)用的是哪個(gè)?L

    int calc(int);

    Integral intVal;

    SmallInt si(intVal); // ok: convert intVal to SmallInt and copy to si

    標(biāo)準(zhǔn)類型轉(zhuǎn)換優(yōu)先于類類型轉(zhuǎn)換

    因此下面的定義是有效的:

    void calc(SmallInt);

    short sobj;

    // sobj 先由short 轉(zhuǎn)換為 int,這是標(biāo)準(zhǔn)類型轉(zhuǎn)換

    // int通過 SmallInt(int) 構(gòu)造函數(shù)轉(zhuǎn)換為 SmallInt ,這是使用構(gòu)造函數(shù)來執(zhí)行隱式轉(zhuǎn)換

    calc(sobj);

    實(shí)參匹配和轉(zhuǎn)換

    (這部分其實(shí)在第一次閱讀的時(shí)候可以跳過。因?yàn)槎紝儆?/span>advance類型的主題,比較糾結(jié)。今天北京又是一個(gè)高溫?zé)o雨,不過濕度有點(diǎn)大了,可怕的桑拿天L讀這部分。)

    類類型轉(zhuǎn)換在帶來便利的同時(shí)也是編譯錯(cuò)誤的源泉。用起來要格外的小心。之所以會是這樣,是由于有多種方式來實(shí)現(xiàn)一個(gè)類型到另一個(gè)類型的轉(zhuǎn)換。(Problems arise when there are multiple ways to convert from one type to another.

    實(shí)參匹配和多種轉(zhuǎn)換操作符

    一般情況下,類提供到兩種內(nèi)置類型的轉(zhuǎn)換,或者轉(zhuǎn)換到兩種內(nèi)置類型的做法都是錯(cuò)誤的。(Ordinarily it is a bad idea to give a class conversions to or from two built-in types.)例如Smallint類提供了int,還提供了double,那么下面的代碼就會讓編譯器很糾結(jié):

    void extended_compute(long double);

    SmallInt si;

    extended_compute(si); // error: ambiguous

    si可以轉(zhuǎn)換成intdouble,然后呢,都可以通過標(biāo)準(zhǔn)類型轉(zhuǎn)換轉(zhuǎn)換為long double,完了,編譯器不知道該用那個(gè)了。

    結(jié)論就是:

    如果在調(diào)用時(shí)可以用到兩個(gè)轉(zhuǎn)換操作符,那么如果存在標(biāo)準(zhǔn)轉(zhuǎn)換等級時(shí),那么跟隨轉(zhuǎn)換操作符的標(biāo)準(zhǔn)轉(zhuǎn)換等級就是用來確定最佳匹配的依據(jù)。(If two conversion operators could be used in a call, then the rank of the standard conversion, if any, following the conversion function is used to select the best match.

    實(shí)參匹配和構(gòu)造函數(shù)轉(zhuǎn)換

    例如Smallint類提供了int,還提供了double類型的構(gòu)造函數(shù),那么下面的代碼就會產(chǎn)生二義性:

    void manip(const SmallInt &);

    double d; int i; long l;

    manip(l);     // error: ambiguous

    l有兩種方式來處理:

    1. 先標(biāo)準(zhǔn)轉(zhuǎn)換(long->double,再調(diào)用構(gòu)造函數(shù)SmallInt(double)
    2. 先標(biāo)準(zhǔn)轉(zhuǎn)換(long->int)),再調(diào)用構(gòu)造函數(shù)SmallInt(int)

    因此編譯器就會報(bào)錯(cuò)了。

    結(jié)論:

    如果類定義有兩個(gè)構(gòu)造函數(shù)的類型轉(zhuǎn)換,標(biāo)準(zhǔn)轉(zhuǎn)換的級別(如果存在)用來判斷最佳匹配。(When two constructor-defined conversions could be used, the rank of the standard conversion, if any, required on the constructor argument is used to select the best match.

    當(dāng)兩個(gè)類都定義轉(zhuǎn)換引起二義性

    一個(gè)類定義的是構(gòu)造函數(shù)的類型轉(zhuǎn)換,另一個(gè)類是類型轉(zhuǎn)換操作(conversion operation):

    class Integral;

    class SmallInt {

    public:

        SmallInt(Integral); // convert from Integral to SmallInt

        // ...

     };

    class Integral {

    public:

        operator SmallInt() const; // convert from SmallInt to Integral

        // ...

     };

    void compute(SmallInt);

    Integral int_val;

    compute(int_val); // error: ambiguous

    int_val這個(gè)Integral類型如何轉(zhuǎn)換為SmallInt呢?因?yàn)橛袃煞N個(gè)方法,編譯器是不會選擇的。

    解決辦法就是顯式調(diào)用:

    compute(int_val.operator SmallInt());   // ok: use conversion operator

    compute(SmallInt(int_val));             // ok: use SmallInt constructor

    結(jié)論:

    最好的方法避免二義性是避免一對類都提供彼此間的隱式轉(zhuǎn)換。(The best way to avoid ambiguities or surprises is to avoid writing pairs of classes where each offers an implicit conversion to the other.

    重載確定類實(shí)參(Overload Resolution and Class Arguments

    標(biāo)準(zhǔn)轉(zhuǎn)換跟隨轉(zhuǎn)換操作符

    哪個(gè)函數(shù)最匹配取決于在匹配的不同函數(shù)里是否包含一個(gè)或多個(gè)類類型轉(zhuǎn)換。(Which function is the best match can depend on whether one or more class-type conversions are involved in matching different functions.

    如果兩個(gè)轉(zhuǎn)換序列使用同樣的轉(zhuǎn)換操作,那么跟著類類型轉(zhuǎn)換的標(biāo)準(zhǔn)類型轉(zhuǎn)換作為選擇標(biāo)準(zhǔn)(The standard conversion sequence following a class-type conversion is used as a selection criterion only if the two conversion sequences use the same conversion operation.

    很難理解的概念,但是一看例子就了然了J

    void compute(int);

    void compute(double);

    void compute(long double);

    SmallInt si;

    compute(si);

    調(diào)用的是void compute(int);因?yàn)槭蔷_匹配。

    多個(gè)轉(zhuǎn)換和重載確定

    如果類類型里定義了兩個(gè)內(nèi)置類型的轉(zhuǎn)換,例如SmallInt定義有轉(zhuǎn)換操作int()double(),那么還是上面的例子compute(si);編譯器就會報(bào)錯(cuò),因?yàn)榇嬖趦煞N調(diào)用方式:

    void compute(int);

    void compute(double);

    編譯器傻了。

    解決辦法:為了避免二義性而顯式強(qiáng)制轉(zhuǎn)換(Explicit Constructor Call to Disambiguate

    SmallInt si;

    compute(static_cast<int>(si)); // ok: convert and call compute(int)

    標(biāo)準(zhǔn)轉(zhuǎn)換和構(gòu)造函數(shù)

    兩個(gè)類都定義了相同類型的構(gòu)造函數(shù):

    class SmallInt {

     public:

         SmallInt(int = 0);

     };

     class Integral {

     public:

         Integral(int = 0);

     };

    void manip(const Integral&);

    void manip(const SmallInt&);

    manip(10); // error: ambiguous

    解決方法就是顯式調(diào)用構(gòu)造函數(shù):

    manip(SmallInt(10));    // ok: call manip(SmallInt)

    manip(Integral(10));    // ok: call manip(Integral)

    另外,即使SmallInt中定義的類型轉(zhuǎn)換是short,也是沒意義的,編譯器依然會報(bào)錯(cuò)。事實(shí)上,當(dāng)選擇重載版本的調(diào)用時(shí),一個(gè)調(diào)用標(biāo)準(zhǔn)類型轉(zhuǎn)換,另一個(gè)不需要,這無關(guān)緊要。( The fact that one call requires a standard conversion and the other does not is immaterial when selecting among overloaded versions of a call.)編譯器必不是更喜歡直接的構(gòu)造函數(shù)。

    重載,轉(zhuǎn)換和操作符(Overloading, Conversions, and Operators

    這個(gè)問題的提出是這樣滴:

    ClassX sc;

    int iobj = sc + 3;

    這樣一個(gè)語句,你說這是操作符重載,或者是類類型轉(zhuǎn)換(構(gòu)造函數(shù),轉(zhuǎn)換操作符)?

    重載確定和操作符

    成員函數(shù)和非成員函數(shù)都是有可能的這一事實(shí)改變選擇候選函數(shù)的方式。(The fact that member and nonmember functions are possible changes how the set of candidate functions is selected.

    操作符的候選函數(shù)

    根據(jù)表達(dá)式中用到的操作符,候選函數(shù)包括內(nèi)置的操作符以及所有一般的非成員函數(shù)版本的操作符。除此之外,如果左邊的操作數(shù)是類類型,那么候選函數(shù)還應(yīng)包括類定義的重載操作符。(In the case of an operator used in an expression, the candidate functions include the built-in versions of the operator along with all the ordinary nonmember versions of that operator. In addition, if the left-hand operand has class type, then the candidate set will contain the overloaded versions of the operator, if any, defined by that class.

    調(diào)用自己決定要考慮的名字的范圍(the call itself determines the scope of names that will be considered.)如果調(diào)用是通過類對象來完成的,那么只會考慮類的成員函數(shù)。

    轉(zhuǎn)換能夠?qū)е聝?nèi)置操作符的二義性(Conversions Can Cause Ambiguity with Built-In Operators

    同一個(gè)類即提供到算術(shù)類型的轉(zhuǎn)換函數(shù)又重載操作符,就會導(dǎo)致重載操作符和內(nèi)置操作符的二義性。

    這樣的例子就是SmallInt即定義到int的轉(zhuǎn)換函數(shù),又重載操作符’+’,那么int i = s3 + 0;就會產(chǎn)生二義性的錯(cuò)誤,是把s3轉(zhuǎn)換成int再去計(jì)算,還是把0通過構(gòu)造函數(shù)轉(zhuǎn)換成SmallInt再去計(jì)算呢?

    class SmallInt {

     public:

         SmallInt(int = 0); // convert from int to SmallInt

         // conversion to int from SmallInt

         operator int() const { return val; }

         // arithmetic operators

         friend SmallInt

         operator+(const SmallInt&, const SmallInt&);

     private:

          std::size_t val;

     };

    SmallInt s3 = s1 + s2;         // ok: uses overloaded operator+

    int i = s3 + 0;                // error: ambiguous

    規(guī)則(rules of thumb

    針對類進(jìn)行重載操作符(overloaded operators)、構(gòu)造函數(shù)轉(zhuǎn)換(conversion constructors)以及轉(zhuǎn)換函數(shù)(conversion functions)設(shè)計(jì)必須小心。構(gòu)造函數(shù)轉(zhuǎn)換(conversion constructors)以及轉(zhuǎn)換函數(shù)(conversion functions)又可以統(tǒng)稱為轉(zhuǎn)換操作(conversion operators)。如果類既定義了轉(zhuǎn)換操作又重載操作符,那么很容易產(chǎn)生二義性。(ambiguities are easy to generate if a class defines both conversion operators and overloaded operators.)這里有兩條規(guī)則:

    1.      不要定義相互轉(zhuǎn)換類。(Never define mutually converting classe

    2.      避免定義轉(zhuǎn)換到內(nèi)置算術(shù)類型的轉(zhuǎn)換函數(shù)。(Avoid conversions to the built-in arithmetic types.)特別的,如果定義了這類函數(shù),那么

    不要重載以算術(shù)類型為形參的操作符。(Do not define overloaded versions of the operators that take arithmetic types.

    不要定義超過一種的轉(zhuǎn)換到算術(shù)類型的轉(zhuǎn)換函數(shù)。(Do not define a conversion to more than one arithmetic type.)如果定義了到int的轉(zhuǎn)換函數(shù)就不要定義轉(zhuǎn)換到double的轉(zhuǎn)換函數(shù)。

    posted on 2009-07-01 08:33 amenglai 閱讀(858) 評論(0)  編輯  收藏 所屬分類: C++ Primer 之 讀書筆記

    主站蜘蛛池模板: 亚洲色图校园春色| 日本高清免费不卡在线| AV大片在线无码永久免费| 无码一区二区三区AV免费| 又爽又高潮的BB视频免费看| 亚洲人成国产精品无码| 亚洲成av人片天堂网| 亚洲乱码卡三乱码新区| 男男黄GAY片免费网站WWW| aa在线免费观看| 蜜桃AV无码免费看永久| 高清国语自产拍免费视频国产 | 在线观看免费a∨网站| 免费a级毛片网站| 久久亚洲国产中v天仙www| 亚洲人成影院午夜网站| 国产精品成人亚洲| 在线观看特色大片免费网站| 亚洲三级在线免费观看| 日韩免费视频网站| 亚洲区小说区图片区QVOD| 亚洲国产精品免费在线观看| 色天使亚洲综合一区二区| 青青操免费在线视频| 亚洲中文无码永久免费| 亚洲精品无码久久久久AV麻豆| 亚洲男人天堂2017| 亚洲av日韩专区在线观看| 久久美女网站免费| 女人与禽交视频免费看| 国产综合亚洲专区在线| 亚洲成a人片毛片在线| 四虎成人精品国产永久免费无码| 久久99青青精品免费观看| 日美韩电影免费看| 亚洲爱情岛论坛永久| 色欲aⅴ亚洲情无码AV蜜桃| 无码一区二区三区免费| 日韩免费无砖专区2020狼| 久久亚洲免费视频| 国产精品久久久久久亚洲小说|