Зачем использовать завершающие переводы строк вместо лидирующих с printf?

79

Я слышал, что при использовании вы должны избегать вводить новые строки printf. Так что вместо printf("\nHello World!")тебя стоит использоватьprintf("Hello World!\n")

В этом конкретном примере выше это не имеет смысла, поскольку выходные данные будут другими, но учтите это:

printf("Initializing");
init();
printf("\nProcessing");
process_data();
printf("\nExiting");

по сравнению с:

printf("Initializing\n");
init();
printf("Processing\n");
process_data();
printf("Exiting");

Я не вижу никакой выгоды от трейлинга новых строк, кроме того, что он выглядит лучше. Есть ли другая причина?

РЕДАКТИРОВАТЬ:

Я обращаюсь к закрытым голосам здесь и сейчас. Я не думаю, что это относится к переполнению стека, потому что этот вопрос в основном касается дизайна. Я хотел бы также сказать , что , хотя это может быть мнение по этому вопросу, ответ Kilian Фот в и ответ cmaster в доказывает , что на самом деле есть очень объективные преимущества с одним подходом.

klutt
источник
5
Этот вопрос находится на границе между «проблемами с кодом» (который не по теме) и «концептуальным дизайном программного обеспечения» (который по теме). Это может быть закрыто, но не принимайте это слишком усердно. Я думаю, что добавление конкретных примеров кода, тем не менее, было правильным выбором.
Килиан Фот
46
Последняя строка будет сливаться с командной строкой в ​​Linux без завершающей строки.
GrandmasterB
4
Если он «выглядит лучше» и у него нет недостатков, это достаточно веская причина для этого, ИМО. Написание хорошего кода ничем не отличается от написания хорошего романа или технической статьи - дьявол всегда в деталях.
алефзеро
5
Делать init()и process_data()печатать что-нибудь самим? Как бы вы ожидали, что результат будет выглядеть, если они сделали?
Берги
9
\nэто линия терминатора , не линия сепаратора . Об этом свидетельствует тот факт, что текстовые файлы в UNIX почти всегда заканчиваются на \n.
Джонатон Рейнхарт

Ответы:

222

Достаточное количество терминальных операций ввода-вывода буферизуется строкой , поэтому, заканчивая сообщение \ n, вы можете быть уверены, что оно будет отображаться своевременно. С начальным \ n сообщение может отображаться или не отображаться сразу. Часто это будет означать, что каждый шаг отображает сообщение о ходе выполнения предыдущего шага, что не вызывает путаницы и лишнего времени, когда вы пытаетесь понять поведение программы.

