2012-06-02 40 views
104

Sự hiểu biết của tôi là string là thành viên của không gian tên std, vậy tại sao những điều sau xảy ra?C++ printf với std :: string?

#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; 
} 

enter image description here

Mỗi lần chương trình chạy, myString in một chuỗi dường như ngẫu nhiên của 3 nhân vật, chẳng hạn như trong đầu ra ở trên.

+6

Chỉ để cho bạn biết, rất nhiều người [phê bình] (http://stackoverflow.com/questions/5250596/how-should-i-undo-damage-caused-by-reading-c-primer-plus) cuốn sách đó. Điều mà tôi có thể hiểu được, 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ĩ rằng nó là xấu như mọi người yêu cầu. –

+0

ouf! tốt, nó là tốt để giữ điều này trong tâm trí trong khi tôi thực hiện theo cách của tôi thông qua cuốn sách. Tôi chắc chắn nó sẽ không phải là cuốn sách C++ duy nhất tôi sẽ đọc trong quá trình của năm tới hay như vậy, vì vậy tôi hy vọng nó không làm quá nhiều damange :) – TheDarkIn1978

Trả lời

153

Nó biên dịch vì printf không phải là loại an toàn, vì nó sử dụng đối số biến theo nghĩa C . printf không có tùy chọn cho std::string, chỉ có 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 cung cấp cho bạn những kết quả mà bạn muốn. Nó thực sự là hành vi không xác định, vì vậy bất cứ điều gì ở tất cả đều có thể xảy ra.

Cách đơn giản nhất để khắc phục điều này, kể từ khi bạn đang sử dụng C++, được in nó bình thường với std::cout, vì std::string hỗ trợ thông qua điều hành quá tải:

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

Nếu vì một lý do nào, bạn cần phải giải nén chuỗi kiểu C, bạn có thể sử dụng phương thức c_str() của std::string để nhận số const char * không được chấm dứt. Sử dụng ví dụ của bạn:

#include <iostream> 
#include <string> 

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 có một chức năng đó là như printf, nhưng là loại an toàn, hãy nhìn vào các mẫu variadic (C++ 11, được hỗ trợ trên tất cả các trình biên dịch lớn như của MSVC12). Bạn có thể tìm thấy một ví dụ về một số here. Không có gì tôi biết được thực hiện như vậy trong thư viện chuẩn, nhưng có thể có trong Tăng cường, cụ thể là boost::format.


[1]: Điều này có nghĩa là bạn có thể chuyển số lượng đối số bất kỳ, nhưng hàm dựa vào số lượng và loại đối số đó. Trong trường hợp printf, điều đó có nghĩa là một chuỗi có thông tin loại được mã hóa như %d có nghĩa là int. Nếu bạn nói dối về loại hoặc số, hàm không có cách thức tiêu chuẩn để biết, 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.

+1

không đề cập đến việc sử dụng 'cout' cho chuỗi là tốt? –

+0

@MooingDuck, Good point. Đó 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 xem và giải pháp được đề xuất. – chris

21

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

35

Xin đừng dùng printf("%s", your_string.c_str());

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

Đến lý do tại sao bạn nên sử dụng cout thay vì printf, lý do rất nhiều. Dưới đây là một số ví dụ điển hình nhất:

  1. Khi câu hỏi hiển thị, printf không an toàn cho loại. Nếu loại bạn vượt qua khác với loại được đưa ra trong trình biến đổi chuyển đổi, printf sẽ cố gắng sử dụng bất kỳ thứ gì mà nó tìm thấy trên ngăn xếp như thể đó là loại được chỉ định, cho 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 có thể không/sẽ không có ở tất cả, và không ai có thể trong mọi trường hợp.
  2. printf không thể mở rộng được. Bạn chỉ có thể truyền các kiểu nguyên thủy cho nó. Tập hợp các chỉ số chuyển đổi mà nó hiểu được được mã hóa cứng trong quá trình triển khai và không có cách nào để bạn thêm nhiều/người khác. C++ được viết tốt nhất nên sử dụng các kiểu này chủ yếu để triển khai các kiểu hướng tới 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. Đối với một ví dụ rõ ràng, khi bạn đang in số để mọi người đọc, bạn thường muốn chèn hàng nghìn dấu tá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 tách khác nhau, nhưng cout cũng được bao gồm. Ví dụ:

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

    Miền địa phương không tên ("") chọn ngôn ngữ dựa trên cấu hình của người dùng. Do đó, trên máy tính của tôi (được định cấu hình cho tiếng Anh Mỹ), bản in này ra là 123,456.78. Đối với ai đó có máy tính của họ cấu hình cho (nói) Đức, nó sẽ in ra một cái gì đó như 123.456,78. Đối với ai đó với nó được cấu hình cho Ấn Độ, nó sẽ in ra là 1,23,456.78 (và tất nhiên có nhiều người khác). Với printf tôi nhận được một kết quả chính xác: 123456.78. Nó nhất quán, nhưng nó liên tục sai cho mọi người ở mọi nơi. Về cơ bản, cách duy nhất để làm việc xung quanh đó là thực hiện định dạng riêng, sau đó chuyển kết quả dưới dạng chuỗi thành printf, bởi vì printf chính nó sẽ đơn giản là 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, nhưng các chuỗi định dạng printf có thể khá khó đọc. Ngay cả trong số các lập trình viên C sử dụng printf hầu như mỗi ngày, tôi đoán ít nhất 99% sẽ cần phải tìm mọi thứ để đảm bảo rằng những gì # trong %#x có nghĩa là, và cách khác với những gì # trong %#f có nghĩa là (và có, chúng có nghĩa là hoàn toàn khác nhau).
