B 班 第 6 课:函数

适用对象: 完成第 5 课(字符串与字符处理)的同学 使用方式: 自学讲义,Dev-C++ 5.11。边读边敲,做完练习再进入下一课 学完本课,你应该能:

  1. 把 Python 的 def 翻译成 C++ 的函数,说清楚两边的四处差别
  2. 分清有返回值的函数和 void 函数,会用 return 提前结束
  3. 解释"参数是复印件",并说明为什么数组参数是个例外
  4. 分清局部变量和全局变量

这一课的好消息: 函数是你在 Python 里用了四个学期的东西,思路完全不用重学,只是写法变了。C++ 这边要多写几个类型,仅此而已。


0. 热身:字符串回顾(10 分钟)

先把上节课的手感捡回来。这个程序读入一整行,数出里面有多少个英文字母:

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    getline(cin, s);
    int cnt = 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s[i];
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) cnt++;
    }
    cout << cnt << endl;
    return 0;
}

输入 Hello, World!,输出:10

getline 读整行、s.length() 取长度、字符按 ASCII 比大小——这三样今天还会一直用。


1. 函数:Python 的 def 换个写法(20 分钟)

Python 你这样写:

def add(a, b):
    return a + b

print(add(3, 5))

C++ 改成这样:

#include <bits/stdc++.h>
using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    cout << add(3, 5) << endl;
    return 0;
}

输出:8

把两段对照着看,差别只有四处:

Python C++
① 开头的关键字 def 没有 def,直接写返回值类型
② 返回值类型 不用写 必须写在函数名前面int add
③ 参数类型 不用写 每个参数都要单独写类型int a, int b,不能写成 int a, b
④ 函数体范围 靠缩进 { }

读一个函数头,从左往右念就懂了:

int      add     (int a, int b)
 ↑        ↑            ↑
返回一个  函数名叫    要两个整数参数,
整数      add        分别叫 a 和 b

⚠️ 参数类型不能省着写。 int add(int a, b) 是错的,必须 int add(int a, int b)——每个参数都得有自己的类型。

C++ 的新规矩:函数必须"先定义,后使用"

Python 里函数定义写在文件哪个位置都无所谓,C++ 不行:

#include <bits/stdc++.h>
using namespace std;

int add(int a, int b) {        // ← 必须写在 main 前面
    return a + b;
}

int main() {
    cout << add(3, 5) << endl;
    return 0;
}

如果把 add 挪到 main 后面,编译直接失败:

error: 'add' was not declared in this scope

意思是"到这一行为止,编译器还没听说过 add 这个名字"。编译器是从上往下逐行读代码的,用到一个名字的时候它必须已经见过。

📝 本课的写法约定:所有自己写的函数一律放在 main 上面。 C++ 其实还有一种"先在上面声明、把定义放到下面"的写法(叫函数原型),以后学——现在用不着。

一个更有用的例子

#include <bits/stdc++.h>
using namespace std;

bool isEven(int n) {
    return n % 2 == 0;
}

int main() {
    cout << isEven(10) << " " << isEven(7) << endl;
    if (isEven(10)) cout << "10 是偶数" << endl;
    return 0;
}

输出:

1 0
10 是偶数

📝 返回 bool 的函数直接拿去当 if 的条件用,读起来就像英语:if (isEven(10)) = "如果 10 是偶数"。这是函数最大的价值——给一段逻辑起个名字,代码就变得能读了。

⚠️ 注意 cout 输出 bool 时打印的是 10,不是 true / false——这一点和 Python 打印 True / False 不一样。

动手检查点 1(5 分钟)

新建 check1.cpp:写一个 int square(int x) 返回 x 的平方,在 main 里输出 square(7)。(应该输出 49。)


2. void:没有返回值的函数(15 分钟)

Python 里有些函数只干活、不返回东西:

def print_line(n):
    for i in range(n):
        print("-", end="")
    print()

C++ 里这种函数的"返回值类型"要写成 void,意思就是"什么都不返回":

#include <bits/stdc++.h>
using namespace std;

