this 指標

指標 那篇中,我們理解了什麼是指標。而在類別 Class 中,存在了另一個指標,叫做 this 指標(this pointer)。

這章我們就是要來一探究竟這個指標究竟是什麼東西!

初學者的疑問

初學類別的人很常問的一個問題就是:「我在呼叫成員函數時,程式碼是怎麼知道是哪個實體物件的函數?」

聽不懂什麼意思?我們來看一個例子:

class Object
{
private:
    int m_val{};

public:
    Object(int val)
        : m_val(val) {}

    int getVal() const { return m_val; }
    void setVal(int val) { m_val = val; }
};

int main()
{
    Object obj_1 = Object(1);
    Object obj_2 = Object(2);

    std::cout << obj_1.getVal() << std::endl;

    return 0;
}

這個程式碼最後會輸出 1,我想不會有人有問題。 但是當我們在呼叫 obj_1.getVal() 時,程式碼怎麼知道是要提取 obj_1 的呢?

你可能會說啊前面都已經說是 obj_1 了,這樣不是很明顯嗎?

以人類的眼睛來說是沒錯!但以程式碼的角度來看的話,其實當中還有更多的秘密,而這就是 this 指標表現的地方!

什麼是 this 指標

我們在 指標 那章學到,指標是用來儲存一個物件的地址。

其實在每一個由類別所創建出來的實體物件中,都有一個「隱藏」的 this 指標。

為什麼說是「隱藏」的呢?因為我們平常不會把它寫出來。

當我們把它寫出來的話,程式碼會變這樣:

class Object
{
private:
    int m_val{};

public:
    Object(int val)
        : m_val(val) {}
    
    // 利用 this 指標存取 m_val
    int getVal() const { return this->m_val; }
    void setVal(int val) { this->m_val = val; }
};

int main()
{
    Object obj_1 = Object(1);
    Object obj_2 = Object(2);

    std::cout << obj_1.getVal() << std::endl;

    return 0;
}

基本上和原本的程式碼一模一樣,輸出結果也完全一樣。

唯一改變的地方在於這裡:

int getVal() const { return this->m_val; }
void setVal(int val) { this->m_val = val; }

在這裡,我們利用 this 去取得當前物件,然後利用 -> 取得物件的特徵。

而每個實體物件都有各自的 this 指標,因此在上面那個例子中,obj_1obj_2 都各有一個 this 指標,總共有兩個!

成員函數轉換

前面說到,程式碼會利用 this 指標查看應該使用哪一個實體物件。

實際上他到底是怎麼利用呢? 我們來看看這個簡單的呼叫函數:

obj_1.setValue(5);

我們呼叫這個函數時,看似只有傳入一個參數,但實際上它接收了兩個!

程式碼其實會在編譯過後長成這樣:

Object::setValue(&obj_1, 5);

&obj_1 的值,也就是 obj_1 的地址,就成了 this 指標的值了!

應用

現在我們知道 this 指標的存在了,那我們可以用它來做什麼事呢?

其中一個很常見的應用就是 method chaining 方法鏈 我們來看一個例子:

class Robot
{
private:
    int m_x{};
    int m_y{};

public:
    Robot(int x, int y)
        : m_x(x), m_y(y) {}

    void right() { m_x += 1;}
    void left() { m_x -= 1;}
    void up() { m_y += 1;}
    void down() { m_y -= 1;}
};

int main()
{
    Robot rob = Robot(0, 0);
    
    // 移動機器人
    rob.left();
    rob.right();
    rob.up();

    return 0;
}

在這個例子中,我們利用三行程式碼讓機器人向左、右、上行走。

我們可以修改一下成員函數,並回傳 this 指標,做到方法鏈:

class Robot
{
private:
    int m_x{};
    int m_y{};

public:
    Robot(int x, int y)
        : m_x(x), m_y(y) {}

    Robot& right() { m_x += 1; return *this;}
    Robot& left() { m_x -= 1; return *this;}
    Robot& up() { m_y += 1; return *this;}
    Robot& down() { m_y -= 1; return *this;}
};

int main()
{
    Robot rob = Robot(0, 0);
    
    // 利用方法鏈移動機器人
    rob.left().right().up();

    return 0;
}

這樣是不是簡潔很多!尤其在更複雜的程式碼,方法鏈可以大幅度的增加可讀性。

我們來仔細看看最後這行程式碼是如何運作的:

rob.left().right().up();

首先,我們呼叫 rob.left(),這會對物件成員 m_x 做相對應改變。

left() 之後返回 this 指標,這只是對物件 rob 的引用,因此 rob 會在隨之而來的函數呼叫中被使用。

同樣的流程經過 right()up(),最後再一次返回 this 指標,但這不會進一步使用,因此被忽略。

這大概就是 this 指標最常見的應用方式了!每當我們可以使用可鏈接的成員函數時,我們都應該使用或是實作它!

總結

這章我們了解了什麼是 this 指標,它的作用以及實際例子。

這對實際寫程式碼來說沒有多大的改變,甚至可以說改變甚小,但是對於理解程式碼的背後運作邏輯是很重要的!

這樣我們就對程式理解更進一步了!恭喜你們!