Kodewick
第 8 课,共 30 课目录

函数

第 8 课,共 30 课 · 第 2 部分 · 流程和函数 · 约 15 分钟

同一段代码要在好几个地方用,不用抄好几遍:给它起个名字,写成一个函数,要用的时候调用它。main 本身就是一个函数,现在来写你自己的。

main.cpp
1#include <iostream>2 3double with_tax(double price) {4    return price * 1.08;5}6 7int main() {8    std::cout << with_tax(100) << '\n';9    std::cout << with_tax(250) << '\n';10    return 0;11}
输出
108
270

with_tax 拿到一个价格,算出加税以后的价格交回来。main 调用了它两次,每次给的价格不一样。

定义和调用

一个函数的定义有四部分:

部分上面的例子里
返回类型:交回来的值是什么类型double
函数名with_tax
参数:括号里,调用时要给的值double price
函数体:花括号里,调用时要跑的代码return price * 1.08;

什么都不用交回来的函数,返回类型写 void。什么都不用给的函数,括号里空着:

main.cpp
1#include <iostream>2 3void line() {4    std::cout << "----------\n";5}6 7int main() {8    line();9    std::cout << "购物清单\n";10    line();11    return 0;12}
输出
----------
购物清单
----------

调用时,程序跳进函数,从头跑到尾,再回到调用它的地方接着往下跑。点单步看它怎么跳进跳出。调用时括号不能省:只写 line; 什么也不会发生。

参数

参数让同一个函数每次做的事稍有不同。几个参数用逗号隔开,每个都要写类型;调用时按同样的顺序给值:

main.cpp
1#include <iostream>2#include <string>3 4void show_item(std::string name, int count, double price) {5    std::cout << count << " 份" << name << ":" << count * price << " 元\n";6}7 8int main() {9    show_item("牛奶", 2, 6.5);10    show_item("面包", 1, 12);11    return 0;12}
输出
2 份牛奶:13 元
1 份面包:12 元

调用时给的值("牛奶"、2、6.5)叫实参,它们按顺序放进参数 name、count、price 里。个数不对、类型对不上,编译器会报错。

返回值

return 做两件事:把一个值交回给调用的地方,并且马上结束这个函数。交回来的值可以存进变量、拿去算,也可以直接显示:

main.cpp
1#include <iostream>2 3int bigger(int a, int b) {4    if (a > b) {5        return a;6    }7    return b;8}9 10int main() {11    int best = bigger(78, 91);12    std::cout << best << '\n';13    std::cout << bigger(5, 3) + bigger(2, 4) << '\n';14    return 0;15}
输出
91
9

a > b 成立时,return a; 就结束了函数,后面的 return b; 不会跑。

先猜一猜

这段程序显示什么?

1#include <iostream>2 3int twice(int n) {4    return n * 2;5    std::cout << "算好了\n";6}7 8int main() {9    std::cout << twice(twice(3)) << '\n';10    return 0;11}

先声明,后定义

编译器从上往下读代码。在 main 里调用一个写在下面的函数,读到调用的时候它还不认识这个名字:

main.cpp
1#include <iostream>2 3int main() {4    std::cout << square(4) << '\n';5    return 0;6}7 8int square(int n) {9    return n * n;10}
输出
main.cpp: In function 'int main()':
main.cpp:4:18: error: 'square' was not declared in this scope
    4 |     std::cout << square(4) << '\n';
      |                  ^~~~~~

'square' was not declared in this scope:在这里还没声明过 square。要么把函数挪到 main 上面,要么在上面先写一行声明(也叫原型):只有返回类型、名字和参数,以分号结尾,没有函数体:

main.cpp
1#include <iostream>2 3int square(int n);4 5int main() {6    std::cout << square(4) << '\n';7    return 0;8}9 10int square(int n) {11    return n * n;12}
输出
16

声明告诉编译器「有这么个函数,长这样」,函数体可以放到后面再写。函数多的时候,先把声明列在上面,main 紧跟着,读的人一眼就能看到程序是做什么的。

传值:改不到外面

调用函数时,实参的值被复制一份,放进参数里。函数里改参数,改的是这份复制,外面的变量不受影响:

