Variabler och strängar

Variabler och strängar

I detta kapitel ska vi se närmare på vad variabler är, vilka typer av variabler som finns och hur de används. Vi kommer även att behandla strängar. De påminner mycket om variabler även om de inte är det. 

Vad är en variabel?

Inom programmering är en variabel ett namngivet objekt som har och kan ändra värde. Det finns flera olika typer av variabler som används i olika sammanhang. Variablerna som vi ska se närmare på är:

  • int
  • byte
  • long
  • float
  • boolean
  • char.

Int

En int är en av de vanligaste variabeltyperna. Int är en förkortning för integer (engelska för heltal) och används för att lagra heltalsvärden. Den är praktisk i bland annat följande situation där Alice och Bob plockar äpplen i korgar. Korgarna som de använder finns i två olika storlekar där den lilla korgen rymmer 25 äpplen och den stora korgen rymmer 40 äpplen. 

Mot slutet av dagen räknar de hur många korgar de har plockat. De kommer fram till att Alice har plockat 20 små korgar och 15 stora korgar. Bob har plockat 17 små korgar och 17 stora korgar. För att räkna ut vem som har plockat flest äpplen skriver vi en liten sketch (FruitPickers).

Variablerna ska vara åtkomliga i hela programmet (inte enbart inne i setup- eller loop-funktionen). Vi deklarerar därför variablerna längst upp i sketchen (ovanför setup-funktionen). Variablerna som deklareras där kallas globala variabler, eftersom de går att komma åt överallt.

Att deklarera en variabel innebär att vi skapar en ny variabel som är av en viss typ (int) och har ett visst namn. Vi kallar variablerna fruitsAlice respektive fruitsBob. Det är i dessa variabler vi i slutänden ska lagra hur många äpplen (frukter) de har plockat.

Del 1/5 av FruitPickers.ino

Vi deklarerar även en int-variabel för den lilla korgen och en int-variabel för den stora korgen. Lägg märke till att vi inleder variabelnamn med gemener. Om variabelnamnet består av flera ord särskiljer vi orden med inledande versaler i stället för med mellanrum. Utöver att deklarera variablerna initierar vi dem också. Det innebär kort och gott att vi ger variablerna värden. 

Del 2/5 av FruitPickers.ino

Till skillnad från littleBasketSize och bigBasketSize är fruitsAlice och fruitsBob inte initierade. De innehåller alltså inga värden ännu (kompilatorn kommer att tilldela värdet 0 i variablerna i brist på annat).

För att tydliggöra hur många korgar Alice och Bob har plockat skapar vi fyra variabler där vi lagrar hur många olika korgar de har plockat. Vi kallar dem littleBasketAlice, bigBasketAlice, littleBasketBob och bigBasketBob.

Del 3/5 av FruitPickers.ino

I setup-funktionen startar vi en seriell kommunikation med Arduinon som utför uträkningen, så att vi får tillbaka svaret på ett snyggt sätt.

Del 4/5 av FruitPickers.ino

I loop-funktionen skriver vi sedan själva uträkningen (även om vi faktiskt hade kunnat lägga hela koden i setup). Vi multiplicerar antalet äpplen per korg med antalet korgar och summerar äpplena från båda korgtyperna. Det värdet (produkten) sparar vi sedan i variablerna fruitsAlice respektive fruitsBob. Lägg märke till att vi inte inleder med bokstäverna int. Variablerna är ju redan deklarerade. Nu ska vi bara lagra värden i dem.

Del 5/5 av FruitPickers.ino

Genom att verifiera, kompilera och ladda upp koden får vi Arduinon till att skriva hur många äpplen Alice och Bob har plockat (det visas i Serial monitor). Vem hade plockat flest äpplen?

Serial monitor visar svaret.

Lägg märke till användningen av metoderna Serial.print respektive Serial.println. Så länge vi vill skriva fler saker på samma rad använder vi Serial.print. När vi vill avsluta en rad och meddela att Arduinon ska skriva nästa sak på en ny rad använder vi Serial.println.

Tips! Det går att åstadkomma radbrytningar i Serial.print genom att skriva \n.

Om det skulle visa sig att vi har räknat fel på hur många äpplen som ryms per korg kan vi lätt korrigera det. Eftersom vi har lagt antalet äpplen i variabler behöver vi enbart ändra det på ett ställe (inte på alla ställen värdena används). Se vad som händer om det i själva verket hade rymts 27 äpplen i den lilla korgen! Vem hade då plockat flest äpplen?

