ทำไมสิ่งนี้ถึงเกิดขึ้น?
สิ่งนี้มีส่วนเกี่ยวข้องเพียงเล็กน้อยกับอินพุตที่คุณให้ไว้ แต่เป็นการstd::getline()
จัดแสดงพฤติกรรมเริ่มต้น เมื่อคุณป้อนข้อมูลสำหรับชื่อ ( std::cin >> name
) คุณไม่เพียง แต่ส่งอักขระต่อไปนี้เท่านั้น แต่ยังมีการเพิ่มบรรทัดใหม่โดยนัยในสตรีมด้วย:
"John\n"
บรรทัดใหม่จะต่อท้ายข้อมูลที่คุณป้อนเสมอเมื่อคุณเลือกEnterหรือReturnเมื่อส่งจากเทอร์มินัล นอกจากนี้ยังใช้ในไฟล์เพื่อเลื่อนไปยังบรรทัดถัดไป การขึ้นบรรทัดใหม่จะถูกทิ้งไว้ในบัฟเฟอร์หลังจากการแยกข้อมูลไปname
จนถึงการดำเนินการ I / O ถัดไปซึ่งอาจถูกทิ้งหรือใช้ไป เมื่อกระแสการควบคุมมาถึงstd::getline()
บรรทัดใหม่จะถูกละทิ้ง แต่อินพุตจะหยุดทันที สาเหตุที่เกิดขึ้นเนื่องจากฟังก์ชันเริ่มต้นของฟังก์ชันนี้กำหนดว่าควร (พยายามอ่านบรรทัดและหยุดเมื่อพบขึ้นบรรทัดใหม่)
เนื่องจากบรรทัดใหม่นำหน้านี้ขัดขวางฟังก์ชันการทำงานที่คาดไว้ของโปรแกรมของคุณจึงต้องข้ามไปโดยไม่สนใจอย่างใด ทางเลือกหนึ่งคือการโทรstd::cin.ignore()
หลังจากการสกัดครั้งแรก มันจะทิ้งอักขระที่มีอยู่ถัดไปเพื่อไม่ให้ขึ้นบรรทัดใหม่อีกต่อไป
std::getline(std::cin.ignore(), state)
คำอธิบายเชิงลึก:
นี่คือการโอเวอร์โหลดstd::getline()
ที่คุณเรียกว่า:
template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
std::basic_string<charT>& str )
charT
เกินพิกัดของฟังก์ชั่นนี้ก็จะใช้เวลาของตัวคั่นประเภท อักขระตัวคั่นคืออักขระที่แสดงขอบเขตระหว่างลำดับของอินพุต การโอเวอร์โหลดโดยเฉพาะนี้ตั้งค่าตัวคั่นเป็นอักขระขึ้นบรรทัดใหม่input.widen('\n')
ตามค่าเริ่มต้นเนื่องจากไม่มีการระบุ
ต่อไปนี้เป็นเงื่อนไขบางประการที่จะstd::getline()
ยุติการป้อนข้อมูล:
- หากสตรีมดึงจำนวนอักขระสูงสุด a
std::basic_string<charT>
สามารถเก็บได้
- หากพบอักขระ end-of-file (EOF)
- หากพบตัวคั่น
เงื่อนไขที่สามคือสิ่งที่เรากำลังเผชิญอยู่ ข้อมูลที่คุณป้อนเข้าstate
จะแสดงด้วยเหตุนี้:
"John\nNew Hampshire"
^
|
next_pointer
ที่next_pointer
เป็นตัวละครต่อไปที่จะแยกวิเคราะห์ เนื่องจากอักขระที่จัดเก็บในตำแหน่งถัดไปในลำดับการป้อนข้อมูลเป็นตัวคั่นจึงstd::getline()
จะละทิ้งอักขระนั้นอย่างเงียบ ๆ เพิ่มnext_pointer
ไปยังอักขระที่มีอยู่ถัดไปและหยุดการป้อนข้อมูล ซึ่งหมายความว่าอักขระที่เหลือที่คุณระบุยังคงอยู่ในบัฟเฟอร์สำหรับการดำเนินการ I / O ถัดไป คุณจะสังเกตเห็นว่าหากคุณทำการอ่านอีกครั้งจากบรรทัดเข้าไปการstate
แยกของคุณจะให้ผลลัพธ์ที่ถูกต้องเป็นคำเรียกสุดท้ายที่จะstd::getline()
ละทิ้งตัวคั่น
คุณอาจสังเกตเห็นว่าโดยทั่วไปแล้วคุณจะไม่ประสบปัญหานี้เมื่อแยกข้อมูลด้วยตัวดำเนินการป้อนข้อมูลที่จัดรูปแบบ ( operator>>()
) เนื่องจากอินพุตสตรีมใช้ช่องว่างเป็นตัวคั่นสำหรับอินพุตและมีstd::skipws
1 ตัวปรับแต่งตามค่าเริ่มต้น สตรีมจะละทิ้งช่องว่างชั้นนำจากสตรีมเมื่อเริ่มดำเนินการป้อนข้อมูลที่จัดรูปแบบ 2
ซึ่งแตกต่างจากตัวดำเนินการป้อนข้อมูลที่จัดรูปแบบstd::getline()
คือฟังก์ชันอินพุตที่ไม่ได้จัดรูปแบบ และฟังก์ชันการป้อนข้อมูลที่ไม่ได้จัดรูปแบบทั้งหมดมีรหัสต่อไปนี้เหมือนกัน:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
ด้านบนเป็นวัตถุยามซึ่งสร้างอินสแตนซ์ในฟังก์ชัน I / O ที่จัดรูปแบบ / ไม่ได้จัดรูปแบบทั้งหมดในการใช้งาน C ++ มาตรฐาน อ็อบเจ็กต์ Sentry ใช้สำหรับเตรียมสตรีมสำหรับ I / O และพิจารณาว่าอยู่ในสถานะล้มเหลวหรือไม่ คุณจะพบว่าในฟอร์แมตtrue
ฟังก์ชั่นการป้อนข้อมูลอาร์กิวเมนต์ที่สองที่จะคอนสตรัคยามคือ อาร์กิวเมนต์นั้นหมายความว่าช่องว่างนำหน้าจะไม่ถูกละทิ้งจากจุดเริ่มต้นของลำดับการป้อนข้อมูล นี่คือคำพูดที่เกี่ยวข้องจากมาตรฐาน [§27.7.2.1.3 / 2]:
explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
[... ] ถ้าnoskipws
เป็นศูนย์และis.flags() & ios_base::skipws
ไม่ใช่ศูนย์ฟังก์ชันจะแยกและละทิ้งอักขระแต่ละตัวตราบเท่าที่อักขระอินพุตถัดไปที่มีc
อยู่เป็นอักขระช่องว่าง [... ]
เนื่องจากเงื่อนไขข้างต้นเป็นเท็จวัตถุยามจะไม่ทิ้งช่องว่าง เหตุผลที่noskipws
กำหนดtrue
โดยฟังก์ชันนี้เนื่องจากประเด็นstd::getline()
คือการอ่านอักขระดิบที่ไม่ได้จัดรูปแบบลงในstd::basic_string<charT>
วัตถุ
การแก้ไขปัญหา:
std::getline()
ไม่มีทางที่จะหยุดพฤติกรรมนี้ไม่ได้ สิ่งที่คุณต้องทำคือทิ้งบรรทัดใหม่ด้วยตัวคุณเองก่อนที่จะstd::getline()
รัน (แต่ให้ทำหลังจากการแยกรูปแบบ) สามารถทำได้โดยใช้ignore()
เพื่อทิ้งอินพุตที่เหลือจนกว่าเราจะถึงบรรทัดใหม่:
if (std::cin >> name &&
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
std::getline(std::cin, state))
{ ... }
คุณจะต้องรวมถึงการใช้<limits>
เป็นฟังก์ชันที่ละทิ้งอักขระตามจำนวนที่ระบุจนกว่าจะพบตัวคั่นหรือถึงจุดสิ้นสุดของสตรีม ( และจะทิ้งตัวคั่นด้วยหากพบ) ฟังก์ชันส่งกลับจำนวนมากที่สุดของตัวละครที่กระแสสามารถยอมรับstd::numeric_limits
std::basic_istream<...>::ignore()
ignore()
max()
อีกวิธีหนึ่งในการละทิ้งช่องว่างคือการใช้std::ws
ฟังก์ชันซึ่งเป็นเครื่องมือจัดการที่ออกแบบมาเพื่อแยกและทิ้งช่องว่างชั้นนำตั้งแต่จุดเริ่มต้นของอินพุตสตรีม:
if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }
อะไรคือความแตกต่าง?
ความแตกต่างคือignore(std::streamsize count = 1, int_type delim = Traits::eof())
3ตัวละครจะละทิ้งอักขระโดยไม่เลือกปฏิบัติจนกว่าจะละทิ้งcount
อักขระค้นหาตัวคั่น (ระบุโดยอาร์กิวเมนต์ที่สองdelim
) หรือถึงจุดสิ้นสุดของสตรีม std::ws
ใช้สำหรับการละทิ้งอักขระเว้นวรรคจากจุดเริ่มต้นของสตรีมเท่านั้น
std::ws
หากคุณกำลังผสมป้อนข้อมูลในรูปแบบที่มีการป้อนข้อมูลที่ยังไม่ฟอร์แมตและคุณจำเป็นต้องทิ้งช่องว่างที่เหลือการใช้งาน ignore()
มิฉะนั้นถ้าคุณจำเป็นต้องล้างออกป้อนไม่ถูกต้องโดยไม่คำนึงถึงว่ามันคืออะไรการใช้งาน ในตัวอย่างของเราเราจำเป็นต้องล้างช่องว่างเนื่องจากสตรีมใช้อินพุตของคุณ"John"
สำหรับname
ตัวแปร สิ่งที่เหลืออยู่คือตัวละครขึ้นบรรทัดใหม่
1: std::skipws
เป็นตัวควบคุมที่บอกให้สตรีมอินพุตทิ้งช่องว่างชั้นนำเมื่อดำเนินการป้อนข้อมูลที่จัดรูปแบบ สิ่งนี้สามารถปิดได้ด้วยstd::noskipws
หุ่นยนต์
2: อินพุตสตรีมถือว่าอักขระบางตัวเป็นช่องว่างโดยค่าเริ่มต้นเช่นอักขระช่องว่างอักขระขึ้นบรรทัดใหม่ฟีดแบบฟอร์มการส่งคืนค่าขนส่งเป็นต้น
3: นี่คือลายเซ็นของstd::basic_istream<...>::ignore()
. คุณสามารถเรียกใช้โดยไม่มีอาร์กิวเมนต์เพื่อละทิ้งอักขระเดี่ยวจากสตรีมอาร์กิวเมนต์หนึ่งเพื่อทิ้งอักขระจำนวนหนึ่งหรือสองอาร์กิวเมนต์เพื่อทิ้งcount
อักขระหรือจนกว่าจะถึงdelim
แล้วแต่ว่าข้อใดจะเกิดขึ้นก่อน โดยปกติคุณจะใช้std::numeric_limits<std::streamsize>::max()
เป็นค่าของcount
ถ้าคุณไม่ทราบว่ามีอักขระจำนวนเท่าใดก่อนตัวคั่น แต่คุณก็ยังต้องการทิ้งอยู่ดี
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
ควรทำงานตามที่คาดไว้ (นอกเหนือจากคำตอบด้านล่าง).