Подтвердить что ты не робот

K & R Упражнение 1-9: вывод данных, замена нескольких заготовок на один пробел

Я работал над некоторыми книгами на C, пытаясь получить мои C-ноги (морские ноги! Получите это?!). Я только что закончил упражнение 1-9 из книги K & R, которая предназначена для "написания программы для копирования ее ввода на свой вывод, заменяя каждую строку одной или несколькими пробелами одним пробелом". У меня вопрос о том, что происходит с моим кодом, хотя -

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

После сброса самого первого символьного ввода программа работает последовательно, чтобы соответствовать требованиям упражнений.

Может ли кто-нибудь дать мне представление о той ошибке, которую я совершил в своей петле, что вызвало проблему? Любые другие советы также приветствуются.

4b9b3361

Ответ 1

В инструкции for-loop у вас появляется ошибка.

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

Здесь вы сохраняете первый символ в ch, а затем снова тестируете if (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 за то, что помогли мне изменить ответ.)

Ответ 2

Вы дважды вызываете getchar в инициализации цикла:

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

Вместо этого вы должны вызвать его один раз при инициализации (чтобы получить первый char), а затем в конце итерации (чтобы получить следующие символы):

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

Ответ 3

Проблема в том, что первая итерация вашего цикла вызывает getchar дважды - один раз при инициализации переменной ch и еще один раз при проверке ch на EOF.

Удаление ch = getchar() устранит эту проблему:

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

Обратите внимание, что вам нужно выполнить init lch с любым значением, отличным от пробела.

Ответ 4

Вы вызываете getchar() один раз до начала цикла, затем один раз за итерацию в for. Таким образом, первый символ, который вы извлекаете, отбрасывается.

Перед тем, как сравнивать его, перед циклом также нужно инициализировать lch. В зависимости от того, что вы хотите сделать, когда первый символ вашей строки является пространством:

  • Установка этого параметра в ' ' приведет к обрезанию ведущего пространства путем "предварительного сопоставления".
  • Установка его на что-либо еще будет обрабатывать ведущее пространство как обычно.

Заголовок цикла становится (во втором случае):

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

Спасибо шикару суману за хедз-ап о неинициализированном 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-loop с for-loop:

//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-construct, вы также можете преобразовать его в hacky for-loop, но почему бы вам? Время проще читать, а компилятору все равно, потому что он будет компилироваться одинаково. (Возможно)

Ответ 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

Используйте print ch из командной строки gbd, чтобы проверить интересные переменные (ch здесь) на разных этапах "запущенного" кода, пройдя через него.

Подробнее о том, как управлять gbd здесь: http://beej.us/guide/bggdb/

Ответ 8

Да, что происходит, когда вы объявляете свой оператор for, сначала вы инициализируете ch с помощью

for( ch= getchar();

Итак, в этот момент вы получаете свой первый char (T), и указатель продвигает одну позицию до следующей char (h)

то вы снова получите char с (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;
}