Variabler og strenger

Variabler og strenger

I dette kapittelet skal vi se nærmere på hva variabler er, hvilke typer variabler som finnes, og hvordan de brukes. Vi skal også gå gjennom strenger. De ligner på variabler, men er det ikke.

Hva er en variabel?

I programmering er en variabel et navngitt objekt som har og kan skifte verdi. Det finnes flere ulike typer variabler som brukes i ulike sammenhenger. Variablene vi skal se nærmere på er:

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

Int

En int er en av de vanligste variabeltypene. Int er en forkortelse for integer (engelsk for heltall) og brukes til å lagre heltallsverdier. Den er praktisk i blant annet følgende eksempel, der Alice og Bob plukker epler i kurver. Kurvene finnes i to ulike størrelser. Den lille kurven rommer 25 epler, og den store rommer 40 epler.

På slutten av dagen regner de ut hvor mange kurver de har plukket. De kommer frem til at Alice har plukket 20 små kurver og 15 store kurver. Bob har plukket 17 små kurver og 17 store kurver. Vi skriver en liten skisse (FruitPickers) for å regne ut hvem som har plukket flest kurver.

Variablene skal være tilgjengelige i hele programmet (ikke bare inne i setup- eller loop-funksjonen). Vi deklarerer derfor variablene øverst i skissen (over setup-funksjonen). Variablene som deklareres der, kalles globale variabler, siden man har tilgang til dem overalt.

Å deklarere en variabel innebærer at vi lager en ny variabel som er av en bestemt type (int) og har et bestemt navn. Vi kaller variablene fruitsAlice og fruitsBob. Det er i disse variablene vi til slutt skal lagre hvor mange epler (frukter) de har plukket.

Del 1/5 av FruitPickers.ino

Vi deklarerer også en int-variabel for den lille kurven og en int-variabel for den store kurven. Legg merke til at vi innleder variabelnavn med små bokstaver. Hvis variabelnavnet består av flere ord, skiller vi dem med stor forbokstav i hvert nye ord i stedet for mellomrom. I tillegg til å deklarere variablene initierer vi dem også. Det betyr at vi gir dem en verdi.

Del 2/5 av FruitPickers.ino

Til forskjell fra littleBasketSize og bigBasketSize er fruitsAlice og fruitsBob ikke initierte. De inneholder altså ingen verdier ennå (kompilatoren kommer til å gi dem verdien 0 i mangel av noe annet).

For å tydeliggjøre hvor mange kurver Alice og Bob har plukket, lager vi fire variabler der vi lagrer hvor mange ulike kurver de har plukket. Vi kaller dem littleBasketAlice, bigBasketAlice, littleBasketBob og bigBasketBob.

Del 3/5 av FruitPickers.ino

I setup-funksjonen starter vi en seriell kommunikasjon med Arduinoen som utfører utregningen, slik at vi får tilbake svaret på en praktisk måte.

Del 4/5 av FruitPickers.ino

I loop-funksjonen skriver vi deretter selve utregningen (selv om vi faktisk kunne ha plassert hele koden i setup). Vi multipliserer antall epler per kurv med antall kurver og summerer eplene fra begge kurvtypene. Den verdien (produktet) lagrer vi deretter i variablene fruitsAlice og fruitsBob. Legg merke til at vi ikke innleder med bokstavene int, fordi variablene allerede er deklarerte. Nå skal vi bare lagre verdien i dem.

Del 5/5 av FruitPickers.ino

Ved å verifisere, kompilere og laste opp koden får vi Arduinoen til å skrive hvor mange epler Alice og Bob har plukket (det vises i Seriell overvåker). Hvem har plukket flest epler?

Seriell overvåker viser svaret.

Legg merke til bruken av metodene Serial.print og Serial.println. Så lenge vi vil skrive flere ting på samme linje, bruker vi Serial.print. Når vi vil avslutte en linje og fortelle Arduinoen at den skal skrive neste ting på en ny linje, bruker vi Serial.println.

