Mengapa kita memanggil cin.clear () dan cin.ignore () setelah membaca masukan?

86

Tutorial C ++ Google Code University dulu memiliki kode ini:

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

Apa pentingnya cin.clear()dan cin.ignore()? Mengapa parameter 10000dan \ndiperlukan?

JacKeown
sumber
2
Ini adalah posting saya yang paling banyak mendapat suara positif sepanjang masa. Aku pasti sudah mencapai puncak di sekolah menengah.
JacKeown

Jawaban:

104

The cin.clear()membersihkan bendera kesalahan pada cin(sehingga masa depan bahwa saya / O operasi akan bekerja dengan benar), dan kemudian cin.ignore(10000, '\n')melompat ke baris baru berikutnya (untuk mengabaikan apa pun di baris yang sama sebagai non-nomor sehingga tidak menyebabkan kegagalan parse lain) . Ini hanya akan melewatkan hingga 10.000 karakter, jadi kodenya mengasumsikan pengguna tidak akan memasukkan baris yang sangat panjang dan tidak valid.

Jeremiah Willcock
sumber
59
+1. Saya ingin menambahkan bahwa daripada mengabaikan hingga 10.000 karakter, akan lebih baik jika digunakan cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');.
Dennis
31
Jika Anda ingin menggunakan std::numeric_limits, pastikan untuk #include <limits>.
gbmhunter
1
Bisakah seseorang menjelaskan sintaksnya std::numeric_limits<std::streamsize>::max()?
Minh Tran
4
@Minh Tran: std :: streamsize adalah integral bertanda yang memberikan jumlah karakter i / o yang ditransfer atau ukuran buffer I / O. Di sini, dengan menggunakan template kelas "numeric_limits" kita ingin mengetahui batas maksimal buffer I / O atau karakter yang ditransfer.
Pankaj
1
@ Minh Tran: hanya satu koreksi kecil "streamsize" bukanlah kelas, itu hanya tipe integral. Kita juga bisa mendapatkan batasan orang lain yaitu. int, char dll .. silahkan cek di [link] ( en.cppreference.com/w/cpp/types/numeric_limits )
Pankaj
47

Anda masuk ke

if (!(cin >> input_var))

pernyataan jika terjadi kesalahan saat mengambil input dari cin. Jika terjadi kesalahan, maka bendera kesalahan disetel dan upaya mendapatkan masukan di masa mendatang akan gagal. Itulah mengapa Anda membutuhkannya

cin.clear();

untuk menghilangkan bendera kesalahan. Juga, input yang gagal akan berada di tempat yang saya asumsikan adalah semacam buffer. Saat Anda mencoba mendapatkan masukan lagi, ia akan membaca masukan yang sama di buffer dan akan gagal lagi. Itulah mengapa Anda membutuhkannya

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

Ini mengambil 10.000 karakter dari buffer tetapi berhenti jika menemukan baris baru (\ n). 10000 hanyalah nilai besar generik.

penerbangan
sumber
11
Hanya menambahkan bahwa default untuk ignore adalah melewatkan satu karakter, jadi Anda memerlukan angka yang lebih besar untuk melewati seluruh baris.
Bo Persson
24

Mengapa kami menggunakan:

1) cin.ignore

2) cin.clear

?

Secara sederhana:

1) Untuk mengabaikan (mengekstrak dan membuang) nilai yang tidak kita inginkan di aliran

2) Untuk membersihkan aliran internal. Setelah menggunakan cin.clear keadaan internal diatur kembali ke goodbit, yang berarti tidak ada 'kesalahan'.

Versi panjang:

Jika sesuatu diletakkan di 'aliran' (cin) maka itu harus diambil dari sana. Yang kami maksud dengan 'diambil' adalah 'bekas', 'dihapus', 'diekstraksi' dari aliran. Aliran memiliki aliran. Data mengalir di cin seperti air yang mengalir. Anda tidak bisa menghentikan aliran air;)

Lihat contohnya:

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

Apa yang terjadi jika pengguna menjawab: "Arkadiusz Wlodarczyk" untuk pertanyaan pertama?

Jalankan programnya untuk melihat sendiri.

Anda akan melihat di konsol "Arkadiusz" tetapi program tidak akan menanyakan 'usia'. Ini hanya akan selesai segera setelah mencetak "Arkadiusz".

Dan "Wlodarczyk" tidak ditampilkan. Sepertinya jika sudah hilang (?) *

Apa yang terjadi? ;-)

Karena ada jarak antara "Arkadiusz" dan "Wlodarczyk".

