printf with std :: string?


157

ความเข้าใจของฉันคือว่าstringเป็นสมาชิกของstdnamespace ดังนั้นทำไมต่อไปนี้เกิดขึ้น

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

ป้อนคำอธิบายรูปภาพที่นี่

แต่ละครั้งที่โปรแกรมทำงานmyStringพิมพ์สตริงสุ่มที่ดูเหมือนว่า 3 ตัวอักษรเช่นในเอาต์พุตด้านบน


8
เพียงเพื่อให้คุณรู้ว่าผู้คนมากมายวิจารณ์หนังสือเล่มนี้ ซึ่งฉันสามารถเข้าใจได้เนื่องจากมีการเขียนโปรแกรมเชิงวัตถุไม่มากนัก แต่ฉันไม่คิดว่ามันจะแย่เท่าที่คนเรียกร้อง
Jesse Good

ouf! มันดีที่จะระลึกถึงสิ่งนี้ไว้ในใจในขณะที่ฉันเดินผ่านหนังสือ ฉันแน่ใจว่ามันจะไม่เป็นหนังสือ C ++ เล่มเดียวที่ฉันจะอ่านในช่วงปีหน้าดังนั้นฉันหวังว่ามันจะไม่สร้างความ
เสียหาย

การใช้คำเตือนคอมไพเลอร์สูงสุดจะตอบคำถามของคุณ - เมื่อรวบรวมด้วย gcc MSVC จัดการเรื่องนี้อย่างไร - ฉันไม่รู้
Peter VARGA

คำตอบ:


237

มันรวบรวมเพราะprintfไม่ได้พิมพ์ปลอดภัยเพราะใช้ข้อโต้แย้งตัวแปรในความรู้สึก C 1 printfไม่มีตัวเลือกสำหรับstd::stringเฉพาะสตริง C-style การใช้สิ่งอื่นแทนสิ่งที่คาดหวังจะไม่ให้ผลลัพธ์ที่คุณต้องการ มันเป็นพฤติกรรมที่ไม่ได้กำหนดจริงๆดังนั้นทุกอย่างจะเกิดขึ้น

วิธีที่ง่ายที่สุดในการแก้ไขปัญหานี้เนื่องจากคุณใช้ C ++ คือการพิมพ์ตามปกติด้วยstd::coutเนื่องจากstd::stringรองรับการทำงานผ่านการใช้งานเกินพิกัด:

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

หากด้วยเหตุผลบางอย่างคุณต้องแยกสตริง C-style ออกคุณสามารถใช้c_str()วิธีการstd::stringเพื่อรับconst char *ค่าที่ยกเลิกด้วยค่า Null ใช้ตัวอย่างของคุณ:

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

ถ้าคุณต้องการฟังก์ชั่นที่ชอบprintfแต่พิมพ์ safe ให้ดูเทมเพลต variadic (C ++ 11, รองรับกับคอมไพเลอร์หลัก ๆ ทุกตัวในฐานะ MSVC12) คุณสามารถค้นหาตัวอย่างของหนึ่งที่นี่ ไม่มีอะไรที่ฉันรู้ว่าการดำเนินการเช่นนั้นในห้องสมุดมาตรฐาน แต่อาจจะมีใน Boost boost::formatเฉพาะ


[1]: ซึ่งหมายความว่าคุณสามารถส่งผ่านจำนวนอาร์กิวเมนต์ใด ๆ ได้ แต่ฟังก์ชั่นนั้นขึ้นอยู่กับคุณที่จะบอกจำนวนและชนิดของอาร์กิวเมนต์เหล่านั้น ในกรณีที่printfหมายถึงว่าสตริงที่มีข้อมูลที่เข้ารหัสประเภทเช่นความหมาย%d intหากคุณโกหกเกี่ยวกับประเภทหรือหมายเลขฟังก์ชั่นนี้ไม่มีวิธีการรู้มาตรฐานแม้ว่าคอมไพเลอร์บางตัวมีความสามารถในการตรวจสอบและให้คำเตือนเมื่อคุณนอน


