目录
一.函数的概念
听到函数,想必大多数人的第一反应应该是数学中的函数,但是在C语言中也引入了函数的概念,更准确的说,在C语言中函数也可以叫做子程序,C语言中的函数就是为了完成某项特定任务的代码,这段代码是拥有特殊写法和调用方式的。函数分为库函数和自定义函数,接下来我们来学习一下这两类函数是如何使用的吧。
二.库函数
1.标准库和头文件的概念
C语言本身是并不提供库函数的,但是C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库,而不同的编译器厂商根据ANSI提供的C语言标准给出了一系列函数的实现,这些函数就被称为库函数。库函数也是函数,有了库函数,我们就不需要去自己编写代码实现一些功能,不仅提升了效率,而且库函数的质量和执行效率都更有保障。这些库函数根据功能的划分,都在不同的头文件中进行了声明,例如printf 和 scanf 这两个库函数被包含在#include<stdio.h>这个头文件中。
2.库函数的使用
我们应该如何学习库函数,并且使用库函数呢?下面为大家提供2个学习和查找库函数的网页:
C 标准库头文件 - cppreference.comhttps://zh.cppreference.com/w/c/header
C library - C++ Referencehttps://legacy.cplusplus.com/reference/clibrary里面都详细的介绍了每个头文件下的包含的各个函数,以及函数的功能,函数使用的参数和该函数的返回类型。对于库函数的学习,大家都可以在里面学习,接下来我们来讲讲自定义函数。
三.自定义函数
了解完库函数,发现库函数在我们编写程序的过程中解决了许多问题,但是库函数总不能解决所有的问题吧,不然还需要程序员做什么,所以这时候就有了自定义函数的概念,自定义函数顾名思义就是自己定义的函数,具体这个函数有什么功能,需要我们自己去编写。
1.函数的语法格式
ret_type fun_name (形式参数)
{
}
//ret_type 是指函数的返回类型
//fun_name 是指函数名
//()括号里面放的是形式参数
// {} 大括号里面放的是函数体,就是该函数需要完成的内容
2.函数的举例应用
了解了自定义函数的语法格式后,我们现在来写一个Add函数,完成两个数相加的功能。
#include<stdio.h>//因为使用了printf这个库函数,所以要包含头文件
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 30;
int ret=Add(a, b);
printf("%d\n", ret);
return 0;
}
//int为Add函数的返回类型
//Add中x,y就是形式参数,用来接收主函数中a和b的值
//return x+y 就是Add函数返回的值
//在主函数main中用ret接受Add返回的值,并打印出来
//其中main函数是主调函数,而Add函数是被调函数
运行结果如下:
根据上面的代码我们可以得知,我们定义了一个Add函数,Add函数返回的类型是int类型,我们通过主函数调用Add函数,将a和b的值传给Add函数,Add函数定义了2个形式参数x和y来接收a和b的值,最后返回x+y的值给ret,那么这样的一个加法函数就实现了。
3.实参和形参的概念
(1) 实参
实参叫做实际参数,根据上面的加法函数,我们在主函数main中调用Add函数的时候,传递给函数的参数a和b就是实际参数,简称实参,实际参数就是真实传递给函数的参数。
(2)形参
形参叫做形式参数,可是为什么叫做形式参数呢,事实上,我们定义了Add函数,但是不去调用它的话,Add函数中的参数x和y只是形式上存在的,并不会向内存申请空间,不会真实存在,所以叫做形式参数,形参只有在函数被调用的过程中为了接收实参的值,向内存申请空间,这个过程就是形参的实例化。
(3) 形参与实参的关系
根据上面的Add函数和这幅图,我们可以看见形参x和y确实接收到了实参a和b的值,但是它们在内存中的地址是不一样的,所以我们可以理解为形参是实参的一份临时拷贝,形参是自己拥有独立的空间的,对形参的改变不会影响实参。对于这种调用,形参的改变不会影响实参,传递的是值,这种调用叫做传值调用。
在函数定义的过程中有几个注意事项:
1.形参的名字可以与实参的名字相同,也可以不同,取决于自己
2.形参的个数可以没有,也可以是一个或者多个
3.形参的个数和实际参数要匹配上
4. return语句的注意事项
1.return 可以返回表达式,例如上面的Add函数中的 return x+y; 在返回前会计算x+y的值,再返回最后的值
2.如果不需要返回值的情况下,函数的返回类型应该用 void 类型,并且可以不写return ,或者写return ,例如下面代码
void Add(int x, int y)
{
printf("%d",x + y);
return;
}
int main()
{
int a = 10;
int b = 30;
Add(a, b);
return 0;
}
3.return返回的值与函数返回类型不一致的情况下,系统会将返回的值转换成函数的返回类型,示例如下:
int Add()
{
return 3.14;
}
int main()
{
int ret=Add();
printf("%d ", ret);
return 0;
}
运行结果
4. return语句执行后,后面的代码不会执行
void Add()
{
printf("hehe\n");
return;
printf("haha\n");
}
int main()
{
Add();
return 0;
}
5.当函数中存在分支语句的情况下,要确保每种情况都要有返回值,否则会出现编译错误,下面来看:
int Add(int n)
{
if (n < 0)
{
return -1;
}
}
int main()
{
int ret=Add(10);
printf("%d ", ret);
return 0;
}
根据上图可以看见 不但发生了警报,而且返回的值也是一个随机数。
5.数组传参
在传参的过程中,肯定不仅仅只有变量是可以进行传参的,那么数组也是可以传参的,数组是如何进行传参的呢,接下来我们编写一个函数将一个数组中的值全部转换成1,再编写一个函数打印数组的内容。
void print_arr(int a[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
void set_arr(int a[], int sz)
{
for (int i = 0; i < sz; i++)
{
a[i] = 1;
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
print_arr(arr,sz);//打印数组转换前的内容
set_arr(arr, sz);//将数组的内容转换成1
print_arr(arr, sz);//打印数组转换后的内容
return 0;
}
运行结果
看完上面的代码和运行结果,相信很多小伙伴有非常多的疑问, 为什么被调函数中定义的数组不需要告知数组的大小呢? 为什么改变函数的形参部分,主函数中的数组的内容也被改变了呢?不是说形参的改变不会影响实参吗?接下来给大家一 一解答这些问题。
在函数调用的时候,我们传递给函数的实参是 arr 和 sz,此时的arr是数组名,sz是数组的元素个数,那么此时的arr 传给形参的究竟是什么?
可以看到arr数组此时已经传给了形参 a,可是形参a 接收到的是整个数组元素吗,很显然并不是,而 a 此时接收到的是arr 这个数组的首元素的地址,而形参部分接收到的是数组首元素的地址,在数组传参的时候,在形参部分不会创建新的数组,所以就不需要定义数组的大小了
但是为什么形参的改变会影响到实参呢?这是因为我们在传参的过程中是将数组首元素的地址传给形参,此时形参访问数组的时候,操作的就是主调函数中的数组,所以此时形参的改变就是对实参进行改变。而这种通过地址传参的调用方式,也叫做传址调用。
这里需要大家知道数组传参的几个重要知识
6.嵌套调用和链式访问
(1). 嵌套调用
嵌套调用就是函数之间的互相调用,接下来我们举个例子,假设我们要计算某年某月有多少天
int is_year(int y)
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
{
return 1;
}
else
{
return 0;
}
}
int get_days(int y, int m)
{
int days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };//1-12月每个月的天数
int day = days[m -1];//因为数组的下标是从0开始的,所以days[m-1]就是对应月份的天数;
if (is_year(y) && m == 2)//调用is_year函数判断是不是闰年
{
day += 1;
}
return day;
}
int main()
{
int year = 0;
int month = 0;
scanf("%d %d", &year, &month);
int day=get_days(year, month);//自定义一个函数求天数,返回值用day接收
printf("%d年%d月有%d天", year,month,day);
return 0;
}
运行结果
从上面的代码我们可以看到我们从主函数调用get_days函数求天数,但是求天数的过程中,我们许需要判断是闰年还是平年,所以我们在函数get_days中调用is_year函数 判断是闰年还是平年,这个过程就叫做函数的嵌套调用。
(2). 链式访问
链式访问就是将一个函数的返回值作为另一个函数的参数,像链条一样将函数串起来就是函数的链式访问,举个例子:
int main()
{
printf("%zd", strlen("hello"));
return 0;
}
strlen是#include<string.h>这个头文件下所包含的一个库函数,strlen的作用就是计算一个字符串的长度,在这里使用 printf 这个库函数将strlen的返回值作为参数打印出来,这就是函数的链式访问。
接下来看看这串代码
int main()
{
printf("%d",printf("%d",printf("%d",43)));
return 0;
}
那么此时屏幕上会打印什么呢? 看一下运行结果
那么为什么是打印4321呢?
通过查阅我们可以知道printf函数在打印成功后,将退还所写的字符总数。所以printf的返回值就是打印成功的字符个数,所以最里面的printf打印出43时,返回的值是2,第二个printf 打印出2 时,返回值是1,而在外面的printf 就会打印1,所以最后打印出的结果是4321。这也是函数的链式访问。
7.函数的声明和定义
对于函数的使用,一定要先声明后使用
(1). 单个文件
int Add(int x,int y)
{
return x + y;
}
int main()
{
int ret = Add(10, 20);
printf("%d", ret);
return 0;
}
上面这个代码是可以正常使用的,但是下面这个代码还可以正常使用吗?
int main()
{
int ret = Add(10, 20);
printf("%d", ret);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
有的小伙伴就说了,不就是函数定义的位置变到主函数下面了吗?是这样的,但是函数定义在主函数下面就会触发警报。
这是因为代码是从上向下编译的,将函数定义在主函数底下时,我们应当先声明这个函数,这样就不会产生不必要的警报了。而将函数定义在调用之前的时候,就不会触发警报的原因,是因为函数定义是一种特殊的函数声明。
int Add(int x, int y);//函数声明
int main()
{
int ret = Add(10, 20);//函数调用
printf("%d", ret);
return 0;
}
int Add(int x, int y)//函数定义
{
return x + y;
}
(2). 多个文件
如果我们有多个文件的情况下,将主函数放在源文件中,将函数的声明放在add.h这个头文件中,将函数的定义放在add.c中,这个时候该如何使用这个函数呢。
//源.C的文件中
#include "add.h"
int main()
{
int ret = Add(10, 20);//函数调用
printf("%d", ret);
return 0;
}
//add.h的头文件
#pragma once
int Add(int x, int y);//函数声明
//add.C的文件
#define _CRT_SECURE_NO_WARNINGS 1
int Add(int x, int y)//函数定义
{
return x + y;
}
在拥有多个文件的情况下,我们通常将函数的声明放在.h的头文件中,将函数的声明放在其他.c文件中,最后我们在主函数中想要调用这个函数时,只要包含一下头文件,就像上面的代码,因为我们已经在add.h这个头文件中声明过Add这个函数了,所以调用前只需要包含一下#include "add.h"这个头文件就可以了。
以上就是本期函数讲解的全部内容啦,如有错误欢迎各位小伙伴评论~