void printLine(int n) {
    for (int i = 0; i < n; i++) cout << "-";
    cout << endl;
}

int main() {
    printLine(10);
    cout << "成绩单" << endl;
    printLine(10);
    return 0;
}

输出:

----------
成绩单
----------

⚠️ void 函数不能写 return 什么什么;,也不能拿去赋值——int x = printLine(10); 是错的,因为它根本没有值给你。

void 函数可以写光秃秃的 return;,作用是"活干完了,提前退出":

#include <bits/stdc++.h>
using namespace std;

void checkAge(int age) {
    if (age < 0) {
        cout << "年龄不能是负数" << endl;
        return;                          // 提前结束,下面那行不执行了
    }
    cout << "年龄是 " << age << endl;
}

int main() {
    checkAge(-5);
    checkAge(12);
    return 0;
}

输出:

年龄不能是负数
年龄是 12

📝 这个套路叫"卫语句":把不合法的情况在开头挡掉、直接 return,剩下的代码就不用套一层 else 了。 和 Python 里的用法完全一样。


3. 参数是"复印件":值传递(20 分钟,本课重点)

先看一段会让人意外的代码:

#include <bits/stdc++.h>
using namespace std;

void mySwap(int x, int y) {
    int t = x;
    x = y;
    y = t;
}

int main() {
    int p = 3, q = 5;
    mySwap(p, q);
    cout << p << " " << q << endl;
    return 0;
}

输出:3 5

没换过来! 函数里明明交换了,外面的 pq 纹丝不动。

原因: 调用 mySwap(p, q) 的时候,C++ 把 pq 的值复印了一份交给 xy。函数里改的是复印件,原件当然不受影响。这叫值传递

📝 这一条 Python 完全一样。 在 Python 里写个函数改传进来的数字,外面的变量同样不会变——你早就知道这个规律了,只是没人给它起过名字。

但数组是个例外

#include <bits/stdc++.h>
using namespace std;

void doubleAll(int a[], int n) {
    for (int i = 0; i < n; i++) a[i] = a[i] * 2;
}

int main() {
    int arr[3] = {1, 2, 3};
    doubleAll(arr, 3);
    cout << arr[0] << " " << arr[1] << " " << arr[2] << endl;
    return 0;
}

输出:2 4 6

这回真的改到原件了。 数组作参数时,传过去的不是整个数组的复印件,而是"这排盒子在哪"的信息——函数照着地址找过去,改的就是原来那排盒子。

📝 这一条 Python 也一样! Python 里把 list 传进函数,函数里 a[0] = 999 改的就是原来那个 list。所以这条规律你不用重新记,两边是一致的:

传数字 传一串数据
Python 改不到原件 list 能改到原件
C++ 改不到原件 传数组能改到原件

背后的原理(C++ 这边叫"数组名退化成指针")以后学,现在记住结论就够了。

⚠️ 写数组参数的时候,方括号里不用写数字: void doubleAll(int a[], int n),不是 int a[3]。因为函数不知道也不关心你传多长,所以长度要另外用一个参数 n 告诉它——这是 C++ 数组参数的固定套路。

动手检查点 2(5 分钟)

新建 check2.cpp:写一个 void clear(int a[], int n) 把数组前 n 个元素全改成 0,在 main 里用 int arr[3] = {7, 8, 9}; 试一下,输出应该是 0 0 0


4. 局部变量与全局变量(15 分钟)

在函数里面(大括号里)声明的变量叫局部变量,出了这个大括号就不存在了。

#include <bits/stdc++.h>
using namespace std;

int g = 100;                 // 全局变量:写在所有函数外面

void f() {
    int x = 5;               // 局部变量:只在 f 里面有效
    g = g + 1;               // 全局变量谁都能改
    cout << x << " " << g << endl;
}

int main() {
    f();
    f();
    cout << g << endl;
    return 0;
}

输出:

5 101
5 102
102

看明白了吗——x 每次进 f 都是崭新的 5(上一次的早没了),而 g 是同一个,改了就一直是改过的样子。

