前言
在数学中,我们就有对函数进行学习过;在数学里函数的概念大概就是:自变量x通过对应法则可以得到因变量y。其实在C语⾔也引⼊函数(function)的概念,也有将其称为子程序。再C语⾔中的函数常用于完成某项特定的任务,通常是一小段代码。C语言的程序实则就是由很多个函数组合而来的,这些函数都会自己的功能;有些函数与函数之间还会有一定的联系。
一、main函数和库函数
首先我们见到的最多的一个函数就是这个main函数了,它被称为主函数;它是C语言程序的入口,是必不可少的。其形式如下
#include <stdio.h>
int mian() //也有写成int main(void)这样的;
{ //void表示不需要参数的意思,其实mian函数也有参数。
return 0;
}
- 对于main函数,只需记住这个固定的格式即可,因为这是C语言的语法规定。
库函数又是什么呢?库函数其实是不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现;就称为库函数。这些函数大多是对一些在编写程序时常用到的功能的实现;有了库函数,这些功能就不需要程序员⾃⼰实现了,⼀定程度提升了效率;同时库函数的质量和执⾏效率上都更有保证。
在使用库函数时,需要先知道库函数的形式,而这些形式都在其相对应的头文件(.h文件)中进行了声明;并且在使用库函数时还需要使用(#include)对其头文件进行包含。
要怎样知道有哪些库函数呢?又要怎么查看它们的头文件呢?其实这些厂商在提供库函数的时候,根据函数的不同功能都进行了划分;并将其在不同的头文件中进行了声明。我们可以通过访问(https://zh.cppreference.com/w/c/header)这个链接来查看。
- 举个例子来看:
例如求9的平方根,它被定义在math.h这个头文件中,来看看:
可以看到有三个类型,具体的选择是根据参数的实际情况来选择的。这里我们选double类型的来看
double sqrt(double arg);
//其中double 表示这个函数会返回一个double类型的值
//sqrt 是这个库函数的函数名
//()括号里的double arg 我们需要给一个double类型的参数
通过了解这个库函数后再来使用这个库函数看看:(使用前务必包含头文件)
二、自定义函数
在程序的编写中,库函数往往只是来辅助我们的。我们的关注度应该聚焦在⾃定义函数上;一千个人就会有一千个哈姆雷特,正是自定义函数给写代码增加了更多的创造性。
自定义函数的形式:
ret_type fun_name(形式参数)
{
}
//ret_type :为函数的返回值的类型
//fun_name :为函数的名字;由自己命名;建议命名有实际的意义较好!
//()括号内的为函数的形式参数;用于接受函数在被调用时的实际参数
//{ }大括号内为函数体,里面是函数功能的具体实现过程
我们可以把函数想成一个工厂,给它原材料,工厂就能生产得到一些产品
函数也类似与这样,给函数一些值;即函数的参数(数量可以为0,也可以为多个);函数经过相应的判断和计算,就可以给我们一个需要的结果;即函数的返回值,函数也可以没有返回值就是返回类型为(void);因为有时可能只需要函数帮我们完成某件事情即可,不需要返任何的结果。
来举个例子看看(例如写一个函数计算两个数的和)
#include <stdio.h>
int add(int x, int y)//add函数的定义
{
return x + y;
}
int main()
{
int n1 = 0;
int n2 = 0;
scanf("%d %d", &n1, &n2);
int c = add(n1, n2);//add函数的调用,
}
可以看到,这里创建了一个add函数来帮我们求两个数的和,add函数需要两个整形类型的参数;返回一个整形类型的结果。我们把代码运行起来看看结果:
这里只是举个简单的简单的例子给大家看一下,函数名、参数、返回类型都是可以根据实际的需求来进行灵活变化的。
三、函数的形参与实参
形参的全称为:形式参数;实参的全称为:实际参数。在定义函数的时写在函数名后括号内的为形式参数;在函数调用时写在函数名后面括号内的为实际参数。
实际参数:
形式参数:
那为什么要称为形式参数呢?其实是;就拿上面这个函数来说,只是定义了add函数,⽽不调⽤的话;函数的参数x和y只是形式上存在的,不会向内存申请空间,所以不是真实存在就称为形式参数。只有在函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程称为形参的实例化。
- 形参与实参的关系
上面讲了函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间。这里形参申请的是新的空间,与实参的空间的独立开来的。我们可以通过调试来查看一下:
能够看到,形参与实参只有值是相同的;它们的地址是不一样的。可以理解成形参是实参的一份临时拷贝。
四、return 语句
可以发现在上面写的add函数里有这么一个语句
return x + y;
其实这个函数的返回值就是需要通过这个return语句来返回的。下面来讲一下使用return语句需要注意的地方:
- return后面可以是⼀个数值,也能是⼀个表达式;是值则会直接返回该值,是表达式则会先计算出表达式的结果然后返回这个结果。
- return后面也可以什么都不加,写成return; 这种写法可以用于返回类型为void型的函数。
- return返回的值如果和函数返回类型不⼀致,编译器会⾃动将返回的值隐式转换为函数的返回类型。
- return语句执⾏后,函数就彻底返回,就是结束了函数;return语句后边的代码都不再执⾏。
- 函数中存在有if等分⽀语句时,就要保证每种情况下都有return返回,不然编译器会报编译错误。
五、数组做函数参数
在将数组做为参数传给函数的形参时,其实并不是把整个数组传过去了,只是把数组首元素的地址传过去了;数组名是数组首元素的地址。下面来介绍两种用形参接收数组传参的写法:
我们用这个例子来看:创建一个能容纳5个整数的数组初始化为0,写一个函数用于给数组中放入值,再写一个函数用于查看数组里的元素。
- 用数组来接收实参(对于指针还不太了解的建议这种写法,更加直观一点)
#include <stdio.h>
void set_arr(int arr[], int sz)//设置函数的定义
{
int i = 0;
for(i = 0; i < sz; i++)
{
scanf("%d",arr[i]);
}
}
void find_arr(int arr[], int sz)//查看函数的定义
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ",&arr[i]);
}
}
int main()
{
int arr[5] = {0, 0, 0, 0, 0};
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
set_arr(arr,sz);//设置数组的函数调用
fand_arr(arr,sz);//查查看元素的函数调用
}
- 用指针来接收实参(这里只给大家展示两个函数的部分了,主函数都是一样的)
void set_arr(int* p, int sz)//设置函数的定义
{
int i = 0;
for(i = 0; i < sz; i++)
{
scanf("%d",p+i);
}
}
void find_arr(int* p, int sz)//查看函数的定义
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ",*(p+i));
}
}
来看看这两种写法是否可以满足需求:
可以看到都没有问题,其实这两种方法只是写法不同。不过对于指针不怎么了解的话可能会对于第二种写法会不太理解。
六、嵌套调用和链式访问
- 嵌套调用其实就是函数与函数之间的互相调用;在一个函数内部我们可以调用很多个其他函数,这些函数相互作用最终得到了我们的程序,就像用一个个积木拼成了一个精美的作品。但是我们要注意在一个函数内部不能定义新的函数,只能调用另外的函数。从上面的例子中可以看到,我们定义的两个函数都是放在mian函数外面的。
- 那什么又是链式访问呢?其实就是把一个函数的返回值,作为另一个函数的参数。就像把函数一个一个连起来就像链条一样,就称为链式访问。举个例子来看看:
库函数strlen可以求字符串的长度,库函数printf可以输出数据,我们可以把strlen函数的返回值直接当成printf函数的参数吗?一块来看看:
使用链式访问:
可以看到同样也没有问题,并且这样写更加简洁。
七、函数的定义和声明
- 单个文件
在只有单个文件时,只需要把自定义的函数直接写出来使用就可以了。如果将自定义函数定义在mian函数之前,则可以不用写函数的声明;(函数的定义是一种特殊形式的声明)若把自定义函数定义在main函数之后,则需要在main函数之前加上函数的声明(函数的声明就是函数调⽤之前先声明⼀下这个函数,声明函数只要交代清楚:函数名,函数的返回类型和函数的参数)否则编译器会报出警告。举个例子来看看:
函数定义在main函数之前:
#include <stdio.h>
void is_even_num(int n)//函数的定义
{
if (n % 2 == 0)
printf("%d是偶数", n);
else
printf("%d不是偶数", n);
}
int main()
{
int input = 0;
scanf("%d", &input);
is_even_num(input);//判断是不是偶数的函数调用
return 0;
}
函数定义在main函数之后:
#include <stdio.h>
void is_even_num(int n);//函数的声明
int main()
{
int input = 0;
scanf("%d", &input);
is_even_num(input);//判断是不是偶数的函数调用
return 0;
}
void is_even_num(int n)//函数的定义
{
if (n % 2 == 0)
printf("%d是偶数", n);
else
printf("%d不是偶数", n);
}
若把函数定义在main函数后面,而且没有在main函数前面加上函数的声明,编译器就会报错,加上函数声明才能正常运行:
- 多个文件
当代码量很多的时候,一般会是由多个人同时来完成的;往往会根据程序的功能,将代码拆分放在多个⽂件中。⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的实现是放在源⽂件(.c)⽂件中。
总结
我们来总结下上面所说的内容:在写mian函数时需要按C语言语法规定的固定格式来写;然后就是库函数,库函数的使用需要包含对应的头文件,不需要完全记住所有的库函数,但是要学会查找库函数。然后就是自定义函数,自定义函数里形参是实参的临时拷贝;函数名、参数、返回类型都是可以根据实际的需求来进行灵活变化的。然后就是return语句,return语句用来给函数返回一个值,也可以什么都后面不加东西什么都不返回,return语句执行了函数就结束了。然后就是数组作为函数的参数,数组名其实是数组首元素的地址,我们可以用两种方式来接收。然后函数与函数之间可以互相调用,但是不能在函数里定义另外一个函数;还有一个函数的返回值可以做为另外一个函数的参数来形成链式访问。最后说了下函数的声明和定义,若函数定义在mian函数之后,就要在mian函数之前加上函数的声明。到这里就讲完了,在这里非常感谢大家的阅读!如果有讲的对不对的地方还请指正出来,以免误导了别人。再次感谢大家!