前言:
昨天在微信公众号看到一篇博文,讲的是在linux环境中利用C语言将一个文本里的内容读出来,把里面的某个字符串替换成另一个字符串,再将内容回写回文本里。这个需求很简单,使用更高级的语言,比如Python,Java等,可能只需一两句话就可以了,但对C语言来说可能就比较没那么好实现。这里涉及到一些指针的应用,对指针要求比较高一些。参考博文地址:点击打开链接。
经过仔细的研读和改进,实现了在windows下的同样功能。
首先创建一个test.txt的文本文件,里面内容如下所示:
现在我们要将文本内容中的your替换成my,我在VS2015中编写程序,实现代码如下:
#include <stdio.h>
#include <string.h>
#include<stdlib.h>
#include<math.h>
// 该函数用来寻找子字符串在被查找的目标字符串中出现的次数;
// 第一个形参是一个指向被查找的目标字符串的指针;
// 第二个形参是一个指向要查找的子字符串的指针
int substr_count(char *str, const char *sub_str)
{
if (str == NULL || sub_str == NULL)
{
printf("ERROR:[substr_count] parameter(s) should not be NULL!\n");
return -1;
}
int n = 0; // 用来表示子串在被查找的目标字符串中出现的次数
char *begin_ptr = str;
char *end_ptr = NULL; // 将NULL初始化给end_ptr,防止指向不明
// strstr函数包含在头文件string.h中,其函数原型如下:
// char *strstr(const char *str, const char *SubStr),其参数解释如下:
// str --->指向被查找的目标字符串“父串”
// SubStr --->指向要查找的字符串对象“子串”
// 该函数用来搜索“子串”在“指定字符串”中第一次出现的位置(char *即字符串指针)
// 若成功找到,返回在“父串”中第一次出现的位置的char *指针
// 若未找到,也即不存在这样的子串,返回:“NULL”
while ((end_ptr = strstr(begin_ptr, sub_str)) != NULL)
{
end_ptr += strlen(sub_str);
begin_ptr = end_ptr; // 更新起始地址,重新调用strstr函数进行匹配。
++n;
}
return n;
}
int str_replace(const char *file_path, const char *new_str, const char *old_str)
{
if (file_path == NULL || new_str == NULL || old_str == NULL)
{
printf("ERROR: [str_replace] parameter(s) should not be NULL!\n");
return -1;
}
FILE *fp;
if ((fp = fopen(file_path, "a+")) == NULL)
{
printf("ERROR: file open error!\n");
return -1;
}
// 用fopen函数打开一个“读写”的文件(w+表示可读可写),如果打开文件成功,函数的返回值
// 是该文件所建立的信息区的起始地址,把它赋给指针变量fp(fp已定义为指向文件的指针变量)。
// 如果不能成功的打开文件,则返回NULL。
long file_len;
fseek(fp, 0, SEEK_END); // 将文件指针移动到文件结尾,成功返回0,不成功返回-1
file_len = ftell(fp); // 求出当前文件指针距离文件开始的字节数
fseek(fp, 0, SEEK_SET); // 再定位指针到文件头
// 在C语言中测试文件的大小,主要使用二个标准函数
// (1)fseek: 函数原型为 int fseek(FILE *_Stream,long _Offset,int _Origin)
// 参数说明: _Stream,文件流指针;_Offset,偏移量; _Origin,原始位置。其中 _Origin
// 的可选值有SEEK_SET(文件开始)、SEEK_CUR(文件指针当前位置)、SEEK_END(文件结尾)。
// 函数说明:对于二进制模式打开的流,新的流位置是 _Origin+_Offset。
// (2)ftell: 函数原型为 long int ftell(FILE * _Stream)
// 函数说明:返回流的位置。对于二进制流返回值为距离文件开始位置的字节数。
char *ori_str = (char *)malloc(file_len*sizeof(char) + 1);
if (ori_str == NULL)
{
printf("ERROR: malloc ori_str failed!\n");
fclose(fp);
return -1;
}
memset(ori_str, 0, file_len*sizeof(char) + 1);
// 开辟空间给ori_str
// malloc函数申请内存空间,file_len*sizeof(char)是为了更严谨,16位机器上char占一个字符,其他机器上可能变化
// 用 malloc函数申请的内存是没有初始值的,如果不赋值会导致写入到时候找不到结束标志符而出现内存比实际申请值大
// 写入数据后面跟随乱码的情况
// memset函数将内存空间都赋值为“\0"
int count = 1;
int ret = fread(ori_str, file_len*sizeof(char), count, fp);
// printf("%d\n",ret);
// C语言允许用fread函数从文件中读一个数据块,用fwrite函数向文件写一个数据块。在读写时
// 是以二进制形式进行的。在向磁盘写数据时,直接将内存中一组数据原封不动、不加转换的复制到
// 磁盘文件上,在读入时也是将磁盘文件中若干字节的内容一批读入内存。
// 它们的一般调用形式为:
// fread(buffer,size,count,fp);
// fwrite(buffer,size,count,fp);
// 其中,buffer是一个地址。对fread来说,它是用来存放从文件读入的数据的存储区的地址。
// 对fwrite来说,它是要把此地址开始的存储区中的数据向文件输出(以上指的是起始地址)
// size: 要读写的字节数;
// count: 要读写多少个数据项(每个数据项长度为size);
// fp: FILE类型指针。
// fread或fwrite函数的类型为int型,如果fread或fwrite函数执行成功,则函数返回值为形参count
// 的值,即输入或输出数据项的个数。
if (ret != count)
{
printf("ERROR: read file error!\n");
fclose(fp);
free(ori_str); //释放malloc开辟的内存区域
return -1;
}
int n = substr_count(ori_str, old_str);
if (n == -1)
{
printf("ERROR: substring count error!\n");
fclose(fp);
free(ori_str); //释放malloc开辟的内存区域
return -1;
}
// 计算子字符串在父串中出现的次数
// 如果出现错误,需要关闭文件,释放内存空间
// 不能忘记
int rst_str_len = file_len + n * abs(int(strlen(new_str) - strlen(old_str))) + 1;
char *rst_str = (char *)malloc(rst_str_len*sizeof(char));
if (rst_str == NULL)
{
printf("ERROR: malloc rst_str failed!\n");
fclose(fp);
return -1;
}
memset(rst_str, 0, rst_str_len*sizeof(char));
// 开辟空间给rst_str
// 替换的时候,新字符串(new_str)与旧字符串(old_str)长度不一致,
// 所以处理之后的字符串(rst_str)长度不一定跟处理前的字符串(ori_str)长度相同。
// 那么在给rst_str开辟空间的时候,分配多少内存呢?通过计算rst_str的长度rst_str_len。
// 最后多加一个1是为了预留一个字符空间用于存放“\0”。
char *cpy_str = rst_str;
char *begin_ptr = ori_str;
char *end_ptr = NULL;
// 替换过程
while ((end_ptr = strstr(begin_ptr, old_str)) != NULL) //子字符串只要匹配上,执行循环体
{
memcpy(cpy_str, begin_ptr, end_ptr- begin_ptr);
cpy_str += (end_ptr - begin_ptr);
memcpy(cpy_str, new_str, strlen(new_str)); //在字符串后面拷贝new_str
cpy_str += strlen(new_str);
end_ptr += strlen(old_str);
begin_ptr = end_ptr;
}
strcpy(cpy_str,begin_ptr);
// memcpy函数包含在头文件string.h中,其函数原型如下
// void *memcpy(void *dst,const void *src,size_t n);
// memcpy函数是内存拷贝函数,功能是从源src所指的内存地址的起始位置开始拷贝n个字节到
// 目标dst所指的内存地址的起始位置中。
printf("ori:%s\n", ori_str);
printf("rst:%s\n", rst_str);
FILE *fp_1;
if ((fp_1 = fopen(file_path, "w")) == NULL)
{
printf("ERROR: file open error!\n");
return -1;
}
// 至此,我们已经开辟了两块内存,其中一块用于存储处理前的字符串(ori_str)
// 我们已将文件内的数据保存在了ori_str所指向的区域中,
// 文本拷贝完毕之后,就要进行文本替换处理,待处理完成后要将结果回写回文件,
// 所以在此之前应该清空文件,否则可能原先的文本会有所残留,不会达到预期效果。
// 在这里我用了“只写”的方式,因为“w”的写入机制会事先删除文本内的所有内容。
// 用fopen函数打开一个“只读”的文件(w表示只读)
// 用w方式打开的文件只能用于向该文件写入数据(即输出文件),而不能用来向计算机输入。
// 如果原来不存在该文件,则在打开文件前新建立一个以指定的名字命名的文件。
// 如果原来已存在一个以该文件名命名的文件,则在打开文件前先将该文件删去,然后
// 重新建立一个新文件。
ret = fwrite(rst_str,strlen(rst_str),count, fp_1);
if (ret != count)
{
printf("ERROR: write file error!\n");
fclose(fp);
free(ori_str); //释放malloc开辟的内存区域
free(rst_str);
return -1;
}
//读写完成以后,释放内存,关闭文件
free(ori_str);
free(rst_str);
fclose(fp_1);
// 如果不关闭文件就结束程序运行将会导致丢失数据。因为,在向文件写数据时,是先将数据输出到
// 缓冲区,待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区时程序结束运行,就有可能
// 使缓冲区中的数据丢失。用fclose()函数关闭文件时,先把缓冲区中的数据输出到磁盘文件,然后
// 才撤销文件信息区。所以应当养成在程序终止之前关闭所有文件的习惯,
return 0;
}
int main()
{
char *new_str = "my";
char *old_str = "your";
char *file_path = "./test.txt"; // 相对地址,当前工作目录下的test.txt文件
if (str_replace(file_path, new_str, old_str) != 0)
{
printf("INFO: string replace failed!\n");
return -1;
}
system("pause");
}
运行程序,可得:
(1)替换过程原理参考原作者博文,很容易理解,过程如下所示:
(2)注意事项:
第一次生成解决方案的时候,编译器报错了,报错提示如下:
'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
为了解决vs2015使用fopen、fprintf等函数报错的问题,寻找了解决办法,如下:右键工程名-->属性-->C/C++-->预处理器-->预处理器定义,编辑右边输入框加入:_CRT_SECURE_NO_WARNINGS 。
工程文件下载地址:点击打开链接(https://download.csdn.net/download/weixin_41695564/10465518)