C语言——函数

一、函数是什么

  • 先说一个容易理解的,在数学中,函数简单来说可以理解为一种法则,输入一个或多个数,通过该法则(函数)输出一个结果,如f(x)=2x这个函数的作用就是把你输入的数乘2,输入x=3,输出结果就是3*2=6;
  • 而在C语言中,和数学中理解类似,维基百科给出的定义是:是一个大型程序中的某部分代码,由一个或多个语句块组成,它负责完成某些特定任务,而相较于其他代码,又具备相对的的独立性,一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏
    通俗来说,C语言当中的函数负责处理某些任务,比如求一些数中的最大值,这个“特定任务”可以类比上述数学中的法则;这里的“输入参数”就是你要处理的数据,可以理解为想要对什么进行操作;你希望看到对参数的操作结果,所以函数可以有“返回值”将操作结果返回;这里说的“封装”可以理解为把函数内部的实现细节全部都包装起来,使用的时候不需要知道内部运行逻辑,只要知道什么功能,能得到需要的结果就行,比如打印函数printf、输入函数scanf使用者只需要知道能打印,能输入,不用关心里面细节如何实现的,使用的时候直接调用,这也体现了函数的优点

二、C语言中函数的分类

2.1 库函数

2.2.1 为什么会有库函数

  • 在学习C语言的过程中,当你写完一个代码总是迫不及待想知道运行结果,想把运行结果打印到屏幕上看一看,这里就需要频繁使用一个功能:将信息按照一定格式打印到屏幕上(printf)

  • 在编程过程中我们也会频繁的进行输入操作(scanf)

  • 在编程过程中也会频繁进行拷贝字符串操作(strcpy)
    像上面所描述的基础功能,我们在开发过程中总能用得到,为了提高编程效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发

  • 注:使用库函数要包含相应的头文件,如使用printf要包含stdio.h的头文件#include<stdio.h>

2.2.2 如何学习库函数

这里分享一个学习库函数的网站Cplusplus

  • 打开网站后是该网站的新版本,有一个缺点是无法搜索,点击legacy version回到旧版

  • 回到旧版,这里就是搜索功能,你可以搜索你想要学习的库函数
    在这里插入图片描述

  • 举例——strcpy
    下面是我拿到一个陌生的函数的学习方法

(1)char * strcpy ( char * destination, const char * source );
首先,这里介绍了该函数的参数类型和返回类型,都是char*,也就是指针类型
通过这里,我们已经知道使用该函数需要传地址给它
(2)第二步,该函数下面有着详细的功能介绍,通过这一步,我们可以了解到该函数具有什么样的功能(本人英语不好,所以直接借助翻译工具翻译,需要注意,有时候翻译不会表达很全面,需要你自己去判断)
翻译序号2的第一段:将源(source)指向的C字符串复制到目标(destination)指向的数组中,包括终止空字符(并在该点停止);
这里可以看出,该函数的作用是将source指向变量里的字符串,拷贝到destination指向的的数组当中
到这一步,已经对这个函数的功能了解的较为全面了
(3)Parameters:介绍参数
在Parameters下面,对该函数的参数进行了详细的介绍
destination:指向要复制内容的目标数组的指针
source:要复制的 C 字符串
(4)Return Value:返回值
“destination is returned”也就是将指针destination返回,即返回destination所指向变量的地址
到这一步,相信你对该函数的了解已经很全面了
(5)左侧栏灰色部分也就是你使用该函数需要包含的头文件(string.h)
(6)Example下面为示例代码,点击Edit&run可以直接运行示例代码,运行结果即为output所显示内容
(7)see also为功能相近的一些函数
(8)这一栏展示的是string.h头文件下可使用的函数

通过以上学习,可以尝试着自己使用一下,如果能够达到预期的效果,那么恭喜你已经学会了
在这里插入图片描述
在这里插入图片描述

2.2 自定义函数

2.2.1 为什么会有自定义函数?

因为C语言支持的库函数只是基本的一些常用的函数,不可能提供所有的函数,所以有些功能需要程序员自己来设计,这也给了我们很大的发挥空间

2.2.2 函数的组成

自定义函数和函数一样,有函数名,返回值类型,函数参数

ret_type fun_name(int x )
{
	statement;//语句项
}
//ret_type 返回类型
//fun_name 函数名
//(---)括号里面放的是函数的参数,即int x
//函数最外层的{}里面是函数体

例如:写一个函数可以找出两个整数的最大值

