學習軌道
/
C++
C++
/
練習
/
Speedywagon 基金會
Speedywagon 基金會

Speedywagon 基金會

學習練習

簡介

和許多其他語言一樣,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,展示神力女超人最具代表性的「真理套索」。

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;

指標與參考的比較

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

說明

歡迎,工程師! 你是 Speedywagon 基金會僅存的幾位老將之一,這個祕密組織數十年來一直對抗著 Pillar Men 這類古老的威脅。 在這段過程中,你花了許多年維護基金會的技術系統,這些系統結合了最先進的技術與日漸老舊的函式庫。

然而近來,負責追蹤 Pillar Men 活動的感測器開始故障了。 基金會的系統已經老舊,程式碼還得與一個無法更新的老舊 C++ 函式庫互動。 你的任務是實作四個核心函式,透過老派的指標型函式庫來監控 Pillar Men 感測器的活動。

身為現代的 C++ 工程師,你會偏好使用智慧指標,但無奈老舊的程式碼就是要你尊重舊有的做法。 人類的命運或許就繫在這些指標上,所以請謹慎行事,願 Hamon 能量引導你。

Note

由於感測器讀數可能非常龐大,我們提供了一個模擬的 struct,對應實際函式庫中所使用的版本。 程式碼已經在標頭檔中為你實作好了。

struct pillar_men_sensor {
    int activity{};
    std::string location{};
    std::vector<int> data{};
};

1. 檢查感測器連線(connection_check)

你的第一項任務,是確保 Pillar Men 感測器正確連線。 我們可不能讓未連線的感測器觸發誤報。 你要撰寫一個函式connection_check,透過檢查nullptr來測試感測器的指標是否有效。

  • 定義一個函式,接受一個指向pillar_men_sensor struct 的指標。
  • 若感測器指標不是 null,這個函式應回傳true,否則回傳false。
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. 計算感測器的活動量(activity_counter)

Pillar Men 正潛伏在暗處,我們需要知道感測器是否偵測到任何活動。 你要撰寫activity_counter函式,它會接受一個感測器陣列,以及一個代表陣列中感測器數量的容量。

  • 定義一個函式,接受一個指向陣列第一個元素的指標,以及該陣列的容量。
  • 使用指標運算來走訪感測器陣列,並累加活動讀數。
  • 回傳累加後的活動量。
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123

3. 警報控制(alarm_control)

除非真的發生危險,否則不是每個感測器都該觸發警報。 alarm_control函式會確保感測器只有在活動量高於 0 時才觸發警報。 這個函式也應檢查感測器是否為 null,以避免系統當機。

  • 定義一個函式,接受一個指向pillar_men_sensor的指標。
  • 函式應先檢查感測器是否為nullptr。若感測器是nullptr,就回傳false。
  • 若感測器有效,且其活動量大於 0,就回傳true;否則回傳false。
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true

4. 使用uv_alarm函式檢查資料是否有異常

在這項任務中,你要實作uv_alarm函式,根據紫外光曝露程度與感測器活動量,判斷是否該觸發警報。 uv_alarm函式應使用提供的uv_light_heuristic函式,它會處理一份資料向量,並根據特定門檻回傳一個值。 這是正式環境中會執行的複雜程式碼的模擬版本,請不要更動它的介面。

在speedywagon命名空間中定義uv_alarm函式。它應該:

  • 接受一個指向pillar_men_sensor struct 的指標,作為它的參數。
  • 若感測器指標是 null,就回傳false。
  • 呼叫uv_light_heuristic函式,並傳入感測器data陣列的位址。
  • 若uv_light_heuristic回傳的值大於sensor->activity的活動量,就回傳true,否則回傳false。
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
C++ Exercism

準備好開始 Speedywagon 基金會 了嗎?

註冊 Exercism,透過 19 個概念100 個練習 和真人引導來學習並精通 C++,全部免費。