K&R 练习 1-9:输出输入,用一个空格替换多个空格

K&R Exercise 1-9: output the input, replacing multiple blanks by a single blank

我一直在研究一些关于 C 的书籍,试图让我的 C 腿(海腿!明白了吗?!)。我刚刚完成了 K&R 书中的练习 1-9,供参考 "write a program to copy its input to its output, replacing each string of one or more blanks by a single blank." 不过我对我的代码发生了什么有疑问--

#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". 

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

谁能告诉我我在循环中犯的错误导致了这个问题?也欢迎任何其他建议。

问题是循环的第一次迭代调用 getchar 两次 - 一次是在初始化 ch 变量时,另一次是在检查 ch 与 [=14= 时].

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

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

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

在 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){...}

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

以上内容可以解决您的问题。

(感谢蓝月亮和hyde帮我修改答案。)

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

您还需要在循环之前初始化 lch,然后再进行比较。当字符串的第一个字符是 space :

时,取决于你想做什么
  • 将其设置为 ' ' 将 trim 领先 space "pre-matching"。
  • 将其设置为任何其他值将正常处理前导 space。

你的循环头变成(在第二种情况下):

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

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

你在循环初始化中调用了两次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 的问题

是的,发生了什么事,当你声明你的 for 语句时,首先你用

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

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

然后你又得到了 (ch = getchar()) !=EOF;

的字符

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

希望修复它。

改变这个循环

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 );
}

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

我还认为作业描述了另一项任务

"write a program to copy its input to its output, replacing each string of one or more blanks by a single blank."

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

除非任务是使用 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 更容易阅读,编译器不关心,因为它会以相同的方式编译。 (大概)

虽然有很多正确的答案,但让我给你一个提示,你可以如何使用调试器(这里是 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 在 "running" 代码的各个阶段检查有趣的变量(此处为 ch),同时单步执行它。

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

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

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

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

并且,正如其他人指出的那样,lch 必须进行初始化,因此:

int lch = 'a';

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

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

这对我有用

#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;
}

@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;
}

另一个流产:

#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;
}
#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;
}

我也在读这本书学习 C,我设法想出了这种方法,我希望得到一些反馈以改进。 为了不浪费内存,我尽量不声明太多变量space。 我最终定义了毯子 space 以便稍后打印它,因为我想将多个选项卡和 space 视为一个案例。

#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);
        }
    }

    
}