'printf' so với 'cout' trong C ++


Câu trả lời:


333

Tôi ngạc nhiên khi tất cả mọi người trong câu hỏi này khẳng định đó std::coutlà cách tốt hơn printf, ngay cả khi câu hỏi chỉ yêu cầu sự khác biệt. Bây giờ, có một sự khác biệt - std::coutlà C ++ và printflà C (tuy nhiên, bạn có thể sử dụng nó trong C ++, giống như hầu hết mọi thứ khác từ C). Bây giờ, tôi sẽ thành thật ở đây; cả hai printfstd::coutcó lợi thế của họ.

Sự khác biệt thực sự

Khả năng mở rộng

std::coutcó thể mở rộng. Tôi biết rằng mọi người cũng sẽ nói rằng nó printfcó thể mở rộng, nhưng phần mở rộng đó không được đề cập trong tiêu chuẩn C (vì vậy bạn sẽ phải sử dụng các tính năng không chuẩn - nhưng thậm chí không có tính năng phi tiêu chuẩn phổ biến nào) và các tiện ích mở rộng đó chỉ là một chữ cái (vì vậy rất dễ xung đột với định dạng đã tồn tại).

Không giống như printf, std::coutphụ thuộc hoàn toàn vào quá tải toán tử, do đó không có vấn đề gì với các định dạng tùy chỉnh - tất cả những gì bạn làm là xác định một chương trình con lấy std::ostreamlàm đối số đầu tiên và loại của bạn là thứ hai. Như vậy, không có vấn đề về không gian tên - miễn là bạn có một lớp (không giới hạn ở một ký tự), bạn có thể phải làm việc std::ostreamquá tải cho nó.

Tuy nhiên, tôi nghi ngờ rằng nhiều người sẽ muốn gia hạn ostream(thành thật mà nói, tôi hiếm khi thấy các tiện ích mở rộng như vậy, ngay cả khi chúng dễ thực hiện). Tuy nhiên, nó ở đây nếu bạn cần nó.

Cú pháp

Vì nó có thể dễ dàng nhận thấy, cả hai printfstd::coutsử dụng cú pháp khác nhau. printfsử dụng cú pháp hàm tiêu chuẩn bằng cách sử dụng chuỗi mẫu và danh sách đối số có độ dài thay đổi. Trên thực tế, printflà một lý do tại sao C có chúng - printfcác định dạng quá phức tạp để có thể sử dụng mà không có chúng. Tuy nhiên, std::coutsử dụng một API khác - operator <<API tự trả về.

Nói chung, điều đó có nghĩa là phiên bản C sẽ ngắn hơn, nhưng trong hầu hết các trường hợp, nó không thành vấn đề. Sự khác biệt là đáng chú ý khi bạn in nhiều đối số. Nếu bạn phải viết một cái gì đó như Error 2: File not found., giả sử số lỗi và mô tả của nó là giữ chỗ, mã sẽ trông như thế này. Cả hai ví dụ đều hoạt động giống hệt nhau (tốt, sắp xếp, std::endlthực sự xóa bộ đệm).

printf("Error %d: %s.\n", id, errors[id]);
std::cout << "Error " << id << ": " << errors[id] << "." << std::endl;

Mặc dù điều này dường như không quá điên rồ (nó chỉ dài hơn hai lần), mọi thứ trở nên điên rồ hơn khi bạn thực sự định dạng các đối số, thay vì chỉ in chúng. Ví dụ, in một cái gì đó như 0x0424chỉ là điên rồ. Điều này được gây ra bởi std::coutsự pha trộn giữa trạng thái và giá trị thực tế. Tôi chưa bao giờ thấy một ngôn ngữ mà một thứ như thế std::setfillsẽ là một loại (tất nhiên không phải là C ++). printfphân tách rõ ràng đối số và loại thực tế. Tôi thực sự muốn duy trì printfphiên bản của nó (ngay cả khi nó trông có vẻ khó hiểu) so với iostreamphiên bản của nó (vì nó chứa quá nhiều tiếng ồn).

printf("0x%04x\n", 0x424);
std::cout << "0x" << std::hex << std::setfill('0') << std::setw(4) << 0x424 << std::endl;

Dịch

Đây là nơi lợi thế thực sự của printfdối trá. Chuỗi printfđịnh dạng cũng ... một chuỗi. Điều đó làm cho nó thực sự dễ dàng để dịch, so với operator <<lạm dụng iostream. Giả sử rằng gettext()hàm dịch và bạn muốn hiển thị Error 2: File not found., mã để nhận bản dịch của chuỗi định dạng được hiển thị trước đó sẽ trông như thế này:

printf(gettext("Error %d: %s.\n"), id, errors[id]);

Bây giờ, giả sử rằng chúng tôi dịch sang F hư cấu, trong đó số lỗi nằm sau phần mô tả. Chuỗi dịch sẽ như thế nào %2$s oru %1$d.\n. Bây giờ, làm thế nào để làm điều đó trong C ++? Vâng, tôi không có ý tưởng. Tôi đoán bạn có thể làm giả iostreamnhững cấu trúc printfmà bạn có thể chuyển đến gettext, hoặc một cái gì đó, cho mục đích dịch thuật. Tất nhiên, $không phải là tiêu chuẩn C, nhưng theo tôi thì nó phổ biến đến mức an toàn khi sử dụng.

Không phải nhớ / tra cứu cú pháp kiểu số nguyên cụ thể

C có rất nhiều kiểu số nguyên và C ++ cũng vậy. std::coutxử lý tất cả các loại cho bạn, trong khi printfyêu cầu cú pháp cụ thể tùy thuộc vào loại số nguyên (có loại không nguyên, nhưng loại không nguyên duy nhất bạn sẽ sử dụng trong thực tế printfconst char *(chuỗi C, có thể thu được bằng to_cphương thức std::string)). Ví dụ, để in size_t, bạn cần sử dụng %zd, trong khi int64_tsẽ yêu cầu sử dụng %"PRId64". Các bảng có sẵn tại http://en.cppreference.com/w/cpp/io/c/fprintfhttp://en.cppreference.com/w/cpp/types/integer .

