详解C语言函数篇 + 递归原理

在这里插入图片描述

前言

函数是什么,在我们初识C语言的时候,开始独立编写自己的一个模块,完成自己的任务,必然离不开函数,在工程中函数是一个分模块的概念,但在了解函数的基础上,进一步的认识你真的知道函数是什么吗? 如果不太清楚请腾出闲暇时间了解一下作者的博客吧,必然会让你有所收获

如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间。这段内存空间有一个起始地址,也称为函数的入口地址。函数名代表函数的起始地址。调用函数时,从函数名得到函数的起始地址,并执行函数代码。”


提示:以下是本篇文章正文内容,下面案例可供参考

函数定义

一:函数定义
函数定义就是函数体的实现,函数体就是一个代码块,他在函数被调用时被执行,函数的定义语法分为:

类型
函数名(形式参数)
函数体
函数返回值

int main(void)
{
		//函数体内存放代码块
	return 0;
}

函数定义的另外一种写法

这种声明形式现在仍为标准所允许,主要是为了让较老的程序无需修改就可以通过编译,但我们应该提倡新的声明风格,理由由二:他消除了旧式风格的冗余,其次,也是最重要的一点,它允许函数原型的使用,提高了编译器在函数调用时检查错误的能力

#include<stdio.h>

int *	//返回值为整形指针类型,注意返回值类型并没有与函数名写在同一行
find_int(int key,int array[],int array_len)
{
	int i = 0;
	
	for(i = 0; i < array_len; i++)
	{
	/*
	检查这个位置的值是否是要被查找的值
	*/
		if(array[i] == key)
		{
			return &array[i];  //如果找到了就返回此元素对应的地址
		}
		
	}
	return NULL; // 没有找到就返回空
}

以上只是对函数的一个简单的了解,函数究竟能干什么能不能为我们带来便捷,看具体功能接下来逐步讲解

函数具体使用

函数功能介绍:找出数组中的指定元素key,并且返回该元素的索引下标

#define _CRT_SECURE_NO_WARNINGS  //微软安全检查
#include<stdio.h>

int functest(int arr[], int len, int key);   //函数声明,后面讲解
//空格
int main()
{
	int len = 0;	 //数组长度
	int key = 0;	//需要查找的键值
	int arr[] = { 1,2,3,4,5,6,7,8,9 }; // 初始化数组
	len = sizeof(arr) / sizeof(arr[0]);//求元素字节
	scanf("%d", &key);
	int ret = functest(arr, len, key);//函数调用,返回值由变量ret接受
	printf("%d",ret);

	return 0;
}
//良好的编程习惯是没有关联关系的代码在中间可以加空格
int functest(int arr[], int len, int key)//函数的具体实现
{

	int i = 0;
	for (i = 0; i < len; i++)
	{
		if (arr[i] == key)
		{
			return i;//返回指定元素的下标
		}
	}

}

用到的知识点:

函数返回值
函数之间的值传递和地址传递
函数返回值

隐藏的细节 len = sizeof(arr) / sizeof(arr[0]) 为什么要求出结果值后当作参数传递,为什么不能在int functest(int arr[], int len, int key) 直接求出数组的总长度,直接参与计算,不要着急后面讲解

return语句

当执行到达函数定义的末尾时,函数就会将执行玩的结果返回(当然这个前提是函数返回值不是void),也就是所函数执行完任务的时候会将返回的结果带回给主调函数,当然return语句可以在任意的位置,但是一个函数只会执行一次return语句,通常情况下,return语句表达式的类型就是函数声明的返回类型,只有当编译器可以通过寻常算术转换把表达式的类型转换为函数声明时的类型,才允许返回类型和函数声明的返回类型不同的表达式

总结:
1、return语句可以有多条,但是return语句只会被执行一次

2、返回值类型与函数声明返回值类型不同时,编译器会将其隐士类型转换为函数声明时的类型

函数声明

当编译器遇到一个函数时,它产生代码传递参数并调用这个函数,而且接受该函数的返回的值(如果有的话)但编译器时如何知道该函数是什么类型什么返回值,多少参数数量的呢,其实向编译器提供函数的信息的方法时使用函数原型,原型总结了函数定义部分的起始部分的声明,向编译器提供该函数如何被调用的完整信息,使用原型最方便的做法是将函数原型置于一个.h(头文件中),如果其他文件需要引用直接调用就好。

