【问题标题】:Search and replace c-style strings in c++在 C++ 中搜索和替换 C 风格的字符串
【发布时间】:2012-05-05 15:19:34
【问题描述】:

我正在尝试编写一个接受三个 c 样式字符串并返回一个 c 样式字符串的函数。此函数在 c 字符串中搜索所有出现的子字符串,并用不同的字符串替换它们。
这个程序有效,但看起来很不优雅。我情不自禁地感觉它本来可以用不那么笨重的方式完成。

char* replaceSubstring(char *original, char *from, char *to)
{
     int origlen = strlen(original);
     int i = 0;
     int count = 0;
     char *ptr;

     //figure out how many times the sub-string occurs in a string.
     //i couldn't figure out a way to avoid this loop
     while (i<origlen)
     {
           ptr = strstr(original+i, from);
           if (!ptr)
               break;
           else
           {
               i = ptr - original + 1;
               count++;
           }
     }
     //figure out what the size of the output string has to be
     int newsize = origlen + (strlen(to) - strlen(from)) * count;

     char *newstring = new char[newsize];  
     newstring[0] = '\0';  
     i = 0;
     while (i < origlen)
     {
          ptr = strstr(original+i, from);
          if (!ptr)
          {
               strcat(newstring,original+i);
               break;
          }
          else
          {
               //this looks extremely ugly and bulky...
               strncat(newstring, original+i, ptr-(original+i));
               strcat(newstring, to);
               i = i + ptr - (original + i) + strlen(from);
          }
     }
     strcat(newstring,"\0");
     return newstring;
}

有人对如何使这段代码更清晰和/或更高效有任何建议吗? 欢迎任何cmets。 请不要建议改用类字符串。这不是一个选择。该函数必须与 c-strings 一起使用

