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_1
和 obj_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
指標,它的作用以及實際例子。
這對實際寫程式碼來說沒有多大的改變,甚至可以說改變甚小,但是對於理解程式碼的背後運作邏輯是很重要的!
這樣我們就對程式理解更進一步了!恭喜你們!