C语言学习第014课——文件操作(一)

概述

磁盘文件和设备文件

磁盘文件

指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入内存。

设备文件

在操作系统中把每一个与主机相连的输入输出设备看作是一个文件,把他们的输入,输出等同于对磁盘文件的读和写

磁盘文件的分类

计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的,以字节为单位进行顺序存储。
从用户或操作系统使用的角度把文件分为

  • 文本文件:基于字符编码的文件
  • 二进制文件:基于值编码的文件

文本文件和二进制文件

文本文件

  • 基于字符编码,常见编码有ASCII UNICODE等
  • 一般可以使用文本文档直接打开
  • 数5678的存储形式(ASCII)为
    00110101 00110110 00110111 00111000
00110101 00110110 00110111 00111000			二进制
   53       54       55       56			ASCII
  ‘5’       ‘6’      ‘7’      ‘8’			字符

二进制文件

  • 基于值编码,自己根据具体应用,指定某个值是什么意思
  • 把内存中的数据按其在内存中的存储形式原样输出到磁盘上
  • 5678的存储形式(二进制)为
    00010110 00101110

从5678不同的编码方式存储在磁盘的空间来看,还是使用二进制存储更节省磁盘空间。很多操作情况下都趋于二进制进行编码

文件的打开和关闭

文件的指针

在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。

typedef struct
{
	short           level;	//缓冲区"满"或者"空"的程度 
	unsigned        flags;	//文件状态标志 
	char            fd;		//文件描述符
	unsigned char   hold;	//如无缓冲区不读取字符
	short           bsize;	//缓冲区的大小
	unsigned char   *buffer;//数据缓冲区的位置 
	unsigned        ar;	 //指针,当前的指向 
	unsigned        istemp;	//临时文件,指示器
	short           token;	//用于有效性的检查 
}FILE;

FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。
声明FILE结构体类型的信息包含在头文件“stdio.h”中,
一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各种操作。
在这里插入图片描述
C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用:

  • stdin:标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从此终端获得数据。
  • stdout:标准输出,默认为当前终端(屏幕),我们使用的printf、puts函数默认输出信息到此终端。
  • stderr:标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到此终端。

文件的打开

任何文件使用之前必须打开:

#include <stdio.h>
FILE * fopen(const char * filename, const char * mode);
功能:打开文件
参数:
	filename:需要打开的文件名,根据需要加上路径
	mode:打开文件的模式设置
返回值:
	成功:文件指针
	失败:NULL

第一个参数的几种形式:

FILE *fp_passwd = NULL;

//相对路径:
//打开当前目录passdw文件:源文件(源程序)所在目录
FILE *fp_passwd = fopen("passwd.txt", "r");

//打开当前目录(test)下passwd.txt文件
fp_passwd = fopen(". / test / passwd.txt", "r");
	
//打开当前目录上一级目录(相对当前目录)passwd.txt文件
fp_passwd = fopen(".. / passwd.txt", "r");
		
//绝对路径:
//打开C盘test目录下一个叫passwd.txt文件
fp_passwd = fopen("c:/test/passwd.txt","r");

第二个参数的几种形式(打开文件的方式):
在这里插入图片描述
b是二进制模式的意思,b只是在Windows有效,在Linux用r和rb的结果是一样的
Unix和Linux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是\r\n结尾
在Windows平台下,以“文本”方式打开文件,不加b,当读取文件的时候,系统会将所有的 “\r\n” 转换成 “\n”,当写入文件的时候,系统会将 “\n” 转换成 “\r\n” 写入 ,以"二进制"方式打开文件,则读和写都不会进行这样的转换
 在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原样输入输出

文件的关闭

任何文件在使用后应该关闭
打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存
一个进程同时打开的文件数是有限制的,超过最大同时打开文件数65535,再次调用fopen打开文件会失败
如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。

#include <stdio.h>
int fclose(FILE * stream);
功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。
参数:
	stream:文件指针
返回值:
	成功:0
	失败:-1

