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 objectgeorië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 = 0;
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 of 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 <stdio.h>
int main(void) {
char naam[10];
naam = "Harry"; // Error! Zie opmerking 1.
strcpy(naam, "Harry"); // OK
printf("%s\n", naam);
strcpy(naam, "Willem-Alexander"); // Error! Zie opmerking 2.
strcpy(naam, "Alex"); // OK
printf("%s\n", naam);
if (naam == "Alex") { // Error! Zie opmerking 3.
// ...
}
if (strcmp(naam, "Alex") == 0) { // OK
// ...
}
}
Opmerkingen:
'=' : left operand must
be l-value
. Aan een array variabele (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. '==' : logical operation on address of string
constant
). De vergelijking naam == "Alex"
levert 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;
}
cin.get();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 syntax voor het versturen van een message is:
naam-van-object.naam-van-message(parameters)
Dus eerst de naam van het object waar de message naartoe 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:
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"); // niet helemaal goed! Zie
hieronder
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). Het gebruik van het type
int
is niet correct, we moeten in
plaats van het type int
het type
string::size_type
gebruiken: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. Het zou namelijk
zo kunnen zijn dat een indexwaarde van een string niet in een int
past (bijvoorbeeld op een 64 bits
machine). 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.string::size_type i = s1.rfind("le");
s1
gelijk is aan "Willem-Alexander"
dan wordt i
gelijk aan 8. 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
.string::size_type i = s1.find_first_of("aeiou", 2);
s1
gelijk is aan "Maxima"
dan wordt i
gelijk aan
3.find_last_of
,
find_first_not_of
en find_last_not_of
spreekt
denk ik voor zich. 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 Microsoft Visual C++ 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:
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 objectgeorië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 |