你在终端里运行的每个程序都可以接收参数:gcc -Wall hello.c -o hello 就传了四个。你自己写的 C 程序通过 main 的两个参数读取它们,整个机制就这么简单:不需要调用库函数,也不需要任何设置。
函数签名
到目前为止,这些文档中的每个例子都用的是 int main(void)。另一种标准形式会接收命令行:
int main(int argc, char *argv[]) {
/* ... */
}
argc(argument count,参数个数)表示有多少个参数,包括程序名。argv(argument vector,参数向量)是一个字符串数组。char *argv[]的意思是“指向 char 的指针数组”,也就是由 C 字符串组成的数组。
你也会经常看到等价的写法 char **argv;数组形参会退化为指针,所以这两种写法声明的是同一个东西。
用编辑器的 参数 面板提供参数(每个输入框是一个参数),然后点击运行。试试填三个:hello、world、42。
参数的构成
运行 ./greet Ada Lovelace 会得到:
| 下标 | 值 | |
|---|---|---|
argv[0] | "./greet" | 调用时使用的程序名 |
argv[1] | "Ada" | 第一个真正的参数 |
argv[2] | "Lovelace" | 第二个真正的参数 |
argv[3] | NULL | 结束标记 |
argc | 3 | 个数,包括 argv[0] |
值得记住的四个事实:
- 实际使用中,
argc至少为 1,因为argv[0]是程序名。真正的参数从下标 1 开始,所以遍历参数的循环从i = 1开始。 - 一切都是字符串。
./prog 42给你的是"42"这两个字符,而不是数字 42。转换是你自己的工作。 argv[argc]是NULL。 这由标准保证,所以如果你愿意,也可以不用argc来遍历数组:for (char **p = argv + 1; *p != NULL; p++)。- 拆分单词的是 shell,不是你的程序。
./prog "Ada Lovelace"是一个参数;引号被 shell 吃掉了,永远不会出现在argv里。
读取之前先检查
没有传入的参数并不是空字符串,而是一块你无权访问的内存。一定要先验证 argc:
这个小程序里有两个约定,在 Unix 工具中都是标准做法:
- 用法提示输出到
stderr,而不是stdout,这样就不会污染通过管道传给其他程序的输出。 main返回非零值,向运行它的程序表示失败。0表示成功,其他任何值都表示出了问题。Shell 脚本会检查这个值。
打印 argv[0] 而不是写死程序名,可以让提示信息与用户的实际调用方式一致,即使程序被改了名或通过不同路径调用也一样。
转换数字参数
argv[1] 是文本。要做算术运算就必须先转换,应该使用的函数是 <stdlib.h> 中的 strtol:
在参数面板中添加 10、20、30 三个参数,然后把其中一个换成 banana,看看出错时的处理。
strtol 的第三个参数是进制:10 表示十进制,16 表示十六进制,0 表示根据 0x 或前导零前缀自动识别。第二个参数用来存放一个指针,指向它没有处理的第一个字符,正是这一点让错误检查成为可能:
end == text表示什么都没解析到:参数不是以数字开头的。*end != '\0'表示数字后面还有剩余文本,所以"12abc"会被拒绝,而不是悄悄变成 12。errno == ERANGE表示值超出了long的范围。
对比一下 atoi,它更短,但这些情况一个都报告不了:
int n = atoi(argv[1]); /* "banana" -> 0, "0" -> 0, overflow -> undefined */
atoi 对无效输入返回 0,这和真正的零无法区分。只要输入来自程序外部,就用 strtol(浮点数用 strtod)。更多内容见字符串转换。
一个小计算器
把这些组合起来:一个接收数字、运算符、数字的程序:
试试 12 + 30,然后是 7 x 6,再试 5 / 0。
注意这里写的是 strcmp(op, "+") == 0,而不是 op == "+"。用 == 比较 char * 值比较的是地址而不是内容,即使文本完全相同,结果也会是假。参见字符串函数。
用 x 代替 * 是一个来自实际使用的细节:大多数 shell 会在你的程序运行之前,把单独的 * 展开成当前目录下的文件列表。给它加上引号('*')也行,但选一个 shell 不会处理的字符更友好。
解析选项
按照惯例,选项以 - 开头。一个简单的手写循环就能处理常见情况:
用四个参数试试:-v、-n、3、hello。
argv[++i] 之前的 i + 1 >= argc 检查是最关键的一行:需要值的选项必须确认这个值真的存在,否则当 -n 是最后一个参数时,程序就会读到数组末尾之外。在真正的 Unix 系统上,<unistd.h> 中的 getopt 会帮你完成这一切,包括 -vn3 这样合并在一起的短选项;一旦程序的选项超过两三个,就值得用它。
浏览器编辑器中的参数
本页的编辑器代码块会为你编译并运行,参数 面板就是你填写终端中程序名后面那些内容的地方。你用 添加参数 加上的每个输入框,正好对应 argv 中的一项。中间没有 shell,所以不会按空格拆分,也不需要引号:内容为 hello world 的输入框会作为单个参数 hello world 传入。要传 -n 3 hello,就添加三个输入框。面板留空时 argc == 1,这就是为什么第一次运行时 argc < 2 的检查会触发,也是它值得写上的原因。
常见错误
- 不检查
argc就读取argv[1]。 没有传入参数时会导致未定义行为,这也是这方面最常见的崩溃原因。 - 循环从
i = 0开始。 这样会把程序名当成数据处理。 - 把
argv[1]当成数字。 在你转换之前,它都是文本。 - 对用户输入使用
atoi。 无法区分错误的参数和零。 - 用
==比较字符串。 请用strcmp。 - 往
argv的字符串里写入。 标准允许修改它们,但空间有限且依赖平台;如果需要修改,就复制到你自己的缓冲区里。
常见问题
C 语言中 argc 和 argv 是什么?
它们是 int main(int argc, char *argv[]) 的两个参数。argc 是命令行参数的个数,包括程序名;argv 是由这些参数组成的字符串数组。argv[0] 是程序名,argv[1] 是第一个真正的参数,而 argv[argc] 始终是 NULL。
怎么给 C 程序传递参数?
在终端里把参数写在可执行文件名后面:./program hello 42。这时 argc == 3,argv[1] 是字符串 "hello",argv[2] 是字符串 "42"。在本页的编辑器中,参数填在“参数”面板里,而不是终端中。
C 语言怎么把命令行参数转换成 int?
使用 strtol:char *end; long n = strtol(argv[1], &end, 10);,然后检查 end 是否仍指向开头(什么都没解析到),以及 *end 是否为 '\0'(后面没有多余的字符)。atoi 更短,但对 "0" 和 "banana" 都返回 0,所以它无法报告错误。
C 语言中 argv[0] 是什么?
启动程序时使用的名字,通常是 ./program 或完整路径。它在用法提示中很有用(fprintf(stderr, "usage: %s FILE\n", argv[0])),能让提示与用户实际的调用方式一致。在少数特殊情况下它可能是空字符串,所以不要假定它一定存在。