Opdracht 1: Gebruik van 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;

Input en output met << 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.

De problemen met 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.

De oplossing in C++: het gebruik van 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.

Het 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 problemen met strings in C.

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:

#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:

  1. Deze regel geeft de volgende foutmelding: '=' : 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.
  2. Deze regel geeft tijdens het compileren geen foutmelding. Tijdens het uitvoeren van het programma kan het programma echter vastlopen of zelfs spontaan de harde schijf gaan formatteren! Dit komt doordat er 17 karakters (even natellen, en het nul-karakter niet vergeten) naar de array naam worden gekopieerd. Terwijl er maar 10 karakters gereserveerd zijn. In C wordt hier echter helemaal niet op gecontroleerd en de 7 extra karakters worden gewoon naar het geheugen geschreven (achter de 10 gereserveerde karakters). Dit kan tot gevolg hebben dat andere delen van je programma plotseling niet meer correct werkt omdat een variabele uit dit deel van het programma overschreven wordt. De wet van Murphy zegt: Het programma zal pas vastlopen door deze fout als je het aan je belangrijkste klant demonstreert!
  3. Ook deze regel geeft tijdens het compileren geen foutmelding (wel een warning: '==' : 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.

De oplossing in C++: het type 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:

#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:

  1. De C++ include file <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>.
  2. Je kunt gewoon een waarde toekennen aan een variabele van het type string met behulp van de operator =.
  3. Het type string is dynamisch en "groeit" als dat nodig is!
  4. Je kunt variabelen van het type string gewoon vergelijken met behulp van de operator ==.

Je eerste stap op weg naar object oriëntatie.

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:

De mogelijkheden van het type string.

In deze paragraaf worden enkele voorbeelden gegeven.

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.

Nogmaals: De 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.

Voorbeeldprogramma.

Tot slot van deze inleiding volgt nog een voorbeeld De link voor dit plaatje verwijst naar een C++ file getest met Borland C++ 5.02. 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;
}

Opdrachtomschrijving.

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 De link voor dit plaatje verwijst naar een C++ file getest met Borland C++ 5.02. met behulp van Microsoft Visual C++ 2013 Express Edition. Lees eerst de inleiding in het gebruik van Visual C++ 2013 Express Edition.

Opdracht 1b.

Het adres van een webpagina (een URL) kan opgesplitst worden in 4 delen. Het protocol, de machine, de directory en de file.
Bijvoorbeeld:
http://bd.eduweb.hhs.nl/ogoprg/pract/opd1a.cpp
kan worden gesplitst in:
protocol:  http
machine:   bd.eduweb.hhs.nl
directory: ogoprg/pract
file:      opdr1a.cpp

Schrijf een programma dat een ingetypte URL spitst op de hierboven beschreven methode.

Het programma moet ook correct werken als de invoer niet correct is:
http:/bd.eduweb.hhs.nl/ogoprg/pract/opd1a.cpp
moet als uitvoer geven:
Deze URL is niet correct.

Hou er ook rekening mee dat er niet atijd een directory aanwezig is in de URL:
https://bd.eduweb.hhs.nl/index.html
moet worden gesplitst in:
protocol:  https
machine:   bd.eduweb.hhs.nl
directory:
file:      index.html

verder met opdracht 2 ...