+0

khi tôi cố gắng biên dịch 'cout << myString << endl;' tôi nhận được lỗi sau: 'Lỗi 1 lỗi C2679: nhị phân '<<': không tìm thấy toán tử nào có toán hạng bên phải của kiểu 'std :: chuỗi '(hoặc không có chuyển đổi được chấp nhận) ' – TheDarkIn1978

+8

@ TheDarkIn1978: Có thể bạn đã quên' #include '. 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ó tới 'cout', mà không bao gồm tiêu đề' '. –

+0

mà tôi đã quên (thực sự không biết)! thanks :) – TheDarkIn1978

1

Lý do chính có thể là chuỗi C++ là cấu trúc bao gồm giá trị độ dài hiện tại, không chỉ địa chỉ của chuỗi ký tự được kết thúc bằng 0 byte. Printf và người thân của nó mong đợi 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ị lẫn lộn bởi các chuỗi C++.

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

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

3

Sử dụng std::printf và c_str() dụ:

std::printf("Follow this command: %s", myString.c_str()); 
1

printf thực sự là khá tốt để sử dụng nếu vấn đề kích thước. Có nghĩa là nếu bạn đang chạy một chương trình mà bộ nhớ là một vấn đề, sau đó printf thực sự là một giải pháp rất tốt và dưới rater. Cout về cơ bản chuyển bit qua để nhường chỗ cho chuỗi, trong khi printf chỉ lấy một số tham số và in nó vào 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 như trái ngược với cout, nó sẽ chiếm hơn 1 triệu bit để biên dịch.

Đối với trường hợp của bạn, id đề xuất sử dụng cout đơn giản vì việc sử dụng thuận tiện hơn nhiều. Mặc dù, tôi sẽ lập luận rằng printf là điều tốt để biết.

0

printf chấp nhận số lượng đối số thay đổi. Những người này chỉ có thể có các loại dữ liệu cũ (POD) Plain. Mã chuyển bất kỳ thứ gì khác ngoài POD sang printf chỉ biên dịch vì trình biên dịch giả sử bạn có định dạng đúng. %s có nghĩa là đối số tương ứng được cho là một con trỏ tới số char. Trong trường hợp của bạn, đây là số std::string không phải là const char*. printf không biết vì loại đối số bị mất và được cho là được khôi phục từ tham số định dạng. Khi chuyển đối số std::string thành const char* con trỏ kết quả sẽ trỏ đến một số vùng không liên quan của bộ nhớ 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.

Trong khi printfan excellent choice for printing out formatted text, (đặc biệt nếu bạn dự định có đệm), có thể nguy hiểm nếu bạn chưa bật cảnh báo trình biên dịch. Luôn bật cảnh báo vì sau đó các lỗi như thế này có thể dễ dàng tránh được. Không có lý do gì để sử dụng cơ chế std::cout vụng về nếu gia đình printf có thể thực hiện 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ẽ tốt. Trong trường hợp bạn sử dụng triển khai printf tùy chỉnh của riêng mình, bạn nên khai báo với cơ chế __attribute__enables the compiler to check the format string against the parameters provided.

Các vấn đề liên quan