學習軌道
/
C++
C++
/
練習
/
Troy 的力量
Troy 的力量

Troy 的力量

學習練習

簡介

C++ 中的 auto 關鍵字

在 C++ 中,auto關鍵字是 C++11 引入的強大功能,用來宣告型別會被推導出來的變數。 編譯器會根據初始值推導出變數的型別,讓程式碼更好讀、也更好維護。

使用範例

看看下面這個用auto宣告變數的例子:

auto dragon_population{3};          // dragon_population is deduced as an integer
auto westeros{7.7777};              // westeros is deduced as a double
auto wedding_location{"The Twins"}; // wedding_location is deduced as a const char*, not std::string

每一個例子裡,變數的型別都是從初始化時所用的值推導出來的。

型別推導

auto關鍵字能減少明寫型別的冗長感,讓程式碼更精簡、更好讀。

const std::vector<std::string> pigeon_pie{"flour", "butter", "pigeon", "salt"};
auto purple_wedding_pie{pigeon_pie};
purple_wedding_pie.emplace_back("the strangler");

在這段迴圈中,auto把purple_wedding_pie的型別推導為std::vector<std::string>,不必再明寫一次型別。

相容性

auto關鍵字能與各種 C++ 結構搭配,是現代 C++ 程式設計中用途廣泛的工具。

auto& element{array[0]};           // reference to an element
const auto object{otherObject};    // const type version of otherObject's type
auto* ptr{&x};                     // pointer to x with the same type as x, but as a pointer.

之後的單元裡,我們會經常看到auto關鍵字搭配 lambda 運算式、以範圍為基礎的for迴圈,以及迭代器。

智慧指標

智慧指標是現代 C++ 的功能,目的是提供自動記憶體管理,協助避免原生指標常見的記憶體洩漏和懸空指標。 它們像是原生指標的包裝,額外提供自動釋放記憶體等功能,在指標不再需要時自動把記憶體釋放掉。

一般語法

智慧指標在 C++ 標準函式庫中通常以類別模板實作。 最常用的兩種智慧指標是std::unique_ptr和std::shared_ptr。

獨佔指標

std::unique_ptr是一種獨佔物件所有權的智慧指標。 它確保在任何時刻,資源都只由一個std::unique_ptr物件所擁有。 當擁有資源的std::unique_ptr被銷毀或重置時,它會自動解構物件並釋放記憶體。

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

std::make_unique() 的優點

建立std::unique_ptr時,建議使用std::make_unique(),而不是直接用new配置記憶體。 std::make_unique()有幾項優點:

  1. 例外安全:std::make_unique()能保證例外安全。 如果在建構物件期間拋出例外,記憶體會自動被釋放,避免記憶體洩漏。
  2. 清楚明瞭:使用std::make_unique()讓程式碼更清楚、更精簡。 它不必明寫要配置的型別,因為模板引數會自動推導出來。
  3. 最佳化機會:比起用new手動配置記憶體,編譯器更有機會有效最佳化std::make_unique(),效能可能因此提升。
  4. 避免誤用:手動建構std::unique_ptr時,有可能把底層資源刪除掉。 當std::unique_ptr在離開作用域時試圖刪除它,就會導致未定義行為。

共享指標

std::shared_ptr是一種智慧指標,允許讓多個std::shared_ptr物件共享同一個資源的所有權。 它會追蹤有多少個共享指標正在引用這個資源,只有在最後一個擁有該資源的共享指標離開作用域或被重置時,才會釋放記憶體。

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

在 C++17 及更早的版本中,並不直接支援透過std::make_shared<T[]>將std::shared_ptr用於陣列。 雖然可以用std::make_shared<T[]>配置陣列,但直接由它建立共享指標可能會導致未定義行為,因為單一物件和陣列在記憶體管理上有所不同。 建議改用std::vector或自訂刪除函式,才能有效以共享指標管理陣列。 在 C++17 中處理陣列配置與共享指標時,請務必確認與你的編譯器和標準函式庫實作相容。

std::make_shared() 的優點

和std::make_unique()類似,std::make_shared()也帶來記憶體效率提升、例外安全和可讀性等好處。 它把控制區塊和受管理物件的記憶體配置合併成一次操作,提升效率並降低記憶體洩漏的風險。 此外,自動推導模板引數能簡化程式碼並提升可讀性。 在 C++ 中操作std::shared_ptr物件時,使用std::make_shared()能寫出更簡潔、更安全、更有效率的程式碼。

使用建議

預設使用智慧指標:獨佔所有權用std::unique_ptr,共享所有權用std::shared_ptr。 原生指標則保留給不具所有權的引用,或與舊有程式碼介接時使用。 大多數情況下,std::unique_ptr就足以處理獨佔所有權,因為它提供輕量的記憶體管理,沒有引用計數的額外負擔。 std::shared_ptr則應謹慎少用,因為除非真的需要共享所有權,否則它會帶來額外負擔和複雜度。

