【C++入門筆記】this指標和類的繼承
前言
此文為小甲魚大佬的《C++快速入門》第十六講內容筆記整理。
this指標
在“物件”的世界裡,有一個特殊的指標,它叫做this。從下面這個典型的栗子來認識它。
class Human{
char fishc;
Human(char fishc);//建構函式,裡面的引數定義為fishc
}
Human::Human(char fishc){ //建構函式初始化
fishc = fishc;
}
此程式的意圖是想將傳入引數賦值給屬性的fishc,但是因為它們的名字一樣,這樣子的話構造器就有可能認不出來。因為它不知道你是要將屬性去覆蓋引數還是講傳入的引數去覆蓋屬性。
如何構造器知道哪個是引數、哪個又是屬性呢?
這時候就需要用到它了——this指標。
this -> fishc = fishc
上句的含義是:這個物件的fishc的屬性 = 傳入的引數。
注意:
使用this指標的基本原則是:如果程式碼不存在二義性隱患,就不必使用this指標!
this指標會在更加高階的方法裡用到(這個未完待續,等我學到後面再擴充這部分)
類的繼承
繼承機制使得程式設計師可以建立一個類的堆疊層次結構(金字塔形),每個子類均整合在它的基類(父類或者超類)裡定義的方法和屬性。
用途:通過繼承機制,程式設計師可以對現有的程式碼進行進一步的擴充,並應用在新的程式中。
#include <iostream> #include <string> class Animal { public: std::string mouth; void eat(); void sleep(); void drool(); }; class Pig : public Animal { public: void climb(); }; class Turtle : public Animal { public: void swim(); }; void Animal::eat() { std::cout << "I am eating!" << std::endl; } void Animal::sleep() { std::cout << "I am sleeping!" << std::endl; } void Animal::drool() { std::cout << "I am drooling!" << std::endl; } void Pig::climb() { std::cout << "I am climbing!" << std::endl; } void Turtle::swim() { std::cout << "I am swimming!" << std::endl; } int main() { Pig pig; Turtle turtle; pig.eat(); turtle.eat(); pig.climb(); turtle.swim(); return 0; }
I am eating!
I am eating!
I am climbing!
I am swimming!
可以看到,子類Pig 和 Turtle 中並未定義相關的方法,但因為其繼承於父類Animal,而父類中擁有這些方法,所以可以使用。
再論this指標
回顧下:this指標是類的一個自動生成、自動隱藏的私有成員,它存在於類的非靜態成員函式中,指向被呼叫函式所在的物件的地址。
當一個物件被建立時,該物件的this指標就自動指向物件資料的首地址。
先看一個簡單的栗子:
#include<iostream>
class Point
{
private:
int x, y;
public:
Point(int a, int b)
{
x = a;
y = b;
}
void MovePoint(int a, int b)
{
x = a;
y = b;
}
void print()
{
std::cout << "x=" << x <<"\n"<< "y=" << y << std::endl;
}
};
int main()
{
Point point1(10, 10);
point1.MovePoint(2, 2);
point1.print();
return 0;
}
x=2
y=2
- 當物件point1呼叫MovePoint(2,2)函式時,即將point1物件的地址傳遞給了this指標。
- MovePoint函式的原型事實上應該是 void MovePoint( Point *this, int a, int b);
- 第一個引數是指向該類物件的一個指標,我們在定義成員函式時沒看見是因為這個引數在類中是隱含的。
- 這樣point1的地址傳遞給了this,所以在MovePoint函式中便可以顯式的寫成:void MovePoint(int a, int b) { this->x = a; this->y = b;}
- 即可以知道,point1呼叫該函式後,也就是point1的資料成員被呼叫並更新了值。
接下來驗證一下:當一個物件被建立時,該物件的this指標就自動指向物件資料的首地址。將this指標的地址和物件的地址打印出來。
#include <iostream>
#include <string>
class Pet
{
public:
Pet(std::string theName);
~Pet();
static int getCount();
protected:
std::string name;
private:
static int count;
};
class Dog : public Pet
{
public:
Dog(std::string theName);
};
class Cat : public Pet
{
public:
Cat(std::string theName);
};
int Pet::count = 0; // 注意這一句:他起碼做了兩件事
Pet::Pet(std::string theName)
{
name = theName;
count++;
std::cout << "一隻寵物出生了,名字叫做: " << name << "\n";
}
Pet::~Pet()
{
count--;
std::cout << name << "掛掉了\n";
}
int Pet::getCount()
{
return count;
}
Dog::Dog(std::string theName) : Pet(theName)
{
std::cout << "this:" << this << "\n";
}
Cat::Cat(std::string theName) : Pet(theName)
{
}
int main()
{
Dog dog("Tom");
std::cout << "dog:" << &dog << "\n";
Cat cat("Jerry");
std::cout << "\n已經誕生了" << Pet::getCount() << "只寵物!\n\n";
Dog dog_2("Tom_2");
std::cout << "dog:" << &dog_2 << "\n";
Cat cat_2("Jerry_2");
std::cout << "\n現在呢,已經誕生了" << Pet::getCount() << "只寵物!\n\n";
std::cout << "\n現在還剩下 " << Pet::getCount() << " 只寵物!\n\n";
return 0;
}
一隻寵物出生了,名字叫做: Tom
this:0x6ffdf0
dog:0x6ffdf0
一隻寵物出生了,名字叫做: Jerry已經誕生了2只寵物!
一隻寵物出生了,名字叫做: Tom_2
this:0x6ffdd0
dog:0x6ffdd0
一隻寵物出生了,名字叫做: Jerry_2現在呢,已經誕生了4只寵物!
現在還剩下 4 只寵物!Jerry_2掛掉了
Tom_2掛掉了
Jerry掛掉了
Tom掛掉了
可以看到地址是一樣的。
在任何一個方法裡都可以使用this指標。從本質上講,C++中的物件其實是一種特殊的結構--除了變數,還包含一些函式的特殊結構。
在程式執行時,物件的屬性(變數)和方法(函式)都是儲存在記憶體裡,這就意味著它們各自都有與之相關聯的地址。
這些地址都可以通過指標來訪問,而this指標毋庸置疑是儲存著物件本身的地址。
每當我們呼叫一個方法的時候,this指標都會隨著你提供的輸入引數被祕密地傳遞個那個方法。
正是因為如此,我們才能夠在方法裡像使用一個區域性變數那樣使用this指標。
又因為靜態變數不是屬於某個特定的物件,(一個物件生成之後是放在棧裡面的)而是由全體物件共享的,(每個物件都有this指標)這就意味著它們(靜態變數)無法訪問this指標。所以,我們才無法在靜態方法裡訪問非靜態的類成員(因為非靜態的類成員需要用到隱含的this指標來訪問)。
總結
此講簡單介紹了在C++中的this指標和繼承的概念,隨著自己理解的深入,我也將逐漸豐富這些筆記。
參考視訊
《C++快速入門--小甲魚》https://www.bilibili.com/video/av28127959?from=search&seid=13080921018912796262