De standard C++ string

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.

Nadelen van C-strings

C-strings hebben twee belangrijke nadelen: onnatuurlijke manipulatie en allocatieproblemen.

Onnatuurlijke manipulatie

Zelfs voor de meest eenvoudige bewerkingen op strings moet men functies gebruiken. De belangrijkste worden gedefinieerd in string.h:

Het is niet mogelijk om = 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.

Allocatieproblemen

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.

std::string

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;

Strings maken, kopieren, aaneenschakelen

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.

Strings vergelijken

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 > hond
Net 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 en functies

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!

Strings indexeren

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.

Voorbeeld: de functie 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.

Controleren of string leeg is

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]