2015-07-23 3 views
33

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

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

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

+4

Примечание, вы используете 'lch' в теле цикла, даже если он не инициализирован до * после * первой итерации цикла. Рассмотрите возможность включения предупреждений в своем компиляторе, вероятно, обнаружит и предупредит об этой проблеме, чтобы вы могли ее исправить. – hyde

+2

Незначительный комментарий к стилю кодирования: Сохраните 'for()' для циклов, которые увеличивают некоторый указатель/указатель на фиксированную сумму. Здесь вызывается цикл while(). – chux

+1

Действительно ли это раздражает предположить, что было бы полезно указать более конкретно то, что называет «книга K & R», а также что название вопроса было бы лучше, если бы он дал понять какой-то аспект того, что касается вопроса (в прямой смысл - поскольку проблема в значительной степени не зависит от ее источника, а кто-то, кто знает C и может дать хороший ответ, но не слышал о «книге K & R», может пропустить вопрос)? – jfhc

ответ

34

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

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

Здесь вы храните первый символ в ч, а затем снова тестирование, если (ч! = EOF), снова чтение ввода символов.

Убрать ch=getchar() из инструкции по инициализации; пусть это будет во второй части.

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

Кроме того, вы должны инициализировать ваш LCH, прежде чем сделать его запустить, как LCH не будет иметь никакого значения, хранящегося в нем, прежде чем сделать сравнение в первой итерации цикла. Итак, сначала необходимо инициализировать lch=0.

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

Рассмотрим включение предупреждений в компилятором, он бы, вероятно, обнаружить и предупредить об этом вопросе, так что вы можете это исправить.

Вышеупомянутая проблема решена.

(Благодаря Blue Moon и Хайд помог мне изменить ответ.)

+4

Другая проблема заключается в том, что 'lch' не инициализируется во время первой итерации. Результат в UB. –

+0

@ BlueMoon-Спасибо, не смотрел внимательно. Вы указали свое имя в ответе на предоставленную помощь. –

+1

Две точки стиля, которые нужно добавить: 1. Использование ';' как null-statement - плохая идея, лучше использовать '{}', таким образом, это явно предназначено. 2. Еще лучше отменить if-statement, поэтому нет необходимости в 'else'. – Deduplicator

13

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

капают ch = getchar() будет решить эту проблему:

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

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

9

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

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

  • Установки в ' ' обрежет ведущее место по «предварительному согласованию» его.
  • Установка его на что-либо еще будет обрабатывать ведущее пространство в обычном режиме.

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

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

Благодаря Shekar Суман для хедз-апе о неинициализированного lch.

16

Вы называете 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 и Шекхар Суман для указывая на проблему с LCH

+0

Мне кажется, что это единственный ответ, который на самом деле делает его читабельным для не-C-программистов (без большой умственной гимнастики) –

3

Да, как там ха pening в том, что, когда вы объявляете для заявления первых инициализации ч с

for(ch= getchar(); 

Так что в этот момент вы получите ваш первый символ (T) и указатель переходит на одну позицию к следующему полукокса (ч)

тогда вы получите снова полукокс с (ch = getchar()) !=EOF;

попытки изменения for (ch= getchar(); и использовать for (ch= '' ; вместо этого.

Надеюсь, что это исправляет.

6

Изменить этот цикл

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

В противном случае в начале цикла вы читаете символ дважды.

Кроме того, на мой взгляд задание описывает еще одну задачу

«написать программу, чтобы скопировать его ввод на вывод, заменяя каждую строку из одного или нескольких пробелов на один пробел.»

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

4

Если задача состоит в том, чтобы сделать это с помощью цикла 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 для цикла, но почему ты? Время проще читать, а компилятору все равно, потому что он будет компилироваться одинаково. (Вероятно)

4

Хотя есть много правильных ответов, позвольте мне дать вам подсказку, как вы могли бы отследить это вниз самостоятельно, используя отладчик GDB (здесь):

Во-первых изменить код, чтобы выглядеть следующим образом (один оператор в единственной строке!):

... 

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

... 

Теперь скомпилировать его с помощью символов (-g для ССАГПЗ), а затем запустить код с помощью отладчика:

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 здесь) на разных этапах «запуска» кода при прохождении через него.

Более подробная информация о том, как рулить ГПБ здесь: http://beej.us/guide/bggdb/

1

Есть три части к for заявлению: инициализация, условие и приращение. Эти части разделены двумя точками с запятой.

Это очень запутанно, когда состояние части инструкции for имеет побочные эффекты. Побочные эффекты относятся к приращению части:

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

И, как и другие показали, lch должен быть инициализирован, так:

INT LCH = 'а';

И, наконец, хотя это не влияет на корректность программы, я бы обратить if тест:

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

Это работает для меня

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