@MooingDuck จุดดี มันอยู่ในคำตอบของ Jerry แต่เป็นคำตอบที่ได้รับการยอมรับนี่คือสิ่งที่ผู้คนเห็นและพวกเขาอาจจากไปก่อนที่จะเห็นคนอื่น ฉันได้เพิ่มตัวเลือกนั้นเพื่อให้เป็นโซลูชั่นแรกที่เห็นและแนะนำ
คริส

43

กรุณาอย่าใช้ printf("%s", your_string.c_str());

ใช้cout << your_string;แทน สั้นง่ายและปลอดภัย ในความเป็นจริงเมื่อคุณเขียน C ++ โดยทั่วไปคุณต้องการหลีกเลี่ยงprintfอย่างสิ้นเชิง - มันเป็นของเหลือจาก C ที่ไม่ค่อยจำเป็นหรือมีประโยชน์ใน C ++

สำหรับเหตุผลที่คุณควรใช้coutแทนprintfเหตุผลมีมากมาย นี่คือตัวอย่างบางส่วนที่ชัดเจนที่สุด:

  1. ดังที่คำถามแสดงว่าprintfไม่ปลอดภัยสำหรับประเภท หากประเภทที่คุณส่งผ่านแตกต่างจากที่ระบุในตัวระบุการแปลงprintfจะพยายามใช้สิ่งที่พบในสแต็กราวกับว่าเป็นประเภทที่ระบุซึ่งให้พฤติกรรมที่ไม่ได้กำหนด คอมไพเลอร์บางคนสามารถเตือนเกี่ยวกับเรื่องนี้ในบางกรณี แต่คอมไพเลอร์บางตัวไม่สามารถ / ไม่ได้เลยและไม่มีใครสามารถทำได้ภายใต้สถานการณ์ทั้งหมด
  2. printfไม่สามารถขยายได้ คุณสามารถส่งผ่านประเภทดั้งเดิมไปเท่านั้น ชุดของตัวระบุการแปลงที่เข้าใจมีการกำหนดค่าตายตัวในการนำไปใช้และไม่มีวิธีใดที่คุณจะสามารถเพิ่ม / อื่น ๆ ได้ ภาษา C ++ ที่เป็นที่นิยมส่วนใหญ่ควรใช้ประเภทเหล่านี้เป็นหลักในการใช้ชนิดที่มุ่งเน้นไปที่ปัญหาที่กำลังแก้ไข
  3. มันทำให้การจัดรูปแบบที่เหมาะสมนั้นยากยิ่งขึ้น สำหรับตัวอย่างที่ชัดเจนเมื่อคุณพิมพ์ตัวเลขเพื่อให้คนอ่านโดยทั่วไปคุณต้องการแทรกตัวคั่นหลายพันหลักทุกสองสามหลัก จำนวนที่แน่นอนของตัวเลขและตัวอักษรที่ใช้เป็นตัวคั่นจะแตกต่างกันไป แต่coutมีที่ครอบคลุมเช่นกัน ตัวอย่างเช่น:

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

    โลแคลที่ไม่ระบุชื่อ ("") เลือกโลแคลตามการกำหนดค่าของผู้ใช้ ดังนั้นในเครื่องของฉัน (กำหนดค่าสำหรับภาษาอังกฤษสหรัฐ) 123,456.78พิมพ์นี้ออกมาเป็น สำหรับคนที่มีเครื่องคอมพิวเตอร์ของพวกเขาการกำหนดค่าสำหรับ (พูด) 123.456,78เยอรมันก็จะพิมพ์ออกมาบางอย่างเช่น สำหรับคนที่กำหนดค่าไว้สำหรับอินเดียมันจะพิมพ์ออกมาเป็น1,23,456.78(และแน่นอนมีคนอื่นอีกมากมาย) ด้วยความที่ผมได้รับสิ่งหนึ่งผล:printf 123456.78มันสอดคล้องกัน แต่ก็ผิดตลอดสำหรับทุกคนทุกที่ โดยพื้นฐานแล้ววิธีเดียวที่จะหลีกเลี่ยงได้คือการจัดรูปแบบแยกต่างหากจากนั้นส่งผ่านผลลัพธ์เป็นสตริงไปprintfเนื่องจากprintfตัวมันเองจะทำงานไม่ถูกต้อง

  4. แม้ว่าจะมีขนาดค่อนข้างเล็ก แต่printfสตริงรูปแบบอาจไม่สามารถอ่านได้ แม้แต่ในหมู่โปรแกรมเมอร์ C ที่ใช้printfแทบทุกวันฉันเดาอย่างน้อย 99% จะต้องมองสิ่งต่าง ๆ ขึ้นเพื่อให้แน่ใจว่าสิ่งที่#อยู่ใน%#xวิธีการและวิธีการที่แตกต่างจากสิ่งที่#อยู่ใน%#fวิธีการ (และใช่พวกเขาหมายถึงสิ่งที่แตกต่างอย่างสิ้นเชิง )

