哈喽呀,各位小伙伴,本肉段er~又来给大家送知识啦!不禁感慨一下,这两周的溜肉段er 还是比较勤奋滴呢,嘻嘻嘻!
这一次,肉段er 将为各位小伙伴们介绍一下 C 语言中的函数,包括函数是什么、函数的分类、函数的参数以及函数的声明和定义。请各位小伙伴跟随肉段er 的步伐一起进入函数的世界吧!
一、函数的概念
一提到函数,各位小伙伴第一时间肯定是想到了数学中的函数,俺也一样(高中时被函数支配的恐惧可是印象深刻呢,【捂脸】)。其实,C语言中的函数(称它为子程序好像更加准确一些)就是一个完成某项特定任务的一小段代码,这段代码是有特殊的写法和调用方法的。
我们看到的一个完整的C语言程序其实是由无数个小的函数组合而成的,也可以理解为,一个大的任务被分解成多个较小的任务(即函数)完成。当然,我们可以通过重复调用某些函数来提升软件开发的效率。
二、函数的分类
在C语言中,我们一般会遇到两类函数:库函数和自定义函数。
2.1 库函数
1. 定义
2. 使用说明
库函数的学习和查看工具有很多,下面贴出了C/C++官方的链接,感兴趣的小伙伴们可以进去看看哦。(C/C++官⽅的链接:https://zh.cppreference.com/w/c/header)
另外,要注意!!!库函数是在标准库中对应的头文件中声明的,所以库函数的使用,务必包含对应的头文件,漏写可是会报错滴呦。
3. 举例
#include <stdio.h>
#include <math.h>
int main()
{
double a = 0;
scanf("%lf", &a);
double result = sqrt(a);
printf("%lf\n", result);
return 0;
}
运行结果:
说明:我们通过查看官网中的标准库头文件可知,sqrt( ) 函数对应的头文件为 <math.h>,scanf( ) 和 printf( ) 函数对应的头文件均为 <stdio.h>,所以我们应在程序中 #include 上这些头文件才可以。另外,由于我们要计算开平方的结果,那结果可能是浮点数,故要注意在定义变量和书写占位符时要对应好。
2.2 自定义函数
自定义函数:正如其名,当库函数的功能无法满足我们的需求时,就需要我们自己去定义一些函数来实现。
1. 语法形式
自定义函数的语法形式如下:
re_type fun_name(形式参数)
{
}
- re_type:表示函数计算结果的返回类型,有时返回类型可以是void,表示什么都不返回;
- fun_name:函数的名字,这个是需要我们自己定义的噢,但是不要给人家瞎起名,还是得有意义才行;
- ( ) 里放的是形式参数,函数有几个参数,都要在括号里要交代清楚这些参数的类型和名字;当然,函数也可以没有参数,这时,括号里就不需要写什么了,空着就好;
- { } 括起来的是函数体,即完成计算的过程。
2. 举例
例1:写一个加法函数,完成两个整型变量的加法操作。
#include <stdio.h>
int ADD(int x, int y)
{
return x + y;
}
int main()
{
int a, b;
scanf("%d %d", &a, &b);
int c = ADD(a, b);
printf("%d\n", c);
return 0;
}
讨论与分析:
通过题干可知,我们要构建一个加法函数,根据函数的语法形式可写出函数的整体框架。这时,小明产生疑问:“为什么return 后面跟的是一个表达式呢?我写成 ‘ int z = x + y; return z; ’ 可以吗?” 肉段儿老师欣慰一笑,并夸赞道,小明同学的提问非常有意义哈!这个问题直接引出了 return 语句使用的注意事项:
(1) return 后面可以是一个数,也可以是一个表达式,如果是表达式,则先执行表达式,再返回表达式的结果;return后面也可以什么都不写,这种情况适用于函数返回类型为void的情况。
(2) return语句执行后,函数就彻底返回了,return后面的代码不再执行。
(3) 如果函数中存在if 等分支的语句,则要保证每种情况都有return返回,否则会出现编译错误。
了解完return 语句的注意事项后,想必大家也就明白了为什么直接写 ‘ return x+y ’了吧!接下来,当我们创建完加法函数后,就可以对其进行调用啦,从代码中可以看出来,我调用ADD函数完成了a和b的相加,然后将函数返回的求和结果放在 c 中。最后,打印结果 c 。Over !!!
三、函数的参数
学到这里,可能会有细心的小明发现,我们在创建函数和调用函数时,函数的参数有些许不同,这种情况则涉及到函数参数的相关知识了。下面,让我们继续深入探索叭!
3.1 实参
在函数使用的过程中,函数的参数被分为实参和形参。我们先来讲一下实参。
实参(实际参数):真实传递给函数的参数,在例1中,我们调用ADD函数时传递给函数的参数 a 和 b 就是实参。
3.2 形参
形参(形式参数):只是形式上存在,不会向内存申请空间,不会真实存在的参数;在例1中,我们定义加法函数时在括号里写的 x 和 y 就属于形式参数。
注:形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才会向内存申请空间,这个过程为形参的实例化。
3.3 实参与形参的关系
讲到这里,我们已经对实参和形参两位朋友有了基本的认识,那它们两个之间是什么关系捏?让我们带着这个问题继续向下看吧!
例2:调试代码,观察实参与形参的内存地址
#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 c = ADD(a, b);
printf("%d\n", c);
return 0;
}
讨论与分析:
在例2中,我们仍以两个数求和为例,写完代码后,开始在键盘上按F11进入调试;然后,在监视窗口添加监视项(在这里,我们分别添加了实参a 、b,形参x、y,实参地址&a、&b,形参地址&x、&y);通过不断进行逐语句调试(调试快捷键:F11),可以观察到形参x、y 确实得到了实参a 、b 的值,但实参地址 &a、&b 与形参地址 &x、&y 并不相同。
由上可知,实参是传递给形参的,但是他俩并不是共用一个内存空间的,而是拥有各自独立的内存空间;也就是说,当你改变形参时,实参并不会随之改变。总而言之,形参是实参的一份临时拷贝。
3.4 数组做函数参数
在函数参数的最后一 part ,我们来讨论一下数组做函数参数的情况。
例3:用函数来实现对一个整型数组的内容,全部置 -1的功能,再使用另一个函数完成打印数组的功能。
#include <stdio.h>
void set_arr(int arr[],int length)
{
for(int i = 0; i < length; i++)
{
arr[i] = -1;
}
}
void print_arr(int arr[], int length)
{
for(int i = 0; i < length; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int len = sizeof(arr) / sizeof(arr[0]);
set_arr(arr,len);
print_arr(arr,len);
return 0;
}
通过观察例3,我们可以了解到数组传参的几个重要知识:
- 函数的形参要和函数的实参个数匹配;
- 函数的实参是数组,形参也可以写成数组形式
- 如果形参是一维数组,数组大小可以省略不写
- 如果形参是二维数组,行可以省略,但列不能省略
- 数组传参,形参并不会创建新的数组
- 形参操作的数组和实参的数组是同一个数组
四、函数的声明和定义
啊啊啊,本篇文章的最后一个部分啦!!!坚持住呀,各位友友(溜肉段er 站在函数世界的终点,奋力挥舞着手上的加油棒,并激动地喊道:“加油,努力,C语言考第一!”)。
(OK, 转回正题)在这一部分,我们讲一下函数的声明和定义。下面,我们分别从单个文件和多个文件两个方面来进行阐述。
4.1 单个文件
在这里,我们仍然用一个例子来进行具象化说明:
例4:写⼀个函数判断⼀年是否是闰年。
#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;
}
在例4中,‘ int is_leap_year (int y) ’ 部分即为函数的定义,而主函数中的 ‘ int r = is_leap_year(y); ’ 为函数的调用。
这种场景属于函数定义在函数调用之前,直接运行不会有问题。但如果我们把函数的定义放在函数调用的后面,如下代码:
#include <stdio.h>
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;
}
这时,编译器会出现‘ is_leap_year ’ 未定义的警告。那该怎么办呢?
解决这个问题其实只需要在函数调用前声明一下‘ is_leap_year ’ 这个函数就可以啦。
声明的格式为:函数类型 函数名(函数的参数);其中,函数声明中参数只保留类型,省略掉名字也是可以的。例如:‘ int is_leap_year( int y ); ’ 就是函数的声明。修改后的代码如下:
#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;
}
总结来说,函数的调用一定要满足:先声明后使用;函数的定义也是一种特殊的声明,所以函数定义放在调用之前也是可以的。
4.2 多个文件
当然,在我们平时工作中写代码时,代码可能比较多,不会将所有的代码都放在一个文件中,更多的是根据程序的功能将代码拆分放在多个文件中。
一般情况下,函数的声明、类型的声明都会放在头文件( .h )中,函数的实现是放在源文件(.c )中,例如:
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 = 0;
int b = 0;
scanf("%d %d",&a, &b);
int c = ADD(a,b);//函数的调用
printf("%d\n", c);
return 0;
}
通过上述代码可知,当我们调用函数时,同样需要在 TEST.c 文件中包含该函数所在的头文件。值得注意的是,与库函数头文件包含方式写法略有不同,要包含自定义函数所在头文件时需写成:#include "ADD.h" 。
五、总结
到这里,函数的基础知识也就学完啦!在这一篇博客中,我们首先了解了函数的概念;接着,继续研究了函数的分类(库函数和自定义函数如何使用);然后,我们深入学习了函数的参数,主要包括:实参和形参的具体解释和数组做函数参数的注意事项;最后,我们分别从单个文件和多个文件出发说明了函数调用一定要满足先声明后使用的规则。
终于!在这个阳光明媚的周日下午,一篇干货满满的博客新鲜出炉啦!希望这篇博客的内容能够对初学C语言的小伙伴们有一些帮助,这也是我写博客的初衷。另外,多说一句,只学不练,永远是纸上谈兵,要想让编程水平飞速进步,还是得需要多加练习才行。最后的最后,一句话送给看到这里的友友们,也送给自己:“ 放弃很简单,但坚持一定很酷!”