Sketchen har ingen interaktion med användarna. Nästa steg i funktionalitetsutvecklingen är att lägga till knappar som Alice och Bob kan trycka på när de har plockat färdigt en korg av endera typen. Då kan Arduinon hålla koll på räkningen åt dem och ge dem löpande uppdateringar om hur de ligger till tävlingsmässigt. Den delen av programmet återkommer vi till i Bibliotek

Konstant int

En konstant kan ses som motsatsen till en variabel. Värdet som lagras i en konstant går inte att ändra efter att det har satts. I exemplet med Alice och Bob som plockar äpplen finns det variabler som är lämpliga att göra om till konstanter. Antalet äpplen som ryms per korg kommer aldrig ändras under programmets körning. De två berörda variablerna kan därför ändras till konstanter. Det gör vi genom att skriva const framför dem.

Del av FruitPickers2.ino

Den stora fördelen med en konstant är i vilket minne den ockuperar plats. Eftersom en Arduino är synnerligen skralt utrustad med minne gäller det att använda minnet på ett så optimalt sätt som möjligt. 

Den tredje revisionen av Arduino Uno (den som ingår i det tillhörande komponentkittet) använder ATmega328. Den mikrokontrollern är utrustad med 32 kB flashminne (där programmen lagras permanent) och 2 kB SRAM-minne (där alla variabler hamnar undertiden programmet körs). Till skillnad från variabler lagras konstanter i det betydligt större flashminnet, vilket frigör plats i SRAM-minnet för övriga variabler. Det är därför en god vana att använda konstanter i stället för variabler när så är möjligt. 

Minnesmängd i ATmega328

Osignerad int

En vanlig int (även kallat signerad int) kan hantera både positiva och negativa värden: från -32768 till 32767. I vissa situationer känns 32767 som ett begränsande lågt värde samtidigt som det känns onödigt att kunna räkna med negativa värden. Så är fallet i exemplet med Alice och Bob som plockar äpplen. Med en vanlig int kan vår Arduino endast räkna upp till 32767 plockade äpplen per person. Vi behöver inte heller kunna räkna med negativa värden eftersom de inte kan plocka ett negativt antal äpplen.

Genom att ändra datatyp till en osignerad int tar vi bort möjligheten att hantera negativa värden. I gengäld kan vi räkna upp till dubbelt så högt (65535). Det gör vi genom att helt enkelt skriva unsigned framför int i deklarationen. 

Del av FruitPickers2.ino

Byte

Som det nämndes tidigare i detta kapitel gäller det att hålla nere minnesanvändningen så mycket det går vid programmering av stora program. Variabeltypen byte kan (precis som int) lagra värden, men tar bara en byte (8 bitar) i anspråk. 8 bitar räcker dock enbart för värden från 0 upp till 255. Byte-variabeln ska därför endast användas om det inte föreligger risk för att värdet överskrider 255. 

Overflow

Om värdet i en variabel blir större än vad variabeln är gjord för inträffar ett så kallat overflow-fel. Det är därför byte-variabeltypen ska användas med försiktighet. Vad händer egentligen om ett program försöker lagra värdet 256 i en byte?

Värdet 0 skrivs binärt på följande vis:

00 00 00 00.

Datorn har alltså reserverat 8 bitar för att kunna skriva 0. Siffran 1 lagras på samma vis:

00 00 00 01.

Så fortsätter datorn att räkna upp successivt till den kommer till 255.

Decimalt värdeByte-värde
0 00 00 00 00
1 00 00 00 01
2 00 00 00 10
3 00 00 00 11
4 00 00 01 00
5 00 00 01 01
6 00 00 01 10
7 00 00 01 11
8 00 00 10 00
253 11 11 11 01
254 11 11 11 10
255 11 11 11 11

Väl framme vid 255 stöter datorn på problem eftersom den inte kan hantera högre värden än 11111111 med enbart åtta bitar att tillgå. Lösningen för datorn blir att helt enkelt räkna om från början.

Decimalt värdeByte-värde
254 11 11 11 10
255 11 11 11 11
256 00 00 00 00
257 00 00 00 01