Tips! Du kan legge inn linjeskift i Serial.print ved å skrive \n.

Hvis det skulle vise seg at vi har regnet feil på hvor mange epler som rommes per kurv, kan vi enkelt korrigere det. Siden vi har lagt antall epler inn i variabler, trenger vi bare endre det på ett sted (ikke på alle stedene der verdiene brukes). Se hva som skjer hvis det egentlig hadde vært plass til 27 epler i den lille kurven! Hvem hadde da plukket flest epler?

Skissen har ingen interaksjon mot brukerne. Neste trinn i funksjonalitetsutviklingen er å legge til knapper som Alice og Bob kan trykke på når de har plukket ferdig en kurv av den ene typen. Da kan Arduinoen holde tellingen og gi dem løpende oppdateringer om hvordan de ligger an i konkurransen. Den delen av programmet kommer vi tilbake til i Biblioteker.

Konstant int

En konstant kan ses på som motsetningen til en variabel. Verdien som lagres i en konstant, kan ikke endres etter at den er angitt. I eksempelet med Alice og Bob er et noen variabler som med fordel kan gjøres om til konstanter. Antall epler som hver kurv rommer, kommer aldri til å bli endret under kjøringen av programmet. De to berørte verdiene kan derfor endres til konstanter. Det gjør vi ved å skrive const fremfor dem.

Del av FruitPickers2.ino

Den store fordelen med en konstant er i hvilket minne den tar opp plass. Siden en Arduino er særdeles dårlig utrustet med minne, gjelder det å bruke minnet på en så optimal måte som mulig.

Den tredje revisjonen av Arduino Uno (den som er inkludert i den tilhørende komponentpakken), bruker ATmega328. Den mikrokontrolleren er utstyrt med 32 kB flashminne (der programmene lagres permanent) og 2 kB SRAM-minne (der alle variabler havner når programmet kjører). I motsetning til variabler lagres konstanter i det betydelig større flashminnet, hvilket frigjør plass i SRAM-minnet til andre variabler. Det er derfor en god vane å bruke konstanter i stedet for variabler hvis det er mulig.

Minnesmängd i ATmega328

Usignert int

En vanlig int (også kalt signert int) kan håndtere både positive og negative verdier: fra -32768 til 32767. I enkelte tilfeller føles 32767 som en begrensende lav verdi, samtidig som det føles unødvendig å kunne regne med negative verdier. Dette er tilfelle i eksempelet med Alice og Bob. Med en vanlig int kan Arduinoen vår bare telle opptil 32767 plukkede epler per person. Vi har heller ikke bruk for å kunne regne med negative verdier, siden de ikke kan plukke et negativt antall epler.

Ved å endre datatype til en usignert int fjerner vi muligheten til å håndtere negative verdier. Til gjengjeld kan vi telle opptil dobbelt så høyt (65535). Det gjør vi ved bare å skrive unsigned fremfor int i deklarasjonen.

Del av FruitPickers2.ino

Byte

Som tidligere nevnt i dette kapittelet gjelder det å holde minnebruken så lav som mulig ved programmering av store programmer. Variabeltypen byte kan (akkurat som int) lagre verdier, men legger bare beslag på én byte (8 biter). 8 biter er imidlertid bare nok til verdiene fra 0 til 255. Byte-variabelen skal derfor bare brukes hvis det ikke er noen risiko for at verdien overstiger 255. 

Overflow

Hvis verdien i en variabel blir større enn det variabelen er laget for, inntreffer en såkalt overflow-feil. Det er derfor byte-variabelen skal brukes med forsiktighet. Hva skjer egentlig hvis et program prøver å lagre verdien 256 i en byte?

Verdien 0 skrives slik binært:

00 00 00 00.

Datamaskinen har altså reservert 8 biter for å kunne skrive 0. Sifferet 1 lagres på samme måte: 

00 00 00 01.

Slik fortsetter datamaskinen å telle til den kommer til 255.

DesimalverdiByte-verdi
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