#include<stdio.h>
//求两个整数最大值
int max(int x,int y)
{
	return(x>y>?x:y;//返回整形,所以函数类型为整形
}
//其中函数名为max,函数类型为int,函数参数为int x,int y
//返回类型(整形)确定函数类型(整形),输入的参数“num1,num2”(整形)确定函数的参数类型(整形)
int main()
{
	int num1,num2;
	scanf("%d %d",&num1,&num2);
	int MaxNum=max(num1,num2);
	printf("%d",MaxNum);
	return 0;
}

三、函数的参数

3.1 实际参数(实参)

真实传递给函数的参数,叫做实参
实参可以是:常量、变量、表达式、函数等
max(a,b); //变量
max(a,3); //变量常量
max(2+5,8-1); //表达式
max(a,max(2,b)); //函数
无论实参是何种类型的值,在进行函数调用时,都必须有确定的值,以便把这些值传递给形参

3.2 形式参数(形参)

形式参数指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存),未被调用时不存在(只是形式上存在),所以叫形参
形参当函数调用结束就自动销毁,因此形参只在函数中有效

四、函数的调用

4.1 传值调用

  • 函数的形参和实参分别占用不同内存块,形参是实参的一种临时拷贝,对形参的修改不会影响实参;
  • 就好比你去拍照,照片和你分别占用不同空间,照片相当于你的一份复制品,对照片的修改不会影响到你本身

示例代码

交换两个整形的错误写法
#include<stdio.h>
void Swap(int x, int y)
{
	int temp = x;
	x = y;
	y = temp;
	printf("x=%d\n", x);
	printf("y=%d\n", y);

}
int main()
{
	int a, b;
	scanf("%d %d", &a, &b);
	Swap(a, b);//仅仅是把a,b的值传到Swap里面,传给x,y而ab本身不会发生变化
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	return 0;
}

4.2 传址调用

  • 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式
  • 这种传参方式可以让函数和函数外部变量通过指针建立起真正的联系,也就是函数可以通过地址来直接操作外部变量
  • 这就好比你传给我地址后,那么我就可以通过你给我的地址找到目的地,该目的地必然是和你的地址一致,所以通过该地址可以直接对目的地的内容进行操作
//交换a,b内容的正确写法
#include<stdio.h>
void Swap(int* x, int* y)//a,b地址是指针类型,所以是int*
{
	int temp = *x;//注意*与&的区别,&x的意思是x的地址,*x是x里面的内容
	*x = *y;
	*y = temp;
	printf("x=%d\n", *x);
	printf("y=%d\n", *y);
	//真正发生变化的只有a,b,temp,而xy的作用仅仅是找到ab并对其进行操作
}
int main()
{
	int a, b;
	scanf("%d %d", &a, &b);
	Swap(&a, &b);//因为需要通过函数对ab进行操作,所以需要传ab的地址,即&a,&b
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	return 0;
}

在这里插入图片描述

通过上图调试界面可以看出,x,y指向的地址即为a,b的地址,这就是传址调用

  • 注意:&a的含义是a的地址,*x的含义是指针x指向内存单元里的内容

五、函数的嵌套调用和链式访问

5.1 嵌套调用

嵌套调用就是在一个函数内调用另一个函数
注意:函数可以嵌套调用,但不能嵌套定义,注意二者区别

  • 嵌套调用
void print()
{
	printf("hello");
}
void test()
{
	for(int i=1;i<10;i++)
	{
		print();//嵌套调用
	}
}
  • 嵌套定义(错误写法)
void test()
{
	for(int i=1;i<10;i++)
	{
			void print() //嵌套定义
			{
				printf("hello");
			}
	}
}

5.2 链式访问

  • 把一个函数的返回值作为另一个函数的参数就叫做链式访问
//举个简单的例子,printf函数的返回值是打印字符的个数
#include<stdio.h>
int main()
{
	printf("%d",printf("%d",123456));//输出结果为1234566
	//先将123456打印出来,1~6字符个数为6个,故printf("%d",123456)值为6
	return 0;
}

六、函数的声明和定义

6.1 函数的声明

  • 函数的声明就是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但是这个函数具体存不存在,函数声明决定不了
  • 函数的声明一般出现在函数使用之前,要满足先声明后使用
