[C++, beginner] inputvraag wordt overgeslagen

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Sallin
  • Registratie: Mei 2004
  • Niet online
Ik ben bezig met het doorwerken van een c++ boek met opgaven. Voor een van die opgaven heb ik het volgende programma geschreven:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
using namespace std;

class employee
{
    private:
    string EmpName;
    long    EmpNumber;
    public:
    void getdata()
    {
        cout << "Enter employee name: ";
        getline(cin, EmpName); //om ook input met spaties toe te staan
        cout << "and employee number: ";
        cin >> EmpNumber;
    }
    void showdata ()
    {
        cout << "Employee " << EmpName << "\tNumber: " << EmpNumber << endl;
    }
};
        

int main()
{
    const int MAX = 10;
    employee    Emp[MAX];
    int     CheckMax = 0;
 
    for (CheckMax; CheckMax<MAX; CheckMax++)
    {
    Emp[CheckMax].getdata();
    }
    
    for (int j=0; j<CheckMax; j++)
    {
    Emp[j].showdata();
    }

    return 0;
}


Het programma vraagt om twee gegevens "Employee name" en "Employee number", slaat deze op in een element van een array van object Emp. In mijn geval wordt er 10 keer om een input gevraagd. De eerste keer gaat goed, maar bij de tweede keer (en de daarop volgende keren) wordt de input op de eerste vraag overgeslagen zoals bijvoorbeeld:
Enter employee name:
bla bla
and employee number:
3
Enter employee name:
and employee number:

Ik kan hier alleen nog maar het employee number invoeren. De output klopt uiteindelijk wel met de gegeven input. Het lijkt er haast op dat mijn <enter> om input mee af te sluiten te snel is, ook al lijkt me dat nogal onwaarschijnlijk.

Om uit te zoeken waar het aan kan liggen heb ik verschillende stukken code verplaatst, bijvoorbeeld de input in de loop in plaats van in de class. Ook heb ik een van de twee inputvragen uitgezet, dit werkt wel. Dus
C++:
1
2
3
4
5
6
7
void getdata()
{
//cout << "Enter employee name: ";
//getline(cin, EmpName); //om ook input met spaties toe te staan
cout << "and employee number: ";
cin >> EmpNumber;
}

en
C++:
1
2
3
4
5
6
7
void getdata()
{
cout << "Enter employee name: ";
getline(cin, EmpName); //om ook input met spaties toe te staan
//cout << "and employee number: ";
//cin >> EmpNumber;
}

werkt allebei.

Ik heb hier de string documentatie bekeken, maar daar wordt ik mbt dit probleem niet echt wijzer van. Verder heeft de voorbeeldcode hetzelfde probleem. Ik compileer met de g++ compiler van kubuntu. Weet iemand wat ik fout doe?

This too shall pass
Debian | VirtualBox (W7), Flickr


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 00:02
Ah, een klassiek probleem. ;) Het is eigenlijk heel simpel, maar je moet even doorhebben hoe iostreams zich precies gedragen.

Je invoer bestaat uit karakters, waarbij regels gescheiden worden door regeleindes ('\n'). getline() leest alle invoer tot aan (en inclusief!) het eerstvolgende regeleinde en retourneert die (waarbij het regeleinde weggelaten wordt). Een invoeroperatie als cin >> i slaat whitespace over en leest daarna zoveel mogelijk cijfers; als er na die cijfers een spatie of regeleinde staat blijft die in de invoerbuffer achter. Een volgende aanroep van getline() zal dus meteen dat regeleinde tegenkomen en een lege string retourneren!

Als jouw invoer is: "foo\n123\nbar\n456" dan consumeert de eerste getline dus "foo\n", de operator>> "123", en de tweede getline "\n" (en in de invoerbuffer staat dan nog "bar\n" wat niet meer als getal geparset kan worden). Dat was natuurlijk niet je bedoeling.

Wanneer je het probleem eenmaal doorhebt is er simpel omheen te werken. Je zou getline() kunnen aanroepen totdat je een niet-lege regel tegenkomt om die regeleindes op te eten (dan is de gebruiker dus ook verplicht een niet-lege naam in te voeren; in dit geval waarschijnlijk wel zinnig), maar in de praktijk is het vaak handig om getline() niet te mengen met andere vormen van I/O. In zo'n geval kun je dus ál je invoer met getline() doen, maar dan moet je de regels die je leest als nummer nog zelf converteren van een string naar een int (bijvoorbeeld met een std::stringstream, of met atoi() of strtol()).

Acties:
  • 0 Henk 'm!

  • Sallin
  • Registratie: Mei 2004
  • Niet online
Ah ok, klinkt logisch. Ik ga morgen eens wat uitproberen. Bedankt!

This too shall pass
Debian | VirtualBox (W7), Flickr


Acties:
  • 0 Henk 'm!

  • -ko-
  • Registratie: Oktober 2006
  • Laatst online: 18-09 16:23

-ko-

Leidend Voorwerp

Soultaker schreef op woensdag 29 juli 2009 @ 21:33:
Ah, een klassiek probleem. ;) Het is eigenlijk heel simpel, maar je moet even doorhebben hoe iostreams zich precies gedragen.

.. knip ..
Beetje topic hijacking, maar ik wil je bedanken voor deze super duidelijke uitleg. Ben zelf ook een beginner en heb er veel aan gehad. :)

@TS kan je de daadwerkelijke oplossing binnen je code ook nog even posten? Dan zoek ik mooi even de verschillen op voor mijn referentie. ;)

Wa Doede Nou


