【深入理解递归、了解命令行参数】

本章重点

1、了解命令行参数

2、深入理解递归

什么是命令行参数呢?

        C语言的命令行参数是指在程序运行时,通过命令行传递给程序的额外信息。当我们在终端或命令提示符中执行一个C语言程序时,可以在程序名后面添加参数,这些参数将被传递给程序并在程序内部使用。

        在C语言中,命令行参数主要通过main函数的参数来接收。main函数也是一个函数,其实也可以携带参数的,标准的main函数定义如下:

main函数的参数包括argcargv,分别表示命令行参数的数量和参数字符串数组。

  • argc(argument count)是一个整数,表示命令行参数的数量,包括程序名称本身。至少会有一个参数,即程序的名称。
  • argv(argument vector)是一个指向指针的数组,每个指针指向一个以空字符结尾的字符串,表示一个命令行参数。

 例如,执行以下命令行:

在程序内部,argc的值将是3,argv数组将包含3个字符串指针:

  • argv[0]指向字符串"./myprogram",表示程序的名称。
  • argv[1]指向字符串"arg1",表示第一个命令行参数。
  • argv[2]指向字符串"arg2",表示第二个命令行参数。

通过访问argcargv,程序可以根据命令行参数的数量和内容,进行相应的逻辑处理。常见的用法包括:

  • 读取命令行参数的值,可以使用argv数组的索引来访问特定位置的参数字符串。
  • 对命令行参数进行解析和处理,例如将字符串转换为数字、进行条件判断等。
  • 根据命令行参数的值来执行不同的程序逻辑,例如根据参数选择不同的操作或配置。

总之,命令行参数为C语言程序提供了一种从外部传递信息的方式,使得程序在运行时可以根据不同的参数执行不同的操作。 

#include<stdio.h>
int main(char argc, char* argv[])
{
	int i = 0;
	for (i = 0; i < argc; i++)
	{
		printf("%s", argv[i]);
	}
}

运行结果

注意:argv数组的最后一个元素存放了一个 NULL 的指针。

vs如何带命令行参数呢?

运行结果

这样我们就给程序带上了命令行参数。其实呢,main函数是有三个参数的,只不过第三个参数通常不写。

第一个参数: argc 是个整型变量,表示命令行参数的个数(含第一个参数)。

第二个参数: argv 是个字符指针的数组,每个元素是一个字符指针,指向一个字符串。这些字符串就是命令行中的每一个参数(字符串)。

第三个参数: envp 是个字符指针的数组,数组的每一个元素是一个指向一个环境变量(字符串)的字符指针 

了解:第三个参数本质其实是获取系统相关环境变量内容,下面的程序就可以输出相关的环境变量。

#include <stdio.h>
int main(int argc, char* argv[], char* envp[])
{
	int i = 0;
	while (envp[i] != NULL)
	{
		printf("%s\n", envp[i]);
		i++;
	}
	return 0;
}

深入理解递归

基本认识 

  1. 递归本质也是函数调用,
  2. 函数调用本质就要形成和释放栈帧 
  3. 根据栈帧的学习,调用函数是有成本的,这个成本就体现在形成和释放栈帧上:时间+空间 
  4. 所以,递归就是不断形成栈帧的过程 

理论认识 

  1. 内存和CPU的资源是有限的,也就决定了,合理的递归是绝对不能无限递归下去,合法递归一定是有次数限制的
  2. 递归不是什么时候都能用,而是要满足自身的应用场景                                            即:目标问题的子问题,也可以采用相同的算法解决,本质就是分治的思想 
  3. 核心思想:大事化小+递归出口

demo1:不使用临时变量,求字符串长度

#include<stdio.h>
/*
	abcd1234
	1+bcd1234
	1+1+cd1234
	...
*/
int my_strlen(const char* arr)
{
	if (!*arr)
		return 0;//递归出口
	else
		return 1 + my_strlen(arr + 1);//大事化小
}
int main()
{
	char arr[] = "abcd1234";
	printf("%d\n", my_strlen(arr));
	return 0;
}

完善程序

#include <stdio.h>
#include <assert.h>
int my_strlen(const char* s)
{
	assert(s != NULL);
	return *s ? (MyStrlen(++s) + 1) : 0;
}
int main()
{
	const char* str = "abcd1234";
	int len = my_strlen(str);
	printf("len: %d\n", len);
	return 0;
}

        功能上,代码增加了一个断言,用于确保传入my_strlen函数的指针不为空。断言是一种在代码中放置的检查,用于在运行时验证某个条件是否为真。在这里,assert(s)用于验证传入的指针s不为空(即不能为NULL)。如果断言条件为假,程序会终止,并输出错误信息。这样可以帮助在调试时快速发现可能出现的问题。

