C语言进阶篇(文件操作)

一.文件的种类(从文件功能的角度来分类的)

1.1程序文件

包括源程序文件(后缀为.c), 目标文件(windows 环境后缀为.obj), 可执行程序(windows 环境后缀为.exe)。

1.2数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

1.3文件名

文件名包括三部分:文件路径+文件名主干+文件后缀

例如:c:\code\test.txt

二.文件的打开和关闭

2.1文件指针

每个被使用的文件都会在内存中开辟一个相应的文件信息区,用来存放文件的相关信息,这些信息保存在一个结构体变量中 ,该结构体类型有系统声明,取名为FILE,也就是说FILE是一个结构体 。

此为VS2013提供的stdio.h头文件中有的文件类型申明

当我们使用fopen打开文件时,会在内存里创建文件信息区,并且会将文件信息区的起始地址返回回来

一般都是通过FILE的指针来维护FILE结构中的变量,即:

FILE* pf     //文件指针变量

可通过pf指向某个文件的文件信息区,再通过文件信息区访问文件,即:通过文件指针变量能够找到与它关联的文件。

2.2文件的打开和关闭

在打开文件时,都会返回一个FILE*的指针变量指向该文件

根据规定:使用fopen函数来打开文件,使用fclose来关闭文件。

fopen:

格式:FILE* fopen(const char* filename, const char* mode);     //FILE*是fopen的返回类型

  • 第一个参数是文件名(相对路径)。
  • 第二个参数是打开模式,常用的有:
模式含义核心特性
r只读打开已存在的文件,文件不存在则打开失败
w只写文件不存在则创建,文件已存在则清空内容(覆盖)//在fopen打开文件时清空
a追加文件不存在则创建,写入内容追加到文件末尾
r+读写打开已存在的文件,可读可写,不清空内容
w+读写文件不存在则创建,已存在则清空内容,可读可写
a+读写文件不存在则创建,写入追加到末尾,可读

如果在后面加上b,即rb、wb等,就表示是二进制模式
 

fclose:

格式:int fclose ( FILE * stream );

使用示例:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","r");//若打开失败,则pf会返回空指针
if(pf==NULL)
{
printf("%s\n",strerror(errno));
return 1;
}

//关闭文件
fclose(pf);
pf=NULL;
return 0;
}

路径分为相对路径和绝对路径:

  • 相对路径:以当前所在目录为起点,简写路径,依赖当前位置。                                                           例:"text.c"
  • 绝对路径:从根目录开始,完整、唯一的路径,不受当前所在位置影响。                                           例:"C:\Users\hpl13\Desktop\text.c"

注:当在代码中使用fopen打开绝对路径时应用\\  例:  FILE*pf=fopen("C:\\Users\\hpl13\\Desktop\\text.c","r");

三.文件的顺序读写

3.1关于文件读写的函数

功能函数名适用于
字符输入函数(读一个字符)fgetc                    所有输入流        
字符输出函数(写一个字符)fputc所有输出流
文本行输入函数(读一行字符)fgets所有输入流
文本行输出函数(写一行字符)fputs所有输入流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输入流
二进制输入fread文件
二进制输出fwrite文件

输入=读,输出=写

  • 键盘输入、文件读取、网络数据接收,本质上都是数据从外部流向程序,这叫输入流
  • 屏幕打印、文件写入、网络数据发送,本质上都是数据从程序流向外部,这叫输出流

fgetc(读一个字符)

格式:int fgetc ( FILE * stream );

使用示例:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","r");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
}
//读文件
int ch=fgetc(pf);
printf("%c\n"ch);//一次只读一个字符
//以下方法可将文件内的内容全部读出来:
int ch=0;
while((ch=fgetc(pf))!=EOF)
{
printf("%c ",ch);
}
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}

fputc(写一个字符)

格式:int fputc ( int character, FILE * stream );

  • 第一个参数是写入的字符。
  • 第二个参数是文件指针变量名。

使用示例:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","w");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
}
//写文件
int i=0;
for(i='a';i<='z';i++);
{
 fputc(i,pf);
}
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}

fgets(读一行字符)

格式:char * fgets ( char * str, int num, FILE * stream );

  • 第一个参数是指向一个字符数组的指针,读取的字符串将被复制到该数组中。
  • 第二个参数是读取的字节数(包括\0,且\0算一个字符)
  • 第三个参数文件指针变量名。

使用示例:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","r");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
}
//读一行字符
char arr[20];
fgets(arr,5,pf);
printf("%s",arr)
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}

fputs(写一行字符)

格式:int fputs ( const char * str, FILE * stream );

使用示例:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","w");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
}
//写一行字符
fputs("hello world",pf);
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}

补充:perror

头文件:#include<stdio.h>

当我们判断pf是否为空指针NULL时,可用perror来代替printf("%s",strerror(errno));

例:

FILE*pf=fopen("text.c","w");
if(pf==NULL)
{
perror(fopen:);
}

结果:

fprintf(格式化写入)

格式:int fprintf ( FILE * stream, const char * format, ... );//与printf相似,只比printf多了第一个参数