打开关闭文件资源的代码示例

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");		以只读方式打开
    if(fp == NULL){
        printf("打开失败\n");
        return -1;
    }
    printf("打开成功 %p\n",fp);
    fclose(fp);								关闭资源
    printf("关闭文件\n");
    return 0;
}

文件的顺序读写

按照字符读写文件fgetc、fputc

文件读取

fgetc(FILE* stream) 读取文件中的一个字符

#include <stdio.h>
int fgetc(FILE * stream);
功能:从stream指定的文件中读取一个字符
参数:
	stream:文件指针
返回值:
	成功:返回读取到的字符
	失败:-1

简单的使用一下

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");			打开一个文件
    if(!fp){									判断是否打开成功
        printf("打开文件失败\n");
        return -1;
    }
    printf("打开文件成功 %p\n",fp);
    char ch;
    ch = fgetc(fp);								读取一个字符
    printf("%c\n",ch);
    return 0;									这里没有关闭资源,其实没有关系,
    											因为程序运行结束,他会自己关闭
}

运行结果
在这里插入图片描述
可以看到,读取到了一个字符 ‘h’
那么,读取下一个字符该怎么操作呢?是不是需要给文件指针fp+1呢?
其实不用,因为fgetc()调用的时候,文件中相当于有个光标,会随着函数的调用,自己移动一格,所以如果想要获取后面的字符,直接再调用fgetc()即可

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");
    if(!fp){
        printf("打开文件失败\n");
        return -1;
    }
    printf("打开文件成功 %p\n",fp);
    char ch;
    ch = fgetc(fp);
    printf("%c\n",ch);
    ch = fgetc(fp);
    printf("%c\n",ch);
    ch = fgetc(fp);
    printf("%c\n",ch);
    ch = fgetc(fp);
    printf("%c\n",ch);
    ch = fgetc(fp);
    printf("%c\n",ch);
    fclose(fp);
    printf("关闭资源\n");
    return 0;
}

运行结果
在这里插入图片描述
如果想要把这个文件读取完,应该以什么作为条件判断呢?也就是说怎么就知道我读取到了文件末尾呢?
文件默认结尾为-1

文件结尾

在C语言中,EOF表示文件结束符(end of file),在while循环中,以EOF作为文件结束标志,
这种以EOF作为文件结束标志的文件,必须是文本文件,
在文本文件中,数据都是以字符的ASCII码值的形式存放,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束的标志

#define EOF     (-1)

二进制文件不能以EOF作为文件结束标志,因为二进制存储过程中,会有-1值出现,为了解决这个问题,ANSI C提供一个feof函数,用来判断文件是否结束,feof函数既可以用于判断文本文件又可以判断二进制文件

#include <stdio.h>
int feof(FILE * stream);
功能:检测是否读取到了文件结尾。判断的是最后一次“读操作的内容”,不是当前位置内容(上一个内容)。
参数:
	stream:文件指针
返回值:
	非0值:已经到文件结尾
	0:没有到文件结尾

使用EOF作为文件结尾判断来读取一整个文件

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");
    if(!fp){
        printf("打开文件失败\n");
        return -1;
    }
    char ch;
    while((ch=fgetc(fp))!=EOF){
        printf("%c",ch);
    }
    fclose(fp);
    return 0;
}

运行结果:hello world

文件写入

写文件,使用fopen的时候,就不能传r了,需要传w
上面表里有说到,w表示的意义是以写方式打开文件,如果文件存在,则清空文件,如果文件不存在,则创建文件

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","w");
    if(!fp){
        printf("打开文件失败\n");
        return -1;
    }
    char ch = 'a';
    fputc(ch,fp);
    fclose(fp);
    return 0;
}

运行结果:可以看到hello.txt中,之前的hello world已经被清空了,只有一个a
如果以上代码代开文件一行,换成

FILE* fp = fopen("nihao.txt","w");

