首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C语言 函数

C语言 函数

作者头像
用户11872857
发布2025-12-17 15:37:23
发布2025-12-17 15:37:23
4610
举报

前言

在 C 语言的世界里,函数就像是一个个功能强大的 “小助手”,它们各自承担着特定的任务,共同协作构建出复杂而高效的程序。今天,就让我们一起深入探索 C 语言函数的奥秘,从基础概念到进阶应用,全面掌握这一重要的编程工具。


目录

1. 函数的概念

2. 库函数

2.1 库函数的定义

2.2 库函数的使用

3. 自定义函数

3.1 函数的语法形式

3.2 函数的举例

3.3 形参和实参

3.4 return 语句

4. 数组做函数参数

5. 嵌套调用和链式访问

5.1 嵌套调用:函数间的协作

5.2 链式访问:简洁高效的编程风格

6. 函数的声明和定义

6.1 单个文件

6.3 多个文件

6.3 static 和 extern


1. 函数的概念

函数在 C 语言中可以看作是完成特定任务的一小段代码,它的存在让程序结构更加清晰,也大大提高了代码的复用性。在数学里,我们熟悉的一次函数y=kx+b,给定x就能得到对应的y值。C 语言中的函数与之类似,输入数据(参数),经过内部处理,输出结果。

C 语言程序通常由众多小函数组合而成,如同一个大型机器由许多小齿轮协同工作。想象一下,一个复杂的游戏程序,其中人物的移动、攻击、场景渲染等功能,都可以由一个个函数来实现。这一个个函数相互配合,最终呈现出精彩的游戏体验。

在C语言中我们一般会见到两种函数

  • 库函数
  • 自定义函数

2. 库函数

2.1 库函数的定义

C 语言本身并不提供库函数,但 C 语言的国际标准 ANSI C 规定了一些常用函数的标准,不同的编译器厂商依据这些标准实现了相应的库函数。我们熟悉的printfscanf等都是库函数,它们为我们提供了输入输出等常见功能,让编程变得更加便捷。

这些库函数根据功能的不同,被分类存放在不同的头文件中。例如,数学相关的库函数在math.h头文件中声明,字符串相关的库函数在string.h头文件中声明。要使用库函数,必须包含对应的头文件,否则可能会出现编译错误。我们可以通过一些官方网站,如https://zh.cppreference.com/w/c/header ,来查看各个头文件中包含的函数和类型信息。

2.2 库函数的使用

学习和查看库函数的工具众多,除了前面提到的官方链接,C library - C++ Reference 也是一个很好的学习资源。以sqrt函数为例,它用于计算平方根,函数原型为double sqrt (double x);,其中sqrt是函数名,x是参数,要求传递一个double类型的值,返回值也是double类型,即计算结果。

这是我搜索出来的sqrt函数的介绍,本身页面是英文的,我用浏览器的翻译功能进行了翻译:

代码语言:javascript
复制
#include <stdio.h>
#include <math.h>

int main() 
{
    double d = 16.0;
    double r = sqrt(d);
    printf("%lf\n", r);
    return 0;
}

在这个例子中,我们定义了一个double类型的变量d,并将其初始化为 16.0,然后调用sqrt函数计算d的平方根,将结果存储在变量r中,最后使用printf函数输出结果。

库函数的文档一般包含函数原型功能介绍参数返回类型说明代码举例代码输出以及相关知识链接等内容。通过阅读文档,我们可以快速了解库函数的使用方法和注意事项。

3. 自定义函数

3.1 函数的语法形式

自定义函数的语法形式与库函数类似,其基本结构如下:

代码语言:javascript
复制
ret_type fun_name(形式参数)
{

}

// ret_type 是函数返回类型
// fun_name 是函数名
// 括号中放的是形式参数
// {}括起来的是函数体
  • ret_type是函数返回类型,用于指定函数计算结果的类型,也可以是void,表示函数不返回任何值;
  • fun_name函数名,应根据函数的功能取一个有意义的名字,方便调用和理解;
  • 括号中的形式参数相当于工厂的 “原材料”,可以有 0 个或多个,需要明确参数的类型和名字;
  • {}括起来的部分是函数体,是完成计算任务的核心代码。
3.2 函数的举例

为了更好地理解自定义函数的编写,我们来实现一个加法函数,用于完成两个整型变量的加法操作:

代码语言:javascript
复制
#include <stdio.h>
int Add(int x, int y)
{
    int z = 0;
    z = x+y;
    return z;
}

int main()
{
    int a = 0;
    int b = 0;
    //输⼊
    scanf("%d %d", &a, &b);
    //调⽤加法函数,完成a和b的相加
    //求和的结果放在r中
    int r = Add(a, b);
    //输出
    printf("%d\n", r);
    return 0;
}
代码语言:javascript
复制
#include <stdio.h>

//Add函数也可以简化为:

int Add(int x, int y) 
{
    return x + y;
}

