【问题标题】:Remove spaces from a string in C从C中的字符串中删除空格
【发布时间】:2010-12-16 03:02:28
【问题描述】:

在 C 中从字符串中删除空格的最简单和最有效的方法是什么?

【问题讨论】:

  • 最简单和最高效的不一定一样
  • @JimFell 该问题的标题(曾经)非常具有误导性:它只是在开头删除空格

标签: c string spaces


【解决方案1】:

正如我们从发布的答案中看到的那样,这令人惊讶地不是一项微不足道的任务。当面对这样的任务时,许多程序员似乎选择抛开常识,以产生他们可能想出的最晦涩难懂的 sn-p。

需要考虑的事项:

  • 您需要制作字符串的副本,去掉空格。修改传递的字符串是不好的做法,它可能是字符串文字。此外,有时将字符串视为immutable objects 也有好处。
  • 您不能假设源字符串不为空。它可能只包含一个空终止字符。
  • 调用函数时,目标缓冲区可以包含任何未初始化的垃圾。检查它是否为空终止没有任何意义。
  • 源代码文档应说明目标缓冲区需要足够大以包含修剪后的字符串。最简单的方法是使其与未修剪的字符串一样大。
  • 函数完成后,目标缓冲区需要保存一个以空字符结尾且没有空格的字符串。
  • 考虑是要删除所有空格字符还是只删除空格' '
  • C 编程并不是一场关于谁能在一条线路上挤入尽可能多的运算符的竞争。恰恰相反,一个好的 C 程序包含可读的代码(始终是最重要的品质)而不牺牲程序效率(有些重要)。
  • 因此,通过将其作为复制代码的一部分来隐藏目标字符串的空终止插入,您不会获得任何奖励积分。相反,应明确插入空终止插入,以表明您并非偶然成功地做到了这一点。

我会做什么:

void remove_spaces (char* restrict str_trimmed, const char* restrict str_untrimmed)
{
  while (*str_untrimmed != '\0')
  {
    if(!isspace(*str_untrimmed))
    {
      *str_trimmed = *str_untrimmed;
      str_trimmed++;
    }
    str_untrimmed++;
  }
  *str_trimmed = '\0';
}

在此代码中,源字符串“str_untrimmed”保持不变,这是通过使用适当的 const 正确性来保证的。如果源字符串只包含一个空终止符,它不会崩溃。它总是 null 终止目标字符串。

内存分配留给调用者。该算法应该只专注于完成其预期的工作。它会删除所有空格。

代码中没有微妙的技巧。它不会试图在一条线上挤入尽可能多的运营商。这将使IOCCC 成为一个非常糟糕的候选人。然而,它会产生与更晦涩的单行版本几乎相同的机器代码。

在复制某些内容时,您可以通过将两个指针声明为restrict 来进行一些优化,这是程序员和编译器之间的合约,程序员保证目标和源不是同一个地址。这允许更有效的优化,因为编译器可以直接从源复制到目标,而无需中间的临时内存。

【讨论】:

  • 为什么要使用restrict 关键字?您没有理由不能传递与源和目标相同的指针,并且您的代码支持这一点。
  • @chqrlie 当然可以删除它,但代价是通用用例中的代码较慢。我认为我没有对这段代码进行基准测试,但我怀疑它不应该有太大的不同。
  • 这是我见过的最明智的答案。它清晰,简洁,初学者可以很好地理解!谢谢。
  • 我将 str_untrimmed 替换为 scatteredstr_trimmed 替换为 condensed
  • @Wolf 对你有好处。现在请停止通过少量多余的编辑来破坏人们的帖子,或者根据您的个人喜好更改编码风格。显然,您的代表太高了,无法审核编辑,否则您将收到编辑禁令。
【解决方案2】:

最简单和最有效的通常不会一起使用……

以下是就地移除的可能解决方案:

void remove_spaces(char* s) {
    char* d = s;
    do {
        while (*d == ' ') {
            ++d;
        }
    } while (*s++ = *d++);
}

【讨论】:

  • 如果输入源是从字符串字面量初始化的会怎样?
  • @Suppressingfire:假设你的意思是RemoveSpaces("blah");,而不是char a[] = "blah"; RemoveSpaces(a);,那么未定义的行为。但这不是这段代码的错。不建议将只读字符串传递给已记录的函数以修改传递给它的字符串(例如,通过删除空格);-)
  • 我认为你应该这样做 *i = '\0';最后。
  • *i = 0*i = '\0' 是一样的:)
  • 如何...这是如何工作的?我是 C 和指针的新手,因此非常感谢您了解正在发生的事情