Acties:
  • 0 Henk 'm!

  • Sallin
  • Registratie: Mei 2004
  • Niet online
Bij deze
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <string>
#include <sstream>


using namespace std;

class employee
{
    private:
    string EmpName, tempEmpNumber;
    long    EmpNumber;
    public:
    void getdata()
    {
        cout << "Enter employee name: " << endl;
        getline(cin, EmpName);
        cout << "and employee number: " << endl;
        getline(cin, tempEmpNumber);
        stringstream(tempEmpNumber) >> EmpNumber;
    }
    void showdata ()
    {
        cout << "Employee " << EmpName << "\tNumber: " << EmpNumber << endl;
    }
};
        

int main()
{
    const int MAX = 10;
    employee    Emp[MAX];
    int     CheckMax = 0, CheckMore=1;
    string  tempCheckMore;
        
    while (CheckMore != 0 && CheckMax<MAX)
    {
    Emp[CheckMax].getdata();
    cout << "continue? (y=1/n=0) " << endl;
    getline(cin, tempCheckMore);
    stringstream(tempCheckMore) >> CheckMore;
    CheckMax++;
    }
    
    for (int j=0; j<CheckMax; j++)
    {
    Emp[j].showdata();
    }
    return 0;
}

Het enige wat niet 100% ok is, is de check of er meer invoer moet komen of niet. Deze zou op y/n moeten reageren (dus doorgaan zolang CheckMore != 'n'), maar ik loop te klooien met van string naar char gaan. Ach ja het werkt zullen we maar zeggen :).

This too shall pass
Debian | VirtualBox (W7), Flickr


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Regel 20 zou ik anders aanpakken. Het werkt in jouw geval, omdat je een long wilt inlezen, en voor long is er een member operator>> op istream. Echter, voor custom types en dingen als std::string is de operator>> gedefineerd buiten de istream class definitie. Normaal is dat geen probleem, maar er is een subtiliteit: je gebruikt op regel 20 een temporary (de stringstream("tempEmpNumber")), en temporaries kunnen niet binden aan non-const references. En nou wil het eerste argument van een operator>> nou net een non-const reference zijn. Die worden dus niet overwogen door de compiler.

Nou is dat voor een std::string niet zo heel erg - dat resulteert in een compile error. Maar als je een C-style string wilt in- of uitvoeren (invoeren zou ik sowieso niet doen, maar dat is een ander verhaal), dan krijg je ineens ostream::operator<<(const void *) ipv operator<<(ostream &, const char *).

Oftewel: gebruik geen temporary, maar definieer een variabele van het type stringstream. En gebruik een apart statement om de >> te doen.

[ Voor 7% gewijzigd door .oisyn op 30-07-2009 23:11 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Sallin
  • Registratie: Mei 2004
  • Niet online
Bedankt voor je suggestie, ik moet bekennen dat ik niet goed begrijp wat je bedoelt. Omdat ik "per ongeluk" EmpNumber als long heb gedefineerd gaat het goed, omdat voor long specifiek ">>" is gedefinieerd? En als ik dus iets anders wil, bijvoorbeeld met een c-string als input dan kom ik in de problemen (programma compileert niet). Ik heb hier gekeken of ik je opmerking beter kon plaatsen, maar dat lukt nog niet :). Zou je het wat meer kunnen toelichten?

This too shall pass
Debian | VirtualBox (W7), Flickr


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Sure :). Maar ik zal wel even een ander voorbeeld gebruiken.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Foo
{
    void bar() { }
};

void baz(Foo & foo) { }

int main()
{
    Foo a;
    const Foo b;
    a.bar(); // ok
    b.bar(); // error, b is een const Foo, en Foo::bar is niet const (dus verwacht een non-const Foo)

    baz(a); // ok
    baz(b); // error, b is een const Foo, en baz verwacht een non-const Foo
}

Ik neem aan dat je bekend bent met deze regels?

Welnu, temporaries binden nooit aan een non-const reference. Je kunt een temporary dus niet aan baz() geven:
C++:
1
2
3
4
5
6
7
Foo eenFunctieDieFooReturnt();

int main()
{
    baz(Foo()); // error
    baz(eenFunctieDieFooReturnt()); // error
}

Echter, dit geldt niet voor member functies. Foo().bar() kun je dus gewoon aanroepen, of eenFunctieDieFooReturnt().bar().

Nu introduceren we wat operators, zowel in als buiten de class definitie
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Foo
{
    void operator << (const void * v) { std::cout << "Foo::operator<<(const void*)" << std::endl; }
};

void operator << (Foo & foo, int i) { std::cout << "Foo::operator<<(int)" << std::endl; }
void operator << (Foo & foo, const char *) { std::cout << "Foo::operator<<(const char *)" << std::endl; }

int main()
{
    Foo foo;
    foo << 3; // ok
    Foo() << 3; // error, operator<<(Foo&,int) wil een non-const Foo

    // maar nu het erge!
    foo << "hoi"; // ok, operator<<(Foo&, const char *)
    Foo() << "hoi"; // ook ok!!!
}

Het probleem is alleen, die laatste variant doet niet wat je verwacht. Die roept namelijk de member operator aan die een void* verwacht, ipv de variant die buiten de class is gedefinieerd voor de const char *. Dit omdat die buiten de class niet voldoet, aangezien hij een non-const ref verwacht, terwijl je een temporary gebruikt (dus een const ref)

Op dezelfde manier doet een ostringstream() << "hoi" ook niet wat je verwacht. Want de const char * versie is buiten de class gedefinieerd, terwijl in de class een const void * is gedefinieerd, net als in mijn voorbeeld.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1