运行结果就是会新创建一个nihao.txt,里面有一个a
写一个类似于记事本的程序,键盘上输入字符,写入到文件中,知道键盘输入@,程序结束

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","w");
    if(!fp){
        printf("打开文件失败\n");
        return -1;
    }
    char ch;
    while(1){
        scanf("%c",&ch);
        if(ch=='@'){
            break;
        }
        fputc(ch,fp);
    }
    fclose(fp);
    return 0;
}

键盘输入
在这里插入图片描述
文件内容
在这里插入图片描述
练习:文件简单的加密解密

原文件
在这里插入图片描述
加密代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp1 = fopen("原文件.txt","r");
    FILE* fp2 = fopen("加密.txt","w");
    if(!fp1 || !fp2){
        return -1;
    }
    char ch;
    while((ch = fgetc(fp1))!=EOF){
        ch++;
        fputc(ch,fp2);
    }
    fclose(fp1);
    fclose(fp2);
    return 0;
}

加密文件:
在这里插入图片描述
解密代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp1 = fopen("加密.txt","r");
    FILE* fp2 = fopen("解密.txt","w");
    if(!fp1 || !fp2){
        return -1;
    }
    char ch;
    while((ch = fgetc(fp1))!=EOF){
        ch--;
        fputc(ch,fp2);
    }
    fclose(fp1);
    fclose(fp2);
    return 0;
}

解密文件
在这里插入图片描述

按照行读写文件fgets、fputs

读文件

#include <stdio.h>
char * fgets(char * str, int size, FILE * stream);
功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,
最后会自动加上字符 '\0' 作为字符串结束。
参数:
	str:字符串
	size:指定最大读取字符串的长度(size - 1)
	stream:文件指针
返回值:
	成功:成功读取的字符串
	读到文件尾或出错: NULL

简单的一个示例,演示一下该函数的使用方法
目标文件:hello.txt
在这里插入图片描述
代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");					打开文件
    char* str = (char*)malloc(sizeof(char)*100);		分配缓冲区
    memset(str,0,100);									初始化缓冲区
    fgets(str,100,fp);									读取一行
    printf("%s",str);									打印 :春天来了,把你手拉上
    return 0;
}

代码中分配了100个字节的缓冲区,但是实际可以使用到的是99个字节,因为最后一个字节要留着存放\0
如果我们把分配的100个字节缓冲区换成5个字节的

char* str = (char*)malloc(sizeof(char)*5);
memset(str,0,5);
fgets(str,5,fp);
printf("%s",str);

运行结果为“春天”,一个汉字两个字节,剩下一个字节是\0
如果将缓冲区大小改成4,运行结果就是“春”,最后一个字节是\0,前面只剩下3个字节,只够一个半汉字的,所以只显示了一个汉字
思考:上面5个字节大小缓冲区的情况下,我如果再读取一次,是继续读第一行后面的内容呢?还是从下一行开始读?

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");
    char* str = (char*)malloc(sizeof(char)*5);
    memset(str,0,5);
    fgets(str,5,fp);
    printf("%s",str);
	//第二次读取
    memset(str,0,5);		把之前读取的结果缓存清理了,防止影响读取结果
    fgets(str,5,fp);
    printf("%s",str);
    return 0;
}

运行结果为“春天来了”
所以在读取的过程中,如果因为缓冲区大小限制,导致一行没读完,没有遇到\n,他会保留光标位置,下次读取的时候,继续从光标位置开始读,直到遇到\n

将一个文件中的所有内容全部读出来
这里涉及到一个问题,怎么判断是否读取到了文件的结尾。
前面《文件结尾》说过,有一个feof函数,用来判断是否到了文件结尾处,
如果没有到文件结尾返回0,如果到了文件结尾返回非0

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");
    if(!fp)
    	return -1;
    char* str = (char*)malloc(sizeof(char)*100);
    while(!feof(fp)){
        memset(str,0,100);
        fgets(str,100,fp);
        printf("%s",str);
    }
    free(str);
    fclose(fp);
    return 0;
}

运行结果:
在这里插入图片描述

写文件

#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 '\0'  不写入文件。 
参数:
	str:字符串
	stream:文件指针