說明

這個練習會帶你進入特洛伊的世界。 這裡居民的生活充滿了驚奇與魔法。 特洛伊的許多人類擁有力量,並且在日常生活中頻繁使用。 力量可以用來重塑世界,或影響特洛伊的動物和其他人。 魔法也會顯現在獨特的器物上,這些器物深受冒險者、工匠和賢者追捧。

在這個練習中,你將撰寫程式碼來模擬特洛伊的人類、他們所擁有的器物,以及力量之間的互動。

你有 6 項任務。 第一項與建立人類有關,其餘 5 項則與處理力量和器物有關。

1. 將人類帶到特洛伊的世界

在你的特洛伊模型中,人類是最重要的部分。 你模型裡的人類應該要能擁有獨特的器物。 他們也應該要能顯現力量。 這些力量可能會影響其他人類,所以你也會想模擬人類是否受到其他力量影響。

你已經有artifact和power結構的基本實作。 請實作一個human結構(或類別),其中有一個名為possession的artifact成員變數,並以智慧指標指向它。 在任何時刻,每件器物只能由單一人類擁有。

human還必須有 2 個額外的成員變數。 一個用來存放own_power,另一個則存放他們influenced_by的力量。 own_power和influenced_by都是指向power的智慧指標。 每個power可能由單一人類擁有,但同時也能影響其他人類。

預設情況下,人類出生時不帶任何器物,既不擁有任何力量,也不受任何力量影響。

human mindy_mccready{};
mindy_mccready.possession;
// => nullptr
mindy_mccready.own_power;
// => nullptr
mindy_mccready.influenced_by;
// => nullptr

2. 將器物帶入特洛伊

如果模型中的各個部分不能互動,就太無聊了。 你想創造獨特的器物,並把它們交給特定的人類。

定義函式give_new_artifact,它不會回傳任何東西,但接受一個human的參考和一個string。 它應該用這個string定義一個新的artifact物件,並據此設定該human的possession指標。 這個函式不應該回傳任何東西。

human erik_magnus_lehnsherr{};
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet");

erik_magnus_lehnsherr.possession->name;
// "Mind shielding helmet"

3. 讓物品可以交易

特洛伊的世界充滿了互動。 你希望人們能透過交換彼此的所有物來進行交易。

撰寫一個函式exchange_artifacts,它不會回傳任何東西,但接受 2 個器物智慧指標來交換物品。 請記住,你無法複製unique_ptr。 這也包括用在函式參數上的情況。 請改用unique_ptr的參考。

human uchiha{};
give_new_artifact(uchiha, "konoha headband");
human uzumaki{};
give_new_artifact(uzumaki, "forehead protector");

exchange_artifacts(uchiha.possession, uzumaki.possession);

uchiha.possession->name;
// "forehead protector"
uzumaki.possession->name;
// "konoha headband"

4. 將力量賦予人們

特洛伊最令人興奮的特色,就是人們可能擁有的特殊力量。 有些人能用意念熔煉鋼鐵,有些人則能在夜晚瞬間治癒所有傷口。

定義函式manifest_power,它不會回傳任何東西,但接受一個human的參考和一個string。 它應該用這個string定義一個新的powers物件,並據此設定該human的own_power指標。 這個函式不應該回傳任何東西。

human eleven {};
manifest_power(eleven, "psychokinesis");

eleven.own_power->effect;
// "psychokinesis"

5. 使用力量

再強大的力量,如果不能使用,又有什麼用呢。 你的模型聚焦於人類,所以你想追蹤力量的影響。

撰寫一個void函式use_power,它接受 2 個 human 的參考。 第一個是施術的人類,第二個則代表目標。 目標的influenced_by指標應該指向施術者的力量。

為了簡單起見,人類只能受到單一力量的影響。 即使施術者已經不存在,這股力量仍會持續作用。

human pamela_isley{};
manifest_power(pamela_isley, "control pheromones");

human count_vertigo{};
use_power(pamela_isley, count_vertigo);
count_vertigo.influenced_by->effect;
// "control pheromones"

6. 留意力量的強度

某些力量在施加於多個人類時,會失去效力,或在你的模擬中觸發特定效果。 你想追蹤與每個力量連結的人數。

定義函式power_intensity,它接受一個 human,並以int回傳其力量的強度。 如果這個人沒有任何力量,回傳值應該是0。 否則,強度應該反映出施術者以及目前所有受影響的人。

human jean_grey{};
manifest_power(jean_grey, "uplifting personality");

human scott{};
human logan{};
human ororo{};

use_power(jean_grey, ororo);
use_power(jean_grey, logan);
use_power(jean_grey, scott);

power_intensity(jean_grey);
// 4
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
C++ Exercism

準備好開始 Troy 的力量 了嗎?

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