C语言——初识函数

哈喽大家好,我是保护小周ღ,本期为大家带来的是C语言基础知识当中的函数,从什么是函数库函数和自定义函数函数的参数函数的调用函数的声明和定义函数的递归几个方面保姆级讲述包您一看就会,快来试试吧~

目录

一、初识函数

1.1 什么是函数

二、函数的分类

2.1 库函数

2.1.1 库函数的意义是什么

2.2 自定义函数

2.3 求一个字符数组数据元素的个数

2.3.1 库函数实现

2.3.2 自定义函数实现

三、函数的参数

3.1 实际参数 

3.2 形式参数

四、函数的调用

4.1 传值调用 

4.2 传址调用

4.3 传值调调用,传址调用那个更好?

 五、函数的声明和定义

六、函数的递归

6.1 递归简介

6.2 递归和非递归

6.2.1 求n的阶乘递归做法

 6.2.2 求n的阶乘非递归做法


一、初识函数

1.1 什么是函数

维基百科中对函数的定义:子程序

1. C语言的函数是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性且可以重复使用,用来独立的完成某个功能。

2. 一般函数有参数有返回值,函数可以没有参数也可以没有返回值,C语言 main() 也是一个函数

 函数可以没有参数也可以没有返回值,我们可以写成:

void 函数名( void )
{
   语句;
   ……
}

C语言——初识函数_第1张图片

C语言——初识函数_第2张图片

为什么说头文件也可以说是一个函数集呢,举个简单的例子,我们的输入、输出函数,scanf、printf,都是函数且都在 stdio.h 头文件里,所以我们在使用这来两个函数是需要声明头文件,才可以使用,注意C 语言没有专门的输入、输出语句。printf、scanf,这个两个函数是有返回值的,返回值正常情况下是函数参数的个数。简单来说包含在头文件里的函数统称为库函数。


二、函数的分类

2.1 库函数

上文简短的了解了什么样的函数叫做库函数,是我们C语言的基础库中提供的一系列的函数,可以解决我们常用的基础功能,我们声明对应的头文件,即可直接使用库函数实现某一方面的功能,例如说,输入、输出、strlen,strcpy,sprt ,pow等。函数有一个特点,就是有一对标准的括号()这个括号其实是函数的操作符,有一个例外,sizeof() 这个是关键字,不是函数。

使用库函数,必须包含 #include 对应的头文件,再输入与之对应的参数,即可使用实现某一方面的功能。

简单的总结,C语言常用的库函数都有:
IO函数
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数

……


2.1.1 库函数的意义是什么

C语言的库函数并不是C语言本身的一部分,它是由编译程序根据一般用户的需要编制并提供用户使用的一组程序,例如Vs 2019里面的库函数就是按照国际C语言的标准,统一参数,统一函数名,统一返回类型,统一函数功能,而函数的功能实现(代码实现)则需要要微软公司自主设计实现。C的库函数极大地方便了用户,同时也补充了C语言本身的不足。

C语言就是语法,“C语言标准”就是做了一些工作:

设定:库函数功能,函数名,参数,返回类型,怎么实现(不管),实现是交给编译器厂商实现。

如何查看这些库函数的标准呢?博主会把网站链接发在评论区,有兴趣的朋友可以看看。

 C语言——初识函数_第3张图片


2.2 自定义函数

根据自己的需求,设计一个函数来实现某一方面的功能,自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是这一切都是由自身来设计的,有很大的发挥空间。

自定义函数的作用:是通过函数封装可重复使用的代码块,从而节省代码数。自定义函数指的是定义一个函数库里没有的函数,并给予其运行方式。将代码段封装成函数的过程叫做函数定义。

自定义函数的好处,我们把一个具体的功能用一个函数封装,如果有需要还可以把细小的功能继续封装为函数,这样在结构上是不是就很雅观,通俗易懂,而且函数是可以重复使用的,像交换两个整型变量的内容,我们在数据排序的时候是不是要经常使用啊,直接封装一个函数,在数据交换的时候直接调用,输入对应的参数,即可实现功能。

在程序设计中,使用的最多的就是自定义函数。


2.3 求一个字符数组数据元素的个数

