printf dengan std :: string?

157

Pemahaman saya adalah itu stringadalah anggota stdnamespace, jadi mengapa hal berikut terjadi?

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

masukkan deskripsi gambar di sini

Setiap kali program berjalan, myStringmencetak string 3 karakter yang tampaknya acak, seperti pada output di atas.

TheDarkIn1978
sumber
8
Sekadar informasi, banyak orang mengkritik buku itu. Yang bisa saya mengerti, karena tidak banyak tentang pemrograman berorientasi objek, tapi saya pikir itu tidak seburuk yang diklaim orang.
Jesse Good
wah! baik, baik untuk mengingat ini sementara saya mencari jalan melalui buku. Saya yakin ini bukan satu-satunya buku C ++ yang akan saya baca sepanjang tahun depan, jadi saya harap buku ini tidak terlalu merusak :)
TheDarkIn1978
Menggunakan peringatan kompiler tertinggi akan menjawab pertanyaan Anda - saat kompilasi dengan gcc. Bagaimana MSVC menangani ini - saya tidak tahu.
Peter VARGA

Jawaban:

237

Mengkompilasi karena printfbukan tipe aman, karena menggunakan argumen variabel dalam pengertian C 1 . printftidak memiliki opsi untuk std::string, hanya string gaya-C. Menggunakan sesuatu yang lain di tempat yang diharapkannya pasti tidak akan memberi Anda hasil yang Anda inginkan. Ini sebenarnya perilaku yang tidak terdefinisi, sehingga apa pun bisa terjadi.

Cara termudah untuk memperbaikinya, karena Anda menggunakan C ++, adalah mencetaknya dengan normal std::cout, karena std::stringmendukungnya melalui overloading operator:

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

Jika, karena alasan tertentu, Anda perlu mengekstrak string gaya-C, Anda dapat menggunakan c_str()metode std::stringuntuk mendapatkan const char *yang diakhiri dengan null. Menggunakan contoh Anda:

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

Jika Anda menginginkan fungsi yang seperti printf, tetapi mengetikkan safe, lihatlah templat variadic (C ++ 11, yang didukung pada semua kompiler utama pada MSVC12). Anda dapat menemukan contohnya di sini . Tidak ada yang saya tahu tentang implementasi seperti itu di perpustakaan standar, tetapi mungkin ada di Boost, khususnya boost::format.


[1]: Ini berarti Anda bisa memberikan sejumlah argumen, tetapi fungsinya bergantung pada Anda untuk memberi tahu nomor dan jenis argumen itu. Dalam kasus ini printf, itu berarti sebuah string dengan tipe informasi yang dikodekan seperti %dmakna int. Jika Anda berbohong tentang jenis atau angka, fungsi tidak memiliki cara standar untuk mengetahui, meskipun beberapa penyusun memiliki kemampuan untuk memeriksa dan memberikan peringatan ketika Anda berbohong.

chris
sumber
@ MoooDuck, Poin bagus. Ada dalam jawaban Jerry, tetapi sebagai jawaban yang diterima, inilah yang dilihat orang, dan mereka mungkin pergi sebelum melihat yang lain. Saya telah menambahkan opsi itu agar menjadi solusi pertama yang terlihat, dan yang direkomendasikan.
chris
43

Tolong jangan gunakan printf("%s", your_string.c_str());

Gunakan cout << your_string;sebagai gantinya. Singkat, sederhana, dan aman untuk mengetik. Bahkan, ketika Anda menulis C ++, Anda umumnya ingin menghindari printfsepenuhnya - itu adalah sisa dari C yang jarang dibutuhkan atau berguna dalam C ++.

