第七章:让程序更聪明——函数¶
你好!欢迎来到第七章!在前面的章节,我们写的程序都是顺序执行的,代码都挤在 main 函数里。如果有一段代码需要重复使用,难道要复制粘贴很多遍吗?那太麻烦了!这时候就需要函数——把一段代码打包成一个“积木块”,需要的时候随时调用。这一章我们就来学习如何自己创造函数,让程序更聪明、更简洁!
7.1 函数程序范例¶
先来看一个简单的例子:我们写一个函数,用来计算两个整数的和,然后在 main 里调用它。
#include <iostream>
using namespace std;
// 定义一个函数,名字叫 add,它的功能是返回两个整数的和
int add(int x, int y) {
int result = x + y;
return result; // 把结果返回给调用者
}
int main() {
int a = 10, b = 20;
int sum = add(a, b); // 调用 add 函数,把 a 和 b 传进去,得到返回值
cout << "a + b = " << sum << endl;
int c = 30, d = 40;
cout << "c + d = " << add(c, d) << endl; // 也可以直接输出返回值
return 0;
}
运行结果:
a + b = 30
c + d = 70
这个程序里,我们定义了一个 add 函数,它接受两个整数参数,返回它们的和。在 main 中,我们可以多次调用它,不用重复写加法代码。
7.2 函数的用法¶
7.2.1 函数的概念¶
函数就是一段可以重复使用的代码块,它有自己的名字,你可以通过这个名字来执行它。就像你有一个“做作业”的流程:拿出作业本、写作业、检查、合上本子。你可以把这个流程打包成一个函数叫 doHomework(),每次想写作业时就调用它。
函数的好处:
- 避免重复代码:相同的逻辑只写一次。
- 模块化:把大问题拆成小问题,每个函数解决一个小问题。
- 便于修改和维护:如果需要修改某个功能,只需改函数内部,不用到处找。
7.2.2 语句块与作用域¶
语句块就是用大括号 {} 括起来的一组语句。比如 if 语句的后面、循环的后面,还有函数体都是语句块。
作用域指的是变量在程序中的有效范围。在一个语句块内定义的变量,只能在这个块内部使用,块外面是访问不到的。这叫做局部变量。
#include <iostream>
using namespace std;
int main() {
int a = 10; // 这是 main 函数内的局部变量
if (a > 5) {
int b = 20; // b 只在这个 if 块内有效
cout << a << " " << b << endl; // 可以访问 a 和 b
}
// cout << b; // 错误!b 在这里已经不存在了
return 0;
}
函数也是语句块,函数内部定义的变量只属于这个函数,其他函数不能直接访问。
7.2.3 自定义函数介绍¶
定义一个函数的基本格式:
返回值类型 函数名(参数列表) {
// 函数体:要执行的代码
return 返回值; // 如果返回值类型不是 void,必须返回对应类型的值
}
- 返回值类型:函数执行完后要返回什么类型的数据,比如
int、double、char,如果没有返回值就用void。 - 函数名:自己起名字,要符合变量命名规则,最好能说明功能。
- 参数列表:函数需要的输入数据,可以有多个,用逗号分隔,每个参数要写明类型和名字。也可以没有参数,写成
()或(void)。 - 函数体:具体执行的代码。
- return 语句:把结果返回给调用者。如果返回值类型是
void,可以没有return,或者只写return;表示结束函数。
例子:一个没有参数、没有返回值的函数
#include <iostream>
using namespace std;
// 输出欢迎信息
void sayHello() {
cout << "你好,欢迎学习C++!" << endl;
}
int main() {
sayHello(); // 调用函数
sayHello(); // 可以多次调用
return 0;
}
例子:有参数但没有返回值
#include <iostream>
using namespace std;
// 输出两个数的和,但不返回结果
void printSum(int x, int y) {
cout << x << " + " << y << " = " << x + y << endl;
}
int main() {
printSum(3, 5);
printSum(10, 20);
return 0;
}
7.2.4 函数的返回值¶
return 语句有两个作用:
1. 结束函数的执行,返回到调用它的地方。
2. 把后面的值返回给调用者。
例子:返回较大值的函数
#include <iostream>
using namespace std;
int max(int x, int y) {
if (x > y) {
return x;
} else {
return y;
}
}
int main() {
int a = 15, b = 20;
int m = max(a, b);
cout << "较大的数是:" << m << endl;
return 0;
}
注意:如果函数声明了返回值类型(非 void),那么所有分支都必须有 return,否则编译错误。
7.2.5 函数的形参与实参¶
- 形参(形式参数):定义函数时写的参数,就像占位符,告诉调用者需要传什么类型的数据。比如
int add(int x, int y)中的x和y。 - 实参(实际参数):调用函数时实际传递的值,比如
add(3, 5)中的3和5。
调用时,实参会复制给形参,然后在函数内部使用形参。函数内部对形参的修改不会影响实参(除非传的是指针或引用,但初学者先不管)。
#include <iostream>
using namespace std;
void change(int x) {
x = 100; // 修改形参
cout << "函数内部 x = " << x << endl;
}
int main() {
int a = 10;
change(a);
cout << "main 中 a = " << a << endl; // a 还是 10,没变
return 0;
}
7.2.6 函数的声明¶
在C++中,函数必须先声明或定义,然后才能调用。如果函数的定义写在调用之后,就需要提前声明(也叫函数原型)。
#include <iostream>
using namespace std;
// 函数声明,告诉编译器有这个函数,后面再定义
int max(int x, int y);
int main() {
int a = 5, b = 8;
cout << max(a, b) << endl;
return 0;
}
// 函数定义
int max(int x, int y) {
return (x > y) ? x : y;
}
函数声明只需要写返回值类型、函数名和参数类型,可以省略参数名(但建议保留,便于阅读)。
7.2.7 函数的调用与递归¶
调用很简单,写函数名加括号和实参即可。
递归:函数自己调用自己。就像俄罗斯套娃,一层套一层。递归必须有一个结束条件,否则会无限循环。
例子:用递归计算阶乘(n! = 1×2×…×n)
#include <iostream>
using namespace std;
int factorial(int n) {
if (n == 0 || n == 1) { // 递归结束条件
return 1;
} else {
return n * factorial(n - 1); // 自己调用自己
}
}
int main() {
int n;
cout << "请输入一个整数:";
cin >> n;
cout << n << "! = " << factorial(n) << endl;
return 0;
}
执行过程(比如 n=3):
- factorial(3) 返回 3 * factorial(2)
- factorial(2) 返回 2 * factorial(1)
- factorial(1) 返回 1
- 然后一步步返回:factorial(2) = 21=2, factorial(3)=32=6。
递归虽然有趣,但初学者容易绕晕。刚开始只要理解概念就好。
7.2.8 数字查找之顺序和二分¶
这一节结合函数来实现查找算法。
顺序查找¶
在一个数组中找一个数,从第一个开始一个一个比较,直到找到或找完。
#include <iostream>
using namespace std;
// 顺序查找函数:在数组a中找key,返回下标,找不到返回-1
int sequentialSearch(int a[], int n, int key) {
for (int i = 0; i < n; i++) {
if (a[i] == key) {
return i; // 找到了,返回下标
}
}
return -1; // 没找到
}
int main() {
int arr[] = {34, 67, 12, 89, 45, 23, 56};
int n = sizeof(arr) / sizeof(arr[0]);
int key;
cout << "请输入要查找的数:";
cin >> key;
int pos = sequentialSearch(arr, n, key);
if (pos != -1) {
cout << "找到了,位置是:" << pos << endl;
} else {
cout << "没找到" << endl;
}
return 0;
}
二分查找¶
二分查找要求数组必须是有序的(升序)。它每次都和中间元素比较,如果等于就找到;如果小于中间,就在左半部分找;如果大于,就在右半部分找。这样每次都能排除一半。
#include <iostream>
using namespace std;
// 二分查找函数:在升序数组a中找key,返回下标,找不到返回-1
int binarySearch(int a[], int n, int key) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (a[mid] == key) {
return mid;
} else if (a[mid] < key) {
left = mid + 1; // 去右半部分
} else {
right = mid - 1; // 去左半部分
}
}
return -1;
}
int main() {
int arr[] = {12, 23, 34, 45, 56, 67, 89}; // 必须有序
int n = sizeof(arr) / sizeof(arr[0]);
int key;
cout << "请输入要查找的数:";
cin >> key;
int pos = binarySearch(arr, n, key);
if (pos != -1) {
cout << "找到了,位置是:" << pos << endl;
} else {
cout << "没找到" << endl;
}
return 0;
}
对比:顺序查找适用于任何数组,但慢;二分查找快,但要求数组有序。
7.3 编程实例讲解¶
实例1:判断素数函数¶
写一个函数,判断一个整数是否是素数(只能被1和自身整除的数)。
#include <iostream>
#include <cmath> // 用sqrt函数
using namespace std;
bool isPrime(int n) {
if (n <= 1) return false;
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
int main() {
int num;
cout << "请输入一个整数:";
cin >> num;
if (isPrime(num)) {
cout << num << " 是素数" << endl;
} else {
cout << num << " 不是素数" << endl;
}
return 0;
}
实例2:求最大公约数函数(辗转相除法)¶
#include <iostream>
using namespace std;
int gcd(int a, int b) {
while (b != 0) {
int temp = a % b;
a = b;
b = temp;
}
return a;
}
int main() {
int x, y;
cout << "请输入两个整数:";
cin >> x >> y;
cout << "最大公约数是:" << gcd(x, y) << endl;
return 0;
}
实例3:递归求斐波那契数列第n项¶
斐波那契数列:1, 1, 2, 3, 5, 8, 13, … 从第三项起,每一项等于前两项之和。
#include <iostream>
using namespace std;
int fib(int n) {
if (n == 1 || n == 2) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
int main() {
int n;
cout << "请输入项数:";
cin >> n;
cout << "第" << n << "项是:" << fib(n) << endl;
return 0;
}
注意:递归虽然简单,但效率低,因为重复计算很多。可以用循环改进。
实例4:数组排序函数¶
把冒泡排序封装成函数,可以对任意整数数组排序。
#include <iostream>
using namespace std;
// 冒泡排序函数,升序
void bubbleSort(int a[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (a[j] > a[j + 1]) {
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
// 输出数组函数
void printArray(int a[], int n) {
for (int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
int arr[] = {34, 67, 12, 89, 45};
int n = sizeof(arr) / sizeof(arr[0]);
cout << "排序前:";
printArray(arr, n);
bubbleSort(arr, n);
cout << "排序后:";
printArray(arr, n);
return 0;
}
7.4 第7章编程作业¶
恭喜你学完了函数!现在来挑战几个综合题目,检验一下学习成果。
作业1:计算器函数¶
写四个函数:add、sub、mul、div(除法要考虑除数为0的情况)。然后在 main 中让用户输入两个数和运算符,调用对应的函数计算结果。
作业2:数组统计函数¶
写一个函数,接受一个整数数组和数组长度,返回数组中的最大值、最小值、平均值(可以返回多个值吗?初学者可以用引用参数或返回结构体,但这里可以定义多个函数分别求,或者用全局变量。建议先定义多个函数:int maxArray(int a[], int n),int minArray(...),double avgArray(...))。
作业3:进制转换函数¶
写一个函数,将一个十进制整数转换为二进制字符串(用string返回)。提示:不断除以2取余,最后反转字符串。
作业4:递归求幂¶
用递归实现求 x 的 n 次幂(n为非负整数)。x^n = x * x^(n-1),当n=0时返回1。
作业5:猜数字游戏(函数版)¶
把猜数字游戏的主要逻辑写成函数:比如 void playGame(int maxNumber, int maxTries),在 main 中调用开始游戏。
好了,你已经学会了如何自己创造函数,把程序拆分成一个个小模块,这样代码更清晰、更容易维护。加油!🚀