Tại sao nên sử dụng dòng mới thay vì dẫn đầu với printf?


79

Tôi nghe nói rằng bạn nên tránh hàng đầu mới khi sử dụng printf. Vì vậy, thay vì printf("\nHello World!")bạn nên sử dụngprintf("Hello World!\n")

Trong ví dụ cụ thể ở trên, nó không có ý nghĩa gì, vì đầu ra sẽ khác nhau, nhưng hãy xem xét điều này:

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

so với:

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

Tôi không thể thấy bất kỳ lợi ích nào với các dòng mới, ngoại trừ việc nó có vẻ tốt hơn. Có bất kỳ lý do khác?

BIÊN TẬP:

Tôi sẽ giải quyết các phiếu bầu gần đây và ngay bây giờ. Tôi không nghĩ rằng điều này thuộc về Stack tràn, bởi vì câu hỏi này chủ yếu là về thiết kế. Tôi cũng sẽ nói rằng mặc dù có thể có ý kiến ​​về vấn đề này, câu trả lời của Kilian Fothcâu trả lời của cmaster chứng minh rằng thực sự có những lợi ích rất khách quan với một cách tiếp cận.


5
Câu hỏi này nằm ở ranh giới giữa "các vấn đề với mã" (không có chủ đề) và "thiết kế phần mềm khái niệm" (thuộc chủ đề). Nó có thể bị đóng cửa, nhưng đừng quá khó khăn. Tôi nghĩ rằng thêm các ví dụ mã cụ thể là sự lựa chọn đúng đắn.
Kilian Foth

46
Dòng cuối cùng sẽ hợp nhất với dấu nhắc lệnh trên linux mà không có dòng mới.
GrandmasterB

4
Nếu nó "có vẻ tốt hơn" và nó không có nhược điểm, đó là một lý do đủ tốt để làm điều đó, IMO. Viết mã tốt không khác gì viết một cuốn tiểu thuyết hay một bài viết kỹ thuật tốt - ma quỷ luôn ở trong chi tiết.
alephzero

5
Làm init()và tự process_data()in bất cứ điều gì? Bạn mong đợi kết quả sẽ như thế nào nếu họ làm vậy?
Bergi

9
\nlà một dấu kết thúc dòng , không phải là dấu phân cách dòng . Điều này được chứng minh bằng thực tế là các tệp văn bản, trên UNIX, hầu như luôn luôn kết thúc \n.
Jonathon Reinhart

Câu trả lời:


222

Một số lượng lớn I / O của thiết bị đầu cuối được đệm theo dòng , do đó, bằng cách kết thúc một tin nhắn với \ n, bạn có thể chắc chắn rằng nó sẽ được hiển thị kịp thời. Với một \ n hàng đầu, tin nhắn có thể hoặc không thể được hiển thị cùng một lúc. Thông thường, điều này có nghĩa là mỗi bước sẽ hiển thị thông báo tiến trình của bước trước đó , điều này không gây ra sự nhầm lẫn và lãng phí thời gian khi bạn cố gắng hiểu hành vi của chương trình.


20
Điều này đặc biệt quan trọng khi sử dụng printf để gỡ lỗi chương trình gặp sự cố. Đặt dòng mới ở cuối printf có nghĩa là thiết bị xuất chuẩn vào bảng điều khiển sẽ bị xóa ở mỗi printf. (Lưu ý rằng khi stdout được chuyển hướng đến một tập tin, các thư viện std sẽ thường chặn đệm thay vì dòng đệm, vì vậy mà làm cho printf gỡ một vụ tai nạn khá khó khăn ngay cả với newline ở cuối.)
Erik Chỉ cần nạp

25
@ErikEidt Lưu ý rằng bạn nên sử dụng fprintf(STDERR, …)thay thế, thường không được đệm ở đầu ra cho chẩn đoán.
Ded repeatator

4
@Ded repeatator Viết các thông báo chẩn đoán vào luồng lỗi cũng có nhược điểm của nó - nhiều tập lệnh cho rằng một chương trình đã thất bại nếu có gì đó được ghi vào luồng lỗi.
Voo

54
@Voo: Tôi sẽ lập luận rằng bất kỳ chương trình nào giả sử ghi vào stderr đều cho thấy một lỗi là chính nó. Mã thoát của quá trình là những gì chỉ ra liệu nó có thất bại hay không. Nếu đó là một thất bại, thì đầu ra stderr sẽ giải thích tại sao . Nếu quá trình thoát thành công (mã thoát 0) thì đầu ra stderr nên được coi là đầu ra thông tin cho người dùng, không có ngữ nghĩa phân tích cụ thể bằng máy (ví dụ: có thể chứa cảnh báo có thể đọc được của con người), trong khi stdout là đầu ra thực tế của chương trình, có thể phù hợp để xử lý thêm.
Daniel Pryden

