ทำไมเราถึงเรียก cin.clear () และ cin.ignore () หลังจากอ่านอินพุต


86

บทแนะนำ C ++ ของ Google Code Universityเคยมีรหัสนี้:

// Description: Illustrate the use of cin to get input
// and how to recover from errors.

#include <iostream>
using namespace std;

int main()
{
  int input_var = 0;
  // Enter the do while loop and stay there until either
  // a non-numeric is entered, or -1 is entered.  Note that
  // cin will accept any integer, 4, 40, 400, etc.
  do {
    cout << "Enter a number (-1 = quit): ";
    // The following line accepts input from the keyboard into
    // variable input_var.
    // cin returns false if an input operation fails, that is, if
    // something other than an int (the type of input_var) is entered.
    if (!(cin >> input_var)) {
      cout << "Please enter numbers only." << endl;
      cin.clear();
      cin.ignore(10000,'\n');
    }
    if (input_var != -1) {
      cout << "You entered " << input_var << endl;
    }
  }
  while (input_var != -1);
  cout << "All done." << endl;

  return 0;
}

ความสำคัญของอะไรcin.clear()และcin.ignore()? ทำไมพารามิเตอร์10000และจึง\nจำเป็น?


2
นี่เป็นโพสต์ที่ได้รับการโหวตมากที่สุดตลอดกาลของฉัน ฉันต้องพีคในโรงเรียนมัธยม
JacKeown

คำตอบ:


104

cin.clear()ล้างธงข้อผิดพลาดในcin(ดังนั้นในอนาคตที่ I / O การดำเนินงานจะทำงานได้อย่างถูกต้อง) แล้วcin.ignore(10000, '\n')ข้ามไปขึ้นบรรทัดถัดไป (ที่จะไม่สนใจสิ่งอื่นในบรรทัดเดียวกันเป็นจำนวนไม่ใช่เพื่อที่จะไม่ก่อให้เกิดความล้มเหลวในการแยกวิเคราะห์อื่น) . มันจะข้ามได้ไม่เกิน 10,000 อักขระดังนั้นโค้ดจึงถือว่าผู้ใช้จะไม่ใส่บรรทัดที่ยาวมากและไม่ถูกต้อง


59
+1. ฉันต้องการเพิ่มว่าแทนที่จะไม่สนใจถึง 10000 cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');ตัวอักษรมันจะดีกว่าที่จะใช้
Dennis

31
หากคุณต้องการใช้std::numeric_limitsให้แน่ใจว่า#include <limits>ได้
gbmhunter

1
ใครช่วยอธิบายไวยากรณ์ได้std::numeric_limits<std::streamsize>::max()ไหม
Minh Tran

4
@Minh Tran: std :: streamize ถูกเซ็นชื่อแบบอินทิกรัลซึ่งให้จำนวนอักขระ i / o ที่ถ่ายโอนหรือขนาดของบัฟเฟอร์ I / O ที่นี่โดยใช้คลาสเทมเพลต "numeric_limits" เราต้องการทราบขีด จำกัด สูงสุดของบัฟเฟอร์ I / O หรืออักขระที่ถ่ายโอน
Pankaj

1
@ มินห์ทราน: การแก้ไขเพียงเล็กน้อย "ขนาดสตรีม" ไม่ใช่คลาส แต่เป็นเพียงอินทิกรัล เราสามารถรับลิมิตของคนอื่นได้เช่นกัน int, ถ่าน ฯลฯ .. โปรดตรวจสอบที่ [link] ( en.cppreference.com/w/cpp/types/numeric_limits )
Pankaj

47

คุณป้อนไฟล์

if (!(cin >> input_var))

คำสั่งหากข้อผิดพลาดเกิดขึ้นเมื่อรับอินพุตจาก cin หากข้อผิดพลาดเกิดขึ้นการตั้งค่าสถานะข้อผิดพลาดและความพยายามในการรับอินพุตในอนาคตจะล้มเหลว นั่นเป็นเหตุผลที่คุณต้องการ

cin.clear();

