1 Texthantering I detta avsnitt tas det upp två sätt att arbeta med text i C++ program. Det första sättet som behandlas är hanteringen av textfält. Texter i C++ består utav en serie med enstaka tecken av typen char. Dessa serier med tecken kallas fält eller arrayer. 1.1 Typen char #include<iostream> Variabler av datatypen char representerar ett tecken som exempelvis bokstaven 'A'. Datatypen char är i grund och botten ett heltal bestående av en enda byte, alltså med ett väldigt litet talområde. Skillnaden mellan variabler av chartyp och heltalsvariabler visar sig när man skriver ut dem till skärmen. När en char variabel skrivs ut till skärmen visas ett tecken exempelvis 'A', när en heltalsvariabel skrivs till skärmen visas ett tal. Programexempel med char variabler. char tecken_1, tecken_2, tecken_3; tecken_1 = 'H'; tecken_2 = 'e'; tecken_3 = 'j'; cout<< tecken_1 << tecken_2 << tecken_3 <<endl; Resultatet av programmet blir Hej Press any key to continue Skrivsättet vid tilldelningarna exempelvis tecken_1 = 'H'; anger att variabeln ska tilldelas den teckenkod/värde som tecknet H har. Teckenkoderna för de flesta tecken hittar du i bilagan. Som tur är behöver man väldigt sällan bry sig om vilka teckenkoder ett visst tecken har, utan man kan nästan alltid använda sig av det skrivsätt som visats ovan. Man kan däremot i olika sammanhang utnyttja att char variabler är heltal, som i följande exempel Exempel, skriv ut alfabetet Tecknen 'A' till 'Z' ligger i ordnade i turordning i teckentabellen (se bilaga), man kan därför skriva ut alfabetet genom en iteration som startar på tecknet 'A' och slutar på tecknet 'Z' och för varje iteration skriver ut tecknet som representeras av iterationsvariabeln.
#include<iostream> for (char c='a'; c <='Z'; c++) cout<<c; Programmet ger resultatet ABCDEFGHIJKLMNOPQRSTUVWXYZ Press any key to continue Rad 6: Initieringen i for satsen kan läsas som tilldela heltalet/tecknet c teckenkoden till tecknet 'A' ( värdet 65 ) Rad 6: Iterationsvillkoret blir då, repetera tills heltalet/tecknet c har ett värde större än värdet/teckenkoden för tecknet 'Z'. 1.1.1 Char fält cout<<"hello World"; Vi har tidigare sett i samband med utmatningar hur texter skickats till utskrift genom exempelvis följande sats Uttrycket "Hello World" representerar ett char fält eller en char array. Termerna fält och array är synonymer. I datorns minne representeras detta som flera på varandra följande char instanser 1. H e l l o W o r l d \0 Varje cell representerar en byte av datorns minne. Det sista tecknet kallas för nolltecken och skrivs i programkod som '\0'. Teckenkoden för nolltecknet är 0. Nolltecknet har till uppgift att ange att texten slutar. Varje textfält måste ha ett avslutande nolltecken, i många fall erhåller fältet automatiskt ett nolltecken, men ibland måste man foga till nolltecknet uttryckligen i programkoden. Deklarationer av charfält Char fält kan deklareras ungefär på samma sätt som vanliga variabler, men man måste i deklarationen ange att det är fråga om ett fält. Fältdeklarationer kommer i två olika former 1: char min_text[] = "Hello World"; här skapas ett fält bestående av 12 tecken, och variabeln min_text kallas för en fältvariabel. 1 Termen variabel används ibland synonymt med instans. Det hade varit felaktigt i detta fall eftersom det inte finns någon variabelidentifierare som refererar till de olika tecknen som utgör texten.
Operatorn [], kallad indexeringsoperator, anger att det är frågan om ett fält. Vid deklarationer används indexeringsoperatorn för att ange en storlek på fältet. I 1 ovan behövs inte det eftersom storleken är antydd av texten "Hello World" = 11 tecken + nolltecknet = 12 tecken. Fältet min_text är således 12 tecken(bytes) stort. 2: char tom_text[12]; här skapas ett fält bestående av 12 ej initierade char instanser. Det faktum att de ej är initierade som i 1, gör att innehållet inte är definierat eller, om man så vill, innehåller skräp. Denna typ av deklaration har ett liknande syfte som en vanlig variabeldeklaration typ int i; dvs. att fungera som en behållare för texter. 1.2 Fältvariabler En fältvariabel fungerar inte riktigt på samma sätt som en vanlig variabel. Man ska betrakta en fältvariabel som att den anger var fältet börjar. En fältvariabel pekar ut det första tecknet i fältet, eller mer korrekt, den första char instansen. Den anger vilken minnescell, adress, som den första instansen befinner sig i. Man kan i ett schematiskt diagram åskådliggöra deklarationen char min_text[]="hello World"; som Variabel: min_text Adress: 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 Innehåll: 206 H e l l o W o r l d \0 Numreringen av cellerna är påhittad och är olika från programkörning till programkörning. Ett enklare men lika användbart diagram ser ut som min_text En egenskap hos fältvariabler är att de refererar alltid till samma fält. Detta innebär att char min_text[]="hello World"; char tom_text[12]; H e l l o W o r l d \0 // tilldelning? tom_text = min_text; // otillåtet, kan inte referera till nytt fält tom_text = "Hello World"; // otillåtet, samma operation som ovan inte fungerar. Man ska betrakta denna tilldelning som att man försökt låta pilen för tom_text peka på min_text fältet. min_text H e l l o W o r l d \0 tom_text Detta är alltså ej tillåtet eftersom man "slarvar bort" det ursprungliga tomma fältet.
1.2.1 Hantering av enstaka fältelement En fältvariabel tillåter genom indexeringsoperatorn att man manipulerar enstaka element. char hej_text[4]; // tomt hej_text[0] = 'H'; // obs första elementet är element 0 hej_text[1] = 'e'; hej_text[2] = 'j'; hej_text[3] = 0; // alternativt skrivsätt = '\0' Programraderna resulterar i att det fält som deklarerats att innehålla 4 char element tilldelas tecken för tecken innehållet. Obs, tecknet H är element nummer 0. min_text H e j \0 1.3 In och utmatning Man kan använda de vanliga in och utströmsoperatorerna på samma sätt som man gör för enkla variabler ex char in_text[256]; cout<<"hej vad heter du?"<<endl; cin>>in_text; cout<<"hejsan "<<in_text<<"!"; En programkörning kan då se ut som Hej vad heter du? Gunvald Hejsan Gunvald! Inmatningen fyller då de 8 första elementen i fältet in_text med texten "Gunvald" + nolltecken. Ett problem med denna typ av inmatning dyker upp om användaren skriver fler än ett ord per inmatning, exempelvis om programkörningen sett ut som Hej vad heter du? Gunvald Larsson Hejsan Gunvald! Det är inte utströmsoperatorn som felar utan det är sättet som inströmsoperatorn fungerar på som är otillräckligt. Det som händer är samma sak som tidigare att in_text fylls med texten "Gunvald" + nolltecken och texten Larsson lämnas kvar i inmatningsbufferten för senare inläsning.
Inströmsoperatorn ser alltså "Gunvald Larsson" som två texter eftersom de är åtskilda med ett så kallat vittecken (eng. whitespace). All inmatning med >> operatorn, sk. formaterad inmatning, skiljer element med vittecken. Vittecken är blankstegstecken, tab tecken, nyrad tecken och vagnreturtecken. Jämför sidan 7, inmatning av två flyttal. Det finns två sätt att komma runt denna egenskap hos inströmsoperatorn 1. Gör två inmatningar 2. Använd medlemsfunktionen getline Exempel getline char in_text[256]; cout<<"hej vad heter du?"<<endl; cin.getline(in_text, 255); cout<<"hejsan "<<in_text<<"!"; #include<iostream> getline kallas för medlemsfunktion eftersom den verkar på cin (är medlem av cin). Detta är det första exemplet på ett funktionsanrop. ( ) kallas funktionsoperatorn och har en hög prioritet. Innanför parenteserna anger man funktionsargument. Det första argumentet in_text anger en fältvariabel (eller motsvarande) som ska ta emot den inmatade texten. Det andra argumentet anger den maximala längden på den inmatade texten i form av ett heltal (int). Exempel, in och utmatning med teckenfält char hej[]="välkommen till 10000 kronorsfrågan"; char fraga[]="ange ditt för och efternamn"; char fornamn[80], efternamn[80]; cout<<hej<<endl; cout<<fraga<<endl; cin>>fornamn>>efternamn; cout<<"hej "<<fornamn<<" "<<efternamn<<endl; 1.3.1 Övning Skriv om exemplet ovan så att samma funktion fås med funktionen getline 1.3.2 Texthanteringsoperationer Att handskas med text på detta sätt gör att enkla operationer som tilldelning, jämförelser osv blir ganska besvärliga. De naturliga operatorerna som =, ==, < etc. kommer istället för att tilldela och jämföra texter, bara att tilldela och jämföra fältvariabler. Fältvariabler anger ju bara vilken position/adress i minnet texten befinner sig. Därför är man tvungen att gå vissa omvägar för att exempelvis göra en tilldelning.
#include<iostream> Exempel, tilldelning av ett textfält char source[]="den ursprungliga texten"; char target[80]; // en tilldelning görs genom att kopiera element för element for( int idx=0; source[idx]!=0; idx++) target[idx]=source[idx]; target[idx]=0; cout<<"efter tilldelningen innehåller target:"<<target; Principen är att med iteration löpa igenom varje tecken i source och gör tilldelningen tecken för tecken till target. Notera särskilt hur indexoperatorn används. Operationen på rad 11 ska ses som en (char) = (char) operation. Ej (char[])=(char[])! Indexoperatorn väljer alltså ut en speciell char ur ett charfält. Rad 10: Variabeln idx (index) initieras till noll, vilket kan ses som att iterationen börjar på fältets första element. Jämför avsnitt Hantering av enstaka fältelement ovan. Rad 10: source[idx]!=0, iterationsvillkoret anger att repetionen skall fortgå tills nolltecknet (textslut) påträffas. Detta är en väldigt viktig egenskap att komma ihåg för alla textfält. Eftersom ett fält inte innehåller någon information om storleken på fältet har man alltid ett nolltecken som avslutar varje textfält. Rad 10: idx++ nästa tecken i fälten Rad 11: för över det tecken som finns på position idx i source fältet till samma position i target fältet. Rad 12: Iterationen slutar innan nolltecknet förs över. Satsen garanterar att även target avslutas med ett nolltecken. 1.3.3 Övning: Tänk igenom programexekvering rad för rad, sats för sats, och se till att du förstår meningen hos varje programrad. Detta är en standardkonstruktion av en for sats för fält som du måste behärska.
#include<iostream> Exempel, att räkna längden på en text. char in_text[255]; // cout<<"mata in en textrad"<<endl; cin.getline( in_text, 255); for( int idx=0; in_text[idx]!='\0'; idx++); cout<<"längden på den inmatade texten är:"<<idx<<"tecken"; Principen är att gå igenom textfältet tecken för tecken tills man når nolltecket som anger slut på texten. Rad 9: cin.getline( ) används för att kunna ta emot inmatning av flera ord. Rad 11: Observera semikolon ; direkt efter for satsen. Iterationen utför inget annat än att räkna upp idx variabeln tills iterationsvillkoret uppnåtts. 1.3.4 Övning: Även här måste du tänka igenom programexekvering rad för rad, sats för sats, och se till att du förstår meningen hos varje programrad. Som tidigare är detta en standardkonstruktion av en forsats för fält som du måste behärska. 1.4 Texthanteringsfunktioner I det tidigare avsnittet visades hur for satser var nödvändiga för att göra vanliga textoperationer. Naturligtvis har man sett ett behov av att göra detta på en enklare sätt. Det finns ett bibliotek eller headerfil som heter cstring (string.h på äldre kompilatorer) där många texthanteringsfunktioner finns för att göra de vanligaste funktionerna. Funktioner i cstring (urval) strcpy string copy tilldelning strcat string concatenate konkatenering (ihopslagning) strcmp string compare alfabetisk jämförelse strlen string length textlängd
Syntax funktioner i cstring strcpy(char * target, const char * source):char * den kopierade texten strcat(char * target, const char * source):char * den konkatenerade texten strcmp(const char * txt1, const char * txt2):int se nedan strlen(const char * ):int längden, antal tecken Typen char * i tabellen ovan är en variant av char fält som vi kan betrakta som likvärdiga variabler som deklarerats som char text[80]; dvs ett vanligt textfält. (Typen char * är en pekartyp, en pekare är en variabel som pekar ut olika positioner i minnet, som här; början på texten/första tecknet i texten). Typen const char innebär en garanti att inget tecken i texten kommer att ändras. strcat: Att konkatenera texterna "Hello " och "World" ger resultatet "Hello World". strcmp: Det heltal som anges som returvärde kan anta tre olika slags värden <0: txt1 kommer före txt2 alfabetiskt. Mer korrekt enligt teckentabellen vilket innebär att alla stora bokstäver kommer före alla små bokstäver i "alfabetet" (se bilaga för teckenkoder). 0: txt1 och txt2 innehåller likadan text >0: txt2 kommer före txt1. Syntaxtabellen ska tolkas som ex Funktionen strcmp tar 2 argument av textfältstyp, returvärdet är ett heltal. Ett lösryckt avsnitt kan då se ut som: char passwd[80]; cin>>passwd; if( strcmp(passwd, "hemligt") == 0) cout<<"korrekt lösen"; else cout<<"felaktigt lösen"; Notera speciellt hur jämförelsen == 0 måste göras för att kontrollera likhet. Det naturliga för likhet är vanligtvis något i stil med if ( lika(text1,text2)==true). Det konstiga med detta är att heltalet 0 betyder logiskt false. Rad 3: strcmp( ) kallas för funktionsanrop. De argument som ges innanför parenteserna kallas aktuella argument. Det som händer är att ett underprogram anropas/utförs med de aktuella argumenten. Då underprogrammet har utfört sitt arbete lämnar det tillbaka resultatet av programmet i form av returvärdet. Det är sedan med detta returvärde som jämförelsen görs. Dvs if ( (returvärde) == 0) Underprogrammet för strcmp (och de andra funktionerna) är en del av det som kallas standardbiblioteket.
Exempel, användning av texthanteringsfunktioner. #include<iostream> #include<cstring> // för texthanteringsfunktioner char namn1[255]; char namn2[255]; // cout<<"mata in ett efternamn"<<endl; cin>> namn1; cout<<"mata in ett annat efternamn"<<endl; cin>> namn2; int len_namn1 = strlen( namn1 ); cout<<"det första namnet innehåller:"<< len_namn1 <<" tecken"<<endl; int jmf = strcmp( namn1, namn2 ); cout<<namn1<<" kommer "; if ( jmf < 0) cout<< "före "<<namn2; else cout<< "efter "<<namn2; cout<<" i ordlistan"<<endl; char namn1och2[512]; // tilldelning namn1 > namn1och2 strcpy( namn1och2, namn1 ); // lägg till texten ", " strcat(namn1och2, ", "); // lägg till namn2 strcat(namn1och2, namn2); cout<<"de båda namnen var "<<namn1och2<<endl; Exempel på programkörning
Mata in ett efternamn Larsson Mata in ett annat efternamn Svensson Det f rsta namnet innehõller:7 tecken Larsson kommer f re Svensson i ordlistan De bõda namnen var Larsson, Svensson Press any key to continue Vanligtvis ignoreras de returvärden som erhålls från funktionerna strcpy och strcat. Dessa funktionsanrop är väldigt viktiga för förståelsen för skrivsättet vid funktionsanrop. De är dessutom viktiga för förståelsen av syftet med funktioner. for satsen i exemplet, att räkna längden på en text, är förbluffande lik det underprogram som utförs då funktionen strlen anropas. Teknisk not: Jämför gärna funktioner och operatorer. Ex och strcat( txt1, txt2 ): resultat tal1 + tal 2: resultat en giltig omskrivning av tal1 + tal 2 skulle vara operator+ (tal1, tal2 ): resultat Där enda skillnaden är funktionsnamnet strcat respektive operator+ och dess datatyper. Skillnaden mellan funktioner och operatorer är språkmässigt mest syntaktisk. Det finns en viss fast uppsättning operatorer +,, * osv. medan funktionsnamn och funktioner kan skapas helt efter behov. Annars är den bakomliggande principen densamma. Det finns en operator ( ) funktionsoperatorn (se operatortabell i bilaga) som utvärderas på samma sätt som övriga operatorer, sånär som på det syntaktiska. 1.5 Datatypen string Det finns i moderna C++ miljöer en datatyp speciellt utvecklad för att handskas med text som en enkel datatyp. För denna datatyp fungerar de enkla operatorerna på ett naturligt och enkelt sätt. Datatypen heter string och är en sk. klasstyp. Det innebär att typen är skapad med de enkla datatyperna som bas men med funktioner och operatorer omskrivna så att de kan användas på ett naturligt sätt. Typen är definierad i en header string. Headern cstring behövs inte om man använder typen string.
#include<iostream> #include<string> Exempel, operationer för text av typen string string text1 ="Hello World"; // deklaration + initiering string text2; // deklaration text2 = text1; // enkel tilldelning, ingen strcpy if ( text2== text1) // sant, jämför textinnehållet cout<<"texterna är lika"<<endl; if ( text2 < text1) // ej sant, jämför ordning cout<<"text2 är före i ordlistan"<<endl; text1 = "Hello"; text1 +=" "; // konkatenering text2 = text1 + "World"; // konkatenering cout<< text2<<endl; // ger texten "Hello World" int len_text2 = text2.size(); cout<< "består av "<< len_text2 <<" tecken"<<endl; Datatypen string kan i många lägen helt ersätta det gamla skrivsättet med char fält. Programmet ger följande utskrift Texterna õr lika Hello World bestõr av 11 tecken Press any key to continue 1.5.1 Övning: Ersätt textfälten i de tidigare exemplen med texter av typen string.