23
@Voo: Bạn đang mô tả chương trình gì? Tôi không biết về bất kỳ gói phần mềm được sử dụng rộng rãi nào hoạt động như bạn mô tả. Tôi biết rằng có những chương trình thực hiện, nhưng nó không giống như tôi đã tạo ra quy ước mà tôi mô tả ở trên: đó là cách mà phần lớn các chương trình trong môi trường giống như Unix hoặc Unix, và theo hiểu biết của tôi, theo cách của tôi đại đa số các chương trình luôn luôn có. Tôi chắc chắn sẽ không ủng hộ bất kỳ chương trình nào để tránh viết lên stderr đơn giản vì một số tập lệnh không xử lý tốt.
Daniel Pryden

73

Trên các hệ thống POSIX (về cơ bản là bất kỳ linux, BSD, bất kỳ hệ thống dựa trên nguồn mở nào bạn có thể tìm thấy), một dòng được định nghĩa là một chuỗi các ký tự bị chấm dứt bởi một dòng mới \n. Đây là giả thiết cơ bản tất cả các công cụ dòng lệnh chuẩn xây dựng dựa trên, bao gồm (nhưng không giới hạn) wc, grep, sed, awk, và vim. Đây cũng là lý do tại sao một số trình soạn thảo (như vim) luôn thêm một \nphần cuối của tệp và tại sao các tiêu chuẩn C trước đó yêu cầu các tiêu đề phải kết thúc bằng một \nký tự.

Btw: Việc có \ncác dòng kết thúc giúp cho việc xử lý văn bản dễ dàng hơn nhiều: Bạn biết chắc chắn rằng bạn đã có một dòng hoàn chỉnh khi bạn có bộ kết thúc đó. Và bạn biết chắc chắn rằng bạn cần xem xét nhiều nhân vật hơn nếu bạn chưa gặp kẻ hủy diệt đó.

Tất nhiên, đây là về phía đầu vào của chương trình, nhưng đầu ra chương trình thường được sử dụng làm đầu vào chương trình một lần nữa. Vì vậy, đầu ra của bạn nên tuân theo quy ước vì mục đích cho phép đầu vào liền mạch vào các chương trình khác.


25
Đây là một trong những cuộc tranh luận lâu đời nhất trong công nghệ phần mềm: tốt hơn là sử dụng các dòng mới (hoặc, trong ngôn ngữ lập trình, một dấu hiệu "kết thúc câu lệnh" khác như dấu chấm phẩy) làm dấu kết thúc dòng hoặc dấu tách dòng ? Cả hai phương pháp đều có ưu và nhược điểm của chúng. Thế giới Windows chủ yếu giải quyết ý tưởng rằng chuỗi dòng mới (thường là CR LF ở đó) là một dấu tách dòng , và do đó, dòng cuối cùng trong một tệp không cần kết thúc với nó. Tuy nhiên, trong thế giới Unix, một chuỗi dòng mới (LF) là một bộ kết thúc dòng và nhiều chương trình được xây dựng xung quanh giả định này.
Daniel Pryden

33
POSIX thậm chí còn định nghĩa một dòng là "Một chuỗi gồm 0 hoặc nhiều ký tự không phải dòng mới cộng với một ký tự dòng mới kết thúc ."
đường ống

6
Cho rằng như @pipe nói, đó là trong đặc tả POSIX, có lẽ chúng ta có thể gọi nó là de jure trái ngược với de facto như câu trả lời gợi ý?
Baldrickk

4
@Baldrickk Phải. Bây giờ tôi đã cập nhật câu trả lời của mình để khẳng định hơn.
cmaster

C cũng thực hiện quy ước này cho các tệp nguồn: một tệp nguồn không trống không kết thúc bằng một dòng mới tạo ra hành vi không xác định.
R ..

31

Ngoài những gì người khác đã đề cập, tôi cảm thấy như có một lý do đơn giản hơn nhiều: đó là tiêu chuẩn. Bất cứ khi nào bất cứ điều gì in lên STDOUT, hầu như luôn luôn giả định rằng nó đã ở trên một dòng mới, và do đó không cần phải bắt đầu một dòng mới. Nó cũng giả sử dòng tiếp theo được viết sẽ hoạt động theo cùng một cách, vì vậy nó kết thúc một cách hữu ích bằng cách bắt đầu một dòng mới.

Nếu bạn xuất các dòng hàng đầu mới xen kẽ với các dòng mới theo dõi tiêu chuẩn, "nó sẽ trông giống như thế này:

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

... đó có lẽ không phải là những gì bạn muốn.

Nếu bạn chỉ sử dụng các dòng mới hàng đầu trong mã của mình và chỉ chạy nó trong IDE, thì nó có thể trở nên ổn. Ngay khi bạn chạy nó trong một thiết bị đầu cuối hoặc giới thiệu mã của người khác sẽ ghi vào STDOUT cùng với mã của bạn, bạn sẽ thấy đầu ra không mong muốn như trên.


