适用对象: 完成第 5 课(字符串与字符处理)的同学 使用方式: 自学讲义,Dev-C++ 5.11。边读边敲,做完练习再进入下一课 学完本课,你应该能:
def 翻译成 C++
的函数,说清楚两边的四处差别void 函数,会用
return 提前结束这一课的好消息: 函数是你在 Python 里用了四个学期的东西,思路完全不用重学,只是写法变了。C++ 这边要多写几个类型,仅此而已。
先把上节课的手感捡回来。这个程序读入一整行,数出里面有多少个英文字母:
#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 比大小——这三样今天还会一直用。
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)——每个参数都得有自己的类型。
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 时打印的是
1 和 0,不是 true /
false——这一点和 Python 打印 True /
False 不一样。
新建 check1.cpp:写一个 int square(int x)
返回 x 的平方,在 main 里输出
square(7)。(应该输出 49。)
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 里的用法完全一样。
先看一段会让人意外的代码:
#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
没换过来! 函数里明明交换了,外面的 p
和 q 纹丝不动。
原因: 调用 mySwap(p, q) 的时候,C++ 把
p 和 q 的值复印了一份交给
x 和
y。函数里改的是复印件,原件当然不受影响。这叫值传递。
📝 这一条 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++ 数组参数的固定套路。
新建 check2.cpp:写一个
void clear(int a[], int n) 把数组前 n 个元素全改成 0,在
main 里用 int arr[3] = {7, 8, 9};
试一下,输出应该是 0 0 0。
在函数里面(大括号里)声明的变量叫局部变量,出了这个大括号就不存在了。
#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——x
是 f 的私有财产。
同名的时候,局部的挡住全局的:
#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 是同一个道理。
错误 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 都没有,输出是
3:7.8 被砍成 7,再
7 / 2 整除得 3。
📝 C++ 会自动做类型转换,而且是静悄悄的。
函数的形参写的是什么类型,传进去的东西就会被硬掰成什么类型。想要小数就把形参写成
double。
⭐ 基础题 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 里的 x、y 和函数里的
a、b
名字不用一样——a 和 b
只是函数内部对"收到的那两个数"的称呼。
⭐ 基础题 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;
}函数里换的是复印件 x 和 y,原件
p、q 根本没动过。函数一结束,x 和
y 就消失了,交换的成果一起消失。
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) 编译错 |
调用时数组名后面不加方括号 |
def add(a, b): return a + b 翻译成
C++,四处差别一处不漏mySwap 换不过来,而
doubleAll 却能改到原数组long long res 改成 int res
跑一遍 n = 20,亲眼看看溢出成什么样