嵌入式第二十五课!!!文件读写操作补充与应用、以及文件流定位相关函数接口
文件读写操作补充
fgets/fputs
这个函数用来读写文本文件,不能对二进制文件进行读写;
在使用 gets 和 fgets 时,需要注意的是:
fgets:
1. 从指定的已打开文件中读取最多一行数据(遇到\n停止读取)
2. fgets保留\n字符并字符串末尾添加\0
3. fgets最多读取size-1个字符,最后一个位置存放\0gets:
1. 指定从终端设备读取数据
2. gets会将终端读到的\n字符替换成\0;
3. gets是危险的,因为在读取时,没有大小的限制,可能造成内存越界。
如下图所示,二者读取方式不同:
Linux操作系统默认已打开的三个文件
FILE *stdin:标准输入流 ——>标准输入设备:键盘
FILE *stdout:标准输出流——>标准输出设备:显示屏
FILE *stderr:标准出错流——>标准出错设备:显示屏
举个例子:
int ret = fgetc(stdin) 等价于 int ret = getchar( );
fwrite函数与fread函数
fwrite函数
fwrite 函数是这样使用的:
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:向文件中写入nmemb个大小是size的数据到文件中
参数:
ptr : 要写入的数据的首地址
size:每个元素的字节数
nmemb:要写入的元素个数
stream:要写入的文件流指针
返回值:
成功:返回实际写入的元素个数
因为fwrite函数可以传参读取数据的元素字节大小,故 fwrite 函数(以及fread函数)是用来读(写)固定大小的二进制数据(比如说结构体就可以通过这个函数读取)
#include <stdio.h>struct stu
{int id;char name[32];int score;
};int main(int argc, const char *argv[])
{FILE *fp = fopen("./1.txt", "w");if (NULL == fp){printf("fopen error\n");return -1;}
/*int num = 100;int a[5] = {100,200,300,400,500};size_t cnt = fwrite(&num, sizeof(int), 3, fp);printf("cnt = %ld\n", cnt);
*/struct stu s = {1, "zhangsan", 99};struct stu ss[5] = {{1, "zhangsna", 99}, {2, "lisi", 88},{3, "wanger", 67}, {4, "maliu", 56},{5, "zhaowu", 100}};size_t cnt = fwrite(&s, sizeof(struct stu), 1, fp);printf("cnt = %ld\n", cnt);cnt = fwrite(ss, sizeof(struct stu), 5, fp);printf("cnt = %ld\n", cnt);fclose(fp);return 0;
}
运行结果如下:
fread函数
fread函数是这样使用的:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件中读取nmemb个大小是size的元素
参数:
ptr:存储读取到数据的首地址
size:每个元素的大小
nmemb:希望从文件中读取的元素个数
stream:要读的文件流指针
返回值:
成功:实际读到的元素个数
读到文件末尾:0
举个例子:
#include <stdio.h>struct stu
{int id;char name[32];int score;
};int main(int argc, const char *argv[])
{FILE *fp = fopen("./1.txt", "r");if (NULL == fp){printf("fopen error\n");return -1;}struct stu s;struct stu ss[10];size_t cnt = fread(&s, sizeof(struct stu), 1, fp);printf("cnt = %ld\n", cnt);printf("%d %s %d\n", s.id, s.name, s.score);cnt = fread(ss, sizeof(struct stu), 10, fp);printf("cnt = %ld\n", cnt);for (int i = 0; i < cnt; i++){printf("%d %s %d\n", ss[i].id, ss[i].name, ss[i].score);}fclose(fp);return 0;
}
运行结果如下:
文件读写操作应用练习:
1. 读取一个bmp文件的头部,打印该bmp文件的分辨率和像素深度
bmp文件的构成是这样的:
故我们在读取bmp文件的图片信息前,要先读取掉前14个字节的文件相关信息,让文件流指针指到图片相关信息部分,后再进行读取;
厘清思路,我们来对bmp的存储相关信息结构体开始定义:
#include <stdio.h>#pragma pack(1)//bmp文件相关信息
typedef struct tagBITMAPFILEHEADER {short bfType; // 文件类型标志int bfSize; // 文件大小,单位为字节short bfReserved1; // 保留字节short bfReserved2; // 保留字节int bfOffBits; // 数据偏移量,即实际图像数据开始的位置
}Bmp_file_head_t;
//bmp图像信息
typedef struct tagBITMAPINFOHEADER {int biSize; // BITMAPINFOHEADER的大小,单位为字节int biWidth; // 位图的宽度,单位为像素int biHeight; // 位图的高度,单位为像素short biPlanes; // 目标设备的位平面数,必须为1short biBitCount; // 每像素位数(颜色深度)int biCompression; // 图像压缩类型int biSizeImage; // 图像大小,单位为字节int biXPelsPerMeter;// 水平分辨率,单位为像素/米int biYPelsPerMeter;// 垂直分辨率,单位为像素/米int biClrUsed; // 实际使用颜色数int biClrImportant; // 重要颜色数
}Bmp_info_t;
(ps:此处对于bmp文件的相关信息来源于深入解析BMP文件格式及读写操作-CSDN博客)
在定义结构体之前,使用 “ pragma pack (1) ”来让结构体按1字节对齐,防止数据错乱;
int get_bmp_head_info(const char *bmpname, Bmp_file_head_t *pheadinfo, Bmp_info_t *pbmpinfo)
{FILE *fp = fopen(bmpname, "r");if (NULL == fp){printf("fopen error\n");return -1;}fread(pheadinfo, sizeof(Bmp_file_head_t), 1, fp);fread(pbmpinfo, sizeof(Bmp_info_t), 1, fp);fclose(fp);return 0;
}
int main(int argc, const char *argv[])
{Bmp_file_head_t headinfo;Bmp_info_t bmpinfo;get_bmp_head_info("./3.bmp", &headinfo, &bmpinfo);printf("sizeof(Bmp_file_head_t) = %ld\n", sizeof(Bmp_file_head_t));printf("sizeof(Bmp_info_t) = %ld\n", sizeof(Bmp_info_t));printf("biWidth = %d, biHeight = %d, biBitCount = %d\n", bmpinfo.biWidth, bmpinfo.biHeight, bmpinfo.biBitCount);return 0;
}
此后,正常打开bmp文件信息,先读前14个字节的 file_head_t文件,再读40个字节的file_info_t文件,后打印出结构体中的这些信息(分辨率与像素深度);
流定位相关接口
fseek和ftell
fseek 函数是这样使用的:
int fseek(FILE *stream, long offset, int whence);
功能:实现文件流重新定位
参数:
stream:需要定位的文件流指针
offset:偏移量
whence:定位的相对位置
SEEK_SET : 从头进行偏移
SEET_CUR:从当前位置开始偏移
SEET_END: 从文件末尾偏移
返回值:
成功:返回当前的偏移量
失败:-1(EOF)
ftell 函数是这样使用的:
long ftell(FILE *stream);
功能:获取流的当前位置到文开头的偏移量
参数:
stream:文件流
返回值:
偏移量:byte
举个例子:
#include <stdio.h>int main(int argc, const char *argv[])
{FILE *fp = fopen("./1.txt", "w");if (NULL == fp){printf("fopen error\n");return -1;}fseek(fp, 10, SEEK_SET);fputc('A', fp);long offset = ftell(fp);printf("offset = %ld\n", offset);fseek(fp, 5, SEEK_CUR);fputc('B', fp);offset = ftell(fp);printf("offset = %ld\n", offset);fseek(fp, -2, SEEK_CUR);fputc('C', fp);fseek(fp, 4, SEEK_END);fputc('D', fp);fseek(fp, -10, SEEK_END);fputc('E', fp);fclose(fp);return 0;
}
运行结果为:
rewind
rewind 是这样使用的:
void rewind(FILE *stream);
功能:流复位函数(复位到开头)
等价于:fseek(fp, 0, SEEK_SET);
练习题目:
1.获取文件的大小(字节数)
#include <stdio.h>int main(int argc, const char *argv[])
{FILE *fp = fopen("1.txt", "w"); if (NULL == fp){printf("fopen error\n");return -1;}fseek(fp, 0, SEEK_END);long len = ftell(fp)printf("%ld\n",len);fclose(fp);return 0;
}
先把指针移到文件的末尾,后使用 ftell 计算此时离文件开头的偏移量(即字节数)
2.模拟迅雷下载(抢占磁盘空间)
所谓的抢占磁盘空间,即是在硬盘里下载文件前,先产生一个空洞文件,占据下载内容大小的空间;如果硬盘大小不够,及时提醒用户硬盘空间不足;
模拟下载这个过程总共分为三步:
0.获取源文件大小
1. 抢占目标文件的磁盘空间
2. 将数据拷贝到目标磁盘对应的位置(fread/fwrite实现文件拷贝)
#include <stdio.h>
#include <stdlib.h>int main(int argc, const char *argv[])
{if (argc < 3){printf("Usage : ./a.out <srcfile> <dstfile>\n");return -1;}FILE *fpsrc = fopen(argv[1], "r");if (NULL == fpsrc){printf("fopen error srcfile\n");return -1;}//1、 获取源文件大小fseek(fpsrc, 0, SEEK_END);long len = ftell(fpsrc);rewind(fpsrc);//2、抢占目标文件的磁盘空间FILE *fpdst = fopen(argv[2], "w");if (NULL == fpdst){printf("fopen error dstfile\n");return -1;}fseek(fpdst, len-1, SEEK_SET);int ret = fputc('\0', fpdst);if (EOF == ret){printf("磁盘空间不足\n");fclose(fpsrc);fclose(fpdst);return -1;}rewind(fpdst); //3、数据拷贝char buff[1024] = {0};while (1){size_t cnt = fread(buff, 1, sizeof(buff), fpsrc);if (0 == cnt){break;}fwrite(buff, 1, cnt, fpdst);}fclose(fpsrc);fclose(fpdst);return 0;
}
以上就是今天和大家分享的内容!!!!感谢你的阅读!!!如有遗漏和错误,欢迎评论区进行指正!!!