//因为ADD函数的定义在主函数之后,编译器编译时是从上至下扫描的,若不声明,
//则扫描到主函数里的ADD时,ADD的定义未被扫描到,编译器会警告,为了不让编译器警告,则需在开始时声明一下
//保证先声明再使用,函数的声明一般放在头文件中
// 定义也是一种特殊的声明,比声明更彻底,所以写代码时函数定义尽量写在前面
int ADD(int x,int y);//函数的声明,声明时形参变量名字可以不写
//int ADD(int,int)也可
int main()
{
	int a = 1;
	int b = 1;
	int c = ADD(a, b);
	printf("%d", c);
}
int ADD(int x, int y)
{
	return x + y;
}
  • 上述声明是放在同一源文件中,函数的声明一般要放在头文件中,如下
    在这里插入图片描述

注:在头文件声明后,想使用就要包含一下该头文件,#include"add.h"的意义相当于把add.h头文件里的内容拷贝了一份到该源文件

七、函数递归

7.1 什么是递归

程序调用自身的编程技巧称为递归
它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
只需少量的程序就可以描述出解题过程中所需要多次重复计算,大大减少了程序的代码量
注:递归编写效率高,但机器运行效率不一定高
递归主要思考方式在于:把大事化小

  • 举个例子
//史上最简单的递归
//注意该递归为死递归,这些不用关心,到这一步只需了解什么是递归
#include<stdio.h>
int main()
{
	printf("hello\n");
	main(); //调用自身
	return 0;
}

7.2 死递归导致程序崩溃的原因

递归就是递推与回归,递推一次就是调用一次自身的过程,而调用函数就会在内存中的栈区分配空间,并且只有回归的时候才会收回空间,回归就是最后一次递推后面的操作,若一直永无休止的递推必然不会有回归,则不能回收空间,而内存空间是有限的,则必然会产生栈溢出,从而程序崩溃;
大家可以运行上面的代码尝试一下,报错信息显示Stack overflow就是栈溢出的意思

7.3 递归的两个必要条件

  • 存在限制条件,当满足这个限制条件的时候,递归便不在继续

如果没有限制条件就像上述代码一样,一致递推从而产生死递归

  • 每次递归调用之后越来越接近这个限制条件

如果不接近该条件,那么由于一直不满足条件,或者离该条件越来越远,也会一致递推从而产生死递归

  • 示例代码
#include<stdio.h>
//接受一个无符号整形,按照顺序打印每一位
//如输入:1234 输出:1 2 3 4
void print(int x)
{
	if (x > 9) //限制条件
	{
		print(x / 10); //此操作后递归调用之后越来越接近大于9这个限制条件
	}
	printf("%d ", x % 10);
}
int main()
{
	int num;
	scanf("%d", &num);
	print(num);
	return 0;
}
  • 一图胜千言,下面是该程序的思路以及递归过程
    在这里插入图片描述
  • 再来一道题:编写函数不允许创建临时变量,求字符串长度

拿到递归的题目,首先得细细先分析一下:
第一步,先确定函数的参数类型返回类型,参数由调用函数时传给函数的东西有关,因为求的是“字符串”长度,所以调用函数时传递的东西必然是数组名,而数组名是该数组首元素地址,所以函数的参数为字符类型的指针类型,即char*,而函数的返回类型由这个函数的功能确定,因为要求字符串的 “长度”,长度是整形, 故函数的返回类型为int型
第二步,分析递归过程,比如求abcd的字符串长度,字符串长度为首个"\0"之前的字符串长度,所以abcd相当于abcd’\0’,由递归的两个必要条件可以知道,递归必须要有限制条件,则这里的’\0’的位置就是限制条件,当找到’\0’时,递归过程就开始回归,也就是不在调用函数,可以使用一下方式递归:首先判断第一个字符是否为’\0’,若是,则返回0(字符串长度为0),若不是,则返回1+第二个位置及其往后的字符串长度,第二个位置的地址就是arr+1(arr为接受字符串的数组),这里就出现了可递归的地方,也就是return(1+my_strlen(arr+1))(假设递归函数为my_strlen())
以下为题目代码

//递归求字符串长度
#include<stdio.h>
#define length 100 //定义输入字符串长度上限为100
int my_strlen(char* arr2)
{
	if (arr2[0] == '\0')  //判断首元素是否为空
	//或*arr2=='\0',*arr2为arr2当前指向的字符,即首元素内容
	{
		return 0;
	}
	else
	{
		return 1 + my_strlen(arr2 + 1);  
		//不为空+1(因为确定首元素非空了)后继续递归
		//arr2+1为arr2数组第二个位置元素的地址
	}
}
int main()
{
	char arr[length];
	scanf("%s", arr); //输入字符串内容
	printf("%d", my_strlen(arr)); //打印字符串长度
	return 0;
}

递归过程
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值