Ved 255 støter datamaskinen på et problem, siden den ikke kan håndtere høyere verdier enn 11111111 med bare åtte biter tilgjengelig. Løsningen for datamaskinen blir ganske enkelt å telle fra begynnelsen av.

DesimalverdiByte-verdi
254 11 11 11 10
255 11 11 11 11
256 00 00 00 00
257 00 00 00 01

Selv om det er viktig å velge variabler som er datagjerrige, er det viktig å ikke bli for gjerrig. Hvis det er den minste tvil om hvorvidt en byte-variabel er stor nok, er det bedre å velge det sikre fremfor det usikre, altså velge en int-variabel i stedet.

Historisk sett har underdimensjonerte variabler forårsaket mange morsomme bugs. En av de mest kjente er den såkalte "kill screen-bug" i den klassiske arkade-/spillemaskinversjonen av Donkey Kong. Når spilleren kommer til nivå 22, dør Jumpman (hovedkarakteren som er mistenkelig lik Super-Mario) fordi tiden renner ut. Årsaken er at spillet regner ut en bonustid på en bestemt måte. På nivå 22 vil resultatet av utregningen av denne verdien overstige 255, hvilket fører til en overflow-bug, som gjør at spilleren bare har et fåtall sekunder på seg til å gjennomføre nivået. Det er umulig å klare hele nivået på så kort tid, så ingen spillere har noensinne kommet lenger enn nivå 22.

Ingen har kommet lenger enn nivå 22 i Donkey kong på grunn av en overflow-bug.8

Long

Enkelte situasjoner krever høye verdier. I eksempelet med epleplukking er det ikke sikkert at det holder med usignerte int-verdier (dvs. opptil 65535). Da kan en variabel av typen long brukes i stedet. Den krever 32 biter og kan derfor lagre opptil 4294967295 under forutsetning av at variabelen er usignert. Hvis long-variabelen er signert (standard), kan den bare håndtere opptil halvparten så høye verdier, men til gjengjeld kan den håndtere negative tall.

Så snart Arduinoen starter, begynner den å telle antall millisekunder den har vært i gang. Ikke uventet lagrer Arduinoen det i en usignert long-variabel. 4294967295 millisekunder er nok til å holde tellingen i 49 døgn, 17 timer, 2 minutter og 47 sekunder. Etter dette inntreffer en overflow, og Arduinoen begynner å telle på nytt fra null. Arduinoen er imidlertid ikke særlig god til å holde tiden (den er som en veldig dårlig klokke). På grunn av det kommer vi i Hold tiden med Arduino til å bruke en såkalt RTC-modul til å holde tiden.

Tips! Du kan få vite hvor lenge Arduinoen har vært i gang ved å kalle opp funksjonen millis().

Float (og double)

Variabeltypene som vi hittil har gått gjennom (byte, int og long), håndterer bare heltall. Variabeltypen float kan håndtere alle rasjonale tall, slik at man kan regne med desimaler. Det er ikke bare nyttig i matematiske sammenhenger, men også ved innlesing av data fra analoge sensorer.

Float-variabler krever 32 biter og er alltid signerte (dvs. at de håndterer både positive og negative verdier). De kan lagre verdier fra -3,4028235 ∙ 1038 til 3,4028235 ∙ 1038.

Ulempen med float-variabler er at de er tunge å regne ut. Hvis du for eksempel kan bruke en long-variabel i stedet, er det å foretrekke.

Merk at nøyaktigheten til float-utregninger ikke er så høy som førsteinntrykket skulle tilsi. Nøyaktigheten er bare 6 til 7 verdisifre (uavhengig av på hvilken side av desimalskilletegnet sifrene står). For å få høyere presisjon brukes variabeltypen double i enkelte programmeringsspråk. I Arduino-sammenheng gir imidlertid double ingen forbedret presisjon (gjelder de ATmega-baserte Arduino-utviklingskortene inkl. Arduino Uno).

