3.“代码食谱”:函数的定义与调用
“代码食谱”:函数的定义与调用
本章导读 (The Hook)
想象一下,你在家里要举办一场盛大的宴会,菜单上有三道菜都用到了同一种秘制酱料。你会怎么做?
- 糟糕的做法:每次做菜时,都把制作酱料的十几个步骤(买香料、研磨、混合、熬制...)从头到尾重复一遍。
- 聪明的做法:把制作秘制酱料的步骤,单独写在一张“食谱”上。每次需要用的时候,你只需要对厨房助手说:“嘿,去照着这张‘秘制酱料’的食谱做一份过来!”
在编程中,我们也经常遇到类似的情况:一段实现特定功能的代码(比如“打印菜单”、“计算平均分”、“验证用户密码”)需要在程序的不同地方被多次使用。
函数 (Function),就是我们在 C++ 中创建的“代码食谱”。我们可以把一段高内聚的代码逻辑打包成一个函数,并给它起一个有意义的名字。之后,在任何需要执行这段逻辑的地方,我们只需要“调用”一下这个函数名,而无需再重复编写同样的代码。
这不仅极大地减少了代码量,更重要的是,它让我们的程序结构变得清晰、模块化,就像一本由多个独立章节构成的书,而不是一团乱麻。到目前为止,我们一直在使用的 main(),其实就是 C++ 程序中那个最特殊、必须有的主函数!
专业术语速查表 (Glossary)
点击展开/折叠本章术语表
函数 (Function)
- 通俗比喻:一张写好了具体步骤的“代码食谱”,或者是工具箱里的一个专用工具(比如“扳手”)。
- 解释:一个封装了特定功能的、可重复使用的具名代码块。
函数定义 (Function Definition)
- 通俗比喻:编写这张“食谱”的具体内容,包括它叫什么名字、需要什么“原料”、以及详细的制作步骤。
- 解释:提供函数的完整实现,包括函数头和函数体。
函数调用 (Function Call)
- 通俗比喻:“使用”这张食谱。你喊一声食谱的名字,厨房就按照步骤开始工作。
- 解释:通过函数名来执行函数中定义的代码。
返回值 (Return Value)
- 通俗比喻:食谱制作完成后,端出来的成品。比如,“制作酱料”这个食谱,它的成品就是一碗酱料。有些食谱可能没有成品,比如“打扫厨房”,它只是完成一个动作。
- 解释:函数执行完毕后,返回给调用方的一个值。
返回类型 (Return Type)
- 通俗比喻:在食谱的标题上注明“成品类型”,比如“成品:酱料(液体)”或“成品:烤鸡(固体)”。如果一个食谱没有成品,我们注明“无成品”。
- 解释:在函数定义中,指定该函数将返回的数据类型。如果函数不返回任何值,使用关键字
void。
参数 (Parameter)
- 通俗比喻:食谱上列出的“原料清单”。比如,“秘制酱料”食谱可能需要“一份酱油、两瓣大蒜”。
- 解释:在函数定义中声明的变量,用于接收调用时传递过来的数据。
实参 (Argument)
- 通俗比喻:当实际使用食谱时,你递给厨房的具体原料。比如,你递过去一瓶“海天牌”酱油和两瓣“山东”大蒜。
- 解释:在函数调用时,传递给函数的实际值。
void- 通俗比喻:“无”或“空”。用在返回类型上,表示“这个食谱没有成品”;用在参数列表里,表示“这个食谱不需要任何原料”。
- 解释:C++ 的一个关键字,表示“无类型”。
核心概念讲解 (The Core Concept)
一个完整的函数使用过程,分为“定义”和“调用”两步。
1. 定义一个函数 (编写食谱)
- 语法:
返回类型 函数名(参数列表) { // 函数体:实现功能的代码 // 如果有返回值,需要用 return 语句 }
让我们来定义三个不同类型的“食谱”:
类型一:无参数,无返回值 (最简单)
这个食谱不需要原料,也没有成品,它只是完成一个固定的动作。// 定义一个名为 printMenu 的函数 void printMenu() { cout << "\n--- 超级游戏菜单 ---" << endl; cout << "1. 开始新游戏" << endl; cout << "2. 继续游戏" << endl; cout << "3. 退出" << endl; }类型二:有参数,无返回值
这个食谱需要你提供原料,但它没有成品,只是利用原料完成一个动作。// 定义一个名为 greetUser 的函数 // 它需要一个 string 类型的“原料”,我们给这个原料在食谱内部起名叫 name void greetUser(string name) { cout << "你好, " << name << "! 欢迎来到我们的世界。" << endl; }类型三:有参数,有返回值 (最完整)
这个食谱既需要原料,完成后还会给你一个成品。// 定义一个名为 add 的函数 // 它需要两个 int 类型的原料,分别叫 num1 和 num2 // 它的成品是一个 int 类型的值 int add(int num1, int num2) { int sum = num1 + num2; return sum; // 使用 return 关键字,将“成品” sum 返回 }
2. 调用一个函数 (使用食谱)
调用函数非常简单,只需要写 函数名(实参列表); 即可。
#include <iostream>
#include <string>
using namespace std;
// --- 函数定义区 (通常写在 main 函数之前) ---
void printMenu() {
cout << "\n--- 超级游戏菜单 ---" << endl;
cout << "1. 开始新游戏" << endl;
cout << "2. 继续游戏" << endl;
cout << "3. 退出" << endl;
}
void greetUser(string name) {
cout << "你好, " << name << "! 欢迎来到我们的世界。" << endl;
}
int add(int num1, int num2) {
int sum = num1 + num2;
return sum;
}
// --- 程序的主入口 ---
int main() {
// --- 调用函数 ---
// 1. 调用无参数,无返回值的函数
printMenu();
// 2. 调用有参数,无返回值的函数
// "张三" 就是我们传递的“实参”,它会被复制给 greetUser 函数里的“形参” name
greetUser("张三");
// 3. 调用有参数,有返回值的函数
int a = 10;
int b = 20;
// 调用 add 函数,并将 "a" 和 "b" 作为实参传递
// 函数会返回一个计算结果 (30),我们用一个新的变量 result 来“接住”这个成品
int result = add(a, b);
cout << "调用 add 函数的结果是: " << result << endl;
// 我们也可以直接在 cout 中使用函数的返回值
cout << "5 + 8 的结果是: " << add(5, 8) << endl;
printMenu(); // 看,我们可以轻松地重复调用它!
return 0;
}动手试试 (Try It Yourself!)
最大值函数
- 在之前的数组练习中,我们写过一段寻找最大值的逻辑。现在,请把它封装成一个函数。
- 定义一个名为
findMax的函数,它接收两个int类型的参数。 - 函数体内,判断哪个参数更大,并使用
return语句返回那个较大的数。 - 在
main函数中,调用findMax(10, 25)并打印返回的结果。
打印星号矩形
- 定义一个名为
printRectangle的函数,它接收两个int参数:width(宽度) 和height(高度)。 - 在函数内部,使用嵌套的
for循环来打印一个由*组成的、指定宽度和高度的实心矩形。 - 该函数没有返回值 (
void)。 - 在
main函数中,调用printRectangle(8, 3)来测试它。
- 定义一个名为
“打印星号矩形”参考答案 (点击展开)
#include <iostream>
using namespace std;
// 函数定义
// 接收宽度和高度作为“原料”
void printRectangle(int width, int height) {
// 外层循环控制行数 (高度)
for (int i = 0; i < height; i++) {
// 内层循环控制每行打印的星号数量 (宽度)
for (int j = 0; j < width; j++) {
cout << "* ";
}
// 每打印完一行,都需要换行
cout << endl;
}
}
int main() {
cout << "打印一个 8x3 的矩形:" << endl;
printRectangle(8, 3); // 调用函数
cout << "\n打印一个 5x5 的正方形:" << endl;
printRectangle(5, 5); // 再次调用,传入不同的“原料”
return 0;
}“防坑”指南与常见错误 (The Pitfalls)
常见错误:函数定义的位置
在 C++ 中,当你调用一个函数时,编译器必须在调用点之前就已经“认识”了这个函数。
int main() {
sayHello(); // 错误!编译器此时还不知道 sayHello 是什么东西
return 0;
}
void sayHello() { // 定义在 main 之后
cout << "Hello!" << endl;
}解决方案:
- (新手推荐) 始终把你的函数定义,写在
main函数的前面。 - (进阶做法) 在
main函数前只写一个函数声明(就像是“电影预告”),告诉编译器有这么一个函数存在,而把完整的函数定义(“电影正片”)放在main之后。我们将在后续课程中详细讲解。
易错点:实参和形参的匹配
调用函数时,传递的实参在数量和类型上,必须与函数定义中的形参相匹配。
void printInfo(string name, int age) { ... }
// --- 调用时的错误示范 ---
printInfo("张三"); // 错误:缺少了 age 这个参数
printInfo(20, "张三"); // 错误:类型和顺序不匹配
printInfo("张三", 20, "北京"); // 错误:多了一个参数本章小结 (The Summary)
我们掌握了组织代码、实现复用的最核心武器——函数!
- 函数能将一段代码逻辑打包,实现代码复用和模块化。
- 使用函数分为两步:定义(编写食谱)和调用(使用食谱)。
- 函数可以有参数(原料)和返回值(成品)。
void关键字用于表示函数没有返回值或没有参数。return语句用于从函数中返回一个值。- 函数的定义通常放在
main函数之前,以确保在调用前已被编译器知晓。
我们今天学习的函数,在传递“原料”(参数)时,用的是一种最简单、最安全的方式——就像是把原料复印一份给厨房,厨房怎么折腾这份复印件,都不会影响你手里的原件。
但如果我想让厨房直接修改我手里的那份“原件”呢?比如,我递进去一个需要加工的零件,希望函数能直接把它打磨好再还给我。这就需要一种新的参数传递方式了。下一章,我们将深入探讨这个进阶话题——“食谱进阶”:参数传递 (值传递 vs. 引用传递)。