Bạn không thể in byte NUL, \0

printfsử dụng các chuỗi C trái ngược với các chuỗi C ++, nó không thể in byte NUL mà không có các thủ thuật cụ thể. Trong một số trường hợp nó có thể sử dụng %cvới '\0'như một cuộc tranh cãi, mặc dù đó là rõ ràng một hack.

Sự khác biệt không ai quan tâm

Hiệu suất

Cập nhật: Hóa ra iostreamlà chậm đến mức nó thường chậm hơn ổ cứng của bạn (nếu bạn chuyển hướng chương trình của mình sang tệp). Vô hiệu hóa đồng bộ hóa với stdiocó thể giúp đỡ, nếu bạn cần xuất nhiều dữ liệu. Nếu hiệu suất là một mối quan tâm thực sự (trái ngược với việc viết một vài dòng cho STDOUT), chỉ cần sử dụng printf.

Mọi người đều nghĩ rằng họ quan tâm đến hiệu suất, nhưng không ai bận tâm để đo lường nó. Câu trả lời của tôi là I / O vẫn bị nghẽn cổ chai, bất kể bạn sử dụng printfhay iostream. Tôi nghĩ rằng printf thể nhanh hơn từ một cái nhìn nhanh vào lắp ráp (được biên dịch bằng tiếng kêu bằng cách sử dụng -O3tùy chọn trình biên dịch). Giả sử ví dụ lỗi của tôi, printfví dụ thực hiện cách gọi ít hơn so với coutví dụ. Đây là int mainvới printf:

main:                                   @ @main
@ BB#0:
        push    {lr}
        ldr     r0, .LCPI0_0
        ldr     r2, .LCPI0_1
        mov     r1, #2
        bl      printf
        mov     r0, #0
        pop     {lr}
        mov     pc, lr
        .align  2
@ BB#1:

Bạn có thể dễ dàng nhận thấy rằng hai chuỗi và 2(số) được đẩy làm printfđối số. Đó là về nó; Không có gì khác cả. Để so sánh, điều này được iostreambiên soạn để lắp ráp. Không, không có nội tuyến; mỗi operator <<cuộc gọi có nghĩa là một cuộc gọi khác với một bộ đối số khác.

