Noch C noch C++ hebben eigenlijk een ingebouwd
stringtype.
Nochtans is de C-conventie om een string voor te stellen
als een tabel lettertekens (char array), afgesloten
door een nulkarakter ('\0'), zodanig
ingeburgerd dat men kortweg van de C-string spreekt.
Er zijn evenwel andere representaties mogelijk.
In plaats van een afsluitend nulkarakter toe te voegen zou men
bijvoorbeeld de lengte van de string kunnen
bijhouden. Omdat data (de lettertekens) en lengte
samenhoren kan men ze best in een
struct steken:
struct str {
char *data;
int len;
};
Dit is de basis van een groot aantal varianten
stringklassen in C++.
Tot voor kort ontwikkelde elke compilerbouwer
(Borland, Microsoft, ...) zijn eigen stringklasse,
wat niet erg bevorderlijk was voor de portabiliteit.
Gelukkig is er sinds enkele jaren een C++ standaard.
De officiële standard string is de STL (standard template library) string.
C-strings hebben twee belangrijke nadelen: onnatuurlijke manipulatie en allocatieproblemen.
Zelfs voor de meest eenvoudige bewerkingen op strings moet men functies gebruiken. De belangrijkste worden gedefinieerd in string.h:
= of +
te gebruiken om strings
te dupliceren of aaneen te schakelen, zoals
we bij andere eenvoudige objecten doen.
Bovendien moet men de juiste syntax van de functies
en het type en de volgorde van de argumenten kennen.
Indien een C-string aangemaakt is als een karaktertabel
van lengte N, dan
kan ze niet meer dan N lettertekens
bevatten (eigenlijk N-1 wegens het
afsluitende nulkarakter).
Het precieze aantal letters van een string
is echter vaak op voorhand niet gekend.
Traditioneel voorziet men dan "meer dan voldoende" plaats:
char s[100]; cin >> s; // wat als meer dan 100 letters ??Dit is uiteraard een gevaarlijke praktijk.
Indien het precieze aantal lettertekens wel gekend is kan men dynamisch alloceren:
char* s = new char[aantal];Men mag dan echter niet vergeten te dealloceren door
delete [] s;Dit is vooral een probleem indien een nieuwe string gealloceerd wordt in een functie zonder dat de oproeper dit in de gaten heeft. Bijvoorbeeld:
char* s = datum_string();
// opgelet: new in functie; vergeet delete[] niet!!
Men kan dit probleem oplossen door strings als objecten van een stringklasse voor te stellen, waarbij allocatie in de
constructor gebeurt en deallocatie in de destructor. De destructor wordt vanzelf opgeroepen indien het object ophoudt te bestaan.
De klasse string wordt gedefinieerd in de header
<string> (niet te verwarren met
<string.h> die gebruikt wordt voor
traditionele C-strings!).
De klasse zit in namespace std. De volledige naam is dus std::string.
Om de namespaceprefix niet elke keer te moeten typen
kan men een using-constructie toevoegen.
In een programma dat standard strings wenst te gebruiken
vindt men typisch
#include <string> // geen .h !! using namespace std;
Standard strings zijn zeer eenvoudig en intuïtief te gebruiken. Het programma
string s1 = "hallo";
string s2(" wereld");
string s3; // default = ""
string s4(s2);
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
s3 = s1;
cout << s3 << endl;
cout << (s1+s2) << endl;
s1 += '!';
cout << s1 << endl;
geeft als output:
hallo
wereld
wereld
hallo
hallo wereld
hallo!
In het voorbeeld worden drie vormen van constructie
gedemonstreerd. De eerste twee zijn equivalent.
De default constructor geeft een lege string ("").
Een string wordt gewoon gekopieerd door de operator =.
De vervelende strcpy() hebben we dus niet meer nodig.
Twee strings kunnen aaneengeschakeld worden door
de operator +, wat er heel wat
natuurlijker uitziet dan strcat().
Men kan ook een karakter bij een string optellen.
Naast de operator + is natuurlijk ook de
operator += gedefinieerd.
Voor het vergelijken van strings hebben we geen functie
strcmp() meer nodig. Gebruik gewoon operatoren
==, !=, <,
<=, >, >=.
Bijvoorbeeld:
string s = "kat", t = "hond";
if (s == t)
cout << s << " == " << t << endl;
if (s != t)
cout << s << " != " << t << endl;
if (s < t)
cout << s << " < " << t << endl;
if (s > t)
cout << s << " > " << t << endl;
geeft als output:
kat != hond kat > hondNet zoals
strcmp() hebben deze
vergelijkingsoperatoren betrekking op de lexicografische
volgorde. De volgorde van twee strings wordt bepaald
door de ASCII-code van het eerste karakter dat verschilt, enz.
Enkele voorbeelden:
"a" < "b" "A" < "a" "0" < "1" "abcd" < "abce" "aap" < "aapje"
Strings kunnen eenvoudig als argumenten meegegeven worden aan functies. Ook het retourneren levert geen problemen op:
string verdubbel(string s) {
return s + s;
}
Hierbij treden geen allocatieproblemen op: new
en delete gebeuren automatisch achter de schermen.
Opmerking: hoewel een string een "groot" object is, is het niet nodig een string-argument als reference door te geven uit efficientie-overwegingen. De stringklasse is op vernuftige wijze geoptimaliseerd: er wordt eigenlijk intern een referentie doorgegeven; de data zelf worden slechts gekopieerd indien de kopie gewijzigd wordt!
Een aangenaam facet aan de standard string is dat men
individuele lettertekens kan manipuleren door indexering:
het i-de element
van een string s is
gewoon s[i] zoals bij een tabel. Dit is een char. Zoals bij C-strings start de nummering
aan nul.
Het aantal karakters in een string verkrijgt men door
de lidfunctie size()
. Merk op dat dit een O(1) operatie is, in tegenstelling tot de klassieke strlen()
die O(N) is.
naar_hoofdletters() zet een string om naar hoofdletters:
void naar_hoofdletters(string &s) {
for (int i = 0; i < s.size(); i++)
if (s[i] >= 'a' && s[i] <= 'z')
s[i] = s[i] - 'a' + 'A';
}
In dit geval is het argument natuurlijk
wel een reference want het is een uitvoerparameter.
Opmerking: eigenlijk is het beter het type
string::sizetype te gebruiken voor de index
i in plaats van int.
Ook size() geeft eigenlijk een string::sizetype terug.
Zie verder.
Om te checken of een string leeg zijn er verschillende equivalente mogelijkheden:
if (s == "") ... if (s.size() == 0) ... if (s.empty()) ...
[W. Schepens sept. 2003]