C++類別別成員函數定義方法詳解

2020-07-16 10:04:39
類成員函數定義與常規函數類似,除特殊情況外,它們有一個包含返回型別(可能為 void)、函數名和形參列表(可能為空)的函數頭。執行函數動作的語句包含在一對大括號中,跟在函數頭後面。

我們在《類物件的建立和使用》一節定義 Circle 類時,已經在類宣告本身中定義了它的兩個成員函數。當一個類函數定義在類宣告中時,它被稱為行內函式

行內函式提供了在類宣告中包含函數資訊的便捷方式,但只能在函數體非常短(通常是單行)時使用。當函數體更長時,則可以在函數的類宣告中放置一個原型,而不是函數定義本身。然後,將函數定義放在類宣告之外,要麼跟在它後面,要麼放在一個單獨的文仵中。

雖然 Circle 類中的兩個函數足夠短,可以寫成行內函式,但是也可以將它們重寫為常規函數,定義在類宣告之外。在類宣告中,函數將被以下原型所替代:

void setRadius(double);
double getArea();

按照類宣告,我們將放置一個包含以下函數定義的函數實現部分:
void Circle::setRadius(double r)
{
    radius = r;
}
double Circle::getArea()
{
    return 3.14 * pow(radius, 2);
}
可以看到,以上函數實現語句和普通函數看起來是一樣的,區別在於,在函數返回型別之後函數名之前,放置了類名和雙冒號(::)。:: 符號稱為作用域解析運算子。它可以用來指示這些是類成員函數,並且告訴編譯器它們屬於哪個類。

注意,類名和作用域解析運算子是函數名的擴充套件名。當一個函數定義在類宣告之外時,這些必須存在,並且必須緊靠在函數頭中的函數名之前。

以下範例通過對比,清晰說明了當類函數定義在類宣告之外時,該如何使用作用域解析運算子:

double getArea () //錯誤!類名稱和作用域解析運算子丟失
Circle :: double getArea () //錯誤!類名稱和作用域解析運算子錯位
double Circle :: getArea () //正確

下面程式在類宣告之外定義了成員函數:
// This program demonstrates a simple class with member functions defined outside the class declaration.
#include <iostream>
#include <cmath>
using namespace std;

//Circle class declaration
class Circle
{
    private:
        double radius; // This is a member variable.
    public:
        void setRadius(double); // These are just prototypes
        double getArea(); // for the member functions.
};

void Circle::setRadius(double r)
{
    radius = r;
}

double Circle::getArea()
{
    return 3.14 * pow(radius, 2);
}
int main()
{
    Circle circle1,circle2;
    circle1.setRadius(1); // This sets circle1's radius to 1.0
    circle2.setRadius(2.5); // This sets circle2's radius to 2.5
    cout << "The area of circle1 is " << circle1.getArea() << endl;
    cout << "The area of circle2 is " << circle2.getArea() << endl;
    return 0;
}
程式輸出結果為:

The area of circle1 is 3.14
The area of circle2 is 19.625

類成員函數的命名約定

下面的程式提供了使用類和物件的另一個範例。它宣告並實現了一個具有兩個私有成員變數和 5 個公共成員函數的 Rectangle 類:
#include <iostream>
using namespace std;

// Rectangle class declaration
class Rectangle
{
    private:
        double length;
        double width;
    public:
        void setLength(double);
        void setWidth(double);
        double getLength();
        double getWidth();
        double getArea();
};

//Member function implementation section
void Rectangle::setLength(double len)
{
    if (len >= 0.0)
        length = len;
    else
    {
        length = 1.0;
        cout << "Invalid length. Using a default value of 1.0n";
    }
}
void Rectangle::setWidth(double w)
{
    if (w >= 0.0)
        width = w;
    else
    {
        width = 1.0;
        cout << "Invalid width. Using a default value of 1.0n";
    }
}
double Rectangle::getLength()
{
    return length;
}
double Rectangle::getWidth()
{
    return width;
}
double Rectangle::getArea()
{
    return length * width;
}
int main()
{
    Rectangle box;    // Declare a Rectangle object
    double boxLength, boxWidth;
    //Get box length and width
    cout << "This program will calculate the area of a rectangle.n";
    cout << "What is the length?";
    cin >> boxLength;
    cout << "What is the width?";
    cin >> boxWidth;
    //Call member functions to set box dimensions
    box.setLength(boxLength);
    box.setWidth(boxWidth);
    //Call member functions to get box information to display
    cout << "nHere is the rectangle's data:n";
    cout << "Length: " << box.getLength() << endl;
    cout << "Width : " << box.getWidth () << endl;
    cout << "Area : " << box.getArea () << endl;
    return 0;
}
程式執行結果:

