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

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

本章重点


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;
}


深入理解递归


基本认识

  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;
}


运行结果:


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

相关文章
函数递归(详细解读)(下)
函数递归(详细解读)(下)
|
1月前
函数的递归
函数的递归
16 0
|
5月前
|
C语言
C语言函数递归详解:理解递归的原理与应用
C语言函数递归详解:理解递归的原理与应用
105 0
|
6月前
|
C语言
函数递归.
这篇内容介绍了递归的概念,指出在C语言中递归是函数自我调用。它通过一个简单的死递归示例展示了未设置停止条件会导致栈溢出。接着,文章阐述了递归的两个必要条件:存在限制条件以终止递归,以及每次递归调用都更接近这个限制条件。随后,文章通过计算阶乘和顺序打印整数位的例子展示了递归的应用,并对比了递归和迭代的效率,强调在存在冗余计算时,迭代通常比递归更高效。
33 0
|
6月前
|
机器学习/深度学习 算法
详解函数递归
详解函数递归
|
6月前
|
算法 Java C语言
C语言函数的递归
C语言函数的递归
45 0
|
算法 C语言
C语言函数递归练习详解
C语言函数递归练习详解
|
算法 程序员 编译器
C语言函数与递归
C语言函数与递归
122 0
|
Shell
shell编程之函数以及函数中的递归(下)
在编写脚本时,有些脚本可以反复使用,可以调用函数来解决。 语句块定义成函数约等于别名。 函数的作用: 使用函数可以避免代码重复; 使用函数可以将一个大的工程分割为若干小的功能模块,代码的可读性更强。 函数的使用方法: 先定义函数 再引用函数
174 0
|
Shell
shell编程之函数以及函数中的递归(上)
在编写脚本时,有些脚本可以反复使用,可以调用函数来解决。 语句块定义成函数约等于别名。 函数的作用: 使用函数可以避免代码重复; 使用函数可以将一个大的工程分割为若干小的功能模块,代码的可读性更强。 函数的使用方法: 先定义函数 再引用函数
200 0