Std :: getline () biçimlendirilmiş bir ayıklamadan sonra girişi neden atlar?


109

Kullanıcıdan adını ve durumunu soran aşağıdaki kod parçasına sahibim:

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

Bulduğum şey, adın başarıyla çıkarıldığı, ancak devletin olmadığı. İşte girdi ve ortaya çıkan çıktı:

Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in "

Devletin adı neden çıktıdan çıkarıldı? Doğru girdiyi verdim, ancak kod bir şekilde onu görmezden geliyor. Bu neden oluyor?


Ben std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)de beklendiği gibi çalışması gerektiğine inanıyorum . (Aşağıdaki cevaplara ek olarak).
jww

Yanıtlar:


125

Bu neden oluyor?

Bunun, kendi sağladığınız girdiyle çok az ilgisi vardır, bunun yerine varsayılan davranış std::getline()sergileriyle ilgilidir. Ad ( std::cin >> name) için girişinizi sağladığınızda, yalnızca aşağıdaki karakterleri göndermediniz, aynı zamanda akışa örtük bir satırsonu da eklendi:

"John\n"

Bir terminalden seçim yaptığınızda Enterveya Returngönderirken girişinize her zaman yeni satır eklenir . Ayrıca, bir sonraki satıra geçmek için dosyalarda da kullanılır. Satırsonu, çıkarıldıktan sonra, nameatıldığı veya tüketildiği bir sonraki G / Ç işlemine kadar arabellekte bırakılır . Kontrol akışı ulaştığında std::getline(), satırsonu atılacak, ancak girdi hemen kesilecektir. Bunun olmasının nedeni, bu işlevin varsayılan işlevselliğinin bunu yapması gerektiğini belirtmesidir (bir satırı okumaya çalışır ve bir satırsonu bulduğunda durur).

Bu öncü satırsonu, programınızın beklenen işlevselliğini engellediğinden, bir şekilde göz ardı edildiğimizde onun atlanması gerekir. Bir seçenek, std::cin.ignore()ilk çıkarma işleminden sonra aramaktır. Bir sonraki mevcut karakteri atacak, böylece yeni satır artık yolda olmayacak.

std::getline(std::cin.ignore(), state)

Derinlemesine Açıklama:

Bu std::getline()aradığınız aşırı yük :

template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
                                    std::basic_string<charT>& str )

Bu işlevin başka bir aşırı yüklemesi, bir tür sınırlayıcı alır charT. Sınırlayıcı karakter, girdi dizileri arasındaki sınırı temsil eden bir karakterdir. Bu özel aşırı yükleme, sınırlayıcıyı sağlanmadığı için input.widen('\n')varsayılan olarak yeni satır karakterine ayarlar .

Şimdi, bunlar std::getline()girdiyi sonlandıran koşullardan birkaçı :

  • Akış, std::basic_string<charT>tutabileceği maksimum karakter miktarını çıkardıysa
  • Dosya sonu (EOF) karakteri bulunursa
  • Sınırlayıcı bulunmuşsa

Üçüncü koşul, uğraştığımız durumdur. İçine girdi stateDİR thusly temsil:

"John\nNew Hampshire"
     ^
     |
 next_pointer

next_pointerayrıştırılacak sonraki karakter nerede . Giriş sırasındaki bir sonraki konumda saklanan karakter sınırlayıcı olduğundan, std::getline()bu karakteri sessizce atacak next_pointer, bir sonraki kullanılabilir karaktere yükselecek ve girişi durduracaktır. Bu, sağladığınız karakterlerin geri kalanının bir sonraki G / Ç işlemi için hala arabellekte kaldığı anlamına gelir. Hattan içine başka bir okuma yaparsanız state, ayırma std::getline()işleminizin sınırlayıcıyı atmak için son çağrı olarak doğru sonucu vereceğini fark edeceksiniz .


