C语言之函数的讲解

目录

一.函数的概念

二.库函数

        1.标准库和头文件的概念

        2.库函数的使用

三.自定义函数

1.函数的语法格式

2.函数的举例应用

3.实参和形参的概念

        (1) 实参

        (2)形参

        (3) 形参与实参的关系

4. return语句的注意事项

5.数组传参

6.嵌套调用和链式访问

        (1). 嵌套调用

        (2). 链式访问

7.函数的声明和定义

(1). 单个文件

(2). 多个文件

 

    


一.函数的概念

        听到函数,想必大多数人的第一反应应该是数学中的函数,但是在C语言中也引入了函数的概念,更准确的说,在C语言中函数也可以叫做子程序,C语言中的函数就是为了完成某项特定任务的代码,这段代码是拥有特殊写法和调用方式的。函数分为库函数自定义函数,接下来我们来学习一下这两类函数是如何使用的吧。

二.库函数

        1.标准库和头文件的概念

C语言本身是并不提供库函数的,但是C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库,而不同的编译器厂商根据ANSI提供的C语言标准给出了一系列函数的实现,这些函数就被称为库函数。库函数也是函数,有了库函数,我们就不需要去自己编写代码实现一些功能,不仅提升了效率,而且库函数的质量和执行效率都更有保障。这些库函数根据功能的划分,都在不同的头文件中进行了声明,例如printf 和 scanf 这两个库函数被包含在#include<stdio.h>这个头文件中。

        2.库函数的使用

我们应该如何学习库函数,并且使用库函数呢?下面为大家提供2个学习和查找库函数的网页:

C 标准库头文件 - cppreference.comicon-default.png?t=N7T8https://zh.cppreference.com/w/c/header      

C library - C++ Referenceicon-default.png?t=N7T8https://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"这个头文件就可以了。

以上就是本期函数讲解的全部内容啦,如有错误欢迎各位小伙伴评论~

 

 

 

    

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值