4. Programstyrning

 

 

·     Upprepningar.

·     While.

·     Do...while.

·     For.

·     Nästlade slingor.

·     If, else.

·     Switch och case.

·     Break, continue.

·     Goto.

 

 

 

 

 

 

 

 


Upprepningar.

 

Ofta uppstår behovet att upprepa samma sak många gånger, kanske med en eller flera variabel som hela tiden ändrar värde. Man kan tala om att man vill upprepa en kod genom att använda ett visst nyckelord. Dessa är: ‘while’, ‘do’ samt ‘for’.

 

Man måste ha någon for av villkorl som det upprepande kommandot kan kontrollera vid varje upprepning, så att upprepningen kan avbrytas i rätt ögonblick.

 

Variabler som ingår i villkorlet måste initieras på något sätt. Ibland har man en upprepning som inte utförs alls, d.v.s. noll gånger, p.g.a. att den eller de variabler som ingår i villkorlet har sådant värde att villkorlet för avslutning redan är uppfyllt. Detta gäller ‘while’ som vi strax ska se. Vi kommer också att se att motsvarande situation kommer att resultera att koden utföres en gång om man använder nyckelordet ‘do’.

 

Nyckelordet ‘for’ tillåter initiering i syntaxen. Vi kommer att titta på syntaxen för respektive upprepningskommando i följande avsnitt.

 

Generellt gäller för alla upprepningar, och även villkorlsatserna som vi tittar på därnäst, att den kod som avses är endast en programsats. Om man vill upprepa flera programsatser är det bara att avända klamrar, så kommer hela kodavsnittet att upprepas. Om man bara vill upprepa en enda programsats är det frivilligt att använda klamrar.

 

Observera att man kan i vissa situationer utföra allt som behövs i själva villkorl­et! Då avlutar man med en tom programsats, d.v.s. endast ett semikolon:

 

while(*p++);

 

...om vi tar en pekare ‘p’ som pekar på en text. Texten avslutas ju med en nolla, vilket motsvarar falskt och upprepningen avbryts. Denna while-sats slulle alltså leta upp slutnollan i en text, och peka på första byte efter texten.

 

Ett vanligt fel är att man sätter dit ett semikolon av misstag:

 

while(*p);

{

    p++;

}

Pekaren p kommer aldrig att ändra värde och programmet hänger upp sig på while-satsen. (Vi kommer att titta mer på pekare sedan.)


while

 

While betyder ”medan”, eller ”så länge som” och används för att upprepade gånger utföra en eller flera programsatser. Efter while står ett villkor, och så länge detta är uppfyllt utförs den eller de programsatser som följer mellan klamrar. Om villkoret inte är uppfyllt från början utförs inte dessa programsater alls. För att upprepningen ska kunna avbrytas måste det ske något inom programsatserna som förändrar villkoret så att det slutligen inte längre är uppfyllt. Dock kan ju en variabel automatiskt räknas upp (eller ner) i samband med användning, och den kan ju användas i villkoret. Se exemplet med ASCII-listan i föregående kapitel.

 

Syntax:

 

while (<villkor>)

{

   [<programsats>;]...

}

 

Exempel:

 

// Räknar upp a i sista programsatsen i ”loopen”.

int a = 0;

while (a < 25)

{

   printf(”Nu är a = %d\n”,a);

   a += 1;

}

 

// Räknar upp a efter varje test.

int a = 0;

while (a++ < 25)

{

   printf(”Nu är a = %d\n”,a);

}

 

while(1) // En etta som villkorl gör en evig slinga!

 

Övningsuppgift:

 

·      Öppna projektet printf.mak och källkoden ascii.c.

·      Kontrollera så att projektet använder filen ascii.c.

·      Lägg till en while-loop som omger den som redan finns, och lägg i denna till inmatning av startvärde för listningen. Den ska lista 16 rader. Om slutvärdet efter listningen är större än 255 ska programslingan avbrytas, annars ska man kunna mata in ett nytt startvärde.

 

? While.


 Övningsuppgift:

 

·      Du ska göra ett program som du kallar tabell.

·      Bestäm själv om du vill skapa ett nytt projekt, eller om du vill återanvända printf.mak. Fundera över vilken metod som verkar bäst.

