string
en iostream
.
© Harry Broeders.
Deze opdracht is bedoeld om je C kennis op te frissen en leert je daarnaast
werken met de standaard class string
en de standaard objecten
cin
en cout
. In de propedeuse heb je leren
werken met character array's voor het opslaan van strings. In de standaard
C++ library is het type string
gedefinieerd dat veel eenvoudig
te gebruiken is dan character array's. In C gebruik je de stdio
library voor input en output bewerkingen. In de C++ standaard is de
iostream
library gedefinieerd die eenvoudiger te gebruiken is
dan de stdio
library. Deze nieuwe library is bovendien uitbreidbaar
(zoals we in opdracht 2 zullen zien).
C++ is een uitbreiding van de programmeertaal die je al kent: C. C++ is een veel uitgebreidere taal dan C en in C++ kun je allerlei dingen doen die in C niet (of alleen heel onhandig) gedaan kunnen worden, zoals object georiënteerd en generiek programmeren. Alle dingen die je in C geleerd hebt kun je ook toepassen in C++ maar sommige dingen die je in C hebt geleerd kun je in C++ beter (handiger en efficiënter) op een andere manier doen.
Een van de eerste opvallende dingen die je tegenkomt als je van C overstapt
naar C++ is dat de vertrouwde printf
en scanf
functies
in C++ niet meer gebruikt worden maar vervangen zijn door (op het eerste
gezicht) onduidelijke expressies zoals
cout<<"i="<<i<<endl;
en
cin>>i;
<<
en >>
.
Je bent gewend om in C programma's de functies uit de stdio
bibliotheek te gebruiken voor input en output. De meest gebruikte functies
zijn printf
en scanf
. Deze functies zijn echter
niet "type veilig" omdat de inhoud van de als eerste argument meegegeven
format string pas tijdens het uitvoeren van het programma verwerkt wordt.
De compiler merkt het dus niet als de "type aanduidingen" zoals
%d
die in de format string gebruikt zijn niet overeenkomen met
de typen van de volgende argumenten. Tevens is het niet mogelijk om een eigen
format type aanduiding aan de bestaande toe te voegen. Om deze redenen is
in de ISO/ANSI C++ standaard naast de oude stdio
bibliotheek
(om compatibel te blijven) ook een nieuwe I/O library iostream
opgenomen. Bij het ontwerpen van nieuwe software kun je het best van deze
nieuwe library gebruik maken. De belangrijkste output faciliteiten van deze
library zijn de standaard output stream cout
(vergelijkbaar
met stdout
) en de bijbehorende <<
operator.
De belangrijkste input faciliteiten van deze library zijn de standaard input
stream cin
(vergelijkbaar met stdin
) en de bijbehorende
>>
operator.
printf
en scanf
in C.
In een C programma kunnen de functies scanf
en
printf
als volgt gebruikt worden om een double
variabele in te lezen en meteen weer af te drukken.
#include <stdio.h>
// ...
double d;
scanf("%d",d); // deze regel bevat twee fouten!
printf("d=%lf\n",d);
Zie jij de fouten? Op zich is het geen probleem als je de fouten niet ziet, maar het is wel een probleem dat de C compiler deze fouten niet ziet! Als je de bovenstaande code compileert dan geeft de compiler geen error en ook geen warning. Als je het programma start lijkt alles in orde, je kunt gewoon een getal invoeren.
Zodra je echter op enter drukt loopt het programma vast met de volgende foutmelding:
Blijkbaar wordt de ingelezen informatie niet op het juiste plaats in het
geheugen weggeschreven. Bij de functie scanf
moet je als tweede
argument het adres van de variabele d
meegeven.
scanf("%d",&d); // deze regel bevat nog maar één fout!
Als we deze verbetering invoeren en het programma opnieuw compileren en uitvoeren loopt het niet meer vast:
Het resultaat is echter nog steeds niet correct. Als we met
scanf
een variabele van het type double
willen
inlezen moeten we de format specifier %lf
gebruiken in plaats
van %d
(%d
is bedoeld voor het inlezen van het
type int
).
scanf("%lf",&d); // deze regel is correct!
Als we deze verbetering invoeren en het programma opnieuw compileren en uitvoeren zien we dat het nu wel werkt:
De functie printf
heeft soortgelijke problemen. Deze functie
geeft verkeerde uitvoer als de format specifier niet klopt. Ook hier geeft
de C compiler geen error of warning.
cin
met
>>
en cout
met <<
.
In een C++ programma kunnen we een double
variabele als volgt
inlezen en meteen weer afdrukken:
#include <iostream>
// ...
double d;
std::cin>>d; // lees d in vanaf het toetsenbord
std::cout<<"d="<<d<<std::endl; // druk d af op het scherm en ga naar het begin van de volgende regel
Zoals je ziet werkt deze code zoals in het commentaar staat toegelicht:
In de include file iostream
is de variabele (eigenlijk het object,
maar dat wordt later pas duidelijk) std::cin
gedefinieerd. Deze
variabele stelt het toetsenbordgeheugen voor. Je kunt data uit dit geheugen
"trekken" met behulp van de >>
operator. De expressie
std::cin>>d
zorgt er dus voor dat de waarde die op het
toetsenbord wordt ingevoerd wordt ingelezen in de variabele d
.
Merk op dat je niet op hoeft te geven welk type moet worden ingelezen (in
dit geval een double
). De compiler "zoekt" dit zelf voor je
uit (later zullen we zien dat hier gebuik gemaakt wordt van operator
overloading). Het is ook niet nodig om het adres van d
op
te geven. De compiler "snapt" zelf dat de ingelezen waarde naar het adres
van de variabele d
moet worden weggeschreven (later zullen we
zien dat hier gebruik gemaakt wordt van een reference). Omdat de operator
>>
gebruikt wordt om data in te lezen vanuit
std::cin
wordt deze operator in dit geval de get operator
genoemd.
Kijk nog eens naar de fouten in de aanroep van scanf
in het
bovenstaande C programma. Deze fouten kunnen we dus in C++ niet maken als
we std::cin
gebruiken omdat de compiler zelf "uitzoekt" of de
variabele zelf of het adres van de variabele moet worden gebruikt en wat
het type is dat moet worden ingelezen. Handig!
Natuurlijk kun je bij het gebruik van std::cin
ook fouten maken.
Je kunt bijvoorbeeld per ongeluk toch het adres van d
opgeven
(omdat je dat bij scanf
zo gewend was). In dit geval krijg je
netjes een foutmelding.
In de include file iostream
is ook de variabele (eigenlijk het
object, maar dat wordt later pas duidelijk) std::cout
gedefinieerd.
Deze variabele stelt het beeldschermgeheugen voor. Je kunt data naar dit
geheugen "duwen" met behulp van de <<
operator. De expressie
std::cout<<"d"<<d<<std::endl
zorgt er dus
voor dat eerst de tekst d=
op het scherm verschijnt. Vervolgens
wordt de waarde van de variabele d
op het scherm gezet en tot
slot gaat de cursor naar het begin van de volgende regel. Merk op dat je
niet op hoeft te geven welk type moet worden afgedrukt (in dit geval een
double
). De compiler "zoekt" dit zelf voor je uit (later zullen
we zien dat hier gebuik gemaakt wordt van operator overloading). Omdat
de operator <<
gebruikt wordt om data weg te schrijven
naar std::cout
wordt deze operator in dit geval de put
operator genoemd.
Zoals je ziet moet je bij het gebruik van namen die in de ISO/ANSI C++ standaard
include files (zoals iostream
) gedeclareerd zijn (zoals
cin
, cout
en endl
) steeds aangeven
dat de naam uit de standaard wil gebruiken door er std::
voor
te zetten. Dit is gedaan om deze include files ook te kunnen gebruiken in
(oude) bestaande programma's waarin door de programmeur al (toevallig) dezelfde
namen gebruikt zijn. In het bovenstaande programma kun je dus in plaats van
de variabele naam d
ook de naam cin
gebruiken.
Omdat de cin
die geïnclude wordt uit iostream
altijd vooraf gegaan wordt door std::
levert dit (voor de compiler)
geen verwarring op.
#include <iostream>
// ...
double cin;
std::cin>>cin;
std::cout<<"cin="<<cin<<std::endl;
Bij het schrijven van nieuwe programma's kunnen we ook besluiten om de in de standaard include files gebruikte namen niet ook zelf te gaan gebruiken. In dit geval kunnen we aangeven dat onbekende namen altijd afkomstig zijn uit de geïnclude files, dit doe je als volgt:
#include <iostream>
using namespace std;
// ...
double d;
cin>>d; // lees d in vanaf het toetsenbord
cout<<"d="<<d<<endl; // druk d af op het scherm en ga naar het begin van de volgende regel
De iostream library is zeer uitgebreid. Verderop in deze practicumopgave komen we hier nog even op terug.
string
type.
We zullen eerst de problemen met strings in C op een rijtje zetten en daarna
het nieuwe string
type uit C++ bespreken.
De programmeertaal C heeft geen "echt" string type. Als je in C een string
(rij karakters) wilt opslaan dan doe je dat in een character array. In C
geldt de afspraak dat elke string wordt afgesloten door een zogenaamd
nul-karakter '\0'
. Voorbeeld:
char naam[] = "Harry"; // deze array bevat 6 karakters!
Een string in C heeft de volgende problemen:
strxxx
functies gebruikt worden).
#include <string.h>
#include <iostream.h>
int main() {
char naam[10];
naam = "Harry"; // Error! Zie opmerking 1.
strcpy(naam, "Harry"); // OK
cout<<naam<<endl;
strcpy(naam, "Willem-Alexander"); // Error! Zie opmerking 2.
strcpy(naam, "Alex"); // OK
cout<<naam<<endl;
if (naam == "Alex") { // Error! Zie opmerking 3.
// ...
}
if (strcmp(naam, "Alex") == 0) { // OK
// ...
}
}
Opmerkingen:
naam
) kun je namelijk niets toekennen. Als je de
array wilt vullen dan moet je dat met de functie strcpy
(gedefinieerd in <string.h>
) doen.
naam == "Alex"
levert echter altijd false
op! Als
je twee array variabelen met elkaar vergelijkt dan worden hun adressen
met elkaar vergeleken. Als je de inhoud van de array's met elkaar
wilt vergelijken moet je de functie strcmp
(gedefinieerd in
<string.h>
) gebruiken.
string
.
De programmeertaal C++ heeft wel een "echt" string type. Dit type is gedefinieerd in de standaard library. Later zul je leren dat string geen "ingebouwd" type is maar een "zelfgemaakt type" een zogenaamde class. Voor het gebruik maakt dat echter niet uit. Voorbeeld:
#include <string>
using namespace std;
string naam = "Harry"; // deze string bevat 5 karakters.
Een string
in C++ heeft de volgende voordelen ten opzichte van
een string uit C:
string
is dynamisch (de lengte kan tijdens het uitvoeren
van het programma, indien nodig, worden aangepast, er is dus ook geen maximale
lengte!).
string
bevat veel extra functionaliteit.
#include <string> // Zie opmerking 1.
#include <iostream>
using namespace std;
int main() {
string naam;
naam = "Harry"; // Zie opmerking 2.
cout<<naam<<endl;
naam = "Willem-Alexander"; // Zie opmerking 3.
cout<<naam<<endl;
if (naam == "Willem-Alexander") { // Zie opmerking 4.
cout<<"Hoi Alex!"<<endl;
}
return 0;
}
Opmerkingen:
<string>
is dus heel wat anders dan
de C include file <string.h>
. Als je in een C++ programma
toch de oude C strings wilt gebruiken (om oude C code te hergebruiken) dan
kun je de oude strxxx
functies includen met de include file
<cstring>
.
string
met behulp van de operator =
.
string
is dynamisch en "groeit" als dat nodig is!
string
gewoon vergelijken met
behulp van de operator ==
.
Nu komt de verrassing: een variabele van het type (eigenlijk de class)
string
is geen gewone variabele maar een object! Wat
dat precies betekent wordt later nog uitgebreid behandeld. Op dit moment
zullen we alleen bekijken wat dit betekent voor het gebruik van objecten
(variabelen) van de class (het type) string
.
Objecten zijn vergelijkbaar met gewone variabelen: ze hebben een naam en je kunt er "iets" in opslaan. Maar met objecten kun je iets wat met gewone variabelen niet kan. Je kunt objecten boodschappen (messages) sturen.
Je kunt een message naar een object sturen om het object een vraag
te stellen. Als je wilt weten hoeveel karakters een object van de class
string
bevat dan kun je dat object de message size
sturen. Het antwoord op deze message is dan een integer die het aantal karakters
weergeeft.
#include <string>
#include <iostream>
using namespace std;
int main() {
string naam = "Willem-Alexander";
cout<<"De naam "<<naam<<" bevat "<<naam.size()<<" karakters."<<endl;
cin.get();
return 0;
}
Uitvoer:
De naam Willem-Alexander bevat 16 karakters.
De syntax voor het versturen van een message is:
naam-van-object.naam-van-message(parameters)
Dus eerst de naam van het object waar naar toe de message verstuurd moet
worden (dat object wordt de receiver van de message genoemd) dan een
punt gevolgd door de naam van de message en tot slot een haakje openen en
een haakje sluiten met daartussen eventuele parameters. De message
size
heeft geen parameters. Het versturen van een message lijkt
een beetje op het aanroepen van een functie. In C++ wordt het versturen van
een message meestal het aanroepen van een memberfunctie genoemd. Toch
is er een duidelijk verschil tussen een memberfunctie (message) en een functie:
een memberfunctie heeft een receiver en een gewone functie niet!
Je kunt ook een message naar een object sturen om het object iets te laten
doen. Na afloop van de memberfuntie is het object dan veranderd. Als
je bijvoorbeeld iets aan een object van de class string
wilt
toevoegen dan kun je dat object de message append
sturen. De
string
die moet worden toegevoegd moet je als argument meesturen.
#include <string>
#include <iostream>
using namespace std;
int main() {
string naam = "Willem-Alexander";
naam.append(" en Maxima");
cout<<naam<<endl;
cin.get();
return 0;
}
Uitvoer:
Willem-Alexander en Maxima
string
.
In deze paragraaf worden enkele voorbeelden gegeven.
>
<
>=
<=
==
!=
(spreekt
voor zich).
[]
.char
c(s[2]);
s
gelijk is aan Maxima
dan wordt het karakter
c
gelijk aan x
.
[]
.s[3]='a';
s
gelijk is aan Maxima
dan wordt het
s
gelijk aan Maxama
.
=
of met assign
.
Met assign
kun je ook een deel van de ene string
aan de andere string
toekennen.s1.assign(s2, 7,
4);
s1
wordt gelijk aan het deel van s2
dat begint op positie
7 en 4 karakters lang is. Als s2
gelijk is aan
Willem-Alexander
dan zal s1
gelijk worden aan
Alex
.
+
en +=
of met
append
. Met append
kun je ook een deel van de ene
string
achter de andere string
plakken.string
s1("Willem");
s1+="-Alexander";
string
s2("Maxima");
s2+=" en
";
s2.append(s1, 7,
4);
s2
wordt gelijk aan Maxima en Alex
insert
.s1.insert(4,
s2);
s2
op positie 4 in s1
in. Als s1
gelijk is aan Maxima
en s2
gelijk is aan
Willem-Alexander
dan wordt s1
gelijk aan
MaxiWillem-Alexanderma
.s1.insert(4, s2,
7,
4);
s1
gelijk is aan Maxima
en s2
gelijk is aan Willem-Alexander
dan wordt s1
gelijk
aan MaxiAlexma
.
erase
.s1.erase(3,
10);
s1
gelijk is aan Willem-Alexander
dan wordt
s1
gelijk aan Wilder
.
replace
.s1.replace(0,
4,
"Jongs");
s1
gelijk is aan Maxima
dan wordt
s1
gelijk aan Jongsma
.
substr
.s1=s2.substr(7);
s1
wordt gelijk aan het deel van s2
dat begint
op positie 7. Als s2
gelijk is aan Willem-Alexander
dan zal s1
gelijk worden aan Alexander
.s1=s2.substr(7,
4);
s1
wordt gelijk aan het deel van s2
dat begint
op positie 7 en 4 karakters lang is. Als s2
gelijk is aan
Willem-Alexander
dan zal s1
gelijk worden aan
Alex
.
find
.int
i(s1.find("Alex"));
s1
gelijk is aan Willem-Alexander
dan wordt
i
gelijk aan 7. Als de string Alex
niet in
s1
voorkomt krijgt i
de waarde
string::npos
(een in de class string
gedefinieerde
constante). Eigenlijk moeten we deze regel als volgt programmeren:string::size_type
i(s1.find("Alex"));
string
wordt namelijk het type
size_type
gedefinieerd dat gebruikt moet worden om indexwaarden
van een string
in op te slaan.string::size_type
i(s1.find("le"));
string::size_type
j(s1.find("le",
7));
s1
gelijk is aan Willem-Alexander
dan wordt
i
gelijk aan 3 en j
gelijk aan 8.find
de string van voor naar
achter. Je kunt de memberfunctie rfind
gebruiken om van achter
naar voor te zoeken.
find_first_of
.string::size_type
i(s1.find_first_of("aeiou"));
s1
die voorkomt in de als parameter
meegegeven string
. Als s1
gelijk is aan
Maxima
dan wordt i
gelijk aan 1. Als de string
s1
geen van de karakters a
, e
,
i
, o
of u
bevat krijgt i
de waarde string::npos
.find_last_of
,
find_first_not_of
en find_last_not_of
spreekt denk
ik voor zich.
const char*
met
c_str()
.string
.
Voor het complete overzicht verwijs ik je naar de online C++ reference guide:
http://www.cppreference.com/cppstring/index.html.
Als je gebruik wilt maken van de Borland C++ Builder help file moet je niet
zoeken naar string
maar naar basic_string
voor
het complete overzicht. De typenaam string
blijkt een
andere naam te zijn voor het template type basic_string
. Het
begrip template wordt pas later behandeld.
iostream
library.
Zoals je hierboven hebt gelezen gebruiken we in C++ de input/output library
iostream
in plaats van de C library stdio
. Het
zal je niet verbazen dat de variabelen cin
en cout
die je in in het begin van deze opgave hebt leren kennen geen variabelen
maar objecten zijn. Je kunt deze objecten (net als objecten van de class
string
) dus ook messages sturen. Elke class definieert echter
zijn eigen messages. Het object cout
is een object van de class
ostream
en het object cin
is een object van de
class istream
. Later zal blijken dat dit niet helemaal
klopt maar dat maakt voor dit verhaal niets uit. Naar een object
van de class string
kun je bijvoorbeeld de message
size
sturen om te vragen hoeveel karakters het object bevat.
Naar cout
kun je deze message echter niet sturen (dit object
begrijpt deze message niet). De aanroep cout.size()
geeft tijdens
het compileren de volgende foutmelding: 'size' is not a member of
'ostream'
. Welke messages je naar cout
en
cin
kunt sturen kun je opzoeken in de helpfile van de class
ostream
respectievelijk istream
of in de online
C++ reference:
http://www.cppreference.com/cppio/index.html.
Bijvoorbeeld:
cout.fill('#');
cout.width(10);
int i = 189;
cout<<i<<endl;
Uitvoer:
#######189
Zie (als je het niet vanzelf snapt): http://www.cppreference.com/cppio/fill.html en http://www.cppreference.com/cppio/width.html.
Tot slot van deze inleiding volgt nog een voorbeeld
waarin met objecten
van de class
string
wordt gewerkt.
Merk op dat in dit programma gebruik maakt van de hierboven geïntroduceerde
class string
en de objecten cin
en
cout
. Dit programma bestaat uit één functie (de
functie main
). Dit is voor kleine programma's geen probleem.
Als een programma echter groter is of als het uitbreidbaar of onderhoudbaar
moet zijn kunnen we beter gebruik maken van de object georiënteerde
technieken die C++ biedt. Deze technieken zullen in de volgende
practicumopdrachten aan de orde komen.
#include <iostream>
#include <string>
using namespace std;
int main () {
cout<<"Geef je email adres: ";
string mailAdres;
cin>>mailAdres;
string::size_type indexAapje = mailAdres.find("@");
if (indexAapje != string::npos) {
cout<<"Gebruiker: "
<<mailAdres.substr(0, indexAapje)
<<endl;
cout<<"Machine: "
<<mailAdres.substr(indexAapje+1)
<<endl;
}
else {
cout<<mailAdres<<" is geen geldig email adres!"<<endl;
}
cout<<"Druk op de return-toets."<<endl;
cin.get();
cin.get();
return 0;
}
Deze opdracht bestaat uit de deelopdrachten 1a en 1b. Deze opdrachten moet je thuis afmaken en in de les van week 2 je laten aftekenen door de docent. Iedereen moet dus in week 2 met opdracht 2 gaan beginnen!
Opdracht 1a.
Compileer en test het bovenstaande programma
|