本章重点
1、了解命令行参数
2、深入理解递归
什么是命令行参数呢?
C语言的命令行参数是指在程序运行时,通过命令行传递给程序的额外信息。当我们在终端或命令提示符中执行一个C语言程序时,可以在程序名后面添加参数,这些参数将被传递给程序并在程序内部使用。
在C语言中,命令行参数主要通过main函数的参数来接收。main函数也是一个函数,其实也可以携带参数的,标准的main函数定义如下:
main函数的参数包括argc和argv,分别表示命令行参数的数量和参数字符串数组。
- argc(argument count)是一个整数,表示命令行参数的数量,包括程序名称本身。至少会有一个参数,即程序的名称。
- argv(argument vector)是一个指向指针的数组,每个指针指向一个以空字符结尾的字符串,表示一个命令行参数。
例如,执行以下命令行:
在程序内部,argc的值将是3,argv数组将包含3个字符串指针:
- argv[0]指向字符串"./myprogram",表示程序的名称。
- argv[1]指向字符串"arg1",表示第一个命令行参数。
- argv[2]指向字符串"arg2",表示第二个命令行参数。
通过访问argc和argv,程序可以根据命令行参数的数量和内容,进行相应的逻辑处理。常见的用法包括:
- 读取命令行参数的值,可以使用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; }
深入理解递归
基本认识
- 递归本质也是函数调用,
- 函数调用本质就要形成和释放栈帧
- 根据栈帧的学习,调用函数是有成本的,这个成本就体现在形成和释放栈帧上:时间+空间
- 所以,递归就是不断形成栈帧的过程
理论认识
- 内存和CPU的资源是有限的,也就决定了,合理的递归是绝对不能无限递归下去,合法递归一定是有次数限制的
- 递归不是什么时候都能用,而是要满足自身的应用场景 即:目标问题的子问题,也可以采用相同的算法解决,本质就是分治的思想
- 核心思想:大事化小+递归出口
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; }
运行结果
- 如果数字再大,就非常非常慢
- 为何会这么慢呢?根本原因是因为大量的重复计算
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; }
运行结果:
这种方法就是一种典型的迭代方法,空间和时间上消耗的成本都非常低。