main:                                   @ @main
@ BB#0:
        push    {r4, r5, lr}
        ldr     r4, .LCPI0_0
        ldr     r1, .LCPI0_1
        mov     r2, #6
        mov     r3, #0
        mov     r0, r4
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        mov     r0, r4
        mov     r1, #2
        bl      _ZNSolsEi
        ldr     r1, .LCPI0_2
        mov     r2, #2
        mov     r3, #0
        mov     r4, r0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r1, .LCPI0_3
        mov     r0, r4
        mov     r2, #14
        mov     r3, #0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r1, .LCPI0_4
        mov     r0, r4
        mov     r2, #1
        mov     r3, #0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r0, [r4]
        sub     r0, r0, #24
        ldr     r0, [r0]
        add     r0, r0, r4
        ldr     r5, [r0, #240]
        cmp     r5, #0
        beq     .LBB0_5
@ BB#1:                                 @ %_ZSt13__check_facetISt5ctypeIcEERKT_PS3_.exit
        ldrb    r0, [r5, #28]
        cmp     r0, #0
        beq     .LBB0_3
@ BB#2:
        ldrb    r0, [r5, #39]
        b       .LBB0_4
.LBB0_3:
        mov     r0, r5
        bl      _ZNKSt5ctypeIcE13_M_widen_initEv
        ldr     r0, [r5]
        mov     r1, #10
        ldr     r2, [r0, #24]
        mov     r0, r5
        mov     lr, pc
        mov     pc, r2
.LBB0_4:                                @ %_ZNKSt5ctypeIcE5widenEc.exit
        lsl     r0, r0, #24
        asr     r1, r0, #24
        mov     r0, r4
        bl      _ZNSo3putEc
        bl      _ZNSo5flushEv
        mov     r0, #0
        pop     {r4, r5, lr}
        mov     pc, lr
.LBB0_5:
        bl      _ZSt16__throw_bad_castv
        .align  2
@ BB#6:

Tuy nhiên, thành thật mà nói, điều này có nghĩa là không có gì, vì dù sao I / O cũng là nút cổ chai. Tôi chỉ muốn chứng tỏ rằng iostreamnó không nhanh hơn vì nó "an toàn". Hầu hết các triển khai C thực hiện printfcác định dạng bằng goto được tính toán, vì vậy printftốc độ nhanh nhất có thể, ngay cả khi không có trình biên dịch nhận biết printf(không phải là chúng không - một số trình biên dịch có thể tối ưu hóa printftrong một số trường hợp - chuỗi kết thúc liên tục \nthường được tối ưu hóa puts) .

Di sản

Tôi không biết tại sao bạn muốn thừa kế ostream, nhưng tôi không quan tâm. Điều đó cũng có thể xảy ra FILE.

class MyFile : public FILE {}

Loại an toàn

Các danh sách đối số độ dài thay đổi không có độ an toàn, nhưng điều đó không quan trọng, vì trình biên dịch C phổ biến có thể phát hiện các vấn đề với printfchuỗi định dạng nếu bạn bật cảnh báo. Trên thực tế, Clang có thể làm điều đó mà không cần cảnh báo.

$ cat safety.c

#include <stdio.h>

int main(void) {
    printf("String: %s\n", 42);
    return 0;
}

$ clang safety.c

safety.c:4:28: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
    printf("String: %s\n", 42);
                    ~~     ^~
                    %d
1 warning generated.
$ gcc -Wall safety.c
safety.c: In function main’:
safety.c:4:5: warning: format ‘%s expects argument of type char *’, but argument 2 has type int [-Wformat=]
     printf("String: %s\n", 42);
     ^

18
Bạn nói I / O là nút cổ chai nào. Rõ ràng bạn không bao giờ kiểm tra giả định đó. Tôi tự trích dẫn: "Mặt khác, phiên bản iostreams, ở tốc độ 75,3 MB / giây, không thể đệm dữ liệu đủ nhanh để theo kịp với một đĩa cứng. Điều đó thật tệ và thậm chí còn chưa thực hiện được công việc thực sự nào. Tôi không Tôi nghĩ rằng tôi có những kỳ vọng quá cao khi tôi nói rằng thư viện I / O của tôi sẽ có thể bão hòa bộ điều khiển đĩa của tôi. "
Ben Voigt

4
@BenVoigt: Tôi thừa nhận, tôi cố gắng tránh C ++ khi có thể. Tôi đã thử sử dụng nó rất nhiều, nhưng nó gây khó chịu hơn và ít bảo trì hơn so với ngôn ngữ lập trình khác mà tôi đã sử dụng. Đây là một lý do khác để tôi tránh C ++ - điều này thậm chí không nhanh (thậm chí không phải là iostream - toàn bộ thư viện C ++ chậm trong hầu hết các triển khai, có lẽ ngoại trừ std::sort, nhanh đến mức đáng ngạc nhiên so với qsort(2 lần), tại chi phí kích thước thực thi).
Konrad Borowski

3
Không ai ở đây đã đề cập đến các vấn đề trong môi trường song song khi sử dụng cout.
Nicholas Hamilton

9
Đối số hiệu suất của bạn làm cho không có ý nghĩa gì. Việc lắp ráp nhiều hơn trong chương trình của bạn không có nghĩa là chương trình sẽ chậm hơn, bởi vì bạn không hạch toán tất cả các mã tạo ra chức năng printf, rất nhiều mã. Theo tôi, có thể tối ưu hóa cout với toán tử << tốt hơn nhiều so với printf, bởi vì trình biên dịch có thể hiểu rõ hơn về các biến và định dạng.
Ignas2526

18
Tôi thích rất nhiều điều về câu trả lời này, nhưng có lẽ phần yêu thích của tôi là "Mọi người đều nghĩ rằng họ quan tâm đến hiệu suất, nhưng không ai bận tâm để đo lường nó."
Kyle Strand

203

Từ C ++ FAQ :

[15.1] Tại sao tôi nên sử dụng <iostream> thay vì truyền thống <cstdio>?

Tăng độ an toàn của loại, giảm lỗi, cho phép mở rộng và cung cấp khả năng kế thừa.

printf()được cho là không bị phá vỡ, và scanf()có lẽ là có thể sống được mặc dù dễ bị lỗi, tuy nhiên cả hai đều bị giới hạn đối với những gì I / O C ++ có thể làm. C / I / O (sử dụng <<>>) là, liên quan đến C (sử dụng printf()scanf()):

  • An toàn hơn về kiểu: Với <iostream>, loại đối tượng là I / O'd được trình biên dịch biết một cách tĩnh. Ngược lại, <cstdio>sử dụng các trường "%" để tìm ra các loại động.
  • Ít xảy ra lỗi hơn: Với <iostream>, không có mã thông báo "%" dự phòng nào phải phù hợp với các đối tượng thực tế là I / O'd. Loại bỏ sự dư thừa sẽ loại bỏ một lớp lỗi.
  • Mở rộng: <iostream>Cơ chế C ++ cho phép các loại do người dùng xác định mới là I / O'd mà không phá vỡ mã hiện có. Hãy tưởng tượng sự hỗn loạn nếu tất cả mọi người đồng thời thêm các trường "%" không tương thích mới vào printf()scanf()?!
  • Kế thừa: <iostream>Cơ chế C ++ được xây dựng từ các lớp thực như std::ostreamstd::istream. Không giống như <cstdio>'s FILE*, đây là những lớp học thực tế và do đó thừa kế. Điều này có nghĩa là bạn có thể có những thứ do người dùng định nghĩa khác trông giống và hoạt động như luồng, nhưng điều đó làm bất cứ điều gì kỳ lạ và tuyệt vời mà bạn muốn. Bạn tự động được sử dụng hàng trăm dòng mã I / O được viết bởi người dùng mà bạn thậm chí không biết và họ không cần biết về lớp "luồng mở rộng" của bạn.

Mặt khác, printfnhanh hơn đáng kể, điều này có thể biện minh cho việc sử dụng nó theo sở thích couttrong các trường hợp rất cụ thể và hạn chế. Luôn luôn hồ sơ đầu tiên. (Xem, ví dụ: http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout /)


2
Mặt khác, có thư viện FastFormat ( fastformat.org ), cung cấp loại an toàn, tính biểu cảm và hiệu suất cùng một lúc. (Không phải là tôi đã thử nó ...)
xtofl

3
@Marcelo có lẽ vì đó là một bản tóm tắt hay, với mọi thứ được trích dẫn. Định dạng ... vâng, điều đó khá tệ. Tôi nên tự sửa nó, nhưng có vẻ như những người khác (bao gồm cả bạn) đã quan tâm đến nó, điều này, tất nhiên, mang tính xây dựng hơn là chỉ rên rỉ.
Mikeage

2
Càng về cuối printf()cũng được cho là có thể mở rộng. Xem " hookf print
Maxim Egorushkin

4
@MaximYegorushkin: Tiêu chuẩn printfkhông có khả năng đó. Các cơ chế thư viện không di động hầu như không cùng mức độ mở rộng được chuẩn hóa hoàn toàn của iostreams.
Ben Voigt

4
"Mặt khác, printf nhanh hơn đáng kể" printf cũng sạch hơn và dễ sử dụng hơn, đó là lý do tại sao tôi tránh cout khi có thể.
Huỳnh

43

Mọi người thường cho rằng printfnhanh hơn nhiều. Điều này phần lớn là một huyền thoại. Tôi vừa thử nó, với kết quả như sau:

cout with only endl                     1461.310252 ms
cout with only '\n'                      343.080217 ms
printf with only '\n'                     90.295948 ms
cout with string constant and endl      1892.975381 ms
cout with string constant and '\n'       416.123446 ms
printf with string constant and '\n'     472.073070 ms
cout with some stuff and endl           3496.489748 ms
cout with some stuff and '\n'           2638.272046 ms
printf with some stuff and '\n'         2520.318314 ms

Kết luận: nếu bạn chỉ muốn dòng mới, sử dụng printf; mặt khác, coutgần như là nhanh, hoặc thậm chí nhanh hơn. Thông tin chi tiết có thể được tìm thấy trên blog của tôi .

Để rõ ràng, tôi không cố nói rằng iostreams luôn tốt hơn printf; Tôi chỉ đang cố gắng nói rằng bạn nên đưa ra quyết định dựa trên dữ liệu thực chứ không phải là phỏng đoán hoang dã dựa trên một số giả định phổ biến, sai lệch.

Cập nhật: Đây là mã đầy đủ tôi sử dụng để thử nghiệm. Được biên dịch g++mà không có bất kỳ tùy chọn bổ sung nào (ngoài -lrtthời gian).

#include <stdio.h>
#include <iostream>
#include <ctime>

class TimedSection {
    char const *d_name;
    timespec d_start;
    public:
        TimedSection(char const *name) :
            d_name(name)
        {
            clock_gettime(CLOCK_REALTIME, &d_start);
        }
        ~TimedSection() {
            timespec end;
            clock_gettime(CLOCK_REALTIME, &end);
            double duration = 1e3 * (end.tv_sec - d_start.tv_sec) +
                              1e-6 * (end.tv_nsec - d_start.tv_nsec);
            std::cerr << d_name << '\t' << std::fixed << duration << " ms\n"; 
        }
};

int main() {
    const int iters = 10000000;
    char const *text = "01234567890123456789";
    {
        TimedSection s("cout with only endl");
        for (int i = 0; i < iters; ++i)
            std::cout << std::endl;
    }
    {
        TimedSection s("cout with only '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << '\n';
    }
    {
        TimedSection s("printf with only '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("\n");
    }
    {
        TimedSection s("cout with string constant and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789" << std::endl;
    }
    {
        TimedSection s("cout with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789\n";
    }
    {
        TimedSection s("printf with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("01234567890123456789\n");
    }
    {
        TimedSection s("cout with some stuff and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << std::endl;
    }
    {
        TimedSection s("cout with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << '\n';
    }
    {
        TimedSection s("printf with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("%s01234567890123456789%i\n", text, i);
    }
}

5
Trong điểm số của bạn, printf đánh bại cout dễ dàng (trường hợp đa số). Tôi tự hỏi tại sao bạn khuyên bạn nên sử dụng cout khi nói đến sự hoàn hảo. Mặc dù tôi đồng ý sự hoàn hảo không quá khác biệt trong các trường hợp thực tế ..
mishal153

3
@ mishal153: Tôi chỉ cố gắng nói rằng hiệu suất không quá khác biệt, vì vậy lời khuyên thường được nghe là "không bao giờ sử dụng cout vì nó chậm chạp" là ngu ngốc. Lưu ý rằng cout có lợi thế rõ ràng về an toàn loại và cũng thường dễ đọc. (Định dạng dấu phẩy động với iostreams thật kinh khủng ...)
Thomas

35
Sự khác biệt quan trọng giữa printf()std::ostreamtrước đây xuất ra tất cả các đối số trong một cuộc gọi duy nhất trong khi std::ostreamphát sinh một cuộc gọi riêng cho mỗi cuộc gọi <<. Bài kiểm tra chỉ đưa ra một đối số và một dòng mới, đó là lý do tại sao bạn không thể thấy sự khác biệt.
Maxim Egorushkin

12
Trình biên dịch sẽ có thể nội tuyến các cuộc gọi này. Ngoài ra, printfcó thể thực hiện nhiều cuộc gọi dưới vỏ bọc cho các chức năng trợ giúp cho các trình xác định định dạng khác nhau ... hoặc đó là một chức năng nguyên khối quái dị. Và một lần nữa, vì nội tuyến, nó không nên tạo ra sự khác biệt về tốc độ.
Thomas

4
Bạn đã hẹn giờ cho thiết bị đầu cuối của bạn. Sử dụng sprintfhoặc fprintfstringstreamhoặc fstream.
Ben Voigt

41

Và tôi trích dẫn :

Ở cấp độ cao, sự khác biệt chính là an toàn loại (cstdio không có), hiệu suất (hầu hết các triển khai iostream đều chậm hơn so với cstdio) và khả năng mở rộng (iostreams cho phép mục tiêu đầu ra tùy chỉnh và đầu ra liền mạch của các loại do người dùng xác định).


Đặc biệt là trên unix với POSIX, bạn không bao giờ biết kích thước của một trong những typedefs thực sự có nên bạn cần rất nhiều phôi hoặc 99% các chương trình bạn chỉ cần mạo hiểm với% d. Đã thậm chí một thời gian dài trước khi% z đến với C99. Nhưng đối với time_t / off_t, nhiệm vụ cho hướng dẫn định dạng chính xác vẫn tiếp tục.
Lothar

30

Một là một chức năng in ra thiết bị xuất chuẩn. Đối tượng kia là một đối tượng cung cấp một số hàm thành viên và quá tải operator<<bản in đó cho thiết bị xuất chuẩn. Có nhiều sự khác biệt nữa mà tôi có thể liệt kê, nhưng tôi không chắc bạn sẽ làm gì sau đó.


12

Đối với tôi, sự khác biệt thực sự sẽ khiến tôi đi theo 'cout' thay vì 'printf' là:

1) << toán tử có thể bị quá tải cho các lớp của tôi.

2) Luồng đầu ra cho cout có thể dễ dàng thay đổi thành một tệp: (: sao chép dán :)

#include <iostream>
#include <fstream>
using namespace std;

int main ()
{
    cout << "This is sent to prompt" << endl;
    ofstream file;
    file.open ("test.txt");
    streambuf* sbuf = cout.rdbuf();
    cout.rdbuf(file.rdbuf());
    cout << "This is sent to file" << endl;
    cout.rdbuf(sbuf);
    cout << "This is also sent to prompt" << endl;
    return 0;
}

3) Tôi thấy cout dễ đọc hơn, đặc biệt là khi chúng ta có nhiều tham số.

Một vấn đề với coutlà các tùy chọn định dạng. Định dạng dữ liệu (độ chính xác, chính xác, v.v.) trong printfdễ dàng hơn.


1
nó đẹp. Làm thế nào tôi có thể biết không ai sửa đổi cout toàn cầu theo cách này trong một số chủ đề thư viện nước ngoài?
vp_arth

1
Bạn cũng có thể dễ dàng thay đổi printfthành một tệp bằng cách thay thế nó bằng fprintf...
CoffeeTableEspresso

5

Hai điểm không được đề cập ở đây mà tôi thấy có ý nghĩa:

1) coutmang theo nhiều hành lý nếu bạn chưa sử dụng STL. Nó thêm nhiều hơn hai lần mã vào tệp đối tượng của bạn printf. Điều này cũng đúng vớistring và đây là lý do chính khiến tôi có xu hướng sử dụng thư viện chuỗi của riêng mình.

2) coutsử dụng các <<toán tử quá tải , điều mà tôi thấy không may. Điều này có thể thêm sự nhầm lẫn nếu bạn cũng đang sử dụng<< toán tử cho mục đích dự định của mình (dịch chuyển sang trái). Cá nhân tôi không muốn quá tải các nhà khai thác cho các mục đích tiếp tuyến với mục đích sử dụng của họ.

Điểm mấu chốt: Tôi sẽ sử dụng cout(và string) nếu tôi đã sử dụng STL. Nếu không, tôi có xu hướng tránh nó.


4

Với người nguyên thủy, có lẽ hoàn toàn không quan trọng bạn sử dụng cái nào. Tôi nói nơi nó có ích là khi bạn muốn xuất các đối tượng phức tạp.

Ví dụ, nếu bạn có một lớp,

#include <iostream>
#include <cstdlib>

using namespace std;

class Something
{
public:
        Something(int x, int y, int z) : a(x), b(y), c(z) { }
        int a;
        int b;
        int c;

        friend ostream& operator<<(ostream&, const Something&);
};

ostream& operator<<(ostream& o, const Something& s)
{
        o << s.a << ", " << s.b << ", " << s.c;
        return o;
}

int main(void)
{
        Something s(3, 2, 1);

        // output with printf
        printf("%i, %i, %i\n", s.a, s.b, s.c);

        // output with cout
        cout << s << endl;

        return 0;
}

Bây giờ ở trên có vẻ không tốt lắm, nhưng giả sử bạn phải xuất cái này ở nhiều nơi trong mã của bạn. Không chỉ vậy, giả sử bạn thêm một trường "int d." Với cout, bạn chỉ phải thay đổi nó ở một nơi. Tuy nhiên, với printf, bạn phải thay đổi nó ở nhiều nơi và không chỉ vậy, bạn phải tự nhắc nhở mình nên xuất cái nào.

Như đã nói, với cout, bạn có thể giảm rất nhiều thời gian cho việc bảo trì mã của mình và không chỉ vậy nếu bạn sử dụng lại đối tượng "Something" trong một ứng dụng mới, bạn thực sự không phải lo lắng về đầu ra.


Ngoài ra, để thêm về điều hiệu suất, tôi muốn nói rằng bạn không nên xuất bất cứ thứ gì nếu ứng dụng của bạn được tạo ra để thực hiện. Bất kỳ loại đầu ra cho std là khá tốn kém và chậm. Tôi nói bạn nên tránh nó và chỉ xuất ra khi thực sự cần thiết phải làm như vậy.
Daniel

Hãy nhớ rằng lớp của bạn có thể có các thành viên riêng mà bạn không thể truy cập dễ dàng từ bên ngoài. Với toán tử đầu ra, bạn có chính xác một vị trí cần kết bạn với lớp của mình và bây giờ bạn có thể xuất nó ở bất cứ đâu, ngay cả trong mã mà bạn không biết.
hochl

2

Tất nhiên bạn có thể viết "một cái gì đó" tốt hơn một chút để duy trì bảo trì:

#include <iostream>
#include <cstdlib>

using namespace std;

class Something
{
    public:
        Something(int x, int y, int z) : a(x), b(y), c(z) { }
        int a;
        int b;
        int c;

        friend ostream& operator<<(ostream&, const Something&);

        void print() const { printf("%i, %i, %i\n", a, b, c); }
};

ostream& operator<<(ostream& o, const Something& s)
{
    o << s.a << ", " << s.b << ", " << s.c;
    return o;
}

int main(void)
{
    Something s(3, 2, 1);

    // Output with printf
    s.print(); // Simple as well, isn't it?

    // Output with cout
    cout << s << endl;

    return 0;
}

Và một thử nghiệm mở rộng một chút về cout so với printf, đã thêm một thử nghiệm 'double', nếu có ai muốn thực hiện thêm thử nghiệm (Visual Studio 2008, phiên bản phát hành của tệp thực thi):

#include <stdio.h>
#include <iostream>
#include <ctime>

class TimedSection {
    char const *d_name;
    //timespec d_start;
    clock_t d_start;

    public:
        TimedSection(char const *name) :
            d_name(name)
        {
            //clock_gettime(CLOCK_REALTIME, &d_start);
            d_start = clock();
        }
        ~TimedSection() {
            clock_t end;
            //clock_gettime(CLOCK_REALTIME, &end);
            end = clock();
            double duration = /*1e3 * (end.tv_sec - d_start.tv_sec) +
                              1e-6 * (end.tv_nsec - d_start.tv_nsec);
                              */
                              (double) (end - d_start) / CLOCKS_PER_SEC;

            std::cerr << d_name << '\t' << std::fixed << duration * 1000.0 << " ms\n";
        }
};


int main() {
    const int iters = 1000000;
    char const *text = "01234567890123456789";
    {
        TimedSection s("cout with only endl");
        for (int i = 0; i < iters; ++i)
            std::cout << std::endl;
    }
    {
        TimedSection s("cout with only '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << '\n';
    }
    {
        TimedSection s("printf with only '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("\n");
    }
    {
        TimedSection s("cout with string constant and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789" << std::endl;
    }
    {
        TimedSection s("cout with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789\n";
    }
    {
        TimedSection s("printf with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("01234567890123456789\n");
    }
    {
        TimedSection s("cout with some stuff and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << std::endl;
    }
    {
        TimedSection s("cout with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << '\n';
    }
    {
        TimedSection s("printf with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("%s01234567890123456789%i\n", text, i);
    }
    {
        TimedSection s("cout with formatted double (width & precision once)");
        std::cout << std::fixed << std::scientific << std::right << std::showpoint;
        std::cout.width(8);
        for (int i = 0; i < iters; ++i)
            std::cout << text << 8.315 << i << '\n';
    }
    {
        TimedSection s("cout with formatted double (width & precision on each call)");
        std::cout << std::fixed << std::scientific << std::right << std::showpoint;

        for (int i = 0; i < iters; ++i)
            { std::cout.width(8);
              std::cout.precision(3);
              std::cout << text << 8.315 << i << '\n';
            }
    }
    {
        TimedSection s("printf with formatted double");
        for (int i = 0; i < iters; ++i)
            printf("%8.3f%i\n", 8.315, i);
    }
}

Kết quả là:

cout with only endl    6453.000000 ms
cout with only '\n'    125.000000 ms
printf with only '\n'    156.000000 ms
cout with string constant and endl    6937.000000 ms
cout with string constant and '\n'    1391.000000 ms
printf with string constant and '\n'    3391.000000 ms
cout with some stuff and endl    9672.000000 ms
cout with some stuff and '\n'    7296.000000 ms
printf with some stuff and '\n'    12235.000000 ms
cout with formatted double (width & precision once)    7906.000000 ms
cout with formatted double (width & precision on each call)    9141.000000 ms
printf with formatted double    3312.000000 ms

Wow, tại sao lại endlkém hiệu quả hơn nhiều '\n'?
Nicholas Hamilton

1
Tôi tin rằng đó là vì endlxả bộ đệm, và \nkhông, mặc dù tôi không chắc đây là lý do chắc chắn tại sao.
Caleb Xu

Đây không phải là một câu hỏi hay, nó giống như một câu trả lời cho DanielThomas .
Fabio nói Phục hồi lại

2

Tôi muốn chỉ ra rằng nếu bạn muốn chơi với các luồng trong C ++, nếu bạn sử dụng, coutbạn có thể nhận được một số kết quả thú vị.

Xem xét mã này:

#include <string>
#include <iostream>
#include <thread>

using namespace std;

void task(int taskNum, string msg) {
    for (int i = 0; i < 5; ++i) {
        cout << "#" << taskNum << ": " << msg << endl;
    }
}

int main() {
    thread t1(task, 1, "AAA");
    thread t2(task, 2, "BBB");
    t1.join();
    t2.join();
    return 0;
}

// g++ ./thread.cpp -o thread.out -ansi -pedantic -pthread -std=c++0x

Bây giờ, đầu ra tất cả xáo trộn. Nó cũng có thể mang lại kết quả khác nhau, hãy thử thực hiện nhiều lần:

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

Bạn có thể sử dụng printfđể làm cho đúng, hoặc bạn có thể sử dụng mutex.

#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB

Chúc vui vẻ!


2
wtf threads không làm cho đầu ra đi hạt. Tôi chỉ sao chép và tìm thấy cả xyzABCtrong đầu ra. Không có xlingling b / w ABCnhư ABABAB.
Abhinav Gauniyal

1
Tôi không biết làm thế nào để làm coutviệc với các chủ đề, nhưng tôi biết chắc chắn rằng mã bạn đang hiển thị không phải là mã mà bạn đã sử dụng để có được các đầu ra đó. Mã của bạn chuyển chuỗi "ABC"cho luồng 1 và "xyz"cho luồng 2, nhưng đầu ra của bạn hiển thị AAABBB. Vui lòng sửa nó, bởi vì ngay bây giờ nó khó hiểu.
Fabio nói Phục hồi lại

1
cout<< "Hello";
printf("%s", "Hello"); 

Cả hai đều được sử dụng để in các giá trị. Họ có cú pháp hoàn toàn khác nhau. C ++ có cả hai, C chỉ có printf.


19
... gì? bạn đã trộn cái gì chưa?
xtofl

1
Đã khắc phục sự cố. -1 bởi vì nó yêu cầu sửa chữa và câu trả lời để lại rất nhiều mong muốn.
Yacoby

3
Tên hàm đã bị đảo ngược: cout được sử dụng với cú pháp printf và printf được sử dụng với cú pháp cout. Thậm chí không nên được chấp nhận!
Mahmoud Al-Qudsi

2
và nhược điểm chính của cout là nó sử dụng toán tử << dài dòng và lạm dụng toán tử xấu xí và được cho là. :)
jalf

8
Mặc dù đây là sự chắc chắn không phải là câu trả lời hay nhất, nhưng tôi không hiểu làm thế nào scatman bị trừng phạt vì câu trả lời của anh ta chỉ vì nó được chọn là câu trả lời hay nhất. xbit có cách trả lời tệ hơn IMO nhưng có -1 phiếu bầu. Tôi không nói rằng xbit sẽ bị bỏ phiếu nữa, nhưng tôi không thấy công bằng khi bỏ phiếu bầu cho lỗi của OP nữa mà không phải là ...
Jesse

1

Tôi muốn nói rằng sự thiếu mở rộng printfkhông hoàn toàn đúng:
Trong C, nó là sự thật. Nhưng trong C, không có lớp học thực sự.
Trong C ++, có thể quá tải toán tử cast, do đó, quá tải một char*toán tử và sử dụng printfnhư thế này:

Foo bar;
...;
printf("%s",bar);

có thể, nếu Foo quá tải toán tử tốt. Hoặc nếu bạn thực hiện một phương pháp tốt. Tóm lại, printflà mở rộng như coutđối với tôi.

Đối số kỹ thuật tôi có thể thấy đối với các luồng C ++ (nói chung ... không chỉ cout.) Là:

  • Loại an toàn. (Và, nhân tiện, nếu tôi muốn in một bản '\n'tôi sử dụng putchar('\n')... tôi sẽ không sử dụng bom nuke để giết côn trùng.).

  • Đơn giản hơn để học hỏi. (không có tham số "phức tạp" để tìm hiểu, chỉ để sử dụng <<>>vận hành)

  • Làm việc tự nhiên với std::string(vì printfstd::string::c_str(), nhưng choscanf ?)

printftôi thấy:

  • Định dạng phức tạp dễ hơn, hoặc ít nhất là ngắn hơn (về các ký tự được viết). Dễ đọc hơn, đối với tôi (vấn đề của hương vị tôi đoán).

  • Kiểm soát tốt hơn những gì hàm tạo ra (Trả về số lượng ký tự được viết và có %nđịnh dạng: "Không có gì được in. Đối số phải là một con trỏ tới một int đã ký, trong đó số lượng ký tự được viết cho đến nay được lưu trữ." (Từ printf - Tham khảo C ++ )

  • Khả năng sửa lỗi tốt hơn. Đối với lý do tương tự như đối số cuối cùng.

Sở thích cá nhân của tôi dành cho các chức năng printf(và scanf), chủ yếu là vì tôi yêu thích các dòng ngắn và vì tôi không nghĩ các vấn đề về kiểu in trên văn bản thực sự khó tránh. Điều duy nhất tôi làm với các chức năng kiểu C std::stringlà không được hỗ trợ. Chúng ta phải trải qua một char*trước khi đưa nó cho printf(với std::string::c_str()nếu chúng ta muốn đọc, nhưng làm thế nào để viết?)


3
Trình biên dịch không có thông tin loại cho các hàm varargs, vì vậy nó sẽ không chuyển đổi tham số thực tế (ngoại trừ các quảng cáo đối số mặc định , chẳng hạn như các quảng cáo tích phân tiêu chuẩn). Xem 5.2.2p7. Chuyển đổi do người dùng xác định char*sẽ không được sử dụng.
Ben Voigt

Ngay cả khi điều này hoạt động, nó sẽ không phải là một ví dụ về khả năng mở rộng của sprintf, chỉ là một cách hack thông minh để mang lại cho sprintf những gì nó mong đợi, và nó bỏ qua một số vấn đề nghiêm trọng như char*cuộc sống và trong bao lâu, và những nguy hiểm do người dùng xác định diễn viên ngầm.
Marcelo Cantos

1

Khác biệt hơn: "printf" trả về một giá trị nguyên (bằng số lượng ký tự được in) và "cout" không trả về bất cứ thứ gì

Và.

cout << "y = " << 7; không phải là nguyên tử.

printf("%s = %d", "y", 7); là nguyên tử.

cout thực hiện đánh máy, printf không.

Không có iostream tương đương với "% d"


3
coutkhông trả lại bất cứ điều gì vì nó là một đối tượng, không phải là một chức năng. operator<<không trả về một cái gì đó (thông thường là toán hạng bên trái của nó, nhưng giá trị sai nếu có lỗi). Và theo nghĩa nào là printfcuộc gọi "nguyên tử"?
Keith Thompson

9
Nó giống như một quả bom nguyên tử. printf("%s\n",7);
tiếng ồn vô nghĩa

@artlessnoise đợi tại sao lỗi phân khúc? %sLà ?
Abhinav Gauniyal

1
Đó là điểm của tuyên bố 'bom nguyên tử'. Một printf % s lập luận phải có một con trỏ hợp lệ để một chuỗi null chấm dứt. Phạm vi bộ nhớ '7' (một con trỏ) thường không hợp lệ; một lỗi phân khúc có thể là may mắn. Trên một số hệ thống, '7' có thể in rất nhiều rác lên bàn điều khiển và bạn sẽ phải xem xét nó trong một ngày trước khi chương trình dừng lại. Nói cách khác, đây là một điều xấu về printf. Các công cụ phân tích tĩnh có thể nắm bắt được nhiều vấn đề này.
tiếng ồn vô nghĩa

Mặc dù về mặt kỹ thuật printfkhông thực hiện đánh máy, tôi chưa bao giờ sử dụng trình biên dịch không cảnh báo tôi về lỗi loại với printf...
CoffeeTableEspresso

1

TL; DR: Luôn luôn thực hiện nghiên cứu của riêng bạn, liên quan đến kích thước mã máy , hiệu suất , khả năng đọcthời gian mã hóa được tạo trước khi tin tưởng các nhận xét ngẫu nhiên trực tuyến, bao gồm cả nhận xét này.

Tôi không phải là chuyên gia. Tôi tình cờ nghe thấy hai đồng nghiệp nói về cách chúng ta nên tránh sử dụng C ++ trong các hệ thống nhúng vì vấn đề hiệu năng. Chà, đủ thú vị, tôi đã làm một điểm chuẩn dựa trên một nhiệm vụ dự án thực sự.

Trong nhiệm vụ nói trên, chúng tôi đã phải viết một số cấu hình vào RAM. Cái gì đó như:

cà phê =
đường nóng = không
sữa = vú
mac = AA: BB: CC: DD: EE: FF

Đây là các chương trình điểm chuẩn của tôi (Có, tôi biết OP đã hỏi về printf (), không phải fprintf (). Hãy cố gắng nắm bắt bản chất và nhân tiện, liên kết của OP chỉ đến fprintf ().)

Chương trình C:

char coffee[10], sugar[10], milk[10];
unsigned char mac[6];

/* Initialize those things here. */

FILE * f = fopen("a.txt", "wt");

fprintf(f, "coffee=%s\nsugar=%s\nmilk=%s\nmac=%02X:%02X:%02X:%02X:%02X:%02X\n", coffee, sugar, milk, mac[0], mac[1],mac[2],mac[3],mac[4],mac[5]);

fclose(f);

Chương trình C ++:

//Everything else is identical except:

std::ofstream f("a.txt", std::ios::out);

f << "coffee=" << coffee << "\n";
f << "sugar=" << sugar << "\n";
f << "milk=" << milk << "\n";
f << "mac=" << (int)mac[0] << ":"
    << (int)mac[1] << ":"
    << (int)mac[2] << ":"
    << (int)mac[3] << ":"
    << (int)mac[4] << ":"
    << (int)mac[5] << endl;
f.close();

Tôi đã làm hết sức mình để đánh bóng chúng trước khi tôi lặp cả hai lần 100.000 lần. Đây là kết quả:

Chương trình C:

real    0m 8.01s
user    0m 2.37s
sys     0m 5.58s

Chương trình C ++:

real    0m 6.07s
user    0m 3.18s
sys     0m 2.84s

Kích thước tệp đối tượng:

C   - 2,092 bytes
C++ - 3,272 bytes

Kết luận: Trên nền tảng rất cụ thể của tôi , với bộ xử lý rất cụ thể , chạy phiên bản nhân Linux rất cụ thể , để chạy chương trình được biên dịch với phiên bản GCC rất cụ thể , để hoàn thành một nhiệm vụ rất cụ thể , tôi sẽ nói Cách tiếp cận C ++ phù hợp hơn vì nó chạy nhanh hơn đáng kể và cung cấp khả năng đọc tốt hơn nhiều. Mặt khác, C cung cấp dấu chân nhỏ, theo tôi, có nghĩa là gần như không có gì vì quy mô chương trình không phải là mối quan tâm của chúng tôi.

Người nhớ, YMMV.


Tôi không đồng ý rằng C ++ dễ đọc hơn trong ví dụ này, vì ví dụ của bạn gói nhiều dòng vào một lệnh gọi printf. Điều đó tự nhiên ít đọc hơn so với cách bạn đã làm mã C ++ và hiếm khi được thực hiện trong C vì nó khó đọc và khó bảo trì. Một so sánh công bằng sẽ trải rộng C thành các bản in riêng biệt, một cho dòng tiếp cận.
maharvey67

1
@ maharvey67 Đúng như những gì bạn nói. Tuy nhiên, ví dụ tôi cung cấp trong C đã được xem xét về hiệu suất. Cuộc gọi đóng gói trong một đến fprintf chậm hơn hai giây so với tương đương C ++. Nếu tôi làm cho mã C có thể đọc được thì nó có thể còn chậm hơn. Disclaimer: Đây là một năm trước và tôi nhớ rằng tôi đã cố gắng hết sức để đánh bóng cả mã C và C ++. Tôi không có bằng chứng về các cuộc gọi riêng biệt đến fprintf sẽ nhanh hơn một cuộc gọi duy nhất, nhưng lý do tôi thực hiện theo cách này có lẽ chỉ ra rằng nó không phải.
Wesley

0

Tôi không phải là lập trình viên, nhưng tôi là một kỹ sư nhân tố. Tôi cảm thấy một ngôn ngữ lập trình nên dễ học, hiểu và sử dụng, và điều này đòi hỏi nó phải có cấu trúc ngôn ngữ đơn giản và nhất quán. Mặc dù tất cả các ngôn ngữ là tượng trưng và do đó, ở cốt lõi, tùy ý, có những quy ước và tuân theo chúng làm cho ngôn ngữ dễ học và sử dụng hơn.

Có một số lượng lớn các hàm trong C ++ và các ngôn ngữ khác được viết dưới dạng hàm (tham số), một cú pháp ban đầu được sử dụng cho các mối quan hệ chức năng trong toán học trong thời kỳ tiền máy tính. printf()tuân theo cú pháp này và nếu các tác giả của C ++ muốn tạo bất kỳ phương thức logic nào khác để đọc và ghi tệp, họ có thể chỉ cần tạo một hàm khác bằng một cú pháp tương tự.

Trong Python tất nhiên chúng ta có thể in bằng cách sử dụng object.methodcú pháp khá chuẩn , ví dụ: matrixblename.print, vì các biến là các đối tượng, nhưng trong C ++ thì không.

Tôi không thích cú pháp cout vì toán tử << không tuân theo bất kỳ quy tắc nào. Nó là một phương thức hoặc hàm, tức là nó lấy một tham số và thực hiện một cái gì đó cho nó. Tuy nhiên, nó được viết như thể nó là một toán tử so sánh toán học. Đây là một cách tiếp cận kém từ quan điểm yếu tố con người.


-1

printflà một hàm trong khi đó coutlà một biến.


6
Tôi đã quay lại bởi vì, mặc dù bản thân câu trả lời có thể sai, nhưng nó vẫn là một câu trả lời chính hãng. Nếu bạn (chính xác) nghĩ rằng câu trả lời là sai, bạn có hai tùy chọn: 1) thêm nhận xét hoặc 2) thêm câu trả lời mới (hoặc thực hiện cả hai). Đừng thay đổi câu trả lời của ai đó để nó nói điều gì đó hoàn toàn khác với ý định của tác giả.
Đánh dấu

1
printflà một hàm, nhưng printf()là một hàm gọi =)
vp_arth

cout là một đối tượng, không phải là một biến.
Lin
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.