Karakter “spasi” antara nama dan nama belakang merupakan tanda bagi komputer bahwa ada dua variabel yang menunggu untuk diekstraksi pada aliran 'input'.

Komputer berpikir bahwa Anda mengikat untuk mengirim input lebih dari satu variabel. Tanda "ruang" itu adalah tanda baginya untuk menafsirkannya seperti itu.

Jadi komputer menetapkan "Arkadiusz" ke 'nama' (2) dan karena Anda meletakkan lebih dari satu string pada aliran (input) komputer akan mencoba untuk menetapkan nilai "Wlodarczyk" ke variabel 'usia' (!). Pengguna tidak akan memiliki kesempatan untuk meletakkan apa pun di 'cin' di baris 6 karena instruksi itu sudah dijalankan (!). Mengapa? Karena masih ada sesuatu yang tersisa. Dan seperti yang saya katakan sebelumnya, aliran sedang dalam aliran sehingga semuanya harus dihapus darinya sesegera mungkin. Dan kemungkinan datang ketika komputer melihat instruksi cin >> umur;

Komputer tidak tahu bahwa Anda membuat variabel yang menyimpan usia seseorang (baris 4). 'usia' hanyalah sebuah label. Untuk komputer 'usia' bisa juga disebut: 'afsfasgfsagasggas' dan itu akan sama. Baginya itu hanya sebuah variabel yang akan dia coba tetapkan ke "Wlodarczyk" karena Anda memerintahkan / menginstruksikan komputer untuk melakukannya pada baris (6).

Itu salah, tapi hei kaulah yang melakukannya! Itu salahmu! Yah, mungkin pengguna, tapi tetap saja ...


Baiklah baiklah. Tapi bagaimana cara memperbaikinya ?!

Mari kita coba bermain-main dengan contoh itu sedikit sebelum kita memperbaikinya dengan benar untuk mempelajari beberapa hal menarik lainnya :-)

Saya lebih suka melakukan pendekatan di mana kita memahami banyak hal. Memperbaiki sesuatu tanpa sepengetahuan bagaimana kita melakukannya tidak memberikan kepuasan, bukan begitu? :)

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 :-)

Setelah menjalankan kode di atas, Anda akan melihat bahwa status streaming Anda (cin) sama dengan 4 (baris 7). Artinya keadaan internalnya tidak lagi sama dengan goodbit. Ada yang kacau. Cukup jelas, bukan? Anda mencoba untuk menetapkan nilai jenis string ("Wlodarczyk") ke variabel jenis int 'usia'. Jenis tidak cocok. Saatnya memberi tahu bahwa ada sesuatu yang salah. Dan komputer melakukannya dengan mengubah status aliran internal. Ini seperti: "Kamu f **** up man, tolong perbaiki aku. Aku beritahu kamu 'ramah' ;-)"

Anda tidak bisa lagi menggunakan 'cin' (stream). Itu macet. Seperti jika Anda meletakkan balok kayu besar di aliran air. Anda harus memperbaikinya sebelum dapat menggunakannya. Data (air) tidak dapat diperoleh dari aliran itu (cin) lagi karena kayu gelondongan (keadaan internal) tidak memungkinkan Anda untuk melakukannya.

Oh jadi kalau ada halangan (kayu gelondongan) kita bisa keluarkan saja pakai alat yang dibuat untuk melakukannya?

Iya!

Keadaan internal cin set ke 4 seperti alarm yang melolong dan mengeluarkan suara.

cin.clear membersihkan status kembali normal (goodbit). Ini seperti jika Anda datang dan membungkam alarm. Tunda saja. Anda tahu sesuatu telah terjadi sehingga Anda berkata: "Tidak apa-apa untuk berhenti membuat keributan, saya tahu ada sesuatu yang salah, tutup mulut (jelas)".

Baiklah, mari kita lakukan! Mari gunakan cin.clear ().

Panggil kode di bawah ini menggunakan "Arkadiusz Wlodarczyk" sebagai masukan pertama:

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 :-)

Kita pasti dapat melihat setelah menjalankan kode di atas bahwa statusnya sama dengan goodbit.

Hebat jadi masalahnya teratasi?

Panggil kode di bawah ini menggunakan "Arkadiusz Wlodarczyk" sebagai masukan pertama:

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 :-)

Bahkan negara diatur ke goodbit setelah baris 9 pengguna tidak dimintai "usia". Program berhenti.

MENGAPA?!

Ya ampun ... Anda baru saja mematikan alarm, bagaimana dengan batang kayu di dalam air? * Kembali ke teks di mana kita berbicara tentang "Wlodarczyk" bagaimana seharusnya kayu itu hilang.