使用示例:

struct S
{
char name[10];
int age;
float weight;
};
int main()
{
struct S s={"zhangsan",13,50.2};
FILE*pf=fopen("text.c","w");
if(pf==NULL)
{
perror(fopen);
}
fprintf(pf,"%s %d %f",s.name,s.age,s.weight);
fclose(pf);
pf=NULL;
return 0;
}

fscanf(格式化读)

格式:int fscanf ( FILE * stream, const char * format, ... );//与scanf相似,只比scanf多了第一个参数

使用示例:

struct S
{
char name[10];
int age;
float weight;
};
int main()
{
struct S s={0};
FILE*pf=fopen("text.c","r");
if(pf==NULL)
{
perror(fopen);
}
fscanf(pf,"%s %d %f",s.name,&(s.age),&(s.weight));
printf("%s %d %f",s.name,s.age,s.weight);
fclose(pf);
pf=NULL;
return 0;
}

补充:流

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

FILE* stdin - 标准输入流(键盘)

FILE* stdout - 标准输出流(屏幕)

FILE* stderror - 标准错误流(屏幕)

如果将fprintf的第一个参数由pf换为stdout,那么就会写入到屏幕上,对于fputs和fputc也一样

fwrite(二进制方法写)

格式:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

  • 第一个参数是指向你要写入文件的数据的起始地址(数据从哪里来)
  • 第二个参数是一个元素的大小
  • 第三个参数是有几个这样的元素

使用实例:

  1. FILE*pf={"test.c","wd"};
    int arr[] = {10,20,30};
    fwrite(arr, sizeof(int), 3, pf);
  2. FILE*pf={"test.c","wd"};
    struct S s={"zhangsan",18,65.2f};
    fwrite(&s,sizeof(struct S),1,pf);

通过二进制的方式写进去,这会让我们肉眼看不懂,这时就应该用fread以二进制的方式来读

fread(二进制方法读)

格式:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );//与fwrite相同

使用示例:

FILE*pf={"test.c","rd"};
struct S s={"zhangsan",18,65.2};
fread(&s,sizeof(struct S),1,pf);
printf("%s %d %f",s.arr,s.age,s.weight);

3.2关于一些函数的差异

scanf 是针对标准输入的格式化输入语句(键盘)

printf 是针对标准输出的格式化输出语句(键盘)

fscanf 是针对所有输入流的格式化输入语句(文件、键盘输入都可以)

fprintf 是针对所有输出流的格式化输出语句(文件、键盘输入都可以)

sscanf

作用:从一个字符串中转化出一个格式化的数据

#include<stdio.h>
struct S 
{
char name[20];
int  age;
float weight;
};
int main()
{
struct S s={"zhangsan",18,65.2f};
struct S tmp = { 0 };
char buf[100] = { 0 };
//把s中的格式化数据转化成字符串放到buf中
sprintf(buf, "%s %d %f", s.arr, s.age, s.score);

//"zhangsan 20 55.500000";
printf("字符串: %s\n", buf);

//从字符串buf中获取一个格式化的数据到tmp中
sscanf(buf, "%s %d %f", tmp.arr, &(tmp.age), &(tmp.score));
printf("格式化: %s %d %f\n", tmp.arr, tmp.age, tmp.score);
return 0
}

sprintf

作用:把一个格式化的数据写到字符串中,本质是把一个格式化的数据转换成字符串。

格式:int sprintf ( char * str, const char * format, ... );//与printf相似,只比其多了第一个参数

使用示例:

#include<stdio.h>
struct S 
{
char name[20];
int  age;
float weight;
};
int main()
{
struct S s={"zhangsan",18,65.2f};
char buf[100]={0};
sprintf(buf,"%s %d %f",s.name,s.age,s.weight);
printf("%s\n",buf);
return 0;
}

四.文件的随机读写

4.1fseek

作用:指定文件流的读写位置,想从文件哪里读 / 写,就把指针移到哪里。

格式:int fseek ( FILE * stream, long int offset, int origin );

  • 第二个参数是偏移量
  • 第三个参数是起始位置
常量参考位置
SEEK_SET文件开头
SEEK_CUR文件指针的当前位置
SEEK_END文件末尾

使用示例:

#include<stdio.h>
int main()
{
FILE*pf=fopen("test.c","r");//test.c文件里存放的内容:abcdef
if(pf==NULL)
{
perror(open:);
return 1;
}
fseek(pf,2,SEEK_SET);//与文件开头位置的偏移量为2(偏移量可以为负数)
int ch=fputc(pf);
printf("%c",ch);//c
fseek(pf,2,SEEK_CUR);
ch=fputc(pf);
printf("%c",ch);//f
return 0;
}

疑问:第二fseek为什么偏移量是 2 而不是 3?

解释:关键在于:fgetc 读取一个字符后,文件指针会自动向后移动 1 位

4.2ftell

作用:返回文件指针当前的读写位置(距离文件开头的字节偏移量)。

格式:long int ftell ( FILE * stream );

使用示例:

