printf với std :: chuỗi?


157

Sự hiểu biết của tôi stringlà một thành viên của stdkhông gian tên, vậy tại sao điều sau đây xảy ra?

#include <iostream>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString);
    cin.get();

    return 0;
}

nhập mô tả hình ảnh ở đây

Mỗi lần chương trình chạy, myStringin một chuỗi dường như ngẫu nhiên gồm 3 ký tự, chẳng hạn như ở đầu ra ở trên.


8
Chỉ để cho bạn biết, rất nhiều người chỉ trích cuốn sách đó. Điều mà tôi có thể hiểu, bởi vì không có nhiều về lập trình hướng đối tượng, nhưng tôi không nghĩ nó tệ như mọi người tuyên bố.
Jesse Tốt

ôi! tốt, thật tốt khi ghi nhớ điều này trong khi tôi thực hiện theo cách của mình thông qua cuốn sách. Tôi chắc chắn đó sẽ không phải là cuốn sách C ++ duy nhất tôi sẽ đọc trong suốt năm tới hoặc lâu hơn, vì vậy tôi hy vọng nó không làm quá nhiều điều
đáng tiếc

Sử dụng cảnh báo trình biên dịch cao nhất sẽ trả lời câu hỏi của bạn - khi biên dịch với gcc. Làm thế nào MSVC xử lý việc này - tôi không biết.
Peter VARGA

Câu trả lời:


236

Nó đang biên dịch vì printfkhông phải là loại an toàn, vì nó sử dụng các đối số biến theo nghĩa C 1 . printfkhông có tùy chọn cho std::string, chỉ có một chuỗi kiểu C. Sử dụng một cái gì đó khác thay cho những gì nó mong đợi chắc chắn sẽ không mang lại cho bạn kết quả như bạn muốn. Đó thực sự là hành vi không xác định, vì vậy bất cứ điều gì cũng có thể xảy ra.

Cách dễ nhất để khắc phục điều này, vì bạn đang sử dụng C ++, là in bình thường std::cout, vì std::stringhỗ trợ điều đó thông qua quá tải toán tử:

std::cout << "Follow this command: " << myString;

Nếu, vì một số lý do, bạn cần trích xuất chuỗi kiểu C, bạn có thể sử dụng c_str()phương thức std::stringđể lấy một const char *chuỗi kết thúc bằng null. Sử dụng ví dụ của bạn:

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

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString.c_str()); //note the use of c_str
    cin.get();

    return 0;
}

Nếu bạn muốn một hàm giống như thế printf, nhưng gõ an toàn, hãy xem các mẫu biến đổi (C ++ 11, được hỗ trợ trên tất cả các trình biên dịch chính kể từ MSVC12). Bạn có thể tìm thấy một ví dụ về một ở đây . Không có gì tôi biết được thực hiện như thế trong thư viện tiêu chuẩn, nhưng cụ thể có thể có trong Boost boost::format.


[1]: Điều này có nghĩa là bạn có thể vượt qua bất kỳ số lượng đối số nào, nhưng hàm dựa vào bạn để cho nó biết số lượng và loại của các đối số đó. Trong trường hợp printf, điều đó có nghĩa là một chuỗi với thông tin loại được mã hóa như %dý nghĩa int. Nếu bạn nói dối về loại hoặc số, hàm không có cách nhận biết chuẩn, mặc dù một số trình biên dịch có khả năng kiểm tra và đưa ra cảnh báo khi bạn nói dối.


@MooingDuck, Điểm tốt. Đó là câu trả lời của Jerry, nhưng là câu trả lời được chấp nhận, đây là những gì mọi người nhìn thấy và họ có thể rời đi trước khi nhìn thấy những người khác. Tôi đã thêm tùy chọn đó để trở thành giải pháp đầu tiên được nhìn thấy và là giải pháp được đề xuất.
chris

43

Vui lòng không sử dụng printf("%s", your_string.c_str());

Sử dụng cout << your_string;thay thế. Ngắn gọn, đơn giản và an toàn. Trên thực tế, khi bạn viết C ++, bạn thường muốn tránh printfhoàn toàn - đó là phần còn lại từ C hiếm khi cần thiết hoặc hữu ích trong C ++.