返回值:6
	成功:0
	失败:-1

简单的使用一下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","w");
    if(!fp)
        return -1;
    char ch[] = "春天来啦把你手拉上";
    fputs(ch,fp);
    fclose(fp);
    return 0;
}

如果将字符串改为:

char ch[] = "春天来啦\n把你手拉上";

运行结果就会是

“春天来啦
把你手拉上”

如果将字符串改为:

char ch[] = "春天来啦\0把你手拉上";

运行结果就会是
“春天来啦”

所以fputs函数对字符串的操作还是基于字符串的,跟字符串是一样的

通过键盘输入,将内容写入到文件中
思路,通过scanf接收键盘输入,直接使用fputs函数写入到文件中,先简单写一下,看看效果,有问题再修改

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","w");
    if(!fp)
        return -1;
    char* str = (char*)malloc(sizeof(char)*1024);
    while(1){
        memset(str,0,1024);
        scanf("%s",str);
        if(!strcmp(str,"comm=exit")){
            break;
        }
        fputs(str,fp);
    }
    fclose(fp);
    return 0;
}

使用键盘输入
在这里插入图片描述
文件中的内容为
在这里插入图片描述
发现没有空格,也没有换行,
分析原因,是scanf接收到空格和换行 就认为结束了这一次接收过程,,可以理解为是scanf把空格和换行吞噬了,
于是想到了,以前用的scanf通过正则表达式接收非\n,来接收空格

scanf("%{^\n}",str);

再次运行
在这里插入图片描述
发现才按了第一行,回车之后就动不了了,这是为什么呢?
分析原因,是因为scanf的处理机制造成的,scanf默认遇到空格和回车,就将键盘输入的内容赋值到scanf的第二个参数字符串中,
scanf加上正则表达式%[^\n],他可以遇到空格也不提交,等遇到回车的时候才提交
但是scanf提交归提交,他并不接收或者处理回车,回车事件还在缓存中
从代码流程来看,在while循环中,当输入nihao shijie一按回车,scanf将字符串提交了,但是回车事件还在缓存中,之后,代码继续往下走,经过判断和文件写入,又一次循环来到了scanf阻塞函数,等待键盘输入。
这个时候,因为回车还在缓存中,没有被处理掉,scanf就又遇到了这个回车,直接认为提交了,依然不处理回车事件,继续往下走,如此在while中循环,就造成了动不了的结果。
解决办法之一是,在scanf之后,加一个

getchar();

scanf不处理的回车,这里用一个getchar接收了,就没有缓存的回车了,程序就可以正常运行了
除了getchar()也可以使用fflush(stdio)清空键盘缓存,如果这里还没看明白为什么,可以参考这篇博客scanf后面为啥加getchar

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","w");
    if(!fp)
        return -1;
    char* str = (char*)malloc(sizeof(char)*1024);
    while(1){
        memset(str,0,1024);
        scanf("%[^\n]",str);				除了\n啥都能接
        getchar();							专接\n
        if(!strcmp(str,"comm=exit")){
            break;
        }
        fputs(str,fp);
    }
    fclose(fp);
    return 0;
}

运行结果为
在这里插入图片描述
可以看到,没有回车,
是因为回车被getchar()接收了,所以这里需要单独加一个回车符号

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","w");
    if(!fp)
        return -1;
    char* str = (char*)malloc(sizeof(char)*1024);
    while(1){
        memset(str,0,1024);
        scanf("%[^\n]",str);
        getchar();
        if(!strcmp(str,"comm=exit")){
            break;
        }
        strcat(str,"\n");				在str后面增加一个回车符号
        fputs(str,fp);
    }
    fclose(fp);
    return 0;
}

运行结果
在这里插入图片描述

强化训练 文件版四则运算