在这个示例中,我们定义了一个名为Add的函数,它接收两个int类型的参数xy,并返回它们的和。在main函数中,我们通过scanf函数获取用户输入的两个整数,然后调用Add函数计算它们的和,并将结果输出。

3.3 形参和实参

在函数的使用过程中,参数分为实参形参。以我们前面编写的加法函数为例:

代码语言:javascript
复制
#include <stdio.h>

int Add(int x, int y) 
{
    int z = 0;
    z = x + y;
    return z;
}

int main() 
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int r = Add(a, b);
    printf("%d\n", r);
    return 0;
}

在调用Add函数时,传递给函数的参数ab实际参数(实参),它们是真实传递给函数的值。而在定义Add函数时,函数名后的括号中的xy形式参数(形参),在函数未被调用时,它们只是形式上存在,不会占用内存空间。只有在函数被调用时,形参才会为了存放实参传递过来的值而向内存申请空间,这一过程称为形参的实例化。

虽然实参传递给了形参,但它们各自拥有独立的内存空间。通过调试可以观察到,形参和实参的地址是不同的,形参实际上是实参的一份临时拷贝。这意味着在函数内部对形参的修改不会影响到实参的值。

3.4 return 语句

在函数设计中,return语句起着至关重要的作用。它有以下几个注意事项:

  • return后边可以是一个数值,也可以是一个表达式。如果是表达式,则先执行表达式,再返回表达式的结果。例如,return x + y;会先计算x + y的值,然后返回该值。
  • 当函数返回类型为void时,可以直接写return;,表示函数结束,不返回任何值。
  • 如果return返回的值和函数返回类型不一致,系统会自动将返回的值隐式转换为函数的返回类型。不过,为了避免潜在的错误,建议尽量保证返回值类型与函数定义的返回类型一致。
  • return语句执行后,函数就会彻底返回,其后的代码将不再执行。因此,在编写函数时,要确保return语句的位置合理,避免遗漏重要的代码。
  • 如果函数中存在if等分支语句,要保证每种情况下都有return返回,否则会出现编译错误。例如:
代码语言:javascript
复制
int Max(int x, int y) 
{
    if (x > y)
    {
        return x;
    } 
    else 
    {
        return y;
    }
}

4. 数组做函数参数

在实际编程中,经常需要将数组作为参数传递给函数,在函数内部对数组进行操作。比如,我们要实现一个函数将整型数组的内容全部置为 -1,再实现一个函数打印数组的内容。

代码语言:javascript
复制
#include <stdio.h>

void set_arr(int arr[], int sz) 
{
    int i = 0;
    for (i = 0; i < sz; i++) 
    {
        arr[i] = -1;
    }
}

void print_arr(int arr[], int sz) 
{
    int i = 0;
    for (i = 0; i < sz; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() 
{
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int sz = sizeof(arr) / sizeof(arr[0]);
    set_arr(arr, sz);
    print_arr(arr, sz);
    return 0;
}

在这个示例中,set_arr函数和print_arr函数都接收两个参数:一个是数组arr,另一个是数组的元素个数sz。需要注意的是,数组传参时,形参不会创建新的数组,而是与实参指向同一个数组。此外,函数的形式参数要和实参个数匹配,形参如果是一维数组,数组大小可以省略不写;形参如果是二维数组,行可以省略,但列不能省略。

5. 嵌套调用和链式访问

5.1 嵌套调用:函数间的协作

嵌套调用是指函数之间相互调用的情况。就像搭建乐高玩具一样,每个函数都是一个零件,通过它们之间的有效配合,我们可以构建出复杂的程序。

假设我们要计算某年某月有多少天,可以设计两个函数:is_leap_year函数用于判断年份是否为闰年,get_days_of_month函数调用is_leap_year函数确定是否为闰年后,再根据月份计算该月的天数。

代码语言:javascript
复制
int is_leap_year(int y) 
{
    if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) 
    {
        return 1;
    } 
    else 
    {
        return 0;
    }
}

int get_days_of_month(int y, int m) 
{
    int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int day = days[m];
    if (is_leap_year(y) && m == 2) 
    {
        day += 1;
    }
    return day;
}

int main() 
{
    int y = 0;
    int m = 0;
    scanf("%d %d", &y, &m);
    int d = get_days_of_month(y, m);
    printf("%d\n", d);
    return 0;
}

在这个代码中,main函数调用了scanfprintfget_days_of_month函数,get_days_of_month函数又调用了is_leap_year函数。通过函数的嵌套调用,我们实现了复杂的功能。需要注意的是,函数不能嵌套定义,但可以嵌套调用。

5.2 链式访问:简洁高效的编程风格

链式访问是将一个函数的返回值作为另一个函数的参数,就像链条一样将函数串起来。例如,我们可以使用链式访问来简化代码:

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
int main() 
{
    printf("%d\n", strlen("abcdef"));
    return 0;
}

在这个例子中,strlen函数返回字符串的长度,该长度值直接作为printf函数的参数,实现了代码的简洁性。

再看一个有趣的例子:

代码语言:javascript
复制
#include <stdio.h>

int main() 
{
    printf("%d", printf("%d", printf("%d", 43)));
    return 0;
}

要理解这段代码的执行结果,关键是要知道printf函数的返回值是打印在屏幕上的字符的个数。在这个例子中,第三个printf打印 43,返回 2;第二个printf打印 2,返回 1;第一个printf打印 1。所以,屏幕上最终打印 4321。

6. 函数的声明和定义

6.1 单个文件

在单个文件中使用函数时,如果函数的定义在调用之前,通常不会出现问题。例如:

代码语言:javascript
复制
#include <stdio.h>

int is_leap_year(int y) 
{
    if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) 
    {
        return 1;
    } 
    else 
    {
        return 0;
    }
}