⚠️ 在 main 里写 cout << x; 会直接编译报错:'x' was not declared in this scope——xf 的私有财产。

同名的时候,局部的挡住全局的:

#include <bits/stdc++.h>
using namespace std;

int n = 100;

void show() {
    int n = 5;               // 和全局的 n 同名
    cout << n << endl;       // 用的是局部这个
}

int main() {
    show();
    cout << n << endl;       // 用的是全局那个
    return 0;
}

输出:

5
100

📝 实用建议: 需要多个函数共用的数据(比如题目给的那个大数组)就开成全局变量,写在所有函数上面;只在一个函数里用的临时变量就老老实实开成局部的。全局变量还有个好处:自动初始化成 0,局部变量不会(不赋初值就是垃圾值)。

⚠️ 但别把什么都开成全局。 全局变量谁都能改,程序一长就说不清是谁改坏的——这一点和 Python 里少用 global 是同一个道理。


5. 读错误:3 个函数专属的坑(10 分钟)

错误 1:函数定义写在了 main 后面

int main() {
    cout << add(3, 5) << endl;
    return 0;
}
int add(int a, int b) { return a + b; }

报错:

error: 'add' was not declared in this scope

📝 看见 was not declared in this scope,就问自己两件事:名字拼错了吗?定义是不是放到使用的后面去了?

错误 2:说好返回 int,却没写 return

int add(int a, int b) {
    int c = a + b;             ← 算完了,但没 return 出去
}

编译能过,只给一句警告:

warning: no return statement in function returning non-void [-Wreturn-type]

程序照跑,但 add(3, 5) 返回的是个随机数,每次运行还可能不一样。

⚠️ 这是本课最阴险的错误:warning 不是 error,Dev-C++ 照样给你生成 exe。 养成习惯——编译完看一眼下面的信息框,有黄色 warning 也要读,别只看有没有红色 error。

错误 3:实参类型不匹配,悄悄被截断

int half(int x) { return x / 2; }
...
cout << half(7.8) << endl;      ← 传了个 double 进去

编译通过、连 warning 都没有,输出是 37.8 被砍成 7,再 7 / 2 整除得 3。

📝 C++ 会自动做类型转换,而且是静悄悄的。 函数的形参写的是什么类型,传进去的东西就会被硬掰成什么类型。想要小数就把形参写成 double


6. 本课练习(8 题,35 分钟起步,做不完当课后作业)

⭐ 基础题 1:两数之和(文件名 t1.cpp

写一个函数 int add(int a, int b) 返回两数之和,主程序读入两个整数并输出结果。

输入样例:
3 5

输出样例:
8
参考答案
#include <bits/stdc++.h>
using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    int x, y;
    cin >> x >> y;
    cout << add(x, y) << endl;
    return 0;
}

main 里的 xy 和函数里的 ab 名字不用一样——ab 只是函数内部对"收到的那两个数"的称呼。


⭐ 基础题 2:打印星星(文件名 t2.cpp

写一个 void printStars(int n) 打印 n 个星星并换行,主程序读入 n 后调用它。

输入样例:
5

输出样例:
*****
参考答案
#include <bits/stdc++.h>
using namespace std;

void printStars(int n) {
    for (int i = 0; i < n; i++) cout << "*";
    cout << endl;
}

int main() {
    int n;
    cin >> n;
    printStars(n);
    return 0;
}

因为它只负责打印、不用算出什么值交回去,所以返回值类型写 void


⭐⭐ 实战题 3:三个数的最大值(文件名 t3.cpp

写一个 int maxOf3(int a, int b, int c) 返回三个数里最大的那个,主程序读入三个整数并输出。

输入样例:
12 7 25

输出样例:
25
参考答案
#include <bits/stdc++.h>
using namespace std;

int maxOf3(int a, int b, int c) {
    int m = a;
    if (b > m) m = b;
    if (c > m) m = c;
    return m;
}

int main() {
    int x, y, z;
    cin >> x >> y >> z;
    cout << maxOf3(x, y, z) << endl;
    return 0;
}

打擂台的写法(L03 学过)搬进函数里,一模一样。C++ 其实自带一个 max(a, b) 函数,三个数可以写成 max(a, max(b, c))——两种都会最好。


⭐⭐ 实战题 4:质数判断(文件名 t4.cpp

写一个 bool isPrime(int n) 判断 n 是不是质数,主程序读入 n,输出 1 到 n 之间质数的个数。

输入样例:
20

输出样例:
8

(2、3、5、7、11、13、17、19 共 8 个。)

参考答案
#include <bits/stdc++.h>
using namespace std;

bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) return false;      // 找到一个因数,直接判定不是
    }
    return true;
}