2
Điều gì đó tương tự xảy ra với các chương trình bị gián đoạn trong trình bao tương tác - nếu một dòng một phần được in (thiếu dòng mới của nó), thì trình bao bị nhầm lẫn về việc con trỏ đang bật cột nào, khiến cho việc chỉnh sửa dòng lệnh tiếp theo trở nên khó khăn. Trừ khi bạn thêm một dòng mới hàng đầu vào $PS1, sau đó sẽ gây khó chịu theo các chương trình thông thường.
Toby Speight

17

Vì các câu trả lời được đánh giá cao đã đưa ra lý do kỹ thuật tuyệt vời tại sao nên theo dõi các dòng mới, tôi sẽ tiếp cận nó từ một góc độ khác.

Theo ý kiến ​​của tôi, các nội dung sau đây giúp chương trình dễ đọc hơn:

  1. tỷ lệ tín hiệu trên tạp âm cao (hay đơn giản nhưng không đơn giản)
  2. ý tưởng quan trọng đến đầu tiên

Từ những điểm trên, chúng ta có thể lập luận rằng các dòng mới kéo dài là tốt hơn. Các dòng mới đang định dạng "nhiễu" khi so sánh với tin nhắn, tin nhắn sẽ nổi bật và do đó sẽ xuất hiện trước (tô sáng cú pháp cũng có thể giúp ích).


19
Vâng, "ok\n"tốt hơn nhiều so với "\nok"...
cmaster

@cmaster: Nhắc tôi đã đọc về MacOS bằng API chuỗi Pascal trong C, yêu cầu tiền tố tất cả các chuỗi ký tự chuỗi có mã thoát ma thuật như thế nào "\pFoobar".
grawity

16

Sử dụng dòng mới theo dõi đơn giản hóa các sửa đổi sau này.

Là một ví dụ (rất nhỏ) dựa trên mã của OP, giả sử bạn cần tạo ra một số đầu ra trước thông báo "Khởi tạo" và đầu ra đó đến từ một phần logic khác của mã, trong một tệp nguồn khác.

Khi bạn chạy thử nghiệm đầu tiên và thấy "Đang khởi tạo" hiện được gắn vào cuối một dòng của một số đầu ra khác, bạn phải tìm kiếm qua mã để tìm nơi nó được in và sau đó hy vọng thay đổi "Đang khởi tạo" thành "\ nInitializing "Không làm hỏng định dạng của một cái gì đó khác, trong các trường hợp khác nhau.

Bây giờ hãy xem xét cách xử lý thực tế rằng đầu ra mới của bạn thực sự là tùy chọn, do đó, thay đổi của bạn thành "\ nInitializing" đôi khi tạo ra một dòng trống không mong muốn khi bắt đầu đầu ra ...

Bạn có đặt cờ toàn cầu ( sốc kinh dị không? đầu ra trước đó của bạn và khiến độc giả mã trong tương lai tự hỏi tại sao "Khởi tạo" này không có "\ n" hàng đầu như tất cả các thông báo đầu ra khác làm gì?

Nếu bạn liên tục xuất ra các dòng mới, tại thời điểm bạn biết bạn đã đến cuối dòng cần chấm dứt, bạn sẽ bỏ qua tất cả những vấn đề đó. Lưu ý, điều đó có thể yêu cầu một câu lệnh đặt ("\ n") riêng ở cuối một số logic tạo ra một dòng theo từng mảnh, nhưng điểm quan trọng là bạn xuất dòng mới ở vị trí sớm nhất trong mã mà bạn biết bạn cần làm điều đó, không phải nơi nào khác.


1
Nếu mọi mục đầu ra độc lập được cho là xuất hiện trên dòng riêng của nó, thì các dòng mới có thể hoạt động tốt. Tuy nhiên, nếu nhiều mục nên được hợp nhất khi thực tế, mọi thứ trở nên phức tạp hơn. Nếu nó thực tế để cung cấp tất cả đầu ra thông qua một thói quen thông thường, thì một thao tác để chèn một dòng rõ ràng nếu ký tự cuối cùng là CR, không có gì nếu ký tự cuối cùng là một dòng mới và một dòng mới nếu ký tự cuối cùng là bất cứ điều gì khác, có thể hữu ích nếu các chương trình cần phải làm gì đó ngoài việc tạo ra một chuỗi các dòng độc lập.
supercat

7

Tại sao nên sử dụng dòng mới thay vì dẫn đầu với printf?

Kết hợp chặt chẽ C spec.

Thư viện C định nghĩa một dòngkết thúc bằng một ký tự dòng mới '\n' .

Luồng văn bản là một chuỗi các ký tự được sắp xếp thành các dòng , mỗi dòng bao gồm 0 hoặc nhiều ký tự cộng với một ký tự dòng mới kết thúc. Liệu dòng cuối cùng có yêu cầu ký tự dòng mới kết thúc được xác định theo thực hiện hay không. C11 §7,21.2 2

Mã ghi dữ liệu dưới dạng dòng sau đó sẽ khớp với khái niệm đó của thư viện.

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: dòng cuối cùng yêu cầu một ký tự dòng mới kết thúc . Tôi khuyên bạn nên luôn luôn viết một trận chung kết '\n'về đầu ra và chấp nhận sự vắng mặt của nó trên đầu vào.


Kiểm tra chính tả

Trình kiểm tra chính tả của tôi phàn nàn. Có lẽ bạn cũng vậy.

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

 v--------v OK
"Processing\n");