生成一个文件,100行,每行内容都是一个四则运算表达式,还没有算出结果,写一个程序,自动算出其结果后修改文件。
生成文件:
思路:首先需要在一个for循环中生成随机数a和b,
然后生成加减乘除也可以使用随机数的方式,利用枚举
最后利用sprintf的方式,生成字符串,写入到文件中

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
enum opt{			利用枚举随机生成加减乘除运算符
add,sub,mul,dive
};
int main(void){
    srand((size_t)time(NULL));			随机数种子
    FILE* fp = fopen("hello.txt","w");
    if(!fp)
        return -1;
    int a,b;
    char ch;
    char* str = (char*)malloc(sizeof(char)*20);
    for(int i = 0;i<100;i++){
        a = rand()%10+1;			随机生成两个数
        b = rand()%10+1;
        switch(rand()%4){			随机生成4个数 0 1 2 3分别代表加减乘除
        case add:
            ch = '+';
            break;
        case sub:
            ch = '-';
            break;
        case mul:
            ch = '*';
            break;
        case dive:
            ch = '/';
            break;
        }
        memset(str,0,20);
        sprintf(str,"%d%c%d=\n",a,ch,b);		组拼成字符串,写入到文件中
        fputs(str,fp);
    }
    free(str);
    fclose(fp);
    return 0;
}

运行结果
在这里插入图片描述
计算结果文件生成:
思路:读取算术题的文件,一行一行读出来,因为每个格式都一样,所以使用sscanf函数,
获取到a b 以及是属于哪种运算方式,switch使用运算方式作为判断条件,计算每个题的结果,
再次利用sprintf函数生成字符串,写入到另一个文件中
代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>

int main(void){
    int a,b,value;
    char ch;
    FILE* fp1 = fopen("hello.txt","r");		读旧文件
    FILE* fp2 = fopen("world.txt","w");		写新文件
    if(!fp1 || !fp2){
        return -1;
    }
    char* str = (char*)malloc(sizeof(char)*20);
    while(!feof(fp1)){						判断是否是文件结尾
        memset(str,0,20);
        fgets(str,20,fp1);
        sscanf(str,"%d%c%d=\n",&a,&ch,&b);		获取到题中的两个数和运算方式
        switch(ch){				根据运算方式计算结果
        case '+':value = a + b;break;
        case '-':value = a - b;break;
        case '*':value = a * b;break;
        case '/':value = a / b;break;
        }
        memset(str,0,20);
        sprintf(str,"%d%c%d=%d\n",a,ch,b,value);		将结果合成新的字符串
        fputs(str,fp2);				写入到文件中
    }
    free(str);						回收资源
    fclose(fp1);
    fclose(fp2);
    return 0;
}

运行结果:
在这里插入图片描述

按照格式化读写文件 fprintf fscanf

读文件

#include <stdio.h>
int fscanf(FILE * stream, const char * format, ...);
功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:
	stream:已经打开的文件
	format:字符串格式,用法和scanf()一样
返回值:
	成功:参数数目,成功转换的值的个数
	失败: - 1

简单的使用一下:
hello.txt
在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");
    if(!fp)
        return -1;
    char str[100];
    fscanf(fp,"%s",str);	读取fp文件,然后以字符串的形式保存到str中
    printf("%s\n",str);		运行结果:春天来啦
    fclose(fp);
    return 0;
}

发现只读取了一行,同理我们继续读取文件剩下的部分

FILE* fp = fopen("hello.txt","r");
    if(!fp)
        return -1;
    char str[100];
    fscanf(fp,"%s",str);
    printf("%s\n",str);

    fscanf(fp,"%s",str);
    printf("%s\n",str);

    fscanf(fp,"%s",str);
    printf("%s\n",str);
	fclose(fp);
    return 0;

运行结果为:
在这里插入图片描述
发现最后少三个字,为啥呢?因为这三个字前面有个空格,fscanf遇到空格停下来了
结合上一个例子,fscanf和scanf是一样的,都是遇到空格和回车就停止
再加一个读取操作,剩下的3个字就读出来了。
如果给文件加上一个数字,同样的方式,也是能读出来的
在这里插入图片描述

