2022-11-28

文件包含:#include

当预处理器发现#include 指令时, 会查看后面的文件名并把文件的内容包含到当前文件中, 即替换源文件中的#include指令。 这相当于把被包含文件的全部内容输入到源文件#include指令所在的位置。

#include <stdio.h> ←文件名在尖括号中

#include "mystuff.h" ←文件名在双引号中

在 UNIX 系统中, 尖括号告诉预处理器在标准系统目录中查找该文件。双引号告诉预处理器首先在当前目录中(或文件名中指定的其他目录) 查找该文件.

集成开发环境(IDE) 也有标准路径或系统头文件的路径。 许多集成开发环境提供菜单选项, 指定用尖括号时的查找路径。 在 UNIX 中, 使用双引号意味着先查找本地目录, 但是具体查找哪个目录取决于编译器的设定。 有些编译器会搜索源代码文件所在的目录, 有些编译器则搜索当前的工作目录, 还有些搜索项目文件所在的目录。

ANSI C不为文件提供统一的目录模型, 因为不同的计算机所用的系统不同。 一般而言, 命名文件的方法因系统而异, 但是尖括号和双引号的规则与系统无关。

C语言习惯用.h后缀表示头文件, 这些文件包含需要放在程序顶部的信息。 头文件经常包含一些预处理器指令。 有些头文件(如stdio.h) 由系统提供, 当然你也可以创建自己的头文件。

包含一个大型头文件不一定显著增加程序的大小。 在大部分情况下, 头文件的内容是编译器生成最终代码时所需的信息, 而不是添加到最终代码中的材料。

头文件示例

该头文件包含了一些头文件中常见的内容: #define指令、 结构声明、typedef和函数原型。 注意, 这些内容是编译器在创建可执行代码时所需的信息, 而不是可执行代码。

可执行代码通常在源代码文件中, 而不是在头文件中。

get_names()函数通过s_gets()函数调用了fgets()函数, 避免了目标数组溢出。

两个源代码文件都使用names_st类型结构, 所以它们都必须包含names_st.h头文件。

必须编译和链接names_st.c和useheader.c源代码文件。

声明和指令放在nems_st.h头文件中, 函数定义放在names_st.c源代码文件中。

使用头文件

浏览任何一个标准头文件都可以了解头文件的基本信息。

明示常量——例如, stdio.h中定义的EOF、 NULL和BUFSIZE(标准I/O缓冲区大小) 。

宏函数——例如, getc(stdin)通常用getchar()定义, 而getc()经常用于定义较复杂的宏, 头文件ctype.h通常包含ctype系列函数的宏定义。

函数声明——例如, string.h头文件(一些旧的系统中是strings.h) 包含字符串函数系列的函数声明。 在ANSI C和后面的标准中, 函数声明都是函数原型形式。

结构模版定义——标准I/O函数使用FILE结构, 该结构中包含了文件和与文件缓冲区相关的信息。 FILE结构在头文件stdio.h中

类型定义——标准 I/O 函数使用指向 FILE 的指针作为参数。 通常,stdio.h 用#define 或typedef把FILE定义为指向结构的指针。 类似地, size_t和time_t类型也定义在头文件中。

源代码文件中包含该头文件后也包含了该声明,但是只要声明的类型一致, 在一个文件中同时使用定义式声明和引用式声明没问题。

需要包含头文件的另一种情况是, 使用具有文件作用域、 内部链接和const 限定符的变量或数组。 const 防止值被意外修改, static 意味着每个包含该头文件的文件都获得一份副本。

其他指令

程序员可能要为不同的工作环境准备C程序和C库包。 不同的环境可能使用不同的代码类型。 预处理器提供一些指令, 程序员通过修改#define的值即可生成可移植的代码。 #undef指令取消之前的#define定义。

#undef指令

#undef指令用于“取消”已定义的#define指令。

#define LIMIT 400

然后, 下面的指令:

#undef LIMIT

将移除上面的定义。 现在就可以把LIMIT重新定义为一个新值。 即使原来没有定义LIMIT, 取消LIMIT的定义仍然有效。

从C预处理角度看已定义