首先我们使用的是库函数的strlen()函数计算,那国际标准下的strlen()函数 有什么格式上的要求呢?

C语言——初识函数_第4张图片

 以上标准,我们可以知道该函数的基本使用方法:

1. 函数的返回值 size_t 为整型数据,函数的参数是一个 const 修饰的字符指针,字符指针对应的值不可被修改,所以我们传参的时候可以是 一个字符数组的数组名,代表字符数组的首地址。给到指针后,可以通过指针来访问数组元素,当然字符数组的内容是不允许修改的,所以用const 修饰。当然这个传参也可以是指针,注意参数类型。

2. C字符串的长度是由结束空字符决定的:一个C字符串的长度是字符串开始和结束空字符之间的字符数(不包括结束空字符本身),字符串的结束标志为 ‘\0’,访问到这个标志程序结束,所计算结果即使字符串长度。

3. 不应将此值与保存字符串的数组的大小混淆。例如: 

char mystr[100]="test string";

定义了一个长度为100个字符的字符数组,但是初始化mystr时使用的C字符串长度只有11个字符。因此,当sizeof(mystr)计算值为100时,strlen(mystr)返回11。虽然开辟了很大的空间,并不代表这即使mystr 存储的字符串的长度,不是一个概念,严格根据结束标志来‘\0’。

所以库函数的内部实现还是有些复杂的,要求很多,但造就了标准。


2.3.1 库函数实现

C语言——初识函数_第5张图片


2.3.2 自定义函数实现

我们就按照strlen()函数的标准,模拟实现一下函数的功能,看能不能达到相同的效果。

C语言——初识函数_第6张图片

当然这个函数功能的实现部分有很多种方法,递归也可以实现,所以自定义函数的自由度比较高。

那我们可不可以自己写一个头文件,把我们自己写的函数包含到里面去,使用的时候声明自定义的头文件,直接使用函数呢?答案是可以的,以后参加工作写项目之后就可以领略其中的奥妙。


三、函数的参数

3.1 实际参数 

1. 实际参数,简称实参,指在主调函数中调用一个函数时,函数名后括号中的参数。真实传给函数的参数,叫实参。
2. 实参可以是:常量、变量、表达式、函数等。
3. 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形
参。

3.2 形式参数

1. 形式参数,形参是在定义函数名和函数体时使用的参数,目的是用来接收调用函数时传递的参数。

2. 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(操作系统为形参分配空间),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了,形参的作用域就在函数的内部。

C语言——初识函数_第7张图片


四、函数的调用

4.1 传值调用 

C语言——初识函数_第8张图片

C语言——初识函数_第9张图片

传值总结:我们在调用sum()函数的时候,操作系统会为A,B 在栈区上开辟空间,同时拥有了和实参a,b一样的内容,函数被调用时,形参可以理解为是实参的一份临时拷贝,函数结束,自动销毁,空间由操作系统回收。所占main() 函数结束之后我们整个程序就结束了。


4.2 传址调用

顾名思义,就是传地址调用,例如说,上图我们把&a,&b 的地址传过去,用指针接收,指针就有能力找到这两个变量的地址,并访问其中的内容,当然也有能力改变其中的内容,这个时候形参就不是实参的拷贝了。

C语言——初识函数_第10张图片

C语言——初识函数_第11张图片

void 就是空的意思,放在函数名前代表这个函数没有返回值。

如果函数没有写void ,且没有写返回值,函数默认返回一个值,有些编译器返回的是,最后一条语句产生的结果。例如: 函数最后 sum=A+B; 那么默认返回 sum;(要避免这种情况)


4.3 传值调调用,传址调用那个更好?

这两种方式都有属于自己的应用场景。

如果我们采用传值调用,实参传给形参,我们的形参也需要开辟和实参一样大的空间,例如:实参占20个字节,函数调用时,形参也会开辟20个字节的空间存储,需要准备一份额外的临时空间,这样会造成一定的空间上的浪费,从时间的角度来讲,传参也需要时间来接收的。但是,因为形参是一份独立的空间,所以形参的改变,不会对实参造成任何影响,我们的return 标识符 一次也只能返回一个值。这个应用场景结合实际来选择。

