2021-02-28 分类: 网站建设
学习如何构造一个 C 文件并编写一个 C main 函数来成功地处理命令行参数。
我知道,现在孩子们用 Python 和 JavaScript 编写他们的疯狂“应用程序”。但是不要这么快就否定 C 语言 —— 它能够提供很多东西,并且简洁。如果你需要速度,用 C 语言编写可能就是你的答案。如果你正在寻找稳定的职业或者想学习如何捕获空指针解引用,C 语言也可能是你的答案!在本文中,我将解释如何构造一个 C 文件并编写一个 C main 函数来成功地处理命令行参数。
我:一个顽固的 Unix 系统程序员。
你:一个有编辑器、C 编译器,并有时间打发的人。
让我们开工吧。
Parody O'Reilly book cover, "Hating Other People's Code"
C 程序以 main() 函数开头,通常保存在名为 main.c 的文件中。
- /* main.c */
- int main(int argc, char *argv[]) {
- }
这个程序可以编译但不干任何事。
- $ gcc main.c
- $ ./a.out -o foo -vv
- $
正确但无聊。
main() 函数是开始执行时所执行的程序的第一个函数,但不是第一个执行的函数。第一个函数是 _start(),它通常由 C 运行库提供,在编译程序时自动链入。此细节高度依赖于操作系统和编译器工具链,所以我假装没有提到它。
main() 函数有两个参数,通常称为 argc 和 argv,并返回一个有符号整数。大多数 Unix 环境都希望程序在成功时返回 0(零),失败时返回 -1(负一)。
参数 | 名称 | 描述 |
---|---|---|
argc | 参数个数 | 参数向量的个数 |
argv | 参数向量 | 字符指针数组 |
参数向量 argv 是调用你的程序的命令行的标记化表示形式。在上面的例子中,argv 将是以下字符串的列表:
- argv = [ "/path/to/a.out", "-o", "foo", "-vv" ];
参数向量在其第一个索引 argv[0] 中确保至少会有一个字符串,这是执行程序的完整路径。
当我从头开始编写 main.c 时,它的结构通常如下:
- /* main.c */
- /* 0 版权/许可证 */
- /* 1 包含 */
- /* 2 定义 */
- /* 3 外部声明 */
- /* 4 类型定义 */
- /* 5 全局变量声明 */
- /* 6 函数原型 */
- int main(int argc, char *argv[]) {
- /* 7 命令行解析 */
- }
- /* 8 函数声明 */
下面我将讨论这些编号的各个部分,除了编号为 0 的那部分。如果你必须把版权或许可文本放在源代码中,那就放在那里。
另一件我不想讨论的事情是注释。
- “评论谎言。”
- - 一个愤世嫉俗但聪明又好看的程序员。
与其使用注释,不如使用有意义的函数名和变量名。
鉴于程序员固有的惰性,一旦添加了注释,维护负担就会增加一倍。如果更改或重构代码,则需要更新或扩充注释。随着时间的推移,代码会变得面目全非,与注释所描述的内容完全不同。
如果你必须写注释,不要写关于代码正在做什么,相反,写下代码为什么要这样写。写一些你将要在五年后读到的注释,那时你已经将这段代码忘得一干二净。世界的命运取决于你。不要有压力。
我添加到 main.c 文件的第一个东西是包含文件,它们为程序提供大量标准 C 标准库函数和变量。C 标准库做了很多事情。浏览 /usr/include 中的头文件,你可以了解到它们可以做些什么。
#include 字符串是 C 预处理程序(cpp)指令,它会将引用的文件完整地包含在当前文件中。C 中的头文件通常以 .h 扩展名命名,且不应包含任何可执行代码。它只有宏、定义、类型定义、外部变量和函数原型。字符串 <header.h> 告诉 cpp 在系统定义的头文件路径中查找名为 header.h 的文件,它通常在 /usr/include 目录中。
- /* main.c */
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <libgen.h>
- #include <errno.h>
- #include <string.h>
- #include <getopt.h>
- #include <sys/types.h>
这是我默认会全局包含的最小包含集合,它将引入:
#include 文件 | 提供的东西 |
---|---|
stdio | 提供 FILE、stdin、stdout、stderr 和 fprint() 函数系列 |
stdlib | 提供 malloc()、calloc() 和 realloc() |
unistd | 提供 EXIT_FAILURE、EXIT_SUCCESS |
libgen | 提供 basename() 函数 |
errno | 定义外部 errno 变量及其可以接受的所有值 |
string | 提供 memcpy()、memset() 和 strlen() 函数系列 |
getopt | 提供外部 optarg、opterr、optind 和 getopt() 函数 |
sys/types | 类型定义快捷方式,如 uint32_t 和 uint64_t |
- /* main.c */
- <...>
- #define OPTSTR "vi:o:f:h"
- #define USAGE_FMT "%s [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]"
- #define ERR_FOPEN_INPUT "fopen(input, r)"
- #define ERR_FOPEN_OUTPUT "fopen(output, w)"
- #define ERR_DO_THE_NEEDFUL "do_the_needful blew up"
- #define DEFAULT_PROGNAME "george"
这在现在没有多大意义,但 OPTSTR 定义我这里会说明一下,它是程序推荐的命令行开关。参考 getopt(3) man 页面,了解 OPTSTR 将如何影响 getopt() 的行为。
USAGE_FMT 定义了一个 printf() 风格的格式字符串,它用在 usage() 函数中。
我还喜欢将字符串常量放在文件的 #define 这一部分。如果需要,把它们收集在一起可以更容易地修正拼写、重用消息和国际化消息。
最后,在命名 #define 时全部使用大写字母,以区别变量和函数名。如果需要,可以将单词放连在一起或使用下划线分隔,只要确保它们都是大写的就行。
- /* main.c */
- <...>
- extern int errno;
- extern char *optarg;
- extern int opterr, optind;
extern 声明将该名称带入当前编译单元的命名空间(即 “文件”),并允许程序访问该变量。这里我们引入了三个整数变量和一个字符指针的定义。opt 前缀的几个变量是由 getopt() 函数使用的,C 标准库使用 errno 作为带外通信通道来传达函数可能的失败原因。
- /* main.c */
- <...>
- typedef struct {
- int verbose;
- uint32_t flags;
- FILE *input;
- FILE *output;
- } options_t;
在外部声明之后,我喜欢为结构、联合和枚举声明 typedef。命名一个 typedef 是一种传统习惯。我非常喜欢使用 _t 后缀来表示该名称是一种类型。在这个例子中,我将 options_t声明为一个包含 4 个成员的 struct。C 是一种空格无关的编程语言,因此我使用空格将字段名排列在同一列中。我只是喜欢它看起来的样子。对于指针声明,我在名称前面加上星号,以明确它是一个指针。
- /* main.c */
- <...>
- int dumb_global_variable = -11;
全局变量是一个坏主意,你永远不应该使用它们。但如果你必须使用全局变量,请在这里声明,并确保给它们一个默认值。说真的,不要使用全局变量。
在编写函数时,将它们添加到 main() 函数之后而不是之前,在这里放函数原型。早期的 C 编译器使用单遍策略,这意味着你在程序中使用的每个符号(变量或函数名称)必须在使用之前声明。现代编译器几乎都是多遍编译器,它们在生成代码之前构建一个完整的符号表,因此并不严格要求使用函数原型。但是,有时你无法选择代码要使用的编译器,所以请编写函数原型并继续这样做下去。
当然,我总是包含一个 usage() 函数,当 main() 函数不理解你从命令行传入的内容时,它会调用这个函数。
- /* main.c */
- <...>
- int main(int argc, char *argv[]) {
- int opt;
- options_t options = { 0, 0x0, stdin, stdout };
- opterr = 0;
- while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
- switch(opt) {
- case 'i':
- if (!(options.input = fopen(optarg, "r")) ){
- perror(ERR_FOPEN_INPUT);
- exit(EXIT_FAILURE);
- /* NOTREACHED */
- }
- break;
- case 'o':
- if (!(options.output = fopen(optarg, "w")) ){
- perror(ERR_FOPEN_OUTPUT);
- exit(EXIT_FAILURE);
- /* NOTREACHED */
- }
- break;
- case 'f':
- options.flags = (uint32_t )strtoul(optarg, NULL, 16);
- break;
- case 'v':
- options.verbose += 1;
- break;
- case 'h':
- default:
当前题目:如何写好C main函数?
URL分享:/news1/103501.html成都网站建设公司_创新互联,为您提供云服务器、网页设计公司、小程序开发、电子商务、服务器托管、外贸网站建设
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联
猜你还喜欢下面的内容