Tôi đã làm một lần cải thiện ispell.elđể đối phó tốt hơn với điều đó. Tôi thừa nhận rằng đó thường \tlà vấn đề và có thể tránh được bằng cách chia chuỗi thành nhiều mã thông báo, nhưng đó chỉ là tác dụng phụ của công việc "bỏ qua" chung chung hơn, để bỏ qua một cách có chọn lọc các phần không phải là văn bản của HTML hoặc phần thân nhiều phần của MIME và các phần không bình luận của mã. Tôi luôn có ý định mở rộng nó sang các ngôn ngữ chuyển đổi nơi có siêu dữ liệu phù hợp (ví dụ <p lang="de_AT">hoặc Content-Language: gd), nhưng không bao giờ có được Round Tuit. Và người bảo trì từ chối bản vá của tôi hoàn toàn. :-(
Toby Speight

@TobySpeight Đây là một toit tròn . Nhìn về phía trước để thử cải thiện của bạn ispell.el.
chux

4

Các dòng mới hàng đầu thường có thể giúp viết mã dễ dàng hơn khi có điều kiện, ví dụ,

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

(Nhưng như đã lưu ý ở nơi khác, bạn có thể cần phải xóa bộ đệm đầu ra trước khi thực hiện bất kỳ bước nào tốn nhiều thời gian của CPU.)

Do đó, một trường hợp tốt có thể được thực hiện cho cả hai cách thực hiện, tuy nhiên cá nhân tôi không thích printf () và sẽ sử dụng một lớp tùy chỉnh để xây dựng đầu ra.


1
Bạn có thể giải thích tại sao phiên bản này dễ viết hơn một phiên bản mới không? Trong ví dụ này nó không rõ ràng với tôi. Thay vào đó tôi có thể thấy các vấn đề phát sinh với đầu ra tiếp theo được thêm vào cùng một dòng như "\nProcessing".
Raimund Krämer

Giống như Raimund, tôi cũng có thể thấy các vấn đề phát sinh khi làm việc như thế này. Bạn cần xem xét các bản in xung quanh khi gọi printf. Điều gì xảy ra nếu bạn muốn điều kiện hóa toàn bộ dòng "Đang khởi tạo"? Bạn phải bao gồm dòng "Cung cấp" trong điều kiện đó để biết liệu bạn có nên thêm tiền tố với một dòng mới hay không. Nếu có một bản in khác ở phía trước và bạn cần điều kiện hóa dòng "Đang xử lý", bạn cũng cần đưa bản in tiếp theo vào điều kiện đó để biết liệu bạn có nên thêm tiền tố vào một dòng mới hay không, v.v.
JoL

2
Tôi đồng ý với nguyên tắc này, nhưng ví dụ không phải là một điều tốt. Một ví dụ phù hợp hơn sẽ là với mã được cho là xuất ra một số lượng mục trên mỗi dòng. Nếu đầu ra được cho là bắt đầu bằng một tiêu đề kết thúc bằng một dòng mới và mỗi dòng được cho là bắt đầu bằng một tiêu đề, có thể nói dễ dàng hơn, ví dụ if ((addr & 0x0F)==0) printf("\n%08X:", addr);và thêm vô điều kiện một dòng mới vào đầu ra ở cuối, hơn là sử dụng mã riêng cho tiêu đề của mỗi dòng và dòng mới.
supercat

1

Các dòng mới hàng đầu không hoạt động tốt với các chức năng thư viện khác, đáng chú ý puts()perrortrong Thư viện tiêu chuẩn, mà còn bất kỳ thư viện nào khác mà bạn có thể sử dụng.

Nếu bạn muốn in một dòng viết sẵn (có thể là một hằng số hoặc một dòng đã được định dạng - ví dụ như với sprintf()), thì đó puts()là lựa chọn tự nhiên (và hiệu quả). Tuy nhiên, không có cách nào puts()để kết thúc dòng trước đó và viết một dòng bị hủy bỏ - nó luôn luôn viết dòng kết thúc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.