1. 程式人生 > 實用技巧 >《c++入門經典》筆記15

《c++入門經典》筆記15

第十五章 運算子過載

15.1過載運算子

對於c++內建型別,對其使用相應運算子,編譯器能準確知道其意思,比如:

int x = 17,y = 12,z;
z = x * (y + 5);

通過使用成員函式multiply()和add(),類也能提供這樣的功能,但語法複雜得多。假如有個表示整數的Number類,下述程式碼與上例的相同:

Number x(17);
Number y(12);
Number z,temp;
temp = y.add(5);
z = x.multiply(temp);

這些程式碼將5與y相加,再將結果與x相乘,最終結果同樣是289

為了簡化程式碼,可過載運算子,這樣便可使用運算子來操作物件。

運算子過載定義了將運算子用於物件時執行的操作,幾乎所有的c++運算子都可過載。

程式清單15.1 Counter.cpp

#include<iostream>

class Counter
{
private:
    int value;
public:
    Counter();
    ~Counter(){}
    int getValue() const{return value;}
    void setValue(int x){value = x;}
};

Counter::Counter():value(0)
{}

int main()
{
    Counter c;
    std::cout<<"The value of c is "<<c.getValue()<<std::endl;
    return 0;
}

執行結果就不貼了。這個程式並未進行運算子過載,也沒有進行改動,Counter物件也不能進行遞增、遞減、相加和賦值,不能用其他運算子操作它,顯示其值也不容易(需要呼叫成員函式進行顯示)。

編寫遞增方法

通過過載運算子,可給類提供原本不能進行的運算子操作。

要在類中過載運算子,最常見的方式是使用成員函式。

函式名由operator和要定義的運算子(如+或++)組成

程式清單15.2 Counter2.cpp

#include <iostream>

class Counter
{
private:
    int value;

public:
    Counter();
    ~Counter() {}
    int getValue() const { return value; }
    void setValue(int x) { value = x; }
    void increment() { ++value; }
    const Counter &operator++(); //過載函式宣告
};

Counter::Counter() : value(0) {}

const Counter &Counter::operator++()
{
    ++value;
    return *this;//對this指標解引用以返回當前物件
}

int main()
{
    Counter c;
    std::cout << "The value of c is " << c.getValue() << std::endl;
    c.increment();
    std::cout << "The value of c is " << c.getValue() << std::endl;
    ++c;//++過載
    std::cout << "The value of c is " << c.getValue() << std::endl;
    Counter a = ++c;
    std::cout << "The value of a: " << a.getValue() << std::endl;
    std::cout << " and c: " << c.getValue() << std::endl;
}

過載字尾運算子

給成員函式operator++()新增一個int引數。在函式體內,不會使用這個引數,它只用於表明改函式定義的是字尾運算子。

為此,在過載的成員函式中,必須建立一個臨時物件,用於儲存原始值,以便對原物件進行遞增。返回的將是原始物件,因為字尾運算子要求使用原始值,而不是遞增後的值。

必須按值(而不是按引用)返回該臨時物件,否則函式返回時它將不再在作用域內。

程式清單15.3 Counter3.cpp

#include <iostream>

class Counter
{
private:
    int value;

public:
    Counter();
    ~Counter() {}
    int getValue() const { return value; }
    void setValue(int x) { value = x; }
    const Counter &operator++();
    const Counter operator++(int);
};

Counter::Counter() : value(0) {}

const Counter &Counter::operator++()
{
    ++value;
    return *this;
}

const Counter Counter::operator++(int)
{
    Counter temp(*this);
    ++value;
    return temp;
}

int main()
{
    Counter c;
    std::cout << "The value of c is " << c.getValue() << std::endl;
    c++;
    std::cout << "The value of c is " << c.getValue() << std::endl;
    ++c;
    std::cout << "The value of c is " << c.getValue() << std::endl;
    Counter a = ++c;
    std::cout << "The value of a : " << a.getValue() << std::endl;
    std::cout << "and c : " << c.getValue() << std::endl;
    a = c++;
    std::cout << "The value of a : " << a.getValue() << std::endl;
    std::cout << "and c : " << c.getValue() << std::endl;
    return 0;
}

過載加法運算子

和上面的意識是大同小異

程式清單15.4 Counter4.cpp

#include <iostream>

class Counter
{
private:
    int value;

public:
    Counter();
    Counter(int intialValue);
    ~Counter() {}
    int getValue() const { return value; }
    void setValue(int x) { value = x; }
    Counter operator+(const Counter &);
};

Counter::Counter() : value(0) {}
Counter::Counter(int intialValue) : value(intialValue) {}
Counter Counter::operator+(const Counter &rhs)//過載+運算子
{
    return Counter(value + rhs.getValue());
}