main.cpp
1#include <iostream>2 3void add_bonus(int score) {4    score += 10;5    std::cout << "函数里:" << score << '\n';6}7 8int main() {9    int score = 80;10    add_bonus(score);11    std::cout << "main 里:" << score << '\n';12    return 0;13}
输出
函数里:90
main 里:80

两个变量都叫 score,但它们是两个不同的变量。点单步看看:调用 add_bonus 时,画面上多出一层,那是这次调用自己的地方,里面有它自己的 score。函数里加了 10,变的只是这一层的 score;函数结束,这一层就没了,main 里的 score 一直是 80。

想让函数的结果留下来,就把它 return 回去,再由调用的地方存起来:

main.cpp
1#include <iostream>2 3int add_bonus(int score) {4    return score + 10;5}6 7int main() {8    int score = 80;9    score = add_bonus(score);10    std::cout << score << '\n';11    return 0;12}
输出
90

(第 10 课还有另一个办法:引用。)

返回 std::string

函数可以返回任何类型,包括 std::string。把一段判断装进函数,用的地方就很清楚:

main.cpp
1#include <iostream>2#include <string>3 4std::string grade_of(int score) {5    if (score >= 90) {6        return "优秀";7    } else if (score >= 60) {8        return "及格";9    }10    return "不及格";11}12 13int main() {14    std::cout << "小林:" << grade_of(95) << '\n';15    std::cout << "小周:" << grade_of(72) << '\n';16    std::cout << "小陈:" << grade_of(48) << '\n';17    return 0;18}
输出
小林:优秀
小周:及格
小陈:不及格

忘了 return

返回类型不是 void 的函数,每一条路都要以 return 结束。漏了一条,g++ 会在输出上面给出一条警告(warning:)。警告不会让编译失败,程序照样运行:

main.cpp
1#include <iostream>2 3int sign(int n) {4    if (n > 0) {5        return 1;6    } else if (n < 0) {7        return -1;8    }9}10 11int main() {12    int a = sign(5);13    int b = sign(0);14    std::cout << a << " " << b << '\n';15    return 0;16}
输出
main.cpp: In function 'int sign(int)':
main.cpp:9:1: warning: control reaches end of non-void function [-Wreturn-type]
    9 | }
      | ^
control reached the end of 'sign' without a return value

发生了什么: 停在第 9 行:函数「sign」说好要交回一个 int,可是走到最后也没有 return。

control reaches end of non-void function:函数跑到了结尾,却没有 return。sign(5) 还好好的;sign(0) 两个条件都不成立,函数什么也没交回来,这是未定义行为,程序在这里就出事了,一行也没显示。看到警告别跳过:补上最后的 return 0; 再运行。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

长方形的面积

写一个函数 area,接收长和宽(都是 int),返回面积。main 已经写好了,程序要显示 面积:24。

要显示
面积:24
exercise1.cpp
1#include <iostream>2 3int main() {4    std::cout << "面积:" << area(6, 4) << '\n';5    return 0;6}
输出
点「运行」或按 Ctrl+Enter
练习 2

摄氏转华氏

写一个函数 to_fahrenheit,接收摄氏度(double),返回华氏度(摄氏 × 1.8 + 32)。显示 25 度 = 77 华氏度。

要显示
25 度 = 77 华氏度
exercise2.cpp
1#include <iostream>2 3int main() {4    double c = 25;5 6    return 0;7}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它:没加上的库存

restock 本想给库存加 20,可 main 里的 stock 还是 5。不用引用,改好它,让程序显示 库存:25。

要显示
库存:25
exercise3.cpp
1#include <iostream>2 3void restock(int stock) {4    stock += 20;5}6 7int main() {8    int stock = 5;9    restock(stock);10    std::cout << "库存:" << stock << '\n';11    return 0;12}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 函数 = 返回类型 + 名字 + 参数 + 函数体;不交回值的写 void。调用时写名字和括号,给实参。
  • return 交回一个值,并且马上结束函数。
  • 编译器从上往下读:函数要在调用之前定义,或者先写声明(原型)int square(int n);。
  • 传值:实参被复制进参数,函数里改参数不影响外面的变量;想留下结果,就 return 回去。
  • 不是 void 的函数,每条路都要 return;漏了会有警告 control reaches end of non-void function,别跳过警告。