Anda perlu menghapus "Wlodarczyk" potongan kayu itu dari sungai. Mematikan alarm sama sekali tidak menyelesaikan masalah. Anda baru saja membungkamnya dan menurut Anda masalahnya sudah hilang? ;)

Jadi sudah waktunya untuk alat lain:

cin.ignore dapat disamakan dengan truk khusus dengan tali yang datang dan menyingkirkan batang kayu yang membuat arus macet. Ini membersihkan masalah yang dibuat oleh pengguna program Anda.

Jadi bisakah kita menggunakannya bahkan sebelum alarm berbunyi?

Iya:

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" akan dihilangkan sebelum membuat suara berisik di baris 7.

Berapa 10000 dan '\ n'?

Dikatakan hapus 10.000 karakter (untuk berjaga-jaga) sampai '\ n' terpenuhi (ENTER). BTW Ini bisa dilakukan lebih baik menggunakan numeric_limits tetapi itu bukan topik dari jawaban ini.


Jadi penyebab utama masalah hilang sebelum kebisingan dibuat ...

Lalu mengapa kita perlu 'jelas'?

Bagaimana jika seseorang menanyakan pertanyaan 'beri saya umurmu' pada baris 6 misalnya: "dua puluh tahun" alih-alih menulis 20?

Jenis tidak cocok lagi. Komputer mencoba untuk menetapkan string ke int. Dan alarm dimulai. Anda bahkan tidak memiliki kesempatan untuk bereaksi pada situasi seperti itu. cin.ignore tidak akan membantu Anda dalam kasus seperti itu.

Jadi kita harus menggunakan clear dalam kasus seperti itu:

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

Tetapi apakah Anda harus membersihkan negara bagian 'untuk berjaga-jaga'?

Tentu saja tidak.

Jika ada yang salah (cin >> age;) instruksi akan memberi tahu Anda tentang hal itu dengan mengembalikan false.

Jadi kita bisa menggunakan pernyataan bersyarat untuk memeriksa apakah pengguna salah ketik di aliran

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";

Baiklah jadi kita bisa memperbaiki masalah awal kita seperti misalnya:

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

Tentu saja ini dapat diperbaiki dengan misalnya melakukan apa yang Anda lakukan dalam pertanyaan menggunakan loop while.

BONUS:

Anda mungkin bertanya-tanya. Bagaimana jika saya ingin mendapatkan nama dan nama keluarga di baris yang sama dari pengguna? Apakah mungkin menggunakan cin jika cin mengartikan setiap nilai yang dipisahkan oleh "spasi" sebagai variabel yang berbeda?

Tentu, Anda dapat melakukannya dengan dua cara:

1)

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

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

2) atau dengan menggunakan fungsi getline.

getline(cin, nameOfStringVariable);

dan itulah cara melakukannya:

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

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

Opsi kedua mungkin menjadi bumerang jika Anda menggunakannya setelah Anda menggunakan 'cin' sebelum getline.

Mari kita lihat:

Sebuah)

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;

Jika Anda memasukkan "20" sebagai usia, Anda tidak akan diminta untuk nameAndSurname.

Tetapi jika Anda melakukannya seperti itu:

b)

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

semuanya baik-baik saja.

APA?!

Setiap kali Anda meletakkan sesuatu pada input (aliran) Anda meninggalkan karakter putih akhir yang ENTER ('\ n') Anda harus entah bagaimana memasukkan nilai ke konsol. Jadi itu harus terjadi jika datanya berasal dari pengguna.

b) karakteristik cin adalah ia mengabaikan spasi, jadi ketika Anda membaca informasi dari cin, karakter baris baru '\ n' tidak masalah. Itu akan diabaikan.

a) fungsi getline membuat seluruh baris naik ke karakter baris baru ('\ n'), dan ketika karakter baris baru adalah hal pertama fungsi getline mendapat '\ n', dan itu saja yang didapat. Anda mengekstrak karakter baris baru yang ditinggalkan di aliran oleh pengguna yang menempatkan "20" di aliran di baris 3.

Jadi untuk memperbaikinya adalah dengan selalu memanggil cin.ignore (); setiap kali Anda menggunakan cin untuk mendapatkan nilai apa pun jika Anda akan menggunakan getline () di dalam program Anda.

Jadi kode yang tepat adalah:

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;

Saya harap streaming lebih jelas bagi Anda.

Hah tolong bungkam saya! :-)

Morfidon
sumber
1

gunakan cin.ignore(1000,'\n')untuk menghapus semua karakter sebelumnya cin.get()di buffer dan itu akan memilih untuk berhenti ketika bertemu '\ n' atau 1000 charspertama.

Phy Lieng
sumber