C语言基础-四、函数

目录

1、函数的定义、声明、使用

[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、C语言常用库函数

[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、综合练习

[3.1 猜数字游戏(完整代码)](#3.1 猜数字游戏(完整代码))

[3.2 练习扩展](#3.2 练习扩展)

练习1:判断素数

练习2:计算阶乘

练习3:使用指针交换两个数

4、参数传递方式

[4.1 值传递(默认方式)](#4.1 值传递(默认方式))

[4.2 地址传递(指针传递)](#4.2 地址传递(指针传递))

[4.3 两种传递方式对比](#4.3 两种传递方式对比)

[4.4 栈帧示意图](#4.4 栈帧示意图)

5、进阶知识(补充)

[5.1 递归函数](#5.1 递归函数)

[5.2 函数指针](#5.2 函数指针)

[5.3 多文件项目规范](#5.3 多文件项目规范)

6、常见错误与避坑指南

函数:程序中独立的功能模块,用于完成特定任务的可重复调用代码块

核心价值:提高代码复用性、可维护性、可读性


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))
相关推荐
HoneyMoose1 小时前
Jenkins 更新时候提示 Key 错误
java·开发语言
csbysj20201 小时前
XSLT `<template>` 标签详解
开发语言
We་ct2 小时前
LeetCode 114. 二叉树展开为链表:详细解题思路与 TS 实现
前端·数据结构·算法·leetcode·链表·typescript
秦奈2 小时前
Unity学习复习随笔(12):网络开发基础
网络·笔记·学习·unity
2 小时前
2.20进制转化,表达式求值,删除字符
开发语言·c++·算法
cqbzcsq2 小时前
MC Forge 1.20.1 mod开发学习笔记(战利品、标签、配方)
java·笔记·学习·mod·mc
爱编码的小八嘎2 小时前
第3章 Windows运行机理-3.1 内核分析(4)
c语言
郝学胜-神的一滴2 小时前
单例模式:从经典实现到Vibe Coding时代的思考
开发语言·c++·程序人生·单例模式·设计模式·多线程
人道领域2 小时前
SpringBoot多环境配置实战指南
java·开发语言·spring boot·github