int main() {
    int n;
    cin >> n;
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (isPrime(i)) cnt++;
    }
    cout << cnt << endl;
    return 0;
}

⚠️ 两个细节:① n < 2 要单独挡掉,0 和 1 都不是质数;② 循环条件写 i * i <= n 而不是 i <= n,只用试到 √n 就够了,快得多。

体会一下有函数的好处:主程序那几行读起来就是"从 1 数到 n,是质数就加一",判断质数的细节被藏进函数里了——这就是函数的意义,把复杂度关在盒子里。


⭐⭐ 实战题 5:阶乘(文件名 t5.cpp

写一个 long long fact(int n) 返回 n 的阶乘,主程序读入 n 并输出。

输入样例:
20

输出样例:
2432902008176640000
参考答案
#include <bits/stdc++.h>
using namespace std;

long long fact(int n) {
    long long res = 1;
    for (int i = 1; i <= n; i++) res = res * i;
    return res;
}

int main() {
    int n;
    cin >> n;
    cout << fact(n) << endl;
    return 0;
}

⚠️ 返回值类型和函数里的 res 都要是 long long。只把返回值写成 long long、里面用 int 算是没用的——int 在算的过程中就已经溢出了。这就是 L03 说的"20! 装不下",现在用函数重写一遍。


⭐⭐ 实战题 6:数组求和(文件名 t6.cpp

写一个 int sumArray(int a[], int n) 返回数组前 n 个元素之和,主程序读入 n 和 n 个整数后调用它。

输入样例:
5
3 1 4 1 5

输出样例:
14
参考答案
#include <bits/stdc++.h>
using namespace std;

int sumArray(int a[], int n) {
    int s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    return s;
}

int main() {
    int n;
    cin >> n;
    int a[1005];
    for (int i = 0; i < n; i++) cin >> a[i];
    cout << sumArray(a, n) << endl;
    return 0;
}

⚠️ 调用的时候写 sumArray(a, n)——数组名后面不加方括号。写成 sumArray(a[], n)sumArray(a[n], n) 都是错的。


⭐⭐⭐ 冲刺题 7:综合大题——用函数重写成绩统计(文件名 t7.cpp

还记得上一课的冲刺题 7 吗?输入 n 个学生的成绩,依次输出:最高分、90 分及以上 的人数、平均分(保留 1 位小数)。

这次要求把它拆成三个函数int maxScore(int a[], int n)int countHigh(int a[], int n)double average(int a[], int n)main 里只负责读入和输出。

输入样例:
5
78 92 85 90 60

输出样例:
92 2 81.0
参考答案
#include <bits/stdc++.h>
using namespace std;

int maxScore(int a[], int n) {
    int m = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > m) m = a[i];
    }
    return m;
}

int countHigh(int a[], int n) {
    int cnt = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] >= 90) cnt++;
    }
    return cnt;
}

double average(int a[], int n) {
    int s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    return (double)s / n;
}

int main() {
    int n;
    cin >> n;
    int a[1005];
    for (int i = 0; i < n; i++) cin >> a[i];
    printf("%d %d %.1f\n", maxScore(a, n), countHigh(a, n), average(a, n));
    return 0;
}

把这份代码和上一课的答案摆在一起看——算出来的东西一模一样,但这一份的 main 只有 6 行,一眼就能看懂它在干什么。 这就是函数的价值:不是让程序变短,是让程序能读

