在实际开发中,程序读取配置文件以加载数据非常常见,如何安全高效地读取文件比较重要。本文代码来自于《c++新经典》。
?假设有一个txt格式的文件,里面记录了花里胡哨的内容,需要把这些内容一字不差地读取到程序中,应该如何用c++实现?
书中的代码如下:
#include <iostream>
using namespace std;
int main()
{
//打开文件
FILE* fp = nullptr;
errno_t err;
err = fopen_s(&fp, "config.txt", "r");
if (!fp) {
cout << "can not open file!" << endl;
}
//打开文件成功
else {
//定义存储每行字符串的数组
char linebuff[1024];
//没读到文件尾就一直执行
while (!feof(fp))
{
linebuff[0] = 0;//给第一位置零
//fgets函数功能为从指定的流中读取数据,每次读取一行。其原型为:char *fgets(char *str, int n, FILE *stream);
//从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止
if (fgets(linebuff, sizeof(linebuff) - 1, fp) == nullptr)//如果读到文件尾或者没读到字符,跳出本次循环
continue;
if (linebuff[0] == '\0') {//如果数组第一位是零,说明没读到内容,则跳出本次循环
continue;
}
lblprocstring:
if (strlen(linebuff) > 0) {//如果行尾是换行(0X0A,10)或者换行(0X0D,13)就都截掉
if (linebuff[strlen(linebuff) - 1] == 10 || linebuff[strlen(linebuff) - 1] == 13) {
linebuff[strlen(linebuff) - 1] = 0;//截掉,注意strlen不计算句尾的0,sizeof会计算
goto lblprocstring;
}
}
if (strlen(linebuff) <= 0)//如果是个空行,跳出本次循环
continue;
printf("%s\n", linebuff);//打印一行
}
fclose(fp);
}
return 0;
}
结果如下图所示,已经完整地将配置文件的内容打印出来了。
?代码使用一个字符数组记录每行内容,为防止读取失败,代码中通过第一位清零并查询的方式检查:
linebuff[0] = 0;//给第一位置零
......
if (linebuff[0] == '\0') {//如果数组第一位是零,说明没读到内容,则跳出本次循环
continue;
}
如果不需要代码非常严谨,可以将以上的内容从代码中删除。
读取一行文本的核心实现是以下代码:
//fgets函数功能为从指定的流中读取数据,每次读取一行。其原型为:char *fgets(char *str, int n, FILE *stream);
//从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止
if (fgets(linebuff, sizeof(linebuff) - 1, fp) == nullptr)//如果读到文件尾或者没读到字符,跳出本次循环
continue;
fgets函数读取到内容后,会返回文件流指针fp。
fgets读取 (n-1) 个字符时,或者读取错误时,或者到达文件末尾时,会返回nullptr。在这个情况我们需要考虑该如何处理。
1.如果读取到了最大字符数量,但是仍旧没有读到换行符,说明该行的字符长度超过了数组设置的长度:这种情况下会返回一个不完整的行,并且在下一次会继续在该行进行字符读取。按照书中的设计思路,这种情况不应该出现,配置文件每行的最长长度应该有个明确的上限,该上限决定了字符数组的长度。
2.读取错误:这种情况下没别的可以做,应该采用continue跳过该次循环,进行重复读取 。
3.到达文件尾:这种情况下需要与情况2进行区分,因此循环应使用feof(fp)作为循环的判断依据,通过feof而不是fgets来判断文件是否读取完毕,既然这个任务交给feof去做了,那么这里依然采用continue跳过该次循环即可。
数组读取完数据之后需要对句尾进行裁剪,把回车符号都去掉:
lblprocstring:
if (strlen(linebuff) > 0) {//如果行尾是换行(0X0A,10)或者换行(0X0D,13)就都截掉
if (linebuff[strlen(linebuff) - 1] == 10 || linebuff[strlen(linebuff) - 1] == 13) {
linebuff[strlen(linebuff) - 1] = 0;//截掉,注意strlen不计算句尾的0,sizeof会计算
goto lblprocstring;
}
}
if (strlen(linebuff) <= 0)//如果是个空行,跳出本次循环
continue;
printf("%s\n", linebuff);//打印一行
这里采用了一个goto循环,在循环中不断地判断数组内数据的尾部是不是回车符或者换行符,若是的话便对该符号进行清零操作。注意这里使用strlen()-1来判断数据末尾的位置,这是因为strlen函数是自动无视掉字符串末尾的‘/0’的,而sizeof并不会,因此在这种末尾清零操作中必须使用strlen。
由于不习惯使用goto循环,我改成了while循环:
while (strlen(linebuff)>0)
{
if (linebuff[strlen(linebuff) - 1] == 10 || linebuff[strlen(linebuff - 1)] == 13) {
linebuff[strlen(linebuff) - 1] = 0;
}
else {
break;
}
}
|