Навіщо використовувати останні рядки замість провідних з 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");

Я не бачу ніякої користі від прокладання нових рядків, крім того, що це виглядає краще. Чи є якась інша причина?

Редагувати:

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


5
Це питання знаходиться на межі між "проблемами з кодом" (що не є темою) та "концептуальним дизайном програмного забезпечення" (що в темі). Він може закритися, але не сприймайте це занадто важко. Я думаю, що додавання конкретних прикладів коду було правильним вибором.
Кіліан Фот

46
Останній рядок буде зливатися з командним рядком на Linux без зворотного нового рядка.
GrandmasterB

4
Якщо це "виглядає краще" і не має недоліків, це досить вагомий привід для цього, ІМО. Написання хорошого коду нічим не відрізняється від написання хорошого роману чи хорошого технічного паперу - чорт завжди в деталях.
алефзеро

5
Робіть init()і process_data()друкуєте що-небудь самі? Як би ви очікували виглядати результат, якби вони?
Бергі

9
\nє лінійним термінатором , а не роздільником ліній . Про це свідчить той факт, що текстові файли в UNIX майже завжди закінчуються \n.
Джонатан Райнхарт

Відповіді:


222

Справедлива кількість вводу-виводу терміналу буферизована лінією , тому, закінчуючи повідомлення з \ n, ви можете бути впевнені, що воно буде відображено своєчасно. За допомогою ведучого \ n повідомлення може відображатися або не відображатись одразу. Часто це означає, що кожен крок відображає повідомлення про хід попереднього кроку, що не спричиняє кінця плутанини та втрачає час, коли ви намагаєтесь зрозуміти поведінку програми.


