C 编程/库
在 C 语言中,一个库是多个头文件的集合,这些头文件被暴露出来供其他程序使用。因此,库由一个用.h
文件(称为“头文件”)表示的接口和一个用.c
文件表示的实现组成。这个.c
文件可能是预编译的或不可访问的,也可能是可供程序员访问的。(注意:库可能调用其他库(如标准 C 库或数学库)中的函数来完成各种任务。)
库的格式会因使用的操作系统和编译器而异。例如,在 Unix 和 Linux 操作系统中,库由一个或多个目标文件组成,这些目标文件包含通常是编译器(如果源语言是 C 或类似语言)或汇编器(如果源语言是汇编语言)的输出的目标代码。然后,这些目标文件通过ar 归档器(一个将文件存储到更大的文件中的程序,不考虑压缩)被转换成一个库形式的归档文件。库的文件名通常以“lib”开头,以“.a”结尾;例如,libc.a 文件包含标准 C 库,而“libm.a”文件包含数学例程,链接器会将它们链接进来。其他操作系统,如 Microsoft Windows,使用“.lib”扩展名表示库,使用“.obj”扩展名表示目标文件。Unix 环境中的某些程序,如 lex 和 yacc,会生成 C 代码,这些代码可以与 libl 和 liby 库链接以创建可执行文件。
我们以一个包含一个函数的库为例:一个用于从命令行解析参数的函数。命令行上的参数可以是独立的
-i
具有可选参数,该参数与字母连接起来
-ioptarg
或者在单独的 argv 元素中具有参数
-i optarg
该库除了函数之外,还导出四个声明:三个整数和一个指向可选参数的指针。如果参数没有可选参数,则指向可选参数的指针将为 null。
为了解析所有这些类型的参数,我们编写了以下“getopt.c”文件
#include <stdio.h> /* for fprintf() and EOF */
#include <string.h> /* for strchr() */
#include "getopt.h" /* consistency check */
/* variables */
int opterr = 1; /* getopt prints errors if this is on */
int optind = 1; /* token pointer */
int optopt; /* option character passed back to user */
char *optarg; /* flag argument (or value) */
/* function */
/* return option character, EOF if no more or ? if problem.
The arguments to the function:
argc, argv - the arguments to the main() function. An argument of "--"
stops the processing.
opts - a string containing the valid option characters.
an option character followed by a colon (:) indicates that
the option has a required argument.
*/
int
getopt (int argc, char **argv, char *opts)
{
static int sp = 1; /* character index into current token */
register char *cp; /* pointer into current token */
if (sp == 1)
{
/* check for more flag-like tokens */
if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
return EOF;
else if (strcmp (argv[optind], "--") == 0)
{
optind++;
return EOF;
}
}
optopt = argv[optind][sp];
if (optopt == ':' || (cp = strchr (opts, optopt)) == NULL)
{
if (opterr)
fprintf (stderr, "%s: invalid option -- '%c'\n", argv[0], optopt);
/* if no characters left in this token, move to next token */
if (argv[optind][++sp] == '\0')
{
optind++;
sp = 1;
}
return '?';
}
if (*++cp == ':')
{
/* if a value is expected, get it */
if (argv[optind][sp + 1] != '\0')
/* flag value is rest of current token */
optarg = argv[optind++] + (sp + 1);
else if (++optind >= argc)
{
if (opterr)
fprintf (stderr, "%s: option requires an argument -- '%c'\n",
argv[0], optopt);
sp = 1;
return '?';
}
else
/* flag value is next token */
optarg = argv[optind++];
sp = 1;
}
else
{
/* set up to look at next char in token, next time */
if (argv[optind][++sp] == '\0')
{
/* no more in current token, so setup next token */
sp = 1;
optind++;
}
optarg = 0;
}
return optopt;
}
/* END OF FILE */
接口将是以下“getopt.h”文件
#ifndef GETOPT_H
#define GETOPT_H
/* exported variables */
extern int opterr, optind, optopt;
extern char *optarg;
/* exported function */
int getopt(int, char **, char *);
#endif
/* END OF FILE */
在最少的情况下,程序员拥有接口文件以了解如何使用库,尽管一般来说,库程序员也编写了有关如何使用库的文档。在上述情况下,文档应该说明提供的参数**argv
和*opts
都不能是 null 指针(否则你为什么要使用getopt
函数呢?)。具体而言,它通常说明每个参数的用途以及在哪些条件下可以预期哪些返回值。使用库的程序员通常不关心库的实现,除非实现存在错误,在这种情况下,他们会希望以某种方式提出投诉。
getopts 库的实现和使用库的程序都应该声明#include "getopt.h"
,以便引用相应的接口。现在库与包含 main() 函数的程序“链接”在一起。程序可能引用数十个接口。
在某些情况下,仅放置#include "getopt.h"
似乎是正确的,但仍然无法正确链接。这表明库没有正确安装,或者可能需要一些额外的配置。您将需要检查编译器文档或库文档以了解如何解决此问题。
头文件中放什么
[edit | edit source]一般来说,头文件应该包含程序中其他模块“可见”的任何声明和宏定义(预处理器#define
)。
可能的声明
- 结构体、联合体和枚举声明
- typedef 声明
- 外部函数声明
- 全局变量声明
在上面的getopt.h
示例文件中,声明了一个函数(getopt
),还声明了四个全局变量(optind
、optopt
、optarg
和opterr
)。这些变量在头文件中使用存储类说明符extern
声明,因为该关键字指定“真实”变量存储在其他位置(即getopt.c
文件)而不是头文件内。
#ifndef GETOPT_H/#define GETOPT_H
技巧俗称包含保护。这样做是为了防止getopt.h
文件在翻译单元中被包含多次时,该单元只看到一次内容。或者,在头文件中使用#pragma once
也可以在某些编译器中实现相同的效果(#pragma
是一个不可移植的万能符)。
将库链接到可执行文件
[edit | edit source]将库链接到可执行文件的方式因操作系统和使用的编译器/链接器而异。在 Unix 中,可以使用-L选项将链接的目标文件目录指定给 cc 命令,使用-l(小写 l)选项指定单个库。例如,-lm选项指定应将 libm 数学库链接进来。