⚠️ average 里必须写 (double)s / n。写成 s / n 的话两个都是 int,做的是整除——本题 405 / 5 = 81 恰好整除,看不出毛病,但换成 4 个人的成绩立刻就错。L02 的对照表里写过:Python 的 7 // 2 才是整除,C++ 的 / 碰上两个整数就是整除


⭐⭐⭐ 冲刺题 8:交换两个数(文件名 t8.cpp,选做)

写一个 void mySwap(int x, int y) 试图交换两个数,主程序读入两个整数,调用后输出它们。

输入样例:
3 5

输出样例:
3 5

没错,输出样例就是没交换的结果。 请你先自己写、自己跑,确认它确实换不过来,再想一想为什么——然后看下面的加餐。

参考答案
#include <bits/stdc++.h>
using namespace std;

void mySwap(int x, int y) {
    int t = x;
    x = y;
    y = t;
}

int main() {
    int p, q;
    cin >> p >> q;
    mySwap(p, q);
    cout << p << " " << q << endl;
    return 0;
}

函数里换的是复印件 xy,原件 pq 根本没动过。函数一结束,xy 就消失了,交换的成果一起消失。


7. 学有余力:让 mySwap 真的能换(加餐,可跳过)

C++ 提供了一个办法,让参数不再是复印件、而是原件本身的另一个名字——在参数类型后面加一个 &

#include <bits/stdc++.h>
using namespace std;

void mySwap(int &x, int &y) {        // 注意这两个 &
    int t = x;
    x = y;
    y = t;
}

int main() {
    int p = 3, q = 5;
    mySwap(p, q);
    cout << p << " " << q << endl;
    return 0;
}

输出:5 3

这回换成功了。 加了 & 之后,x 就是 p 本人的小名,改 x 就是改 p。这叫引用传参

📝 Python 里没有这个东西——Python 想让函数改外面的数字,只能 return 回去自己接住。C++ 多给了一条路。

📝 顺便一说:C++ 自带一个 swap(a, b) 函数,直接用就行,不用自己写。自己写一遍是为了搞懂值传递。

以后学: 函数默认参数、函数重载(同名函数不同参数)、递归(函数调用自己)、指针、vector


本课要点速查

Python ↔︎ C++ 函数对照:

Python C++ 说明
def add(a, b): int add(int a, int b) { 没有 def,返回值类型写最前面
return a + b return a + b; 一样,记得分号
不写返回类型 必须写;不返回东西就写 void
参数不写类型 每个参数单独写类型 int a, int b,不能 int a, b
定义写在哪都行 必须写在 main 前面 否则 was not declared in this scope
传数字改不到原件 int/double 改不到原件 值传递,两边一致
list 能改到原件 传数组能改到原件 两边一致
没有对应写法 int &x 引用传参能改到原件 C++ 独有(加餐)

函数头怎么读:

返回值类型  函数名(参数类型 参数名, 参数类型 参数名)
   int      add  (  int      a  ,   int      b   )

数组参数的固定套路:

定义:void f(int a[], int n)      // 方括号里不写数字,长度另外传
调用:f(a, n)                     // 数组名后面不加方括号

易错点:

症状 原因
was not declared in this scope 函数定义放到 main 后面了,或名字拼错
函数返回值是随机数 声明了 int 却漏写 return(只有 warning,别忽略)
double 进去结果不对 形参是 int,小数被静悄悄截断了
阶乘算出负数 函数里的中间变量还是 int,没跟着改成 long long
mySwap 换不过来 值传递,改的是复印件(要用 &
sumArray(a[], n) 编译错 调用时数组名后面不加方括号

结束前的自我检查

  1. 不看讲义,把 def add(a, b): return a + b 翻译成 C++,四处差别一处不漏
  2. 说清楚:为什么 mySwap 换不过来,而 doubleAll 却能改到原数组
  3. 练习 1~5 全部通过;⭐⭐⭐ 第 7 题能独立拆成三个函数
  4. 把练习 5 的 long long res 改成 int res 跑一遍 n = 20,亲眼看看溢出成什么样
  5. 说出全局变量和局部变量各自的适用场合,以及全局变量的那个"自动清零"福利