【问题标题】:K&R Exercise 1-9: output the input, replacing multiple blanks by a single blankK&R 练习 1-9:输出输入,用一个空格替换多个空格
【发布时间】:2015-10-14 09:13:29
【问题描述】:

我一直在阅读一些关于 C 的书籍,试图让我的 C 腿(海腿!明白吗?!)。我刚刚完成了 K&R 书中的练习 1-9,作为参考,该练习是“编写一个程序将其输入复制到其输出,将一个或多个空格的每个字符串替换为一个空格”。不过,我对我的代码发生了什么有疑问--

#include <stdio.h>

//Copy input to output. Replace each string of multiple spaces with one single space

int main(int argc, char *argv[]){

    int ch, lch;      // Variables to hold the current and last characters, respectively


    /* This loop should 'put' the current char, then store the current char in lc,
     * loop back, 'get' a new char and check if current and previous chars are both spaces.
     * If both are spaces, do nothing. Otherwise, 'put' the current char
     */

    for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){
            if(ch == ' ' && lch == ' ')
                    ;
            else putchar(ch);
    }

    return 0;
}

这主要是有效的,除了第一个字符输入。例如,如果第一行输入是

"This        is   a test"

我的代码输出

"his is a test". 

删除第一个字符输入后,该程序始终如一地工作以满足练习的要求。

有人可以告诉我我在循环中犯的导致问题的错误吗?也欢迎任何其他建议。

【问题讨论】:

  • 注意,您在循环体中使用了lch 变量,即使它在第一次循环迭代之后 之前未初始化。考虑在你的编译器中启用警告,它可能会检测到这个问题并发出警告,所以你可以修复它。
  • 次要编码风格注释:保存for() 用于将某些索引/指针增加固定量的循环。此处调用了while() 循环。
  • 建议最好更明确地提及“K&R 书”所指的内容是否真的很烦人,而且问题标题如果能明确说明某些方面会更好问题指的是(在直接意义上 - 因为问题在很大程度上独立于其来源,并且知道 C 并且可以提供一个很好的答案但没有听说过“K&R 书”的人可能会跳过这个问题)?
  • @chux - 这不是for 的用途。它用于具有初始化部分、条件部分和后迭代部分的循环。立即想到使用迭代器。据我所知,他使用for 没有任何问题。
  • @jfhc 建议去哪里找一个没听过传统标准书的C程序员?如果他们确实存在,他们就不可能在正确的历史背景下判断这个问题。

标签: c kernighan-and-ritchie


【解决方案1】:

在 for 循环语句中,您遇到了错误。

for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){...}

在这里,您将第一个字符存储在 ch 中,然后通过再次读取字符输入再次测试 (ch!=EOF)。

从初始化语句中删除ch=getchar();让它在第二部分。

for(;(ch = getchar()) != EOF; lch = ch){...}

此外,您必须在运行之前初始化 lch,因为在循环的第一次迭代中进行比较之前,lch 不会存储任何值。所以,先初始化lch=0

for(lch = 0; (ch = getchar()) != EOF; lch = ch){...}

考虑在您的编译器中启用警告,它可能会检测到此问题并发出警告,因此您可以修复它。

以上将解决您的问题。

(感谢 Blue Moon 和 hyde 帮我修改答案。)

【讨论】:

  • 另一个问题是lch在第一次迭代时没有被初始化。导致 UB。
  • @BlueMoon-谢谢,没仔细看。在提供帮助的答案中提到了您的名字。
  • 要添加的两个样式点: 1. 使用; 作为空语句是一个坏主意,最好使用{},这样显然是有意的。 2. 最好反转 if 语句,这样就不需要 else 部分。
  • @Deduplicator-感谢您的指出。顺便说一句,您可以自己进行必要的编辑,也可以尝试将其发布为新答案。无论如何,再次感谢您提出改进建议...
  • for(;( = ...);...) ... 啊,是的,我知道所有这些 C 书籍的原因在于它的语法是如何最可怕的这个星球上被滥用的东西。
【解决方案2】:

你在循环初始化中调用了两次getchar:

 for(ch = getchar(); (ch = getchar()) != EOF; lch = ch)

相反,您应该在初始化时调用一次(以获取第一个字符),然后在迭代结束时调用它(以获取下一个字符):

int ch, lch = 0; // avoid using uninitialized variable

for(ch = getchar(); ch != EOF; lch = ch)
{
        if(ch == ' ' && lch == ' ')
                ;
        else putchar(ch);

        ch = getchar();
} 

UPD:感谢 Blue Moon 和 shekhar suman 指出 lch 的问题