Seperti mengapa Anda harus menggunakan coutbukan printf, alasan banyak. Berikut beberapa contoh yang paling jelas:

  1. Seperti yang ditunjukkan oleh pertanyaan, printftidak aman bagi tipe. Jika jenis yang Anda berikan berbeda dari yang diberikan dalam specifier konversi, printfakan mencoba menggunakan apa pun yang ditemukan di tumpukan seolah-olah itu jenis yang ditentukan, memberikan perilaku yang tidak ditentukan. Beberapa kompiler dapat memperingatkan tentang hal ini dalam beberapa keadaan, tetapi beberapa kompiler tidak bisa / tidak akan sama sekali, dan tidak ada yang bisa dalam semua keadaan.
  2. printftidak bisa diperpanjang. Anda hanya dapat meneruskan tipe primitif ke dalamnya. Himpunan penentu konversi yang dipahaminya memiliki kode-keras dalam implementasinya, dan tidak ada cara bagi Anda untuk menambahkan lebih banyak / orang lain. Kebanyakan C ++ yang ditulis dengan baik harus menggunakan tipe-tipe ini terutama untuk mengimplementasikan tipe yang berorientasi pada masalah yang sedang dipecahkan.
  3. Itu membuat pemformatan yang layak jauh lebih sulit. Untuk contoh yang jelas, saat Anda mencetak angka untuk dibaca orang, Anda biasanya ingin memasukkan ribuan pemisah setiap beberapa digit. Jumlah pasti digit dan karakter yang digunakan sebagai pemisah bervariasi, tetapi coutjuga mencakup hal itu. Sebagai contoh:

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

    Lokal tanpa nama ("") memilih lokal berdasarkan konfigurasi pengguna. Oleh karena itu, pada mesin saya (dikonfigurasi untuk Bahasa Inggris AS) ini dicetak sebagai 123,456.78. Untuk seseorang yang memiliki komputer mereka dikonfigurasi untuk (katakanlah) Jerman, itu akan mencetak sesuatu seperti 123.456,78. Untuk seseorang yang dikonfigurasi untuk India, itu akan dicetak sebagai 1,23,456.78(dan tentu saja ada banyak lainnya). Dengan printfsaya mendapatkan tepat satu hasil: 123456.78. Itu konsisten, tetapi konsisten salah untuk semua orang di mana pun. Pada dasarnya satu-satunya cara untuk bekerja di sekitar itu adalah untuk melakukan format secara terpisah, kemudian lulus hasilnya sebagai string untuk printf, karena printfitu sendiri hanya akan tidak melakukan pekerjaan dengan benar.

  4. Meskipun mereka cukup kompak, printfformat string bisa sangat tidak dapat dibaca. Bahkan di antara programmer C yang menggunakan printfhampir setiap hari, saya kira setidaknya 99% perlu mencari hal-hal untuk memastikan apa artinya #di %#x, dan bagaimana itu berbeda dari apa artinya #di %#f(dan ya, mereka berarti hal yang sama sekali berbeda ).
Jerry Coffin
sumber
11
@ TheDarkIn1978: Anda mungkin lupa #include <string>. VC ++ memiliki beberapa keanehan pada tajuknya yang memungkinkan Anda menentukan string, tetapi tidak mengirimkannya cout, tanpa menyertakan <string>tajuk.
Coffin Jerry
28
@ Jerry: Hanya ingin menunjukkan bahwa menggunakan printf JAUH lebih cepat daripada menggunakan cout ketika berhadapan dengan data besar. Jadi, tolong jangan katakan itu tidak berguna: D
Programmer
7
@Programmer: lihat stackoverflow.com/questions/12044357/… . Ringkasan: sering kali coutlebih lambat, itu karena Anda telah menggunakan std::endltempat yang tidak seharusnya.
Jerry Coffin
29
Keangkuhan pakar C ++. Jika printf memang ada, mengapa tidak menggunakannya?
kuroi neko
6
OKE, maaf untuk komentar tajam. Namun, printf cukup berguna untuk debugging dan stream, meskipun jauh lebih kuat, memiliki kelemahan bahwa kode tidak memberikan gagasan tentang output aktual. Untuk output yang diformat, printf masih merupakan alternatif yang layak, dan sayang sekali kedua sistem tidak dapat bekerja sama dengan lebih baik. Pendapat saya saja.
kuroi neko
28