11
@ TheDarkIn1978: #include <string>คุณอาจลืม VC ++ มีความแปลกประหลาดบางอย่างในส่วนหัวของมันที่จะช่วยให้คุณกำหนดสตริง แต่ไม่ส่งไปยังcoutโดยไม่รวม<string>ส่วนหัว
Jerry Coffin

28
@Jerry: เพียงแค่ต้องการชี้ให้เห็นว่าการใช้ printf นั้นเร็วกว่าการใช้ cout เมื่อต้องรับมือกับข้อมูลขนาดใหญ่ ดังนั้นโปรดอย่าพูดว่ามันไร้ประโยชน์: D
โปรแกรมเมอร์

7
@Programmer: ดูstackoverflow.com/questions/12044357/… . สรุป: เวลาส่วนใหญ่ที่coutช้ากว่านั้นเป็นเพราะคุณเคยใช้ในstd::endlที่ที่ไม่ควรทำ
Jerry Coffin

29
ความเย่อหยิ่งของผู้เชี่ยวชาญ C ++ โดยทั่วไป หาก printf มีอยู่ทำไมไม่ใช้มัน
kuroi neko

6
ตกลงขออภัยสำหรับความคิดเห็นเร็ว ถึงกระนั้น printf นั้นค่อนข้างมีประโยชน์สำหรับการดีบักและสตรีมแม้ว่าจะมีประสิทธิภาพมากกว่าอย่างมาก แต่ก็มีข้อเสียเปรียบที่โค้ดไม่ได้ให้ความคิดเกี่ยวกับเอาต์พุตจริง สำหรับผลลัพธ์ที่จัดรูปแบบแล้ว printf ยังคงเป็นทางเลือกที่เหมาะสมและเป็นเรื่องที่น่าละอายที่ทั้งสองระบบไม่สามารถร่วมมือกันได้ดีกว่า แค่ความเห็นของฉันแน่นอน
kuroi neko



1

เหตุผลหลักอาจเป็นไปได้ว่าสตริง C ++ เป็นโครงสร้างที่มีค่าความยาวปัจจุบันไม่เพียง แต่ที่อยู่ของลำดับของตัวอักษรที่ถูกยกเลิกด้วย 0 ไบต์ Printf และญาติของมันคาดว่าจะพบลำดับดังกล่าวไม่ใช่ struct และทำให้สับสนด้วยสตริง C ++

สำหรับตัวฉันเองฉันเชื่อว่า printf มีสถานที่ที่ไม่สามารถเติมเต็มได้อย่างง่ายดายด้วยคุณสมบัติทางไวยากรณ์ C ++ เช่นเดียวกับโครงสร้างตารางใน html มีสถานที่ที่ divs ไม่สามารถเติมได้ง่าย ดังที่ Dykstra เขียนในภายหลังเกี่ยวกับการข้ามไปเขาไม่ได้ตั้งใจที่จะเริ่มศาสนาและเป็นเพียงการโต้เถียงกับการใช้มันเป็นกากบาทเพื่อชดเชยโค้ดที่ออกแบบมาไม่ดี

