目录
一、实验要求
二、实验原理
1. 文件的基本概念
2. 文件指针
3. 文件的打开与关闭
3.1 fopen
3.2 fclose
4. 文件的读写操作
4.1 fread
4.2 fwrite
5. 文件的定位
三、实验内容
3.1 文件复制
代码
截图
分析
3.2 单词统计
代码
截图
分析
一、实验要求
1.文件基本操作:
目的: 熟悉文件的基本概念,学会如何打开、关闭文件。
任务: 编写程序实现文件的创建、打开、关闭等基本文件操作,理解文件指针的概念。
2.读写文件操作:
目的: 掌握从文件读取数据和向文件写入数据的基本技能。
任务: 编写程序读取文件内容并在屏幕上显示,然后将一些数据写入文件,检验文件的读写操作是否成功。
3.错误处理和高级操作:
目的: 提高对文件操作的鲁棒性,了解一些高级的文件操作技巧。
任务: 添加错误处理机制,处理文件打开失败等异常情况。尝试使用 fseek 进行文件定位,学会处理二进制文件,实现格式化输入输出。
二、实验原理
1. 文件的基本概念
文件是计算机存储设备上的一组数据,可以是文本、图像、音频等形式的信息。
分为文本文件和二进制文件
文本文件:文本文件包含纯文本,由字符组成,可以用文本编辑器打开查看。在C语言中,文本文件的处理通常涉及到字符的读写。
二进制文件:二进制文件包含以二进制形式编码的数据,可以包含任意类型的数据,如图像、音频、可执行文件(exe)等。在C语言中,处理二进制文件通常需要使用二进制读写操作。
2. 文件指针
文件指针用于跟踪文件位置,通过文件指针,可以确定文件中当前读写的位置。
文件指针通常通过 FILE类型的结构体来表示。
FILE* 指针名;
3. 文件的打开与关闭
3.1 fopen
fopen函数位于C语言中的stdio.h库内和C++中的fstream库,用于打开一个文件,并返回一个指向 FILE
结构体的指针,该指针将被用于后续的文件操作。
函数原型:
FILE *fopen(const char *filename, const char *mode);
如果文件成功打开,则返回一个指向 FILE 结构体的指针。
如果打开失败,则返回 NULL。
参数:
filename
:字符串,指定要打开的文件的路径和名称。mode
:字符串,指定文件的打开模式。常见的模式包括:
"r"
:只读,文件必须存在。"w"
:写入,如果文件存在则截断文件,如果文件不存在则创建新文件。"a"
:追加,写入数据到文件末尾,如果文件不存在则创建新文件。"rb"
,"wb"
,"ab"
:以二进制模式打开文件。"r+"
:读写,文件必须存在。"w+"
:读写,如果文件存在则截断文件,如果文件不存在则创建新文件。"a+"
:读写,写入数据到文件末尾,如果文件不存在则创建新文件。"r+b"
,"w+b"
,"a+b"
:以二进制模式打开文件,允许读写。
3.2 fclose
fclose函数位于C语言中的stdio.h库内和C++中的fstream库,用于关闭之前通过 fopen 函数打开的文件。
函数原型:
int fclose(FILE *stream);
如果文件成功关闭,则返回零(0)。
如果关闭失败,则返回非零值。
如果文件关闭成功,则返回值为零。非零值通常表示关闭失败,可能是因为文件已经被关闭或者其他原因。
参数:
一个指向已打开文件的指针,该文件是通过 fopen
打开的。
例如:
#define _CRT_SECURE_NO_WARNINGS
#include<fstream>
#include<iostream>
using namespace std;
int main() {
FILE* filePointer = fopen("C:\\Users\\Administrator\\Desktop\\csdn.txt", "r");
if (filePointer == NULL) {
cout<<"无法打开文件"<<endl;
}
else {
cout << "可以打开文件" << endl;
}
FILE* filePointer1= fopen("C:\\Users\\Administrator\\Desktop\\csdn.txt", "w");
cout << filePointer1;
/*
if (fclose(filePointer) == 0) {
cout << "文件成功关闭" << endl;
}
else {
cout << "文件关闭失败"<<endl;
}
*/
if (fclose(filePointer1) == 0) {
cout << "文件成功关闭";
}
else {
cout<<"文件关闭失败";
}
return 0;
}
由于csdn文件之前并不存在,但在w模式下的fopen作用后结果如下
至于加上注释的那部分代码,由于第一个fopen返回的是NULL,则无法关闭一个不存在的文件,会报错
切记写文件路径时要用"\\"
养成用fclose的好习惯
4. 文件的读写操作
4.1 fread
fread函数用于从文件中读取数据。
函数原型:
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
参数:
ptr
:指向存储读取数据的内存块的指针。size
:每个数据项的字节数。count
:要读取的数据项的数量。stream
:指向FILE
结构体的指针,表示要从中读取数据的文件。
返回实际读取的数据项数量。如果出现错误或到达文件末尾,可能返回一个小于 count
的值,通过 feof
和 ferror
函数可以检查是因为文件末尾还是因为错误。
feof函数:
- 如果到达了文件末尾,
feof
返回非零值(true)。- 如果未到达文件末尾,
feof
返回零值(false)。
ferror函数:
- 如果发生错误,
ferror
返回非零值(true)。- 如果没有发生错误,
ferror
返回零值(false)。
例如:以上面创建的csdn.txt为例,由于刚创建时该txt中没有内容
则
#define _CRT_SECURE_NO_WARNINGS
#include<fstream>
#include<iostream>
using namespace std;
int main() {
FILE* filePointer;
char buffer[100];
// 以只读方式打开文件
filePointer = fopen("C:\\Users\\Administrator\\Desktop\\csdn.txt", "r");
// 检查文件是否成功打开
if (filePointer == NULL) {
cout<<"无法打开文件";
return 1; // 退出程序
}
// 从文件中读取数据,请求读取100个数据,读入到buffer数组中
size_t bytesRead = fread(buffer, sizeof(char), sizeof(buffer), filePointer);
// 检查读取是否成功
if (bytesRead > 0) {
cout<<"成功读取"<<bytesRead<<"字节的数据";
// 处理读取的数据
for (int i = 0; i < bytesRead; i++) {
cout << buffer[i];
}
}
else {
if (feof(filePointer)) {
cout<<"已达到文件末尾";
}
else if (ferror(filePointer)) {
cout<<"读取错误";
}
}
// 关闭文件
fclose(filePointer);
return 0;
}
结果为
当我们在文件中手动添加"xiaoming"进去时再调用上述代码
结果为
注意事项:
1. fread 通常用于读取二进制数据,如果读取文本数据,可以使用 fgets 或者其他文本读取函数。
2. 通过计算 size * count 可以得到总共读取的字节数。
3. fread 可能在读取的数据量小于请求的数据量时,返回小于 count 的值。这可能是因为到达文件末尾或发生了错误。
4. 在使用 fread 之前,应该确保文件已经被成功打开。5.在文件读取过程中,如果
fread
成功读取的字节数小于请求的字节数,不一定表示文件已经到达末尾,可能是由于文件中数据不足请求的数量,或者发生了一些错误。上例就是,所以只有bytesRead等于0时,才用到feof和ferror。
4.2 fwrite
fwrite函数用于将数据写入文件。
函数原型:
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
参数:
ptr
:指向包含要写入的数据的内存块的指针。size
:每个数据项的字节数。count
:要写入的数据项的数量。stream
:指向FILE
结构体的指针,表示要写入数据的文件。
返回实际写入的数据项数量。如果出现错误,可能返回一个小于 count
的值。
还是以上面的csdn.txt为例(不过此时csdn.txt内容为空)
#define _CRT_SECURE_NO_WARNINGS
#include<fstream>
#include<iostream>
using namespace std;
int main() {
FILE* filePointer;
char buffer[] = "Hello, world!";
// 以只写方式打开文件
filePointer = fopen("C:\\Users\\Administrator\\Desktop\\csdn.txt", "w");
// 检查文件是否成功打开
if (filePointer == NULL) {
cout<<"无法打开文件";
return 1; // 退出程序
}
// 将数据写入文件
size_t itemsWritten = fwrite(buffer, sizeof(char), sizeof(buffer), filePointer);
// 检查写入是否成功
if (itemsWritten == sizeof(buffer) / sizeof(char)) {//只能读取整个字符
cout<<"成功写入" <<itemsWritten<<"个数据项";
}
else {
cout<<"写入错误";
}
// 关闭文件
fclose(filePointer);
return 0;
}
结果为:
注意事项:
1. fwrite 通常用于写入二进制数据,如果写入文本数据,可以使用 fputs 或者其他文本写入函数。
2. 通过计算 size * count 可以得到总共写入的字节数。
3. fwrite 可能在写入的数据量小于请求的数据量时,返回小于 count 的值。这可能是因为文件空间不足或者发生了错误。
4. 在使用 fwrite 之前,应该确保文件已经被成功打开,以及数据的来源内存块合法。
5. 如果文件不存在,fwrite 会尝试创建文件;如果文件存在,fwrite 会覆盖文件内容。
5. 文件的定位
fseek函数用于在文件中定位文件指针的位置。
函数原型:
int fseek(FILE *stream, long int offset, int whence);
参数:
stream
:指向FILE
结构体的指针,表示要定位文件指针的文件。offset
:要移动的字节数,可以为正数、负数或零。whence
:定位的起始位置,可以是SEEK_SET
、SEEK_CUR
或SEEK_END
。
SEEK_SET
:从文件开头计算偏移。SEEK_CUR
:从文件指针当前位置计算偏移。SEEK_END
:从文件末尾计算偏移。
如果成功,返回零。
如果发生错误,返回非零值。
以上述csdn.txt为例,此时里面内容为Hello,world!
#define _CRT_SECURE_NO_WARNINGS
#include<fstream>
#include<iostream>
using namespace std;
int main() {
FILE* filePointer;
char buffer[100],buffer1[100],buffer2[100];
// 以只读方式打开文件
filePointer = fopen("C:\\Users\\Administrator\\Desktop\\csdn.txt", "r");
// 检查文件是否成功打开
if (filePointer == NULL) {
cout<<"无法打开文件";
return 1; // 退出程序
}
// 将文件指针定位到文件的第 5 个字节处(从文件开头计算)
if (fseek(filePointer, 5, SEEK_SET) == 0) {
// 读取文件指针当前位置后的数据
fread(buffer,sizeof(char), sizeof(buffer), filePointer);
cout<<"定位后的数据:"<<buffer<<endl;
}
else {
cout<<"定位失败";
}
// 将文件当前指针定位到文件的第 2 个字节处(从文件开头计算)
//当前指针已经到文件末尾,应归零
fseek(filePointer,0,SEEK_SET );
if (fseek(filePointer, 2, SEEK_CUR) == 0) {
// 读取文件指针当前位置后的数据
fread(buffer1, sizeof(char), sizeof(buffer1), filePointer);
cout << "定位后的数据:" << buffer1<<endl;
}
else {
cout << "定位失败";
}
//读取文件最后两个字节的数据
if (fseek(filePointer, -2, SEEK_END) == 0) {
// 读取文件指针当前位置后的数据
fread(buffer2, sizeof(char), sizeof(buffer2), filePointer);
cout << "定位后的数据:" << buffer2;
}
else {
cout << "定位失败";
}
// 关闭文件
fclose(filePointer);
return 0;
}
结果为:
三、实验内容
3.1 文件复制
编写一个程序,要求从一个输入文件中读取内容,并将其复制到一个输出文件中。用户应该能够提供输入文件名和输出文件名。
代码
#define _CRT_SECURE_NO_WARNINGS
#include<fstream>
#include<iostream>
using namespace std;
int main() {
FILE* filePointer,*filePointer1;
char buffer[100],input_name[100],output_name[100];
cout << "请输入输入文件名:";
cin >> input_name;
cout << "请输入输出文件名:";
cin >> output_name;
// 以只读方式打开输入文件
filePointer = fopen(input_name, "r");
// 检查文件是否成功打开
if (filePointer == NULL) {
cout<<"无法打开文件";
return 1; // 退出程序
}
//把输入文件数据读取到buffer中
size_t bytesRead = fread(buffer, sizeof(char), sizeof(buffer), filePointer);
// 检查读取是否成功
if (bytesRead > 0) {
cout << "成功读取" << bytesRead << "字节的数据";
}
else {
if (feof(filePointer)) {
cout << "已达到文件末尾";
}
else if (ferror(filePointer)) {
cout << "读取错误";
}
}
// 关闭文件
fclose(filePointer);
// 以只写方式打开文件
filePointer1 = fopen(output_name, "w");
// 检查文件是否成功打开
if (filePointer1 == NULL) {
cout << "无法打开文件";
return 1; // 退出程序
}
// 将数据写入文件
//至于为什么不是size_t itemsWritten = fwrite(buffer, sizeof(char), sizeof(buffer), filePointer1),是由于buffer的大小并不是实际的大小,只是预分配空间的大小
size_t itemsWritten = fwrite(buffer, sizeof(char), sizeof(char)*bytesRead, filePointer1);
// 检查写入是否成功
if (itemsWritten == bytesRead) {//只能读取整个字符
cout << "成功写入" << itemsWritten << "个数据项";
}
else {
cout << "写入错误";
}
// 关闭文件
fclose(filePointer1);
return 0;
}
截图
分析
文件处理:
- 用户被要求输入输入文件名和输出文件名。
- 打开输入文件以只读方式,检查是否成功打开。
- 读取文件内容到缓冲区中。
- 关闭输入文件。
- 打开输出文件以只写方式,检查是否成功打开。
文件读写:
- 使用
fread
从输入文件读取数据到缓冲区。- 使用
fwrite
将缓冲区的数据写入输出文件。输出:
- 打印读取和写入的字节数。
分析:
- 使用
FILE*
和fopen
进行C风格的文件处理。- 输入文件的内容被读取到固定大小的缓冲区中(大小为100个字符),这可能导致截断文件内容,特别是对于大文件。
- 写入文件时,使用
sizeof(char)*bytesRead
来确保仅写入已读取的字节数。- 如果写入的字节数与已读取的字节数相等,程序认为写入成功。
- 程序中没有检查输入文件是否为空,如果为空,可能导致意外行为。
3.2 单词统计
编写一个程序,要求从一个文本文件中读取内容,并统计文件中每个单词出现的次数。程序应该输出每个单词和其出现的次数,并且不区分大小写。你可以使用一个简单的空格分隔单词。用户应该能够提供输入文件名。
示例:
输入文件内容:
This is a simple example. This example demonstrates word counting.输出:
"this"出现2次
"is"出现1次
"a"出现1次
"simple"出现1次
"example"出现2次
"demonstrates"出现1次
"word"出现1次
"counting"出现1次
本题的思路是,遍历文件内容,如果遇到不符合大写字母和小写字母ASCII码范围的,表明一个单词的结束,如果这个单词第一次出现,则保存到单词数组中,否则该单词次数加1,且全以小写字母为标准。
代码
#define _CRT_SECURE_NO_WARNINGS
#include<fstream>
#include<iostream>
using namespace std;
int main() {
FILE* filePointer;
char buffer[100],input_name[100];
char word_list[100][100] = {'\0'};//单词表
int word_count[100] = { 0 };//单词出现次数
int word_length[100] = { 0 };//单词长度
int count = 0;//单词个数(不包括重复单词个数)
cout << "请输入输入文件名:";
cin >> input_name;
// 以只读方式打开输入文件
filePointer = fopen(input_name, "r");
// 检查文件是否成功打开
if (filePointer == NULL) {
cout<<"无法打开文件";
return 1; // 退出程序
}
//把输入文件数据读取到buffer中
size_t bytesRead = fread(buffer, sizeof(char), sizeof(buffer), filePointer);
// 检查读取是否成功
if (bytesRead > 0) {
cout << "成功读取" << bytesRead << "字节的数据";
}
else {
if (feof(filePointer)) {
cout << "已达到文件末尾";
}
else if (ferror(filePointer)) {
cout << "读取错误";
}
}
//关闭文件
fclose(filePointer);
int index = 0;//每个单词的起始索引
char word[15] = {'\0'};
for (int i = 0; i < bytesRead; i++) {
if ((65 <= buffer[i] && buffer[i] <= 90) || (97 <= buffer[i] && buffer[i] <= 122)) {//如果字符合法
if (65 <= buffer[i] && buffer[i] <= 90) {
word[index++] = buffer[i]+32;
}
else {
word[index++] = buffer[i];
}
}
else {
if (strlen(word) == 0) {//如果字符串为空,跳出循环进行下一轮
continue;
}
//判断此单词第一次出现还是重复出现
int flag = 1;//第一次出现
for (int k = 0; k < count; k++) {
//使用 strcmp 比较字符串时,确保两个字符串都以空字符结尾。
if (strcmp(word, word_list[k]) == 0) {//如果出现重复单词
word_count[k]++;
flag = 0;
break;
}
}
if (flag == 1) {//第一次出现,加入单词表中
for (int j = 0; j < index; j++) {
word_list[count][j] = word[j];
}
word_length[count] = index;
word_count[count]++;
count++;
}
for (int j = 0; j < index; j++) {//字符串清零
word[j] = '\0';
}
index = 0;//重新回到起始点
}
}
for (int i = 0; i < count; i++) {
for (int j = 0; j < word_length[i]; j++) {
cout << word_list[i][j];
}
cout << "出现次数:" << word_count[i] << endl;
}
return 0;
}
截图
分析
文件处理:
- 打开由用户指定的文件进行读取。
- 检查文件是否成功打开。
- 将文件内容读入一个缓冲区。
单词处理:
- 程序使用循环迭代缓冲区中的每个字符。
- 从缓冲区提取单词,将大写字母转换为小写。
- 维护一个数组
word_list
以存储唯一单词,一个数组word_count
以存储每个单词的计数,以及一个数组word_length
以存储每个单词的长度。- 使用嵌套循环检查提取的单词是新单词还是重复单词,并相应地更新数组。
输出:
- 程序打印唯一单词以及它们的计数。
分析:
- 使用
FILE*
和fopen
表明使用了C风格的文件处理,但与C++的iostream混合使用。- 代码似乎使用ASCII值来检查字母字符。
- 单词处理部分采用手动方法,而不是使用C++的字符串功能。
- 如果单词超过15个字符,可能会导致缓冲区溢出。