C语言笔记归纳5:函数

函数

目录

函数

一、先搞懂:函数到底是什么?(核心概念)

函数的分类(两大核心类别)

二、库函数:C 语言自带的 “现成工具”(直接用)

2.1 库函数的核心常识

2.2 常用库函数分类(新手必备)

2.3 库函数使用示例(sqrt 求平方根)

三、自定义函数:自己打造的 “专属工具”(核心重点)

3.1 自定义函数的语法结构(像搭 “小工厂”)

生活化比喻

3.2 自定义函数示例(加法函数)

3.3 形参和实参的核心区别(高频易错点)

代码验证(形参是临时拷贝)

3.4 return 语句的 5 个核心规则(必记)

四、函数的高级用法(实战必备)

4.1 数组作为函数参数(重点!)

核心规则

示例:数组初始化 + 打印(修正原笔记)

4.2 嵌套调用:函数内部调用函数(模块化核心)

完整示例(修正原笔记笔误)

4.3 链式访问:函数返回值作为另一个函数的参数

示例 1:strlen 返回值作为 printf 参数

示例 2:printf 的返回值(打印字符个数)

4.4 函数设计原则:高内聚低耦合(进阶必备)

反例(低内聚)

正例(高内聚低耦合)

五、函数的声明与定义(多文件开发基础)

5.1 单文件场景(新手入门)

示例(先声明后调用)

5.2 多文件场景(项目实战)

步骤 1:创建LeapYear.h(头文件,存放声明)

步骤 2:创建LeapYear.c(源文件,存放定义)

步骤 3:创建main.c(主文件,调用函数)

多文件开发优势

六、static 和 extern:函数与变量的 “访问控制”

6.1 核心概念:作用域与生命周期

6.2 static 的 3 种用法(重点)

(1)修饰局部变量:改变生命周期,不改变作用域

示例

(2)修饰全局变量:限制作用域为当前源文件

(3)修饰函数:限制作用域为当前源文件

6.3 extern:声明外部符号

示例(访问其他文件的全局变量)

6.4 static 与 extern 对比表

七、新手必避的 8 个坑(红标警告!)

📝 总结


✨ 引言:

如果说变量是 C 语言的 “零件”,数组是 “零件收纳盒”,那函数就是 “专用工具模块”—— 把实现特定功能的代码封装起来,需要时直接调用,不用重复写冗余代码。比如计算加法、判断闰年、打印数组,都能做成 “专属工具”,让程序逻辑更清晰、更易维护。

一、先搞懂:函数到底是什么?(核心概念)

函数是 C 语言中 “完成特定任务的独立代码块”,就像生活中的 “专用工具”:

  • 🔧 比如 “螺丝刀”(函数)的任务是 “拧螺丝”(功能),“计算器”(函数)的任务是 “计算数值”(功能);
  • 📦 C 语言程序由无数个函数组成,main 函数是 “程序入口”(所有程序都必须有且只有一个 main 函数);
  • 🎯 核心价值:代码复用(写一次多次调用)、逻辑拆分(复杂问题拆成小任务)、便于维护(修改函数不影响其他代码)。

函数的分类(两大核心类别)

类别定义特点示例
库函数编译器厂商实现的标准函数直接调用,需包含对应头文件printf(打印)、sqrt(平方根)
自定义函数程序员自己编写的函数按需设计,灵活适配需求Add(加法)、is_leap_year(判断闰年)

二、库函数:C 语言自带的 “现成工具”(直接用)

库函数是 C 语言标准规定的 “现成工具”,编译器厂商已经实现,我们只需 “调用”,不用关心内部实现。