int main()
{
    Counter alpha(4), beta(13), gamma;
    gamma = alpha + beta;
    std::cout << "alpha: " << alpha.getValue() << std::endl;
    std::cout << "beta: " << beta.getValue() << std::endl;
    std::cout << "gamma: " << gamma.getValue() << std::endl;
    return 0;
}

對運算子過載的限制

不能過載用於內建型別的運算子:不能改變運算子的優先順序和目數(單目、雙目或三目);另外,不能建立新運算子,因此不能將**宣告為指數(乘方)運算子。

運算子過載是c++新手過度使用和濫用的c++功能之一,他們經常禁不住誘惑,給一些晦澀的運算子提供有趣的新用途,但這常常會導致程式碼令人迷惑,難以理解。

賦值運算子

賦值運算子的過載函式為operator=(),過載後每當給物件賦值都將呼叫它。

過載賦值運算子時,有如下問題需要考慮:

  • 如果將一個物件賦值給另一個物件,比如dallas = wichita;這時物件dallas之前的記憶體如果是在堆中分配的,那麼就應該考慮過載=運算子時釋放該物件所指向的堆記憶體後再進行賦值。
  • 如果將一個物件賦值給自己,比如dallas = dallas;,這種情況可能意外發生。再結合上面的釋放記憶體問題,這個時候就可能導致dallas將堆中分配給自己的記憶體釋放,這樣,就會出現無法預料的意外。為了避免這種問題,可以使用this指標檢查右運算元是否為當前物件。

程式清單15.5 Assignment.cpp

#include <iostream>

class Tricycle
{
private:
    int *speed;

public:
    Tricycle();
    ~Tricycle();
    int getSpeed() const { return *speed; }
    void setSpeed(int newSpeed) { *speed = newSpeed; }
    Tricycle operator=(const Tricycle &);
};

Tricycle::Tricycle()
{
    speed = new int;
    *speed = 5;
}

Tricycle::~Tricycle()
{
    delete speed;
}

Tricycle Tricycle::operator=(const Tricycle &rhs)
{
    if (this == &rhs)
        return *this;
    delete speed;
    speed = new int;
    *speed = rhs.getSpeed();
    return *this;
}

int main()
{
    Tricycle wichita;
    std::cout << "Wichita's speed: " << wichita.getSpeed() << std::endl;
    std::cout << "Setting Wichita's speed to 6 ..." << std::endl;
    wichita.setSpeed(6);
    Tricycle dallas;
    std::cout << "Dallas's speed: " << dallas.getSpeed() << std::endl;
    std::cout << "Copying Wichita to Dallas ..." << std::endl;
    wichita = dallas;
    std::cout << "Dallas's speed: " << dallas.getSpeed() << std::endl;
    return 0;
}

剩下的那些運算子也都大同小異,只要過載運算子時考慮那些可能會出現bug的細節。

15.2轉換運算子

如果您檢視將一個內建型別賦值給一個使用者自定義的型別,如果不建立轉換運算子而直接賦值會導致編譯失敗,比如:

int beta = 5;
Counter alpha = beta;//報錯,無法將int轉換為Counter物件

下面這個程式通過建立一個轉換運算子(其實是一個建構函式,接受一個int引數並建立一個Counter物件)修復了這個問題。

程式清單15.7 Counter6.cpp

#include <iostream>

class Counter
{
private:
    int value;

public:
    Counter() : value(0) {}
    Counter(int newValue);
    ~Counter() {}
    int getValue() const { return value; }
    void setValue(int newValue) { value = newValue; }
};

Counter::Counter(int newValue) : value(newValue) {}

int main()
{
    int beta = 5;
    Counter alpha = beta;
    std::cout << "alpha: " << alpha.getValue() << std::endl;
    return 0;
}

執行輸出是:alpha:5

上面是將內建型別變數賦值給物件,下面將演示將物件賦值給內建變數型別。

c++支援在類中新增轉換運算子,以指定如何將物件隱式地轉換為內建型別變數。

程式清單15,8 Counter7.cpp

#include <iostream>

class Counter
{
private:
    int value;

public:
    Counter() : value(0) {}
    Counter(int newValue);
    ~Counter() {}
    int getValue() const { return value; }
    void setValue(int newValue) { value = newValue; }
    operator unsigned int();
};

Counter::Counter(int newValue) : value(newValue) {}

Counter::operator unsigned int() //轉換運算子
{
    return (value);
}

int main()
{
    Counter epsilon(19);
    int zeta = epsilon;
    std::cout << "zeta: " << zeta << std::endl;
    return 0;
}

執行輸出是:zeta: 19

注意轉換運算子沒有指定返回值,但實際上返回了一個轉換後的值。