Килиан Фот
источник
20
Это особенно важно при использовании printf для отладки программы, которая дает сбой. Помещение новой строки в конец printf означает, что стандартный вывод на консоль сбрасывается при каждом printf. (Обратите внимание, что когда stdout перенаправляется в файл, библиотеки std обычно выполняют буферизацию блоков вместо буферизации строк, что делает отладку при
сбое
25
@ErikEidt Обратите внимание, что вы должны использовать fprintf(STDERR, …)вместо этого, который вообще не буферизируется для диагностического вывода.
Дедупликатор
4
@Deduplicator Запись диагностических сообщений в поток ошибок также имеет свои недостатки - многие сценарии предполагают, что программа завершилась сбоем, если что-то записано в поток ошибок.
Во
54
@ Voo: Я бы сказал, что любая программа, которая предполагает запись в stderr, указывает, что ошибка сама по себе неверна. Код выхода процесса - это то, что указывает, произошел ли сбой. Если это был сбой, вывод stderr объяснит почему . Если процесс завершился успешно (код выхода ноль), то вывод stderr должен рассматриваться как информационный вывод для пользователя-человека, без какой-либо конкретной машиночитаемой семантики (например, он может содержать понятные человеку предупреждения), тогда как stdout является фактическим выводом Программа, возможно, подходит для дальнейшей обработки.
Даниэль Приден
23
@Voo: Какие программы вы описываете? Я не знаю ни одного широко используемого программного пакета, который ведет себя так, как вы описываете. Я знаю, что есть программы, которые это делают, но я не просто сформулировал соглашение, которое я описал выше: именно так работает подавляющее большинство программ в среде Unix или Unix, и насколько я знаю, как Подавляющее большинство программ всегда есть. Я, конечно, не стал бы рекомендовать какой-либо программе избегать записи в stderr просто потому, что некоторые скрипты плохо с этим справляются.
Даниэль Приден
73

В системах POSIX (в основном в любом Linux, BSD, в любой системе с открытым исходным кодом, которую вы можете найти) строка определяется как строка символов, оканчивающаяся символом новой строки \n. Это основное предположение все стандартные инструменты командной строки опираться, в том числе (но не ограничиваясь ими) wc, grep, sed, awk, и vim. Это также причина, по которой некоторые редакторы (например vim) всегда добавляют \nв конце файла, и почему более ранние стандарты C требовали, чтобы заголовки заканчивались \nсимволом.

Кстати: наличие \nзавершенных строк значительно упрощает обработку текста: вы точно знаете, что у вас есть полная строка, когда у вас есть этот терминатор. И вы точно знаете, что вам нужно смотреть на большее количество символов, если вы еще не сталкивались с этим терминатором.

Конечно, это на стороне ввода программ, но вывод программы очень часто снова используется как ввод программы. Таким образом, ваш вывод должен придерживаться соглашения для обеспечения беспрепятственного ввода в другие программы.

cmaster
источник
25
Это один из самых старых дискуссий в программной инженерии: это лучше использовать символ новой строки (или, на языке программирования, другой «конец заявления» маркер как точка с запятой) в качестве линии терминатора или линии сепараторов ? Оба подхода имеют свои плюсы и минусы. Мир Windows в основном остановился на идее, что последовательность новой строки (обычно это CR LF) является разделителем строк , и, следовательно, последняя строка в файле не должна заканчиваться этим. В мире Unix, однако, последовательность новой строки (LF) является ограничителем строки , и многие программы построены на этом предположении.
Даниэль Приден
33
POSIX даже определяет строку как «Последовательность из нуля или более не-символов новой строки плюс завершающий символ новой строки ».
труба
6
Учитывая, что, как говорит @pipe, это в спецификации POSIX, мы, вероятно, можем назвать это де-юре, а не де-факто, как предполагает ответ?
Болдрикк
4
@ Балдрикк Верно. Я обновил свой ответ, чтобы быть более утвердительным, теперь.
Мастер
C также делает это соглашение для исходных файлов: непустой исходный файл, который не заканчивается новой строкой, вызывает неопределенное поведение.
R ..
31

В дополнение к тому, что упомянули другие, я чувствую, что есть гораздо более простая причина: это стандарт. Всякий раз, когда что-либо печатается в STDOUT, оно почти всегда предполагает, что оно уже находится на новой строке, и, следовательно, не нужно начинать новую. Также предполагается, что следующая записываемая строка будет действовать аналогичным образом, поэтому полезно завершить ее, начав новую строку.

Если вы выводите строки, начинающиеся с новой строки, чередующиеся со стандартными строками с конечной новой строкой, «в конечном итоге это будет выглядеть так:

Trailing-newline-line
Trailing-newline-line

Leading-newline-line
Leading-newline-line
Leading-newline-lineTrailing-newline-line
Trailing-newline-line

Leading-newline-lineTrailing-newline-line
Trailing-newline-line
Trailing-newline-line

... что, по-видимому, не то, что вы хотите.

Если вы используете только начальные символы новой строки в своем коде и запускаете его только в IDE, это может оказаться нормальным. Как только вы запустите его в терминале или введете чужой код, который будет писать в STDOUT вместе с вашим кодом, вы увидите нежелательный вывод, как описано выше.

Парень в шляпе
источник
2
Нечто подобное происходит с программами, которые прерываются в интерактивной оболочке - если печатается частичная строка (без ее новой строки), то оболочка запутывается в том, на каком столбце находится курсор, что затрудняет редактирование следующей командной строки. Если только вы не добавите в свою строку ведущий символ новой строки $PS1, который будет раздражать обычные программы.
Тоби Спейт
17

Поскольку ответы с большим количеством голосов уже дали отличные технические причины, по которым следует отдавать предпочтение завершающим переводам строк, я подойду к этому с другой стороны.

По моему мнению, следующее делает программу более читабельной:

  1. высокое отношение сигнал / шум (простое, но не простое)
  2. важные идеи на первом месте

Исходя из вышеперечисленных пунктов, мы можем утверждать, что последние строки лучше. Новые строки форматируют «шум» по сравнению с сообщением, сообщение должно выделяться и, следовательно, должно стоять первым (подсветка синтаксиса также может помочь).

Алекс Вонг
источник
19
Да, "ok\n"это намного лучше, чем "\nok"...
Cmaster
@cmaster: Напоминает начитавшись о MacOS с помощью Pascal-строки API - интерфейсов в C, который требуется предваряя все строковые литералы с магическим кодом побега , как "\pFoobar".
благодарность
16

Использование завершающих строк новой строки упрощает последующие модификации.

В качестве (очень тривиального) примера, основанного на коде OP, предположим, что вам нужно произвести некоторый вывод перед сообщением «Initializing», и этот вывод происходит из другой логической части кода, в другом исходном файле.

Когда вы запускаете первый тест и обнаруживаете, что «Инициализация» теперь добавляется в конец строки какого-либо другого вывода, вы должны выполнить поиск в коде, чтобы найти, где он был напечатан, и затем надеяться изменить «Инициализация» на «\ nИнициализация». "не портит формат чего-то другого, при других обстоятельствах.

Теперь рассмотрим, как будет обрабатываться тот факт, что ваш новый вывод на самом деле является необязательным, поэтому ваше изменение "\ nInitializing" иногда приводит к появлению нежелательной пустой строки в начале вывода ...

Вы устанавливаете глобальный ( шок ужас ?? !!! ) флаг, указывающий, был ли какой-либо предыдущий вывод, и проверяете его на вывод «Initializing» с необязательным начальным «\ n», или вы выводите «\ n» вместе с ваш предыдущий вывод и будущие читатели кода задаются вопросом, почему эта «Инициализация» не имеет лидирующего «\ n», как все остальные выходные сообщения?

Если вы последовательно выводите завершающие символы новой строки, в точке, в которой вы знаете, что достигли конца строки, которую необходимо завершить, вы обойдете все эти проблемы. Обратите внимание, что для этого может потребоваться отдельный оператор put ("\ n") в конце некоторой логики, которая выводит строку по частям, но дело в том, что вы выводите новую строку в самом раннем месте кода, где вы знаете, что вам нужно сделай это, а не где-нибудь еще.

alephzero
источник
1
Если предполагается, что каждый независимый элемент вывода отображается в отдельной строке, завершающие новые строки могут работать нормально. Если несколько пунктов должны быть объединены, когда это практически возможно, все становится более сложным. Если целесообразно передать весь вывод через общую подпрограмму, операцию по вставке текста в конец строки, если последний символ был CR, ничего, если последний символ был новой строкой, и новая строка, если последний символ было что-то еще, может быть полезно, если программы должны делать что-то кроме вывода последовательности независимых строк.
суперкат
7

Зачем использовать завершающие переводы строк вместо лидирующих с printf?

Точно соответствует C spec.

Библиотека C определяет линию как заканчивается с новой строки '\n' .

Текстовый поток - это упорядоченная последовательность символов, состоящая из строк , каждая строка состоит из нуля или более символов плюс завершающий символ новой строки. Требует ли последняя строка завершающего символа новой строки, определяется реализацией. C11 §7.21.2 2

Код, который записывает данные в виде строк, будет соответствовать этой концепции библиотеки.

printf("Initializing"); // Write part of a line
printf("\nProcessing"); // Finish prior line & write part of a line
printf("\nExiting");    // Finish prior line & write an implementation-defined last line

printf("Initializing\n");//Write a line 
printf("Processing\n");  //Write a line
printf("Exiting");       //Write an implementation-defined last line

Re: последняя строка требует завершающего символа новой строки . Я бы порекомендовал всегда писать финал '\n'на выходе и терпеть его отсутствие на входе.


Проверка орфографии

Моя проверка орфографии жалуется. Возможно, ты тоже.

  v---------v Not a valid word
"\nProcessing"

 v--------v OK
"Processing\n");
chux
источник
Я однажды ispell.elулучшился, чтобы справиться с этим лучше. Я признаю, что чаще всего \tэто была проблема, и ее можно было избежать, просто разбив строку на несколько токенов, но это был всего лишь побочный эффект более общей «игнорирующей» работы, чтобы выборочно пропустить нетекстовые части HTML или MIME составные части и части кода без комментариев. Я всегда хотел распространить это на переключение языков, где есть соответствующие метаданные (например, <p lang="de_AT">или Content-Language: gd), но никогда не получал Round Tuit. И сопровождающий сразу отклонил мой патч. :-(
Тоби Спейт
@TobySpeight Вот круглая часть . С нетерпением ждем попытки вашего улучшения ispell.el.
Chux
4

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

printf("Initializing");
if (jobName != null)
    printf(": %s", jobName);
init();
printf("\nProcessing");

(Но, как было отмечено в другом месте, вам может потребоваться очистить буфер вывода перед выполнением каких-либо шагов, которые занимают много времени процессора.)

Следовательно, можно сделать хороший пример для обоих способов сделать это, однако лично мне не нравится printf (), и я использовал бы собственный класс для создания вывода.

Ян
источник
1
Не могли бы вы объяснить, почему эту версию легче написать, чем ту, в которой есть перевод строки? В этом примере это не очевидно для меня. Вместо этого я мог видеть проблемы, возникающие при добавлении следующего вывода в ту же строку, что и "\nProcessing".
Раймунд Крэмер
Как и Раймунд, я также вижу проблемы, возникающие при такой работе. Вы должны учитывать окружающие отпечатки при звонке printf. Что, если вы хотите обусловить всю строку «Initializing»? Вы должны были бы включить строку «Procesing» в это условие, чтобы знать, стоит ли вам добавлять префикс новой строки или нет. Если впереди еще один отпечаток, и вам нужно условно обработать строку «Обработка», вам также необходимо включить следующий отпечаток в это условие, чтобы узнать, нужно ли ставить префикс с новым переводом строки и т. Д. Для каждого отпечатка.
JoL
2
Я согласен с принципом, но пример не очень хороший. Более уместным примером будет код, который должен выводить некоторое количество элементов в строке. Если вывод должен начинаться с заголовка, который заканчивается новой строкой, и каждая строка должна начинаться с заголовка, это может быть легче сказать, например, if ((addr & 0x0F)==0) printf("\n%08X:", addr);и безоговорочно добавить новую строку в вывод в конце, чем использовать отдельный код для заголовка каждой строки и завершающего символа новой строки.
суперкат
1

Ведущие перевода строки не работают хорошо с другими функциями библиотеки, в частности puts()и perrorв стандартной библиотеке, но и любой другой библиотеки , вы , вероятно, использовать.

Если вы хотите напечатать предварительно написанную строку (либо константу, либо уже отформатированную, например, с помощью sprintf()), то puts()это естественный (и эффективный) выбор. Однако нет возможности puts()завершить предыдущую строку и написать неопределенную строку - она ​​всегда записывает терминатор строки.

Тоби Спейт
источник