【记录】C-文件输入输出
写在开头
摸鱼摸得昏天黑地,是该炸一炸鱼的本愿了~ 以前总觉得笔记没什么重要的,但有时事到临头,又十分渴求简明的提纲来唤起记忆/提供重学的门路。于是就有了以下的产物,也希望能抑制一下无效摸鱼的堕落,以免不久的将来再次被现实逼下马。
正文
C把文件看作是一系列连续的字节。
文件:存储在外部介质(硬盘、U盘、DVD等)上数据的有命名集合,是操作系统数据管理的单位
- 正文文件:文本文件,随字节按照某种字符集(如ASCII、Unicode)进行编码,内容均看作字符
- 二进制文件:数据按照其在内存中的存储形式原样存放
注意不同系统下不同的文件格式
C的文件操作范式
文件类型指针:是一种文件结构体
作业练习而有所得
1. 一些声明
FILE *fp;
char filename[32],s[81],line[1024];
//文件名一般不超过32个字符,显示器上一行通常80个字符,文件最大物理行长度一般为1024
char buf[BUFSIZE];
//在stdio.h中定义,作用如其意,缓冲存储中间处理的字符串
char ch;
2. 养成好习惯,凡是打开文件,便要确认是否成功打开;配上随手关文件
if( (fp=fopen(filename,"r"))==NULL )
{
perror(filename);
return -1;
}
3. fgetc()与fputc()
int fgetc(FILE *stream)
返回所读字符,出错则EOF
int fputc(int c,FILE *stream)
返回所输出字符,出错则EOF
//从一端读取,输入到另外一端,若为stdin/stdout,可改为getchar()与putchar()
while((ch=fgetc(fp))!=EOF)
fputc(ch,fp) //'\n'被同样处理,刚刚好
联合使用,灵活运用返回值
FILE *fin,*fout;
while(!feof(fin))
fputc(fgetc(fin),fout);
4. fopen()中filename的规则
以编译时文件所在目录为起始,格式为路径名称。(“..”表示后从当前目录回退一层)
char filename[]="..\\testdata\\fileio_example.c";
//当前目录回退一层,再进入testdata目录下的文件xxx 同时注意转义字符表示'\'
5. fscanf()与fprintf()
类比sscanf()与sprintf(),字符串改为文件指针
返回值:成功I/O的个数;EOF
6. 对于返回指针的函数:C不支持调用函数时返回局部变量地址
warning:function returns address of local variable
原因:函数调用完后,存储在栈空间的数据会被自动释放,那指针所指的地方就毫无意义了!
解决办法:
①加static
变为局部全局变量,存储在静态区。
②使用malloc()
分配空间,存储在堆空间。
7. 对多维数组的正确理解:以数组为元素的数组
因此,传递参数时的形参不能简单的用多维指针,不要被平时“x维数组”的称呼而轻视了本质
void function(int (*grade)[3][4])//传递一个指向 [3][4]二维数组 的指针
void function(int grade[][3][4]) //也可
void function(int ***grade) //错误!
8. 打表手段'\t'以及认识'\r'
'\t'
:字符长模8后,不足8个字符的用空格凑足8个字符
'\r'
:回车后,内容会被覆盖的
9. fgets()与fputs()
函数原型(注意返回值):
char *fgets(char *str,int size,FILE *stream);
返回指向自身的指针,若没有则为NULL,很自然
int *fputs(char *str,FILE *stream);
返回写入的最后一个字符,出错则为EOF
//作业 fileio_schedule.c
//向reminder.txt文档添加事项,打印已有事项于stdout
#include<stdio.h> //定义了BUFSIZ,本地是512
#include<ctype.h>
#include<stdlib.h>//定义了exit();直接推出进程,似乎正好用于void返回值的函数打开文件失败
void append_reminder()//添加事项
{
FILE *fp;
char buf[BUFSIZ],filename[]="reminder.txt";
int m,d;
if( (fp=fopen(filename,"a"))==NULL )
{
perror(filename);
exit(-1);
}
printf("Enter to add something to your schedule:\n");
while(1)
{
fgets(buf,BUFSIZ,stdin);//fgets()确保最后一位为'\0'的前提后,会把结尾的'\n'读入。即最大读取数为BUFZISIZE-1,如果读入数<=(BUFSIZE-2)就会读入结尾的'\n'
if(buf[0]=='\n') goto A;
if(sscanf(buf,"%d.%d",&m,&d)!=2)//利用sscanf返回读入个数来判断是否按格式输入
{
fputs("Input format:<month>.<day><message>\n",stderr);//区分stderr和stdout
continue;
}
fputs(buf,fp);//与fgets适配
}
A:fclose(fp);
return;
}
void print_schedule()//打印已有事项
{
FILE *fp;
char buf[BUFSIZ];
int m,d;
char message[BUFSIZ]={0};//可以不初始化,因为%s会自动补上'\0' 复习
if( (fp=fopen("reminder.txt","r"))==NULL )
{
perror("reminder.txt");
exit(-2);
}
while(fgets(buf,BUFSIZ,fp)!=NULL)
{
sscanf(buf,"%d.%d%[^\n]",&m,&d,message);
printf("%02d.%02d%s\n",m,d,message);
}
fclose(fp);
return;
}
int main()
{
append_reminder();
print_schedule();
return 0;
}
10. stderr与stdout的理解
stdout有行缓冲,而stderr没有
//trial.c
int main()
{
fprintf(stdout,"first ");
fprintf(stderr,"second");
}
执行时,照理应该是先输出second的,因为stdout行缓冲,遇到'\n'才会从buf中输入到标准输出文件默认设备中。然而本地还是顺序输出的,不知道为何!
但是还是如果给stdout重定向的话,区别就很明显:在命令行中输入 trial.exe > tmp1.txt 再执行,则"first"输入到tmp1.txt中而"second"输入到屏幕上
11. fseek()和ftell()
函数原型:
int fseek(FILE *stream,long offset, int whence)
- 位移量:
offset
表示以起始点为基准,向前向后移动的字节数 (e.g.1L
-2L
ftell(fp)
) - 起始点:
whence
可以是三个符号SEEK_SET
(0)fSEEK_CUR
(1)SEEK_END
(2)
成功则返回非0,出错则非0
函数原型:
long ftell(FILE *stream)
成功则返回相对文件头部的偏移量,出错则-1L
void rewind(FILE *stream)
将文件指针移动到起始,等价于fseek(stream,0,SEEK_SET)
//作业 fileio_len.c 求文件的长度(字节数)-片段
fp=fopen(filename,"rb");//用r打开似乎也一样欸
fseek(fp,0L,SEEK_END);
len=ftell(fp);
printf("Lenth of File is %ld bytes\n",len);
12. 文件存储形式深入理解
//作业 fileio_reverse.c
//将文件按行倒序输出
#include<stdio.h>
#define MAXN 100
//对EOF和fgets以及返回值NULL的理解:fgets读到'\n'或EOF或指定数目时停止,返回所读字符串(也就是说若什么都没读就是NULL)
//EOF只是一个表示文件结束的常量(-1),不是特殊字符
int main()
{
FILE *fp;
char buf[BUFSIZ],filename[]="tmp1.txt";
long offset[MAXN]={0};//标记每行offset的值以便跳转;offset[0]本就为0
int i,j;
//gets(filename);
if((fp=fopen(filename,"r"))==NULL)
{
perror(filename);
return -1;
}
for(i=1;fgets(buf,BUFSIZ,fp)!=NULL;i++) //!执行顺序一定要想清楚!
offset[i]=ftell(fp);//刚好为行首偏移量
//3行的文件,i最后为4,指针指在第5行
for(j=i-2;j>=0;j--)
{
fseek(fp,offset[j],SEEK_SET);
fgets(buf,BUFSIZ,fp);
fputs(buf,stdout);
}
fclose(fp);
}
/*文件存储形式如下 (换行只是为了显示'\n'效果)
abcd\r\n
efghij\r\n
klmnopq\r\n
(EOF)
*/
13. 读/写操作不能连续交替进行,需要“同步文件状态”
//作业 fileio_substitute 将某字符替换成另一字符-片段
while(!feof(fp))
{
if(fgetc(fp)=='*')
{
fseek(fp,-1L,SEEK_CUR);//回退
putc('&',fp);
fseek(fp,ftell(fp),SEEK_SET);//必不可少,同步文件状态
}
}
14. 二进制方式读写相关
应该注意,此时按数据项数计数,而不是 正文模式 中的字节数(char)
常见配套函数:fread()与fwrite()
size_t fwrite(const void *buf, size_t size, size_t count, FILE *stream)
size_t fread (void *buf, size_t size, size_t count, FILE *stream)
需要中间商buf[]
作转接
返回读/写的数据项数(以size
长度为单位),出错则为0
一些操作
//写入数字 3*4=12Byte
int a[3]={23222,1132,128};
fp=fopen("in.dat","wb");
fwrite(a,sizeof(int),3,fp);
fclose(fp);
//整块读取:一次性读入BUFSIZE个整数(512...)
fp=fopen("bin_file.txt","r");
n=fread(buf,sizeof(int),BUFSIZE,fp);
len=ftell(fp);
printf("%d int read,current position is:%d\n",n,len);
//复杂结构的存取
struct d_type{
int size,year,value,group;
double width,height,ratio;
char name[8];
}d_table[MAX_N];
int n;
fwrite(&n,sizeof(int),1,fp_out);//直接取地址,不要忘了哟~
fread(&n,sizeof(int),1,fp_in);
fwrite(d_table,sizeof(struct d_type),n,fp_out);
fread(d_table,sizeof(struct d_type),n,fp_in);
15.文件打开方式备忘录
差不多就粗粗是这些了