int main(void){
    FILE* fp = fopen("hello.txt","r");
    if(!fp)
        return -1;
    char str[100];
    int a;
    fscanf(fp,"%d",&a);
    printf("%d\n",a);

    fscanf(fp,"%s",str);
    printf("%s\n",str);

    fscanf(fp,"%s",str);
    printf("%s\n",str);

    fscanf(fp,"%s",str);
    printf("%s\n",str);
	fclose(fp);
    return 0;
}

运行结果:
在这里插入图片描述
假如前面的四则运算题里面,我要把里面的所有int类型数据都读出来
假如hello.txt中有一行 5+8=13,将这3个数取出并打印

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");
    if(!fp)
        return -1;
    int a,b,c;
    char ch;
    fscanf(fp,"%d%c%d=%d",&a,&ch,&b,&c);
    printf("%d\n",a);
    printf("%d\n",b);
    printf("%d\n",c);
    fclose(fp);
    return 0;
}

运行结果:
在这里插入图片描述

写文件

#include <stdio.h>
int fprintf(FILE * stream, const char * format, ...);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0'  为止。
参数:
	stream:已经打开的文件
	format:字符串格式,用法和printf()一样
返回值:
	成功:实际写入文件的字符个数
	失败:-1

简单的使用一下:将一串字符串通过fprintf写入到文件中

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","w");
    if(!fp)
        return -1;
    char ch[] = "hello world";
    fprintf(fp,"%s",ch);
    fclose(fp);
    return 0;
}

如果将上述写入的内容再读出来:

int main(void){
    FILE* fp = fopen("hello.txt","r");
    if(!fp)
        return -1;
    char ch[20] ;
    fscanf(fp,"%s",ch);
    printf("%s\n",ch);
    fclose(fp);
    return 0;
}

运行结果只有hello,因为fscanf和scanf一样,遇到空格就停下来了
如果想让fscanf可以遇到空格不停下来,也可以,还是一样的方式:

fscanf(fp,"%[^\n]",ch);

如果用这种方式读取:

fscanf(fp,"%12s",ch);

读取出来的还是hello 因为不管s前面加什么数组,始终迈不过fscanf遇到空格就停的坎
改成用:

fscanf(fp,"%12c",ch);

就同样可以遇到空格也不停,因为他是按照字符读取的,只当空格也是一个字符 ,但是这种操作需要将ch初始化为0,因为有一个字符结束标志的原因
如果要读取hel 3个字符

fscanf(fp,"%3c",ch);
fscanf(fp,"%3s",ch);

都可以 ,3s是读取的时候 他已经给初始化好\0了,3c不会初始化\0,需要自己初始化
把一个int类型的数写入到文件中

int main(void){
    FILE* fp = fopen("hello.txt","w");
    if(!fp)
        return -1;
    int a = 10;
    fprintf(fp,"%d",a);
    fclose(fp);
    return 0;
}

运行结果 10
修改代码:

fprintf(fp,"%05d",a);

运行结果 00010
结论就是,fsanf fprintf 沿袭了scanf和printf的全部特点
学了这两个函数之后,前面的四则运算就可以写的更便捷了。

练习:随机生成1000个0-256以内的数,将他写在一个文件中,再在另一个文件中给他排好序 写进去

生成文件:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>

int main(void){
    srand((size_t)time(NULL));
    FILE* fp = fopen("数据.txt","w");
    if(!fp)
        return -1;
    for(int i = 0;i<1000;i++){
        fprintf(fp,"%d\n",rand()%257);
    }
    fclose(fp);
    return 0;
}

生成排序后的文件:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "util.h"

#define SIZE 1000


int main(void){
    FILE* fp1 = fopen("数据.txt","r");
    FILE* fp2 = fopen("排序.txt","w");
    if(!fp1 || !fp2)
        return -1;
    int* arr = malloc(sizeof(int)*SIZE);
    memset(arr,0,sizeof(int)*SIZE);      因为memset函数是以字节进行初始化的,所以这里不能直接写1000
    for(int i=0;i<SIZE;i++){
        fscanf(fp1,"%d\n",&arr[i]);
    }
    BubbleSort(arr,SIZE);
    for(int i = 0;i<SIZE;i++){
        fprintf(fp2,"%d\n",arr[i]);
    }
    fclose(fp1);
    fclose(fp2);
    return 0;
}