·      Programmet ska skriva ditt namn i 5 kolumner och på 15 rader. Använd två while-slingor, en för upprepning på en rad och en för upprepning av rader. För detta behöver du en kolumnräknare och en radräknare.

 

Övningsuppgift:

 

·      Du ska göra ett program som du kallar ascii.

·      Bestäm själv om du vill skapa ett nytt projekt, eller om du vill återanvända printf.mak. Fundera över vilken metod som verkar bäst.

·      Programmet ska skriva ascii-koder i 8 kolumner och på 8 rader. Använd två while-slingor, en för upprepning på en rad och en för upprepning av rader. För detta behöver du en kolumnräknare och en radräknare.

·      Utskriftsformatet ska vara t.ex. ‘A = 65’.

·      Utlistningen ska börja med asciikod 32.

·      Programmet ska vänta på att man trycker på en tangent mellan varje sida.

 

 

 


do...while

 

I exemplet under avsnittet while, med två nästlade while-slingor, märkte vi att det inte alltid är så praktiskt att testa först, med resultat att de programsatser som ingår i slingan kanske aldrig utförs. Med hjälp av do...while flyttar vi testen till efter programsatserna, och därigenom kommer alltid programsatserna att utföras minst en gång.

 

Syntax:

 

do

{

   [<programsats>;]...

}  while (<villkor>);

 

Exempel:

 

#include <stdio.h>

 

void main()

{

   int iStart = 1, iSlut = 0; // Lista inte första gången,

   do                         // fråga bara, efter startvärde.

   {

      while (iStart <= iSlut)

      {

         printf("ASCII-koden för %c = %d\n", iStart, iStart);

         iStart++;

      }

      printf("\nVar vill du börja lista\?");

      scanf("%d", &iStart);

      printf("\n");

      iSlut = iStart + 16;

   }  while (iStart > 0);

} 

                     

 

Övningsuppgift:

 

·      Skapa ett nytt projekt gissa.

·      Skriv ett program med en do...while-slinga som frågar ut dig om vilket tal du tänker på. Du ska svara ‘s’ om ditt tal är större än datorns gissning och ‘m’ om ditt tal är mindre. Svarar du ‘r’ betyder det att datorn gissat rätt. Talet ska vara inom talområdet 1 till 100.

 

 

 

? Do.

 

for

 

I en while-slinga initierar man en variabel i förväg, testar den före utförandet av slingans programsatser samt gör omräkning för vidare test antingen vid test eller bland programsatserna. Do...while-slingan gör i stort sett samma sak, men testar efter programsatserna.

 

For, däremot, samlar allt detta på ett och samma ställe: inom paranteserna efter nyckelordet ”for”, åtskilda med semikolon.

 

Syntax:

 

for ([<variabelinitiering>];[<villkor>];[<uppräkning>])

{

   [<programsats>;]...

}

 

Den så populära BASIC-loopen:

         for i = 1 to 100

         ...

         next i

 

...blir i C:

 

         for (i = 1; i <= 100; i++)

         {

            ...

         }

 

Man kan initiera flera variabler på en gång, och även räkna upp flera på en gång. Man kan t.o.m. utföra hela beräkningar i uppräkningsdelen. Villkoret kan naturligtvis vara sammansatt, d.v.s. bestå av flera villkor sammansatta med && eller ||.

 

Som synes på syntaxdagrammet är allt utom nyckelordet ‘for’ och paranteserna valfritt att ha med. Man måste inte initiera i parantesen, man måste inte räkna upp i parantesen etc. Har man inget slutvillkorl blir det dock en evig slinga. Jag har sett förljande eviga slinga i en kommandoloop i ett windowsprogram (courtesy of Microsoft):

 

for(;;)

{

...

}

 

? for.

 

Övningsuppgift:

 

·      Ändra i programmet tabell så att det använder två for-slingor i stället för while.

·      Vilket tycker du är bäst för denna uppgift, for eller while?

 

 

