假设程序要在开头和中间各打印一次欢迎横幅,最直接的写法是抄两遍:
#include <iostream>
int main() {
std::cout << "========================" << std::endl;
std::cout << " 欢迎使用计算器" << std::endl;
std::cout << "========================" << std::endl;
std::cout << "请输入数字:" << std::endl;
// ……中间一堆处理代码……
std::cout << "========================" << std::endl;
std::cout << " 欢迎使用计算器" << std::endl;
std::cout << "========================" << std::endl;
// 同样三行,又抄了一遍
return 0;
}
其实你早就在用函数了——rand()、srand()、time()
都是别人写好、封装成函数的代码,你只管调用,不用管它内部怎么实现。
函数就是把一段代码打包、取一个名字。以后需要这段逻辑时, 直接喊它的名字(调用)就行,不用再抄一遍。
| 部分 | 例子 | 含义 |
|---|---|---|
| 返回类型 | void | 这个函数不返回值(下节课细讲) |
| 函数名 | printBanner | 调用时用这个名字 |
| 参数列表 | () | 圆括号,空的表示不需要参数 |
| 函数体 | { … } | 大括号里是要执行的代码 |
把打印横幅的三行代码包进 printBanner(),需要的地方直接调用它:
#include <iostream>
void printBanner() {
std::cout << "========================" << std::endl;
std::cout << " 欢迎使用计算器" << std::endl;
std::cout << "========================" << std::endl;
}
int main() {
printBanner();
std::cout << "请输入数字:" << std::endl;
// ……中间一堆处理代码……
printBanner();
return 0;
} Python:def + 冒号 + 缩进
def print_banner():
print("=====")
print("欢迎")
C++:返回类型 + 函数名 + 大括号
void printBanner() {
std::cout << "=====";
}
printBanner(); 才是调用;只写 printBanner(不带括号)不会执行函数——
这和调用 rand()、time() 是同一套规矩。
printBanner(); 时,需要已经见过这个函数的定义或声明。
现阶段最简单的做法,是把自定义函数完整地写在 main 前面;以后也可以先写
void printBanner(); 这样的函数声明,再把函数定义放到 main 后面。
仿照 print_banner.cpp,把下面的骨架补完整:在 sayHi()
函数体里加一行 std::cout,打印一句你想说的话。
#include <iostream>
void sayHi() {
// 在这里写一行 std::cout,打印一句你想说的话
}
int main() {
sayHi();
sayHi();
return 0;
} 补完后运行,确认那句话被打印了两次——这就是“只写一次,用很多次”。
sayHi() 每次打印的内容都写死了。如果想跟不同的人打招呼,
总不能给“小明”“小红”各写一个函数吧?
参数能解决这个问题:定义函数时,在括号里声明一个“占位”的变量, 调用时把具体的值传进去。
#include <iostream>
#include <string>
void greet(std::string name) {
std::cout << "你好," << name << "!欢迎来到 C++ 的世界。" << std::endl;
}
int main() {
greet("小明");
greet("小红");
return 0;
} std::string name 是函数括号里声明的参数——它就像一个
还没装东西的容器。调用 greet("小明") 时,"小明" 被装进
name,函数体内的 name 这时就等于 "小明"。
再调用 greet("小红"),name 又变成了 "小红"。
把 greet.cpp 里的两次调用,换成向你自己的名字和同桌的名字打招呼,运行看看效果。
参数不止一个时,括号里用逗号隔开;调用时传的值,也要按同样的顺序 一一对应上:
#include <iostream>
#include <string>
void printInfo(std::string name, int age) {
std::cout << name << " 今年 " << age << " 岁。" << std::endl;
}
int main() {
printInfo("小明", 12);
printInfo("小红", 13);
// printInfo(12, "小明"); // 顺序反了,类型对不上,编译会报错
return 0;
}
调用 printInfo("小明", 12) 时,第一个实参 "小明" 对应第一个形参 name,
第二个实参 12 对应第二个形参 age——完全按位置一一对应,和形参、实参叫什么名字没关系。
printInfo(std::string name, int age) 规定了第一个参数是
std::string、第二个是 int。调用时如果写成
printInfo(12, "小明"),类型对不上,编译器直接报错,根本运行不起来。
| 概念 | 出现在哪 | 对应 print_info.cpp 里的例子 |
|---|---|---|
| 形参 | 定义函数时,括号里的变量 | name、age |
| 实参 | 调用函数时,传进去的具体值 | "小明"、12 |
目前这些参数采用的是按值传递。调用 printInfo("小明", 12)
那一刻,实参的值被复制给对应的形参,函数体内接下来用的都是这份副本。
普通的赋值 = 就是把值复制一份给另一个变量,两者从此互不影响:
int a = 5;
int b = a; // b 复制了一份 a 的值
b = 10; // 只改了 b,a 不受影响
std::cout << a << " " << b << std::endl; // 输出:5 10 对于本节课使用的按值传递,发生的事情和这个很相似——只不过“复制”发生在 调用函数的那一刻。以后还会接触不复制整个值的其他传递方式。
#include <iostream>
void tryDouble(int x) {
x = x * 2;
std::cout << "函数里 x = " << x << std::endl;
}
int main() {
int num = 5;
tryDouble(num);
std::cout << "函数外 num = " << num << std::endl;
return 0;
} x,只是改了这份拷贝,
main 里的 num 原封不动。
把 try_double.cpp 跑起来,运行前先猜两行输出是否一样,再运行验证。
x 和函数外的 num,最终打印出来是同一个值吗?x = x * 2; 挪到 main 里对 num 直接执行一次,对比两种写法的效果。
写一个无参数、无返回值的函数 printDivider(),打印一行由 -
组成的分割线。在 main 里调用三次,把不同内容隔开。
写一个函数 printParity(int n),直接在函数里用 cout 打印
n 是“偶数”还是“奇数”。在 main 里对好几个不同的数分别调用它。
提示:判断逻辑就是模块二的 if / else,只是这次包进了函数里。
写一个函数 printMax(int a, int b),比较两个整数,打印较大的那个。
在 main 里用几组不同的数字测试。
提示:函数体内部用模块二的 if / else 比较 a 和
b;这是一个两个参数的函数,注意参数声明和调用时顺序要对应。
写一个函数 printRectangle(int width, int height),打印一个由
* 组成的、width 列、height 行的实心矩形。
提示:回忆模块三的循环——外层循环控制行数,内层循环控制每行打印几个 *,
两层循环嵌套着写。
| 能力 | 用到的工具 |
|---|---|
| 把代码打包、取名字 | void 函数名() { } |
| 让函数按不同数据办事 | 参数(parameter) |
| 让函数同时按多个数据办事 | 多个参数,用逗号隔开 |
| 区分“定义时”与“调用时” | 形参 / 实参 |
| 知道改参数不会影响外部变量 | 传值(拷贝) |
cout 打印结果。
如果想让函数算出一个值,再拿回 main 里接着用,
就需要 return。下节课学返回值。