【C语言基础】:函数详解

简介: 【C语言基础】:函数详解

C语言函数

1. 函数的概念

C语言是一种面向过程的编程语言,函数是C语言中的基本概念之一。C语言中的函数是一段被命名的、可重复利用的代码块,用于执行特定的任务或操作。函数使程序模块化,提高了代码的可读性和维护性。它封装了一系列的操作或任务,并可以通过函数名进行调用和执行。


2. 函数的声明与定义

函数的声明指的是在使用函数之前,提前告知编译器函数的名称、参数列表和返回类型。通过函数的声明,编译器可以知道函数的存在,并且能够对函数进行正确的调用。函数的声明通常包含在头文件(.h文件)中,并在需要使用函数的源文件中包含该头文件。

函数的定义则是指实际实现函数的代码部分,包括函数体和相应的逻辑。函数的定义通常包含在源文件(.c文件)中,并在需要使用函数的地方进行调用。

注意:


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

总结:通过函数的声明和定义,我们可以将函数的声明放在需要调用函数的地方之前,使编译器知道函数的存在,并确保函数的正确调用。然后,通过函数的定义来实现具体的功能逻辑。这种分离声明和定义的方式有助于提高代码的可读性、可维护性和重用性。


3. 库函数及其使用

(1). 库函数的概念

库函数是预定义的函数,可以直接在程序中使用,不需要自己编写。它们由标准C库和其他头文件提供,用于完成各种常见任务。


(2). 库函数的分类

库函数主要分为以下几类:


  • 输入输出函数:用于读入和输出数据,例如printf()、scanf()、puts()、gets()等。
  • 数学函数:用于数学计算,例如sin()、cos()、sqrt()、pow()等。
  • 字符串处理函数:用于操作和处理字符串,例如strlen()、strcpy()、strcat()、strcmp()等。
  • 内存管理函数:用于操作和管理内存,例如malloc()、calloc()、realloc()、free()等。
  • 时间和日期函数:用于获取和处理时间和日期信息,例如time()、ctime()、localtime()、strftime()等。
  • 文件操作函数:用于操作文件和目录,例如fopen()、fclose()、fread()、fwrite()等。

除了上述主要类别之外,还有一些其他常用的库函数,例如exit()、rand()、system() 等。这些函数大多数都包含在标准C库中,可以通过 #include 指令来包含相应的头文件,并且在程序中直接调用即可。

注意:库函数虽然方便易用,但是也会增加程序的内存和执行时间。在编写程序时应该合理使用库函数,尽可能减少不必要的库函数调用,以提高程序的效率和性能。


(3). 标准库和头文件

标准库(Standard Library)是指由C语言标准规定的一组函数和宏的集合,它们以头文件的形式提供给开发者使用。C语言标准库提供了许多常用的函数和数据类型,可以帮助开发者更方便地进行编程。

C语言标准库主要分为以下几个部分:


  • stdio.h:提供了输入输出相关的函数,例如printf()、scanf()、fprintf()、fscanf()等。
  • math.h:提供了数学计算相关的函数,例如sin()、cos()、sqrt()、pow()等。
  • string.h:提供了字符串处理相关的函数,例如strlen()、strcpy()、strcat()、strcmp()等。
  • stdlib.h:提供了与内存管理和程序控制相关的函数,例如malloc()、calloc()、free()、exit()等。
  • time.h:提供了时间和日期相关的函数,例如time()、ctime()、localtime()、strftime()等。
  • ctype.h:提供了字符分类和转换相关的函数,例如isalpha()、isdigit()、toupper()、tolower()等。
  • stdbool.h:提供了布尔类型相关的定义,包括bool、true和false。

这些头文件都是标准C库提供的一部分,可以在程序中使用#include指令将它们包含进来,以便使用其中的函数和宏。

更多库函数相关头文件:https://zh.cppreference.com/w/c/header


(4). 库函数的使用方法

使用库函数通常需要包含相应的头文件,并按照函数的参数列表和返回值类型来调用函数。

【示例】计算平方根