I fortsättningen när vi gör en övningsuppgift gör vi som följer om inget annat anges: Skapa ett nytt projekt under katalogen C:\CCpp\, och en ny källkodsfil. Dessa ska ha programmets namn. Om programmet ska heta t.ex. ”kalle” så ska det se ut så här (förutom allt annat du har):

 

C:\

                 CCpp\

                                   KALLE\

                                                    KALLE.C

                                                    KALLE.MAK

                                                    ... etc.

 

Övningsuppgift:

 

·      Skapa en multiplikationstabell med endast produkterna. Talvärdena ska sträcka sig från 1 till 10, d.v.s. utskriften ska vara 10 rader * 10 kolumner. Använd 2 nästlade for-slingor, en för rader och en för kolumner.

·      Programmet ska heta produkt och ligga i egen katalog.

·      Utskriften ska se ut som nedan:

 

                


Nästlade slingor       

 

På engelska heter det ”nestling”. I svensk översättning brukar man tala om ”nästlade slingor”. Associationen till ett fågelnäste är inte helt fel, eftersom vad man egentligen gör är att men trasslar in slingor i varandra (av samma typ eller av olika typ). Detta har både för och nackdelar. Dels blir koden svårare att läsa, dels blir det lätt fel, dels kan man göra mer. I exemplen under ”While” och ”Do...while”  har vi nästlat två while-satser respektive en do...while och en while.

 

Teoretiskt sett kan man nästla hur många nivåer som helst, men kom ihåg att datorn måste spara lite adresser och annat för varje loop som startas, och dessa lagras i något man kallar ”stack”. När man lagrat för mycket blir stacken full, och man får ett s.k. run-time error av typen ”stack overflow”.

 

Följande nästling är tillåten:

 

while (a > 5)

{

         do

         {

                 while (q != 99)

                 {

                          ...

                 }

 

         } while (c < 3)

 

}

Detta är dock fel:

 

while (a > 5)

{

         do

         {

                 while (q != 99)

                 {

                          ...

                 }

 

         }

 

} while (c < 3)

Man får alltså inte lägga slingorna ”om lott”, utan de ska innehålla varandra helt och hållet. Felet ovan är att do-slingan avslutas efter att första while har avslutats.

 


if, else

 

Precis som i de flesta språk kan vi använda ”if” för att fatta beslut. Syntaxen är dock litet speciell:

 

if (<villkor>)

{

   <satser>;

}

 

Eller, om vi bara har en sats:

 

if (<villkor>) <sats>;

 

Vi kan också använda else:

 

if (<villkor>)

{

   <satser>;

}

else

{

   <satser>;

}

 

Eller:

 

if (<villkor>)

   <sats>;

else

   <sats>;

 

Det går att nästla if-satser:

 

if (<villkor>)

   <sats>;

else if (<villkor>)

   <sats>;

else if (<villkor>)

   <sats>;

[else

   <sats>;]

 

 

 

 

 

? If, else.


switch och case

 

Har man många nästlade if-satser kanske man hellre skulle använda en switch. Där kan man använda en variabel som switch och ange åtgärd för respektive värde. Man måste bara komma ihåg att avsluta varje åtgärd med break, annars fortsätter programmet att utföra alla efterföljande case-satser.

 

Syntax:

 

switch (<variabel>)

{

   case <värde>:

      <programrader>;

      break;

   [case <annat värde>:

      <programrader>;

      break;]...

   [default

      <programrader>;]

}

 

Exempel: vi har läst sifferbetyg 1-5 och skriver ut omdöme:

 

switch (betyg)

{

   case 1:

      puts(”Icke godkänd”);

      break;

   case 2:

      puts(”Med tvekan godkänd”);

      break;

   case 3:

      puts(”Godkänd”);

      break;

   case 4:

      puts(”Väl godkänd”);

      break;

   case 5:

      puts(”Med utmärkt beröm godkänd”);

      break;

   default:

      puts(”Felaktig betygsättning!”);

}

 

 

 

 

 

 

? Switch case.


break, continue

 

Nu har vi tittat på tre olika typer av slingor. Dessa passar mycket bra in i JSP-strukturerat tänkande. Samma sak kommer att gälla ”if” respektive ”switch”, vilka vi ska titta på senare. Slingor = upprepningar motsvarar ju ett block i JSP märkt med en stjärna i övre vänstra hörnet. I valsituationer byter man stjärnan mot en liten cirkel. (Känner du inte igen dig nu, så titta i dina anteckningar från de två introduktionsveckorna.)

 

