Mengapa ini terjadi?
Ini tidak ada hubungannya dengan masukan yang Anda berikan sendiri, melainkan dengan tampilan perilaku default std::getline()
. Ketika Anda memberikan masukan Anda untuk name ( std::cin >> name
), Anda tidak hanya mengirimkan karakter berikut, tetapi juga baris baru implisit telah ditambahkan ke aliran:
"John\n"
Sebuah baris baru selalu ditambahkan ke masukan Anda saat Anda memilih Enteratau Returnsaat mengirimkan dari terminal. Ini juga digunakan dalam file untuk berpindah ke baris berikutnya. Baris baru dibiarkan di buffer setelah ekstraksi name
hingga operasi I / O berikutnya di mana ia dibuang atau digunakan. Saat aliran kontrol std::getline()
tercapai, baris baru akan dibuang, tetapi masukan akan segera berhenti. Alasan hal ini terjadi adalah karena fungsionalitas default dari fungsi ini menentukan bahwa fungsi ini harus (mencoba membaca baris dan berhenti ketika menemukan baris baru).
Karena baris baru terkemuka ini menghambat fungsionalitas yang diharapkan dari program Anda, maka itu harus dilewati dengan cara apa pun. Salah satu opsi adalah memanggil std::cin.ignore()
setelah ekstraksi pertama. Ini akan membuang karakter yang tersedia berikutnya sehingga baris baru tidak lagi menghalangi.
std::getline(std::cin.ignore(), state)
Penjelasan Mendalam:
Ini adalah kelebihan std::getline()
yang Anda panggil:
template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
std::basic_string<charT>& str )
Kelebihan lain dari fungsi ini membutuhkan pembatas tipe charT
. Karakter pembatas adalah karakter yang merepresentasikan batas antara urutan input. Kelebihan khusus ini menyetel pembatas ke karakter baris baru input.widen('\n')
secara default karena tidak ada yang diberikan.
Sekarang, ini adalah beberapa kondisi dimana std::getline()
input akan diakhiri:
- Jika aliran telah mengekstraksi jumlah maksimum karakter yang
std::basic_string<charT>
dapat ditampung
- Jika karakter end-of-file (EOF) telah ditemukan
- Jika pembatas sudah ditemukan
Kondisi ketiga adalah yang kita hadapi. Masukan Anda ke dalam state
direpresentasikan sebagai berikut:
"John\nNew Hampshire"
^
|
next_pointer
dimana next_pointer
karakter selanjutnya yang akan diurai. Karena karakter yang disimpan di posisi berikutnya dalam urutan masukan adalah pembatas, std::getline()
akan secara diam-diam membuang karakter tersebut, menaikkan next_pointer
ke karakter berikutnya yang tersedia, dan menghentikan masukan. Ini berarti karakter lainnya yang telah Anda berikan masih tetap berada di buffer untuk operasi I / O berikutnya. Anda akan melihat bahwa jika Anda melakukan pembacaan lagi dari baris ke state
, ekstraksi Anda akan menghasilkan hasil yang benar sebagai panggilan terakhir untuk std::getline()
membuang pembatas.
Anda mungkin telah memperhatikan bahwa Anda biasanya tidak mengalami masalah ini saat mengekstrak dengan operator input yang diformat ( operator>>()
). Ini karena input stream menggunakan spasi sebagai pembatas untuk input dan std::skipws
1 manipulator telah disetel secara default. Aliran akan membuang spasi kosong utama dari aliran saat mulai melakukan masukan yang diformat. 2
Berbeda dengan operator input yang diformat, std::getline()
ini adalah fungsi input yang tidak diformat . Dan semua fungsi masukan yang tidak diformat memiliki kode berikut yang agak sama:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
Di atas adalah objek sentry yang dipakai di semua fungsi I / O yang diformat / tidak diformat dalam implementasi C ++ standar. Objek penjaga digunakan untuk menyiapkan aliran untuk I / O dan menentukan apakah dalam keadaan gagal atau tidak. Anda hanya akan menemukan bahwa dalam fungsi masukan yang tidak diformat , argumen kedua untuk konstruktor penjaga adalah true
. Argumen tersebut berarti bahwa spasi putih utama tidak akan dibuang dari awal urutan masukan. Berikut adalah kutipan relevan dari Standar [§27.7.2.1.3 / 2]:
explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
[...] Jika noskipws
nol dan is.flags() & ios_base::skipws
bukan nol, fungsi mengekstrak dan membuang setiap karakter selama karakter input berikutnya yang tersedia c
adalah karakter spasi. [...]
Karena kondisi di atas salah, objek penjaga tidak akan membuang spasi. Alasan noskipws
ditetapkan true
oleh fungsi ini adalah karena intinya std::getline()
adalah untuk membaca karakter mentah yang belum diformat ke dalam std::basic_string<charT>
objek.
Solusinya:
Tidak ada cara untuk menghentikan perilaku ini std::getline()
. Apa yang harus Anda lakukan adalah membuang sendiri baris baru tersebut sebelum std::getline()
dijalankan (tetapi lakukan setelah ekstraksi yang diformat). Ini dapat dilakukan dengan menggunakan ignore()
untuk membuang sisa masukan sampai kita mencapai baris baru yang segar:
if (std::cin >> name &&
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
std::getline(std::cin, state))
{ ... }
Anda harus menyertakan <limits>
untuk menggunakan std::numeric_limits
. std::basic_istream<...>::ignore()
adalah fungsi yang membuang sejumlah karakter tertentu hingga menemukan pembatas atau mencapai akhir aliran ( ignore()
juga membuang pembatas jika menemukannya). The max()
mengembalikan fungsi jumlah terbesar dari karakter yang sungai dapat menerima.
Cara lain untuk membuang spasi putih adalah dengan menggunakan std::ws
fungsi manipulator yang dirancang untuk mengekstrak dan membuang spasi putih utama dari awal aliran input:
if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }
Apa bedanya?
Perbedaannya adalah ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 tanpa pandang bulu membuang karakter sampai karakter tersebut dibuang count
, menemukan pembatas (ditentukan oleh argumen kedua delim
) atau mencapai akhir aliran. std::ws
hanya digunakan untuk membuang karakter spasi dari awal aliran.
Jika Anda mencampur masukan yang diformat dengan masukan yang tidak diformat dan Anda perlu membuang sisa spasi, gunakan std::ws
. Jika tidak, jika Anda perlu menghapus masukan yang tidak valid apa pun itu, gunakan ignore()
. Dalam contoh kita, kita hanya perlu mengosongkan spasi karena aliran menggunakan input Anda "John"
untuk name
variabel. Yang tersisa hanyalah karakter baris baru.
1: std::skipws
adalah manipulator yang memberi tahu aliran input untuk membuang spasi kosong utama saat melakukan input yang diformat. Ini bisa dimatikan dengan std::noskipws
manipulator.
2: Arus input menganggap karakter tertentu sebagai spasi secara default, seperti karakter spasi, karakter baris baru, umpan formulir, carriage return, dll.
3: Ini adalah tanda tangan dari std::basic_istream<...>::ignore()
. Anda dapat memanggilnya dengan nol argumen untuk membuang satu karakter dari aliran, satu argumen untuk membuang sejumlah karakter, atau dua argumen untuk membuang count
karakter atau sampai mencapai delim
, mana saja yang lebih dulu. Anda biasanya menggunakan std::numeric_limits<std::streamsize>::max()
sebagai nilai count
jika Anda tidak tahu berapa banyak karakter sebelum pembatas, tetapi Anda tetap ingin membuangnya.
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
juga harus bekerja seperti yang diharapkan. (Selain jawaban di bawah).