20
Це особливо важливо при використанні printf для налагодження програми, яка виходить з ладу. Якщо розмістити нову лінію в кінці printf, це означає, що stdout до консолі видається при кожному printf. (Зверніть увагу, що коли stdout переспрямовується на файл, бібліотеки std зазвичай роблять блокування буферизацією замість буферизації рядків, так що робить налагодження printf налагодженням аварії досить важким навіть у
новому рядку

25
@ErikEidt Зауважте, що ви повинні використовувати fprintf(STDERR, …)замість цього, який, як правило, не буферний для діагностичного виводу.
Дедуплікатор

4
@Deduplicator У написанні діагностичних повідомлень у потік помилок є і свої недоліки - багато сценаріїв припускають, що програма вийшла з ладу, якщо щось записано в потік помилок.
Voo

54
@Voo: Я заперечую, що будь-яка програма, яка передбачає, що пише на stderr, вказує на збій, сама по собі є неправильною. Код виходу процесу - це те, що вказує на те, чи не вдалося він. Якщо це був збій, то більш жорсткий вихід пояснить, чому . Якщо процес завершився успішно (вихідний код нульовий), то виведення більш жорсткого типу слід вважати інформаційним результатом для людського користувача, без особливих семантичних машинно розбірних (він може містити попередження, прочитані людиною, наприклад), тоді як stdout - фактичний вихід програма, можливо підходить для подальшої обробки.
Даніель Приден

23
@Voo: Які програми ви описуєте? Мені невідомий жоден широко використовуваний програмний пакет, який веде себе так, як ви описуєте. Я знаю, що є такі програми, але це не так, як я щойно склав конвенцію, яку я описав вище: саме так працює переважна більшість програм у середовищі Unix або Unix, як мені відомо, як Переважна більшість програм завжди є. Я, звичайно, не виступаю за будь-яку програму, щоб не писати на stderr, просто тому, що деякі сценарії не справляються з цим.
Даніель Приден

73

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

Btw: Закінчення \nрядків значно спрощує обробку тексту: ви точно знаєте, що у вас є повний рядок, коли у вас є цей термінатор. І ви точно знаєте, що вам потрібно переглянути більше символів, якщо ви ще не стикалися з цим термінатором.

Звичайно, це на стороні введення програм, але вихід програми дуже часто знову використовується як введення програми. Отже, ваш вихід повинен дотримуватися конвенції заради того, щоб дозволяти безперебійне введення в інші програми.


25
Це одна з найдавніших дискусій в інженерії програмного забезпечення: чи краще використовувати нові рядки (або, мовою програмування, інший маркер "кінця висловлювання", як крапка з комою) як термінатори ліній або роздільники ліній ? Обидва підходи мають свої плюси і мінуси. Світ Windows здебільшого спирався на ідею, що послідовність нового рядка (який зазвичай є CR LF) є роздільником рядків , і тому останній рядок у файлі не повинен закінчуватися цим. Однак у світі Unix послідовність нових рядків (LF) є термінатором ліній , і багато програм будуються навколо цього припущення.
Даніель Приден

33
POSIX навіть визначає рядок як "Послідовність нульових або більше символів, що не належать до нового рядка, плюс символ завершення нового рядка ".
труба

6
З огляду на те, що, як говорить @pipe, це є у специфікації POSIX, ми, ймовірно, можемо назвати це де-юре, а не де-факто, як підказує відповідь?
Балдрік

4
@Baldrickk Вправо. Зараз я оновив свою відповідь, щоб бути більш ствердною.
cmaster

C також робить це умовою для вихідних файлів: не порожній вихідний файл, який не закінчується новим рядком, призводить до невизначеної поведінки.
Р ..

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, який би потім дратував наступні звичайні програми.
Toby Speight

17

Оскільки відповіді з високою оцінкою вже дали чудові технічні причини, чому слід віддавати перевагу останнім рядкам, я підійду до нього з іншого ракурсу.

На мою думку, наступні програми роблять програму більш зрозумілою:

  1. високе співвідношення сигнал / шум (він же простий, але не простіший)
  2. важливі ідеї виходять на перше місце

З вищенаведених пунктів, ми можемо стверджувати, що кращі нові лінії є кращими. Нові рядки форматують "шум" у порівнянні з повідомленням, повідомлення повинно виділятися і, таким чином, повинно бути першим (підкреслення синтаксису теж може допомогти).


19
Так, "ok\n"набагато краще, ніж "\nok"...
cmaster

@cmaster: нагадує мені, що я читав про MacOS за допомогою API Pascal-string API на C, для чого потрібна була префіксація всіх рядкових літералів за допомогою магічного коду виходу "\pFoobar".
grawity

16

Використання нових рядків спрощує подальші модифікації.

Як (дуже тривіальний) приклад, що базується на коді ОП, припустимо, що вам потрібно створити деякий вихід до повідомлення "Ініціалізація", і цей вихід надходить з іншої логічної частини коду в іншому вихідному файлі.

Коли ви запустите перший тест і знайдете "Ініціалізація" тепер додається до кінця рядка якогось іншого виводу, вам потрібно пошукати код, щоб знайти, де він надрукований, а потім сподіватися змінити "Ініціалізацію" на "\ nІніціалізація "не накручує формат чогось іншого за різних обставин.

Тепер подумайте, як збиратися вирішити той факт, що ваш новий результат насправді необов’язковий, тому ваша зміна на "\ nініціалізацію" іноді створює небажаний порожній рядок на початку виводу ...

Ви встановлюєте глобальний ( шоковий жах ?? !!! ) прапор, вказуючи, чи був якийсь попередній вихід, і тестуєте його, щоб надрукувати "Ініціалізацію" з необов'язковим провідним "\ n", чи виводите "\ n" разом із ваш попередній вихід і залиште майбутніх читачів кодів цікаво, чому ця "ініціалізація" не має провідного "\ n", як і всі інші вихідні повідомлення?

Якщо ви послідовно виводите кінцеві нові рядки, тоді, коли ви знаєте, що ви досягли кінця рядка, який потрібно припинити, ви переходите до всіх цих проблем. Зауважте, що може знадобитися окремий оператор put ("\ n") наприкінці певної логіки, яка виводить рядок за частиною, але справа полягає в тому, що ви виводите новий рядок на першому місці в коді, де ви знаєте, що вам потрібно робіть це, а не деінде.


1
Якщо кожен незалежний вихідний елемент повинен з’являтися у власному рядку, відстеження нових рядків може спрацювати нормально. Якщо декілька предметів мають бути консолідовані, коли це практично, все ускладнюється. Якщо це практично, щоб подати всі вихідні дані за допомогою звичайної програми, операція з введення чіткого до кінця рядка, якщо останній символ був CR, нічого, якщо останній символ був новим рядком, і новий рядок, якщо останній символ що-небудь інше, може бути корисним, якщо програмам потрібно робити щось інше, ніж виводити послідовність незалежних рядків.
supercat

7

Навіщо використовувати останні рядки замість провідних з printf?

Близько відповідає специфікації C.

Бібліотека 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");

Я колись вдосконалювався, ispell.elщоб краще впоратися з цим. Я визнаю, що частіше \tце була проблема, і цього можна було уникнути, просто розбивши рядок на кілька лексем, але це був лише побічний ефект більш загальної роботи "ігнорувати", щоб вибірково пропустити нетекстові частини HTML або багаточастинні органи MIME та частини коду без коментарів. Я завжди мав на увазі поширити це на перемикання мов там, де є відповідні метадані (наприклад, <p lang="de_AT">або Content-Language: gd), але ніколи не отримав круглу настройку. І власник відверто відхилив мій пластир. :-(
Toby Speight

@TobySpeight Ось кругла туга . З нетерпінням чекайте спроб повторного вдосконалення ispell.el.
chux

4

Провідні рядки часто можуть полегшити запис коду, коли є умовні умови, наприклад,

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

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

Отже, хороший випадок може бути зроблений для обох способів зробити це, однак особисто мені не подобається printf () і я б використовував спеціальний клас для нарощування результатів.


1
Не могли б ви пояснити, чому цю версію простіше написати, ніж версію з новими рядками? У цьому прикладі мені це не видно. Натомість я міг бачити проблеми, що виникають із додаванням наступного виводу до того ж рядка, що і "\nProcessing".
Raimund Krämer

Як і Раймунд, я також можу бачити проблеми, що виникають при такій роботі. Під час дзвінка вам потрібно враховувати навколишні відбитки printf. Що робити, якщо ви хотіли зумовити весь рядок "Ініціалізація"? Вам потрібно буде включити рядок "Обробляти" в цьому стані, щоб знати, чи слід префіксувати новим рядком чи ні. Якщо перед вами ще одна друк і вам потрібно умовити рядок "Обробка", вам також потрібно буде включити наступний друк у такому стані, щоб знати, чи слід префіксувати іншим новим рядком тощо для кожного друку.
JoL

2
Я погоджуюся з принципом, але приклад не є гарним. Більш релевантним прикладом може бути код, який повинен виводити деяку кількість елементів на рядок. Якщо вихід повинен починатися із заголовка, який закінчується новим рядком, і кожен рядок повинен починатися із заголовка, це може бути простіше сказати, наприклад, if ((addr & 0x0F)==0) printf("\n%08X:", addr);і безумовно додавати новий рядок до виводу в кінці, ніж використовувати окремий код для заголовка кожного рядка та останнього рядка.
supercat

1

Провідні перекладу рядка не працюють добре з іншими функціями бібліотеки, в Зокрема puts()і perrorв стандартній бібліотеці, а й будь-якої іншої бібліотеки , ви , ймовірно, використовувати.

Якщо ви хочете надрукувати заздалегідь написаний рядок (або постійний, або вже форматований - наприклад, з sprintf()), puts()це вибір природного (та ефективного). Тим НЕ менше, немає ніякого способу , puts()щоб закінчити попередню лінію і написати незамкнені лінію - вона завжди пише термінатор лінії.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.