你已经在前几个概念里用过、也写过 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
声明相当于给编译器的一张便条,说明存在一个具有该名称、返回类型和形参列表的函数。 如果缺少定义,代码就无法工作。 声明是可选的;如果你在函数定义之前就使用它,就需要先有声明。 声明可以解决循环引用这类问题,也可以用来把接口和实现分开。
const限定符有时你需要确保值在初始化之后不能再被修改。
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"
由于函数声明往往在定义之前就被读到,在那里设置默认实参是更好的做法。 如果一个形参有默认声明,那么它右侧的所有形参也都需要有默认声明。 有时,复杂的函数重载可以重构为数量更少、带默认实参的函数,以提高可维护性。
在这个练习中,你将编写更多代码,用来准备和烹饪你最爱菜谱里那道绝妙千层面。
一共有 5 个任务。 第一个与烹饪本身有关,另外 4 个则围绕完美的准备工作展开。
为了让下一次做千层面时能从容享受烹饪的过程,你希望预留出足够的时间。 你已经规划好千层面要放哪些层。 现在你想据此估算准备需要多长时间。
实现一个preparationTime函数,它接受一个层 vector(元素类型为string)和以分钟为单位的每层平均准备时间(int)。
函数应根据层数返回总准备时间的估算值,类型为int。
如果没有传入平均准备时间,则应使用默认值2。
std::vector<std::string> layers{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"};
preparationTime(layers, 3)
// => 18
preparationTime(layers)
// => 12
除了预留时间,你还要确保 sauce 和 noodles 足够,好做出梦想中的千层面。 千层面中每有一层 noodles,就需要 50 克 noodles。 每有一层 sauce,就需要 0.2 升 sauce。
定义quantities函数,它的形参是一个层 vector,元素为string。
函数会算出做这顿饭所需的 noodles 和 sauce 用量。
结果应以amount struct 的形式返回,它已经为你定义好了。
amount有两个成员变量:一个是用int表示的noodles重量,另一个是用double表示的sauce体积。
struct amount { int noodles; double sauce; };
amount needed{quantities({"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})};
// needed.noodles => 100
// needed.sauce => 0.4
不久前你去朋友家做客,在那里吃了千层面。 那味道太棒了,还带着某种特别的东西。 朋友把配料清单发给了你,并告诉你清单上的最后一项正是让这顿饭如此特别的“秘密配料”。 现在你也想把这味秘密配料加进自己的菜谱。
编写一个addSecretIngredient函数,它接受 2 个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"}
你菜谱上列出的用量只够做 2 份千层面。 下次你想给更多人做,所以需要算出不同份数对应的用量。
实现一个scaleRecipe函数,它接受 2 个形参。
double组成的 vector,保存做 2 份所需的用量。函数应返回一个新的 vector,包含目标份数所需的用量。
不过你要保留原始用量。
这意味着在这个函数里不应修改quantities实参。
std::vector<double> quantities{1.2, 3.6, 10.5};
scaleRecipe(quantities, 4);
// => { 2.4, 7.2, 21 }
你还记得童年时听过的故事,讲的是你的曾姑婆,她守护着家族最深的美食秘密。 传说她掌握着开启超乎想象的味道的钥匙,但她的做法很不寻常。 她不用清单,也不讲分量;她只会低声耳语,一次只透露一种食材。
怀着激动又忐忑的心情,你决定去联系这位曾姑婆。 果然,她用一贯的隐晦方式回应,只给出一种秘密配料,还蒙着一层神秘色彩。
曾姑婆的这份馈赠增添了一层复杂性,而你想把它用在菜谱里。
再实现一个addSecretIngredient函数,它接受 2 个实参:一个代表你配料列表的 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"}
祝你好运,勇敢的厨师!