Biçimlendirilmiş girdi operatörü ( operator>>()) ile ayıklarken tipik olarak bu problemle karşılaşmadığınızı fark etmiş olabilirsiniz . Bunun nedeni, giriş akışlarının giriş için sınırlayıcılar olarak beyaz boşlukları kullanması ve std::skipws1 manipülatörünün varsayılan olarak açık olmasıdır. Akışlar, biçimlendirilmiş girdi gerçekleştirmeye başladığında akıştan baştaki boşlukları çıkarır. 2

Biçimlendirilmiş giriş operatörler farklı olarak, std::getline()bir bir biçimlendirilmemiş giriş işlev. Ve tüm biçimlendirilmemiş giriş işlevlerinin ortak bir şekilde aşağıdaki kodu vardır:

typename std::basic_istream<charT>::sentry ok(istream_object, true);

Yukarıdakiler, standart bir C ++ uygulamasında tüm biçimlendirilmiş / biçimlendirilmemiş G / Ç işlevlerinde somutlaştırılmış bir nöbetçi nesnesidir. Sentry nesneleri, akışı G / Ç için hazırlamak ve hata durumunda olup olmadığını belirlemek için kullanılır. Sadece biçimlendirilmemiş girdi işlevlerinde, sentry kurucusunun ikinci argümanının olduğunu göreceksiniz true. Bu iddia aracı boşluk gelen olacak olup , giriş dizinin başlangıcından itibaren göz ardı edilmesi. Standarttan [§27.7.2.1.3 / 2] ilgili alıntı:

 explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);

[...] noskipwsSıfırsa ve sıfır değilse , is.flags() & ios_base::skipwsişlev, bir sonraki kullanılabilir giriş karakteri cbir boşluk karakteri olduğu sürece her karakteri çıkarır ve atar . [...]

Yukarıdaki koşul yanlış olduğundan, nöbetçi nesne boşlukları atmayacaktır. Bunun nedeni noskipws, truebu işlev tarafından belirlenir , çünkü std::getline()amacı, ham, formatlanmamış karakterleri bir std::basic_string<charT>nesneye okumaktır .


Çözüm:

Bu davranışı durdurmanın bir yolu yok std::getline(). Yapmanız gereken şey, yeni satırı std::getline()çalıştırmadan önce kendiniz atmaktır (ancak bunu biçimlendirilmiş ayıklamadan sonra yapın ). Bu, ignore()yeni bir satıra ulaşana kadar girdinin geri kalanını atmak için kullanılarak yapılabilir :

if (std::cin >> name &&
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
    std::getline(std::cin, state))
{ ... }

Sen dahil etmek gerekir <limits>kullanımına std::numeric_limits. std::basic_istream<...>::ignore()bir sınırlayıcı bulana veya akışın sonuna ulaşana kadar belirtilen miktarda karakteri atan bir işlevdir ( ignore()ayrıca bulursa sınırlayıcıyı da atar). max()İşlevi, bir akım kabul karakter büyük miktarda döner.

Boşluğu atmanın başka bir yolu std::ws, bir giriş akışının başlangıcından önde gelen beyaz boşlukları çıkarmak ve atmak için tasarlanmış bir manipülatör olan işlevi kullanmaktır :

if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }

Fark ne?

Aradaki fark, ignore(std::streamsize count = 1, int_type delim = Traits::eof())3'ün karakterleri atana count, sınırlayıcıyı bulana (ikinci bağımsız değişken tarafından belirtilen delim) veya akışın sonuna ulaşana kadar karakterleri ayrım gözetmeden atmasıdır .std::wsyalnızca akışın başından itibaren boşluk karakterlerini atmak için kullanılır.

Biçimlendirilmiş girdiyi biçimlendirilmemiş girdiyle karıştırıyorsanız ve kalan beyaz boşlukları atmanız gerekiyorsa, kullanın std::ws. Aksi takdirde, geçersiz girişi ne olduğuna bakılmaksızın silmeniz gerekirse, kullanın ignore(). Örneğimizde, akış değişken "John"için girdinizi tükettiğinden, yalnızca boşlukları temizlememiz gerekiyor name. Geriye kalan tek şey satırsonu karakteriydi.