2.1 库函数的核心常识

  • 📚 标准库与头文件:库函数按功能分类,声明在不同头文件中(比如数学函数在math.h,输入输出函数在stdio.h);
  • 📖 查询工具:不确定函数用法时,查这两个网站:
    1. C 语言中文网
    2. cplusplus.com
  • ⚠️ 注意:调用库函数必须包含对应头文件(比如用sqrt要加#include <math.h>)。

2.2 常用库函数分类(新手必备)

  1. IO 函数:输入输出相关(printfscanfgetcharputchar);
  2. 字符串操作函数:字符串处理(strlen求长度、strcpy复制、strcmp比较);
  3. 数学函数:数值计算(sqrt平方根、pow幂运算、abs绝对值);
  4. 内存操作函数:内存管理(malloc申请内存、free释放内存、memcpy拷贝)。

2.3 库函数使用示例(sqrt 求平方根)

#include <stdio.h>
#include <math.h> // 必须包含数学函数头文件

int main()
{
    double num = 100.0;
    double result = sqrt(num); // 调用库函数sqrt,计算100的平方根
    printf("%.2lf\n", result); // 输出:10.00
    return 0;
}

三、自定义函数:自己打造的 “专属工具”(核心重点)

库函数不能满足所有需求,自定义函数是 “按需设计的工具”,核心掌握 “语法结构 + 形参实参 + return 语句”。

3.1 自定义函数的语法结构(像搭 “小工厂”)

返回值类型 函数名(形式参数列表)
{
    函数体; // 实现功能的代码
    return 结果; // 返回计算结果(返回值类型非void时必须有)
}
生活化比喻
  • 返回值类型:“工厂产品的类型”(比如生产 “整数” 就用int,不生产产品就用void);
  • 函数名:“工厂名字”(便于调用,比如Addis_leap_year);
  • 形式参数:“工厂原材料入口”(接收外部传入的数据,可无参数);
  • 函数体:“工厂生产流程”(实现功能的核心代码);
  • return:“产品出口”(把结果返回给调用者)。

3.2 自定义函数示例(加法函数)

#include <stdio.h>

// 加法函数:接收两个int型“原材料”,返回int型“产品”
int Add(int x, int y) // x、y是形式参数(形参)
{
    return x + y; // 生产流程:求和,返回结果
}

int main()
{
    int a = 0, b = 0;
    scanf("%d %d", &a, &b); // 修正原笔记笔误:scnf→scanf
    
    // 调用Add函数:a、b是实际参数(实参),传入Add的x、y
    int sum = Add(a, b);
    printf("sum = %d\n", sum);
    return 0;
}

3.3 形参和实参的核心区别(高频易错点)

特性形式参数(形参)实际参数(实参)
本质函数的 “参数占位符”传递给函数的 “具体数据”
内存分配函数调用时才分配内存(实例化)定义时就分配内存
关系形参是实参的 “临时拷贝”实参的值传递给形参
修改影响修改形参不影响实参实参的修改可能影响传递结果
代码验证(形参是临时拷贝)
#include <stdio.h>

void Swap(int x, int y) // x、y是形参(临时拷贝)
{
    int temp = x;
    x = y;
    y = temp;
    // 这里修改的是x、y,不是实参a、b
}

int main()
{
    int a = 10, b = 20;
    printf("交换前:a=%d, b=%d\n", a, b); // 10, 20
    Swap(a, b);
    printf("交换后:a=%d, b=%d\n", a, b); // 还是10, 20(形参修改不影响实参)
    return 0;
}

✅ 结论:传值调用(普通变量作为实参)时,形参的修改不会影响实参。

3.4 return 语句的 5 个核心规则(必记)

  1. 返回结果:return 后可跟数值或表达式,表达式会先计算再返回;
    int Add(int x, int y)
    {
        return x + y; // 先计算x+y,再返回结果
    }
    
  2. 无返回值:返回值类型为void时,return 后可什么都不写(或省略 return);
    void PrintHello()
    {
        printf("Hello World\n");
        return; // 可省略,函数执行完自动返回
    }
    
  3. 类型转换:返回值类型与函数声明不一致时,会自动隐式转换;
    int Test()
    {
        return 3.14; // 浮点数3.14隐式转为整数3
    }
    int main()
    {
        printf("%d\n", Test()); // 输出:3
        return 0;
    }
    
  4. 强制结束函数:return 执行后,函数立即结束,后续代码不再执行;
    void Test(int n)
    {
        if (n < 0)
            return; // n<0时直接结束函数
        printf("n = %d\n", n);
    }
    
  5. 所有路径必须返回:如果函数有返回值(非 void),所有分支都要包含 return;
    // 错误:n为偶数时无返回值,编译报错
    int IsOdd(int n)
    {
        if (n % 2 == 1)
            return 1;
    }
    // 正确:所有路径都有return
    int IsOdd(int n)
    {
        if (n % 2 == 1)
            return 1; // 奇数返回1
        else
            return 0; // 偶数返回0
    }
    

四、函数的高级用法(实战必备)

4.1 数组作为函数参数(重点!)

数组作为实参时,传递的是数组首元素的地址(不是整个数组),形参本质是指针,因此形参的修改会影响实参数组。

核心规则
  1. 一维数组传参:形参数组大小可省略(写了也无效);
  2. 二维数组传参:行可省略,列不可省略(编译器需知道每行有多少元素);
  3. 必须传数组大小:形参退化为指针,sizeof(形参数组)计算的是指针大小(4/8 字节),需手动传大小。
示例:数组初始化 + 打印(修正原笔记)
#include <stdio.h>

// 数组初始化:将数组所有元素设为-1
void SetArray(int arr[], int size) // 形参arr本质是指针,size是数组大小
{
    for (int i = 0; i < size; i++)
    {
        arr[i] = -1; // 修改形参arr,实参数组也会变(同一内存地址)
    }
}

// 数组打印:打印数组所有元素
void PrintArray(int arr[], int size)
{
    for (int i = 0; i < size; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main()
{
    int arr[10] = {0};
    int size = sizeof(arr) / sizeof(arr[0]); // 计算数组大小(10)
    
    PrintArray(arr, size); // 输出:0 0 0 0 0 0 0 0 0 0
    SetArray(arr, size);   // 初始化数组为-1
    PrintArray(arr, size); // 输出:-1 -1 -1 -1 -1 -1 -1 -1 -1 -1
    return 0;
}

4.2 嵌套调用:函数内部调用函数(模块化核心)

函数可以嵌套调用(但不能嵌套定义),比如 “计算某年某月天数”,可拆分为 “判断闰年” 和 “获取天数” 两个函数。

完整示例(修正原笔记笔误)
#include <stdio.h>
#include <stdbool.h> // _Bool类型头文件

// 函数1:判断是否为闰年(返回true/false)
bool IsLeapYear(int year)
{
    if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
        return true; // 闰年返回true(1)
    else
        return false; // 非闰年返回false(0)
}

// 函数2:获取某年某月的天数(嵌套调用IsLeapYear)
int GetDaysOfMonth(int year, int month)
{
    // 月份天数数组:索引0不用,1~12对应1~12月
    int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int day = days[month];
    
    // 嵌套调用:如果是闰年且是2月,天数+1
    if (IsLeapYear(year) && month == 2)
        day += 1;
    
    return day;
}

int main()
{
    int year = 0, month = 0;
    scanf("%d %d", &year, &month); // 修正原笔记笔误:scaanf→scanf
    
    int days = GetDaysOfMonth(year, month);
    printf("%d年%d月有%d天\n", year, month, days);
    return 0;
}

4.3 链式访问:函数返回值作为另一个函数的参数

函数的返回值可以直接作为另一个函数的实参,形成 “链式调用”。

示例 1:strlen 返回值作为 printf 参数
#include <stdio.h>
#include <string.h>

int main()
{
    // strlen("abcdef")返回6,作为printf的参数
    printf("字符串长度:%d\n", strlen("abcdef")); // 输出:6
    return 0;
}
示例 2:printf 的返回值(打印字符个数)
#include <stdio.h>

int main()
{
    // 内层printf打印43,返回2(字符个数);中层打印2,返回1;外层打印1
    printf("%d\n", printf("%d", printf("%d", 43))); // 输出:4321
    return 0;
}

✅ 解析:printf的返回值是 “成功打印的字符个数”,因此内层printf("%d",43)打印 2 个字符,返回 2;中层打印 1 个字符(2),返回 1;外层打印 1。

4.4 函数设计原则:高内聚低耦合(进阶必备)

  • 高内聚:一个函数只做一件事(比如IsLeapYear只判断闰年,不打印结果);
  • 低耦合:函数不依赖外部代码(比如不直接在函数内打印,而是返回结果让调用者处理)。
反例(低内聚)
#include <stdbool.h>
#include <stdio.h>

// 错误:既判断闰年,又打印结果,耦合度高
bool IsLeapYear(int year)
{
    if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
    {
        printf("%d是闰年\n", year);
        return true;
    }
    else
    {
        printf("%d不是闰年\n", year);
        return false;
    }
}
正例(高内聚低耦合)
#include <stdbool.h>
#include <stdio.h>

// 正确:只判断闰年,返回结果,不打印(高内聚)
bool IsLeapYear(int year)
{
    return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}

int main()
{
    int year = 0;
    scanf("%d", &year);
    bool ret = IsLeapYear(year);
    
    // 打印逻辑由调用者处理(低耦合)
    if (ret)
        printf("%d是闰年\n", year);
    else
        printf("%d不是闰年\n", year);
    return 0;
}

五、函数的声明与定义(多文件开发基础)

函数的 “声明” 是告诉编译器 “函数存在”,“定义” 是实现函数的具体功能。

5.1 单文件场景(新手入门)

如果函数定义在调用之后,必须先声明函数(否则编译器报错)。

示例(先声明后调用)
#include <stdio.h>

// 函数声明:告诉编译器函数名、参数类型、返回值类型
int IsLeapYear(int year);

int main()
{
    int year = 2024;
    bool ret = IsLeapYear(year); // 调用前已声明,编译器不报错
    printf("%d\n", ret);
    return 0;
}

// 函数定义:实现具体功能(定义也是一种声明)
int IsLeapYear(int year)
{
    return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}

5.2 多文件场景(项目实战)

大型项目中,函数声明放在.h头文件,定义放在.c源文件,便于管理和多人协作。

步骤 1:创建LeapYear.h(头文件,存放声明)
// 函数声明
bool IsLeapYear(int year);
步骤 2:创建LeapYear.c(源文件,存放定义)
#include "LeapYear.h" // 包含头文件

// 函数定义
bool IsLeapYear(int year)
{
    return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}
步骤 3:创建main.c(主文件,调用函数)
#include <stdio.h>
#include "LeapYear.h" // 包含自定义头文件(用双引号)

int main()
{
    int year = 2024;
    bool ret = IsLeapYear(year);
    printf("%d是%s闰年\n", year, ret ? "" : "不");
    return 0;
}
多文件开发优势
  1. 逻辑清晰:声明和实现分离,便于查找和修改;
  2. 多人协作:不同开发者负责不同.c文件,互不干扰;
  3. 代码隐藏:可将.c文件编译为静态库(.lib),只对外提供头文件,保护源代码。

六、static 和 extern:函数与变量的 “访问控制”

6.1 核心概念:作用域与生命周期

  • 作用域:变量 / 函数能被访问的范围(比如局部变量的作用域是代码块内);
  • 生命周期:变量从内存分配到释放的时间段(比如局部变量的生命周期是进入到退出代码块)。

6.2 static 的 3 种用法(重点)

(1)修饰局部变量:改变生命周期,不改变作用域
  • 普通局部变量:存储在栈区,生命周期 = 代码块内;
  • static 局部变量:存储在静态区,生命周期 = 整个程序运行期间。
示例
#include <stdio.h>

void Test()
{
    static int n = 0; // static修饰局部变量
    n++;
    printf("%d ", n);
}

int main()
{
    for (int i = 0; i < 5; i++)
    {
        Test(); // 输出:1 2 3 4 5(n不会被销毁,累加)
    }
    return 0;
}
(2)修饰全局变量:限制作用域为当前源文件
  • 普通全局变量:默认有外部链接属性,可通过extern在其他文件访问;
  • static 全局变量:内部链接属性,只能在当前.c文件访问,其他文件无法访问。
(3)修饰函数:限制作用域为当前源文件
  • 普通函数:默认有外部链接属性,可通过extern在其他文件调用;
  • static 函数:内部链接属性,只能在当前.c文件调用,其他文件无法调用。

6.3 extern:声明外部符号

extern用于声明 “定义在其他文件的变量或函数”,告诉编译器 “该符号在其他地方存在,无需报错”。

示例(访问其他文件的全局变量)
// 文件1:global.c
int g_val = 2023; // 普通全局变量(外部链接)

// 文件2:main.c
extern int g_val; // 声明外部变量g_val(定义在global.c)

int main()
{
    printf("%d\n", g_val); // 输出:2023(成功访问其他文件的变量)
    return 0;
}

6.4 static 与 extern 对比表

修饰对象static 作用extern 作用
局部变量生命周期→整个程序,作用域不变无意义(局部变量作用域已限制)
全局变量作用域→当前源文件,生命周期不变声明外部全局变量,扩展访问范围
函数作用域→当前源文件声明外部函数,扩展调用范围

七、新手必避的 8 个坑(红标警告!)

  1. 形参实参混淆:修改形参以为能改变实参(传值调用不行,需传地址);
  2. 数组传参忘传大小:形参退化为指针,sizeof(arr)算的是指针大小;
  3. return 路径不全:非 void 函数的分支未包含 return,编译报错;
  4. 函数嵌套定义:函数内部不能定义函数(只能嵌套调用);
  5. 库函数未包含头文件:比如用sqrt没加#include <math.h>
  6. 多文件未声明外部符号:调用其他文件的函数 / 变量,未用extern声明;
  7. 函数名重复:同一项目中函数名不能重复(static 函数除外);
  8. 二维数组传参省略列:void Test(int arr[][])错误,列不可省(int arr[][3]正确)。

📝 总结

函数是 C 语言模块化编程的核心,掌握 “库函数调用 + 自定义函数设计 + 多文件组织”,就能写出逻辑清晰、可复用的代码。新手重点要吃透:形参实参的区别、return 语句规则、数组传参的特性,再通过实战练习(比如闰年判断、月份天数计算)巩固。

如果这篇博客帮你理清了函数的逻辑,欢迎点赞收藏!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值