มันจะค่อนข้างดีถ้าโครงการ GNU จะเพิ่มตระกูล printf ในส่วนขยาย g ++ ของพวกเขา


1

Printf ใช้งานได้ดีจริง ๆ ถ้าเรื่องขนาด หมายความว่าถ้าคุณใช้โปรแกรมที่หน่วยความจำมีปัญหา printf นั้นเป็นวิธีแก้ปัญหาที่ดีมาก โดยทั่วไปแล้ว Cout จะเลื่อนบิตไปเพื่อให้มีที่ว่างสำหรับสตริงในขณะที่ printf ใช้พารามิเตอร์บางตัวและพิมพ์ไปที่หน้าจอ หากคุณต้องการคอมไพล์โปรแกรม Hello World อย่างง่าย printf จะสามารถรวบรวมได้ในเวลาน้อยกว่า 60, 000 บิตเมื่อเทียบกับ cout มันจะใช้เวลามากกว่า 1 ล้านบิตในการรวบรวม

สำหรับสถานการณ์ของคุณ id แนะนำให้ใช้ cout เพียงเพราะสะดวกในการใช้มากขึ้น แม้ว่าฉันจะยืนยันว่า printf เป็นสิ่งที่ดีที่จะรู้


1

printfยอมรับจำนวนตัวแปรของข้อโต้แย้ง สิ่งเหล่านั้นสามารถมีประเภทล้วนข้อมูลเก่า (POD) โค้ดที่ส่งผ่านสิ่งอื่นนอกจาก POD เพื่อprintfคอมไพล์เท่านั้นเพราะคอมไพเลอร์ถือว่าคุณได้รูปแบบที่ถูกต้อง หมายถึงว่าอาร์กิวเมนต์ที่เกี่ยวข้องควรจะเป็นตัวชี้ไปที่%s charในกรณีของคุณก็เป็นได้std::string ไม่ทราบเพราะประเภทอาร์กิวเมนต์หายไปและควรจะกู้คืนจากพารามิเตอร์รูปแบบ เมื่อเปลี่ยนอาร์กิวเมนต์นั้นเป็นตัวชี้ผลลัพธ์จะชี้ไปยังพื้นที่ที่ไม่เกี่ยวข้องของหน่วยความจำแทนสตริง C ที่คุณต้องการ ด้วยเหตุนี้รหัสของคุณจึงพิมพ์ออกซึ่งพูดพล่อยๆconst char*printfstd::stringconst char*

ในขณะที่printfเป็นตัวเลือกที่ยอดเยี่ยมสำหรับการพิมพ์ข้อความที่จัดรูปแบบ (โดยเฉพาะถ้าคุณตั้งใจจะมีช่องว่างภายใน) มันอาจเป็นอันตรายได้หากคุณไม่ได้เปิดใช้งานคำเตือนคอมไพเลอร์ เปิดใช้งานคำเตือนเสมอเพราะหลีกเลี่ยงข้อผิดพลาดเช่นนี้ ไม่มีเหตุผลที่จะใช้std::coutกลไกเงอะงะถ้าprintfครอบครัวสามารถทำงานเดียวกันได้เร็วขึ้นและดีขึ้น เพียงให้แน่ใจว่าคุณเปิดใช้งานการเตือนทั้งหมด ( -Wall -Wextra) และคุณจะดี ในกรณีที่คุณใช้การปรับใช้แบบกำหนดเองprintfคุณควรประกาศด้วย__attribute__กลไกที่ทำให้คอมไพเลอร์ตรวจสอบสตริงรูปแบบกับพารามิเตอร์ที่ให้ไว้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.