Float-variabler må skrives på riktig måte i all kode. Desimalskilletegnet i denne boken er et komma, men i kodesammenheng skal det være et punktum (for eksempel 8.4 og ikke 8,4).

Boolean

En boolean er en variabel som bare kan lagre én av to verdier: sant eller usant. De to verdiene kan enten skrives som true eller false eller 1 (true) eller 0 (false).

Funksjonen digitalWrite, som brukes i Arduino IDE, styres med en boolean som har fått andre navn. Vanligvis brukes følgende kode for å tenne en lysdiode:

digitalWrite(13, HIGH);

Samme kode kan også skrives på følgende måter.

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

Boolean-variabler er velegnet i sammenligninger og når noe skal være på- eller avslått. 

Char

Variabeltypen char brukes til å lagre enkeltstående tegn. Variabelen krever 8 biter og kan derfor lagre verdier mellom 0 og 255 (på samme måte som byte). Ulike verdier tilsvarer ulike tegn i den såkalte ASCII-tabellen (American Standard Code for Information Interchange). Her følger en liste over hvilke numre som tilsvarer hvilke utvalgte tegn. 

verdiTegn
32 (mellomrom)
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 (slett)

For å deklarere en char-variabel med navnet letterA og sette verdien til bokstaven A, bruker vi følgende kode (legg merke til at nummer 65 tilsvarer en stor A):

char letterA = 65;

At char-variabler ikke er noe annet enn numeriske verdier er viktig å huske før gjennomgangen av strenger. Når det er sagt, er det viktig å kjenne til en snarvei: utviklingsmiljøet kan oversette angitte tegn til oss. Ved å sette tegnet i apostrofer slipper vi å lete frem det riktige tegnnummeret. Den forrige kodelinjen kan altså skrives på følgende måte med samme resultat:

char letterA = 'A';

Obs! Legg merke til at det er apostrofer som brukes (ikke anførselstegn slik som ofte er tilfelle ellers). 

Streng

Strenger oppfører seg ofte som variabler, men faktum er at de ikke er det. De er egentlig sammensetninger av flere char-variabler (som i sin tur bare er verdier som representerer tegn i en liste).

Strenger brukes ofte til å lagre tekst og for å slippe å skrive samme tekst flere ganger. Skissen vår kunne for eksempel bli brukt for flere frukttyper enn epler hvis vi ikke hadde hardkodet inn ordet "apples". For å gjøre koden mer fleksibel legger vi inn frukttypen i en global streng (over setup-funksjonen) og henter den ved behov i loop-funksjonen.

char fruit[] = " apples";

Navnet på strengen vi har laget, er fruit. Det er egentlig et array av char-variabler (dvs. flere char-variabler sammen). Dette indikeres av hakeparentesene. Arrayer går vi gjennom i Arrayer.

Med den nye strengen kan vi gjøre koden mer fleksibel på følgende måte:

Del av FruitPickers2.ino 

For å gjøre det enklere å håndtere strenger har klassen String blitt oppfunnet. Den gjør bruken av strenger enda mer lik variabler. Takket være String-klassen kan vi bytte ut deklarasjonen av fruit med følgende kode (med ivaretatt funksjonalitet):

Del av FruitPickers2.ino

Legg merke til at String skrives med stor S – til forskjell fra variabeltyper. 

Hele FruitPickers2

Her er hele kildekoden til FruitPickers2.

FruitPickers2.ino

Sammendrag

Her er en oversikt over de viktigste variabeltypene.

VariabelStørrelseMinsta verdiStørste verdi
boolean 8 biter 0 1
byte 8 biter 0 255
char 8 biter -128 127
int 16 biter -32768 32767
int (usignert) 16 biter 0 65535
long 32 biter -2147483648 2147483647
long (usignert) 32 biter 0 4294967295
float 32 biter -3,4028235 ∙ 1038 3,4028235 ∙ 1038

Referenser

8. Bilde fra 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). Hentet 2016-07-07.

10. Les mer om klasser i Klasser og objekter.

Sist endret: 2017-09-08