Pull to refresh

Этот загадочный while…

Reading time3 min
Views6.7K
«Все потребности в нем заложены, какие только бывают на свете. И все эти потребности он может удовлетворить. С помощью нашей науки, разумеется.»
А. и Б. Стругацкие


Я думаю, многим из Perl-программистов знакома следующая конструкция построчного чтения содержимого файла:
while (<FILE>) {
    # do something
}
Этот код стал настолько привычным, что многие даже не задумываются, а как же он, собственно, работает. В данной статейке я опишу одну особенность, о которой весьма полезно помнить.

Как вы знаете, цикл while выполняется, пока его условие является истинным. Однако в нашем случае в качестве условия указана строка, которая даёт булевскую истину только в том случае, если она не является:
а) undef;
б) пустой строкой;
в) строкой, содержащей единственный символ "0".
Что же происходит, когда в файле вдруг встречается пустая строка? Неужто цикл перестанет работать? Отнюдь. Считываемые строки включают в себя маркер конца строки, а значит, пустая строка на самом деле не пустая, а "\n", и поэтому будет расценена в булевском контексте как истина. То же самое относится к случаю, когда в файле попадается строчка с одним лишь символом "0". Но есть случай, когда это правило не срабатывает: если последняя строчка файла содержит лишь символ "0" и не завершается символом новой строки. В результате при чтении этой последней строчки мы получим строку "0", которая для while должна считаться ложной, и, следовательно, эта строка не будет обработана. Правильно? Казалось бы, всё правильно, и теперь надо срочно хвататься либо за любимый текстовый редактор, либо за валидол, либо за то и другое одновременно, — в зависимости от степени важности проекта, где использовался данный код.

Можете не торопиться: код совершенно правильный и работать будет корректно, и связано это с хитрой особенностью оператора while. Цитирую perlop (в вольном переводе):
Нижеприведённые строки эквивалентны:
    while (defined($_ = <STDIN>)) { print; }
    while ($_ = <STDIN>) { print; }
    while (<STDIN>) { print; }
    for (;<STDIN>;) { print; }
    print while defined($_ = <STDIN>);
    print while ($_ = <STDIN>);
    print while <STDIN>;
А эта строка кода работает аналогичным образом, но без использования $_:
    while (my $line = <STDIN>) { print $line }
Во всех этих конструкциях для присваиваемого значения (независимо от того, явное было присваивание или нет) выполняется проверка на то, определено ли значение. Это позволяет избежать проблемы, когда строковое значение является ложным в булевском контексте, например, "" или "0" без завершающего символа новой строки. Если вы намеренно хотите использовать эти значения для завершения цикла, необходимо проверять их явным образом:
    while (($_ = <STDIN>) ne '0') { ... }
    while (<STDIN>) { last unless $_; ... }
В других выражениях с булевским контекстом при использовании <filehandle> будет выведено предупреждение, если отсутствует явный вызов функции defined или операция сравнения, при условии, что включена прагма use warnings или используется параметр командной строки -w (или переменная $^W).
Таким образом, внутри цикла while чтение из файла автоматически оборачивается в проверку на defined. Однако стоит вам заменить while на if или даже просто усложнить условие цикла, добавив через and или or ещё какое-нибудь подвыражение, как карета превращается в тыкву оператор теряет все свои волшебные качества и начинает проверять условия строго в соответствии с правилами преобразования типов. О чём нам незамедлительно сообщит компилятор Perl, если, конечно, не забыть его попросить об этом ключом -w.
Tags:
Hubs:
+36
Comments65

Articles

Change theme settings