目录
[1.1 函数声明(函数原型)](#1.1 函数声明(函数原型))
[1.2 函数定义](#1.2 函数定义)
[1.3 函数调用](#1.3 函数调用)
[1.4 参数说明](#1.4 参数说明)
[1.5 函数调用与栈帧](#1.5 函数调用与栈帧)
[1.6 使用注意事项](#1.6 使用注意事项)
[2.1 头文件导入](#2.1 头文件导入)
[2.2 数学函数(math.h)](#2.2 数学函数(math.h))
[2.3 随机数(stdlib.h + time.h)](#2.3 随机数(stdlib.h + time.h))
[3.1 猜数字游戏(完整代码)](#3.1 猜数字游戏(完整代码))
[3.2 练习扩展](#3.2 练习扩展)
[4.1 值传递(默认方式)](#4.1 值传递(默认方式))
[4.2 地址传递(指针传递)](#4.2 地址传递(指针传递))
[4.3 两种传递方式对比](#4.3 两种传递方式对比)
[4.4 栈帧示意图](#4.4 栈帧示意图)
[5.1 递归函数](#5.1 递归函数)
[5.2 函数指针](#5.2 函数指针)
[5.3 多文件项目规范](#5.3 多文件项目规范)
函数:程序中独立的功能模块,用于完成特定任务的可重复调用代码块
核心价值:提高代码复用性、可维护性、可读性
1、函数的定义、声明、使用
1.1 函数声明(函数原型)
cpp
// 语法格式
返回值类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...);
// 示例
int sum(int num1, int num2);
void printHello(void);
⚠️ 重要说明:
- 声明的作用是告知编译器函数的接口信息,便于类型检查
- 声明不一定非要放在main前面 ,只要在调用之前声明即可
- 声明以分号结尾,不包含函数体
- 多文件项目中,通常将声明放在".h头文件*"中
1.2 函数定义
cpp
// 语法格式
返回值类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...)
{
// 函数体(具体实现)
语句1;
语句2;
...
return 返回值; // 返回值类型非void时必须
}
// 示例
int sum(int num1, int num2)
{
int result = num1 + num2;
return result;
}
1.3 函数调用
cpp
// 有返回值的调用
int result = sum(10, 20);
// 无返回值的调用
printHello();
// 忽略返回值(不推荐)
sum(10, 20);
1.4 参数说明
| 类型 | 说明 | 示例 |
|---|---|---|
| 形参 | 定义函数时声明的参数 | int sum(int a, int b) 中的 a, b |
| 实参 | 调用函数时传入的实际值 | sum(10, 20) 中的 10, 20 |
✅ 规则 :形参与实参必须类型匹配、数量一致、顺序对应
1.5 函数调用与栈帧
cpp
函数调用影响内存栈区,遵循 后进先出(LIFO) 原则
调用流程:
1. 保存当前函数的返回地址
2. 为新函数开辟栈帧(存储局部变量、参数)
3. 执行函数体
4. 返回结果,销毁栈帧
5. 恢复调用者的执行环境
1.6 使用注意事项
| 序号 | 注意事项 | 说明 |
|---|---|---|
| 1 | 函数不调用就不执行 | 定义后必须调用才会运行 |
| 2 | 函数名不能重复 | 同一作用域内函数名必须唯一 |
| 3 | 函数不能嵌套定义 | 函数之间是平级关系,不能在函数内定义函数 |
| 4 | 声明位置灵活 | 可在main前声明,也可在调用前声明 |
| 5 | return后代码无效 | return后的代码永远执行不到 |
| 6 | void函数可省略return | 或写return;表示结束,不能跟数据 |
| 7 | 定义只能有一次 | 多文件项目中,函数实现只能在一个.c文件中 |
| 8 | 声明可以有多次 | 头文件中可多次声明,但需保持一致 |
2、C语言常用库函数
2.1 头文件导入
cpp
#include <stdio.h> // 标准输入输出
#include <math.h> // 数学函数
#include <time.h> // 时间函数
#include <stdlib.h> // 随机数、内存分配
2.2 数学函数(math.h)
| 函数 | 功能 | 示例 |
|---|---|---|
sqrt(x) |
平方根 | sqrt(16) → 4.0 |
pow(x, y) |
x的y次方 | pow(2, 3) → 8.0 |
abs(x) |
整数绝对值 | abs(-5) → 5 |
fabs(x) |
浮点绝对值 | fabs(-3.14) → 3.14 |
2.3 随机数(stdlib.h + time.h)
原理
- 使用线性同余方程 生成伪随机数
- 种子不变,随机数序列固定
正确使用步骤
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
// 1. 设置随机数种子(用时间保证每次不同)
srand(time(NULL));
// 2. 获取随机数
int num = rand(); // 0 ~ RAND_MAX
// 3. 指定范围 [A, B] 的通用公式
// 公式:rand() % (B - A + 1) + A
int range_num = rand() % 100 + 1; // 1 ~ 100
printf("随机数: %d\n", range_num);
return 0;
}
随机数范围公式详解
| 需求 | 公式 | 示例 |
|---|---|---|
| [0, n) | rand() % n |
rand() % 100 → 0~99 |
| [1, n] | rand() % n + 1 |
rand() % 100 + 1 → 1~100 |
| [A, B] | rand() % (B-A+1) + A |
rand() % 50 + 10 → 10~59 |
3、综合练习
3.1 猜数字游戏(完整代码)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 函数声明
int generateRandom(int min, int max);
void guessGame(int target);
int main() {
// 设置随机数种子
srand(time(NULL));
// 生成1-100的随机数
int target = generateRandom(1, 100);
printf("=== 猜数字游戏 ===\n");
printf("我已经想好了一个1-100之间的数字\n");
// 开始游戏
guessGame(target);
return 0;
}
// 生成指定范围的随机数
int generateRandom(int min, int max) {
return rand() % (max - min + 1) + min;
}
// 猜数字逻辑
void guessGame(int target) {
int guess;
int count = 0;
while (1) {
printf("请输入你的猜测: ");
scanf("%d", &guess);
count++;
if (guess > target) {
printf("太大了!\n");
} else if (guess < target) {
printf("太小了!\n");
} else {
printf("恭喜你猜对了!\n");
printf("数字是: %d\n", target);
printf("总共猜了: %d 次\n", count);
break;
}
}
}
3.2 练习扩展
练习1:判断素数
cpp
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
// 判断一个数是否为素数
// 返回值:true-是素数,false-不是素数
bool isPrime(int n) {
// 小于2的数不是素数
if (n < 2) {
return false;
}
// 2是素数
if (n == 2) {
return true;
}
// 偶数不是素数
if (n % 2 == 0) {
return false;
}
// 只需判断到sqrt(n)即可
int limit = (int)sqrt(n);
for (int i = 3; i <= limit; i += 2) {
if (n % i == 0) {
return false;
}
}
return true;
}
int main() {
printf("=== 素数判断 ===\n");
// 测试一些数字
int testNums[] = {1, 2, 3, 4, 5, 17, 20, 23, 100};
int count = sizeof(testNums) / sizeof(testNums[0]);
for (int i = 0; i < count; i++) {
int num = testNums[i];
if (isPrime(num)) {
printf("%d 是素数 ✓\n", num);
} else {
printf("%d 不是素数 ✗\n", num);
}
}
// 输出1-100之间的所有素数
printf("\n1-100之间的素数:\n");
int primeCount = 0;
for (int i = 2; i <= 100; i++) {
if (isPrime(i)) {
printf("%d\t", i);
primeCount++;
if (primeCount % 10 == 0) {
printf("\n");
}
}
}
printf("\n共 %d 个素数\n", primeCount);
return 0;
}
练习2:计算阶乘
cpp
#include <stdio.h>
// 方法1:迭代法计算阶乘
long long factorialIterative(int n) {
if (n < 0) {
printf("错误:负数没有阶乘\n");
return -1;
}
if (n == 0 || n == 1) {
return 1;
}
long long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
// 方法2:递归法计算阶乘
long long factorialRecursive(int n) {
if (n < 0) {
printf("错误:负数没有阶乘\n");
return -1;
}
// 递归终止条件
if (n == 0 || n == 1) {
return 1;
}
// 递归调用
return n * factorialRecursive(n - 1);
}
int main() {
printf("=== 阶乘计算 ===\n\n");
// 测试0-20的阶乘
printf("%-5s %-20s %-20s\n", "n", "迭代法", "递归法");
printf("%-5s %-20s %-20s\n", "---", "-------", "-------");
for (int i = 0; i <= 20; i++) {
long long iter = factorialIterative(i);
long long recur = factorialRecursive(i);
printf("%-5d %-20lld %-20lld\n", i, iter, recur);
}
// 用户输入计算
printf("\n请输入要计算阶乘的数字: ");
int num;
scanf("%d", &num);
long long result = factorialIterative(num);
if (result != -1) {
printf("%d! = %lld\n", num, result);
}
return 0;
}
练习3:使用指针交换两个数
cpp
#include <stdio.h>
// 值传递交换(无法真正交换)
void swapByValue(int a, int b) {
int temp = a;
a = b;
b = temp;
printf(" swapByValue 内部: a=%d, b=%d\n", a, b);
}
// 地址传递交换(真正交换)
void swapByPointer(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
printf(" swapByPointer 内部: *a=%d, *b=%d\n", *a, *b);
}
// 使用指针的数组交换
void swapArray(int *arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
int main() {
printf("=== 交换两个数 ===\n\n");
// 测试1:值传递
int a = 10, b = 20;
printf("【值传递测试】\n");
printf("调用前: a=%d, b=%d\n", a, b);
swapByValue(a, b);
printf("调用后: a=%d, b=%d (未改变)\n\n", a, b);
// 测试2:指针传递
int x = 100, y = 200;
printf("【指针传递测试】\n");
printf("调用前: x=%d, y=%d\n", x, y);
swapByPointer(&x, &y);
printf("调用后: x=%d, y=%d (已改变)\n\n", x, y);
// 测试3:数组元素交换
int arr[] = {1, 2, 3, 4, 5};
int arrLen = sizeof(arr) / sizeof(arr[0]);
printf("【数组交换测试】\n");
printf("交换前: ");
for (int i = 0; i < arrLen; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 反转数组
for (int i = 0; i < arrLen / 2; i++) {
swapArray(arr, i, arrLen - 1 - i);
}
printf("交换后: ");
for (int i = 0; i < arrLen; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
4、参数传递方式
4.1 值传递(默认方式)
cpp
#include <stdio.h>
// 值传递:传递的是值的副本
void swapByValue(int a, int b) {
int temp = a;
a = b;
b = temp;
printf("swapByValue: a=%d, b=%d\n", a, b);
}
int main() {
int a = 10, b = 5;
printf("调用前: a=%d, b=%d\n", a, b);
swapByValue(a, b);
printf("调用后: a=%d, b=%d\n", a, b);
return 0;
}
/* 输出:
调用前: a=10, b=5
swapByValue: a=5, b=10
调用后: a=10, b=5 ← 原值未变!
*/
值传递特点:
- 传递的是值的副本,不是变量本身
- 函数内参数和原始变量存储在不同内存位置
- 对副本的修改不影响原始数据
4.2 地址传递(指针传递)
cpp
#include <stdio.h>
// 地址传递:传递的是变量地址
void swapByPointer(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
printf("swapByPointer: *a=%d, *b=%d\n", *a, *b);
}
int main() {
int a = 10, b = 5;
printf("调用前: a=%d, b=%d\n", a, b);
swapByPointer(&a, &b); // 传递地址
printf("调用后: a=%d, b=%d\n", a, b);
return 0;
}
/* 输出:
调用前: a=10, b=5
swapByPointer: *a=5, *b=10
调用后: a=5, b=10 ← 原值已改变!
*/
4.3 两种传递方式对比
| 特性 | 值传递 | 地址传递 |
|---|---|---|
| 传递内容 | 值的副本 | 变量地址 |
| 内存占用 | 额外拷贝 | 只传地址 |
| 修改原值 | ❌ 不能 | ✅ 能 |
| 安全性 | 高(保护原数据) | 低(可能误改) |
| 适用场景 | 基本数据类型 | 数组、结构体、需修改原值 |
4.4 栈帧示意图
cpp
值传递时的内存变化:
调用前(main栈帧):
┌─────────────┐
│ a = 10 │ ← 0x1000
├─────────────┤
│ b = 5 │ ← 0x1004
└─────────────┘
调用swapByValue(a, b)后:
┌─────────────┐ ← 新栈帧(swap)
│ a = 10 │ ← 0x2000(副本)
├─────────────┤
│ b = 5 │ ← 0x2004(副本)
├─────────────┤
│ temp = 10 │
└─────────────┘
┌─────────────┐ ← main栈帧(不变)
│ a = 10 │ ← 0x1000
├─────────────┤
│ b = 5 │ ← 0x1004
└─────────────┘
5、进阶知识(补充)
5.1 递归函数
cpp
// 计算阶乘的递归实现
long long factorial(int n) {
if (n <= 1) {
return 1; // 递归终止条件
}
return n * factorial(n - 1); // 递归调用
}
5.2 函数指针
cpp
// 函数指针声明
int (*fp)(int, int);
// 赋值
fp = sum;
// 调用
int result = fp(10, 20);
5.3 多文件项目规范
cpp
项目结构:
├── main.c // 主程序
├── functions.h // 函数声明
└── functions.c // 函数定义
functions.h:
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
int sum(int a, int b);
void printHello(void);
#endif
functions.c:
#include "functions.h"
int sum(int a, int b) { return a + b; }
void printHello(void) { printf("Hello!\n"); }
main.c:
#include <stdio.h>
#include "functions.h"
int main() { ... }
6、常见错误与避坑指南
| 错误类型 | 错误示例 | 正确写法 |
|---|---|---|
| 声明缺少分号 | int sum(int a, int b) |
int sum(int a, int b); |
| 定义后忘记调用 | 定义函数但从未调用 | 确保在适当位置调用 |
| 返回值类型不匹配 | int函数返回void |
确保return值类型一致 |
| 形参实参不匹配 | sum(10) 但需要2个参数 |
确保参数数量类型一致 |
| 嵌套定义函数 | 在函数内定义函数 | 函数必须平级定义 |
| 随机数种子未设置 | 直接使用rand() |
先srand(time(NULL)) |