今天我们要学习的是函数。
我们从初中开始就接触过函数,在数学中函数的形式是 y = F(x)这样的,我们给定一个x值,就会返回y的值,这是数学中的函数。
在C语言中函数与数学有些相似,但区别又很大,我更倾向于把C语言中的函数称为:子程序。其实从名字中就可以看出来,子程序(函数)是为了完成一个大程序中的其中一个小任务的程序。一个大的程序就是由许多小函数构成的。同时,如果我们要重复做某一件事情,我们就可以反复调用我们写好的程序,在下面的文章中我们就会体现。
在C语言中,我们一般会见到两种函数:
- 库函数
- 自定义函数
接下来我们就围绕着这两种函数展开。
目录
一、库函数
库函数在我们之前的学习中就使用过,例如printf、scanf等都是库函数,那么究竟什么是库函数呢。
1.标准库和头文件
C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数。C语⾔的国际标准ANSI C规定了⼀ 些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列 函数的实现。这些函数就被称为库函数。
库函数也是函数,只不过这些函数不需要我们自己去实现,编译器厂商以及帮我们实现好了,我们直接用即可,有了库函数可以提升我们编写程序的效率。
库函数有很多种,根据功能的不同被分类放进了不同的头文件中,我们可以通过下面的网址进行查看。
库函数相关头⽂件:https://zh.cppreference.com/w/c/header
2.库函数的使用方法
库函数学习的方式有很多,例如:
C/C++官⽅的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
在学习一个新的函数时我们应该看函数的功能、函数的头文件包含、函数的参数类型、函数的返回值等等。在此我们以sqrt()函数为例。
double sqrt (double x);
//sqrt 是函数名
//x 是函数的参数
//第一个double 是函数返回值的类型
//第二个double 是传给函数的值的类型
功能是计算x的平方根并返回其值。
头文件是<math.h>,如果不包含此头文件,该函数的使用就会出现问题。
参数类型是double,我们在传参的时候要传符合函数参数类型的变量。
返回值是double,我们在接收返回值的时候,最好使用同类型的变量来接收。
在清楚上面几点后,我们就可以在代码中实践一下了。
#include <stdio.h>
#include <math.h>
int main()
{
double a=4.0;
double b=sqrt(a);
printf("%lf",b);
return 0;
}
显而易见其运行结果就是2.0。
二、自定义函数
在了解了库函数之后,我们应该把我们的关注度更多的放在自定义函数,自定义函数的自由程度跟高,在我们写代码的时候有更多的创造性,可以实现更多的功能。
1.自定义函数的语法形式
自定义的函数的语法形式与库函数是一样的,其形式如下:
ret_type fun_name (形式参数)
{
}
ret_type是函数的返回值类型,其类型可以是任何形式,可以是int(返回整型),可以是void(不返回任何值)。
fun_name是函数名,为了方便调用函数,在起名的时候,最好与功能有一定的相关性。
()中是形式参数,函数的参数也可以是各种类型,在穿参的时候,要表明函数的类型与个数。
{}中是函数体,用来实现函数功能的。
2.自定义函数举例
例:写一个加法函数,完成两个整型变量的加法操作。
#include <stdio.h>
int Add(int x,int y)
{
int z;
z=x+y;
return z;
}
int main()
{
int a,b;
scanf("%d %d",&a,&b);
int c=Add(a,b);
printf("a+b=%d",c);
return 0;
}
其中Add函数也可以简化为:
int Add (int x,int y)
{
return x+y;
}
从举得例子中可以看出,我们在在自定义函数时,要交代清楚函数的名字,返回类型,参数的类型以及个数。
以上只是一个简单的例子,未来在编写代码的过程中,我们在自定义函数时函数名,返回类型,参数等都会更加的灵活。
3.形参和实参
在使用函数的过程中,我们把函数的参数,分为形参和实参。顾名思义,形参就是形式参数,实参就是实际参数。
我们从之前的代码中进行分析:
#include <stdio.h>
int Add(int x,int y)
{
int z;
z=x+y;
return z;
}
int main()
{
int a,b;
scanf("%d %d",&a,&b);
int c=Add(a,b);
printf("a+b=%d",c);
return 0;
}
在这段代码中,main函数中的Add(a,b)中的a,b就是实参,是实际真实传递给函数的参数。
在Add(x,y)中x,y就是形参,所谓形参就是形式上存在,当我们定义了Add函数之后,我们不去调用Add函数时x,y只是形式上存在,并不会向内存中申请空间。没有真实存在,所以叫形式参数。 只有Add函数被调用时,为了存放数据,才会向内存中申请空间,这个过程被称为形式的实例化。
4.形参与实参的关系
为了探讨形参与实参之间的关系,我们还是根据刚才的代码进行分析。它们之间是有联系的,但是不是同一块空间,我们可以借助调试工具来观察现象。
#include <stdio.h>
int Add(int x,int y)
{
int z;
z=x+y;
return z;
}
int main()
{
int a,b;
scanf("%d %d",&a,&b);
int c=Add(a,b);
printf("a+b=%d",c);
return 0;
}
在调试的过程中我们发现,a与x,b与y的值确实一样,但是它们的地址是不一样的,通过这个现象我们就可以了解到,形式参数只是实参的一份拷贝。
4.return语句
在函数编写的过程中,经常会出现return语句。接下来来讲解一下return语句的注意事项。
(1)return语句后面可以跟一个数值,也可以跟一个表达式,如果是表达式,则先执行再返回数值。
(2)当函数返回类型是void时,return语句后也可以什么也不跟,直接写 return;
(3)当return语句的返回类型与函数的返回类型不一样时,系统会将return的返回类型转换成函数的返回类型。
(4)当return语句执行时,函数就彻底结束,不再执行后面的语句。
(5)如果函数中存在分支语句时,确保每一个分支后面都跟有一个return语句,否则会出现编译错误。
三、函数的进阶用法
在理解了函数的基础之后我们就可以利用函数实现一些功能了,接下来我们将用函数实现一些更加难的功能。
1.数组做函数参数
在用函数解决实际问题时,难免会把数组作为函数的参数进行传递,思考一下数组的传递与其他类型的参数传递时一样吗。比如说我们要写一个函数把一个数组中的所有元素全部设置成1,然后在写一个函数打印该数组的元素,我们应该如何实现?
这里我们需要两个函数,一个重置内容,一个打印内容。在对数组进行操作时,我们需要遍历数组的所有元素,所以我们需要知道数组的元素个数,所以我们在传递参数的需要把数组传递给函数,也需要把数组的元素个数也传递给函数,这样一来我们就可以实现对数组进行操作。
在设计函数之前,我们需要了解数组传参的几个知识点:
(1)函数的形式参数要和函数的实际参数的个数匹配。
(2)函数的实参是数组,形参也可以写成数组的形式。(实际上传递的是地址,后续讲指针时会讲到)
(3)形参如果是一维数组,数组的大小2不可以省略。
(4)形参如果是二维数组,数组的行可以省略,但列不行。
(5)数组传参形参是不会创建新的数组的,是在实参是直接进行操作的,形参和实参是同一个数组。(这个也与指针有关)
在有了以上的知识之后,我们就可以设计函数了,其实现效果如下:
#include <stdio.h>
//设置数组元素
void set_arr (int arr [10],int sz)
{
for(int i=0;i<sz;i++)
{
arr[i]=1;
}
}
//打印数组元素
void print_arr (int arr [10],int sz)
{
for(int i=0;i<sz;i++)
{
printf("%d",arr[i]);
}
}
int main()
{
int arr[10]={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;
}
2.嵌套调用和链式访问
在设计程序的过程中函数并不是独立的,函数与函数之间存在着许多联系,一个函数中包含着另一个函数就是嵌套调用,一个函数的返回值作为另一个函数的参数就是链式访问,正是因为函数的之间的关联,之间的相互调用,才能写出相对较大的程序。
(1)嵌套调用
嵌套调用就是函数之间的互相调用,我们通过一个例子来理解。
例:判断某年的某个月有几天。
当我们拿到一个要求时,我们要先对其进行分析。年份分为闰年与平年,闰年与平年的区别就在于二月的天数不同,所以我们在输入二月时要进行一次判断该年是否为闰年。剩下的就简单了,只需要判断月份即可。有了以上的判断,我们需要设计两个函数,一个函数是判断月份的,一个是来判断年份的。接下来我们就来实现这个程序。
#include <stdio.h>
int year (int y)
{
if((y%4==0 && y%100 !=0)||(y%400==0))
{
return 1;
}
else
{
return 0;
}
}
int day (int y,int m)
{
int month[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};//为了输入的月份与元素下标一致
int d=month[m];
if(year(y) && m==2)
{
d+=1;
}
return d;
}
int main()
{
int y,m;
scanf("%d %d",&y,&m);
int d=day(y,m);
printf("%d",d);
return 0;
}
在这个程序中函数的互相调用体现了很多,例如main函数调用了scanf函数、day函数、printf函;day函数中调用了year函数。在未来写一些更大的函数时,嵌套调用会更加频繁。
(2)链式访问
所谓链式访问,就是把一个函数的返回值,作为另一个函数的参数,我们举个例子。
例:
//这是不使用链式访问的方法
#include <stdio.h>
int main()
{
int x=strlen('abcdef');//求一个字符串的长度
printf("%d",x);
return 0;
}
//这是使用链式访问的方法
#include <stdio.h>
int main()
{
printf("%d",strlen('abcdef'));//直接把strlen函数的返回值当作printf函数的参数
return 0;
}
仔细思考一下,上面两个代码的区别,这就是链式访问。
四、函数的定义与声明
函数的声明方式在C语言中主要有两种,一种是我们只写在单个文件中,另一种是我们可以在多个文件写。
1.单文件声明
在我们之前写一些简单程序的时候,我们只在一个文件中进行各种操作,在同一个函数中声明函数我们已经很熟悉了。
#include <stdio.h>
//
int Add(int x, int y)
{
int z;
z = x + y;
return z;
}
//
int main()
{
int a, b;
scanf("%d %d", &a, &b);
int c = Add(a, b);
printf("a+b=%d", c);
return 0;
}
就像这段函数一样, 被//框起来的是函数的定义,而在main函数中,是函数的调用。在这种情况下,函数定义在被调用之前,没啥问题。但是如果我们把函数定义放在函数调用之后会发生声明情况,如下:
#include <stdio.h>
int main()
{
int a, b;
scanf("%d %d", &a, &b);
int c = Add(a, b);
printf("a+b=%d", c);
return 0;
}
int Add(int x, int y)
{
int z;
z = x + y;
return z;
}
这段代码在编译器中就会出现下面的警告信息:
这是因为因为,在C语言中,编译时是从第一行开始向下逐行编译的,当在main函数中,遇到Add时,发现Add函数并没有定义,就出现了上面的警告。
那我们应该如何解决这个问题呢,我们只需要在调用函数之前,先对函数进行声明即可,函数声明要交代,函数的名字、返回值、参数类型及个数。
#include <stdio.h>
int Add(int x, int y);//函数声明
int main()
{
int a, b;
scanf("%d %d", &a, &b);
int c = Add(a, b);
printf("a+b=%d", c);
return 0;
}
int Add(int x, int y)
{
int z;
z = x + y;
return z;
}
这时候程序就能正常编译了,int Add(int x, int y); 这就是函数声明,我们在自己写函数的时候,一定要满足,先定义再调用。
函数的定义也是一种特殊的声明,所以函数的定义写在调用之前也可以正常编译。
2.多文件声明
到此为止,我们已经弄明白了什么是函数声明了,我们在写一些复杂程序的时候,不会将所有代码都放到同一个文件中,往往我们会根据程序的功能,将代码放在不同的文件中。
一般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现放在源文件(.c)中。如下:
add.c
//函数的定义
int Add(int x, int y)
{
int z;
z = x + y;
return z;
}
add.h
//函数的声明
int Add(int x, int y);
test.c
#include <stdio.h>
#include "add.h"
int main()
{
int a=1;
int b=2;
//函数调用
int c=Add(a,b);
printf("%d\n",c);
return 0;
}
运行结果是:
到此为止,函数的一些基本概念就介绍完毕了,我认为比较重要的点在于自定义函数以及函数声明这两部分,要增加对其的理解。