Về lý do tại sao bạn nên sử dụng coutthay vì printf, lý do rất nhiều. Đây là một mẫu của một vài trong số rõ ràng nhất:

  1. Như câu hỏi cho thấy, printfkhông phải là loại an toàn. Nếu loại bạn vượt qua khác với loại được chỉ định trong công cụ xác định chuyển đổi, printfsẽ cố gắng sử dụng bất cứ thứ gì nó tìm thấy trên ngăn xếp như thể nó là loại được chỉ định, đưa ra hành vi không xác định. Một số trình biên dịch có thể cảnh báo về điều này trong một số trường hợp, nhưng một số trình biên dịch hoàn toàn không thể / không thể, và không ai có thể trong mọi trường hợp.
  2. printfkhông thể mở rộng. Bạn chỉ có thể truyền các loại nguyên thủy cho nó. Tập hợp các công cụ xác định chuyển đổi mà nó hiểu được mã hóa cứng khi triển khai và không có cách nào để bạn thêm / người khác. Hầu hết C ++ được viết tốt nên sử dụng các loại này chủ yếu để thực hiện các loại hướng đến vấn đề đang được giải quyết.
  3. Nó làm cho định dạng phong nha khó khăn hơn nhiều. Ví dụ rõ ràng, khi bạn in số để mọi người đọc, bạn thường muốn chèn hàng ngàn dấu phân cách mỗi vài chữ số. Số chữ số chính xác và các ký tự được sử dụng làm dấu phân cách khác nhau, nhưng coutcũng được bao phủ. Ví dụ:

    std::locale loc("");
    std::cout.imbue(loc);
    
    std::cout << 123456.78;

    Ngôn ngữ không tên ("") chọn một miền địa phương dựa trên cấu hình của người dùng. Do đó, trên máy của tôi (được định cấu hình cho tiếng Anh Mỹ), bản in này là 123,456.78. Đối với ai đó có máy tính của họ được cấu hình cho (nói) Đức, nó sẽ in ra một cái gì đó như thế 123.456,78. Đối với ai đó có cấu hình cho Ấn Độ, nó sẽ được in ra 1,23,456.78(và tất nhiên có nhiều người khác). Với printftôi nhận được chính xác một kết quả : 123456.78. Điều đó phù hợp, nhưng nó luôn sai đối với mọi người ở mọi nơi. Về cơ bản, cách duy nhất để làm việc xung quanh nó là thực hiện định dạng riêng biệt, sau đó chuyển kết quả dưới dạng một chuỗi printf, vì printfđơn giản là nó sẽ không thực hiện công việc một cách chính xác.

  4. Mặc dù chúng khá nhỏ gọn, các printfchuỗi định dạng có thể khá khó đọc. Ngay cả trong số các lập trình viên C người sử dụng printfhầu như mỗi ngày, tôi đoán ít nhất 99% sẽ cần phải nhìn mọi thứ lên để chắc chắn những gì #trong %#xphương tiện, và làm thế nào mà khác với những gì #trong %#fphương tiện (và có, họ có nghĩa là mọi thứ hoàn toàn khác nhau ).

11
@ TheDarkIn1978: Có lẽ bạn đã quên #include <string>. VC ++ có một số điểm kỳ lạ trong các tiêu đề của nó sẽ cho phép bạn xác định một chuỗi, nhưng không gửi nó đến cout, mà không bao gồm <string>tiêu đề.
Jerry Coffin

28
@Jerry: Chỉ muốn chỉ ra rằng sử dụng printf nhanh hơn nhiều so với sử dụng cout khi xử lý dữ liệu lớn. Vì vậy, xin đừng nói rằng nó vô dụng: D
Lập trình viên

7
@ Trình lập trình: xem stackoverflow.com/questions/12044357/ . Tóm tắt: hầu hết thời gian coutchậm hơn, đó là vì bạn đã sử dụng những std::endlnơi bạn không nên.
Jerry Coffin

29
Chuyên gia C ++ điển hình kiêu ngạo. Nếu printf tồn tại, tại sao không sử dụng nó?
Kuroi neko

