你已經在幾個概念裡用過也寫過 C++ 函式了,現在該來認真談點技術細節啦。下面的程式碼片段列出了最常見的術語,方便你隨時查閱。由於 C++ 會忽略空白,這裡調整了排版,把每個元素各自放在一行。
// Function declaration:
bool // Return type
admin_detected(string user, string password) // Type signature
; // Don't forget the ';' for the declaration
// Function definition:
bool // Return type
admin_detected // Function name
(string user, string password) // Parameter list
{ return user == "admin" && password == "1234"; } // Function body
宣告就像是寫給編譯器的一張便條,告訴它有個函式叫這個名字,有著這樣的回傳型別和參數列表。 如果少了定義,程式就無法運作。 宣告是選用的,但當你在定義之前就用到這個函式時,就需要它。 宣告可以解決循環參照之類的問題,也能用來把介面和實作分開。
有時你會想確保某些值在初始化之後就不能再被改變,C++ 使用const關鍵字作為常數的限定詞。
const int number_of_dragon_balls{7};
number_of_dragon_balls--; // compilation error
你經常會看到常數以 UPPER_SNAKE_CASE 來命名。如果沒有其他慣例,建議把這種大小寫形式保留給巨集使用。
如果你在設定之後試著修改常數變數,程式就無法編譯。這有助於避免不小心改動,也為編譯器開啟了最佳化的可能性。對人來說也一樣,如果你知道某些部分不會受到影響,就更容易推敲整段程式碼。
你也可以把const當作函式參數的限定詞。
string guess_number(const int& secret, const int& guess) {
if (secret < guess) return "lower.";
if (secret > guess) return "higher.";
return "exact!";
}
當你傳入const參照給函式時,就能確保它不會被改動。對於複製起來可能很耗資源的物件,例如較長的字串,你經常會看到使用const參照。const限定詞的第三種用途,是不會改變類別執行個體的成員函式。
class Stubborn {
public:
Stubborn(string reply) {
response = reply;
}
string answer(const string& question) const {
if (question.length() == 0) { return ""; }
return response;
}
private:
string response{};
};
Stubborn的成員函式answer使用const string&參照作為參數,這樣就避免了對傳入函式的原始物件進行複製。
只要參數列表不同,多個函式就可以共用同一個名稱。這叫做函式多載,通常用於這些函式要執行非常相似的工作時。
去掉回傳型別的函式標頭,就是這個函式的__型別簽章__。型別簽章只要有變,就是一個新的函式。
play_sound這個例子有六種不同的多載,來應付不同的情境:
// different argument types:
void play_sound(char note); // C, D, E, ..., B
void play_sound(string solfege); // do, re, mi, ..., ti
void play_sound(int jianpu); // 1, 2, 3, ..., 7
// different number of arguments:
void play_sound(string solfege, double duration);
// different qualifiers:
void play_sound(vector<string>& solfege);
void play_sound(const vector<string>& solfege);
型別簽章由函式的名稱、參數的數量、參數的型別,以及參數的限定詞所定義(但不包含參數的名稱)。 回傳型別明確地不屬於型別簽章的一部分,如果你有兩個只有回傳型別不同的函式,就會得到編譯錯誤。 編譯器會抱怨,因為它不清楚該用這兩個之中的哪一個。
有些函式會變得非常冗長,而且許多呼叫可能對大部分參數都使用相同的值。這些呼叫中的重複,可以用預設引數來避免。
void record_new_horse_birth(string name, int weight, string color="brown-ish", string dam="Alruccaba", string sire="Poseidon");
record_new_horse_birth("Urban Sea", 130); // color will be brown, dam "Alruccabam", sire "Poseidon"
record_new_horse_birth("Highclere", 175, "off-white", "Fall Aspen"); // sire will be "Poseidon"
由於函式宣告通常比定義更早被讀到,所以在宣告裡設定預設引數是比較好的做法。如果某個參數有預設值,它右邊的所有參數也都必須有預設值。有時候可以把複雜的函式多載重構成較少的函式並搭配預設引數,來提升可維護性。
在這個練習中,你會再寫一些程式碼,內容與準備和烹調你最愛食譜中那道絕妙的千層麵有關。
這個練習有五個任務。 第一個任務與烹調本身有關,其他四個則是關於完美的準備工作。
為了接下來要做的千層麵,你想確保自己預留了足夠的時間,好享受烹調的過程。 你已經規劃好千層麵要有哪些層了。 現在你想根據這些規劃,估算準備工作需要多久。
實作一個函式 preparationTime,它接受一個存放各層的 vector(型別為string),以及每層的平均準備時間(單位為分鐘,型別為int)。
這個函式應根據層數,以int回傳總準備時間的估算值。
如果沒有傳入平均準備時間,就使用預設值2。
std::vector<std::string> layers{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"};
preparationTime(layers, 3)
// => 18
preparationTime(layers)
// => 12
除了預留時間,你也想確保有足夠的醬汁和麵條,能煮出你夢想中的千層麵。 千層麵中每有一層麵條,就需要 50 公克的麵條。 每有一層醬汁,就需要 0.2 公升的醬汁。
定義函式 quantities,它接受一個存放各層的 vector 作為參數,型別為string。
這個函式會算出做這道料理所需的麵條與醬汁份量。
結果應以amount _struct_的形式回傳,這個結構已經替你定義好了。
amount有兩個成員變數,一個是noodles的重量,型別為int;另一個是sauce的體積,型別為double。
struct amount { int noodles; double sauce; };
amount needed{quantities({"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})};
// needed.noodles => 100
// needed.sauce => 0.4
前陣子你去拜訪一位朋友,在他家吃了千層麵。 那千層麵非常美味,而且有某種特別之處。 朋友把食材清單寄給你,並告訴你清單上的最後一項,就是讓那頓飯如此特別的「祕密食材」。 現在你也想把那個祕密食材加進自己的食譜裡。
寫一個函式 addSecretIngredient,它接受兩個vector of strings作為參數。
第一個參數是你自己食譜裡食材的_參考_,第二個則是朋友寄給你的食材_vector_。
你的食材清單中最後一個元素一律是"?"。
這個函式應把它替換成朋友清單中的最後一項。
注意:addSecretIngredient不會回傳任何東西,你應該直接修改自己的食材清單。
朋友食材的清單也可以使用參考(為了提升效能),但不應該被修改。
請用const參考確保清單不會被修改。
std::vector<std::string> friendsList{"noodles", "sauce", "mozzarella", "kampot pepper"};
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
addSecretIngredient(myList, friendsList);
// myList => {"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}
你的食譜書上列出的份量,只夠做出兩人份的千層麵。 因為下次你想煮給更多人吃,所以想計算不同份數所需的份量。
實作一個函式 scaleRecipe,它接受兩個參數。
double組成的 vector,代表 2 人份所需的份量。這個函式應回傳一個新的 vector,內含所需份數對應的份量。
不過,你希望保留原本的食譜。
這代表在這個函式中,quantities引數不應該被修改。
std::vector<double> quantities{1.2, 3.6, 10.5};
scaleRecipe(quantities, 4);
// => { 2.4, 7.2, 21 }
你還記得小時候聽過的故事,那些關於你曾姨婆的傳說,她是家族最深邃烹飪祕密的守護者。 傳說她握有解開超乎想像滋味的鑰匙,但她的方法很不尋常。 她不用清單,也不講份量;她輕聲細語,一次只透露一種食材。
帶著既興奮又忐忑的心情,你決定聯絡曾姨婆。 一如預期,她用神祕難解的方式回應,只給出一種祕密食材,裹著層層謎團。
曾姨婆的貢獻增添了一層複雜度,而你想把它用在食譜裡。
請實作另一個版本的addSecretIngredient函式,它接受兩個引數:一個代表你食材清單的 vector 參考,以及一個代表曾姨婆神祕獻禮的字串。
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
std::string secretIngredient{"spice melange"};
addSecretIngredient(myList, secretIngredient);
// myList => {"noodles", "meat", "sauce", "mozzarella", "spice melange"}
祝你好運,勇敢的廚師!