0%

C语言传递的参数还能这样解析

前言

以前写脚本的时候用到过传参:python最简单,直接import argparse。shell中使用$#表示传递到脚本的参数个数,用$*表示以一个单字符串显示所有向脚本传递的参数,如”$*“用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数。bat脚本传参和shell类似,%#表示传递到脚本的参数个数,%*表示参数字符串,*可以是数字。在C语言中可以使用getopt,它是一个标准的C库函数,用于解析命令行参数。它可以帮助你处理短选项(-h)和长选项(–help)。

今天学习 adpcm-xq 看到一个C代码直接用指针操作argv,如*++*argv,能差不多看懂,现在回家再来理一下。

代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
int main (argc, argv) int argc; char **argv;
{
int lookahead = 3, flags = ADPCM_FLAG_NOISE_SHAPING, blocksize_pow2 = 0, overwrite = 0, asked_help = 0;
char *infilename = NULL, *outfilename = NULL;
FILE *outfile;

// if the name of the executable ends in "encoder" or "decoder", just do that function
encode_only = argc && strstr (argv [0], "encoder") && strlen (strstr (argv [0], "encoder")) == strlen ("encoder");
decode_only = argc && strstr (argv [0], "decoder") && strlen (strstr (argv [0], "decoder")) == strlen ("decoder");

// loop through command-line arguments

while (--argc) {
#if defined (_WIN32)
if ((**++argv == '-' || **argv == '/') && (*argv)[1])
#else
if ((**++argv == '-') && (*argv)[1])
#endif
while (*++*argv)
switch (**argv) {

case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
lookahead = **argv - '0';
break;

case 'B': case 'b':
blocksize_pow2 = strtol (++*argv, argv, 10);

if (blocksize_pow2 < 8 || blocksize_pow2 > 15) {
fprintf (stderr, "\nblock size power must be 8 to 15!\n");
return -1;
}

--*argv;
break;

case 'D': case 'd':
decode_only = 1;
break;

case 'E': case 'e':
encode_only = 1;
break;

case 'F': case 'f':
flags &= ~ADPCM_FLAG_NOISE_SHAPING;
break;

case 'H': case 'h':
asked_help = 0;
break;

case 'Q': case 'q':
verbosity = -1;
break;

case 'R': case 'r':
flags |= ADPCM_FLAG_RAW_OUTPUT;
break;

case 'V': case 'v':
verbosity = 1;
break;

case 'Y': case 'y':
overwrite = 1;
break;

default:
fprintf (stderr, "\nillegal option: %c !\n", **argv);
return 1;
}
else if (!infilename) {
infilename = malloc (strlen (*argv) + 10);
strcpy (infilename, *argv);
}
else if (!outfilename) {
outfilename = malloc (strlen (*argv) + 10);
strcpy (outfilename, *argv);
}
else {
fprintf (stderr, "\nextra unknown argument: %s !\n", *argv);
return 1;
}
}

if (verbosity >= 0)
fprintf (stderr, "%s", sign_on);

if (!outfilename || asked_help) {
printf ("%s", usage);
return 0;
}

if (!strcmp (infilename, outfilename)) {
fprintf (stderr, "can't overwrite input file (specify different/new output file name)\n");
return -1;
}

if (!overwrite && (outfile = fopen (outfilename, "r"))) {
fclose (outfile);
fprintf (stderr, "output file \"%s\" exists (use -y to overwrite)\n", outfilename);
return -1;
}

return adpcm_converter (infilename, outfilename, flags, blocksize_pow2, lookahead);
}

argc argv 知识

C语言中的argcargv通常用于处理命令行参数。在C程序中,main函数可以接受两个参数,分别是argc(参数计数)和argv(参数向量)。

  • argc 表示命令行参数的数量(包含命令),它是一个整数。
  • argv 是一个指向字符指针数组的指针,每个指针指向一个字符串,这些字符串是命令行参数的实际内容。argv[0]通常是程序的名称,而argv[1]argv[2]等则是传递给程序的参数。

下面是一个简单的例子,展示了如何在C语言中使用argcargv

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Number of arguments: %d\n", argc);
// 输出所有命令行参数
for (int i = 0; i < argc; i++) {
printf("Argument %d: %s\n", i, argv[i]);
}
return 0;
}