6
OK, xin lỗi vì nhận xét linh hoạt. Tuy nhiên, printf khá tiện dụng để gỡ lỗi và các luồng, mặc dù mạnh hơn rất nhiều, nhưng có một nhược điểm là mã không đưa ra bất kỳ ý tưởng nào về đầu ra thực tế. Đối với đầu ra được định dạng, printf vẫn là một lựa chọn khả thi và thật đáng tiếc là cả hai hệ thống không thể hợp tác tốt hơn. Hiển nhiên đó chỉ là ý kiến ​​của tôi thôi.
Kuroi neko

28

sử dụng myString.c_str()nếu bạn muốn một chuỗi giống như c ( const char*) để sử dụng với printf

cảm ơn



1

Lý do chính có lẽ là một chuỗi C ++ là một cấu trúc bao gồm giá trị độ dài hiện tại, không chỉ là địa chỉ của một chuỗi ký tự kết thúc bằng 0 byte. Printf và người thân của nó hy vọng tìm thấy một chuỗi như vậy, không phải là một cấu trúc và do đó bị nhầm lẫn bởi các chuỗi C ++.

Nói về bản thân tôi, tôi tin rằng printf có một vị trí không thể dễ dàng lấp đầy bởi các tính năng cú pháp của C ++, giống như các cấu trúc bảng trong html có một vị trí không thể dễ dàng điền vào bởi các div. Như Dykstra đã viết sau đó về goto, anh ta không có ý định bắt đầu một tôn giáo và thực sự chỉ tranh cãi về việc sử dụng nó như một loại bùn để bù đắp cho mã được thiết kế kém.

Sẽ khá tuyệt nếu dự án GNU sẽ thêm họ printf vào các phần mở rộng g ++ của họ.


1

Printf thực sự là khá tốt để sử dụng nếu kích thước quan trọng. Có nghĩa là nếu bạn đang chạy một chương trình trong đó bộ nhớ là một vấn đề, thì printf thực sự là một giải pháp rất tốt và theo rater. Cout về cơ bản chuyển các bit qua để nhường chỗ cho chuỗi, trong khi printf chỉ cần lấy một số loại tham số và in nó ra màn hình. Nếu bạn biên dịch một chương trình hello world đơn giản, printf sẽ có thể biên dịch nó trong ít hơn 60, 000 bit so với cout, nó sẽ mất hơn 1 triệu bit để biên dịch.

Đối với tình huống của bạn, id đề nghị sử dụng cout đơn giản vì nó thuận tiện hơn nhiều khi sử dụng. Mặc dù, tôi sẽ tranh luận rằng printf là một cái gì đó tốt để biết.


1

printfchấp nhận một số lượng đối số khác nhau. Những người chỉ có thể có các loại Dữ liệu cũ (POD). Mã vượt qua bất cứ thứ gì ngoài POD để printfchỉ biên dịch vì trình biên dịch giả định rằng bạn đã định dạng đúng. %scó nghĩa là đối số tương ứng được coi là một con trỏ tới a char. Trong trường hợp của bạn thì std::stringkhông const char*. printfkhông biết điều đó vì kiểu đối số bị mất và được cho là sẽ được khôi phục từ tham số định dạng. Khi biến std::stringđối số đó thành const char*con trỏ kết quả sẽ trỏ đến một vùng bộ nhớ không liên quan thay vì chuỗi C mong muốn của bạn. Vì lý do đó, mã của bạn in ra vô nghĩa.

Mặc dù printfmột lựa chọn tuyệt vời để in ra văn bản có định dạng , (đặc biệt nếu bạn có ý định đệm), nó có thể nguy hiểm nếu bạn không bật cảnh báo trình biên dịch. Luôn bật cảnh báo vì sau đó những lỗi như thế này có thể dễ dàng tránh được. Không có lý do để sử dụng std::coutcơ chế vụng về nếu printfgia đình có thể làm nhiệm vụ tương tự theo cách nhanh hơn và đẹp hơn nhiều. Chỉ cần chắc chắn rằng bạn đã kích hoạt tất cả các cảnh báo ( -Wall -Wextra) và bạn sẽ ổn. Trong trường hợp bạn sử dụng printftriển khai tùy chỉnh của riêng mình, bạn nên khai báo nó với __attribute__cơ chế cho phép trình biên dịch kiểm tra chuỗi định dạng theo các tham số được cung cấp .

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.