【发布时间】:2021-09-15 02:13:57
【问题描述】:
目标是仅使用 C 字符串替换另一个字符串中给定文本的多次(或全部)出现。
(自答题)
【问题讨论】:
-
“匹配”是什么意思?正则表达式?或者是其他东西?您是否需要在替换中扩展反向引用?这个问题不清楚,也太宽泛了。
-
“匹配”只是指“在另一个文本中发现的给定文本的出现”。编辑了它,希望它更清楚。我打算分享对我有用的代码。
目标是仅使用 C 字符串替换另一个字符串中给定文本的多次(或全部)出现。
(自答题)
【问题讨论】:
这使用固定大小的缓冲区,您必须确保它们足够大以在替换完成后容纳字符串。
使用前定义尺寸:
#define LINE_LEN 256
此代码已使用 MSVC 2019 进行测试。
void replaceN(char* line,const char* orig,const char* new, int times){
char* buf;
if(times==0) return; //sem tempo irmao
if((times==-1||--times>0) && (buf = strstr(line,orig))!=NULL){ //find orig
for(const char *c=orig;*c;c++) buf++; //advance buf
replaceN(buf,orig,new,times); //repeat until the last occurrence
}
//this will run first for the last match
if((buf = strstr(line,orig))!=NULL){
char tmp[LINE_LEN];
int i = buf-line; //pointer difference
strncpy(tmp,line,i); //copy everything before the match
for(const char *k=orig;*k;k++) buf++; //buf++; //skip find string
for(const char *k=new;*k;k++) tmp[i++]=*k; //copy replace chars
for(;*buf;buf++) tmp[i++]=*buf; //copy the rest of the string
tmp[i]='\0';
strcpy(line,tmp);
}
}
inline void replace(char* line,const char* orig,const char* new){replaceN(line, orig, new, 1);}
inline void replaceAll(char* line,const char* orig,const char* new){replaceN(line,orig,new,-1);}
【讨论】:
原来我太自尊了。该代码未经测试,如果没有适当的测试,我不应该发布它。我添加此评论以提醒其他人不要犯同样的错误。如果您发现任何其他错误,请告诉我。
为了简单起见,我就不原地做了。相反,它需要一个预先分配的输出缓冲区。如果新字符串的大小比原始字符串长,那么就地执行是有风险的。而且还有一个边缘情况可能很难处理,那就是要替换的原始子字符串是新字符串的子字符串。
运行 allt his 所需的标头:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
主要的替换功能。它替换最多 n 次出现并返回替换次数。 dest 是一个足够大的缓冲区来保存结果。所有指针都必须为非 NULL 且有效。您可能会注意到我使用的是goto,这可能会令人不悦,但使用它干净地退出非常方便。
size_t replace(char *dest, const char *src, const char *orig,
const char *new, size_t n) {
size_t ret = 0;
// Maybe an unnecessary optimization to avoid multiple calls in
// loop, but it also adds clarity
const size_t newlen = strlen(new);
const size_t origlen = strlen(orig);
if(origlen == 0 || n == 0) goto END; // Edge cases
do {
const char *match = strstr(src, orig);
if(!match) goto END;
// Length of the part of src before first match
const ptrdiff_t offset = match - src;
memcpy(dest, src, offset); // Copy before match
memcpy(dest + offset, new, newlen); // Replace
src += offset + origlen; // Move src past what we have already copied.
dest += offset + newlen; // Advance pointer to dest to the end
ret++;
} while(n > ret);
END:
strcpy(dest, src); // Copy whatever is remaining
return ret;
}
为分配编写一个包装器很容易。我们借用和修改find the count of substring in string的部分代码
size_t countOccurrences(const char *str, const char *substr) {
if(strlen(substr) == 0) return 0;
size_t count = 0;
const size_t len = strlen(substr);
while((str = strstr(str, substr))) {
count++;
str+=len // We're standing at the match, so we need to advance
}
return count;
}
然后是一些计算缓冲区大小的代码
size_t calculateBufferLength(const char *src, const char *orig,
const char *new, size_t n) {
const size_t origlen = strlen(orig);
const size_t newlen = strlen(new);
const size_t baselen = strlen(src) + 1;
if(origlen > newlen) return srclen;
n = n < count ? n : count; // Min of n and count
return baselen +
n * (newlen - origlen);
}
还有最后的功能。它结合了分配和替换。它返回一个指向缓冲区的指针,如果分配失败则返回 NULL。
char *replaceAndAllocate(const char *src, const char *orig,
const char *new, size_t n) {
const size_t count = countOccurrences(src, orig);
const size_t size = calculateBufferLength(src, orig, new, n);
char *buf = malloc(size);
if(buf) replace(buf, src, orig, new, n);
return buf;
}
最后,一个简单的main 带有一些测试用例
int main(void) {
puts(replaceAndAllocate("hoho", "ha", "he", SIZE_MAX ));
puts(replaceAndAllocate("", "", "", 5));
puts(replaceAndAllocate("", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "", 5));
puts(replaceAndAllocate("", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 5));
puts(replaceAndAllocate("hihihi!!!", "hi", "of", 2));
puts(replaceAndAllocate("!!!hihihi", "hi", "x", 3));
puts(replaceAndAllocate("asdfasdfasdf", "asdf", "x", 2));
puts(replaceAndAllocate("xxxxxxxxxxxx", "x", "y", SIZE_MAX ));
puts(replaceAndAllocate("xxxxxxxxxxxx", "x", "y", 0));
puts(replaceAndAllocate("xxxxxxxxxxxx", "x", "y", 1));
puts(replaceAndAllocate("xxxxxxxxxxxx", "x", "", SIZE_MAX ));
puts(replaceAndAllocate("xxxxxxxxxxxx", "x", "", 3 ));
puts(replaceAndAllocate("!asdf!asdf!asdf!", "asdf", "asdf#asdf", SIZE_MAX));
// Yes, I skipped freeing the buffers to save some space
}
-Wall -Wextra -pedantic 没有警告,输出为:
$ ./a.out
hoho
ofofhi!!!
!!!xxx
xxasdf
yyyyyyyyyyyy
xxxxxxxxxxxx
yxxxxxxxxxxx
xxxxxxxxx
!asdf#asdf!asdf#asdf!asdf#asdf!
请注意,我没有任何替换一个和替换所有的特殊功能。如果你真的想要这些,只需使用n=1 或n=SIZE_MAX 编写包装器。使用SIZE_MAX 是安全的,因为字符串不能大于这个值。
我为一个替代品去掉了一个特殊功能的另一个原因是它非常低效。而且,这样写更容易,也更干净。
与上次相比,我对代码进行了很多更改,这非常感谢我在 Codereview 获得的出色帮助。您可以在我在那里发布的问题上看到代码之前的情况:https://codereview.stackexchange.com/q/263785/133688
【讨论】:
while 和strstr 是我的第一个想法,但由于我将" 替换为\",因此它进入了无限循环。
goto 而不是break?
goto。当然,在这种情况下它是等价的。但它是符合意境的,并立即告诉读者,如果它是一个空指针,那么我们应该转到代码的最后部分并退出。有点像例外。