运行结果:
在这里插入图片描述
运行结果没有问题,但是思考这样的写法会不会有什么不太好的地方,比如说,生成1000个随机数 循环1000次,将这个数读到arr中,又循环1000次,写入文件 1000次 排序 499500次 这也太多了,虽然计算机运算速度快,但是也不能这么造!想想有没有别的思路,可以计算次数少一些的
思路:因为这1000个数,大小在0-256范围,所以是不是可以定义一个257大小的数组arr,arr[3]= 5 表示3这个数出现了5次 这样,就不用写排序算法了

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "util.h"

#define SIZE 1000

int main(void){
    FILE* fp1 = fopen("数据.txt","r");
    FILE* fp2 = fopen("排序.txt","w");
    if(!fp1 || !fp2)
        return -1;
    int* arr = malloc(sizeof(int)*257);			创建一个257大小的数组
    int value;
    memset(arr,0,sizeof(int)*257);				初始化数组
    for(int i=0;i<SIZE;i++){
        fscanf(fp1,"%d\n",&value);				从文件中读取数据,赋值给value
        arr[value]++;							value当做角标,对应的arr的值自增
    }
    for(int i = 0;i<257;i++){
        for(int j = 0;j<arr[i];j++){
            fprintf(fp2,"%d\n",i);				这里注意,写入的是i的值,
        }
    }
    free(arr);
    fclose(fp1);
    fclose(fp2);
    return 0;
}

运行结果 和冒泡排序的结果一样,但是循环次数明显少了好几个量级

按照块读写文件 fread fwrite

块读写是一个基于二进制的操作,和文本操作不一样
前面的内容学习到,在Windows平台下 操作二进制文件的打开方式是 wb和rb,Linux平台下 r和rb效果一样

写文件

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式给文件写入内容
参数:
	ptr:准备写入文件数据的地址
	size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
	nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
	stream:已经打开的文件指针
返回值:
	成功:实际成功写入文件数据的块数目,此值和 nmemb 相等
	失败:0

简单的写个例子将5678以二进制的方式写入到hello.txt中

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","wb");
    int a = 5678;
    fwrite(&a,sizeof(int),1,fp);			如果要写一个数组进去的话 这里的参数1要改成数组个数
    fclose(fp);
    return 0;
}

运行结果
在这里插入图片描述

读文件

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式从文件中读取内容
参数:
	ptr:存放读取出来数据的内存空间
	size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小
	nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
	stream:已经打开的文件指针
返回值:
	成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
	失败:0

简单的试验一下将上面写入文件的5678二进制码读出来

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","rb");
    int value;
    fread(&value,sizeof(int),1,fp);
    printf("%d\n",value);
    fclose(fp);
    return 0;
}

运行结果:

在这里插入图片描述
有了上面的例子,我们可以试试把一个数组写入到文件中 并读出来
写文件

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","wb");
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    fwrite(arr,sizeof(int),10,fp);
    fclose(fp);
    return 0;
}

写好的文件:
在这里插入图片描述
读文件

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    FILE* fp = fopen("hello.txt","rb");
    int arr[10] = {0};
    fread(arr,sizeof(int),10,fp);
    for(int i=0;i<10;i++){
        printf("%d\n",arr[i]);
    }
    fclose(fp);
    return 0;
}

运行结果:
在这里插入图片描述
关于fread的第二个和第三个参数,不必很遵循第二个参数是元素的大小,第三个参数是元素个数,只要保证他俩的乘积相同,也就是说保证告诉函数总共有多少个字节,运行结果不受影响,但是为了便于阅读代码,还是要按规定写

fread(arr,sizeof(int),10,fp);
fread(arr,4,10,fp);
fread(arr,10,4,fp);
fread(arr,5,8,fp);		写这几种方式都行