fseek(pf,2,SEEK_SET);//与文件开头位置的偏移量为2(偏移量可以为负数)
int ch=fputc(pf);
printf("%c\n",ch);//c
printf("%d\n",ftell(pf));//3
fseek(pf,2,SEEK_CUR);
ch=fputc(pf);
printf("%c",ch);//f
printf("%d\n",ftell(pf));//6

4.3rewind

作用:让文件指针的位置回到文件的起始位置

格式:void rewind ( FILE * stream );

使用示例:

fseek(pf,2,SEEK_SET);
int ch=fputc(pf);
printf("%c\n",ch);//c
printf("%d\n",ftell(pf));//3
rewind(pf);
ch=fputc(pf);
printf("%c",ch);//a
printf("%d\n",ftell(pf));//1

五.文本文件和二进制文件

根据数据的组织形式,数据文件被称为文本文件或者二进制文件

二进制文件:数据在内存中以二进制的形式存储,不加转换的输出到外存(我们看不懂)

文本文件:在外存上以ASCLL字符的形式存储的文件(我们看得懂)

数据在内存中的存储方式:

  • 字符一律以ASCLL形式存储
  • 数值型数据既可以以ASCLL形式存储,也可以以二进制形式存储

以10000为例:

把10000转换为16进制,也就是 10000 = 0x00002710

当为大段存储时:00 00 27 10

当为小段存储时:10 27 00 00

六.文件读取结束的判定

6.1错误地使用feof

6.1.1feof

作用:检测上一次读操作是否遇到了文件末尾千万不能用来预判文件有没有结束

应用于:判断是否读取失败结束,还是遇到文件尾结束

格式:int feof ( FILE * stream );

返回值:文件结束 → 返回非 0 值没结束 → 返回 0

    feof(pf)!=0:上一次读操作正常碰到文件末尾,正常结束

使用示例:

char buf[100];
while(fgets(buf, 100, fp) != NULL) 
{
    printf("%s", buf);
}

if(feof(fp)) 
{
    printf("正常读到末尾\n");
}

6.1.2ferror

格式:int ferror ( FILE * stream );

作用:判断是否因【读取错误】结束

返回值:上一次文件操作发生错误->返回非 0;操作正常,无错误->返回 0

  ferror(fp) != 0:上一次读操作发生 IO 错误,异常失败结束

总结:

  • feof:只管是不是撞到文件尾、正常读完
  • ferror:只管是不是读写错误、异常失败

6.1.3用来判断文件是否读取结束的方法

1.文本文件是否读取结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)       

  • fgetc判断是否为EOF
  • fgets判断返回值是否为NULL                                  

2.二进制文件是否读取结束,判断返回值是否小于实际要读的个数

  • fread判断返回值是否小于实际要读的个数

count是我们实际要读的个数(我们要fread读的),而fread的返回值等于fread实际读到值的个数

  • 文件读到末尾:已经读完文件里所有有效数据,此时缓冲区还有最后一个有效数据
  • 文件读取结束:在读完所有数据后,又尝试再读一次,读操作失败

七.文件缓冲区

文件缓冲区:是C 语言标准库在内存中开辟的一块临时存储区域,用来暂存程序和磁盘文件之间的数据。缓冲区的大小根据编译系统决定

数据先写到输出缓冲区中,等到输出缓冲区满了后,再一次性写到硬盘中,但是也有库函数可以设置缓冲区的大小

因为有缓冲区的存在,C 语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件有问题

内容概要:本文系统深入地讲解了VS Code代码高亮自定义的底层原理与全链路实践技术,涵盖从基础配置到专家级主题开发的完整知识体系。文章首先剖析了VS Code高亮系统的分层架构、TextMate作用域规范及token分词机制,明确了语法解析、作用域匹配与主题渲染的核心流程。随后提出三大自定义层级:轻量化配置(基于settings.json快速调整基础语法元素)、精细化Scope定制(利用textMateRules实现多语言差异与细粒度控制)以及完整主题开发(通过Yeoman脚手架创建可发布的独立主题)。文中提供了适用于Python、JavaScript、Java、C/C++、Go、HTML/CSS等主流语言的专属高亮方案,并融合工业级护眼配色美学原则,强调低饱和、层级清晰、主次分明的视觉设计。同时配套作用域查询工具使用、故障排查、配置优先级、团队同步等工程化落地策略,形成闭环的技术指南。; 适合人群:具备基本编程经验的开发者,尤其是希望提升编码效率与视觉体验的前端、后端、全栈及跨语言开发人员,适用于工作1-5年并有个性化编辑器定制需求的技术人员;; 使用场景及目标:①解决默认主题高亮模糊、配色刺眼、语法区分度低等问题;②实现多语言差异化高亮与团队统一视觉规范;③开发发布专属VS Code主题;④构建护眼、高效、美观的个性化编码环境; 阅读建议:学习过程中应结合VS Code实际环境操作,利用Developer: Inspect Editor Tokens and Scopes工具验证作用域,优先从轻量化配置入手,逐步过渡到精细化规则与主题开发,注意配置优先级与冲突排查,同时参考文中的标准化配色模板与避坑准则,确保美观性与稳定性兼顾。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值