int main() 
{
    int y = 0;
    scanf("%d", &y);
    int r = is_leap_year(y);
    if (r == 1) 
    {
        printf("闰年\n");
    } 
    else
    {
        printf("非闰年\n");
    }
    return 0;
}

但如果将函数的定义放在调用之后,在 VS2022 等编译器上编译时会出现警告信息。这是因为编译器从第一行开始扫描源代码,当遇到函数调用时,如果没有找到函数的定义,就会报错。

为了解决这个问题,我们需要在函数调用之前先声明函数。函数声明只需交代清楚函数名、函数的返回类型和函数的参数,参数名可以省略。例如:int is_leap_year(int y);。修改后的代码如下:

代码语言:javascript
复制
#include <stdio.h>

int is_leap_year(int y);

int main() {
    int y = 0;
    scanf("%d", &y);
    int r = is_leap_year(y);
    if (r == 1) {
        printf("闰年\n");
    } else {
        printf("非闰年\n");
    }
    return 0;
}

int is_leap_year(int y) {
    if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) {
        return 1;
    } else {
        return 0;
    }
}

函数的调用一定要满足先声明后使用的原则,而函数的定义也是一种特殊的声明,所以如果函数定义放在调用之前也是可以的。

6.3 多个文件

在企业开发中,代码量通常较大,不会将所有代码都放在一个文件中。一般会根据程序的功能,将代码拆分放在多个文件中。通常,函数的声明、类型的声明放在头文件(.h)中,函数的实现放在源文件(.c)中。

例如,我们有一个加法函数Add,可以将其声明放在add.h文件中,实现放在add.c文件中,在test.c文件中调用该函数:

代码语言:javascript
复制
// add.c
int Add(int x, int y) {
    return x + y;
}

// add.h
int Add(int x, int y);

// test.c
#include <stdio.h>
//包含要调用函数的头文件,自定义的头文件文件名都是用“ ”包含的
#include "add.h"
int main() {
    int a = 10;
    int b = 20;
    int c = Add(a, b);
    printf("%d\n", c);
    return 0;
}
6.3 static 和 extern

staticextern是 C 语言中的关键字,它们用于控制变量和函数的作用域和链接属性

extern 是⽤来声明外部符号的

static 修饰局部变量static修饰局部变量会改变变量的生命周期。局部变量原本存储在内存的栈区,出函数时会销毁;被static修饰后,存储到了静态区,生命周期与程序相同,只有程序结束时才会销毁。但作用域不变,仍然是变量所在的局部范围。例如:

代码语言:javascript
复制
#include <stdio.h>

void test() {
    static int i = 0;
    i++;
    printf("%d ", i);
}

int main() {
    int i = 0;
    for (i = 0; i < 5; i++) {
        test();
    }
    return 0;
}

在这个例子中,test函数中的istatic修饰,每次调用test函数时,i的值会在上一次的基础上累加。

static 修饰全局变量:一个全局变量被static修饰后,只能在本源文件内使用,不能在其他源文件内使用。这是因为全局变量默认具有外部链接属性,而被static修饰后,外部链接属性变为内部链接属性。例如:

代码语言:javascript
复制
// add.c
static int g_val = 2018;

// test.c
#include <stdio.h>
extern int g_val;

int main() {
    printf("%d\n", g_val);
    return 0;
}

static 修饰函数 static 修饰全局变量是⼀模⼀样的,⼀个函数在整个⼯程都可以使⽤, 被static修饰后,只能在本⽂件内部使⽤,其他⽂件⽆法正常的链接使⽤了。 本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部使⽤。 使⽤建议:⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 1. 函数的概念
  • 2. 库函数
    • 2.1 库函数的定义
    • 2.2 库函数的使用
  • 3. 自定义函数
    • 3.1 函数的语法形式
    • 3.2 函数的举例
    • 3.3 形参和实参
    • 3.4 return 语句
  • 4. 数组做函数参数
  • 5. 嵌套调用和链式访问
    • 5.1 嵌套调用:函数间的协作
    • 5.2 链式访问:简洁高效的编程风格
  • 6. 函数的声明和定义
    • 6.1 单个文件
    • 6.3 多个文件
    • 6.3 static 和 extern
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档