Även om det är bra att välja variabler som är datasnåla, är det viktigt att aldrig snåla in för mycket. Finns det minsta osäkerhet kring huruvida en byte-variabel är stor nog är det bättre att ta det säkra före det osäkra och välja en int-variabel i stället.

Genom historien har underdimensionerade variabler orsakat många roliga buggar. En av de mest kända är den så kallade "kill screen-buggen" i den klassiska arkadversionen av Donkey Kong. När spelaren kom till bana 22 dog Jump man (huvudkaraktären som är misstänkt lik Super-Mario) för att tiden rann ut. Anledningen var att spelet räknade ut att banans tidsbegränsning var drygt 255 sekunder. Eftersom tiden enbart hade 8 bitar att tillgå orsakades en overflow-bugg som gjorde att spelaren endast hade ett fåtal sekunder på sig att klara banan. Det var omöjligt att klara hela banan på så kort tid, så ingen spelare har någonsin kommit längre än till bana 22. 

Ingen har kommit längre än bana 22 i Donkey kong på grund av en overflow-bugg.8

Long

Vissa situationer kräver riktigt stora värden. I exemplet med plockade äpplen är det inte säkert att det räcker med osignerade int-värden (d.v.s. upp till 65535). Då kan variabel­typen long användas i stället. Den tar 32 bitar i anspråk och kan därigenom lagra upp till 4294967295 under förutsättning att variabeln är osignerad. Om long-variabeln är signerad (standard) kan den enbart hantera upp till hälften så stora värden, men i gengäld kan den även hantera negativa heltal.

Så fort Arduinon startar börjar den räkna antalet millisekunder den har varit igång. Föga oväntat sparar Arduinon det i en osignerad long-variabel. 4294967295 millisekunder räcker för att hålla räkningen i 49 dygn, 17 timmar, 2 minuter och 47 sekunder. Därefter inträffar en overflow och Arduinon börjar räkna om från noll. Arduinon är dock inte speciellt bra på att hålla tiden (den är som en riktigt dålig klocka). Av den anledningen kommer vi i Håll tiden med Arduino använda en så kallad RTC-modul för att hålla tiden. 

Tips! Det går att få reda på hur länge Arduinon har varit igång genom att anropa funktionen millis().

Float (och double)

Variabeltyperna som vi har kollat på hittills (byte, int och long) har enbart hanterat heltal. Variabeltypen float kan hantera alla rationella tal så att det går att räkna med decimaler. Det är inte bara användbart i matematiska sammanhang utan också vid inläsning av data från analoga sensorer. 

Float-variabler tar 32 bitar i anspråk och är alltid signerade (d.v.s. de hanterar både positiva och negativa värden). De kan lagra värden från -3,4028235 ∙ 1038 upp till 3,4028235 ∙ 1038

Nackdelen med float-variabler är att de är tunga att beräkna. Om det går att använda till exempel en long i stället är det att föredra. 

Observera att noggrannheten på float-uträkningar inte är så god som det första intrycket antyder. Noggrannheten är endast 6 till 7 värdesiffror (oberoende av vilken sida siffrorna har om decimalskiljetecknet)9.  För att få högre precision används i vissa programmeringsspråk variabeltypen double. I Arduino-sammanhang ger double dock ingen förbättrad precision (gäller de ATmega-baserade Arduino-utvecklingskorten inkl. Arduino Uno).

Float-variabler måste skrivas på rätt sätt i all kod. Decimalskiljetecknet i denna bok är ett kommatecken, men i kodsammanhang ska det vara en punkt (t.ex. 8.4 i stället för 8,4).  

Boolean

En boolean är en variabel som bara kan lagra ett av två värden: sant eller falskt. De två värdena kan antingen skrivas ut som true respektive false eller förkortat till siffrorna 1 (true) respektive 0 (false).

Funktionen digitalWrite som användes i Arduino IDE styrs med en boolean som har fått andra namn. Normalt används följande kodsnutt för att tända en lysdiod.

digitalWrite(13, HIGH);

Samma kod hade även kunnat skrivas på något av följande sätt.

digitalWrite(13, true);
digitalWrite(13, 1);

Booleaner är mycket praktiska i jämförelser och när något ska vara på- eller avstängt. 

Char

