Strängar och tecken
Table of Contents
1 Tecken
I C är en variabel som deklarerats med typen char
inget annat än ett
(något förklätt) heltal (jämför med Char
i till exempel Haskell, som
är en helt egen datatyp). Teckenkonstanter som 'a'
och '?'
är
precis samma sak som motsvarande ASCII-värde (97 respektive 63). Att
skriva 'a' + 3
är alltså helt okej i C, och det kommer ge precis samma
resultat som om man skrev 97 + 3
.
1.1 printf
Hittills har vi använt printf
utan att reflektera så mycket över hur
den funktionen fungerar. Det ska vi råda bot på nu!
printf
är en funktion som används för att skriva ut formaterad text
(f:et i slutet av namnet står för “format”). Funktionen tar en så kallad
formatsträng och ett godtyckligt antal andra värden som argument.
Formatsträngen är en vanlig sträng som kan innehålla ett antal tecken
med speciell betydelse. Så kallade “escape-characters” inleds med
backslash och används för att skriva ut speciella tecken, till exempel
\n
för radbrytning eller \t
för tab. Anropet
printf("Hej!\nHopp\tHejsan!\n");
skriver ut “Hej!” och “Hopp Hejsan” på varsin rad, med en tab mellan “Hopp” och “Hejsan”.
Formatsträngen kan också innehålla “formatspecifikationer”, som inleds
med ett procenttecken (%
). Formatspecifikationer anger hur de andra
argumenten till printf
ska skrivas ut. Den första
formatspecifikationen i strängen anger formatet för det första
argumentet efter formatsträngen, den andra för den andra, och så vidare.
Exempel på formatspecifikationer är
%d
– Ett heltal%f
– Ett flyttal%c
– Motsvarande ASCII-tecken%s
– En sträng
Om vi antar att name
är en sträng och age
ett heltal så skriver
följande anrop ut båda dessa värden:
printf("Jag heter %s och är %d år gammal", name, age);
Som en kort övning på att använda printf ska vi nu skriva ett program som skriver ut hela ASCII-tabellen:
- Öppna en ny fil och döp den till
ascii.c
. - Skriv det gamla vanliga main-skelettet och inkludera
stdio.h
. Skriv enfor
-loop där loop-variabeln går från 0 till och med 127. Inuti loop-kroppen ska du skriva ett anrop tillprintf
som skriver ut loop-variabelns värde både som heltal och som ASCII-tecken. När du är klar borde resultatet se ut ungefär så här:
#include <stdio.h> int main(){ for (int i = 0; i < 128; ++i){ printf("%d\t%c\n", i, i); } return 0; }
- Notera att
i
förekommer som argument till funktionen två gånger. Den första gången skrivs det ut som ett heltal (genom%d
), och den andra gången som ett tecken (genom%c
). Prova att kompilera och köra och se om utskriften verkar vettig (notera att alla ASCII-tecken inte går att skriva ut, till exempel är 10 ASCII-värdet för radbrytning). - Som avslutning kan vi göra tabellen lite snyggare genom att sätta dit rubriker. Skriv dit följande rader högst upp i main-funktionen:
printf("ASCII\tChar\n"); printf("=====\t====\n");
Att det sitter en tab (\t
) mellan kolumnerna ser till att det blir ett
jämnt avstånd mellan dem.
printf
erbjuder många fler möjligheter sätt att ange format
(hexadecimal form, tiopotens, antal värdesiffror, vänsterjustering…).
Du kan läsa man-filen för printf
för att lära dig mer!
2 Strängar
På samma sätt som med tecken så finns det ingen speciell datatyp för
strängar. En sträng representeras helt enkelt som en array av char=s,
som avslutas med ett =NUL
-tecken ('\0'
, ASCII-värdet 0). Strängen
"IOOPM"
är alltså en array bestående av sex tecken,
{'I', 'O', 'O', 'P', 'M', '\0'}
. Denna array är i sin tur är precis
samma sak som en array bestående av heltalen {73, 79, 79, 80, 77, 0}
.
Nu ska vi titta på och ändra i ett program som använder strängar:
- Öppna filen
cheerleader.c
. - Försök lista ut vad programmet gör. Kompilera sedan och kör.
- På första raden i
main
-funktionen deklareras en array av chars som initieras med strängen “Uppsala”. Vi hade precis lika gärna kunnat skriva ut det som en array:
char name[] = {'U', 'p', 'p', 's', 'a', 'l', 'a', '\0'};
Eftersom tecken i C bara är kortformer för deras ASCII-koder hade vi också kunnat skriva
char name[] = {85, 112, 112, 115, 97, 108, 97, 0};
men det torde vara uppenbart att det är mer praktiskt att skriva som det
redan står i filen (notera att det avslutande NUL
-tecknet är implicit
när man skriver en sträng inom citattecken).
- Loopen i funktionen
cheer
låter loopvariabelni
gå från 0 till (men inte med) det första värde därname[i]
är'\0'
. Loopen itererar alltså över hela strängen och terminerar när den når det avslutandeNUL
-tecknet iname
.
Vad händer i loop-kroppen då? Jo, först läses det i
:te tecknet ut i
variabeln letter
, sen skrivs det ut två gånger på varsin rad (först
som “Ge mig ett …”, sen som ett svar).
Efter loopen används funktionen puts
för att skriva ut strängen “Vad
blir det?”, och till sist strängen name
. Notera att puts
automatiskt
lägger till en radbrytning efter utskriften (i printf
var vi tvungna
att själva lägga till '\n'
), och att funktionen kan skriva ut en
strängvariabel direkt.
- Nu är det dags att utöka programmet! Till att börja med skulle vi
vilja att det var lite mer umpf i svaren. Om man inkluderar
ctype.h
(på samma sätt somstdio.h
är inkluderad) får man tillgång till funktioner som kontrollerar och manipulerarchar=s (se man-filen för =ctype
). Till exempel finns funktionentoupper
som gör om en bokstav till motsvarande versal (“stora bokstav”). Inkluderactype.h
och ändra den sista raden i loopen till:
printf("%c!\n", toupper(letter));
Kompilera och kör programmet igen för att kontrollera resultatet. Prova
gärna att byta strängen i name
. Prova också att använda tolower
för
att göra om letter
till en gemen (“liten bokstav”) i den första
utskriften.
- Vi skulle också vilja att den avslutande utskriften var lite mer
övertygande. Eftersom störst går först så löser vi det på samma sätt
som sist, genom att skriva ut hela strängen som versaler. Det gör vi
genom att iterera över strängen och ändra varje tecken till
motsvarande versal Skriv en sån loop innan den sista utskriften i
cheer
! (OBS Genom att göra detta introducerar vi en bugg! Se nästa avsnitt.)
När du är klar borde det se ut ungefär så här:
for (int i = 0; name[i] != '\0'; ++i){ name[i] = toupper(name[i]); }
2.1 string.h
Slutligen ska vi titta på lite av vad man kan göra med de funktioner som
finns i Cs strängbibliotek. För att komma åt dessa funktioner behöver du
inkludera string.h
. Vi håller oss kvar i cheerleader.c
ett litet tag
till, och lägger märke till att funktionen cheer
permanent ändrar på
sitt argument (prova att skriva puts(name)
efter funktionsanropet).
Bara för att man ville få sitt namn ropat av en hejaklack är det inte
säkert att man ville få sitt namn permanent ändrat till något annat. Det
här ska vi fixa nu!
- Vi ska se till att det första
cheer
gör är att kopiera sitt argument till en ny sträng. Först låter vi cheer deklarera en ny sträng, men för att göra det måste vi veta hur lång den ska vara. Vi vill ha en array avchar=s som är lika lång som strängen =name
plus ett förNUL
-tecknet. Istring.h
finns en funktion som heterstrlen
som returnerar längden av en sträng (exklusiveNUL
-tecknet). Vi inledercheer
med följande två rader:
int len = strlen(name); char newName[len + 1];
- Härnäst vill vi kopiera innehållet i
name
tillnewName
. Det gör vi med funktionenstrncpy(s1, s2, n)
, som kopierar strängens2
tills1
, dock som mestn
tecken. Funktionen förutsätter att det finns tillräckligt med ledigt minne för att utföra kopieringen, men det har vi ju sett till i deklarationen avnewName
(funktionenstrcpy
fungerar på samma sätt, fast sätter inget maxtak på antalet tecken. Detta gör det svårare att garantera att man inte skriver utanför =s1=s gränser!).
strncpy(newName, name, len);
- Nu är det bara att ändra alla förekomster av
name
icheer
tillnewName
(kom ihågM-x query-replace
). När du är klar kan du jämföra medcheerleader_finished.c
. Prova att skriva utname
efter anropet tillcheer
och se att strängen inte har ändrats (Fundera gärna på om man kan skriva funktionencheer
utan att användastrncpy
)
Det finns många fler funktioner i strängbiblioteket, till exempel för
att jämföra strängar, slå ihop strängar eller leta efter förekomster av
ett visst tecken. Dubbelkolla alltid vad som finns i string.h
(man string
) innan du skriver en egen strängfunktion!
2.2 Att ta med sig
- Ett tecken är precis samma sak som ett heltal. Det är hur man tolkar värdet som spelar roll
- Strängar är bara arrayer av
char=s. Glöm inte att se till att det finns plats för =NUL
-tecknet också! En sträng som är n tecken lång tar upp n + 1 =char=s. - En sträng är inte en lista som i Haskell. Tänk alltid på strängar som arrayer, inget annat.
- Sa vi att en sträng bara är en array?
Questions about stuff on these pages? Use our Piazza forum.
Want to report a bug? Please place an issue here. Pull requests are graciously accepted (hint, hint).
Nerd fact: These pages are generated using org-mode in Emacs, a modified ReadTheOrg template, and a bunch of scripts.
Ended up here randomly? These are the pages for a one-semester course at 67% speed on imperative and object-oriented programming at the department of Information Technology at Uppsala University, ran by Tobias Wrigstad.