C语言基础 Day10 文件(上)
1. 文件概述
文件分为两类,一类是设备文件,如屏幕、键盘、磁盘、网卡、声卡、显示器、扬声器等;还有一类是磁盘文件。磁盘文件又分为文本文件与二进制文件。文本文件中存储的是ASCII码。而二进制文件存储的主要是音频、视频、图片等内容。
前面我们使用的printf函数实际上就是将内容输出到屏幕上,屏幕也就是我们说的标准输出stdout。而我们也会使用scanf函数在键盘上获取输入,键盘也就是我们说的标准输入stdin。除了之外,后面还需要经常使用标准错误stderr,使用perror函数进行使用。perror的使用方法如下:perror("错误名称");
程序运行的时候会自动进行字符串拼接,打印出错误的信息,格式为错误名称: 错误的原因
。
我们前面使用的系统文件就主要是上面三个。
系统文件 | 文件指针 | 编号 |
---|---|---|
标准输入 | stdin | 0 |
标准输出 | stdout | 1 |
标准错误 | stderr | 2 |
这三个系统文件程序执行时由系统默认打开,用户无需定义就可以直接使用。当程序结束后就会自动被关闭。若我们关闭了系统文件则不能正常执行一些功能。关闭文件我们使用fclose(FILE *fp);
函数,里面传入文件的指针即可。下面以一个关闭标准输出的例子为例。
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
int main(int argc, char* argv[])
{
fclose(stdout);
printf("hello world\n");
system("pause");
return 0;
}
#endif
此时就会导致程序无法完成正常的输出。
2. 文件打开与读写
对文件的操作,我们主要分为三步:
- 第一步打开文件:使用fopen函数。
- 第二步操作文件:使用文件操作函数,如fputc、fgetc、fputs、fgets、fread、fwrite等。
- 第三步关闭文件:使用fclose函数。
首先来看看这些函数:
函数名 | 函数原型 | 返回值 | 参数 | 作用 | tip |
---|---|---|---|---|---|
fopen | FILE * fopen(const char * filename, const char * mode); | 成功返回文件指针,失败返回NULL | filename: 需要打开的文件名,根据需要加上路径 mode: 打开文件的模式设置 | 打开文件 | - |
fclose | int fclose(FILE * stream); | 成功返回0,失败返回-1 | stream: 文件指针 | 关闭先前使用fopen打开的文件。此作用让缓冲区的数据写入文件中,并释放系统所提供的的文件资源 | - |
fgetc | int fgetc(FILE * stream); | 成功返回读取到的字符,失败返回-1 | stream: 文件指针 | 从stream指定的文件中读取一个字符 | - |
fputc | int fputc(int ch, FILE * stream); | 成功返回写入的字符,失败返回-1 | ch: 需要写入文件的字符 stream: 文件指针 | 将ch写入stream指定的文件中 | - |
fgets | char * fgets(char * str, int size, FILE * stream); | 成功返回读取到的字符串,失败或者到文件尾返回NULL | str: 字符串 size: 指定最大读取字符串的长度(size-1) stream: 文件指针 | 从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现了换行符、督导文件末尾或是读了size-1个字符为止 | 会自动加上字符串的结尾符号’\0’ |
fputs | int fputs(const char * str, FILE * stream); | 成功返回0,失败返回-1 | str: 字符串 stream: 文件指针 | 将str所指定的字符串写入到stream指定的文件中 | 字符串结尾符号’\0’不写入文件中 |
fread | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); | 成功返回读取到的内容块数,如果这个值比nmemb小但是大于0,说明读取到了文件的结尾。失败返回0 | ptr: 存放读取出来数据的内存空间 size: 指定读取文件内容的块数据大小 nmemb: 读取we年的块数 stream: 已经打开的文件指针 | 以快书记的方式从文件中读取内容 | - |
fwrite | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); | 成功写入文件数据的块数目,与nmemb相等,失败返回0 | ptr: 准备写入文件数据的地址 size: 指定写入文件内容的块数据大小 nmemb: 写入文件的块数 stream: 已经打开的文件指针 | 以数据块的方式给文件写入内容 | - |
fprintf | int fprintf(FILE * stream, const char * format, …); | 实际写入文件的字符个数,失败返回-1 | stream: 已经打开的文件 format: 字符串格式,用法与printf一样 | 根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中 | - |
fscanf | int fscanf(FILE * stream, const char * format, …); | 参数成功转换的值的个数,失败返回-1 | stream: 已经打开的文件 format: 字符串格式,用法与scanf一样 | 从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据 | - |
feof | int feof(FILE * stream); | 到文件末尾返回非0值,没有到文件末尾返回0 | stream: 文件指针 | 检测是否读取到了文件末尾 | 判断的是最后一次读操作的内容,不是当前位置的内容 |
在上面这个表格中,涉及到了文件打开的模式设置,接下来看看模式设置有哪一些。
打开模式 | 含义 |
---|---|
r 或 rb | 以只读的方式打开一个文本文件或二进制文件。若文件不存在则报错 |
w 或 wb | 以写方式打开文件,若文件存在则清空文件,若文件不存在则创建一个文件 |
a 或 ab | 以追加方式打开文件,在末尾添加内容。若文件不存在则创建文件 |
r+ 或 rb+ | 以可读、可写的方式打开文件,不创建新文件 |
w+ 或 wb+ | 以可读、可写的方式打开文件,如果文件存在则清空文件,若文件不存在则创建一个文件 |
a+ 或 ab+ | 以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件 |
需要注意的是上面有b的是针对与二进制文件的,没有b的仅仅只能操作文本文件。
既然要访问文件,那么就需要对文件的路径需要了解。文件路径分为相对路径和绝对路径。
- 相对路径:从磁盘的根盘符开始,找到待访问的文件路径。需要注意的是在Windows中使用\的话需要使用\\才行。而在Linux中不支持这种分割方法。还有一种使用/分割文件,这种方法不仅在Windows中可以用,在Linux中也同样适用。
- 绝对路径:相对路径如果使用VS的话有两种情况。一种是VS的环境下编译执行,则文件相对路径是相对于
项目名.vcxproj
所在的目录位置。如果是直接运行可执行文件,则是相对于可执行文件所在的目录位置。
上面叙述了那么多,接下来看看上面的代码是如何使用的。
首先给出一个fputc的例子,将二十六个字母写入到指定文件中。
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
// fputc函数的使用
void test1()
{
char *filename = "test03-1.txt";
FILE *fp = fopen(filename, "w");
if (fp == NULL)
{
perror("fopen error");
return -1;
}
int ret = fputc('A', fp);
printf("ret = %d\n", ret);
if (ret == -1)
{
perror("fputc error");
return -1;
}
fclose(fp);
}
// 在文件中写入二十六个小写字母
void test2()
{
char *filename = "test03-2.txt";
FILE *fp = fopen(filename, "w");
if (fp == NULL)
{
perror("fopen error");
return -1;
}
int ret = 0;
for (int i = 0; i < 26; i++)
{
ret = fputc('a' + i, fp);
if (ret == -1)
{
perror("fputc error");
return -1;
}
}
fclose(fp);
}
int main(int argc, char* argv[])
{
//test1();
test2();
printf("=======finish=========\n");
system("pause");
return 0;
}
#endif
使用fgetc读取文件的示例如下:
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
int main(int argc, char* argv[])
{
srand((unsigned int)time(NULL));
char *filename = "test04-1.txt";
FILE *fp = fopen(filename, "r");
// 读失败就写
if (fp == NULL)
{
perror("fopen error");
fp = fopen(filename, "w");
// 写失败就关闭
if (fp == NULL)
{
perror("fopen error");
return -1;
}
int ret = 0;
for (int i = 0; i < 10; i++)
{
ret = fputc(rand() % 26 + 'a', fp);
if (ret == -1)
{
perror("fputc error");
if (fp) fclose(fp);
return -1;
}
}
fclose(fp);
fp = fopen(filename, "r");
if (fp == NULL)
{
perror("fopen error");
return -1;
}
}
int ret = 0;
for (int i = 0; i < 11; i++)
{
ret = fgetc(fp);
printf("%d\n", ret);
}
fclose(fp);
system("pause");
return 0;
}
#endif
使用feof的例子如下:
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
// 向文件中写入数据
void test1(char *filename)
{
FILE *fp = fopen(filename, "w");
if (!fp)
{
perror("fopen error");
return;
}
fputc('a', fp);
fputc('f', fp);
fputc(-1, fp);
fputc('s', fp);
fputc('v', fp);
fputc('g', fp);
fputc('h', fp);
fclose(fp);
}
// 读文件,使用EOF判断文件末尾,只能判断文本文件
void test2(char *filename)
{
FILE *fp = fopen(filename, "r");
if (!fp)
{
perror("fopen error");
return;
}
while (1)
{
char ch = fgetc(fp);
if (ch == EOF) return;
printf("%d \n", ch);
}
fclose(fp);
}
// 使用feof判断文件结束
void test3(char *filename)
{
FILE *fp = fopen(filename, "r");
if (!fp)
{
perror("fopen error");
return;
}
while (1)
{
char ch = fgetc(fp);
if (feof(fp))
{
break;
}
printf("%c\n", ch);
}
fclose(fp);
}
int main(int argc, char* argv[])
{
char *filename = "test05-1.txt";
//test1(filename);
//test2(filename);
test3(filename);
system("pause");
return 0;
}
#endif
feof函数有个特性,要是用它检测文件结束标志,必须在调用该函数之前,使用读文件函数。除此之外判断文件结束的标志也可以使用EOF。EOF是宏定义的-1。但是EOF与feof函数的区别是EOF只能判断文本文件的结尾,非文本文件就无法进行判断。
接下来看一个关于fgets和fputs函数的示例代码:
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
int main(int argc, char* argv[])
{
char *filename = "test06-1.txt";
FILE *fp = fopen(filename, "w");
if (fp == NULL)
{
perror("fopen error");
return -1;
}
char buf[4096] = { 0 };
while (1)
{
fgets(buf, 4096, stdin);
if (strcmp(buf, ":wq\n") == 0)
{
break;
}
fputs(buf, fp);
}
fclose(fp);
system("pause");
return 0;
}
#endif
3. 文件版四则运算
在一个文件中写入一系列待计算的运算式,并使用文件的读写操作将运算的结果该入改文件中。
思路:首先封装write_file函数,将四则运算的表达式写入。其次封装read_file函数,将四则运算的表达式读出,使用sscanf进行拆分,然后在封装一个calc函数对拆分出来的表达式进行计算。然后将最后的运算式再用sprintf拼接回去。最后将所有的结果的运算式拼接起来。将原文件关闭再重新用写的方式打开,就清空了原文件的内容,此时再将结果写入。
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
#define N 20
// 四则运算写入
void write_file(char *filename)
{
srand((unsigned int)time(NULL));
char buf[4096] = { 0 };
FILE *fp = fopen(filename, "w");
if (!fp)
{
perror("fopen error");
return;
}
for (int i = 0; i < N; i++)
{
int number1 = rand() % 1000 + 1;
char ch = rand() % 4;
int number2 = rand() % 1000 + 1;
switch (ch)
{
case 0:
ch = '+';
break;
case 1:
ch = '-';
break;
case 2:
ch = '*';
break;
case 3:
ch = '/';
break;
}
sprintf(buf, "%d%c%d=\n", number1, ch, number2);
fputs(buf, fp);
fputs(buf, stdout);
}
fclose(fp);
}
int calc(char ch, int number1, int number2)
{
switch (ch)
{
case '+':
return number1 + number2;
case '-':
return number1 - number2;
case '*':
return number1 * number2;
case '/':
return number1 / number2;
default:
break;
}
}
void read_files(char *filename)
{
char res_buf[4096] = { 0 };
char buf[4096] = { 0 };
FILE *fp = fopen(filename, "r");
if (fp == NULL)
{
perror("fopen error");
return;
}
while (1)
{
fgets(buf, 4096, fp);
if (feof(fp))
{
break;
}
int number1 = 0;
int number2 = 0;
char op = 0;
sscanf(buf, "%d%c%d=\n", &number1, &op, &number2);
int op_res = calc(op, number1, number2);
sprintf(buf, "%d%c%d=%d\n", number1, op, number2, op_res);
strcat(res_buf, buf);
}
fclose(fp);
fp = fopen(filename, "w");
if (!fp)
{
perror("fopen error");
return;
}
fputs(res_buf, fp);
fclose(fp);
}
int main(int argc, char* argv[])
{
char *filename = "test07-1.txt";
write_file(filename);
printf("==========================\n");
read_files(filename);
system("pause");
return 0;
}
#endif