【问题讨论】:

  • 多次使用strcat 可能会影响性能,因为对于每次调用,您都必须迭代整个字符串。此外,strcat(newstring, "\0") 实际上什么都不做,因为无论如何newstring 必须以NULL 结尾才能与strcat 一起使用。
  • 不能使用字符串类?这样的要求让我怀疑这是不是作业……是吗?
  • 不,这不是家庭作业,我刚刚意识到我已经有一段时间没有使用 c-strings 了,我必须重新了解它们是如何工作的。所以我为自己创建了一些练习,只是为了看看我可以做与 std::string 相同的基本操作
  • +1 表示想要[重新]学习指针。
  • 函数的整体冗长和简单是合理的——基本上和问题一样混乱。 Seth 的 cmets 完全有效(注意 strcat 将保持输出终止 - 无论如何都不需要 strcat(newstring, "\0"),并且您可以通过调用 str[n]cat(newstring + characters_so_far, ... 来避免降低性能的 Seth 警告)。此外,当您 new 时,您必须为 NUL 终止符分配一个额外的字节,并考虑在 while 循环之外调用 strlen(from) 并保存该值。你的函数参数应该是const

标签: c++ c search pointers c-strings


【解决方案1】:

不言自明:http://ideone.com/ew5pL

这就是丑陋和笨重的样子 - 除了最后的 strlen 和 memcpy 之外,没有 C 函数。

我觉得你的看起来又漂亮又紧凑。

【讨论】:

    【解决方案2】:

    我会做的一个改进可能会同时提高优雅和效率是

    1. 分配一个整数数组,该数组将保存与给定字符串匹配的子字符串的索引。
    2. 遍历字符串并找到所有匹配的子字符串,并将每个子字符串添加到数组中,根据需要重新分配更大的数组(因为您不想使用我假设的 STL;如果可以,请使用 @ 987654321@ std::list std::deque)。
    3. 根据原始字符串的长度和找到的子字符串数量为修改后的字符串分配新内存。
    4. 同时迭代旧字符串和数组,将旧字符串中不匹配的部分复制到新字符串中。
    5. 用替换线填补您留下的孔。

    另外,我不会在函数内部动态分配内存,而是将其更改为接受调用者分配的缓冲区和最大缓冲区大小。这样,调用者可以完全负责内存的生命周期(如果他们愿意/可以使用自动内存),您不必担心计算缓冲区大小(您依赖调用者)。


    编辑:

    这是我创建的一个示例实现。如果有人发现任何错误,请告诉我,这很可能。 (如果您想自己弄清楚,您可能不想阅读此内容。)

    char* strreplace(const char* haystack, const char* needle, const char* replacement) {
        // using deque for pop_front
        std::deque<const char*> positions;
        unsigned int haystacklen    = strlen(haystack),
                     needlelen      = strlen(needle),
                     replacementlen = strlen(replacement);
    
        for (const char* cur = haystack, *pos = strstr(cur, needle); pos; cur = pos + 1, pos = strstr(cur, needle))
            positions.push_back(pos);
    
        char* newstr    = new char[haystacklen + replacementlen * positions.size() + 1],
              dst       = newstr;
        const char* src = haystack;
    
        while (src <= haystack + haystacklen)
            if (!positions.empty() && src == positions.front()) {
                strcpy(dst, replacement);
                dst += replacementlen;
                src += needlelen;
                positions.pop_front();
            } else
                *dst++ = *src++;
    
        return newstr;
    }
    

    别忘了delete[]那个函数的返回值。

    我追求效率而没有进行最大程度的优化。例如,您可以有一个在positions.empty() 为假时执行的while 循环,然后当它变为真时,只需退出循环并直接执行strcpy 其余部分,因为没有更多的替换要做,这将让您避免为每个字符不必要地调用positions.empty(),即使没有替换,或者根本没有替换。。但我认为这是一个小问题,代码传达了这一点。

    另外,我使用 std::list std::deque 删除了所有阵列管理代码,但如果你想自己做的话应该是直截了当的。

    正如 ildjarn 在 cmets 中提到的,我从 list 更改为 deque,因为我使用 size 成员,根据他的评论,它不是 O(1)(通常是 O(n)) C++11 之前的实现,所以 deque 使用它的恒定时间 size 会更有效率。

    【讨论】:

    • std::list&lt;&gt;::size() 在所有 C++11 之前的实现中不是 O(1);我建议使用std::deque&lt;&gt; 而不是std::list&lt;&gt;
    • @ildjarn 谢谢,我更新了代码并为此添加了注释。
    【解决方案3】:

    这是我制作的一个几乎只使用指针的版本(省略了错误检查等)(我还注意到它在某些情况下会失败):

    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    
    char* replaceSubstring(char *original, char *from, char *to)
    {
    // This could be improved (I was lazy and made an array twice the size)
        char* retstring = new char[std::strlen(original) * 2];
    
        int pos = 0;
        for (int i = 0; *(original + i); ++i)
        {   
            if (*(original + i) == *(from)) 
            {
                // Got a match now check if the two are the same
                bool same = true; // Assume they are the same
                for (int j = 1, k = i + 1; *(from + j) && *(original + k); ++j, ++k)
                {
                    if (*(from + j) != *(original + k))
                    {
                        same = false;
                        break;
                    }
                }
                if (same)
                {
                    // They are the same now copy to new array
                    for (int j = 0; *(to + j); ++j)
                    {
                        retstring[pos++] = *(to + j);
                    }
                    i += std::strlen(from) - 1;
                    continue;
                }
            }
            retstring[pos++] = *(original + i);
        }
        retstring[pos] = '\0';
        return retstring;
    }
    
    int main()
    {
        char orig1[] = "Replace all the places that say all";
        char* r1 = replaceSubstring(orig1, "all", "Replacement");
        std::cout << r1 << std::endl;
        delete [] r1;
    
        char orig2[] = "XXXXXX with something else XXXXXX";
        char* r2 = replaceSubstring(orig2, "XXXXXX", "hello");
        std::cout << r2 << std::endl;
        delete [] r2;
    }
    

    【讨论】:

      【解决方案4】:

      如果您简单地将新字符串的大小设置为求解后的最大可能大小,则可以去掉代码的第一部分来计算计数。

      特别是:

      int newsize = origlen + (strlen(to) - strlen(from)) * origlen/strlen(from);
      

      另外,不要多次调用 strlen(from),只需将其分配给一个变量(例如 srtlen_from)并使用它。

      【讨论】:

        猜你喜欢
        • 2010-09-10
        • 1970-01-01
        • 1970-01-01
        • 2014-02-11
        • 2014-02-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多