目录
函数概念
库函数
自定义函数
嵌套调用和链式访问
函数的声明和定义
一、函数概念
函数(function),也叫子程序,在C语言中是完成某项特定任务的一小段代码。通过对同一个函数的调用,大大提高了软件的开发效率。
在C语言中,函数一般分为两类:
- 库函数
- 自定义函数
二、库函数
C语言本身并不提供库函数,C语言的国际标准ANSIC规定了一些常用函数标准,成为标准库,不同的编译器厂商根据ANSIC提供的标准给出了一系列的函数的实现。这些函数就被称为库函数。
例如我们平时使用的"printf""scanf"都是库函数,那么要怎么查看库函数到底有哪些呢?怎么查看其是否使用呢?在这里提供给大家两个网站:
- C/C++官方网站:C 标准库头文件 - cppreference.com
- cplusplus.com:C library - C++ Reference
库函数的种类和个数很多,大家可以在后续的学习中慢慢接触,这里就不再一一介绍。
三、自定义函数
(一)自定义函数介绍
既然库函数是编译器厂商实现的函数,那么我们自己实现的函数就是自定义函数。自定义函数的结构如下:
ret_type fun_name(形式参数)
{
}
- ret_type是函数的返回类型,比如“int”就表示返回一个整形。返回类型也可以是void,表示什么都不返回(返回一个空值)
- fun_name是函数名。为了方便我们调用函数,我们一般命名地有意义一些。
- 小括号里面是函数的参数,当函数被调用时,就会把程序中的字符带入参数中。参数可以是void,也可以没有参数。如果有参数要明确规定参数的类型和名字以及参数个数。
- {}里面的内容被称为函数体,也就是完成计算的过程。
我们可以来看看下面这段代码:
#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 r = Add(a, b);
printf("%d", r);
return 0;
}
在这里我们定义了一个函数:Add,返回类型整型,参数个数两个分别是x,y。由于我这里要做加法运算,所以我把名字命名为Add,这样更有助于理解。
(二)形参和实参
依旧是这段代码,我们可以看到在函数的定义中我们给了两个参数x,y。当我调用函数时,真正代入函数的是a,b。那么x,y就是形参,a,b就是实参。
相当于数学中的一个函数f(x),f(x)后面的表达式由x作为参数,这个参数就是形参,而如果我们要求某一点的函数值代入的x值就是实参。
那为什么会有这种名称呢?我们来探讨一下实质。在函数定义阶段,我们只是定义函数,并没有使用,这个函数里的参数只存在于代码中,并没有运行,既然没有运行,那么这个参数就不会申请内存,因此称作形参。而当调用函数后,函数运行,需要申请内存,因此才被称为实参。
(三)return语句
return就是返回的意思,在函数的设计中,我们常常需要一个返回值。
- return后面可以是一个数值,如“return 0”,也可以是一个表达式,例如上面代码中的“return x+y”,如果是返回一个表达式,则是先执行表达式,再返回表达式的结果。
- return后面也可以无任何东西,直接写作“return”,一般用于返回类型是“void”的情况。
- 一般情况下,return的返回值类型和函数的返回值类型一致,如果不一致,系统会自动将返回值隐式转换为函数的返回类型。
- return语句执行后,函数就彻底返回,后面的代码不再执行。
- 如果函数中存在分支语句,则要保证每种情况、每个分支都要有return返回,否则会出现编译错误。
(四)数组作函数参数
既然数组是一种数据类型,在使用函数时难免会将数组传递给函数。我们来举个例子,写一个函数将整型数组的内容全部置换为1,然后打印数组的内容,那么大概的思路应该是这样的:
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);//设置数组内容为1
printf_arr(arr,sz);//打印数组内容
return 0;
}
那么在这里我们就要定义两个函数,一个是“set_arr”,一个是“print_arr”,我们先来看第一个函数:对于给数组依次赋值,应该把每一个元素都取出来单独赋值,因此我们可以这样:
void set_arr(int arr[], int sz)
{
int i = 0;
for (i = 1;i < sz;i++)
{
arr[i] = 1;
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 1;i < sz;i++)
{
printf("%d", arr[i]);
}
}
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;
}
注意:
- 函数形参实参要一致,形参是数组实参也要是数组。
- 形参如果是一维数组,数组大小可以省略,如果是二维数组,行可以省略,列不能省略。
- 数组传参,形参是不会创建新数组的。
- 形参操作的数组和实参的数组是同一个数组。
四、嵌套调用和链式访问
(一)嵌套调用
函数的调用大大的提高了效率,但是如果想在不同函数中实现某种功能,就要写很多次重复的代码,那么这个时候我们就可以把能实现这个功能的函数调用进来,在一个函数中调用另一个函数就叫做嵌套调用。话不多说,我们直接来个例子说明一下,如下:
假设现在需要我们计算某年某月有多少天,如果要用函数实现,我们总体需要两个函数:
- 根据年份确定是否是闰年,我们定义为year函数
- 根据月份确定有多少天,我们定义为month函数
我们可以先写一下确定闰年的函数:
int year(int y)
{
if ((y % 4 = 0) && (y % 100 != 0) || (y % 400 = 0))
return 1;
else
return 0;
}
接下来就是判断月份的函数,这个时候怎么把一年12个月的天数写进程序里呢?那就要用到我们的数组了,但是大家注意要数组的下标,第一个数下标是0,为了与月份对应,我们可以在数组最前面添加0,这样就一一对应了,参考代码如下:
int 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 (year(y)&& m == 2)//调用了year函数
day += 1;
return day;
}
大家可以看到这个函数中调用了year函数来判断2月有多少天,这就是函数的嵌套调用。那剩下的就是我们的主函数部分了,如下:
int main()
{
int y = 0;
int m = 0;
scanf("%d %d", &y, &m);
int d = month(y, m);
printf("%d", d);
return 0;
}
我们来浅浅分析一下我们这里用了哪些嵌套调用:
- year函数无调用
- month函数调用了year函数
- main函数调用了scanf、printf、month函数
(二)链式访问
链式访问,就是将一个函数的执行结果作为另外一个函数的参数,由此把几个函数串起来,比如:
#include<stdio.h>
#include<string.h>
int main()
{
printf("%d", strlen("abcdef"));
return 0;
}
将函数strlen的返回结果作为printf函数的参数,这就是链式访问。
这里给大家一道思考题,请看代码:
#include<stdio.h>
int main()
{
printf("%d",printf("%d",printf("%d",43)));
return 0;
}
这段代码的输出结果是什么呢?那如果改成这样:
#include<stdio.h>
int main()
{
printf("%d ",printf("%d ",printf("%d ",43)));
return 0;
}
输出结果又是什么呢?我们在文尾揭晓答案!
五、函数的声明和定义
(一)单个文件中函数的声明和定义
假如我们要判断某一年是否为闰年,我们可以这样用函数:
int 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 = year(y);
if (r == 1)
printf("是闰年");
else
printf("不是闰年");
return 0;
}
那么上面我们对函数year的定义就叫函数定义。在一项大工程中,我们难免会遇到程序写了一半需要一个新函数的情况。这时候如果返回第一行写函数显然不太可能,这时候我们就需要把函数放在后面,那我们不妨试试,看看会发生什么结果:
int main()
{
int y = 0;
scanf("%d", &y);
int r = year(y);
if (r == 1)
printf("是闰年");
else
printf("不是闰年");
return 0;
}
int year(int y)
{
if ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))
return 1;
else
return 0;
}
走起来我们可以看到编译器报错。这是因为,编译器编译时,是从上往下编译,由于编译到year时,year未定义,便不会继续编译下去,因此即使后面有函数他也不会调用。
这时候如果我们在前面加上一句:
int year(int y);
程序便可以正常运行,那么这句话起到的作用就是声明作用,相当于提醒编译器我有这个函数,就在后面,这时候函数就可以被调用了。
(二)多个文件中函数的声明和定义
企业在写代码时,代码较多需要分工完成,往往会将代码拆分为多个文件。一般情况下,函数的声明和函数类型声明放在头文件(.h文件)中,函数的实现放在源文件(.c文件)中,例如我们实现加法函数,可以这样写:
add.c:
//函数的定义
int Add(int x ,int y )
{
return x+y;
}
add.h:
//函数的声明
int Add(int x ,int y )
在正式文件中:
#include<stdio.h>
#include<add.h>
int main()
{
//函数主体
return 0;
}
这样看来几万行几十万行的代码也不是太不可思议了吧!
下面是对于文中问题的解答:
#include<stdio.h>
int main()
{
printf("%d",printf("%d",printf("%d",43)));
return 0;
}
这段代码的返回结果是4321,首先我们需要知道printf函数的返回类型,printf函数返回的是打印字符的个数 。这段代码中:
- 第三个printf,打印43,两个字符,返回2
- 第二个printf,打印2,一个字符,返回1
- 第一个printf,打印1
#include<stdio.h>
int main()
{
printf("%d ",printf("%d ",printf("%d ",43)));
return 0;
}
对于这段代码,由于空格的存在,空格也算字符,因此打印结果是43 3 2,大家可以根据上文自行思考一下!
本期分享到此结束啦!再见!C语言小白,不定期更新C语言基础知识点。