在这里插入图片描述
函数原型
在这里插入图片描述

使用这钟方式的好处是为了能够便于维护和管理

函数的参数,值传递与址传递

C语言的函数均以 “传值调用” 方式进行传递,这就意味着函数将获得参数值的一份拷贝,这样函数就可以放心修改这个拷贝值,而不必担心修改调用程序实际传递给他的参数,但是,如果被传递的参数是一个数组名,并且在函数中使用数组下标修改数组的元素值就会反馈到调用函数处的信息,这个行为被称为 ”传址调用“

以下展示传值调用和传递至调用的区别

//先来看传值调用  
#include<stdio.h>
#include"func.h"

void Swap(int val_1, int val_2) //值传递
{
	int tmp = val_1;
	val_1 = val_2;
	val_2 = tmp;
	printf("val1_1 = %d,val_2 = %d\n", val_1,val_2);
}

int main()
{
	int a = 10, b = 20;

	Swap(a, b);
	printf("a = %d,b = %d\n", a, b);
}

运行结果可以看出值传递改变的只是实参传递给形参的一份临时拷贝数据,但是这个临时拷贝的数据并不会影响到我们的主程序

在这里插入图片描述
再来看我们的地址传递

#include<stdio.h>
#include"func.h"

void Swap1(int *val_1, int *val_2) //地址传递
{
	int tmp = *val_1;
	*val_1 = *val_2;
	*val_2 = tmp;
	printf("val1_1 = %d,val_2 = %d\n", val_1, val_2);
}

int main()
{
	int a = 10, b = 20;

	Swap(a, b);
	printf("a = %d,b = %d\n", a, b);
}

实际观察

在这里插入图片描述

F11进入Swap函数

在这里插入图片描述

函数被调用完回到主调函数

在这里插入图片描述

总结:
1、值传递只是一份实参传递给形参的临时拷贝,临时变量的修改不会直接反馈到主调函数
2、址传递之所以能够修改实参变量的值是因为传递进去的是变量的地址,得到变量的地址进而再修改变量是会直接影响的

以下小知识有兴趣可以了解一下,笔者也愿意将它分享出来
在这里插入图片描述

函数递归

一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。

空白的简述不适于我们理解,下面上实例给看大家详说递归的细节,举一道题目
编写一个函数 reverse_string(char * string)(递归实现)

  • 实现:将参数字符串中的字符反向排列,不是逆序打印。

  • 要求:不能使用C函数库中的字符串操作函数。

  • 比如: 逆序之后数组的内容变成:fedcba

//这是原字符串
char arr[] = "abcdef";

解题思路

以递归的方式将首字符与尾字符,逐个交换,将下标为0的第一个元素的值存入临时变量中,末尾字符给\0,以此控制字符串长度不断缩减,使得不断的接近递归的终止条件,每层递归都会存放tmp值至栈中(也叫现场值),等带递归终止后,挨个返回回去,把栈中的值取出来,放到目标位置处

递归原理图

在这里插入图片描述
在这里插入图片描述





#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<assert.h>
int my_strlen(const char *ptr) 
{
	int count = 0;


	while (*ptr != '\0') 
	{
		count++;
		ptr++;
	}
	return count;
}


void reverse_string(char * string) 
{
		assert(string);
		char *p = string;


		int len = my_strlen(p) - 1;
		char tmp = p[0];
		p[0] = p[len];
		p[len] = '\0';


		if (my_strlen(p + 1) > 1)
		{
			
			reverse_string(p + 1);
		}


		p[len] = tmp;


		return ;
	
}
int main()
{
char str[] = "abcdef";
reverse_string(str);
puts(str);
  return 0;
}





以上就是今天要讲的内容

总结

提示:这里对文章进行总结:
1、函数递归是什么,递归就是自己调用自己,递归不能一直递归,需要终止条件(出口)
2、return语句可以有多条,但是只会被执行一句return语句
3、实参传递给形参:使用传值的方式,形参的改变不会影响到实参,而传地址的方式,形参的改变会直接影响到主调函数
4、函数的声明必须在最前面,这是提供给编译器的一个信息

在这里插入图片描述

  • 20
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱生活,爱代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值