This program will calculate the area of a rectangle.
What is the length?10.1
What is the width?5

Here is the rectangle's data:
Length: 10.1
Width : 5
Area : 50.5

此程式中的成員函數的名稱全部以單詞 set 或單詞 get 開頭。函數 setLength 和 setWidth 都是所謂的設定器函數或 set 函數,通常用一個單詞 set 來命名一個設定器,後面加上它設定的值的成員變數的名稱。顧名思義,setLength 函數設定的就是 length 成員變數的值,而 setWidth 函數設定的就是 width 成員變數的值。

成員函數 getLength 和 getWidth 都是存取器函數或 get 函數。通常使用單詞 get 命名一個存取器,後面跟著將被獲取其值的成員變數的名稱。函數 getLength 返回儲存在 length 成員變數中的值,而 getWidth 則返回儲存在 width 成員變數中的值。

最後一個成員函數 getArea 也是一個存取器函數,但並不改變儲存在類變數中的任何值,它用於返回計算的結果,而不僅僅是檢索儲存在類變數中的值。

前面提到過,在設計類時,通常的做法是使所有成員變數為 private,並為存取這些變數提供公共的 set 和 get 函數,這樣可以保護資料。類之外的函數只能通過呼叫公共成員函數來存取成員資料,並且這些函數可以被寫入,以防止資料被破壞或修改,從而可能會對類的物件的行為產生不利影響。

注意,程式中寫入 set 函數一定要濾出無效資料,而不是允許將無效值儲存在成員變數中,如果傳遞給它們的資料不可接受,則它們將使用預設值。

避免陳舊資料

在 Rectangle 類中,getLength 和 getWidth 成員函數返回儲存在 length 和 width 成員變數中的值,但 getArea 成員函數返回計算結果。

有人可能會奇怪,為什麼矩形的面積沒有儲存在成員變數中。該面積未被儲存,是因為它可能會變得陳舊。當某個專案的值取決於其他資料,並且當其他資料被更改而該專案未及時更新時,就可以說該專案已經變得陳舊。如果矩形的面積儲存在成員變數中,則只要 length 或 width 成員變數發生變化,那麼它的值就會變得不正確。

在設計類時,一般不要使用成員變數來儲存可能變得過時的計算值,相反,應該提供使用最新資料計算值的成員函數,然後返回計算結果。

行內函式詳解

在設計一個類時,將需要確定哪些成員函數在類宣告中作為行內函式來編寫,哪些函數在類之外定義。編譯器對行內函式的處理方式與常規函數完全不同,了解這種差異將有助於程式設計師決定使用這兩種函數的時機。

每次呼叫常規函數時,都會在幕後進行許多操作。一些特殊的專案,如函數執行完成時返回的地址和函數實參的值,都必須儲存在名為棧(Stack)的記憶體部分中;另外,還需要建立區域性變數並保留一個位置來儲存函數的返回值。所有這些函數呼叫階段的設定開銷都會佔用 CPU 時間,雖然所需的時間很小,但是如果一個函數被呼叫多次(例如在某個迴圈中),那麼它是會被累加的。

另一方面,行內函式根本不是傳統意義上的內聯,相反,在所謂內聯擴充套件的過程中,編譯器將使用函數本身的實際程式碼替換對函數的每次呼叫。這意味著,如果從程式中的多個地方呼叫該函數,則其程式碼的整個主體將被多次插入,從而增加程式的大小,這就是為什麼只有寥寥幾行程式碼的函數才能寫成一個行內函式。

實際上,如果函數太大而使得內聯擴充套件不可行,那麼編譯器將忽略以這種方式處理常式的請求。但是,當一個成員函數很小的時候,把它寫成一個行內函式確實可以提高效能,因為當沒有進行實際的函數呼叫時,它的開銷更少。