【讨论】:

  • 在我看来,这似乎是唯一能让非 C 程序员读懂的答案(无需大量脑力操作)
【解决方案3】:

问题是循环的第一次迭代调用了两次getchar - 一次是在初始化ch 变量时,另一次是在检查chEOF 时。

删除ch = getchar() 将解决此问题:

for( lch = '?' ; (ch = getchar()) != EOF; lch = ch) {
    ...
}

请注意,您需要使用空格以外的任何值初始化lch

【讨论】:

    【解决方案4】:

    您在循环开始前调用getchar() 一次,然后在for 条件下每次迭代调用一次。因此,您检索的第一个字符将被丢弃。

    您还需要在循环之前初始化lch,然后再进行比较。当字符串的第一个字符是空格时,取决于您想要做什么:

    • 将其设置为 ' ' 将通过“预匹配”来修剪前导空格。
    • 将其设置为其他任何值都会正常处理前导空格。

    您的循环标题变为(在第二种情况下):

     for(lch = 'a' /*arbitrary*/; (ch = getchar()) != EOF; lch = ch)
    

    感谢 shekar suman 关于未初始化的lch 的提醒。

    【讨论】:

      【解决方案5】:

      改变这个循环

      for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){
              if(ch == ' ' && lch == ' ')
                      ;
              else putchar(ch);
      }
      

      以下方式

      for( lch = EOF; ( ch = getchar() ) != EOF; lch = ch )
      {
              if ( ch != ' ' || lch != ' ' ) putchar( ch );
      }
      

      否则在循环开始时你会读取一个字符两次。

      在我看来,该作业还描述了另一项任务

      "编写一个程序将其输入复制到其输出,替换每个 一个或多个空格由一个空格组成的字符串。”

      您应该用一个空格替换每一整行空格。:) 上面显示的循环不执行此任务。

      【讨论】:

        【解决方案6】:

        除非任务是使用 for 循环来完成,否则如果您尝试获得更清晰的代码,则更好地学习该语言。只需告诉自己代码的作用,例如将等效的 while 循环与 for 循环进行比较:

        //initialize lch to prevent undefined behaviour
        //if the first character is a space, it will be printed
        lch = 'A';
        
        // as long as you can read characters
        while((ch = getchar()) != EOF) {
        
            // if either the current character or the previous one is not a space
            if(ch!=' ' || lch!=' ') { 
        
                //print it
                putchar(ch);
            }
        
            // remember the current for the next round
            lch = ch;
        }
        

        一旦您了解了 while 构造,您也可以将其转换为 hacky for 循环,但您为什么要这样做呢? while 更容易阅读并且编译器不在乎,因为它会以相同的方式编译两者。 (大概)

        【讨论】:

          【解决方案7】:

          虽然有很多正确答案,但让我提示您如何使用调试器(此处为 gdb)自行跟踪此问题:

          首先将代码更改为如下所示(每行仅一个语句!):

          ...
          
          for(ch = getchar(); 
             (ch = getchar()) != EOF; 
             lch = ch){
          
          ...
          

          现在使用符号编译它(-g 用于 gcc),然后使用调试器运行代码:

           gdb ./a.out
          

          main()处设置断点:

          (gdb) break main
          

          启动程序:

          (gdb) run
          

          看到它停在main():

          Breakpoint 1, main (argc=1, argv=0x7fffffffe448) at main.c:15
          15      for(ch = getchar(); 
          (gdb) 
          

          单步执行代码:

          (gdb) step
          

          使用 gbd 命令行中的print ch 在“运行”代码的各个阶段检查有趣的变量(此处为ch),同时单步执行。

          有关如何在此处引导 gbd 的更多详细信息:http://beej.us/guide/bggdb/

          【讨论】:

            【解决方案8】:

            是的,有趣的是,当你声明你的 for 语句时,首先你用

            初始化 ch
            for( ch= getchar();
            

            所以此时你得到了你的第一个字符(T)并且指针前进一个位置到下一个字符(h)

            然后你再次得到(ch = getchar()) !=EOF;的字符

            尝试更改for (ch= getchar(); 并改用for (ch= '' ;

            希望能修复它。

            【讨论】:

              【解决方案9】:

              for 语句包含三个部分:初始化、条件和增量。这些部分用两个分号隔开。

              for 语句的条件部分有副作用时,这非常令人困惑。副作用属于增量部分:

              for (ch = getchar(); ch != EOF; lch = ch, ch = getchar())
              

              而且,正如其他人所指出的,lch 必须被初始化,所以:

              int lch = 'a';

              最后,虽然这不会影响程序的正确性,但我会反转 if 测试:

              if (ch != ' ' || lch != ' ')
                  putchar(ch);
              

              【讨论】:

                【解决方案10】:

                这对我有用

                #include <stdio.h>
                int main(int arg, char *argv[]){
                char c = 0;
                long blank = 0;
                long tab   = 0;
                while((c=getchar())!= EOF){
                 if(c == ' '){
                    ++blank;  
                 }
                
                 if(c != ' '){
                     if(blank>1){
                       printf("%c", ' ');
                       blank = 0;
                       printf("%c", c);            
                        }
                 else{
                        printf("%c", c);                            
                     }
                  }    
                
                 } //end of while
                return 0;
                }
                

                【讨论】:

                  【解决方案11】:

                  @elessar 有一个小的变化。第 12 行必须从 (blank>1) 更改为 (blank>=1),因为前一行不会打印单个空格。

                  #include <stdio.h>
                  int main(int arg, char *argv[]){
                  char c = 0;
                  long blank = 0;
                  long tab   = 0;
                  while((c=getchar())!= EOF){
                   if(c == ' '){
                      ++blank;  
                   }
                  
                   if(c != ' '){
                       if(blank>=1){
                         printf("%c", ' ');
                         blank = 0;
                         printf("%c", c);            
                          }
                   else{
                          printf("%c", c);                            
                       }
                    }    
                  
                   } //end of while
                  return 0;
                  }
                  

                  【讨论】:

                    【解决方案12】:

                    另一个中止:

                    #include <stdio.h>
                    
                    int main()
                    {
                        int charac;
                    
                        // Variable declared for verifying consecutive whitespaces 
                        bool blank = false;
                    
                        // As long as you did not input EOF (Ctrl + Z on Windows, Ctrl + D on linux, macOS)
                        while ((charac = getchar()) != EOF){
                    
                            // Current char is whitespace, the one before was also whitespace => go to next iteration    
                            if((charac == ' ') && (blank == true)){
                                continue;
                            }
                            // If current char is whitespace, keep this in mind(blank = true) and output the whitespace
                            else if(charac == ' ')
                            {
                                blank = true;
                                putchar(charac);
                                continue;
                            }
                    
                            // If current character is not whitespace, output it and reset the blank boolean
                            putchar(charac);
                            blank = false;
                        }
                    
                        return 0;
                    }
                    

                    【讨论】:

                      【解决方案13】:
                      #include <stdio.h>
                      #include <ctype.h>
                      
                      /* replace each string of one or more blanks by a single blank */
                      int main() {
                          int c, s1;
                      
                          s1 = 0;
                          while ((c = getchar()) != EOF) {
                              if (isspace(c)) {
                                  ++s1;
                              } else {
                                  s1 = 0;
                              }
                              if (s1 > 1) {
                                  continue;
                              }
                              putchar(c);
                          }
                      
                          return 0;
                      }
                      

                      【讨论】:

                        【解决方案14】:

                        我也在阅读这本书学习 C 并且我设法提出了这种方法,我希望得到一些反馈来改进。 为了不浪费内存空间,我尽量不要声明太多变量。 我最终定义了毯子空间以便稍后打印它,因为我想将多个制表符和空格视为一个案例。

                        #include <stdio.h>
                        
                        /* space char was defined so I can treat ' ' and '\t' on the same case */
                        #define BLANK ' '
                        
                        
                        int main(){
                        
                            int c;
                        
                            while((c = getchar()) != EOF){
                        
                                /* if char is either ' ' or '\t' */
                                if((c == ' ') || (c == '\t')){
                        
                                    /* print a blank */
                                    putchar(BLANK);
                                    /* read next char */
                                    c = getchar();
                        
                                    /* while after the ' ' or '\t' the char is again ' ' or '\t' ... */
                                    /* I'm not going to bother with it and I'm going to read the next char */
                                    while((c == ' ') || (c == '\t')){
                                        c=getchar();
                                    }
                        
                                    /* print the char */
                                    putchar(c);
                                }
                        
                                /* another char */
                                else {
                                    putchar(c);
                                }
                            }
                        
                            
                        }
                        

                        【讨论】:

                        • 您是否要求进行代码审查?如果是这样,请在Code Review提问。
                        猜你喜欢
                        • 2017-12-21
                        • 2020-06-25
                        • 1970-01-01
                        • 1970-01-01
                        • 2010-11-19
                        • 1970-01-01
                        • 2013-01-16
                        • 1970-01-01
                        • 2013-05-30
                        相关资源
                        最近更新 更多