เพื่อกำจัดแฟล็กข้อผิดพลาด นอกจากนี้อินพุตที่ล้มเหลวจะอยู่ในสิ่งที่ฉันคิดว่าเป็นบัฟเฟอร์บางประเภท เมื่อคุณพยายามรับอินพุตอีกครั้งมันจะอ่านอินพุตเดียวกันในบัฟเฟอร์และจะล้มเหลวอีกครั้ง นั่นเป็นเหตุผลที่คุณต้องการ

cin.ignore(10000,'\n');

ใช้อักขระ 10,000 ตัวจากบัฟเฟอร์ แต่จะหยุดหากพบการขึ้นบรรทัดใหม่ (\ n) 10,000 เป็นเพียงค่าขนาดใหญ่ทั่วไป


11
เพียงแค่เพิ่มค่าเริ่มต้นสำหรับการละเว้นคือการข้ามอักขระตัวเดียวดังนั้นคุณต้องมีตัวเลขที่มากขึ้นเพื่อข้ามทั้งบรรทัด
Bo Persson

24

ทำไมเราถึงใช้:

1) cin.ignore

2) cin.clear

เหรอ?

เพียงแค่:

1) เพื่อละเว้น (แยกและทิ้ง) ค่าที่เราไม่ต้องการในสตรีม

2) เพื่อล้างสถานะภายในของสตรีม หลังจากใช้ cin.clear internal state ถูกตั้งค่าอีกครั้งกลับไปที่ goodbit ซึ่งหมายความว่าไม่มี 'ข้อผิดพลาด'

เวอร์ชันยาว:

หากมีบางสิ่งอยู่ใน 'สตรีม' (cin) จะต้องถูกนำมาจากที่นั่น โดย 'taken' เราหมายถึง 'used', 'removed', 'extract' จากสตรีม กระแสมีการไหล ข้อมูลไหลบน cin เหมือนน้ำในสตรีม คุณไม่สามารถหยุดการไหลของน้ำได้)

ดูตัวอย่าง:

string name; //line 1
cout << "Give me your name and surname:"<<endl;//line 2
cin >> name;//line 3
int age;//line 4
cout << "Give me your age:" <<endl;//line 5
cin >> age;//line 6

จะเกิดอะไรขึ้นถ้าผู้ใช้ตอบ: "Arkadiusz Wlodarczyk" สำหรับคำถามแรก

เรียกใช้โปรแกรมเพื่อดูตัวคุณเอง

คุณจะเห็นบนคอนโซล "Arkadiusz" แต่โปรแกรมจะไม่ถาม "อายุ" จากคุณ มันจะเสร็จทันทีหลังจากพิมพ์ "Arkadiusz"

และ "Wlodarczyk" ไม่ปรากฏ ดูเหมือนว่ามันจะหายไป (?) *

เกิดอะไรขึ้น? ;-)

เนื่องจากมีช่องว่างระหว่าง "Arkadiusz" และ "Wlodarczyk".

อักขระ "ช่องว่าง" ระหว่างชื่อและนามสกุลเป็นสัญญาณสำหรับคอมพิวเตอร์ว่ามีตัวแปรสองตัวที่รอการแยกออกจากสตรีม "อินพุต"

คอมพิวเตอร์คิดว่าคุณกำลังคาดว่าจะส่งไปยังอินพุตมากกว่าหนึ่งตัวแปร เครื่องหมาย "เว้นวรรค" นั้นเป็นสัญญาณให้เขาตีความแบบนั้น

ดังนั้นคอมพิวเตอร์จึงกำหนด "Arkadiusz" เป็น "name" (2) และเนื่องจากคุณใส่มากกว่าหนึ่งสตริงในสตรีม (อินพุต) คอมพิวเตอร์จะพยายามกำหนดค่า "Wlodarczyk" ให้กับตัวแปร 'age' (!) ผู้ใช้จะไม่มีโอกาสใส่อะไรใน 'cin' ในบรรทัดที่ 6 เนื่องจากคำสั่งนั้นได้ดำเนินการไปแล้ว (!) ทำไม? เพราะยังมีบางอย่างเหลืออยู่ในสตรีม และอย่างที่บอกไปก่อนหน้านี้สตรีมอยู่ในกระแสดังนั้นทุกอย่างจะต้องถูกลบออกโดยเร็วที่สุด และความเป็นไปได้เกิดขึ้นเมื่อคอมพิวเตอร์เห็นคำสั่งอายุ >>