Variabeltypen char används för att lagra enskilda tecken. Variabeln tar 8 bitar i anspråk och kan således lagra värden mellan 0 och 255 (precis som byte). Olika värden motsvarar i sin tur olika tecken i den så kallade Ascii-tabellen (American Standard Code for Information Interchange). Här följer en lista över vilka nummer som motsvarar vilka utvalda tecken. 

NummerTecken
32 (mellanrum)
33 !
34 "
35 #
36 $
37 %
38 &
39 '
40 (
41 )
42 *
43 +
44 ,
45 -
46 .
47 /
48 0
49 1
50 2
51 3
52 4
53 5
54 6
55 7
56 8
57 9
58 :
59 ;
60 <
61 =
62 >
63 ?
64 @
65 A
66 B
67 C
68 D
69 E
70 F
71 G
72 H
73 I
74 J
75 K
76 L
77 M
78 N
79 O
80 P
81 Q
82 R
83 S
84 T
85 U
86 V
87 W
88 X
89 Y
90 Z
91 [
92 \
93 ]
94 ^
95 _
96 `
97 a
98 b
99 c
100 d
101 e
102 f
103 g
104 h
105 i
106 j
107 k
108 l
109 m
110 n
111 o
112 p
113 q
114 r
115 s
116 t
117 u
118 v
119 w
120 x
121 y
122 z
123 {
124 |
125 }
126 ~
127 (radera)

För att deklarera en char-variabel vid namn letterA och sätta värdet till bokstaven A används följande kod (lägg märke till att nummer 65 motsvarar bokstaven stora A).

char letterA = 65;

Att char-variabler inte är något annat än numeriska värden är viktigt att komma ihåg inför genomgången av strängar. Med det sagt är det värt att känna till en genväg: utvecklingsmiljön kan översätta angivna tecken åt oss. Genom att sätta tecknet inom apostrofer slipper vi leta upp det rätta teckennumret. Den förra kodraden kan alltså skrivas på följande sätt med samma resultat.

char letterA = 'A';

Obs! Lägg märke till att det är apostrofer som används (inte citationstecken som det oftast är annars). 

Sträng

Strängar beter sig ofta som variabler, men faktum är att de inte är det. De är egentligen sammansättningar av flera char-variabler (som i sin tur enbart är värden som representerar tecken ur en lista). 

Strängar används ofta för att lagra texter och för att slippa skriva samma text flera gånger. Vår sketch skulle exempelvis kunna användas för fler frukttyper än äpplen om vi inte hade hårdkodat in ordet "apples". För att göra koden mer flexibel lägger vi frukt­typen i en global sträng (ovanför setup-funktionen) och hämtar den vid behov i loop-funktionen.

char fruit[] = " apples";

Namnet på strängen vi har skapat är fruit. Det är i själva verket en array av char-variabler (d.v.s. flera char-variabler tillsammans). Det indikeras av hakparenteserna. Arrayer behandlas närmare i Arrayer.

Med den nya strängen kan vi bygga koden mer flexibel på följande vis.

Del av FruitPickers2.ino 

För att göra det enklare att hantera strängar har klassen String uppfunnits10. Den gör användningen av strängar ännu mer lik variabler. Tack vare String-klassen kan vi byta ut deklarationen av fruit till följande kod (med bibehållen funktionalitet).

Del av FruitPickers2.ino

Lägg märke till att, till skillnad från variabeltyper, skrivs String med stort S. 

Hela FruitPickers2

Här följer hela källkoden till sketchen FruitPickers2.

FruitPickers2.ino

Summering

Här följer en summering över de viktigaste variabeltyperna.

VariabelStorlekMinsta värdeStörsta värde
boolean 8 bitar 0 1
byte 8 bitar 0 255
char 8 bitar -128 127
int 16 bitar -32768 32767
int (osignerad) 16 bitar 0 65535
long 32 bitar -2147483648 2147483647
long (osignerad) 32 bitar 0 4294967295
float 32 bitar -3,4028235 ∙ 1038 3,4028235 ∙ 1038

Referenser

8. Bildruta ur filmen "Donkey Kong (MAME) - 905,700 Killscreen" (kjll.cm/arduino-kill-screen). CC-BY 3.0.

9. Arduino LLC. Reference: Float (www.arduino.cc/en/Reference/Float). Hämtad 2016-07-07.

10. Läs mer om klasser i Klasser och objekt.

Senast ändrad: 2017-01-19