处理器在识别标识符时, 遵循与C相同的规则: 标识符可以由大写字母、 小写字母、 数字和下划线字符组成, 且首字符不能是数字。 当预处理器在预处理器指令中发现一个标识符时, 它会把该标识符当作已定义的或未定义的。 这里的已定义表示由预处理器定义。 如果标识符是同一个文件中由前面的#define指令创建的宏名, 而且没有用#undef 指令关闭, 那么该标识符是已定义的。 如果标识符不是宏, 假设是一个文件作用域的C变量, 那么该标识符对预处理器而言就是未定义的。

已定义宏可以是对象宏, 包括空宏或类函数宏:

#define LIMIT 1000 // LIMIT是已定义的

#define GOOD // GOOD 是已定义的

#define A(X) ((-(X))*(X)) // A 是已定义的

int q; // q 不是宏, 因此是未定义的

#undef GOOD // GOOD 取消定义, 是未定义的

#define宏的作用域从它在文件中的声明处开始, 直到用#undef指令取消宏为止, 或延伸至文件尾(以二者中先满足的条件作为宏作用域的结束) 。 另外还要注意, 如果宏通过头文件引入, 那么#define在文件中的位置取决于#include指令的位置。

__DATE__和__FILE__。 这些宏一定是已定义的, 而且不能取消定义。

条件编译

可以使用其他指令创建条件编译(conditinal compilation) 。 也就是说,可以使用这些指令告诉编译器根据编译时的条件执行或忽略信息(或代码)块。

#ifdef、 #else和#endif指令

#ifdef指令说明, 如果预处理器已定义了后面的标识符(MAVIS),则执行#else或#endif指令之前的所有指令并编译所有C代码(先出现哪个指令就执行到哪里) 。 如果预处理器未定义MAVIS, 且有 #else指令, 则执行#else和#endif指令之间的所有代码。

#ifdef #else很像C的if else。 两者的主要区别是, 预处理器不识别用于标记块的花括号({}) , 因此它使用#else(如果需要) 和#endif(必须存在)来标记指令块。 这些指令结构可以嵌套。 也可以用这些指令标记C语句块。