【解决方案3】:

虽然这不像其他答案那么简洁,但对于 C 语言新手来说,这很容易理解,改编自 Calculix 源代码。

char* remove_spaces(char * buff, int len)
{
    int i=-1,k=0;
    while(1){
        i++;
        if((buff[i]=='\0')||(buff[i]=='\n')||(buff[i]=='\r')||(i==len)) break;
        if((buff[i]==' ')||(buff[i]=='\t')) continue;
        buff[k]=buff[i];
        k++;
    }
    buff[k]='\0';
    return buff;
}

【讨论】:

    【解决方案4】:

    这是在微控制器中实现的,它可以工作,它应该避免所有问题,这不是一个聪明的方法,但它会工作:)

    void REMOVE_SYMBOL(char* string, uint8_t symbol)
    {
      uint32_t size = LENGHT(string); // simple string length function, made my own, since original does not work with string of size 1
      uint32_t i = 0;
      uint32_t k = 0;
      uint32_t loop_protection = size*size; // never goes into loop that is unbrakable
      while(i<size)
      {
        if(string[i]==symbol)
        {
          k = i;
          while(k<size)
          {
            string[k]=string[k+1];
            k++;
          }
        }
        if(string[i]!=symbol)
        {
          i++;
        }
        loop_protection--;
        if(loop_protection==0)
        {
          i = size;
          break;
        }
      }
    }
    

    【讨论】:

    • 这是在微控制器中实现的,它可以工作:恐怕不行。这个解决方案效率很低(二次时间复杂度)并且不正确:它没有设置空终止符,而是复制了字符串的最后一个字节。添加了loop_protection kludge 以尝试修复仅包含空格的字符串上的无限循环。它不能解决问题,甚至可能在长字符串上适得其反。从其他答案中研究更简单的解决方案。
    【解决方案5】:
    /* Function to remove all spaces from a given string.
       https://www.geeksforgeeks.org/remove-spaces-from-a-given-string/
    */
    void remove_spaces(char *str)
    {
        int count = 0;
        for (int i = 0; str[i]; i++)
            if (str[i] != ' ')
                str[count++] = str[i];
        str[count] = '\0';
    }
    

    【讨论】:

    • counti 的类型更改为size_t,您将获得一个干净而强大的解决方案。
    【解决方案6】:

    这是我能想到的最简单的事情。请注意,此程序使用第二个命令行参数 (argv[1]) 作为删除空格的行。

    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    /*The function itself with debug printing to help you trace through it.*/
    
    char* trim(const char* str)
    {
        char* res = malloc(sizeof(str) + 1);
        char* copy = malloc(sizeof(str) + 1);
        copy = strncpy(copy, str, strlen(str) + 1);
        int index = 0;
    
        for (int i = 0; i < strlen(copy) + 1; i++) {
            if (copy[i] != ' ')
            {
                res[index] = copy[i];
                index++;
            }
            printf("End of iteration %d\n", i);
            printf("Here is the initial line: %s\n", copy);
            printf("Here is the resulting line: %s\n", res);
            printf("\n");
        }
        return res;
    }
    
    int main(int argc, char* argv[])
    {
        //trim function test
    
        const char* line = argv[1];
        printf("Here is the line: %s\n", line);
    
        char* res = malloc(sizeof(line) + 1);
        res = trim(line);
    
        printf("\nAnd here is the formatted line: %s\n", res);
    
        return 0;
    }
    

    【讨论】:

      【解决方案7】:

      我遇到了这个问题的一种变体,您需要将多个空格减少到一个空格“代表”这些空格。

      这是我的解决方案:

      char str[] = "Put Your string Here.....";
      
      int copyFrom = 0, copyTo = 0;
      
      printf("Start String %s\n", str);
      
      while (str[copyTo] != 0) {
          if (str[copyFrom] == ' ') {
              str[copyTo] = str[copyFrom];
              copyFrom++;
              copyTo++;
      
              while ((str[copyFrom] == ' ') && (str[copyFrom] !='\0')) {
                  copyFrom++;
              }
          }
      
          str[copyTo] = str[copyFrom];
      
          if (str[copyTo] != '\0') {
              copyFrom++;
              copyTo++;
          }
      }
      
      printf("Final String %s\n", str);
      

      希望对你有帮助:-)

      【讨论】:

        【解决方案8】:

        这是我能想到的最简单的方法(已测试)并且有效!!

        char message[50];
        fgets(message, 50, stdin);
        for( i = 0, j = 0; i < strlen(message); i++){
                message[i-j] = message[i];
                if(message[i] == ' ')
                    j++;
        }
        message[i] = '\0';
        

        【讨论】:

          【解决方案9】:

          取自 zString 库的代码

          /* search for character 's' */
          int zstring_search_chr(char *token,char s){
                  if (!token || s=='\0')
                  return 0;
          
              for (;*token; token++)
                  if (*token == s)
                      return 1;
          
              return 0;
          }
          
          char *zstring_remove_chr(char *str,const char *bad) {
              char *src = str , *dst = str;
          
              /* validate input */
              if (!(str && bad))
                  return NULL;
          
              while(*src)
                  if(zstring_search_chr(bad,*src))
                      src++;
                  else
                      *dst++ = *src++;  /* assign first, then incement */
          
              *dst='\0';
              return str;
          }
          

          代码示例

            Exmaple Usage
                char s[]="this is a trial string to test the function.";
                char *d=" .";
                printf("%s\n",zstring_remove_chr(s,d));
          
            Example Output
                thisisatrialstringtotestthefunction
          

          看看 zString 代码,你会发现它很有用 https://github.com/fnoyanisi/zString

          【讨论】:

          • 为什么要一遍遍地检查传递的参数是否为NULL?多余的 NULL 检查使其成为所有发布版本中效率最低的版本。为什么不使用标准的strpbrk 而不是您的自制版本? const 的正确性在哪里?
          • 对,第一个if 语句可以被删除,并且可以在for 循环的逻辑测试部分内完成检查,谢谢,我会调查一下.... .>> 为什么不使用标准的strpbrk 而不是您的自制版本?只是为了好玩而写了这段代码(整个 zString 的东西),并试图根本不使用标准函数。所以,说它是一个有趣的项目并没有什么坏处,但这当然不应该阻止任何人贡献代码
          • 与评论中所说的不同,zstring_search_chr 不返回 chr 的索引,它的 char* 参数应该是 const 限定的。 zstring_remove_chr这个函数效率很低。
          • @chqrlie,更新了 cmets 和 zstring_remove_chr() 的代码。我希望看到更高效的zstring_remove_chr() 版本或您的一些建议。谢谢
          • 您可以在codereview.stackexchange.com 上发布代码。如果你这样做,我会写一篇评论。确实有一些改进的想法。
          【解决方案10】:

          这是一个非常紧凑但完全正确的版本:

          do while(isspace(*s)) s++; while(*d++ = *s++);
          

          这里,只是为了我的消遣,是不完全正确的代码版本,并让评论者感到不安。

          如果你可以冒险一些未定义的行为,并且永远不会有空字符串,你可以摆脱身体:

          while(*(d+=!isspace(*s++)) = *s);
          

          哎呀,如果你说的空格是指空格字符:

          while(*(d+=*s++!=' ')=*s);
          

          不要在生产中使用它:)

          【讨论】:

          • 有意思,前两个函数在我的机器上。但我猜所有这些都是未定义的,因为在一个语句中使用 s++ 和 *s 会导致未定义的行为?
          • 确保在取消引用时不要超出字符串的末尾。
          • @Andomar:第一个是完全安全的。最后两个确实是粗略的(在 GCC4.2 中测试)。
          • 称它为“声音”可能有点太客气了。所有 3 个版本都完全不可读,没有性能提升。 Apple agrees 大括号是不必要的。我的意思是,与编写大括号所涉及的纯粹痛苦相比,数百万美元的损失和世界上所有的程序员都在嘲笑你,这算什么?
          • 当您可以使用逗号运算符和 for 循环解决风险时,为什么有必要冒未定义行为的风险?
          【解决方案11】:

          从字符串中删除空格的最简单最有效的方法是简单地从字符串文字中删除空格。例如,使用您的编辑器“查找并替换”"hello world""helloworld",然后就可以了!

          好的,我知道你不是这个意思。并非所有字符串都来自字符串文字,对吗?假设您希望从中删除空格的字符串不是来自字符串文字,我们需要考虑您的 string 的来源和目的地......我们需要考虑您的整个算法,您尝试解决的实际问题,以便提出最简单和最佳的方法。

          也许你的字符串来自一个文件(例如stdin)并且一定会被写入另一个文件(例如stdout)。如果是这样的话,我会质疑为什么它首先需要变成一个字符串。把它当作一个字符流来对待,当你遇到它们时丢弃空格......

          #include <stdio.h>
          
          int main(void) {
              for (;;) {
                  int c = getchar();
                  if (c == EOF) { break;    }
                  if (c == ' ') { continue; }
                  putchar(c);
              }
          }
          

          通过消除存储字符串的需要,不仅整个程序变得更加更加短,而且理论上也更加高效。

          【讨论】:

          • 这个问题根本没有提到字符串文字。但是您必须假设可以将字符串文字传递给函数。如果输入来自其他地方怎么办,例如您正在编写某种文本解析器。
          • 当质疑程序的效率时,我们必须考虑整个程序,而不仅仅是一小部分。这就是我想要表达的意思,我认为你错过了这一点,@Lundin。
          【解决方案12】:

          在 C 中,您可以就地替换一些字符串,例如 strdup() 返回的字符串:

          char *str = strdup(" a b c ");
          
          char *write = str, *read = str;
          do {
             if (*read != ' ')
                 *write++ = *read;
          } while (*read++);
          
          printf("%s\n", str);
          

          其他字符串是只读的,例如那些在代码中声明的字符串。您必须将它们复制到新分配的内存区域并通过跳过空格来填充副本:

          char *oldstr = " a b c ";
          
          char *newstr = malloc(strlen(oldstr)+1);
          char *np = newstr, *op = oldstr;
          do {
             if (*op != ' ')
                 *np++ = *op;
          } while (*op++);
          
          printf("%s\n", newstr);
          

          你可以看到为什么人们发明了其他语言;)

          【讨论】:

          • 您的第二个示例忘记正确终止目标字符串。
          • ..您的第一个示例根本没有做正确的事情(例如,如果字符串以两个非空格字符开头)。
          • @caf:while 循环将针对 \0 终止符运行,因为它是 while (*(op++)) 而不是 while (*(++op))
          • 是的,这意味着它仍然有问题,因为它会跳过第一个字符,无论它是否是空格。
          • 您可以在此处共享循环:void copyExceptSpace(char*, const char*);void removeSpace(char *s) { copyExceptSpace(s,s); }char *dupExceptSpace(const char *s) { char *n = malloc(strlen(s)+1); if (n) copyExceptSpace(n,s); return n; }。或者类似的东西。
          【解决方案13】:
          #include<stdio.h>
          #include<string.h>
          main()
          {
            int i=0,n;
            int j=0;
            char str[]="        Nar ayan singh              ";
            char *ptr,*ptr1;
            printf("sizeof str:%ld\n",strlen(str));
            while(str[i]==' ')
             {
               memcpy (str,str+1,strlen(str)+1);
             }
            printf("sizeof str:%ld\n",strlen(str));
            n=strlen(str);
            while(str[n]==' ' || str[n]=='\0')
              n--;
            str[n+1]='\0';
            printf("str:%s ",str);
            printf("sizeof str:%ld\n",strlen(str));
          }
          

          【讨论】:

          • strlen 返回size_t。所以使用%zu,而不是%ld。并使用int main()return 0;
          • 另外,memcpy 不适合复制内存的重叠区域。请改用memmove
          【解决方案14】:

          如果你仍然感兴趣,这个函数会从字符串的开头删除空格,我只是让它在我的代码中工作:

          void removeSpaces(char *str1)  
          {
              char *str2; 
              str2=str1;  
              while (*str2==' ') str2++;  
              if (str2!=str1) memmove(str1,str2,strlen(str2)+1);  
          }
          

          【讨论】:

            【解决方案15】:
            #include <ctype>
            
            char * remove_spaces(char * source, char * target)
            {
                 while(*source++ && *target)
                 {
                    if (!isspace(*source)) 
                         *target++ = *source;
                 }
                 return target;
            }
            

            注释;

            • 这不处理 Unicode。

            【讨论】:

            • 这不会跳过第一个字符吗?
            • 您应该将传递给isspace 的值转换为unsigned char,因为该函数被定义为接受unsigned char 或EOF 范围内的值。
            • 它仍然会删除第一个字符,如果在其第一个元素中使用target contating '\0' 调用它会失败(我不明白检查其内容的目的是什么)。将while(*source++ &amp;&amp; *target) {...} 更改为do {...} while(*source++); 似乎工作正常。
            • 你的意思是ctype.h
            • 1) 无法删除 source 中的初始空格。 2) 如果source == "",切勿将终止空字符附加到target。 3) 取决于target[0] 中的值。
            【解决方案16】:

            我假设 C 字符串在一个固定的内存中,所以如果你替换空格,你必须移动所有字符。

            最简单的似乎是创建新字符串并迭代原始字符串并仅复制非空格字符。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-11-20
              相关资源
              最近更新 更多