double sqrt (double x);
//sqrt 是函数名
//x 是函数的参数,表⽰调⽤sqrt函数需要传递⼀个double类型的值
//double 是返回值类型 - 表⽰函数计算的结果是double类型的值
#include <math.h>  // 包含math.h头文件
#include <stdio.h>  // 包含stdio.h头文件
int main() {
    double x = 16.0;
    double c = sqrt(x);  // 调用sqrt函数计算平方根
    printf("%lf\n", c);  // 调用printf函数进行输出
    return 0;
}

4af0e621ec279a2f91bfe9dbe862f1f7_bd5a3388e2a1467fafd327fe459ee45c.png

库函数的关键是了解所需函数的功能、参数以及返回值,然后按照相应的格式进行调用即可。当然,还需要注意处理函数返回值、错误检查等细节,以确保程序的正确性和健壮性。


4. 自定义函数

在C语言中,你可以自定义函数来实现特定的功能,以便在程序中重复调用。自定义函数通常包括函数声明和函数定义两部分


(1). 函数的定义

函数的语法:

return_type function_name(形式参数) {
    // 函数体
}
  • return_type 是函数返回类型
  • function_name 是函数名
  • 括号中放的是形式参数
  • {}括起来的是函数体
  • return_type:指定函数返回值的数据类型,可以是int(整型)、float(浮点型)、char(字符型)、void(空类型)等。
  • function_name:函数的名称,用于标识函数。
  • parameter list:函数的参数列表,可以包含零个或多个参数,每个参数由参数类型和参数名组成。
  • 函数体:包含在花括号{}中的一组语句,描述了函数要执行的具体操作。
  • 函数的参数就相当于,工厂中送进去的原材料,函数的参数也可以是 void ,明确表示函数没有参
  • 数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
  • {}括起来的部分被称为函数体,函数体就是完成计算的过程。

【示例】自定义一个加法函数,完成整数加法

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

36513a0d68b670a5cb8b94e3db35db50_eb650fef36634742876ff199c5f5da21.png


(2). 形参和实参

在函数使用的过程中,把函数的参数分为形参和实参。

形参指的是函数定义中的参数,它们位于函数名后的括号内。形参是函数定义时用来接收调用该函数时传递的值或引用的变量。


int add(int a, int b) {
    return a + b;
}

在这个例子中,int a 和 int b 分别是函数add的形参,它们用来接收调用add函数时传递的值。


实参指的是函数调用时传递给函数的具体数值或变量,它们位于函数名后的括号内,并用来初始化函数定义中的形参。

#include<stdio.h>
int add(int x, int y) {
    return x + y;
}
int main() {
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int sum = add(a, b);  // 调用自定义函数add,num1和num2是实参
    printf("%d\n", sum);
    return 0;
}

e94c77b35257b4f219fd9b292c2b2f4e_db34ca54ef6d4b58862469b85a9928c3.png

dfa8262fc08538fab93e045d45448762_adef7655336e406ca1af16f542a35521.png


通过调试我们发现,形参x,y得到了a和b的值,但x,y的地址和a,b的地址是不一样的,所以我们可以认为形参是实参的临时拷贝


在这个例子中,a 和 b 是函数add的实参,它们在调用函数时传递给了函数定义中的形参x 和 y。


总之:在函数调用过程中,实参会被传递给函数的形参,从而使得函数能够处理相应的数据。形参和实参的对应关系是通过函数调用时的顺序和类型来建立的,因此在使用函数时需要确保实参的类型和数量与函数定义中的形参匹配。


(3). return语句

在C语言中,return语句用于从函数中返回一个值,并终止函数的执行。return语句通常用于将函数的计算结果或其他需要传递给调用者的值返回给调用者。

return语句的基本语法如下:

return expression;

其中,expression是要返回的值,可以是一个常量、变量、表达式或函数调用等。expression的类型必须与函数的返回类型相匹配。


