我有一个datas.txt文件:
格式 : 姓氏 债务偿还
bir bir 100 2
iki iki 200 2
eray alakese 100 5
john doe 2000 10
我正在学习C,我只知道简单的文件函数(fscanf,fprinf,fopen等(
我会
- 询问用户名和姓氏
scanf
,然后将它们分配给名称和姓氏变量。 - 它将搜索文件名和姓氏,然后将债务和付款分配给债务,付款变量(
fscanf(file, "%s %s %d %d", name, surname, &debt, &payment);
( - 删除或修改此行
这是我的源代码。
scanf("%s", &name);
scanf("%s", &surname);
file = fopen("datas.txt", "r");
/* this fscanf() is working as expected. There is no problem. */
fscanf(file, "%s %s %d %d", name, surname, &debt, &payment);
/* modify and delete actions here */
fclose(file);
例子:
- 我想删除"约翰·多伊"的记录。
- 我想将"约翰·多伊"的债务减少到 100 美元
您不能删除/修改文本文件的单个行;唯一的解决方案是 1( 创建一个新的临时文件,2( 将内容复制到(但不包括(要修改/删除的行,3( 输出修改后的行,4( 复制原始文件的其余部分,5( 用临时文件替换旧文件。
[*] 仅当修改后的行与原始行的长度相同时,才能进行修改。
编辑:PS:使用fgets,然后是sscanf(或其他标记行的方法(将为您节省很多悲伤。
这有点难做到,因为 C 的文件模型继承自 Unix(它们主要是共同开发的(,实际上并没有将文件定义为行列表。相反,它将一行定义为以换行符结尾的字节字符串,将文件(大致(定义为存储的字节字符串,长度可能有限,您可以在其中跳到不同的部分。这相当模糊,但请耐心等待。
当我们尝试将我们的想法("修改此行"、"删除该行"(转换为文件操作时,问题变得更加清晰。我们可以通过在换行符处停止来读取一行,但是根本没有命令将其切成部分;只设置结束(ftruncate(((。因此,要更改行的大小,我们需要复制其后的所有数据。这是可以完成的,但重新创建文件通常更容易。比较实现 memmove(( 的微妙之处。
这样做的传统方法有两种变体,具体取决于您可以忍受的副作用。
一种是将更新的版本写入另一个文件中,然后将其重命名((。这样做的好处是,新文件在您放置到位时已经完成,但缺点是它可能与旧文件在权限等方面不完全匹配,并且对于已经打开旧文件的其他程序不可见。如果两个程序像这样修改文件,则这是一个争用条件,因为其中一个更改被另一个更改覆盖。
另一种是完全加载数据并将修改后的版本写到位。这意味着文件本身、权限和所有内容都保留在原位,但在您保存时会有一个持续时间,它是新旧内容的混合。文本编辑器倾向于这样做,通常同时将旧内容保存为单独的文件,以防出现问题。
还有一些工具可以管理副作用,例如版本控制系统,文件锁定,甚至是为并行更改准备的库(想到metakit(。大多数时候,我们将使用已经存在的工具,例如 sed -i。
为了删除或更改一行,您必须"移动"它后面的所有内容。例如,请考虑以下两个文件:
bir bir 100 2 bytes 0-14
iki iki 200 2 bytes 15-29
eray alakese 100 5 bytes 30-49
john doe 2000 10 bytes 50-67
和
bir bir 100 2 bytes 0-14
iki iki 200 2 bytes 15-29
john doe 2000 10 bytes 30-57 <-- byte offsets have changed
这当然是可能的,但一般来说支持起来非常复杂(你必须做很多寻找和告诉(。更常见的方法是有效地复制文件:您从输入文件中读入并将所有内容打印到输出文件中,进行所需的修改。(例如,要"删除"一行,您根本不打印该行。然后,最后,在关闭两个文件后,您"重命名"输出文件以覆盖输入文件。这是命令行实用工具(如 sed
和 perl
(在指示"就地"修改文件时使用的方法。
通常要做的是读取所有文件并将其全部写回临时文件,然后删除原始文件并重命名临时文件。
/* pseudo-code!! */
fopen();
while (fscanf(source, ...)) {
/* massage data */
fprintf(temporary, ...);
}
fclose();
remove(source);
rename(temporary, source);
我通常处理这样的事情的方式是编写一个函数,该函数可以"读入"您的数据并将其存储到某种结构中。然后是一个将数据从结构写入文件的函数。
这样,您就可以操作数组中的数据。这也使您的程序更具可扩展性,可以执行排序或额外的数学运算,而这些操作无法通过写入文件顶部来完成。
例如,尝试编写一个可以读入结构的函数,例如:
struct Client
{
char name[255];
double owes;
double paid;
}
然后你做什么来制作这些结构的数组并操纵它们。您将学到很多关于结构,动态内存分配的知识,并且无疑会遇到一些有趣的问题,这些问题将帮助您学习。
我的建议是也跳过C并C++...从长远来看,使用 iostreams 而不是 *printf/*scanf 函数和向量来学习这些东西可能对你更好。