【手撕C语言 第七集】函数(下)

本文详细介绍了C语言中函数的嵌套调用、链式访问、函数声明与定义,以及递归的概念、必要条件和实例,包括阶乘和斐波那契数的递归计算,同时指出递归在处理复杂问题时的效率问题和迭代替代方案。
摘要由CSDN通过智能技术生成


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

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。

1.嵌套调用

#include <stdio.h>
void new_line()
{
	printf("hehe\n");
}
void three_line()
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		new_line();
	}
}
int main()
{
	three_line();
	return 0;
}

函数可以嵌套调用,但是不能嵌套定义

2.链式访问

把一个函数的返回值作为另外一个函数的参数。

#include <stdio.h>
#include <string.h>
int main()
{
	char arr[20] = "hello";
	int ret = strlen(strcat(arr, "bit"));//这里介绍一下strlen函数
	printf("%d\n", ret);
	return 0;
}
#include <stdio.h>
int main()
{
	printf("%d", printf("%d", printf("%d", 43)));
	//结果是啥?
	//注:printf函数的返回值是打印在屏幕上字符的个数
	return 0;
}

在这里插入图片描述

结果是4321

六、函数的生命和定义

1.函数声明

  1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数
    声明决定不了。
  2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
  3. 函数的声明一般要放在头文件中的。

2.函数定义

函数的定义是指函数的具体实现,交待函数的功能实现。

无论是变量还是函数,都得满足先声明后使用。

一般情况下:
add.h文件 放函数的声明
add.c文件 放函数的定义
test.c文件 (#include “add.h”)里面可以调用函数
这样写可以将代码进行封装和隐藏。
在这里插入图片描述

test.c中需要使用某个函数,某人卖给他使用,但不暴露函数实现的源代码,某人将add模块生成静态库add.lib,将add.h文件和静态库add.lib卖给别人使用。别人使用add.h文件和test.c文件,并在test.c文件中写上以下这样的代码:
#pragma comment(lib,“add.lib”)
#incluide “add.h”
即可使用

七、函数递归

1.什么是递归?

程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的
一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小

通俗来说,递归就是自己调用自己
每一次函数调用都要在栈区申请空间,当栈区空间耗干,就会出现栈溢出(Stack overflow)

2.递归的两个必要条件

存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。

3.练习

(1)

接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4
在这里插入图片描述

#include <stdio.h>
void print(int n)
{
	if (n > 9)
	{
		print(n / 10);
	}
	printf("%d ", n % 10);
}
int main()
{
	int num = 1234;
	print(num);
	return 0;
}

(2)

编写函数不允许创建临时变量,求字符串的长度

思路:当发现指向的第一个字符不是’\0’时,长度为1,字符串的长度是1+其余字符串的长度。
在这里插入图片描述

#include <stdio.h>
int Strlen(const char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + Strlen(str + 1);
}
int main()
{
	char* p = "abcdef";
	int len = Strlen(p);
	printf("%d\n", len);
	return 0;
}

注意点
在这里插入图片描述

因为++是后置,已经把a的地址传进去了,str再++,str++后的结果留给当前函数了。当调用my_strlen时,新的my_strlen函数2的str还是指向a的,之后再调用的其他my_strlen中的str都是指向a的。所以如果写成str++,直接死递归了。

🎗️所以,在递归中尽量不要写成前置++,或者后置++,会带有副作用

写成str+1,就是把下一个字符的地址传进去了,str没动
写成str++,把下一个字符地址传进去了,str也动了,如果递归回来,还想使用这个地址的话,而这个地址已经变了,就有问题了。

4.递归与迭代

练习

(1)

求n的阶乘。(不考虑溢出)
在这里插入图片描述

int factorial(int n)
{
if(n <= 1)
return 1;
else
return n * factorial(n-1);
}
(2)

求第n个斐波那契数。(不考虑溢出)

🎗️斐波那契数列:
1 1 2 3 5 8 13 21 34 55 …
特点:当前位置的值是前两个数相加的和。

在这里插入图片描述

这样写可以解决问题,但是当我们输入的n值比较大时,比如50,在程序的运行窗口迟迟计算不出来结果,为什么呢?
探究
在这里插入图片描述
接下来我们可以统计一下Fib(3)的调用次数
代码如下:

#include<stdio.h>
int count = 0;
int Fib(int n) {
	if (n == 3) {
		count++;  //统计的是第三个斐波那契数被重复计算的次数
	}
	if (n <= 2) {
		return 1;
	}
	else {
		return Fib(n - 1) + Fib(n - 2);
	}
}
int main() {
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\n", ret);
	printf("count=%d\n", count);
	return 0;
}

在这里插入图片描述

由此发现,递归虽然能解决问题,但是它的效率比较低。

换个思路
在这里插入图片描述
用迭代的思路,一直用a+b赋值给c,如果计算的结果c过大会溢出,所以范围有限。

提示:

  1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

5.推荐题目

函数递归的几个经典题目(自主研究):

  1. 汉诺塔问题
  2. 青蛙跳台阶问题
  • 17
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C语言中,没有内置的ArrayList数据结构,但是我们可以通过使用指针和动态内存分配来实现类似ArrayList的功能。 下面是一个简单的示例代码,展示了如何在C语言中创建一个类似ArrayList的数据结构: ```c #include <stdio.h> #include <stdlib.h> typedef struct { int* data; // 存储数据的数组 int size; // 当前存储的元素个数 int capacity; // 数组的容量 } ArrayList; // 初始化ArrayList void init(ArrayList* list) { list->data = NULL; list->size = 0; list->capacity = 0; } // 向ArrayList中添加元素 void add(ArrayList* list, int element) { if (list->size == list->capacity) { // 如果数组已满,则扩展数组的容量 int newCapacity = (list->capacity == 0) ? 1 : list->capacity * 2; int* newData = (int*)realloc(list->data, newCapacity * sizeof(int)); if (newData == NULL) { printf("内存分配失败\n"); return; } list->data = newData; list->capacity = newCapacity; } list->data[list->size] = element; list->size++; } // 获取ArrayList中指定索引的元素 int get(ArrayList* list, int index) { if (index < 0 || index >= list->size) { printf("索引越界\n"); return -1; } return list->data[index]; } // 释放ArrayList占用的内存 void freeArrayList(ArrayList* list) { free(list->data); list->data = NULL; list->size = 0; list->capacity = 0; } int main() { ArrayList list; init(&list); add(&list, 10); add(&list, 20); add(&list, 30); printf("元素个数:%d\n", list.size); printf("第一个元素:%d\n", get(&list, 0)); printf("第二个元素:%d\n", get(&list, 1)); printf("第三个元素:%d\n", get(&list, 2)); freeArrayList(&list); return 0; } ``` 这段代码演示了如何使用指针和动态内存分配来创建一个类似ArrayList的数据结构。你可以通过调用`add`函数向ArrayList中添加元素,通过调用`get`函数获取指定索引的元素。在使用完ArrayList后,记得调用`freeArrayList`函数释放占用的内存。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值