C语言文件操作&预处理

144 阅读8分钟

C语言文件操作&预处理

文件操作

文件的打开和关闭

文件: 内存中的数据持久化到硬盘上

文件指针: 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE.

truct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息

一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。

截屏2024-08-15 18.17.57.png

打开和关闭文件函数
//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );

打开模式: r--读 w--写 a--追加 rb--读二进制数据 wb--写二进制数据

截屏2024-08-15 18.20.30.png

文件的顺序读写

C语言程序,只要运行起来 默认就打开三个流

  1. 标准输入流 stdin File*
  2. 标准输出流 stdout File*
  3. 标准错误流 stderr File*

输入输出都是相对于程序来说的

读 每一次读 会读当前位置的下一个位置

字符输入函数fgetc
int fgetc(FILE *stream)

参数: stream --> 指向FILE对象的指针,FILE对象上有维护流

返回值: 该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF

字符输出函数fputc
int fputc(int ch, FILE *stream)

参数:ch--要输出的字符, stream--文件指针

返回值:如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

文本行输入函数fgets(重要)
char *fgets(char *str, int n, FILE *stream)

str 指向字符数组的指针 数组作为一个缓冲区存储要读取的字符串 n 要读取的一大字符数,通常是str传递的数组长度 stream 文件指针

返回值: 如果成功,返回相同的str参数,如果到达文件末尾或者没有读到任何字符,返回一个空指针

文本行输出函数fputs(重要)
int fputs(const char *str, FILE *stream)

str -- 数组 包含了要写入的字符串

stream -- 文件指针

返回值 该函数返回一个非负值,如果发生错误则返回 EOF

格式化输入函数fscanf
int fscanf(FILE *stream, const char *format, ...)

参数 stream 文件指针 format格式串(例如“%d,%d") ...占位符对应的值

返回值 如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF

格式化输出函数fprintf
int fprintf(FILE *stream, const char *format, ...)

参数功能相同

返回值 如果成功,则返回写入的字符总数,否则返回一个负数。

二进制输入fread(重要)
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

参数: ptr 内存块的指针 size每个元素的大小(单位字节),nmemb元素的个数,stream 文件指针

返回值: 成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

二进制输出fwrite(重要)
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

参数: ptr内存块指针,size写入元素大小,nmemb元素个数,stream文件指针

返回值: 如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。

几种输入输出函数的对比:

scanf/fscanf/sscanf printf/fprintf/sprintf

scanf 从标准输入流读取格式化的数据

printf 向标准输出流写格式化的数据

fscanf 适用于所有输入流的格式化输入函数

fprintf 适用于所有输出流的格式化输出函数

sprintf 把一个格式化的数据转化为字符串

sscanf 从字符串中读取格式化的数据

文件随机读写

fseek
int fseek ( FILE * stream, long int offset, int origin );
//根据文件指针的位置和偏移量来定位文件指针

stream 文件指针 offset偏移量 origin起始位置

SEEK_SET 起始位置 SEEK_END 结束位置 SEEK_CUR 当前位置

ftell
long int ftell(FILE* stream);//返回文件指针相对于其实位置的偏移量
//不知道文件读到哪里了,可以使用这个函数返回偏移量
rewind
void rewind(FILE *stream) //让文件指针的位置返回到文件的起始位置

文本文件和二进制文件

数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件 10000 可以以字符的方式(5个字节),也可以以整数的形式(4个字节,int变量)存入

文件缓冲区

缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。

从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。

如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。

截屏2024-08-15 20.33.44.png

强制刷新 --》 调用fflush函数 将文件指针传入/ fclose 关闭文件时会强制刷新缓冲区

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件

预处理

编译+链接

截屏2024-08-15 20.36.27.png

组成一个程序的每个源文件是先单独编译为目标文件 再由链接器链接为一个可执行文件的

链接器还会引入标准C函数库中被程序用到的函数,链接到程序中

编译的过程: 1. 预处理(结果.i文件) 2. 编译(结果.s文件) 3.汇编(结果.o文件)

预处理: 注释的删除 头文件包含 #define符号的替换 其实就是只做文本操作 所有的预处理指令都是在预处理阶段处理的

编译: 把C语言代码翻译为汇编指令,语法分析 词法分析 语义分析 符号汇总 --》对代码有充分的理解 才能够编译为汇编代码

汇编: 把汇编代码翻译为二进制指令形成符号表 链接期间会查看这个符号表(不包括局部变量)

链接: 1. 合并段表 2. 符号表的合并和重定位

程序执行: 将程序载入内存中,执行,调用函数,创建运行时堆栈

#define

定义标识符

本质: 就是将程序代码中的预定义符号替换为相关内容

#define MAX 1000//这里不要加分号,不然分号也会带入进行替换造成错误
定义宏

define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义 宏(define macro)(在预处理阶段进行替换)

#define name( parament-list ) stuff

参数列表的左括号必须与name紧邻。

如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

宏只会单纯的进行替换,不带括号可能会出现运算符优先级错误(比如想要加法之后再进行乘法,结果是先进行乘法了) 所以宏要把整体用括号围起来,对运算符优先级的规定也要用括号进行表明,(外围最好也用括号括起来)

宏不能进行递归

使用# ,把一个宏参数变成对应的字符串

#define PRINT(FORMAT, VALUE) printf("the value of " #VALUE "is "FORMAT "\n", VALUE);

## 可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。

宏的参数不能有副作用(如a++,b++),因为会替换多个位置,造成无法预料的后果

宏与函数的对比
  1. 小规模运算,用宏效率高,因为函数调用需要开辟栈帧,存在资源消耗
  2. 函数只能在类型合适的表达式使用,宏与类型无关
  3. 宏如果长了,会造成替换文本时程序代码长度过大,编译效率下降
  4. 宏无法调试(在预处理阶段已经被处理了)
  5. 可能会带来运算符优先级的问题
  6. 宏可以传递类型(函数无法做到)

命名规定: 宏名字全部大写, 函数名不要全部大写

命令行定义

我们可以在程序运算的时候再把宏定义, 适用于编译程序的不同版本 用-D选项

gcc -D ARRAY_SIZE=10 programe.c
条件编译

用#if #endif #elif #else #ifdefined(宏)(是否被定义) #ifndefined(宏)(是否没有被定义) 组合可以形成条件决定其中的代码是否需要被编译

文件包含

使用# pragma once避免这个文件的内容被多次重复引入

# include <> 表示去标准库路径下查找

# include "" 表示先在本地路径下查找,找不到了才去标准库路径下找

自定义的头文件中不能定义全局变量,否则如果有多个文件时链接可能会产生冲突