运行结果都不受影响
写一个将结构体写入二进制文件中

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct student{
    char name[21];
    int age;
    int score;
    char addr[51];
}student;

int main(void){
    FILE* fp = fopen("hello.txt","wb");
    if(!fp)
        return -1;
    student stu[3]={
        {"张三",21,99,"张家庄"},
        {"李四",21,97,"李家屯"},
        {"王五",21,90,"王家坳"}
    };
    for(int i=0;i<3;i++){
        fwrite(&stu[i],sizeof(student),1,fp);
    }
    fclose(fp);
    return 0;

}

在这里插入图片描述

再将结构体从文件中读取出来

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct student{
    char name[21];
    int age;
    int score;
    char addr[51];
}student;

int main(void){
    FILE* fp = fopen("hello.txt","rb");
    if(!fp)
        return -1;
    student* pstu = (student*)malloc(sizeof(student)*3);
    int i = 0;
    while(!feof(fp)){				feof函数可以判断文本文件和二进制文件是否到达文件结束标志
        fread(pstu+i,sizeof(student),1,fp);
        i++;
    }
    for(int i=0;i<3;i++){
        printf("%s ",pstu[i].name);
        printf("%d ",pstu[i].age);
        printf("%d ",pstu[i].score);
        printf("%s \n",pstu[i].addr);
    }
    free(pstu);
    fclose(fp);
    return 0;

}

在这里插入图片描述
强化练习:大文件读写
写一个程序,可以通过命令行的方式将一个大概20M的文件,拷贝出来一份
首先需要主函数的参数做出改变

int main(int argc,char* argv[]){
    for(int i=0;i<argc;i++){		打印参数
        printf("%s\n",argv[i]);
    }
    return 0;
}

在代码目录中使用gcc将代码编译成.exe格式,然后在命令行中执行以下代码

helloworld.exe C:\test.avi D:\test.avi

运行结果:
在这里插入图片描述
可以看到,main函数中argv数组的第一个是程序名称,第二个第三个一次是后面的文件名称所以开始写代码 进行拷贝

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define SIZE 1024
int main(int argc,char* argv[]){
    FILE* fp1 = fopen(argv[1],"rb");
    FILE* fp2 = fopen(argv[2],"wb");
    if(!fp1 || !fp2){
        printf("操作失败\n");
        return -1;
    }
    if(argc<3){
        printf("缺少参数\n");
        return -2;
    }
    //开始读写
    char* ptemp = (char*)malloc(sizeof(char)*SIZE);
    while(!feof(fp1)){
    	memset(ptemp,0,SIZE);
        fread(ptemp,1,SIZE,fp1);
        fwrite(ptemp,1,SIZE,fp2);
    }
    free(ptemp);
    fclose(fp1);
    fclose(fp2);
    printf("拷贝成功\n");
    return 0;
}

执行拷贝命令
在这里插入图片描述
两个文件比对结果
在这里插入图片描述
发现有点不对,总体上,大小是差不多,但是具体的多少字节不一致,分析一下原因
问题可能出现在拷贝的操作上,拷贝一次大小是SIZE 也就是1024个字节,但是源文件不一定是1024字节的整数倍,
假如说,最后一次拷贝,剩下100个字节,我们用1024个字节读了,ptemp中只有100个有效字节,其他都是无效字节,但是最后写的过程是按照1024个字节写进去了,所以目标文件会比源文件多出来不到1024个字节,
解决办法:我们注意到fread有返回值,返回值是成功读取到的块数,那么我们可以拿这个返回值去给了fwrite的参数,fread读了多少,fwrite就写多少

	//开始读写
    char* ptemp = (char*)malloc(sizeof(char)*SIZE);
    int count = 0;
    while(!feof(fp1)){
    	memset(ptemp,0,SIZE);
        count = fread(ptemp,1,SIZE,fp1);
        fwrite(ptemp,1,count,fp2);
    }

继续执行命令,查看结果:
在这里插入图片描述
可以看到,拷贝成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值