Nu ska vi (tyvärr) titta på hur man kan förstöra denna vackra symmetri m.h.a. ”break” och ”continue”.

 

Som du säkert märkt är det inte alltid så lätt att strukturera programmet på sådant sätt att man helt följer JSP-strukturen. Man kanske tar ett beslut mitt i en slinga som resulterar i att men kanske vill avbryta den även om slutvillkoret inte uppfyllts, eller bara hoppa över bearbetningen detta varv och hoppa till slingans början igen. Detta kan man göra med ”break” respektive ”continue”.

 

#include <stdio.h>

void main()

{

   int r, k, p; // rad, kolumn och produkt.

   for (r = 1; r <= 10; r++)

   {

      for (k = 1; k <= 10; k++)

      {

         if (k > r) break; // Fixar till triangelutseendet

         p = r * k;        // enligt utskriften nedan!

         printf("%4d",p);

      }

      printf("\n");

   }

} 

 

 

 

Vill vi däremot bara hoppa över ett ”varv” den inre slingan kan vi använda ”continue”, som i nedanstående variation av exemplet:

 

#include <stdio.h>

void main()

{

   int r, k, p; // rad, kolumn och produkt.

   for (r = 1; r <= 10; r++)

   {

      for (k = 1; k <= 10; k++)

      {

         if (k == r)        // Tar bort diagonalen, se nedan!

         {

            printf("    "); // Skriv ut blank i stället.

                    continue;       // Fortsätt loopa utan att avsluta

         }                  // varvet.

         p = r * k;         // Normal utskrift!

         printf("%4d",p);

      }

      printf("\n");

   }

}

 

 

 

 

 

 

Övningsuppgift:

·      Skriv ett program lcnt (letter count) som räknar bokstäver i en mening som användaren matar in (OBS du måste använda gets()).

·      Textsträngen ska deklareras så här: char cMening[81]; och du använder ett index int iIndex för att ”peka” på varje bokstav i meningen cMening[iIndex].

·      Använd en while-slinga som avslutas när strängen inte innehåller någon text.

·      Använd continue för att hoppa över allt som inte är bokstäver.

·      Programmet ska svara: ”Meningen innehåller <antal> bokstäver.

 

? Break, continue.


Övningsuppgift:

 

·      Gör tillägg i lcnt (exemplet ovan) så att man kan fortsätta att mata in flera meningar.

·      Det ska ske med hjälp av en yttre do-slinga, som avbryts med break om man inte har matat in något (cMening[0] = ‘\0’).

·      Redovisa bägge uppgifterna för din handledare.

 

 

                

 

 


goto

 

Rubricerat ord är att betrakta som en svordom i C/C++! Själva språkets upp­byggnad gör att man gärna skriver välstrukturerade program a’la JSP, och gör man ett s.k. ”vilt programhopp” här, så har man med största sannolikhet gjort ett stort avsteg från sin vackra struktur. Jag har t.o.m. hört talas om program­merings­företag som mer eller mindre förbjudit användandet av goto. På ADB Malmö fick vi endast använda goto för att avbryta en slinga eller ett kodavsnitt genom att hoppa fram till slutet, men i C/C++ kan man lika gärna använda break, om man saknar fantasi.

 

Det finns dock en situation då det är nästan rumsrent att använda goto: Man har nästlat sig djupt in i ett antal slingor och upptäcker av någon anledning ett fel, och en snygg exit är onödig eftersom programmet ändå måste avbrytas. Då behöver man inte lämna varje slinga för sig med ett break, utan kan hoppa ut till yttersta slingans slut med ett goto.

 

För att kunna hoppa med goto behöver man en programetikett att hoppa till. En programetikett är ett (nästan) fritt valt ord vilket avslutats med kolon.

 

Syntax:

 

goto <etikett>;

.

.

.

<etikett>:

 

Exempel:

 

goto Avbryt;

.

.

.

Avbryt:

 

 

 

 

 

 

 

 

 

? Goto.