Opdracht 1: Gebruik van string en iostream.

© Harry Broeders.

Deze opdracht is bedoeld om je C kennis op te frissen en behandelt daarnaast enkele kleine verbeteringen van C++ ten opzichte van C. 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).

Lees voordat je met deze opgave gaat beginnen eerst hoofdstuk 1 van het dictaat door.

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

  1. Deze regel geeft de volgende foutmelding: "Lvalue required". 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 (of zelfs andere programma's) plotseling niet meer correct werken omdat hun geheugen 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. De vergelijking 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.

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;
   }
   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 in hoofdstuk 3 van het dictaat 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

De mogelijkheden van het type string.

Voor het complete overzicht verwijs ik je naar de helpfile van de class string. Als je in Borland C++ Builder het woordje string intypt en op F1 drukt moet je kiezen voor 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 in hoofdstuk 4 van het dictaat behandeld.

In deze paragraaf worden enkele voorbeelden gegeven.

De iostream library.

Zoals je in paragraaf 1.7 van het dictaat 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 paragraaf 1.7 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. Bijvoorbeeld:

cout.fill('#');
cout.width(10);
int i(189);
cout<<i<<endl;

Uitvoer:
#######189

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 de vernieuwingen die in C++ zijn ingevoerd ten opzichte van C namelijk het gebruik van abstracte data typen en object georiënteerde technieken in dit programma nog niet toegepast zijn. In dit programma wordt C++ dus op een C manier gebruikt. 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;
}

Opdrachtomschrijving.

Deze opdracht bestaat uit de deelopdrachten 1a tot en met 1c. Opdrachten 1b en 1c moet je laten aftekenen door de docent.

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 Borland C++ Builder. Lees eerst de inleiding in het gebruik van C++ Builder.

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.thrijswijk.nl/sopx2/pract/opd1a.cpp
kan worden gesplitst in:
protocol:  http
machine:   bd.thrijswijk.nl
directory: sopx2/pract
file:      opdr1a.cpp
Schrijf een programma dat een ingetypte URL spitst op de hierboven beschreven methode.

Opdracht 1c.

Soms is alleen 1 van de 4 onderdelen (protocol, machine, directory of file) nodig. Het ontleden van de hele URL is dan overbodig. Schrijf één of meer functies zodanig dat het volgende testprogramma correct werkt. Lees indien nodig eerst paragraaf 1.10 en 1.11 van het dictaat.

int main() {
   string tURL("http://bd.thrijswijk.nl/sopx2/pract/opd1c.cpp");
   cout<<"test 1:"<<endl; OntleedUrl(tURL);
   cout<<"test 2:"<<endl; OntleedUrl(tURL, "file");
   cout<<"test 3:"<<endl; OntleedUrl(tURL, "directory");
   cout<<"test 4:"<<endl; OntleedUrl(tURL, "machine");
   cout<<"test 5:"<<endl; OntleedUrl(tURL, "protocol");
   cin.get();
   return 0;
}

De uitvoer moet dan zijn:
test 1:
protocol: http
machine: bd.thrijswijk.nl
directory: sopx2/pract
file: opdr1c.cpp
test 2:
file: opdr1c.cpp
test 3:
directory: sopx2/pract
test 4:
machine: bd.thrijswijk.nl
test 5:
protocol: http

verder met opdracht 2 ...