【问题标题】:How to write a C function that removes a portion of a string?如何编写删除部分字符串的 C 函数?
【发布时间】:2016-10-06 08:38:44
【问题描述】:

我想编写一个 C 函数,用于删除给定索引范围的字符串的一部分。

例如,如果输入字符串是“ABCDEFGHIJK”,起始索引是 2,结束索引是 5,那么输出应该是:“ABGHIJK”。

我正在尝试使用两个函数来执行此操作,一个函数获取我们要删除的子字符串:

void get_substring(char string[], char substring[], int start, int end) {
    strncpy(substring, string + start, end - start + 1);
}

然后是删除此子字符串的第二个函数:

void remove_portion(char string[], char substring[]) {
    // memmove?
}

我正在考虑的另一种可能性是直接修改原始字符串而不使用子字符串:

void remove_portion(char string[], int start, int end) {
    // if end is less then the length of the string, then
    // copy everything after string[end] into a temp string
    // Then replace string[start] with '\0' and then concatenate
    // string and temp.
    // If end is greater than the length of string then just replace
    // string[start] with '\0'.
}

这是正确的方法吗?是否有任何来自 string.h 的内置函数在这里有用?

【问题讨论】:

  • memmove 函数在这两种情况下都是正确的。使用第一个变体(有两个函数)的问题是您在remove_portion 函数中必须首先find 子字符串。还不如直接选择第二种。
  • 我查看了 memmove 的文档,据我了解,它将 n 个字符从一个字符串复制到另一个字符串,但我仍然不明白这如何帮助删除字符。当 memmove 移动字符时,它是否也将它们从原始字符串中删除?
  • 它不会“删除”字符,而是覆盖它们。它与string[i] = string[i + x] 基本相同,但以一种安全的方式进行(除非你越界)。您只需告诉memmove 获取字符串的尾部(要删除的子字符串之后的部分),包括终止符并将其移到要删除的子字符串上。

标签: c string


【解决方案1】:

我会像第二种方法一样使用 memmove:

void remove_portion(char string[], int start, int end) 
{
    if (start>=0 && end>=start && start<strlen(string) && end<strlen(string)) {  // some more sanity checking (EDIT added later)
         memmove(string+start, string+end+1, strlen(string)-(end+1)+1);  // final +1 to copy string terminator
    }
}

还请注意,在您的第一个示例中(使用 strncpy)不会将结束字符串终止符 \0 复制到子字符串。所以你需要添加

substring[end - start + 1]= '\0'; 

到那个。

【讨论】:

  • "请注意,在您的第一个示例中(使用 strncpy)不会将结束字符串终止符 \0 复制到子字符串中" 这是永远不应该使用 strncpy 的主要原因之一:它总是创建与缺少空终止相关的此类错误。
  • 是的,我在源代码中看到了 strncpy 并且知道会有麻烦。这个功能绝对是邪恶的。
  • 您还应该检查:start &lt; endend &lt; strlen
  • 该检查不一定在函数内部。听起来这是来电者的事。
【解决方案2】:

使用这个:

void remove_portion( char * str, int start, int end){
char* stro = calloc(strlen(str - (end-start+1)), sizeof(char));
strncpy(stro,str,start);
strcat(stro,&str[end]);
strcpy(str,stro);
}

同时添加条件。

【讨论】:

  • 移除第二个 strcat 并使用 strcpy(stro+start, str+end);这比在 stro 中从 0 开始寻找 ASCII 空值要快。
  • 如果在开始之前有一个空字符,你的函数可能会出现异常。
【解决方案3】:

我自己是 C 新手,但这对我有用:

void remove_portion(char str[], int start, int end) {
    assert((end > start) && (strlen(str) > end));
    char out[strlen(str) - (end - start)];
    int i, j = 0;
    for (i = 0; str[i] != '\0'; i++) {
        if ((i < start) || (i > end))
            out[j++] = str[i];
    }
    out[j] = '\0';
    strcpy(str, out);
}

【讨论】:

    【解决方案4】:

    有人向我指出,为此使用 strcpy() 是错误的,因为标准中未定义“在重叠的对象之间发生的复制”(ISO/IEC 9899:1999 7.21.2.4)。

    这是一个使用memmove() 的版本,对有效索引进行了一些检查。如果第一个大于第二个,则交换索引,如果索引超出范围,则返回 NULL:

    char * remove_portion(char *str, int start, int end)
    {
        int str_len = strlen(str);
        int temp;
    
        if (start > end) {
            temp = start;
            start = end;
            end = temp;
        }
    
        if (end > (str_len - 1) || start < 0 || end < 0) {
            str = NULL;
        } else {
            int ncopy = str_len - end; 
            memmove(&str[start], &str[end+1], ncopy);
        }
    
        return str;
    }
    

    memmove() 的标准说:“复制的发生就像首先将 s2 指向的对象中的 n 个字符复制到不与 s1 和 s2 指向的对象重叠的 n 个字符的临时数组中一样,然后将临时数组中的 n 个字符复制到 s1 指向的对象中。” (ISO/IEC 9899:1999 7.21.2.2)

    所以memmove() 专门设计用于处理诸如将数组的一部分复制到自身的情况。

    【讨论】:

    • strcpywith 重叠的内存部分是 UB。
    • 来自例如this strcpy reference:“如果字符串重叠,则行为未定义。”他们在这里做什么。
    猜你喜欢
    • 2019-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-02
    相关资源
    最近更新 更多