删除C中字符串中的所有非字母字符--可能是编译器问题

删除C中字符串中的所有非字母字符--可能是编译器问题,c,alphabet,string.h,C,Alphabet,String.h,我正在用C编写一个函数,它将接受一个字符串并删除所有不是小写字母字符的字符。到目前为止,我已经编写了以下代码: void strclean(char* str) { while (*str) { if (!(*str >= 'a' && *str <= 'z')) { strcpy(str, str + 1); str--; } str++; } } 当我让它在输入if语句的每一行之

我正在用C编写一个函数,它将接受一个字符串并删除所有不是小写字母字符的字符。到目前为止,我已经编写了以下代码:

void strclean(char* str) {
   while (*str) {
      if (!(*str >= 'a' && *str <= 'z')) {
         strcpy(str, str + 1);
         str--;
      }
      str++;
   }
}
当我让它在输入if语句的每一行之后打印时,我收到的输出如下:

hello][]woldd
hello[]woldd
hello]woldd
hellowoldd
它似乎真的很接近,但我不明白为什么它会产生这种输出!最奇怪的是,我已经把代码给了另外两个朋友,并且在他们的电脑上运行得很好。我们都运行相同版本的Linux(ubuntu 14.04.3),并且都使用gcc进行编译


我不确定代码是否存在会导致输出不一致的问题,或者是编译器问题造成了问题。与他们的机器相比,这可能与我的机器上的strcpy有关。

如果范围重叠,
strcpy
功能不能保证工作,就像您的情况一样。从
C117.24.2.3 strcpy功能/2
(我的重点):


strcpy
函数将
s2
指向的字符串(包括终止的空字符)复制到
s1
指向的数组中如果复制发生在重叠的对象之间,则行为未定义。

根据
C11 7.24.2.2 memmove函数/2
,您可以使用类似于
memmove
的功能,它可以处理重叠的范围:

memmove
函数将
n
字符从
s2
指向的对象复制到
s1
指向的对象中复制发生时,就像将
s2
指向的对象中的
n
字符首先复制到一个由
n
字符组成的临时数组中,该数组不会与
s1
s2
指向的对象重叠一样,然后将临时数组中的
n
字符复制到
s1
所指向的对象中


但是有一个更好的解决方案,即时间复杂度
O(n)
而不是
O(n2)
,同时仍然是重叠安全的:

void strclean (char* src) {
    // Run two pointers in parallel.

    char *dst = src;

    // Process every source character.

    while (*src) {
        // Only copy (and update destination pointer) if suitable.
        // Update source pointer always.

        if (islower(*src)) *dst++ = *src;
        src++;
    }

    // Finalise destination string.

    *dst = '\0';
}
您会注意到,我还使用
islower()
(来自
ctype.h
)来检测小写字母字符。由于C标准不要求字母字符具有连续的代码点(数字是唯一保证连续的数字),因此这更便于移植

也不需要单独检查
isalpha()
,因为根据
C117.4.1.2 isalpha函数/2
islower()==true
意味着
isalpha()==true

isalpha
函数测试
isupper
islower
为真的任何字符,或

从7.21.2.3开始,strcpy功能

如果复制发生在 重叠,则行为未定义

memmove
即使区域重叠也可以使用

void strclean(char* str) {
   while (*str) {
      if (!islower(*str)) { /* include ctype.h to use islower function */
         memmove(str, str + 1, strlen(str)); /* strlen(str + 1) + 1 (for terminating null character) should be strlen(str) */
      } else {
         str++;
      }
   }
}

由于指针的减法是未定义的行为,因此它指向数组之前的区域,因此我还重新构造了
str
操作。

嗯,哦!strcpy
strcpy
中的字符串不能重叠。作为一般提示,不要怀疑编译器或实现(在实践中,它们非常可靠),而是首先怀疑您自己的代码。阅读更多关于
str--str
指向顶部时,代码>使指针无效。哦,是的,这是一个很好的观点。我也没想到。谢谢大家!如果
islower
返回“true”,那么
isalpha
是否也会返回“true”呢?意思是你不需要检查
isalpha
吗?@Joachim,你是正确的,因为isalpha至少适用于
islower
isupper
的所有内容。我会调整答案的。谢谢!这个答案非常简洁,因为它几乎不会改变我的代码。我不知道memmove的事。我认为上面的人对我的代码做了很好的改进,但我喜欢你的答案,因为它详细说明了我的问题。MikeCAT,根据BLUEPIXY对问题的评论,从指向数组开头的指针中减去是未定义的行为。您可以通过删除
str--
并将
str++
放入
else
块,相对简单地消除这种情况。我认为把strcpy留在那里也是一种疏忽。我会做出这些改变,但如果你不高兴,请随时改进。虽然并没有消除O(n^2)的性质,但它仍然是一个有用的答案,除了极长的字符串(它们很少且相隔很远)。
void strclean(char* str) {
   while (*str) {
      if (!islower(*str)) { /* include ctype.h to use islower function */
         memmove(str, str + 1, strlen(str)); /* strlen(str + 1) + 1 (for terminating null character) should be strlen(str) */
      } else {
         str++;
      }
   }
}