gunakan myString.c_str()jika Anda ingin string c-like ( const char*) digunakan dengan printf

Terima kasih

Alessandro Pezzato
sumber
6

Gunakan std :: printf dan c_str () contoh:

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

Alasan utama mungkin adalah bahwa string C ++ adalah sebuah struct yang menyertakan nilai panjang-saat ini, bukan hanya alamat urutan karakter yang diakhiri oleh 0 byte. Printf dan kerabatnya berharap menemukan urutan seperti itu, bukan struct, dan karena itu menjadi bingung oleh string C ++.

Berbicara sendiri, saya percaya bahwa printf memiliki tempat yang tidak dapat dengan mudah diisi oleh fitur sintaksis C ++, sama seperti struktur tabel dalam html memiliki tempat yang tidak dapat dengan mudah diisi oleh divs. Ketika Dykstra menulis kemudian tentang goto, dia tidak berniat untuk memulai agama dan benar-benar hanya berdebat menentang menggunakannya sebagai kludge untuk menebus kode yang dirancang dengan buruk.

Akan lebih baik jika proyek GNU akan menambahkan keluarga printf ke ekstensi g ++ mereka.

MMACD
sumber
1

Printf sebenarnya cukup bagus untuk digunakan jika ukuran penting. Berarti jika Anda menjalankan program di mana masalah memori, maka printf sebenarnya adalah solusi yang sangat bagus dan di bawah penilai. Pada dasarnya Cout menggeser bit untuk memberi ruang bagi string, sementara printf hanya mengambil semacam parameter dan mencetaknya ke layar. Jika Anda mengkompilasi program hello world yang sederhana, printf akan dapat mengkompilasinya dalam waktu kurang dari 60.000 bit dibandingkan dengan cout, akan membutuhkan lebih dari 1 juta bit untuk dikompilasi.

Untuk situasi Anda, id sarankan menggunakan cout hanya karena jauh lebih nyaman untuk digunakan. Meskipun, saya berpendapat bahwa printf adalah sesuatu yang baik untuk diketahui.

Howard Howard
sumber
1

printfmenerima sejumlah variabel argumen. Mereka hanya dapat memiliki tipe Data Lama Biasa (POD). Kode yang meneruskan apa pun selain POD printfhanya mengkompilasi karena kompilator menganggap Anda mendapatkan format yang benar. %sberarti bahwa argumen masing-masing seharusnya menjadi penunjuk ke char. Dalam kasus Anda itu std::stringbukan const char*. printftidak mengetahuinya karena tipe argumen hilang dan seharusnya dikembalikan dari parameter format. Ketika mengubah std::stringargumen itu menjadi const char*pointer yang dihasilkan akan menunjuk ke beberapa wilayah memori yang tidak relevan alih-alih string C yang Anda inginkan. Karena alasan itu, kode Anda mencetak omong kosong.

Meskipun printfmerupakan pilihan yang sangat baik untuk mencetak teks yang diformat , (terutama jika Anda bermaksud memiliki padding), itu bisa berbahaya jika Anda belum mengaktifkan peringatan kompiler. Selalu aktifkan peringatan karena kesalahan seperti ini mudah dihindari. Tidak ada alasan untuk menggunakan std::coutmekanisme canggung jika printfkeluarga dapat melakukan tugas yang sama dengan cara yang jauh lebih cepat dan lebih cantik. Pastikan Anda telah mengaktifkan semua peringatan ( -Wall -Wextra) dan Anda akan baik-baik saja. Jika Anda menggunakan printfimplementasi kustom Anda sendiri, Anda harus mendeklarasikannya dengan __attribute__mekanisme yang memungkinkan kompiler untuk memeriksa string format terhadap parameter yang disediakan .

Dubuk
sumber