指標

指標 在 C++

1 個練習

關於 指標

如同許多其他語言,C++ 也有_指標_。 你已經知道_參考_和_指標_很相似,但可以把它們想成更接近電腦內部運作的一層。 _指標_是儲存物件位址的變數。 它們用來直接與物件互動,讓 C++ 能動態配置記憶體,並有效率地操作資料。

如果你剛接觸_指標_,可能會覺得有點神祕,但一旦熟悉之後,就會發現它們其實相當直觀。

指標是 C++ 中非常關鍵的一部分,所以花點時間好好理解它們吧。 這個概念裡最基本的版本也稱為_笨指標_或_原始指標_。 在現代 C++ 中還有_智慧指標_;最基本的型態本身一點也不聰明,所有工作都得自己手動處理。

在深入細節之前,值得先了解_指標_的用途。 _指標_是一種把物件位址分享給程式其他部分的方法,這在兩個主要方面很有用:

  1. 和_參考_一樣,指標能避免複製,有助於減少程式的資源用量。
  2. 和_參考_不同,指標可以重新指定到不同的物件。
  3. 指標也可以指向空值,表示它們目前沒有指向任何物件。

一般語法

在 C++ 中宣告指標時,需要指定指標所指向的資料型態,接著寫一個星號(*)和指標的名稱。 指標在宣告時不會自動初始化。 如果沒有明確指定,指標通常會持有不確定的值,一般稱為「垃圾位址」。 雖然某些編譯器可能會把指標初始化為nullptr,但並非所有編譯器都保證這種行為,所以千萬不要依賴它。 最好的做法是明確地初始化原始指標,並在使用前確認它不是空指標,以避免潛在問題。

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

要把變數的位址指定給指標,可以使用取址運算子(&)。 要解參考指標,則使用_間接運算子_(*)。

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

注意:解參考必須明確寫出來,而_參考_則像別名一樣直接運作。

指標運算

_指標運算_讓你能對指標進行算術運算,在處理陣列時特別有用。 把整數加到指標上,會讓它指向不同的元素。

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

在 C++ 中,如果沒有小心處理,指標運算很容易導致 未定義行為。 未定義行為可能會以意想不到的程式結果、當機,甚至安全漏洞等形式出現。 未定義行為造成後果的一個惡名昭彰的例子,是 1996 年的亞利安 5 號火箭爆炸:一個把 64 位元浮點數轉換成 16 位元有號整數所引发的軟體例外,導致了災難性的失敗。

存取成員變數

在 C++ 中,->運算子用來透過指向物件的指標存取該物件的成員。 它是一種簡寫,能簡化透過指標存取物件成員的過程。 舉例來說,如果ptr是指向某個具有成員變數x的物件,你可以直接使用ptr->x,而不必寫成(*ptr).x。 在處理指向物件的指標時,這個運算子能提升程式碼的可讀性,並減少冗長的寫法。

這裡有個簡短的例子,其中 struct Superhero有一個成員變數superpower。 主函式建立了一個指向Superhero物件(代表神力女超人)的指標dianaPrince。 ->運算子用來存取成員變數superpower,展示神力女超人標誌性的「Lasso of Truth」。

struct Superhero {
    std::string superpower;
};

Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";

// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

// Memory cleanup:
delete dianaPrince;

指標與參考的比較

指標和參考都能間接存取物件,但它們的能力和安全考量不同。 指標可以靈活地改變所指向的物件,也可以指定為空值。 不過,這種彈性也帶來風險,例如解參考空指標,或產生懸空指標。 相較之下,參考不能是空值,而且在建立時就綁定到有效的物件,因此避開了這些風險。 由於參考比較安全,除非真的需要指標提供的額外功能,否則應該優先使用參考。

Advanced

動態記憶體配置與空指標

在 C++ 中,new用來在執行期間於堆積上動態配置物件所需的記憶體。 它會回傳指向所配置記憶體位址的指標,讓物件的生命週期不必綁定在某個特定作用域。 不過,重要的是要讓new總是搭配delete,才能正確釋放記憶體。 使用動態記憶體配置時要謹慎,以避免記憶體洩漏、存取已釋放記憶體等常見問題。

指標也可以設成一種稱為_空指標_的特殊值,表示它們沒有指向有效的位址。 空指標通常用來在把指標指定到有效值之前,先行初始化指標。

// There is a dark mark bearer, but who is it?
std::string* darkMarkBearer{nullptr};
// It is Bellatrix!
darkMarkBearer = new std::string{"Bellatrix Lestrange"};
// She has been defeated
delete darkMarkBearer;
// Reset pointer to null, symbolizing there is no more darkMarkBearer at the moment
darkMarkBearer = nullptr;

指標應該不是持有有效物件的位址,就是設為nullptr。 這樣一來,你只需要檢查指標是否為空,就可以假設任何非空指標都是有效的。 當物件被銷毀時,任何指向該物件的指標都會變成_懸空_(它們不會自動被設為nullptr)。 偵測這些情況,並確保之後把那些指標設為nullptr,是你的責任。

在較舊的程式碼中,你可能會遇到兩種nullptr的替代寫法。 首先,字面值0會被特別解讀為指標的空值,不過這也是整數字面值唯一能指定給指標的情況。 其次,從 C 繼承而來、定義於<cstddef>標頭檔中的preprocessor macro NULL,是空指標的另一種表示方式,不過在現代 C++ 程式碼中較不常見。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 指標

練習已鎖定

再解鎖 1 個練習,就能練習 指標