【问题标题】:How to find and replace multiple or all occurences in C strings如何查找和替换 C 字符串中的多个或所有匹配项
【发布时间】:2021-09-15 02:13:57
【问题描述】:

目标是仅使用 C 字符串替换另一个字符串中给定文本的多次(或全部)出现。

(自答题)

【问题讨论】:

  • “匹配”是什么意思?正则表达式?或者是其他东西?您是否需要在替换中扩展反向引用?这个问题不清楚,也太宽泛了。
  • “匹配”只是指“在另一个文本中发现的给定文本的出现”。编辑了它,希望它更清楚。我打算分享对我有用的代码。

标签: c replace find c-strings


【解决方案1】:

这使用固定大小的缓冲区,您必须确保它们足够大以在替换完成后容纳字符串。

使用前定义尺寸:

#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);}

【讨论】:

    【解决方案2】:

    原来我太自尊了。该代码未经测试,如果没有适当的测试,我不应该发布它。我添加此评论以提醒其他人不要犯同样的错误。如果您发现任何其他错误,请告诉我。

    为了简单起见,我就不原地做了。相反,它需要一个预先分配的输出缓冲区。如果新字符串的大小比原始字符串长,那么就地执行是有风险的。而且还有一个边缘情况可能很难处理,那就是要替换的原始子字符串是新字符串的子字符串。

    运行 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=1n=SIZE_MAX 编写包装器。使用SIZE_MAX 是安全的,因为字符串不能大于这个值。

    我为一个替代品去掉了一个特殊功能的另一个原因是它非常低效。而且,这样写更容易,也更干净。

    与上次相比,我对代码进行了很多更改,这非常感谢我在 Codereview 获得的出色帮助。您可以在我在那里发布的问题上看到代码之前的情况:https://codereview.stackexchange.com/q/263785/133688

    【讨论】:

    • 这段代码对我不起作用,但它似乎是一个不错的实现!使用whilestrstr 是我的第一个想法,但由于我将" 替换为\",因此它进入了无限循环。
    • @Vitorbnc 有很多问题,但我认为现在已经更正了
    • 为什么是goto 而不是break
    • @Deduplicator 在这种情况下,我认为它更清楚。特别是因为我之前已经有另一个goto。当然,在这种情况下它是等价的。但它是符合意境的,并立即告诉读者,如果它是一个空指针,那么我们应该转到代码的最后部分并退出。有点像例外。
    猜你喜欢
    • 2020-06-23
    • 2012-02-07
    • 1970-01-01
    • 2023-01-06
    • 1970-01-01
    • 2011-08-29
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    相关资源
    最近更新 更多