注意:


  • 返回值类型匹配:return语句返回的值必须与函数的返回类型匹配。如果函数的返回类型是int,那么return语句后面应该是一个整数表达式;如果函数的返回类型是float,那么return语句后面应该是一个浮点数表达式,以此类推。如果返回值类型不匹配,可能会导致编译错误或未定义的行为。
  • return语句的位置:return语句可以出现在函数的任何地方,但是一旦执行到return语句,函数将立即结束,不再执行后续的语句。因此,在编写函数时,需要确保return语句能够被执行到,并且所有可能的执行路径都有返回语句。
  • 仅限于函数内部使用:return语句只能在函数内部使用,用于返回值给调用者。它不能在全局作用域、循环、条件语句等其他代码块中使用。如果在这些地方使用return语句,会导致语法错误。
  • 不带返回值的函数:如果函数的返回类型是void,表示函数没有返回值,那么return语句可以省略或者使用空的return语句return;来结束函数的执行。在这种情况下,return语句主要用于提前终止函数的执行,而不是返回一个具体的值。
  • 返回引用类型:C语言中,不支持直接返回引用类型。如果需要返回引用类型,可以通过指针或结构体等间接返回引用。例如,可以使用指针返回动态分配的内存地址。
  • 多个返回语句:函数中可以有多个return语句,但是只会执行其中一个。一旦遇到return语句,函数将立即返回,并且不会再执行后续的语句。因此,在编写函数时,需要确保所有可能的执行路径都有返回语句,以避免出现未定义的行为。
  • 返回数组:C语言不支持直接返回数组,如果需要返回数组,可以使用指针或结构体等间接返回。

(4). 数组做函数参数

在C语言中,可以将数组作为函数参数传递给函数。这样做可以方便地在函数内部对数组进行操作和修改。

【示例】写一个函数,将整数数组的内容全部改为-1