คอมพิวเตอร์ไม่รู้ว่าคุณสร้างตัวแปรที่เก็บอายุของใครบางคน (บรรทัดที่ 4) "อายุ" เป็นเพียงป้ายกำกับ สำหรับ 'อายุ' ของคอมพิวเตอร์สามารถเรียกได้เช่นกันว่า 'afsfasgfsagasggas' และจะเหมือนกัน สำหรับเขามันเป็นเพียงตัวแปรที่เขาจะพยายามกำหนด "Wlodarczyk" ให้เพราะคุณสั่ง / สั่งให้คอมพิวเตอร์ทำในบรรทัดที่ (6)

มันผิดที่จะทำ แต่เดี๋ยวก่อนคุณเป็นคนทำ! มันเป็นความผิดของคุณ! อาจเป็นผู้ใช้ แต่ก็ยัง ...


เอาล่ะ แต่จะแก้ไขอย่างไร?!

มาลองเล่นกับตัวอย่างนั้นสักเล็กน้อยก่อนที่เราจะแก้ไขอย่างเหมาะสมเพื่อเรียนรู้สิ่งที่น่าสนใจเพิ่มเติม :-)

ฉันชอบที่จะหาแนวทางที่เราเข้าใจสิ่งต่างๆ แก้ไขบางอย่างโดยที่เราไม่รู้ว่ามันไม่ให้ความพึงพอใจได้อย่างไร? :)

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate(); //new line is here :-)

หลังจากเรียกใช้โค้ดด้านบนคุณจะสังเกตเห็นว่าสถานะของสตรีม (cin) ของคุณมีค่าเท่ากับ 4 (บรรทัดที่ 7) ซึ่งหมายความว่าสถานะภายในจะไม่เท่ากับ goodbit อีกต่อไป มีบางอย่างเกิดความสับสน มันค่อนข้างชัดเจนใช่มั้ย? คุณพยายามกำหนดค่าประเภทสตริง ("Wlodarczyk") ให้กับตัวแปรประเภท int 'age' ประเภทไม่ตรงกัน ถึงเวลาแจ้งว่ามีบางอย่างผิดปกติ และคอมพิวเตอร์ทำได้โดยการเปลี่ยนสถานะภายในของสตรีม มันเหมือนกับ: "คุณ f **** ขึ้นมาโปรดแก้ไขฉันด้วยฉันแจ้งให้คุณทราบ 'กรุณา' ;-)"

คุณไม่สามารถใช้ 'cin' (สตรีม) ได้อีกต่อไป มันติด. เช่นถ้าคุณวางท่อนไม้ขนาดใหญ่บนลำธารน้ำ คุณต้องแก้ไขก่อนจึงจะใช้งานได้ ไม่สามารถรับข้อมูล (น้ำ) จากสตรีม (cin) ได้อีกต่อไปเนื่องจากบันทึกของไม้ (สถานะภายใน) ไม่อนุญาตให้คุณทำเช่นนั้น

อ้อถ้ามีสิ่งกีดขวาง (ท่อนไม้) เราก็เอามันออกโดยใช้เครื่องมือที่ทำขึ้นมาได้ไหม?

ใช่

สถานะภายในของ cin ตั้งค่าเป็น 4 เหมือนสัญญาณเตือนที่ส่งเสียงโหยหวนและส่งเสียงดัง

cin.clear ล้างสถานะกลับสู่ปกติ (goodbit) เหมือนกับว่าคุณมาแล้วปิดเสียงปลุก คุณเพียงแค่ถอดมันออก คุณรู้ว่ามีบางอย่างเกิดขึ้นดังนั้นคุณจึงพูดว่า: "ไม่เป็นไรถ้าจะหยุดส่งเสียงฉันรู้ว่ามีบางอย่างผิดปกติแล้วก็หุบปากซะ"