假设你将上述代码保存在一个名为 example.c 的文件中,然后通过命令行编译并运行:

1
2
gcc example.c -o example
./example arg1 arg2 arg3

这将输出:

1
2
3
4
5
CopyNumber of arguments: 4
Argument 0: ./example
Argument 1: arg1
Argument 2: arg2
Argument 3: arg3

这里,argc是4,因为有四个参数(包括程序的名称),而argv包含这四个参数的字符串。

代码分析

接下来我将对代码片段中的argc argv相关代码的逐行分析:

片段1

1
2
3
// if the name of the executable ends in "encoder" or "decoder", just do that function
encode_only = argc && strstr (argv [0], "encoder") && strlen (strstr (argv [0], "encoder")) == strlen ("encoder");
decode_only = argc && strstr (argv [0], "decoder") && strlen (strstr (argv [0], "decoder")) == strlen ("decoder");

argc:传递给程序的命令行参数数量;argv[0]:可执行文件的名称(第一个命令行参数)。

char *strstr(const char *haystack, const char *needle); 返回一个指向第一次出现 needle 的指针,如果未找到,则返回 NULL

strlen用于计算字符串的长度,即字符串中字符的个数,不包括字符串末尾的 null 终止符。

这段代码用于检查可执行文件的名称(从命令行参数 argv[0] 获取)是否以 “encoder” 或 “decoder” 结尾(出现字符通过strstr保证,结尾通过strlen保证)。它根据这些条件设置两个布尔变量 encode_onlydecode_only

片段2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    // 循环了 argc - 1 次,遍历完所有命令参数
while (--argc) {
#if defined (_WIN32)
// argv首先自增,即指向下一个字符串,**argv是字符串的第一个字符,比较是否为'-'或'/',
// && (*argv)[1],且这个字符串存在第二个字符
if ((**++argv == '-' || **argv == '/') && (*argv)[1])
#else
// argv首先自增,即指向下一个字符串,**argv是字符串的第一个字符,比较是否为'-',
// && (*argv)[1],且这个字符串存在第二个字符
if ((**++argv == '-') && (*argv)[1])
#endif
// argv是指针数组,*argv是指向的字符串,++*argv是增加偏移1字节,指向字符串下一个字符,
// *++*argv就是下个字符,while (*++*argv)意思就是当下个字符不为结束符'\0'
while (*++*argv)
// **argv是字符串中的一个字符,上一行用++让字符串*argv的指向+1,**argv就是当前指向的字符串字符
switch (**argv) {

片段3

1
2
3
4
5
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
lookahead = **argv - '0'; // lookahead为数值,字符串转数值
break;

片段4

1
2
3
4
5
6
7
8
9
10
11
12
13
case 'B': case 'b':
// long strtol(const char *str, char **endptr, int base);
// strtol 用于将字符串转换为长整型数(long)返回,str是要转换的字符串,base 10表示十进制,
// endptr 如果不是 NULL,则它存储一个指向第一个无法转换的字符的指针,或者如果字符串为空,则指向 str 的开始,
// 感觉这行代码endptr把 argv 传进去有风险,如果++*argv无法转换成long,argv则会被写
blocksize_pow2 = strtol (++*argv, argv, 10);
if (blocksize_pow2 < 8 || blocksize_pow2 > 15) {
fprintf (stderr, "\nblock size power must be 8 to 15!\n");
return -1;
}
// 感觉这行可有可无,都要break了,接下来就是检查下个字符串了
--*argv;
break;

片段5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else if (!infilename) {
// 先是把非'-'开头的参数赋给输入文件名字
infilename = malloc (strlen (*argv) + 10);
strcpy (infilename, *argv);
}
else if (!outfilename) {
// 再是把非'-'开头的参数赋给输出文件名字
outfilename = malloc (strlen (*argv) + 10);
strcpy (outfilename, *argv);
}
else {
// 如果参数有问题就输出stderr,打印出是哪个参数错误
fprintf (stderr, "\nextra unknown argument: %s !\n", *argv);
return 1;
}

在没有用解析命令参数库的情况下,竟然可以这样实现一些常见的命令选项功能,佩服!