#include<stdio.h>
void set_arr(int arr[], int sz)
{
  int i = 0;
  for ( i = 0; i < sz; i++)
  {
    arr[i] = -1;
  }
}
void printf_arr(int arr[], int sz)
{
  int i = 0;
  for ( i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
int main()
{
  int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  set_arr(arr, sz);  // 设置数组内容为-1
  printf_arr(arr, sz);  // 打印数组内容
  return 0;
}

18a08946c7b2547bf692495172243e42_c4ae49cf6d1e4d4ca859b0386863e618.png

注意:


  • 函数的形参和实参个数要匹配:在调用函数时,传递给函数的实际参数的个数必须与函数声明或定义中形式参数的个数相同,否则会导致编译错误。
  • 形参可以写成数组形式:函数的形式参数可以是数组类型,这样就可以通过形参来访问和操作数组元素。
  • 一维数组形参的大小可以省略:当函数的形式参数是一维数组时,可以省略数组的大小。这是因为只需要数组的首地址即可访问整个数组,数组的大小可以在函数内部通过其他方式获得,比如通过字符串结束符’\0’确定字符串数组的大小。
  • 二维数组形参的行可以省略,但列不能省略:当函数的形式参数是二维数组时,可以省略数组的行大小,但不能省略列大小。这是因为在二维数组中,每一行的大小必须是相同的,而列的大小可以不同。
  • 数组传参不会创建新的数组:当将数组作为参数传递给函数时,函数内部使用的是原始数组的地址,而不是创建一个新的数组。这意味着对形参数组的修改会直接影响原始数组。
  • 形参操作的数组和实参的数组是同一个数组:当将数组作为参数传递给函数时,形参操作的是原始数组的副本。也就是说,形参数组和实参数组指向的是同一个数组。因此,对形参数组的修改会影响到实参数组。

5. 函数的嵌套调用和链式访问

(1). 函数的嵌套调用

函数的嵌套调用是指一个函数内部调用另一个函数。通过函数的嵌套调用,可以将程序的功能划分为更小的模块,使程序结构更清晰,代码更可读和可维护。

【示例】计算某年某月有多少天

#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 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;
}

e19857851b8ad03b31910e96c83f54ee_35eff5c3bfae49e581fec9961aae509b.png

通过函数的嵌套调用,我们可以将程序的功能分解为更小的任务,并让每个函数专注于完成自己的任务。这样做有助于提高代码的可读性、可维护性和重用性。同时,函数的嵌套调用也可以形成函数调用栈,函数会按照一定的顺序被调用和返回,以实现程序的逻辑流程。


(2). 函数的链式访问

函数的链式访问是指通过连续调用多个函数,将它们的返回值作为下一个函数的参数,以实现一连串的操作。

【示例】

#include<stdio.h>
int main()
{
  printf("%d\n", strlen("abcdef"));  // 函数的链式访问
  return 0;
}

9332cd70f88778aba86038353cf5a95b_c39c1871d4a2448ba326303f6001a457.png

总结:函数的链式访问是通过连续调用多个函数,将它们的返回值作为下一个函数的参数,实现一连串的操作。


6. static 和 extern

static和extern是用来修饰变量和函数的关键字,它们具有不同的作用和含义。


(1). static

1. 修饰全局变量

当static用于修饰全局变量时,它表示该变量的作用域仅限于当前文件,在其他文件中是不可见的。这意味着,如果在一个文件中使用static修饰一个全局变量,那么其他文件无法直接访问该变量。这种用法可以实现信息隐藏和避免命名冲突。

【示例】

// 文件1.c
static int x = 10;
// 文件2.c
extern int x;  // 通过extern声明可以访问文件1.c中的x变量

2. 修饰局部变量

当static用于修饰局部变量时,它表示该变量在程序执行期间一直存在,而不是像普通的局部变量那样只在函数执行时存在。此外,被static修饰的局部变量的生命周期延长到整个程序运行期间,但作用域仍然是限定在定义该变量的函数内部。

【示例】

void function() {
    static int y = 20;
    // ...
}

作用域

变量的作用域指的是变量在程序中的可见范围,也就是变量可以被访问的代码区域。


生命周期

变量的生命周期指的是变量存在的时间段,即从创建到销毁的时间段。


  • 自动变量:在函数内部定义的非静态变量,生命周期和作用域都在函数内部,当函数返回时自动销毁。
  • 静态变量:在函数内部定义的带有static关键字的变量,生命周期和作用域都在函数内部,但是其值会在函数调用结束后保留,下次函数调用时继续使用。
  • 全局变量:在函数外部定义的变量,生命周期和作用域都在整个程序运行期间,直到程序结束才被销毁。
  • 动态内存:通过malloc()和free()等函数分配和释放的内存,生命周期由程序员手动控制。

(2). extern

extern关键字用于声明一个变量或函数,表明该变量或函数是在别处定义的。通常用于引用其他文件中定义的全局变量或函数。通过extern声明,可以使得当前文件能够访问其他文件中定义的变量或函数。

【示例】

// 文件1.c
int global_var = 100;
// 文件2.c
extern int global_var;  // 声明文件1.c中定义的global_var变量

总结:static和extern在C语言中主要用于控制变量和函数的作用域和链接性。static用于限定作用域,使得变量或函数只在当前文件内可见;而extern用于声明在其他文件中定义的变量或函数,使得当前文件能够访问其他文件中定义的内容。

相关文章
|
15天前
|
存储 C语言
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
21 1
|
8天前
|
算法 Unix Linux
C语言随机数的产生(rand、srand、time函数细节讲解)
C语言随机数的产生(rand、srand、time函数细节讲解)
|
12天前
|
存储 API C语言
C语言函数大全--e开头的函数
【6月更文挑战第6天】本篇介绍 C语言中 e开头的函数【C语言函数大全】
30 16
C语言函数大全--e开头的函数
|
7天前
|
存储 API C语言
C语言函数大全--f开头的函数(上)
【6月更文挑战第7天】本篇介绍 C语言中 f 开头的函数(上篇)【C语言函数大全】
23 3
C语言函数大全--f开头的函数(上)
|
14天前
|
存储 Linux Serverless
C语言函数大全--d开头的函数
【6月更文挑战第5天】本篇介绍 C语言中 d开头的函数【C语言函数大全】
20 1
C语言函数大全--d开头的函数
|
2天前
|
算法 Java C语言
Java中的算法与C语言中的函数
Java中的算法与C语言中的函数
8 2
|
7天前
|
安全 C语言
【C语言基础】:内存操作函数
【C语言基础】:内存操作函数
|
7天前
|
编译器 C语言 C++
【C语言基础】:字符函数和字符串函数-2
【C语言基础】:字符函数和字符串函数
|
7天前
|
C语言
【C语言基础】:字符函数和字符串函数-1
【C语言基础】:字符函数和字符串函数
TU^
|
7天前
|
机器学习/深度学习 C语言
C语言之函数递归
C语言之函数递归
TU^
12 1