预处理
预处理器
- 预处理器:C语言在编译之前会使用预处理器处理代码;宏替换、文件包含、条件编译。
- 预处理指令:以#开头;预处理指令应该放在代码开头部分;都以#开头,指令前面可以有空格,#和指令之间也可以有,为了兼容老的编译器,一般不留空格。
- 预处理指令不需要分号作为结束符,指令结束是通过换行符来识别的。
- 预处理指令通常不能写在函数内部,有些编译器的扩展允许将预处理指令写在函数里。
宏定义
- 宏定义:用一个标识符(宏名称)来表示一个替换文本。
- 语法: #define 宏名称 替换文本
- 宏名称,一般大写字母表示,以便和变量区分。
- 替换文本:可以含任何字符,可以是字面量、表达式、if语句、函数等,预处理程序不会作检查,直接替换,如有错误,在编译已被宏展开后的源程序时发现。
- 宏定义嵌套:在宏定义的替换文本中可以使用已经定义的宏名,在宏展开时由预处理程序层层替换。
- 取消宏定义:#undef 命令。
- 带参数的宏定义:在宏定义中的称为形参,在宏调用中的称为实参,宏展开时用实参替换形参。
- 带参宏调用形式: 宏名 (实参列表)。
- 带参宏定义中,形参之间可以出现空格,宏名和形参列表之间不能出现空格。
- 带参宏定义中,形参不会分配内存,不必指明数据类型,在宏调用中,实参必须指明数据类型。
- 带参宏定义和函数的区别:
- 宏展开只是文本的替换,不会对表达式进行计算,宏在编译之前就被处理了,不参与编译,也不占用内存。
- 函数是一段可以重复使用的代码,会被编译,会分配内存,每次调用函数,就是执行这块内存中的代码。
#include <stdio.h>
// 带参数宏定义
// 预处理指令#代码,末尾不能有 分号
// 注意: 1. 宏定义的时候形参之间可以有空格
// 2. 宏名字与形参列表之间不能有空格
#define MAX(a, b) a > b ? a : b
#define SQUARE(x) x * x
int main() {
printf("%d\n",MAX(10, 12));
printf("%d\n",SQUARE(12));
return 0;
}
文件包含
- #include 指令用于引入标准库头文件、自定义头文件和其他外部源代码文件。
- 一个源文件可以导入多个头文件,一个头文件可以被多个源文件导入。
- 头文件的扩展名都是 .h。
- 标准库头文件: #include <stdio.h>
- 自定义头文件: #include "文件名.h"
- 相对路径:如果自定义的头文件在源文件的同级目录或在目录的下级目录,使用 ./ 开头的路径, ./ 可以省略;如果自定义的头文件在源文件所在目录的上级或更上级,使用 ../ 开头的路径。
- 绝对路径:从文件系统的盘符(Windows系统)或根目录(Linux系统、MacOS系统)开始,沿着文件系统的目录结构一直到达目标文件。
条件编译
#if
- #if ... #endif : 预处理器的条件判断,满足条件时,内部的行会被编译,否则被编译器忽略。
- #if后面的判断条件通常是一个表达式,如果表达式的值不等于0,表示判断为真,编译内部的语句,如果表达式的值等于0,表示为假,忽略内部的语句。
#include <stdio.h>
#if 1
int a = 100;
double kill = 3.23;
char ch = 'a';
#endif
#if 1 - 1
可以作为注释
#endif
int main() {
// if条件编译
printf("%d %lf\n", a, kill);
return 0;
}
- #if ... #else ... #endif:#if ... #endif之间可以加入#else指令,用于指定判断条件不成立时,需要编译的语句。
#include <stdio.h>
#define OK 0
#if OK
int a = 10;
double b = 2.43;
#else
double PI = 3.14;
int arr[] = {10, 20, 30};
typedef struct {
char *name;
} Students;
#endif
int main() {
printf("%lf\n", PI);
return 0;
}
- #if ... #elif ... #else ... #endif:如果有多个判断条件,可以加入#elif命令。
#include <stdio.h>
int main() {
#define OK 1
#if OK == 1
printf("我叫 tom"); // 预处理指令再全局中不能打印输出,这能打印是编译器做的
#elif OK == 2
printf("welcome");
#else
printf("thank you");
#endif
printf("Hello World!\n");
return 0;
}
#ifdef / #if defined
- #ifdef ... #endif:判断某个宏是否定义过。
- #ifdef 与#if defined 相同。
#include <stdio.h>
#include "./src/stdc89.h"
#include "./src/stdc89.h"
// 判断是否存在宏
#define INT
#ifdef INT
int a = 1000;
#else
int a = 10;
#endif
int main() {
printf("%d\n", a);
printf("%d\n", d);
return 0;
}
#ifndef
- 3ifndef‘...#endif指令跟#ifdef ... #endif 正好相反。用来判断如果某个宏没有被定义过。
#include <stdio.h>
#ifndef BOOL
#define BOOL 1
#endif
int main() {
printf("%d\n", BOOL);
return 0;
}
预处理命令总结:
指令 | 说明 |
#include | 包含一个源代码文件 |
#define | 定义宏 |
#undef | 取消已定义的宏 |
#if | 如果给定条件为真,则编译下面代码 |
#ifdef | 如果宏已经定义,则编译下面代码 |
#ifndef | 如果宏没有定义,则编译下面代码 |
#elif | 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个#if……#else条件编译块 |
文件操作
- 文件基本介绍:文件最主要的作用就是保存文件。文件中数据的输入输出操作以 流的方式进行。
- 输入流:数据从数据源(文件、屏幕)到程序(内存)的传输路径。
- 输出流:数据从程序(内存)到数据源(文件、屏幕)的传输路径。
输入 & 输出
- 标准文件:以下的文件会在程序执行时自动打开,以便访问屏幕和键盘。
标准文件 | 文件指针 | 设备 |
标准输入 | stdin | 键盘 |
标准输出 | stdout | 屏幕 |
标准错误 | stderr | 屏幕 |
文件指针:访问文件的方式。
- getchar() 和 putchar() 函数:
- getchar() :用于从标准输入流(键盘)读取一个字符,这个函数在同一时间内只会读取一个单一的字符。
- 函数原型:int getchar(void);
- 返回值:返回一个整数,表示读取的字符。
- putchar() : 将一个字符写入到标准输出流(屏幕),在同一时间内只会输出一个单一的字符。
- 函数原型:int putchar(int character);
- 返回值:返回一个整数,是写入的字符的ASCII码值。
- 参数:character 是要写入的字符的ASCII码值。
- getchar() :用于从标准输入流(键盘)读取一个字符,这个函数在同一时间内只会读取一个单一的字符。
#include <stdio.h>
int main() {
// 从键盘输入一个字符,用char类型变量存储
char ch = getchar();
// 输出打印字符
putchar(ch);
return 0;
}
2.gets() 和 puts() 函数
- gets()函数: 从标准输入流(键盘)读取一行文本,并将其存储到一个字符数组中,直到遇到换行符。
- 函数原型:char *gets( char *str)
- 返回值:返回一个指向存储在str中的字符串的指针。
- 参数:字符数组的指针,用于存储读取的输入数据。
- puts() 函数:将字符串输出到标准输出流(屏幕),并自动添加换行符,它接受一个字符串作为参数,然后将其显示到屏幕上。
- 函数原型:int puts(const char *str)
- 返回子:返回成功写入的字符数,如果写入失败或出现错误,返回特殊值 EOF(EOF是一个定义在stdio.h头文件中的常量,值通常为 -1);
- 参数: str 是要输出的字符串。
#include <stdio.h>
int main() {
// 定义字符数组:用来保存用户从键盘输入的数据
char buffer[100];
// 输入
gets(buffer);
// 输出
puts(buffer);
return 0;
}
文件读写
打开文件
- fopen() 函数:创建一个新的文件或者打开一个已有的文件。函数位于stdio.h 头文件中。
- 函数原型: FILE *fopen(const char *filename, const char *mode)
- 返回值:返回一个指向FILE结构的指针,该结构表示文件流,使用这个指针来及逆行文件的读取和写入操作。如果打开失败,返回一个空指针(NULL)。
- 参数:
- filename是一个以字符串形式指定的文件,表示要打开的文件的名称。可以包括文件路径和文件名;
- mode是一个以字符串形式指定的打开模式。
模式 | 描述 |
r | 只读模式。 打开一个已有的文本文件,只允许读取文件。 |
w | 只写模式。 打开一个文本文件,从头写入文件。如果文件不存在,则会创建一个新文件并写入;如果文件存在,则清空文件并从头写入。 |
a | 追加模式。 打开一个文本文件,追加写入文件。如果文件不存在,则会创建一个新文件并写入;如果文件存在,在已有内容后面追加写入。 |
rb | 只读二进制模式。 打开一个已有的二进制文件,只允许读取文件。 |
wb | 只写二进制模式。 打开一个二进制文件,从头写入文件。如果文件不存在,则会创建一个新文件并写入;如果文件存在,则清空文件并从头写入。 |
ab | 追加二进制模式。 打开一个二进制文件,追加写入文件。如果文件不存在,则会创建一个新文件并写入;如果文件存在,在已有内容后面追加写入。 |
r+ | 读写模式。 打开一个文本文件,允许读写文件。 |
w+ | 读写模式。 打开一个文本文件,从头读写文件。如果文件不存在,则会创建一个新文件并读写;如果文件存在,则清空文件并从头读写。 |
a+ | 读写模式。 打开一个文本文件,读取或追加写入文件。如果文件不存在,则会创建一个新文件并读写;如果文件存在,读取或在已有内容后面追加写入。 |
rb+ | 读写二进制模式。 打开一个二进制文件,允许读写文件。 |
r+b | |
wb+ | 读写二进制模式。 打开一个二进制文件,从头读写文件。如果文件不存在,则会创建一个新文件并读写;如果文件存在,则清空文件并从头读写。 |
w+b | |
ab+ | 读写二进制模式。 打开一个二进制文件,读取或追加写入文件。如果文件不存在,则会创建一个新文件并读写;如果文件存在,读取或在已有内容后面追加写入。 |
a+b |
关闭文件
- fclose() 函数:关闭文件,也位于stdio.h 头文件中。
- 函数原型: int fclose(FILE *stream)
- 返回值:返回一个整数值,通常为零(0),表示关闭操作成功,如果关闭失败,返回特殊值EOF;
- 参数:stream是一个指向FILE结构的指针,表示要关闭的文件流。
写入文件
fputc()
- fputc() 函数:逐字符写入文件,位于stdio.h 头文件中。
- 函数原型:int fputc(int character, FILE *stream)
- 返回值:返回一个整数值,通常是写入的字符的ASCII码值,如果写入成功,返回的值与输入的character值相同,如果失败,返回特殊值EOF;
- 参数:
- character:要写入的字符,以整数的形式表示,即字符的ASCII码值。
- stream:是一个指向FILE结构的指针,表示要写入字符的文件流。
#include <stdio.h>
int main() {
// C语言中操作文件
// 第一个参数:文件名字[字符串即可] 第二个参数:模式 demo
FILE *file = fopen("a.txt", "a"); // a 追加模式
// 判断文件是否创建成功
if (file != NULL) {
// 写入字符
for (int i = 0; i < 10; i++) {
fputc('a', file);
}
}
// 关闭文件
fclose(file);
return 0;
}
fputs()
- fputs()函数:将字符串写入文件,位于stdio.h头文件中。
- 函数原型:int fputs(const char *str, FILE *stream)
- 返回值:返回一个整数值,如果写入成功,返回非负整数(通常是写入的字符数),否则返回特殊值EOF;
- 参数:
- str 是要写入的字符串,通常以const char *指针的形式传递。
- stream是指向输出流的指针,通常是文件指针。
#include <stdio.h>
int main() {
// 文件操作
FILE *file = fopen("b.txt", "a");
if (file != NULL) {
char *str = "我成功的写入字符串!";
fputs(str, file);
}
fclose(file);
return 0;
}
fprintf()
- fprintf()函数:格式化写入问价,位于stdio.h头文件中。
- 函数原型:int fprintf( FILE *stream, const char *format, ...)
- 返回值:返回一个整数值,如果写入成功,返回非负整数(通常是写入的字符数),否则返回特殊值EOF;
- 参数:
- stream是一个指向FILE结构的指针,表示要写入的文件流。
- format是一个格式化字符串,类似于printf()函数中的格式化字符串。
- .... 表示可变数量的参数,根据格式化字符串中的格式占位符对应。
#include <stdio.h>
int main() {
FILE *file = fopen("c.txt", "a");
if (file != NULL) {
fprintf(file, "我的%s", "宝贝");
}
fclose(file);
return 0;
}
读取文件
fgetc()
- fgetc() 函数:从文件中逐字符读取,位于stdio.h 头文件中。
- 函数原型:int fgetc(FILE *stream)
- 返回值:如果读取成功,返回读取字符的ASCII码值(0-255之间的整数),如果到达文件结束或发生错误,返回特殊值EOF。
- 参数:stream是一个指向FILE结构的指针,表示要写入字符的文件流。
fgets()
- fgets():从文件中逐行读取,遇到换行符读取结束,读取的内容包含换行符,位于stdio.h头文件中。
- 函数原型:char *fgets(char * str , int num , FILE *stream)
- 返回值:如果读取成功,返回指向str的指针,如果到达文件结束或发生错误,返回一个空指针(NULL);
- 参数:
- str是一个指向字符数组的指针,用于存储读取的字符串;
- num是要读取的最大字符数(包括字符串终止符 \0),通常是str 的长度;
- stream是一个文件流,通常是标准输入流(stdin)或其他文件流,用于指定从哪里读取数据。
fscanf()
- fscanf()函数:从文件中解析数据并存储到变量中,使用空白字符(空格、制表符、换行符等)分隔内容赋值给不同的变量,该函数位于标准库的stdio.h头文件中。
- 函数原型:int fscanf(FILE *stream, const char *format, ....)
- 返回值:返回成功读取的分配的参数数目,如果没有成功读取任何参数,返回0,如果读取过程中发生错误,返回特殊值EOF;
- 参数:
- stream:一个指向FILE结构的指针,表示要从中读取数据的文件流。
- format:是一个格式化字符串,类似于prinft()函数中的格式化字符串。
- ....表示可变数量的参数,根据格式化字符串中的格式指定要存储数据的变量。
#include <stdio.h>
int main() {
// 读取模式
FILE *file = fopen("d.txt", "r");
if (file != NULL) {
// char ch = fgetc(file);
// printf("%c\n",ch); // 读取一个字符
// char ch1 = fgetc(file);
// printf("%c\n", ch1); // 读取一个字符
// char buffer[5];
// fgets(buffer, 5, file); // 将字符数组的 结尾 \0 也计算进去
// printf("%s\n", buffer);
char buffer1[100];
fscanf(file, "%s", buffer1); // buffer 字符数组不用 &
printf("%s\n", buffer1);
}
fclose(file);
return 0;
}