เอาล่ะมาทำ! มาใช้ cin.clear () กัน

เรียกใช้โค้ดด้านล่างโดยใช้ "Arkadiusz Wlodarczyk" เป็นอินพุตแรก:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl; 
cin.clear(); //new line is here :-)
cout << cin.rdstate()<< endl;  //new line is here :-)

เราสามารถเห็นได้อย่างแน่นอนหลังจากรันโค้ดด้านบนว่าสถานะเท่ากับ goodbit

ดีมากเพื่อให้ปัญหาได้รับการแก้ไข?

เรียกใช้โค้ดด้านล่างโดยใช้ "Arkadiusz Wlodarczyk" เป็นอินพุตแรก:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl;; 
cin.clear(); 
cout << cin.rdstate() << endl; 
cin >> age;//new line is here :-)

แม้ว่าสถานะจะถูกตั้งค่าเป็น goodbit หลังจากบรรทัดที่ 9 ผู้ใช้จะไม่ถูกถามถึง "อายุ" โปรแกรมจะหยุด

ทำไม?!

โอ้มนุษย์ ... คุณเพิ่งดับสัญญาณเตือนแล้วท่อนไม้ในน้ำล่ะ * กลับไปที่ข้อความที่เราพูดถึง "Wlodarczyk" ว่ามันหายไปได้อย่างไร

คุณต้องนำไม้ "Wlodarczyk" ออกจากลำธาร การปิดนาฬิกาปลุกไม่ช่วยแก้ปัญหา แต่อย่างใด คุณแค่ปิดเสียงและคุณคิดว่าปัญหาจะหมดไป? ;)

ถึงเวลาแล้วสำหรับเครื่องมืออื่น:

cin.ignore เปรียบได้กับรถบรรทุกพิเศษที่มีเชือกที่มาและเอาท่อนไม้ที่มีสตรีมติดอยู่ เป็นการเคลียร์ปัญหาที่ผู้ใช้โปรแกรมของคุณสร้างขึ้น

เราจะใช้มันก่อนที่เสียงปลุกจะดับลงได้ไหม?

ใช่:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;

"Wlodarczyk" จะถูกลบออกก่อนที่จะส่งเสียงดังในบรรทัดที่ 7

10,000 และ "\ n" คืออะไร

มันบอกว่าลบ 10,000 อักขระ (ในกรณี) จนกว่าจะพบ "\ n" (ENTER) BTW สามารถทำได้ดีกว่าโดยใช้ numeric_limits แต่ไม่ใช่หัวข้อของคำตอบนี้


สาเหตุหลักของปัญหาจึงหมดไปก่อนที่จะเกิดเสียงดัง ...

ทำไมเราต้อง 'ชัดเจน'?

จะเป็นอย่างไรหากมีคนถามคำถาม "ให้อายุของคุณ" ในบรรทัดที่ 6 เช่น "อายุยี่สิบปี" แทนที่จะเขียน 20

ประเภทไม่ตรงกันอีก คอมพิวเตอร์พยายามกำหนดสตริงให้กับ int และสัญญาณเตือนจะเริ่มขึ้น คุณไม่มีโอกาสแม้แต่จะตอบสนองต่อสถานการณ์เช่นนั้น cin.ignore จะไม่ช่วยคุณในกรณีเช่นนั้น

ดังนั้นเราต้องใช้ clear ในกรณีเช่นนั้น:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;
cin.clear();
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

แต่คุณควรล้างสถานะ 'ในกรณี' หรือไม่?

ไม่แน่นอน

หากมีสิ่งผิดปกติ (cin >> age;) คำแนะนำจะแจ้งให้คุณทราบโดยส่งคืนเท็จ

ดังนั้นเราจึงสามารถใช้คำสั่งเงื่อนไขเพื่อตรวจสอบว่าผู้ใช้ใส่ผิดประเภทในสตรีมหรือไม่

