Странный вывод putchar(), почему это происходит?

Если я наберу слова «Hello World» в стандартный поток ввода, эта программа выведет странные символы коробки вместо ожидаемого «Hello World» обратно в стандартный вывод.

#include <stdio.h>

int main(void)
{
    // print out all characters from the stream until '/n' character is found
    int ch;
    while (ch = getchar() != '\n')
    {
        putchar(ch);
    }
    putchar('\n');
}

Я знаю, как решить проблему. Но почему эта строка кода неверна?

while (ch = getchar() != '\n')

person zxgear    schedule 27.03.2015    source источник
comment
Кстати, вы можете найти список приоритетов операторов здесь: en.cppreference.com/w /c/language/operator_precedence. (Чувствовал себя немного глупо, публикуя ответ с только что пришедшим миллионом. :D)   -  person Ulfalizer    schedule 27.03.2015
comment
Интересно, я не думал, что приоритет операций в C будет отличаться от C# и Java.   -  person zxgear    schedule 27.03.2015
comment
Если вы думаете об этом конкретном случае, то похоже, что в Java и C# все одинаково. Было бы плохо, если бы x_eq_y = x == y интерпретировалось как (x_eq_y = x) == y, как глупый пример. Тем не менее, у C есть некоторые шаткие приоритеты (которые были признаны авторами ошибкой, но все еще эмулируются другими языками для совместимости). Например, x == y << z совпадает с x == (y << z), как и следовало ожидать, а x == y & z совпадает с (x == y) & z.   -  person Ulfalizer    schedule 27.03.2015
comment
Попробуйте это в Java: (fooBoolean = fooInteger == barInteger). Оператор == имеет более высокий приоритет, чем оператор =.   -  person zxgear    schedule 27.03.2015
comment
@JohnH В любом случае приоритет операторов в Java и C # отличается от C. Не наоборот (потому что C старше). Кроме того, чтобы никогда больше не ошибаться в приоритете в C, вы должны помнить, что умножение и деление имеют более высокий приоритет, чем сложение и вычитание. Все остальное требует скобок.   -  person gon1332    schedule 27.03.2015
comment
ИМО, именно поэтому было создано единое заявление -› единая ответственность -› принцип единого действия... чтобы люди не получали WTF operator precedence каждые 5 минут. Либо разделяйте составные операторы, либо используйте явные круглые скобки. В любом другом случае вы напрашиваетесь на неприятности или навязываете их другим людям.   -  person    schedule 27.03.2015


Ответы (4)


(ch = getchar() != '\n') следует переписать как

((ch = getchar()) != '\n')

Потому что != связывает сильнее, чем = в таблице приоритетов операторов C. Операторы не упорядочены слева направо (направление чтения английского языка), как можно было бы ожидать. Например, результатом 2 + 3 * 5 будет 17, а не 25. Это связано с тем, что * будет выполняться до выполнения +, поскольку оператор * имеет более высокий приоритет, чем оператор +.

Итак, когда вы пишете что-то вроде

ch = getchar() != '\n'

Вы ожидаете, что это будет эквивалентно: (ch = getchar()) != '\n'

Но на самом деле это эквивалентно: ch = (getchar() != '\n')

Поскольку результатом != является либо true, либо false, вы видите на экране символ \001. Я полагаю, что \001 отображается в виде полей1 в вашей системе.


1: Символ \001 может отображаться в виде прямоугольника, точки или какого-либо странного символа или вообще не отображаться в выводе.

person Mohit Jain    schedule 27.03.2015
comment
Что касается For example result of 2 + 3 * 5 is 17 and not 25.... Это отличный ответ, но если бы это был 2 * 3 + 5, он все равно оценивался бы как 11, а не как 16. Это связано с приоритетом оператора * над +, чем с оценкой справа налево. . Какой-нибудь новичок воспримет это неправильно.\ - person WedaPashi; 27.03.2015
comment
@WedaPashi Спасибо за ваш полезный комментарий, я соответствующим образом обновлю свой ответ. - person Mohit Jain; 27.03.2015
comment
По логике программы \000 не будет встречаться никогда, а не редко. Как только gethcar() возвращает '\n', ch присваивается 0 и тело цикла не выполняется. - person Hagen von Eitzen; 27.03.2015

И в качестве слегка мета-ответа всеобъемлющее исправление всегда компилируется с включенными предупреждениями:

$ gcc t.c -Wall
t.c: In function ‘main’:
t.c:7:5: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
     while (ch = getchar() != '\n')
     ^
t.c:12:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^

Или еще лучше попробуйте clang, который предупреждает по умолчанию и обычно дает более качественные диагностические сообщения:

$ clang t.c
t.c:7:15: warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
    while (ch = getchar() != '\n')
           ~~~^~~~~~~~~~~~~~~~~~~
t.c:7:15: note: place parentheses around the assignment to silence this warning
    while (ch = getchar() != '\n')
          ^
           (                     )
t.c:7:15: note: use '==' to turn this assignment into an equality comparison
    while (ch = getchar() != '\n')
              ^
              ==
1 warning generated.
person Tom Goodfellow    schedule 27.03.2015

Вы должны знать о приоритете операций – такие операторы сравнения, как !=, имеют более высокий приоритет, чем присваивание (=). Используйте круглые скобки, чтобы обеспечить требуемое поведение, т.е. изменить:

while (ch = getchar() != '\n')

to:

while ((ch = getchar()) != '\n')


Приложение: обязательно прислушайтесь к совету @TomGoodfellow в отдельном ответе ниже - использование приличного компилятора с включенными предупреждениями (например, gcc -Wall) немедленно предупредило бы вас об этой проблеме.

person Paul R    schedule 27.03.2015

Потому что вам нужно написать это как while ((ch = getchar()) != '\n')

person Yasir Majeed    schedule 27.03.2015
comment
Хотя вы очень правы, вы должны добавить описание того, что, как и почему в свой ответ, чтобы сделать его отличным. Ваше здоровье !! :-) - person Sourav Ghosh; 27.03.2015