同归斐波那契数列,来看递归调用的成本问题

摘自百科

        斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、 34、……在数学上,斐波那契数列以如下被以递推的方法定义:

F(0)=0,

F(1)=1,

F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)

demo1:初步感受一下计算fib(42)结果出来的所需时间

#include <stdio.h>
int Fib(int n)
{
	if (1 == n || 2 == n) {
		return 1;
	}
	return Fib(n - 1) + Fib(n - 2);
}
int main()
{
	int n = 42;
	int x = Fib(n);
	printf("Fib(%d): %d\n", n, x);
	return 0;
}

通过上面的代码做实验我们发现,fib递归版在个数超过几十(我们这里是42)的时候,就已经相当慢了。

demo2:递归计算fib(42)需要多少时间

#include <stdio.h>
#include <windows.h> 
//包含该头文件,才能使用win提供的GetTickCount()函数,\
来获取开机到现在的累计时间,此处用它,是因为简单
int Fib(int n)
{
	if (1 == n || 2 == n) {
		return 1;
	}
	return Fib(n - 1) + Fib(n - 2);
}
int main()
{
	int n = 42;
	double start = GetTickCount();
	int x = Fib(n);
	double end = GetTickCount();
	printf("Fib(%d): %d\n", n, x);
	printf("%lf s\n", (end - start) / 1000); 
	return 0;
}

运行结果

 现在我们画图示意一下fib(6)需要算什么?

fib(7)呢? 

  •  如果数字再大,就非常非常慢
  • 为何会这么慢呢?根本原因是因为大量的重复计算

demo3:统计一下n=3的时候,被重复计算了多少次

#include <stdio.h>
#include <windows.h>
int count = 0;
int Fib(int n)
{
	if (1 == n || 2 == n) {
		return 1;
	}
	if (n == 3) {
		count++; //不影响运算逻辑,就单纯想统计一下n=3的时候,被重复计算了多少次
	}
	return Fib(n - 1) + Fib(n - 2);
}
int main()
{
	int n = 42;
	double start = GetTickCount();
	int x = Fib(n);
	double end = GetTickCount();
	printf("fib(%d): %d\n", n, x);
	printf("%lf s\n", (end - start) / 1000);
	printf("count = %d\n", count);
	return 0;
}

运行结果:

为何会这样??重复计算,意味着重复调用,重复调用意味着重复形成栈帧,创建与释放栈帧,是有成本的。

demo4:如何解决?----- 迭代

#include <stdio.h>
#include <windows.h>
int Fib(int n)
{
	int* dp = (int*)malloc(sizeof(int) * (n + 1)); //暂时不做返回值判定了
	//[0]不用(当然,也可以用,不过这里我们从1,1开始,为了后续方便)
	if (dp == NULL)
		return -1;
	dp[1] = 1;//第一个数
	dp[2] = 1;//第二个数
	for (int i = 3; i <= n; i++) {
		dp[i] = dp[i - 1] + dp[i - 2];
	}
	int ret = dp[n];
	free(dp);
	return ret;
}
int main()
{
	int n = 42;
	double start = GetTickCount();
	int x = Fib(n);
	double end = GetTickCount();
	printf("fib(%d): %d\n", n, x);
	printf("%lf ms\n", (end - start) / 1000);
	return 0;
}

运行结果:

运行所需时间接近0.0s,只形成一次函数栈帧,消耗的成本非常低。这其实是一种动态规划的算法哦,看起来很神秘,其实这些问题也可以使用改方法解决哦。

demo5:上面的程序保存了每一个fib()的值,是通过空间消耗来节省时间,那我们还能更进一步吗?经过发现,任何一个数字,只和前两个数据相关

#include <stdio.h>
#include <windows.h>
int Fib(int n)
{
	int first = 1;
	int second = 1;
	int third = 1;
	while (n > 2) {
		third = second + first;
		first = second;
		second = third;
		n--;
	}
	return third;
}
int main()
{
	int n = 42;
	double start = GetTickCount();
	int x = Fib(n);
	double end = GetTickCount();
	printf("fib(%d): %d\n", n, x);
	printf("%lf s\n", (end - start) / 1000);
	return 0;
}

运行结果:

这种方法就是一种典型的迭代方法,空间和时间上消耗的成本都非常低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值