int age;
if (cin >> age) //it's gonna return false if types doesn't match
    cout << "You put integer";
else
    cout << "You bad boy! it was supposed to be int";

เอาล่ะเราสามารถแก้ไขปัญหาเริ่มต้นของเราได้เช่น:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

int age;
cout << "Give me your age:" << endl;
if (cin >> age)
  cout << "Your age is equal to:" << endl;
else
{
 cin.clear();
 cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
 cout << "Give me your age name as string I dare you";
 cin >> age;
}

แน่นอนว่าสิ่งนี้สามารถปรับปรุงได้โดยตัวอย่างเช่นการทำสิ่งที่คุณมีปัญหาโดยใช้ loop while

โบนัส:

คุณอาจจะสงสัย แล้วถ้าฉันอยากได้ชื่อและนามสกุลในบรรทัดเดียวกันจากผู้ใช้ล่ะ? เป็นไปได้ไหมที่จะใช้ cin ถ้า cin ตีความแต่ละค่าที่คั่นด้วย "space" เป็นตัวแปรต่างกัน

แน่นอนว่าคุณทำได้สองวิธี:

1)

string name, surname;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin >> surname;

cout << "Hello, " << name << " " << surname << endl;

2) หรือโดยใช้ฟังก์ชัน getline

getline(cin, nameOfStringVariable);

และนั่นคือวิธีการ:

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

ตัวเลือกที่สองอาจย้อนกลับคุณในกรณีที่คุณใช้หลังจากที่คุณใช้ 'cin' ก่อนหน้า getline

มาดูกันเลย:

ก)

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endl;

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

หากคุณใส่ "20" ตามอายุคุณจะไม่ถูกถามถึงชื่อและนามสกุล

แต่ถ้าคุณทำอย่างนั้น:

ข)

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endll

ทุกอย่างปกติดี.

อะไร?!

ทุกครั้งที่คุณใส่บางสิ่งลงในอินพุต (สตรีม) คุณจะปล่อยให้อักขระสีขาวสิ้นสุดซึ่งก็คือ ENTER ('\ n') คุณต้องป้อนค่าลงในคอนโซล ดังนั้นจะต้องเกิดขึ้นหากข้อมูลมาจากผู้ใช้

b) ลักษณะของ cin คือการละเว้นช่องว่างดังนั้นเมื่อคุณอ่านข้อมูลจาก cin อักขระขึ้นบรรทัดใหม่ '\ n' จะไม่สำคัญ มันถูกละเลย

ก) ฟังก์ชัน getline ทำให้บรรทัดทั้งหมดขึ้นบรรทัดใหม่ ('\ n') และเมื่อถ่านขึ้นบรรทัดใหม่เป็นสิ่งแรกที่ฟังก์ชัน getline จะได้รับ '\ n' และนั่นคือทั้งหมดที่จะได้รับ คุณแยกอักขระขึ้นบรรทัดใหม่ที่เหลือในสตรีมโดยผู้ใช้ที่ใส่ "20" ในสตรีมในบรรทัดที่ 3

ดังนั้นในการแก้ไขก็คือต้องเรียก cin.ignore (); ทุกครั้งที่คุณใช้ cin เพื่อรับค่าใด ๆ หากคุณจะใช้ getline () ในโปรแกรมของคุณ

ดังนั้นรหัสที่ถูกต้องจะเป็น:

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cin.ignore(); // it ignores just enter without arguments being sent. it's same as cin.ignore(1, '\n') 
cout << "Your age is" << age << endl;


string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

ฉันหวังว่าสตรีมจะชัดเจนมากขึ้นสำหรับคุณที่รู้

ได้โปรดปิดปากเงียบ! :-)


1

ใช้cin.ignore(1000,'\n')เพื่อล้างตัวอักษรทั้งหมดของก่อนหน้านี้cin.get()ในบัฟเฟอร์และจะเลือกหยุดเมื่อตรงกับ "\ n" หรือ1000 charsก่อน

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