Saya memiliki potongan kode berikut yang meminta nama dan status pengguna:
#include <iostream>
#include <string>
int main()
{
std::string name;
std::string state;
if (std::cin >> name && std::getline(std::cin, state))
{
std::cout << "Your name is " << name << " and you live in " << state;
}
}
Apa yang saya temukan adalah bahwa nama tersebut telah berhasil diekstraksi, tetapi bukan statusnya. Berikut adalah masukan dan keluaran yang dihasilkan:
Input: "John" "New Hampshire" Output: "Your name is John and you live in "
Mengapa nama negara bagian dihilangkan dari keluaran? Saya telah memberikan masukan yang tepat, tetapi kode tersebut mengabaikannya. Mengapa ini terjadi?
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
juga harus bekerja seperti yang diharapkan. (Selain jawaban di bawah).Jawaban:
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: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 kontrolstd::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.Penjelasan Mendalam:
Ini adalah kelebihan
std::getline()
yang Anda panggil: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 baruinput.widen('\n')
secara default karena tidak ada yang diberikan.Sekarang, ini adalah beberapa kondisi dimana
std::getline()
input akan diakhiri:std::basic_string<charT>
dapat ditampungKondisi ketiga adalah yang kita hadapi. Masukan Anda ke dalam
state
direpresentasikan sebagai berikut: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, menaikkannext_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 kestate
, ekstraksi Anda akan menghasilkan hasil yang benar sebagai panggilan terakhir untukstd::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 danstd::skipws
1 manipulator telah disetel secara default. Aliran akan membuang spasi kosong utama dari aliran saat mulai melakukan masukan yang diformat. 2Berbeda 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: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]:Karena kondisi di atas salah, objek penjaga tidak akan membuang spasi. Alasan
noskipws
ditetapkantrue
oleh fungsi ini adalah karena intinyastd::getline()
adalah untuk membaca karakter mentah yang belum diformat ke dalamstd::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 sebelumstd::getline()
dijalankan (tetapi lakukan setelah ekstraksi yang diformat). Ini dapat dilakukan dengan menggunakanignore()
untuk membuang sisa masukan sampai kita mencapai baris baru yang segar:Anda harus menyertakan
<limits>
untuk menggunakanstd::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). Themax()
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:Apa bedanya?
Perbedaannya adalah
ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 tanpa pandang bulu membuang karakter sampai karakter tersebut dibuangcount
, menemukan pembatas (ditentukan oleh argumen keduadelim
) 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, gunakanignore()
. Dalam contoh kita, kita hanya perlu mengosongkan spasi karena aliran menggunakan input Anda"John"
untukname
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 denganstd::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 membuangcount
karakter atau sampai mencapaidelim
, mana saja yang lebih dulu. Anda biasanya menggunakanstd::numeric_limits<std::streamsize>::max()
sebagai nilaicount
jika Anda tidak tahu berapa banyak karakter sebelum pembatas, tetapi Anda tetap ingin membuangnya.sumber
if (getline(std::cin, name) && getline(std::cin, state))
?std::stoi()
, tapi tidak begitu jelas ada keuntungannya. Tapi saya cenderung lebih suka menggunakanstd::getline()
untuk input berorientasi garis dan kemudian berurusan dengan penguraian baris dengan cara apa pun yang masuk akal. Saya pikir itu tidak terlalu rentan terhadap kesalahan.std::getline()
adalah jika Anda ingin menangkap semua karakter hingga pembatas tertentu dan memasukkannya ke dalam string, secara default adalah baris baru. JikaX
jumlah string tersebut hanya satu kata / token maka pekerjaan ini dapat dengan mudah diselesaikan>>
. Jika tidak, Anda akan memasukkan nomor pertama ke dalam bilangan bulat dengan>>
, panggilcin.ignore()
di baris berikutnya, dan kemudian jalankan loop di mana Anda menggunakangetline()
.Semuanya akan baik-baik saja jika Anda mengubah kode awal Anda dengan cara berikut:
sumber
get()
mengkonsumsi karakter berikutnya. Ada juga(std::cin >> name).ignore()
yang saya sarankan sebelumnya dalam jawaban saya.if (getline(std::cin, name) && getline(std::cin, state))
?Ini terjadi karena umpan baris implisit yang juga dikenal sebagai karakter baris baru
\n
ditambahkan ke semua masukan pengguna dari terminal karena memberi tahu aliran untuk memulai baris baru. Anda dapat dengan aman menjelaskan hal ini dengan menggunakanstd::getline
saat memeriksa beberapa baris masukan pengguna. Perilaku defaultstd::getline
akan membaca semuanya hingga dan termasuk karakter baris baru\n
dari objek input stream yangstd::cin
dalam kasus ini.sumber