如果我们采用传址调用我们把实参的地址传给形参,&实参 的类型就是指针类型,所以我们形参要类型对齐,采用指针接收,指针有能力找到实参的地址,有能力访问或改变实参的值。且指针所占空间取决于实参的大小(非普通情况),对于数组之类的实参(一篇连续的空间),指针只需要存储数组首元素的地址,就可以访问整个数组元素,这个时候传值调用,冤大头,而且也无法对原数组进行操作。一个普通类型的指针在32位平台占4个字节,在64位平台占8个字节。


 五、函数的声明和定义

1. 函数在使用之前一定要声明,告诉编译器,函数名叫什么,返回类型是什么,参数是什么等等。

2. 函数的要满足先声明后使用的同时,一般放在头文件的下面 

 被调函数放在主函数下面:

#include 

int main()
{
	int a = 10;
	int b = 20;
	int s = 0;

int Sum(int,int);//函数的声明;
//int Sum(int A,int B);

	s = Sum(a,b);

	printf("%d",s);

	return 0;
}

int Sum(int A, int B)
{
	int sum = 0;
	sum = A + B;
	return sum;
}

这个时候,我们就需要在mian() 函数之前声明一下被调函数,因为编译器扫描代码的时候是从前往后扫描,在执行mian( )函数时,执行被调函数,编译器在扫描的时候遇到了声明,所以呢他就继续往后找,直到找到了被调函数,被调函数结束,返回主函数继续执行。这个时候如果没有在main() 函数之前声明,编译器就视为函数未定义,报错。

 被调函数放在主函数上面:

#include 

int Sum(int A, int B)
{
	int sum = 0;
	sum = A + B;
	return sum;
}

int main()
{
	int a = 10;
	int b = 20;
	int s = 0;

	s = Sum(a,b);

	printf("%d",s);

	return 0;
}

还是一样的道理,编译器顺序扫描,扫描的时候就已经知道了有这个被调函数,在主函数执行被调函数的时候,编译器就可以找的到被调函数的位置,就不会报错了。


六、函数的递归

6.1 递归简介

什么是递归呢? 简单来讲就是函数自己调用自己的编程技巧,只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量,递归可以把大型复杂的问题层层转化未一个与原问题相似的类型较小的问题来求解,我们在学习数据结构的时候会大量运用。

递归的主要思考方式在于:把大事化小。

递归也有缺点,就是空间复杂度大,函数调用函数再继续调用函数,每调用一次,会在栈区上开辟一块空间来维护,函数只有在结束的时候才会回收空间,直到递归结束前,是不是开辟了很多空间来维护,递归结束后,函数陆续回收。同一时间的空间复杂度就挺高的。

递归的两个必要条件:

1. 存在终止条件,当满足这个终止条件的时候,递归就不再继续。

2. 每次递归调用之后越来越接近这个限制条件(循环也是相同的道理)。

系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一
直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。


6.2 递归和非递归

6.2.1 求n的阶乘递归做法

C语言——初识函数_第12张图片

 画图分析:

C语言——初识函数_第13张图片

递归的缺陷:如果栈帧深度太深(递归的次数多),栈空间不够用(大概只有几兆)可能会溢出。 


 6.2.2 求n的阶乘非递归做法

C语言——初识函数_第14张图片

 那就是循环啦,准确的来讲是叫做迭代。

递归改非递归的方法:

  1. 直接改循环(简单一点的递归)
  2. 借助数据结构的“栈”模拟递归过程(复杂一点的递归)

至此C语言基本函数的知识博主已经分享完了,相信大家对这个函数的概念、使用有了一定的理解,大家可以自己动手敲敲代码,感受一下。

 本期收录于博主的专栏——C语言,适用于编程初学者,感兴趣的朋友们可以订阅,查看其它“C语言基础知识”。C语言_保护小周ღ的博客-CSDN博客

感谢每一个观看本篇文章的朋友,更多精彩敬请期待:保护小周ღ  *★,°*:.☆( ̄▽ ̄)/$:*.°★*  

下一篇:函数栈帧的创建和销毁

文章多处存在借鉴,如有侵权请联系修改删除!

你可能感兴趣的