如果省略JUST_CHECKING定义(把它放在C注释中, 或者使用#undef指令取消它的定义) 并重新编译该程序, 只会输出最后一行。 可以用这种方法在调试程序。 定义JUST_CHECKING并合理使用#ifdef, 编译器将执行用于调试的程序代码, 打印中间值。 调试结束后, 可移除JUST_CHECKING定义并重新编译。 如果以后还需要使用这些信息, 重新插入定义即可。 这样做省去了再次输入额外打印语句的麻烦。 #ifdef还可用于根据不同的C实现选择合适的代码块。

#ifndef指令

#ifndef指令与#ifdef指令的用法类似, 也可以和#else、 #endif一起使用,但是它们的逻辑相反。 #ifndef指令判断后面的标识符是否是未定义的, 常用于定义之前未定义的常量。

( 旧的实现可能不允许使用缩进的#define)

通常, 包含多个头文件时, 其中的文件可能包含了相同宏定义。 #ifndef指令可以防止相同的宏被重复定义。 在首次定义一个宏的头文件中用#ifndef指令激活定义, 随后在其他头文件中的定义都被忽略。

#ifndef指令通常用于防止多次包含一个文件。

许多被包含的文件中都包含着其他文件, 所以显式包含的文件中可能包含着已经包含的其他文件。在被包含的文件中有某些项(如, 一些结构类型的声明) 只能在一个文件中出现一次。 C标准头文件使用#ifndef技巧避免重复包含。确保待测试的标识符没有在别处定义用文件名作为标识符、 使用大写字母、 用下划线字符代替文件名中的点字符、 用下划线字符做前缀或后缀(可能使用两条下划线) 。

#if和#elif指令

#if指令很像C语言中的if。 #if后面跟整型常量表达式, 如果表达式为非零, 则表达式为真。

defined是一个预处理运算符, 如果它的参数是用#defined定义过, 则返回1; 否则返回0。 这种新方法的优点是, 它可以和#elif一起使用。

条件编译还有一个用途是让程序更容易移植。 改变文件开头部分的几个关键的定义, 即可根据不同的系统设置不同的值和包含不同的文件。

预定义宏

C标准规定了一些预定义宏

C99 标准提供一个名为_ _func_ _的预定义标识符, 它展开为一个代表函数名的字符串(该函数包含该标识符) 。 那么, _ _func_ _必须具有函数作用域, 而从本质上看宏具有文件作用域。 因此, _ _func_ _是C语言的预定义标识符, 而不是预定义宏。

#line和#error

#line指令重置_ _LINE_ _和_ _FILE_ _宏报告的行号和文件名。

#line 1000 // 把当前行号重置为1000

#line 10 "cool.c" // 把行号重置为10, 把文件名重置为cool.c

#error 指令让预处理器发出一条错误消息, 该消息包含指令中的文本。

#pragma

在现在的编译器中, 可以通过命令行参数或IDE菜单修改编译器的一些设置。 #pragma把编译器指令放入源代码中。

#pragma c9x on

一般而言, 编译器都有自己的编译指示集。 例如, 编译指示可能用于控制分配给自动变量的内存量, 或者设置错误检查的严格程度, 或者启用非标准语言特性等。

C99还提供_Pragma预处理器运算符, 该运算符把字符串转换成普通的编译指示。

泛型选择(C11)

泛型编程(generic programming) 指那些没有特定类型, 但是一旦指定一种类型, 就可以转换成指定类型的代码。C++在模板中可以创建泛型算法, 然后编译器根据指定的类型自动使用实例化代码。 C没有这种功能。 然而, C11新增了一种表达式, 叫作泛型选择表达式(generic selection expression) , 可根据表达式的类型(即表达式的类型是int、 double 还是其他类型) 选择一个值。 泛型选择表达式不是预处理器指令, 但是在一些泛型编程中它常用作#define宏定义的一部分。

宏必须定义为一条逻辑行, 但是可以用\把一条逻辑行分隔成多条物理行。 在这种情况下, 对泛型选择表达式求值得字符串。

MYTYPE()最后两个示例所用的类型与标签不匹配, 所以打印默认的字符串。 可以使用更多类型标签来扩展宏的能力, 但是该程序主要是为了演示_Generic的基本工作原理。

对一个泛型选择表达式求值时, 程序不会先对第一个项求值, 它只确定类型。 只有匹配标签的类型后才会对表达式求值。可以像使用独立类型(“泛型”) 函数那样使用_Generic 定义宏。

内联函数

函数调用都有一定的开销, 因为函数的调用过程包括建立调用、传递参数、 跳转到函数代码并返回。C99还提供另一种方法: 内联函数(inline function) 。C99和C11标准中叙述的是: “把函数变成内联函数建议尽可能快地调用该函数, 其具体效果由实现定义”。 因此, 把函数变成内联函数, 编译器可能会用内联代码替换函数调用, 并(或) 执行一些其他的优化, 但是也可能不起作用。

创建内联函数的定义有多种方法。 标准规定具有内部链接的函数可以成为内联函数, 还规定了内联函数的定义与调用该函数的代码必须在同一个文件中。 因此, 最简单的方法是使用函数说明符 inline 和存储类别说明符static。 通常, 内联函数应定义在首次使用它的文件中, 所以内联函数也相当于函数原型。

内联函数应该比较短小。 把较长的函数变成内联并未节约多少时间, 因为执行函数体的时间比调用函数的时间长得多。

编译器优化内联函数必须知道该函数定义的内容。 这意味着内联函数定义与函数调用必须在同一个文件中。 鉴于此, 一般情况下内联函数都具有内部链接。 因此, 如果程序有多个文件都要使用某个内联函数, 那么这些文件中都必须包含该内联函数的定义。 最简单的做法是, 把内联函数定义放入头文件, 并在使用该内联函数的文件中包含该头文件即可。

一般都不在头文件中放置可执行代码, 内联函数是个特例。 因为内联函数具有内部链接, 所以在多个文件中定义同一个内联函数不会产生什么问题。

与C++不同的是, C还允许混合使用内联函数定义和外部函数定义( 具有外部链接的函数定义)。

_Noreturn函数(C11)

C99新增inline关键字时, 它是唯一的函数说明符(关键字extern和static是存储类别说明符, 可应用于数据对象和函数) 。 C11新增了第2个函数说明符_Noreturn, 表明调用完成后函数不返回主调函数。 exit()函数是_Noreturn 函数的一个示例, 一旦调用exit(), 它不会再返回主调函数。 注意, 这与void返回类型不同。 void类型的函数在执行完毕后返回主调函数,只是它不提供返回值。

_Noreturn的目的是告诉用户和编译器, 这个特殊的函数不会把控制返回主调程序。

C库

最初, 并没有官方的C库。 后来, 基于UNIX的C实现成为了标准。 ANSIC委员会主要以这个标准为基础, 开发了一个官方的标准库。 在意识到C语言的应用范围不断扩大后, 该委员会重新定义了这个库, 使之可以应用于其他系统。

访问C库

不同的系统搜索这些函数的方法不同。

自动访问

在一些系统中, 只需编译程序, 就可使用一些常用的库函数。记住, 在使用函数之前必须先声明函数的类型, 通过包含合适的头文件即可完成。 在描述库函数的用户手册中, 会指出使用某函数时应包含哪个头文件。

文件包含

如果函数被定义为宏, 那么可以通过#include 指令包含定义宏函数的文件。 通常, 类似的宏都放在合适名称的头文件中。 例如, 许多系统(包括所有的ANSI C系统) 都有ctype.h文件, 该文件中包含了一些确定字符性质(如大写、 数字等) 的宏。

库包含

在编译或链接程序的某些阶段, 可能需要指定库选项。 即使在自动检查标准库的系统中, 也会有不常用的函数库。 必须通过编译时选项显式指定这些库。 注意, 这个过程与包含头文件不同。 头文件提供函数声明或原型, 而库选项告诉系统到哪里查找函数代码。

使用库描述

函数文档。

可以在多个地方找到函数文档。 你所使用的系统可能有在线手册, 集成开发环境通常都有在线帮助。 C实现的供应商可能提供描述库函数的纸质版用户手册, 或者把这些材料放在CD-ROM中或网上。 有些出版社也出版C库函数的参考手册。 这些材料中, 有些是一般材料, 有些则是针对特定实现的。 本书附录B中提供了一个库函数的总结。

#include <stdio.h>

fread(ptr, sizeof(*ptr), nitems, stream)

FILE *stream;

首先, 给出了应该包含的文件, 但是没有给出fread()、 ptr、 sizeof(*ptr)或nitems的类型。 过去, 默认类型都是int, 但是从描述中可以看出ptr是一个指针(在早期的C中, 指针被作为整数处理) 。 参数stream声明为指向FILE的指针。 上面的函数声明中的第2个参数看上去像是sizeof运算符, 而实际上这个参数的值应该是ptr所指向对象的大小。

#include <stdio.h>

int fread(ptr, size, nitems, stream;)

char *ptr;

int size, nitems;

FILE *stream;

现在, 所有的类型都显式说明, ptr作为指向char的指针。

ANSI C90标准提供了下面的描述:

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

首先, 使用了新的函数原型格式。 其次, 改变了一些类型。 size_t 类型被定义为 sizeof 运算符的返回值类型——无符号整数类型, 通常是unsignedint或unsigned long。 stddef.h文件中包含了size_t类型的typedef或#define定义。其他文件(包括stdio.h) 通过包含stddef.h来包含这个定义。 许多函数(包括fread()) 的实际参数中都要使用sizeof运算符, 形式参数的size_t类型中正好匹配这种常见的情况。ANSI C把指向void的指针作为一种通用指针, 用于指针指向不同类型的情况。

数学库

数学库中包含许多有用的数学函数。 math.h头文件提供这些函数的原型。 表16.2中列出了一些声明在 math.h 中的函数。 注意, 函数中涉及的角度都以弧度为单位(1 弧度=180/π=57.296 度) 。 参考资料 V“新增C99和C11标准的ANSI C库”列出了C99和C11标准的所有函数。

三角问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容