第08章 函数
8.1 函数基础
8.1.1 什么是函数
函数: 完成特定功能的代码块,是程序的基本模块。
优点:
- 代码复用
- 模块化编程
- 提高可读性
- 便于维护
8.1.2 函数的定义
语法:
返回类型 函数名(参数列表)
{
// 函数体
return 返回值;
}
示例:
#include <stdio.h>
// 无参数,无返回值
void sayHello(void)
{
printf("Hello World!\n");
}
// 有参数,有返回值
int add(int a, int b)
{
return a + b;
}
int main(void)
{
sayHello();
int result = add(3, 5);
printf("3 + 5 = %d\n", result);
return 0;
}
8.1.3 函数的声明
函数声明(函数原型): 告诉编译器函数的存在
语法:
返回类型 函数名(参数类型列表);
示例:
#include <stdio.h>
// 函数声明
int add(int a, int b);
void printArray(int arr[], int n);
int main(void)
{
int result = add(3, 5);
printf("result = %d\n", result);
return 0;
}
// 函数定义
int add(int a, int b)
{
return a + b;
}
⚠️ 注意: 函数必须先声明后使用(或在使用前定义)
8.1.4 函数的调用
#include <stdio.h>
int max(int a, int b)
{
return a > b ? a : b;
}
int main(void)
{
// 直接调用
printf("max(10, 20) = %d\n", max(10, 20));
// 赋值调用
int result = max(30, 15);
printf("result = %d\n", result);
// 作为参数
printf("max(max(1,2), max(3,4)) = %d\n",
max(max(1, 2), max(3, 4)));
return 0;
}
8.2 函数参数
8.2.1 值传递
C语言函数参数采用值传递(传值):
#include <stdio.h>
void func(int x)
{
x = 100; // 只修改了局部变量x
printf("func中x = %d\n", x);
}
int main(void)
{
int a = 10;
func(a);
printf("main中a = %d\n", a); // a的值未改变
return 0;
}
输出:
func中x = 100
main中a = 10
8.2.2 指针参数
通过指针可以修改实参:
#include <stdio.h>
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int x = 10, y = 20;
printf("交换前: x=%d, y=%d\n", x, y);
swap(&x, &y);
printf("交换后: x=%d, y=%d\n", x, y);
return 0;
}
输出:
交换前: x=10, y=20
交换后: x=20, y=10
8.2.3 数组参数
一维数组:
#include <stdio.h>
// 方式1
void printArray(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
// 方式2:等价
void printArray2(int *arr, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr) / sizeof(arr[0]);
printArray(arr, n);
return 0;
}
⚠️ 重要:
- 数组作为参数时退化为指针
- 必须同时传递数组大小
二维数组:
#include <stdio.h>
// 列数必须指定
void printMatrix(int m[][4], int rows)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%3d ", m[i][j]);
}
printf("\n");
}
}
int main(void)
{
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printMatrix(matrix, 3);
return 0;
}
8.2.4 字符串参数
#include <stdio.h>
#include <string.h>
// 方式1
void printString(char str[])
{
printf("%s\n", str);
}
// 方式2:等价
void printString2(char *str)
{
printf("%s\n", str);
}
// 使用const保护参数
void printConstString(const char *str)
{
printf("%s\n", str);
// str[0] = 'X'; // 错误!不能修改
}
int main(void)
{
char str[] = "Hello";
printString(str);
printConstString("World");
return 0;
}
8.2.5 可变参数
使用<stdarg.h>实现可变参数:
#include <stdio.h>
#include <stdarg.h>
int sum(int count, ...)
{
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++)
{
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main(void)
{
printf("sum(3, 1, 2, 3) = %d\n", sum(3, 1, 2, 3));
printf("sum(5, 1, 2, 3, 4, 5) = %d\n", sum(5, 1, 2, 3, 4, 5));
return 0;
}
8.3 函数返回值
8.3.1 return语句
作用:
- 结束函数执行
- 返回值给调用者
#include <stdio.h>
int max(int a, int b)
{
if (a > b)
{
return a; // 返回a,函数结束
}
return b; // 返回b,函数结束
}
int main(void)
{
printf("max(10, 20) = %d\n", max(10, 20));
return 0;
}
8.3.2 void函数
无返回值的函数:
#include <stdio.h>
void printLine(char ch, int n)
{
for (int i = 0; i < n; i++)
{
printf("%c", ch);
}
printf("\n");
// return; // 可选,用于提前退出
}
int main(void)
{
printLine('=', 40);
printLine('-', 40);
return 0;
}
8.3.3 返回指针
#include <stdio.h>
#include <string.h>
// 返回字符串中第一个字符c的位置
char* findChar(char *str, char c)
{
while (*str != '\0')
{
if (*str == c)
{
return str;
}
str++;
}
return NULL; // 未找到
}
int main(void)
{
char str[] = "Hello World";
char *p = findChar(str, 'W');
if (p != NULL)
{
printf("找到字符'W': %s\n", p);
}
return 0;
}
⚠️ 警告: 不要返回局部变量的地址!
int* func()
{
int a = 10;
return &a; // 错误!a的生命周期结束
}
8.3.4 返回结构体
#include <stdio.h>
typedef struct
{
int x;
int y;
} Point;
Point createPoint(int x, int y)
{
Point p = {x, y};
return p; // 返回结构体副本
}
int main(void)
{
Point p = createPoint(10, 20);
printf("Point: (%d, %d)\n", p.x, p.y);
return 0;
}
8.4 函数的递归
8.4.1 什么是递归
递归: 函数直接或间接调用自己。
要素:
- 递归出口(基准情况)
- 递归体(递归关系)
8.4.2 递归示例
示例1: 阶乘
#include <stdio.h>
int factorial(int n)
{
// 递归出口
if (n == 0 || n == 1)
{
return 1;
}
// 递归体
return n * factorial(n - 1);
}
int main(void)
{
printf("5! = %d\n", factorial(5)); // 120
printf("10! = %d\n", factorial(10)); // 3628800
return 0;
}
示例2: 斐波那契数列
#include <stdio.h>
int fibonacci(int n)
{
// 递归出口
if (n == 0) return 0;
if (n == 1) return 1;
// 递归体
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main(void)
{
for (int i = 0; i < 10; i++)
{
printf("%d ", fibonacci(i));
}
printf("\n");
return 0;
}
输出: 0 1 1 2 3 5 8 13 21 34
示例3: 汉诺塔
#include <stdio.h>
void hanoi(int n, char from, char to, char aux)
{
if (n == 1)
{
printf("将盘子1从 %c 移到 %c\n", from, to);
return;
}
hanoi(n - 1, from, aux, to);
printf("将盘子%d从 %c 移到 %c\n", n, from, to);
hanoi(n - 1, aux, to, from);
}
int main(void)
{
int n = 3;
printf("汉诺塔(%d个盘子):\n", n);
hanoi(n, 'A', 'C', 'B');
return 0;
}
8.4.3 递归vs迭代
递归的优缺点:
优点:
- 代码简洁
- 易于理解
- 适合解决递归问题
缺点:
- 效率较低
- 可能栈溢出
- 空间复杂度高
迭代实现阶乘:
int factorial_iterative(int n)
{
int result = 1;
for (int i = 2; i <= n; i++)
{
result *= i;
}
return result;
}
8.5 函数指针
8.5.1 函数指针的定义
语法:
返回类型 (*指针名)(参数类型列表);
示例:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main(void)
{
int (*func)(int, int); // 函数指针声明
func = add; // 指向add函数
// func = &add; // 等价写法
int result = func(3, 5); // 通过函数指针调用
printf("result = %d\n", result);
return 0;
}
8.5.2 函数指针作为参数
回调函数: 通过函数指针调用的函数
#include <stdio.h>
void forEach(int arr[], int n, void (*callback)(int))
{
for (int i = 0; i < n; i++)
{
callback(arr[i]);
}
}
void printElement(int x)
{
printf("%d ", x);
}
void printSquare(int x)
{
printf("%d ", x * x);
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr) / sizeof(arr[0]);
printf("元素: ");
forEach(arr, n, printElement);
printf("\n");
printf("平方: ");
forEach(arr, n, printSquare);
printf("\n");
return 0;
}
8.5.3 函数指针数组
#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
int main(void)
{
// 函数指针数组
int (*ops[])(int, int) = {add, sub, mul, divide};
char *symbols[] = {"+", "-", "*", "/"};
int a = 20, b = 10;
for (int i = 0; i < 4; i++)
{
printf("%d %s %d = %d\n", a, symbols[i], b, ops[i](a, b));
}
return 0;
}
输出:
20 + 10 = 30
20 - 10 = 10
20 * 10 = 200
20 / 10 = 2
8.5.4 qsort函数示例
#include <stdio.h>
#include <stdlib.h>
// 比较函数
int compareInt(const void *a, const void *b)
{
return (*(int*)a - *(int*)b);
}
int compareIntDesc(const void *a, const void *b)
{
return (*(int*)b - *(int*)a);
}
void printArray(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原数组: ");
printArray(arr, n);
// 升序排序
qsort(arr, n, sizeof(int), compareInt);
printf("升序: ");
printArray(arr, n);
// 降序排序
qsort(arr, n, sizeof(int), compareIntDesc);
printf("降序: ");
printArray(arr, n);
return 0;
}
8.6 内联函数
8.6.1 inline关键字
内联函数: 建议编译器将函数体直接嵌入调用处,减少函数调用开销。
#include <stdio.h>
inline int square(int x)
{
return x * x;
}
int main(void)
{
int a = 5;
printf("square(%d) = %d\n", a, square(a));
return 0;
}
⚠️ 注意:
- inline只是建议,编译器可能不采纳
- 适用于简单、频繁调用的函数
- 不适用于递归函数
8.6.2 inline vs 宏
| 特性 | inline函数 | 宏 |
|---|---|---|
| 类型检查 | 是 | 否 |
| 调试 | 容易 | 困难 |
| 副作用 | 无 | 可能有 |
| 推荐度 | 高 | 低 |
// 宏的副作用
#define SQUARE(x) ((x) * (x))
int a = 5;
int b = SQUARE(a++); // 展开为: ((a++) * (a++))
// a被递增两次!
// inline函数无此问题
inline int square(int x)
{
return x * x;
}
int c = 5;
int d = square(c++); // c只递增一次
8.7 静态函数
8.7.1 static函数
作用: 限制函数只在当前文件可见
// file1.c
static void helper() // 只在file1.c中可见
{
printf("Helper function\n");
}
void publicFunc()
{
helper(); // 正确
}
// file2.c
void publicFunc(); // 声明
void helper(); // 声明
int main(void)
{
publicFunc(); // 正确
helper(); // 链接错误!找不到helper
return 0;
}
用途:
- 隐藏实现细节
- 避免命名冲突
- 提高代码封装性
8.8 实践练习
练习1: 实现字符串函数
#include <stdio.h>
// 字符串长度
size_t myStrlen(const char *str)
{
const char *p = str;
while (*p != '\0')
{
p++;
}
return p - str;
}
// 字符串拷贝
char* myStrcpy(char *dest, const char *src)
{
char *ret = dest;
while ((*dest++ = *src++) != '\0');
return ret;
}
// 字符串比较
int myStrcmp(const char *s1, const char *s2)
{
while (*s1 && (*s1 == *s2))
{
s1++;
s2++;
}
return *(unsigned char*)s1 - *(unsigned char*)s2;
}
int main(void)
{
char str1[] = "Hello";
char str2[20];
printf("长度: %lu\n", myStrlen(str1));
myStrcpy(str2, str1);
printf("拷贝: %s\n", str2);
printf("比较: %d\n", myStrcmp(str1, str2));
return 0;
}
练习2: 快速排序
#include <stdio.h>
void quickSort(int arr[], int left, int right)
{
if (left >= right) return;
int pivot = arr[left];
int i = left, j = right;
while (i < j)
{
while (i < j && arr[j] >= pivot) j--;
if (i < j) arr[i++] = arr[j];
while (i < j && arr[i] <= pivot) i++;
if (i < j) arr[j--] = arr[i];
}
arr[i] = pivot;
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
void printArray(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前: ");
printArray(arr, n);
quickSort(arr, 0, n - 1);
printf("排序后: ");
printArray(arr, n);
return 0;
}
练习3: 计算器(使用函数指针)
#include <stdio.h>
typedef double (*Operation)(double, double);
double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double divide(double a, double b) { return b != 0 ? a / b : 0; }
double calculate(double a, double b, char op)
{
Operation ops[] = {add, sub, mul, divide};
char symbols[] = {'+', '-', '*', '/'};
for (int i = 0; i < 4; i++)
{
if (op == symbols[i])
{
return ops[i](a, b);
}
}
return 0;
}
int main(void)
{
double a, b;
char op;
printf("请输入表达式(如: 10 + 5): ");
scanf("%lf %c %lf", &a, &op, &b);
double result = calculate(a, b, op);
printf("%.2f %c %.2f = %.2f\n", a, op, b, result);
return 0;
}
8.9 常见错误
错误1: 忘记返回值
int func(int a)
{
if (a > 0)
{
return 1;
}
// 错误!a<=0时没有返回值
}
// 正确
int func(int a)
{
if (a > 0)
{
return 1;
}
return 0;
}
错误2: 返回局部变量地址
int* func()
{
int a = 10;
return &a; // 错误!a的生命周期结束
}
错误3: 数组参数未传大小
void func(int arr[])
{
int n = sizeof(arr) / sizeof(arr[0]); // 错误!arr是指针
// ...
}
// 正确
void func(int arr[], int n)
{
// ...
}
错误4: 递归无出口
int func(int n)
{
return func(n - 1); // 错误!无递归出口,栈溢出
}
// 正确
int func(int n)
{
if (n == 0) return 1; // 递归出口
return func(n - 1);
}
错误5: 函数未声明
int main(void)
{
func(); // 错误!func未声明
return 0;
}
void func()
{
printf("Hello\n");
}
// 正确:先声明
void func();
int main(void)
{
func();
return 0;
}
void func()
{
printf("Hello\n");
}
8.10 小结
本章学习了:
函数的定义、声明和调用 函数参数传递(值传递、指针、数组) 函数返回值 递归函数及经典问题 函数指针与回调函数 内联函数(inline) 静态函数(static)
重点提示:
- C语言采用值传递,通过指针可以修改实参
- 数组作为参数时必须传递大小
- 不要返回局部变量的地址
- 递归要有明确的出口
- 使用const保护不需要修改的参数
- 函数指针用于回调和多态
- inline适用于简单、频繁调用的函数
函数设计原则:
- 单一职责:一个函数只做一件事
- 函数名要有意义
- 参数不宜过多(建议不超过5个)
- 避免过长的函数(建议不超过50行)