1: std::skipwsgiriş akışına biçimlendirilmiş girdi gerçekleştirirken baştaki boşluğu atmasını söyleyen manipülatördür. Bu, std::noskipwsmanipülatör ile kapatılabilir .

2: Giriş akışları, boşluk karakteri, yeni satır karakteri, form beslemesi, satır başı vb. Gibi belirli karakterleri varsayılan olarak boşluk olarak kabul eder.

3: Bu, imzasıdır std::basic_istream<...>::ignore(). Akıştan tek bir karakteri atmak için sıfır bağımsız değişken, belirli sayıda karakteri atmak için bir bağımsız değişken veya countkarakterleri atmak için iki bağımsız değişken veya delimhangisi önce gelirse ulaşana kadar bunu çağırabilirsiniz . Normalde kullanmak std::numeric_limits<std::streamsize>::max()değeri olarak countsize sınırlayıcı önce kaç tane karakter bilmiyorsanız, ancak yine de onları atmak istiyorum.


1
Neden basitçe değil if (getline(std::cin, name) && getline(std::cin, state))?
Fred Larson

@FredLarson İyi nokta. İlk çıkarım bir tam sayıdan veya dize olmayan herhangi bir şeyden olursa işe yaramaz.
0x499602D2

Tabii ki, burada durum böyle değil ve aynı şeyi iki farklı şekilde yapmanın bir anlamı yok. Bir tamsayı için, doğruyu bir dizge haline getirebilir ve sonra kullanabilirsiniz std::stoi(), ancak o zaman bir avantaj olduğu çok açık değildir. Ama ben sadece std::getline()satıra yönelik girdi kullanmayı tercih ediyorum ve sonra mantıklı olan herhangi bir şekilde satırı ayrıştırmakla uğraşıyorum. Sanırım daha az hataya meyilli.
Fred Larson

@FredLarson Kabul edildi. Belki zamanım varsa onu da eklerim.
0x499602D2

1
@Albin Kullanmak isteyebileceğiniz neden std::getline(), belirli bir sınırlayıcıya kadar olan tüm karakterleri yakalamak ve bunu bir dizeye girmek istiyorsanız, varsayılan olarak satırsonu olur. Bu Xsayı dizeleri yalnızca tek kelimeler / belirteçler ise, bu iş ile kolayca gerçekleştirilebilir >>. Aksi takdirde, ilk sayıyı bir tam sayıya girersiniz , sonraki satırda >>arama cin.ignore()yapar ve ardından kullandığınız yerde bir döngü çalıştırırsınız getline().
0x499602D2

11

İlk kodunuzu aşağıdaki şekilde değiştirirseniz her şey yoluna girecektir:

if ((cin >> name).get() && std::getline(cin, state))

3
Teşekkür ederim. Bu aynı zamanda işe yarayacaktır çünkü get()sonraki karakteri tüketir. (std::cin >> name).ignore()Cevabımda daha önce önerdiğim de var.
0x499602D2

"..iş çünkü get () ..." Evet, aynen öyle. Ayrıntıları olmadan cevabı verdiğim için üzgünüm.
Boris

4
Neden basitçe değil if (getline(std::cin, name) && getline(std::cin, state))?
Fred Larson

0

Bunun nedeni \n, akışa yeni bir satır başlatmasını söylerken, bir uçbirimden gelen tüm kullanıcı girdilerine satırsonu karakteri olarak da bilinen örtük bir satır beslemesi eklenmesidir. std::getlineBirden çok kullanıcı girdisi satırını kontrol ederken kullanarak bunu güvenle hesaba katabilirsiniz. Öğesinin varsayılan davranışı, bu durumda giriş akışı nesnesinden std::getlinesatırsonu karakterine kadar olan her şeyi okur .\nstd::cin

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::getline(std::cin, name) && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
    return 0;
}
Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in New Hampshire"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.