Föreläsning 0: Introduktion av Ada OBS! Detta är antagligen det enda papper som delas ut under hela FÖserien. Det finns förutom detta ett antal OH:n som kommer att användas under nästa FÖ, men sen är det endast tavelskrivande och diskussioner som gäller. Vad kommer vi att behöva så här i kursstarten? Lite begrepp och lite problemlösning. Dessutom lite hur ett program ser ut. Vi börjar med lite begrepp. Ett antal "reserverade identifierare" (reserverade ord). Dessa används för att strukturera upp programmet och får bara användas till just detta. Lite "ihopklumpande" efter hur/när man använder dem: | procedure ... is | loop | begin | end loop; | end ...; | while ... loop | with ...; | end loop; | use ...; | for ... in ... loop | if ... then | end loop; | elsif ... then | else | exit; | end if; | exit when ...; Ett antal "datatyper" ("beskrivningar" av hur data ser ut). Dessa är några av de "fördefinierade identifierare" vi kommer att komma i kontakt med: Integer = heltal (ex. 1, 2, 145, 4) Float = flyttal (ex. 1.0, 2.0, 3.14, 3.0) Character = tecken (ex. 'a', 'b, '2', '#') String(...) = "textsnutt" (ex. "kalle", "berta") Boolean = sanningsvärde (False, True) Ett antal "variabler" (lagringsutrymmen att lägga data i). Dessa blir såkallade "egendefinierade identifierare": I : Integer; F : Float; C : Character; S : String(1 .. 5); B : Boolean; När man skapar dessa variabler använder man datatyperna för att tala om vilken sorts variabel det är. OBS! En identifierare identifierar något i programmet och skall därför vara unikt (inom det "område i programmet" som använder denna). Regel för identifierare: En "identifierare" måste inledas med en bokstav. Därefter får det följa fler bokstäver och/eller siffror och/eller under strykningsstreck. Man får dock inte avsluta identifieraren med ett understrykningsstreck eller ha två sådana i rad inuti identifieraren. Det som är beskrivet hittills är saker som redan finns i Ada utan att man behöver säga något extra. När man kommer till programmeringen behöver man dock ofta lite mer att "leka med". Det som behövs kan vara matematiska bibliotek med "Cos", "Sin", etc. eller hantering av externa enheter såsom tangentbord och skärm (såkallad in och utmatning vilket kallas IO efter det engelska "In and Out"). Dessa delar har man i Ada delat upp i olika moduler kallade "paket". Det finns många sådana fördefinierade och här kommer ett par stycken med några av de saker som finns inuti: Ada.Text_IO Put(...); Put_Line(...); New_Line; New_Line(...); Get(...); Get_Line(...); Skip_Line; Skip_Line(...); Ada.Float_Text_IO Put(...); Get(...); Ada.Integer_Text_IO Put(...); Get(...); Ada.Numerics.Elementary_Functions Sqrt(...) Log(...) Exp(...) Sin(...) Cos(...) Tan(...) Cot(...) Arcsin(...) Arccos(...) Arctan(...) Arccot(...) Sinh(...) Cosh(...) Tanh(...) Coth(...) Arcsinh(...) Arccosh(...) Arctanh(...) Arccoth(...) OBS! Det är inte något ';' efter de saker som finns inuti det sista paketet. Varför kommer vi tillbaka till senare på den FÖ som handlar om underprogram. Stencilen ovan lämnas ut på första FÖ, men finns också tillgänglig via kursens hemsida. Nu sätter vi igång! I denna kurs är det tänkt att vi ska lära oss programspråket Ada för att skriva datorprogram. Vad är ett datorprogram? Vi ser det gärna som en beskrivning, en uppsättning instruktioner som löser ett givet problem. Det är egentligen detta vi är mest intresserade av, att lösa problem. Varför ska man då lära sig just Ada? Det finns ju många programmeringsspråk. För det första så är det som sagt inte jätteintressant exakt vilket språk man använder, det är i allmänhet inte så att vissa språk är bättre än andra, utan det är själva tänket man är ute efter, d.v.s. hur tänker man när man programmerar? Dessvärre måste man ha språket, själva verktyget till hands innan man kan bli bra i en viss färdighet. Man kan jämföra det med att det är svårt att snickra om man inte vet hur man använder sig av sågar, hammare och borrar. För det andra visar det sig att Ada är ett mycket lämpligt språk att börja med. Strukturen i språket är sådan att man lätt ser vad man håller på med, dessutom finns det en mycket "snäll" kompilator, alltså det som gör att man kan översätta sin programkod (ren text) till ett riktigt program. Mer om detta senare. Pappret som ni har fått ut är en kort sammanfattning av de två första föreläsningarna. För tillfället kan vi se det mer som en översikt. På pappret finns det många ord på engelska, det kommer vara dessa (och många fler!) som dina program är uppbyggda av. Vi kommer att prata om "reserverade identifierare", det är ord som har en väldigt specifik betydelse i språket och som man inte får ändra på. (Ungefär som att ändra ordet "är" till "banan" i svenskan, blir lite struligt). Det finns även fördefinierade identifierare, det är ord som finns i programspråket men som man kan ändra på (Om vi t.ex. ändrar ordet Kawazaki till Honda så blir det bara måttlig förvirring). Vi kommer att prata om datatyper och variabler, dessa saker har ihop med hur vi lagrar data (t.ex. ett personnummer, ett banksaldo eller alla dina bilder i din dator). Vi kommer prata om identifierare. Detta är namnen (orden) på allting annat i ett program. Ibland kommer ni att få hitta på helt egna namn på saker, det kanske låter larvigt men är faktiskt mycket mycket viktigt att det blir bra. Det finns också ett stort antal andra "rutiner", man kan tänka sig att dessa rutiner är små färdiga program som ett nytt program kan använda sig av, för att slippa göra grovjobbet. Det finns t.ex. matematiska rutiner (vi kommer att säga underprogram i framtiden), de behövs oftare än man tror! Det finns också underprogram för in- och utmatning. Dessa används för att programmet skall kunna jobba med datorns skärm och tangentbord. Det är så att det är mycket svårt att lära sig programmering om man inte kan se vad som händer i ett program om man skriver in olika saker då programmet körs. Därför tar vi detta i början av kursen, så det är vad lab 0 och resten av föreläsningen kommer att handla om. Hur skriver vi nu ett program med hjälp av dessa saker? Vi kan börja med ett exempel från labserien (den första introduktionslaborationen som följer efter denna FÖ i denna kurs). Följande dialog vill vi att den skall utföras med datorn: Mata in ett heltal: 10 Du matade in heltalet: 10 Mata in en sträng med exakt 5 tecken och ett heltal: Kalle 27 Du matade in heltalet 27 och strängen "Kalle". OBS! Det är meningen att datorn (ditt program) skall skriva ut allt i dialogen på skärmen UTOM det som står efter frågorna "Mata in ...:". Dessa data skall användaren mata in på tangentbordet och det är just dessa inmatningar som sen skall skrivas ut på raden efter (av ditt program). Vi börjar med uppgiften på tavlan (bra att tala om hur många rader det blir och vilka rader man använder när man skriver programmet): Nedan har jag fyllt i hela programmet. Under FÖ växer detta självklart fram efter hand. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; procedure In_Och_Utmatning is I : Integer; S : String(1 .. 5): begin Inmatning av heltal. Ada.Text_IO.Put("Mata in ett heltal: "); Ada.Integer_Text_IO.Get(I); Skip_Line; Utskrift av heltalet. Ada.Text_IO.Put("Du matade in heltalet: "); Ada.Integer_Text_IO.Put(I); Ada.Integer_Text_IO.Put(I, Width => 0); Ada.Text_IO.New_Line; Inmatning av sträng och heltal. Put("Mata in en sträng med exakt 5 tecken och ett heltal: "); Get(S); Get(I); Skip_Line; Utskrift av strängen och heltalet. Put("Du matade in heltalet "); Put(I, Width => 0); Put(" och strängen """); Put(S); Put_Line("""."); end In_Och_Utmatning; Först och främst kan jag tala om att det är inget magiskt som jag kommer göra nu. Det vi skriver här är ren text! Tänk er att jag har öppnat programmet "anteckningar"/"notepad" på datorn och skriver in koden. Jag skulle egentligen kunna använda vilket text-editeringsprogram som helst! Kanske inte MS Word dock, Word sparar undan en massa annan information i filerna som inte syns. Vi vill, att i våra filer ska det bara finnas råtext (trots att källkodsfilerna har .adb som fil-ändelse). Det vi kallar för kompilator kommer senare att göra om råtexten till ett program som datorn verkligen förstår, men för tillfället kan vi tänka oss att det som står här i texten är det som datorn kommer att utföra. Så kan man även tänka i allmänhet när man programmerar, vilket är trevligt. Till att börja med måste man få till programmets grundläggande struktur. Det jag kommer skriva nu är något som alla ada-program har. Det är liksom ett programskelett. (Observera att jag inte behöver skriva radnummren, det är bara för att det skall bli lättare för er att följa med i era anteckningar). 4 ... 9 10 ... 27 procedure In_Och_Utmatning is begin end In_Och_Utmatning; Om ni tittar på den sidan som ni fick ut så ser ni att "procedure" "is" "begin" "end..." är reserverade identifierare. Alltså något som finns specat i språket. Däremot har jag själv hittat på identifieraren "In_Och_Utmatning", själva namnet på programmet. När jag sparar den här filen så kommer jag spar den som "in_och_utmatning.adb" (obs små bokstäver!). När man hittar på identifierare får man ha hur långa ord som helst. Man kan använda understreck och siffror, men det namnet får inte börja på en siffra (se pappret). Programskelettet är inte bara något som ser vackert ut, utan har den funktion (förutom att det finns en plats för programmets namn) att dela upp programmet i två delar, en deklarationsdel och en satsdel (en del med instruktioner). Jag brukar alltid tänka mig detta som ett recept. Högst upp (mellan procedure och begin) har man ingredienser, "dessa saker kommer jag behöva för att utföra programmet", och mellan begin och end har man instruktioner "detta är instruktioner som man skall följa steg för steg för att det skall bli rätt". Obs! Alla språk har inte denna uppdelning, tråkigt för dem. Okej, nu har vi ett program? Nästan. Programmet måste göra något också, det är ju faktiskt helt tomt (förutom skellettet, som inte gör något). Vad vill vi att program ska göra? Vi börjar lite smått med något som torde vara lätt. Textsnutten "Mata in ett heltal: " ska komma ut på skärmen. Detta borde inte vara så komplicerat, men det är det! Varför? Tja, skärmen är komplicerad. Den har ett helt eget gränssnitt som är mycket annorlunda från Ada. Dessutom kan ju inte vårt program göra vad det vill. Det kanske finns andra program som måste få tillgång till skärmen samtidigt, alltså krävs det en tredje part som styr upp så att det inte blir kiv, operativsystemet. INTERNMINNE Put(”Mata in...”) OPERATIVSYSTEM EXTERNA ENHETER UTMATNINGSBUFFERT TANGENTBORDSBUFFERT Bilden växer fram tillsammans med att programmet utvecklas. I/O –m od u l fö r te xt Så det är antagligen inte så lätt som att bara skriva "skriva ut följande textsnutt "Mata in ..." på rad 10. Det krävs säkert ett mycket stort och krångligt program bara för att få till detta. Men nu kommer det underbara: Det (under)programmet är redan skrivet, men det ligger på en separat modul. Det finns en hel del moduler, som vi kallar paket eller bibliotek, som ltal e h r ö man kan välja att ha med när man skriver ett program. Man lf modu – skulle kunna visualisera språket som en blomma, i mitten O / I Adas finns alla de nyckelord och konstruktioner som man behöver i ”kärna” mod ul m (nästan) alla program, t.ex. vårt programskelett. De saker som e funk tione d matem atisk man kanske inte alltid behöver ligger i separata moduler, r m.m a dessa är blommans kronblad. Anger man inte explicit att man . vill ha tillgång till dessa moduler så får man bara tillgång till de mest grundläggande sakerna i språket. Eftersom Ada är ett programspråk som används för många inbyggda system, d.v.s. system som inte är tänkte att föra dialog med en användare, ligger dess rutiner för in- och utmatning på sådana separata moduler. I Ada kallas modulerna bl.a. för paket. I ett paket som heter Ada.Text_IO (IO som i "Input and Output") finns en rutin som heter "Put" som utför precis det vi vill, och vi slipper bry oss om hur det egentligen fungerar. Vi måste givetvis också ange att vi vill få tillgång till Ada.Text_IO, det gör vi med "with". Detta är så pass speciellt att det hamnar utanför både deklarationsdelen och satsdelen av programmet! 1 ... 4 ... 9 10 ... 27 with Ada.Text_IO; procedure In_Och_Utmatning is begin Ada.Text_IO.Put("Mata in ett heltal: "); end In_Och_Utmatning; Nu har vi fått till vår första instruktion, och ett körbart program på samma gång. Självklart vill vi då testa vårt program och se vad som händer när vi kör det. Exakt hur man gör detta går jag inte igenom nu. Det är enklast att förstå när man sitter själv framför datorn. Oroa er inte för detta nu, labassistenterna hjälper er med det på första laborationen. När vi kör programmet så skickar programmet iväg textsnutten till skärmen (vi operativsystemet) och den visas upp för oss. En viktigt detalj är semi-kolonet sist på rad 10. Den talar om att instruktionen, satsen slutar där. Satser kan fortsätta över flera rader, eller stå på samma (även om det är fult) så man behöver något explicit som skiljer en sats från nästa. Bortsett från att skriva ut (matar ut) denna text gör programmet inget mer, det kommer avslutas efter instruktionen. Man kan tänka sig att datorn fortsätter ner till "end In_Och_Utmatning" och efter det så avslutar den helt enkelt programmet. Nu kommer nästa uppgift. Att låta användaren, alltså den som sitter och kör det här programmet att få mata in något. För det mesta så kommer ni köra era egna program, så det kommer vara ni som är användaren, så det kan vara lite förvirrande att helt plötsligt behöva ha två roller "programmeraren" och "användaren", men man kommer in i det. Om ni tycker att det är besvärligt kan ni tänka er att ni är labassistenten som kör ert program. Vad skulle han/hon tycka/tänka? ... 9 10 11 ... begin Ada.Text_IO.Put("Mata in ett heltal: "); Här vill vi hämta ett tal från tangentbordet Hur får vi då in något från tangentbordet? Här är det många saker som skall klaffa. Användaren skall trycka på tangentbordet, programmet skall vara redo att ta emot, och vi måste även se till att det som matades in inte kastas utan sparas någonstans, eftersom vi i nästa skede skall mata ut det igen så att användaren ser vad han skrev in. Vi fokuserar på det första. När kan användaren mata in något? Tja, när som helst faktiskt. Det är faktiskt möjligt för användaren att mata in något innan programmet ens har startat! Detta går att göra, antingen om man är ryyysligt snabb (nu snackar vi nanosekunder), eller om man vet hur man "laddar upp" med inmatning till programmet. Hur man gör detta är inte så intressant nu men vi vet i alla fall att inmatningen kan komma tidigt, vilket kan vara besvärligt om vi inte är beredda på att ta emot än. Tänk er att ni kastar en baseboll och jag har inte ens fått på mig plockhansken än. Den enda lösningen på detta är att användarens knapptryckningar lagras så att vi kan bearbeta dem (ett par mikrosekunder) senare. Operativsystemet har en sådan lagringsplats, mellan tangentbordet och vårt program, som kallas tangentbordsbufferten. Detta är bra. Allting som trycks på tangentbordet hamnar alltså här tills vi (i vårt program) kommer och hämtar upp det. Datat försvinner då ur tangentbordsbufferten men finns kvar i vårt program. Att användaren är snabb är dock i allmänhet inte ett problem, eftersom användaren ofta är jättelångsam. För datorn är det som att det passerar hela tidsåldrar från den tidpunkt då texten "Mata in ett heltal" kommer upp tills då användaren inser att han måste börja trycka på knapparna. Det kan ju vara frestande för datorn att dumpa användaren och gå vidare med livet, d.v.s. att bara gå vidare till andra instruktioner om det inte finns något att hämta i tangentbordsbufferten. Men så får det absolut inte gå till. Då kommer det uppstå väldiga problem när vi senare vill skriva ut det användaren skrev in. Det enda som datorn då kan skriva ut är ett pinsamt erkännande att den inte vet eftersom han inte lyssnade just då. Dessutom kanske man kommer i "osynk" med kommande inmatningar och det heltal som matades in börjar tolkas som något helt annat. Nej! Det enda logiska är att datorn väntar på användaren, även om det betyder att den får vänta natt och dag tills hin håle kommer. Den tredje saken som var viktig var att vi verkligen kunde "spara" det som matades in. Vi skulle behöva lite av datorns minne, inte särskilt stor bit, så att vi kan lagra ett tal. Men hur blir detta? I bilden ligger ju redan själva programmet i datorns internminne, vart lagrar man då sitt data? Svaret är enkelt, detta ligger också i internminnet. Datorns minne är alltså plats för både program och data. Hur som helst behöver vi nu en liten bit minne som kallas för en variabel, i vårt fall blir det en heltalsvariabel. Variabler har alltid tre saker: En typ (kan t.ex. vara heltal, flyttal, tecken o.s.v), ett namn (en identifierare som vi som programmerare får hitta på). och ett värde. Vi brukar rita detta så här (ritas in i bilden ovan): I: 25 I detta exempel heter variabeln "I" och innehåller värdet 25. Datatypen är underförstådd, det är ett heltal. Det är viktigt att veta att variabler har värden alltid! Även innan man aktivt har petat in något i dem. De kan aldrig vara "tomma". För att få tillstånd (av operativsystemet) att använda en sådana variabel måste vi deklarera att vi har den i programmets deklarationsdel. 1 ... 4 5 6 ... 9 with Ada.Text_IO; procedure In_Och_Utmatning is I : Integer; begin "Integer" är alltså typen på variabeln (en fördefinierar identifierare). Och "I" är vårt namn på den. Vi hade kunnat kalla den för "mitt_tal" eller något annat sånt, men nu valde jag just "I". Denna variabel kan vi nu använda i vårt program. Obs, det krävs inga paket eller moduler för att använda variabler av olika typer, de finns i "grundspråket" så att säga. Nu vill vi, förutom de saker som vi sa förut, att det som hämtas från tangentbordsbufferten in skall lagras i den här variabeln "I". Självklart finns det ett underprogram som gör detta, på precis det sätt som vi önskar. Den kallas för Get, och denna rutin ligger i paketet Ada.Integer_Text_IO. På samma sätt som förut måste vi ange att vi vill få tillgång till Ada.Integer_Text_IO. 1 2 3 4 5 6 ... 9 10 11 .. 27 with Ada.Text_IO; with Ada.Integer_Text_IO; procedure In_Och_Utmatning is I : Integer; begin Ada.Text_IO.Put("Mata in ett heltal: "); Ada.Integer_Text_IO.Get(I) end In_Och_Utmatning; Om vi nu kör programmet så kommer följande att ske: - Programmet startar, vi får tillgång till en heltalsvariabel med namn I. - Texten "mata in ett heltal: " kommer ut på skärmen. - Programmet kommer till Get, och frågar operativsystemet om det finns något i tgb-bufferten. "Nej" får det som svar och sätter sig därför för att vänta. - Användaren vaknar upp och inser att han måste trycka in ett tal. De tecken som användaren knappar in hamnar i tangentbordsbufferten, låt säga att han/hon matar in 5. Observera nu att inget kommer att hända föränn användaren trycker på enter. Tänker man till så inser man att det nog är bra att det är på detta sätt. Om man skulle ha matat in fel så har man ju fortfarande chans att sudda och skriva in något annat. Det är först när enter-tangenten trycks ner som operativysstemet kommer att börja agera. - Operativsystemet upptäcker att det finns data som vårt program är intresserat av. OS:et väcker vårt program och talar om för det att det finns data att hämta. - Rutinen Get hämtar femman och sparar undan den, som ett heltal i variabeln I. - Programmet avslutas. Toppen, vi fortsätter raskt med resten av första biten. I nästa skede skall texten "Du matade in heltalet: " komma ut, det löser vi med Put igen. Och sedan skall det tal som finns lagrat i "I" komma ut på samma rad. Vi behöver inte göra något speciellt för att det ska hamna på samma rad, allting hamnar på samma rad tills man explicit säger att man vill hoppa ner till nästa (eller användaren trycker enter, då sker detta automatiskt). Hur skriver vi ut talet i "I" då, jo med en annan utmatningsrutin, som också heter Put. Put för heltal finns i paketet Ada.Integer_Text_IO. 1 2 3 4 5 6 ... 9 10 11 12 13 14 ... 27 with Ada.Text_IO; with Ada.Integer_Text_IO; procedure In_Och_Utmatning is I : Integer; begin Ada.Text_IO.Put("Mata in ett heltal: "); Ada.Integer_Text_IO.Get(I) Ada.Text_IO.Put("Du matade in heltalet: "); Ada.Integer_Text_IO.Put(I, Width => 0); end In_Och_Utmatning; När man använder Put för heltal så får man det "högerjusterat" med 11 teckens "bredd" om man inte säger något. I vårt fall vill vi inte ha detta, vi vill ju bara få ut talet precis som det är (vänsterjusterat), för att få till detta så kan vi sätta denna "bredd" till 0. Vi måste också få till en extra tom rad här, så vi kan använda rutinen New_Line som finns i Ada.Text_IO. 16 Ada.Text_IO.New_Line; Ni kan förstå att det blir trist att skriva ut "Ada.Text_IO" och "Ada.Integer_Text_IO" hela tiden. På ett sätt är det bra, får då ser man ju exakt vilken Put det är man använder (eftersom de heter lika dant), men det är jobbigt att skriva så mycket. Det finns ett sätt att slippa skriva ut paketnamnen. Om man i samband med att man gör "with" på paketet också gör "use" så räcker det med att bara skriva namnet på den rutin man vill ha. jag gör det nu, så blir det mindre att skriva i fortsättningen. 1 2 with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; Man brukar som sagt inte skriva satser på samma rad, men i detta fall kan det faktiskt vara okej. Frågan är bara, om jag inte skriver ut paketnamnet på de rutiner som jag vill använda, hur vet då datorn (eller egentligen kompilatorn) vilken som den skall använda? Svaret är att den kan titta på det som man skickar till rutinerna. Skickas det ett heltal till Put, då vet den att det är Put för heltal som gäller, om det är en textsnutt (vi kommer att säga "sträng" ofta) så är det Put för text vi vill ha. Att man kan ha samma namn på rutiner som gör olika saker kallas för överlagring och är en smidig feature i ett programmeringsspråk. Programmet är nu såpass färdigt att det kan utföra den första halvan av vårt problem. Men vad händer om användaren inte matar in tecknet 5 då? Vad händer om han skriver 5.6 eller "FEM"? Tja, Get klarar av vissa saker, men inte andra. I fallet då man matar in 5.6 kommer det ändå bara att bli 5 i I eftersom Get anser att den är klar när den stöter på en punkt i tangentbordsbufferten. Allting fungerar alltså ändå i detta fall, men observera att ".6" ligger kvar i tangentbordsbufferten! Hade det kommit en till Get hade denna fått problem. Därför kan det vara bra att göra en strategisk tömning av bufferten i detta läga. Det kan man göra med satsen "Skip_Line". 12 Skip_Line; Jag hade kunnat skriva Ada.Text_IO.Skip_Line, men eftersom vi nu har use på Ada.Text_IO så räcker det med "Skip_Line". Om det första som Get ser är något som inte kan tolkas som ett heltal, t.ex. en punkt eller tecknet "F", kommer den att få fnatt. Hur ska den göra nu? Vi kommer komma tillbaka till just detta problem senare, men för tillfället räcker det med att ni vet att get kommer inte att fungera i detta fall. Faktum är att get kommer haverera totalt och dra med sig hela programmet ner i graven, ja programmet dör (det kraschar). När ett program kraschar så avslutas det på en gång, man fortsätter inte med andra instruktioner utan man avslutar tvärt, ofta med ett läskigt felmeddelande. Resten av föreläsningen fortsätter på ett mycket likt sätt. Här är det ett ypperligt läge att låta studenterna vara med och utforma resten av programmet. När strängar tas upp är det viktigt att poängtera skillnaden mellan programmets strängvariabel och tangentbordsbufferten.
© Copyright 2024