大连网站推广排名,google 推广优化,网站代做多少钱,网站的建设内容一#xff1a;文件
1.1 文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区#xff0c;用来存放文件的相关信息#xff08;如文件的名字#xff0c;文件状态及文件当前的位置等#xff09;。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明…一文件
1.1 文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区用来存放文件的相关信息如文件的名字文件状态及文件当前的位置等。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的取名FILE.
例如VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明
struct _iobuf {//文件信息区char *_ptr;int _cnt;char *_base;int _flag;int _file;int _charbuf;int _bufsiz;char *_tmpfname;};
typedef struct _iobuf FILE;一般来说我们是通过文件指针来维护这个文件信息区的
下面我们可以创建一个FILE*的指针变量:
FILE* pf;//文件指针变量定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区是一个结构体变量。通过该文件信息区中的信息就能够访问该文件。也就是说通过文件指针变量能够找到与它关联的文件。
如图所示
1.2文件的打开和关闭
文件在读写之前应该先打开文件在使用结束之后应该关闭文件。在编写程序的时候在打开文件的同时都会返回一个FILE*的指针变量指向该文件也相当于建立了指针和文件的关系。
当在C语言中操作文件时我们通常使用fopen函数来打开文件并使用fclose函数来关闭文件。
fopen函数用于打开一个文件并返回一个指向文件的指针。它的原型如下
FILE *fopen(const char *filename, const char *mode);filename参数是一个字符串表示要打开的文件名称。mode参数是一个字符串用于指定文件的打开模式。
下面是一些常见的打开模式 示例代码
#include stdio.hint main() {FILE *file;char *filename example.txt;char *mode r;file fopen(filename, mode);if (file NULL) {printf(无法打开文件\n);return 1;}// 做一些文件操作...fclose(file);return 0;
}在上面的示例中我们声明了一个指向FILE类型的指针file然后指定了要打开的文件的路径和名称以及打开模式。然后我们使用fopen函数打开该文件并将返回的文件指针赋值给file。
fopen函数的返回值是一个指向FILE类型的指针如果打开文件失败比如文件不存在则返回NULL。
在完成文件操作后我们使用fclose函数来关闭文件。这个函数负责将文件指针指向的文件关闭并释放与该文件相关的资源。关闭文件后我们就不能再对其进行读写操作了。
需要注意的是打开文件后我们必须在不再需要使用文件时及时关闭它以便释放系统资源。
1.3 scanf printf fscanf fprintf sscanf sprintf
scanf scanf是一个标准输入函数用于从标准输入设备键盘中读取格式化输入并将其存储在指定的变量中。它允许以指定的格式读取不同类型的数据如整数、浮点数、字符等。
printf printf是一个标准输出函数用于将格式化的数据输出到标准输出设备屏幕。它允许以指定的格式输出不同类型的数据如整数、浮点数、字符等。
fscanf
int fscanf(FILE *file, const char *format, argument_list)
file要读取的文件指针。format格式字符串指定了输入的预期格式。argument_list读取值的变量列表。
fscanf函数从给定的文件中按照指定的格式读取数据并将读取的值存储到指定的变量中。它会根据格式字符串中的格式说明符逐个读取输入并将读取的值与相应的变量进行匹配。
fscanf是一个文件输入函数用于从文件中读取格式化输入并将其存储在指定的变量中。与scanf函数类似但fscanf从指定的文件中读取数据而不是从键盘。
fprintf
int fprintf(FILE *file, const char *format, argument_list)
file要写入的文件指针。format格式字符串指定了输出的格式。argument_list要写入的变量列表。
fprintf函数将指定的变量根据格式字符串的要求进行格式化并将格式化后的内容写入到给定的文件中。
fprintf是一个文件输出函数用于将格式化的数据输出到指定的文件中。与printf函数类似但fprintf将数据写入指定的文件而不是输出到屏幕。
sscanf
sscanf函数用于从一个字符串中读取数据并按照指定的格式转换。它的函数原型如下
int sscanf(const char *str, const char *format, ...)str是要读取的字符串。format是格式字符串定义了读取数据的规则。...是可变参数列表用于接收读取的数据。
sscanf是一个字符串输入函数用于从一个字符串中读取格式化输入并将其存储在指定的变量中。与scanf类似但sscanf从一个字符串中读取数据而不是从标准输入设备或文件中。
sprintf
sprintf函数用于将格式化的数据写入一个字符串中。它的函数原型如下
int sprintf(char *str, const char *format, ...)str是目标字符串的指针用于存储格式化的数据。format是格式字符串定义了写入数据的规则。...是可变参数列表用于提供需要格式化的数据。
sprintf是一个字符串输出函数用于将格式化的数据输出到一个字符串中。与printf类似但sprintf将数据写入一个字符串而不是输出到屏幕或文件中。
这些函数的区别在于输入/输出的来源或目标不同。scanf和printf用于标准输入输出fscanf和fprintf用于文件输入输出sscanf和sprintf用于字符串输入输出。
下面是一些示例代码
#include stdio.hint main() {int num;char str[100];// 从键盘输入一个整数printf(请输入一个整数);scanf(%d, num);// 将该整数写入文件FILE *file fopen(data.txt, w);fprintf(file, %d, num);//将一个整数num格式化为字符串并将其写入到指定的文件file中。fclose(file);// 从文件中读取整数file fopen(data.txt, r);fscanf(file, %d, num);//从打开的文件file中读取一个整数并将其存储在num变量中。fclose(file);// 将整数转换为字符串sprintf(str, %d, num);//将一个整数num转换为字符串并将结果存储在字符数组str中。// 从字符串中读取整数sscanf(str, %d, num);//从一个字符串str中读取一个整数并将其存储在num变量中// 输出整数和字符串printf(整数%d\n字符串%s\n, num, str);return 0;
}1.4 文件的顺序读写
fgetc fputc fgets fputs fread fwrite 是c语言中常用的顺序读写函数fscanf和fprintf也是下面我们一 一介绍
fgetc函数 int fgetc(FILE *stream)函数用于从指定的文件流中读取一个字符并返回该字符的ASCII码值。它的参数是一个指向FILE类型的指针指向要读取的文件流。函数执行成功时将返回读取的字符若已到达文件末尾或读取失败则返回EOF-1。 下面是一个使用fgetc函数读取文件的示例代码
#include stdio.hint main() {FILE *file fopen(input.txt, r);int character;while ((character fgetc(file)) ! EOF) {printf(%c, character);}fclose(file);return 0;
}fputc函数 int fputc(int character, FILE *stream)函数用于将指定的字符写入到文件中并返回写入成功的字符。它的第一个参数是要写入的字符第二个参数是指向FILE类型的指针指向要写入的文件流。函数执行成功时将返回写入的字符若写入失败则返回EOF-1。 下面是一个使用fputc函数写入文件的示例代码
#include stdio.hint main() {FILE *file fopen(output.txt, w);char character A;fputc(character, file);fclose(file);return 0;
}注意fgetc函数在读取到文件末尾或读取失败时返回EOF-1而fputc函数在写入失败时返回EOF-1。
fgets函数
fgets 函数用于从文件中读取一行文本。它的声明如下
char *fgets(char *str, int n, FILE *stream);str指向用于存储读取数据的字符数组的指针n要读取的字符的最大数量包括空字符所以实际数量要-1通常使用数组的大小stream指向要读取的文件的指针。
fgets 会读取文件中的一行文本并存储在 str 中直到达到以下情况之一
读取到了n-1个字符读取的字符个数不是n而是n-1读取到换行符 ‘\n’读到\n会把\n保存进去到达文件的末尾发生错误。
fgets 会自动在读取到的文本末尾添加空字符 ‘\0’因此 str 数组必须足够大以容纳该字符串。
示例代码
#include stdio.hint main() {// 打开文件以供读取FILE *file fopen(file.txt, r);if (file) {char str[100];// 从文件中读取一行文本while (fgets(str, sizeof(str), file)) {printf(%s, str); // 打印读取的文本}fclose(file); // 关闭文件}return 0;
}fputs函数
fputs 函数用于将字符串写入文件。它的声明如下
int fputs(const char *str, FILE *stream);str要写入文件的字符串stream指向要写入的文件的指针。
fputs 会将 str 中的内容写入 stream 所指向的文件直到遇到空字符 ‘\0’。写入成功时它会返回一个非负值否则返回 EOF。
示例代码
#include stdio.hint main() {// 打开文件以供写入FILE *file fopen(file.txt, w);if (file) {char str[] Hello, World!;// 将字符串写入文件fputs(str, file);fclose(file); // 关闭文件}return 0;
}注意 fgets 返回的是读取到的字符串的指针fputs 的返回值是一个非负整数表示写入的字符数或 EOF表示写入失败。
fread 函数 fread 函数用于从文件中读取数据。其声明如下
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);ptr指向接收数据的缓冲区的指针。读到哪去的地址size要读取的每个元素的大小以字节为单位。count要读取的元素的个数。stream指向要读取的文件的指针。
fread 函数的返回值表示成功读取的元素数。
示例代码
#include stdio.hint main() {FILE *file fopen(data.txt, rb);if (file NULL) {printf(Unable to open the file.\n);return 1;}int numbers[5];size_t elements_read fread(numbers, sizeof(int), 5, file);fclose(file);printf(Read %zu elements: , elements_read);for (int i 0; i elements_read; i) {printf(%d , numbers[i]);}printf(\n);return 0;
}在上面的示例中我们打开一个名为 “data.txt” 的二进制文件然后使用 fread 函数从文件中读取 5 个整数并将它们存储在 numbers 数组中。最后我们遍历数组并将读取的元素打印出来。
fwrite 函数 fwrite 函数用于向文件中写入数据。其声明如下
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);ptr指向要写入的数据的指针。size要写入的每个元素的大小以字节为单位。count要写入的元素的个数。stream指向要写入的文件的指针。
fwrite 函数的返回值表示成功写入的元素数。
示例代码
#include stdio.hint main() {FILE *file fopen(data.txt, wb);if (file NULL) {printf(Unable to open the file.\n);return 1;}int numbers[] {1, 2, 3, 4, 5};size_t elements_written fwrite(numbers, sizeof(int), 5, file);fclose(file);printf(Written %zu elements to the file.\n, elements_written);return 0;
}在上面的示例中我们打开一个名为 “data.txt” 的二进制文件然后使用 fwrite 函数将整数数组 numbers 的内容写入文件。最后我们打印出成功写入的元素数。
注意fread 函数的返回值表示成功读取的元素数而 fwrite 函数的返回值表示成功写入的元素数。
对于c语言中输入输出都是相对于程序来说的如图所示
1.4.1 c语言stdin流 stdout流 stderr流
在c语言中只要c语言程序运行起来就会默认打开3个流 stdin流 stdout流 stderr流
这三个流的指针都是FILE*
所以对于fputc‘a’stdout来说就相当于把a打印在屏幕上
对于int ch fgetcstdin来说就相当于从键盘上读一个字符
1.5 文件的随机读写
1.5.1 fseek
fseek 是 C 语言标准库中的一个函数用于在文件中移动文件指针的位置。它的原型如下
int fseek(FILE* stream, long int offset, int origin);fseek 函数接受三个参数
stream指向 FILE 类型的指针表示要移动文件指针的文件流。offset移动的偏移量单位为字节。origin指示移动起点的位置可以是以下三个常量之一 SEEK_SET从文件开头开始计算。SEEK_CUR从当前位置开始计算。SEEK_END从文件末尾开始计算。
fseek 的返回值为 0 表示成功非 0 值则表示出错。
下面是一个示例代码演示了 fseek 函数的用法
#include stdio.hint main() {FILE* file fopen(example.txt, r);if (file NULL) {printf(无法打开文件。\n);return 1;}// 定位到文件结尾fseek(file, 0, SEEK_END);// 获取文件大小long int size ftell(file);printf(文件大小为 %ld 字节。\n, size);// 重新定位到文件开头fseek(file, 0, SEEK_SET);// 读取文件内容char buffer[256];while (fgets(buffer, sizeof(buffer), file) ! NULL) {printf(%s, buffer);}fclose(file);return 0;
}在这个例子中程序打开了一个名为 example.txt 的文本文件。首先我们使用 fseek(file, 0, SEEK_END) 将文件指针移动到文件末尾然后使用 ftell(file) 获取文件大小以字节为单位。接下来我们使用 fseek(file, 0, SEEK_SET) 将文件指针重新定位到文件开头。最后我们使用 fgets 函数从文件中逐行读取内容并将其打印到控制台。
1.5.2 ftell
C 语言中的 ftell 函数用于获取文件指针的当前位置。它返回一个 long 类型的值表示文件指针相对于文件起始位置的偏移量。下面是对 ftell 函数的详细解释以及一个代码示例。
ftell 函数的原型如下
long ftell(FILE *stream);ftell 函数接受一个指向 FILE 结构体的指针作为参数该指针指向要获取偏移量的文件。ftell 函数返回一个 long 类型的值表示文件指针的当前位置相对于文件开始位置的字节偏移量。如果发生错误函数返回 EOF-1。
下面是一个示例代码演示了如何使用 ftell 函数
#include stdio.hint main() {FILE *file;long filesize;// 打开文件file fopen(example.txt, r);if (file NULL) {printf(无法打开文件。\n);return 1;}// 定位文件指针到文件尾部fseek(file, 0, SEEK_END);// 获取文件指针相对于文件开头的偏移量filesize ftell(file);printf(文件大小为 %ld 字节。\n, filesize);// 关闭文件fclose(file);return 0;
}在这个示例中我们打开一个名为 example.txt 的文本文件并将文件指针定位到文件末尾。然后使用 ftell 函数获取文件指针相对于文件开头的偏移量并将结果存储在 filesize 变量中。最后打印文件大小。
请注意fseek 函数用于定位文件指针的位置而 ftell 函数用于获取这个位置的偏移量。
1.5.3 rewind
rewind 是 C 语言标准库 stdio.h 中的一个函数用于将文件指针重新定位到文件的起始位置。它可用于将文件指针移动到文件开头以便再次读取文件内容或重新从起始位置写入数据。
下面是 rewind 函数的原型
void rewind(FILE *stream);该函数接受一个指向已打开文件的指针作为参数并将文件指针重置到文件起始位置。函数不返回任何值。
下面是一个使用 rewind 函数的示例代码
#include stdio.hint main() {FILE *file fopen(example.txt, r); // 以只读模式打开文件if (file NULL) {printf(无法打开文件\n);return 1;}// 读取文件内容char ch;while ((ch fgetc(file)) ! EOF) {printf(%c, ch);}rewind(file); // 将文件指针重置到文件起始位置printf(\n重新读取文件内容\n);while ((ch fgetc(file)) ! EOF) {printf(%c, ch);}fclose(file); // 关闭文件return 0;
}上述代码打开了一个名为 example.txt 的文件并使用 fgetc 函数逐个字符地读取文件内容并输出。然后在文件指针被重置到起始位置后再次读取文件内容并输出。
1.6 文本文件和二进制文件
根据数据的组织形式数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储如果不加转换的输出到外存就是二进制文件。
如果要求在外存上以ASCII码的形式存储则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢
字符一律以ASCII形式存储数值型数据既可以用ASCII形式存储也可以使用二进制形式存储。
如有整数10000如果以ASCII码的形式输出到磁盘则磁盘中占用5个字节每个字符一个字节而二进制形式输出则在磁盘上只占4个字节VS2013测试。 1.7 文件读取结束的判定
feof函数是C语言中用于检测文件结束的函数。下面是关于该函数的详细解释
函数声明
int feof(FILE *stream);函数参数
stream指向文件的指针该文件必须已被打开。
函数返回值
如果stream指向的文件已经到达文件结尾则返回非零值1。如果stream指向的文件尚未到达文件结尾则返回零值0。
使用feof函数时我们首先需要使用fopen函数打开文件然后通过feof函数来确定文件是否已经结束。
下面是一个示例代码演示了如何使用feof函数来判断文件是否结束
#include stdio.hint main() {FILE *file;char ch;file fopen(example.txt, r);if (file NULL) {printf(无法打开文件);return 1;}while (!feof(file)) {ch fgetc(file);if (!feof(file)) {printf(%c, ch);}}fclose(file);return 0;
}在上述示例中我们首先使用fopen函数打开名为example.txt的文件并将返回的文件指针赋值给file变量。如果文件无法打开将输出提示信息并返回1。
然后我们通过一个循环来读取文件中的字符直到feof函数返回非零值即文件结束。在每次循环中我们首先使用fgetc函数从文件中读取一个字符并将其赋值给变量ch。然后我们使用feof函数来判断文件是否结束如果没有结束将输出该字符。
最后我们使用fclose函数关闭文件并返回0表示程序执行成功。
请注意在文件读取过程中feof函数的返回值表示的是上一次读取操作是否成功。当文件读取结束时feof函数的返回值可能是EOF文件结束符也可能是非零值读取失败。因此直接使用feof函数的返回值来判断文件是否结束是不准确的。
原因是文件的结束可能有多种情况。例如在使用C语言的标准库函数进行文件操作时当文件遇到文件末尾时feof函数会返回非0值但并不表示是文件读取失败。实际上只有在上一次读取操作失败如读取错误或出现其他异常情况时feof函数才能真正地指示出读取失败。
因此在文件读取过程中更好的做法是使用读取函数的返回值进行判断。
下面是对文本文件和二进制文件文件结束的可靠判断
文本文件读取是否结束判断返回值是否为 EOF fgetc 或者 NULL fgets 例如 fgetc 判断是否为 EOF . fgets 判断返回值是否为 NULL .二进制文件的读取结束判断判断返回值是否小于实际要读的个数。 例如 fread判断返回值是否小于实际要读的个数。
1.8 文件缓冲区
文件缓冲区File Buffer是在C语言中用于文件输入和输出的一种机制。它主要用于提高文件的读写效率。当我们打开一个文件进行读取或写入操作时操作系统会为该文件分配一个缓冲区通过该缓冲区来进行数据的读取和写入。
ANSIC 标准采用“缓冲文件系统”处理的数据文件的所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据则从磁盘文件中读取数据输入到内存缓冲区充满缓冲区然后再从缓冲区逐个地将数据送到程序数据区程序变量等。缓冲区的大小根据C编译系统决定的。
如图所示 注意fclose在关闭文件的时候也会刷新缓冲区因为有缓冲区的存在C语言在操作文件的时候需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做可能导致读写文件的问题。
二程序环境
在ANSI C的任何一种实现中存在两个不同的环境。
第1种是翻译环境在这个环境中源代码被转换为可执行的机器指令第2种是执行环境它用于实际执行代码
2.2 翻译环境
翻译环境如图所示
下面是一个简图
在C语言中编译器将源代码转换成可执行的机器码的过程分为预编译、编译、汇编和链接四个阶段。下面对这四个阶段进行详细解释 预编译阶段 移除注释移除源代码中的所有注释。处理预处理指令处理以#开头的预处理指令如宏定义、头文件包含等。展开宏定义将源代码中的宏展开为相应的代码片段。宏替换处理条件编译根据条件编译指令如#if、#ifdef、#ifndef等判断编译哪些部分的代码。生成修改后的源文件将预处理后的代码保存为一个新的文件供下一阶段使用。 编译阶段 词法分析和语法分析将源代码分析成词法单元并根据语法规则构建语法树。语义分析检查代码的语义正确性如变量声明与使用是否匹配函数调用参数是否正确等。中间代码生成将语法树转换成中间代码如三地址码、虚拟机指令等。优化对中间代码进行各种优化如常量折叠、公共子表达式消除、循环展开等。目标代码生成将优化后的中间代码翻译成目标机器代码。 汇编阶段 将目标代码转换成汇编语言表示即将机器指令和操作数用助记符表示。汇编器通过符号表解析符号并为每个符号分配相应的内存地址。生成可重定位的机器代码文件目标文件。 链接阶段 解析引用将不完整的目标文件包含对其他模块的引用与其他模块的目标文件进行符号解析找到引用的符号所在的地址。重定位将目标文件的各个模块的绝对地址转换成最终的绝对地址。符号解析将符号引用与符号定义进行匹配并进行地址重定向。内存分配将各个模块的目标代码和数据分配到内存的不同区域。生成可执行文件将链接后的目标文件生成可执行文件包含所有的符号引用和定义。
2.1 运行环境
程序执行的过程 程序必须载入内存中。在有操作系统的环境中一般这个由操作系统完成。在独立的环境中程序的载入必须由手工安排也可能是通过可执行代码置入只读内存来完成。 程序的执行便开始。接着便调用main函数。 开始执行程序代码。这个时候程序将使用一个运行时堆栈stack存储函数的局部变量和返回地址。程序同时也可以使用静态static内存存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。 终止程序。正常终止main函数也有可能是意外终止断电卡死崩溃
三预处理
3.1预定义
预定义符号
FILE //进行编译的源文件LINE //文件当前的行号DATE //文件被编译的日期TIME //文件被编译的时间STDC //如果编译器遵循ANSI C其值为1否则未定义
这些预定义符号都是语言内置的。
举个栗子
printf(file:%s line:%d\n, __FILE__, __LINE__);//打印文件路径和行号3.2 define定义标识符和宏
在C语言中#define指令可以用于定义标识符和宏。 在C语言中#define是一个预处理指令用于定义常量和宏。 define定义标识符常量 #define MAX 1000 define定义宏 #define ADD(x, y) ((x)(y))
和标识符常量类似宏也会被替换比如
#define ADD(x, y) ((x)(y))
ADDxy就会被替换为((x)(y)) 注意ADD(x, y) 是由#define定义的宏而不是函数请注意区分
下面通过一个简单代码例子来说明宏的作用
#define ADD(x, y) (x)(y)
#include stdio.hint main()
{int sum ADD(2, 3);printf(sum %d\n, sum);sum 10*ADD(2, 3);printf(sum %d\n, sum);return 0;
}宏定义是使用#define关键字指定的宏的名称是ADD宏的替换体是(x)(y)。这个宏定义表示在代码中使用ADD(x, y)时会将其替换为(x)(y)。
在main函数中有两个使用了宏的行。
第一行int sum ADD(2, 3);
表示将宏ADD中的x替换为2y替换为3得到的替换结果是(2)(3)这个结果为5。然后将计算结果赋值给变量sum。
第二行sum 10*ADD(2, 3);
表示将宏ADD中的x替换为2y替换为3得到的替换结果是(2)(3)。 请注意 sum 10*ADD(2, 3); 中的ADD23被替换后 式子变成了10 * 23这个结果等于23请不要误以为是2和3相加后再乘10宏只是简单的进行替换并不运算
关于宏的替换规则当使用ADD(x, y)时预处理器会将其替换为(x)(y)。在替换过程中预处理器会将参数x和y直接替换到宏定义中的对应位置。这种替换是简单的文本替换没有类型检查或计算。因此在使用宏时要确保参数和替换体的正确匹配并注意避免出现意料之外的错误。
注意对于字符串里的符号宏是不会替换的
3.3# 和 ##用法
当我们在C语言中定义宏时有时候我们需要把宏参数转化为对应的字符串。在C语言中我们可以使用#运算符来实现这个目标。
下面是一个示例代码演示了如何使用#运算符将宏参数转化为字符串
#include stdio.h#define PRINT_STRING(x) printf(String: %s\n, #x)int main() {char str[] Hello, world!;PRINT_STRING(str);return 0;
}在这个示例中我们定义了一个宏PRINT_STRING它接受一个参数x。使用#运算符我们可以在宏的定义中将参数x转化为对应的字符串。在main()函数中我们传递一个字符串str给宏PRINT_STRING它将打印出字符串String: Hello, world!。
另外还有一个特殊的运算符##称为连接运算符或者拼接运算符。它用于将两个宏参数组合成单个标识符或者拼接成一个更大的标识符。
下面是一个示例代码演示了如何使用##运算符进行参数拼接
#include stdio.h#define CONCAT(a, b) a ## bint main() {int num1 10;int num2 20;int result CONCAT(num, 1) CONCAT(num, 2);printf(Result: %d\n, result);return 0;
}在这个示例中我们定义了一个宏CONCAT它接受两个参数a和b。使用##运算符我们将参数a和b拼接成一个新的标识符。在main()函数中我们使用CONCAT宏将num1和num2拼接成num1和num2然后将它们相加得到结果30。最后我们使用printf函数打印结果。
通过使用#和##运算符我们可以在C语言中更灵活地操作宏参数并且能够实现一些特定的功能。
3.4 宏和函数的对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。那为什么不用函数来完成这个任务
原因有二
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。
宏的缺点当然和函数相比宏也有劣势的地方 3. 每次使用宏的时候一份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。 宏是没法调试的。 宏由于类型无关也就不够严谨。 宏可能会带来运算符优先级的问题导致程容易出现错。
宏和函数的一个对比 命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的一个习惯是
把宏名全部大写函数名不要全部大写
3.5 #undef
#undef 是 C 语言中的一个预处理指令用于取消定义或者称为取消宏定义。当我们使用 #define 定义了一个宏之后如果需要取消这个宏的定义就可以使用 #undef 进行操作。
#undef 的语法形式为
#undef 宏名其中宏名 是要取消定义的宏的名称。
下面我们来通过一个代码示例来说明 #undef 的使用
#include stdio.h#define PI 3.14159int main() {printf(PI的值为%f\n, PI);#undef PIprintf(取消宏定义后PI的值为%f\n, PI); // 这里会报错因为 PI 已经被取消定义了return 0;
}在上述代码中我们首先使用 #define 定义了一个名为 PI 的宏将其值设置为 3.14159。然后在 main 函数中我们通过 printf 输出了宏 PI 的值。
接着我们使用 #undef 取消了宏 PI 的定义。所以在下一个 printf 中我们尝试输出 PI 的值时编译器会提示错误因为 PI 已经被取消定义。
需要注意的是一旦使用 #undef 取消了宏定义那么在后续代码中就无法再使用该宏了否则会导致编译错误。
#undef 可以在任何地方使用不一定要紧跟在宏定义的后面。它的作用范围仅限于当前文件不会影响其他文件。
3.6 文件包含
在C语言中#include 和 #include 是用于包含头文件的预处理指令它们之间有一些区别。
#include 是用于包含系统标准库的头文件。当使用#include 时编译器会在系统的标准库路径下搜索指定的头文件。示例代码如下
#include stdio.h // 标准输入输出头文件int main() {printf(Hello, World!);return 0;
}#include 是用于包含自定义的头文件或者用户创建的头文件。当使用#include 时编译器首先在当前目录下搜索指定的头文件如果找不到则在编译器的包含路径中搜索。示例代码如下
#include myheader.h // 自定义头文件int main() {// 调用自定义函数myFunction();return 0;
}需要注意的是#include 和 #include 的使用是有规范的开发者应该根据具体的情况选择正确的包含方式。一般来说系统提供的标准库使用#include 自定义的头文件使用#include 。这样可以提高代码的可读性和维护性。
对于库文件也可以使用 “” 的形式包含但是这样做查找的效率就低些当然这样也不容易区分是库文件还是本地文件了。
3.7 条件编译
条件编译是C语言中的一种预处理指令它允许在编译过程中根据指定的条件选择性地包含或排除一部分代码。条件编译经常用于根据不同的编译选项或目标平台来选择性地编译代码。
条件编译使用以下指令
#if如果给定的条件为真则编译下面的代码块。#elif如果前面的条件为假并且给定的条件为真则编译下面的代码块。#else如果前面的条件都为假则编译下面的代码块。#endif表示条件编译的结束。
下面是一个简单的示例演示如何使用条件编译
#include stdio.h#define VERSION 2int main() {#if VERSION 1printf(这是版本1\n);#elif VERSION 2printf(这是版本2\n);#elseprintf(这是其他版本\n);#endifreturn 0;
}在上面的示例中根据定义的VERSION宏的值来选择性地编译不同的代码块。如果VERSION的值是1则输出这是版本1如果VERSION的值是2则输出这是版本2如果VERSION的值既不是1也不是2则输出这是其他版本。
通过改变VERSION的值可以选择不同的代码路径进行编译。
使用条件编译可以根据需要去掉或者包含一些代码从而实现在不同条件下的灵活编译。这在处理不同平台的特定代码或者根据编译选项选择性地编译某些功能时非常有用。
#if defined(symbol)//这两种写法是等效的
#ifdef symbol#if !defined(symbol)//这两种写法是等效的
#ifndef symbol3.7嵌套文件包含
如果出现这样的场景
comm.h和comm.c是公共模块。 test1.h和test1.c使用了公共模块。 test2.h和test2.c使用了公共模块。 test.h和test.c使用了test1模块和test2模块。 这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复
如何解决这个问题 答案条件编译。
每个头文件的开头写
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__或者
#pragma once就可以避免头文件的重复引入。