4. Lektion

Denne lektion handler grundlæggende om Funktioner og Procedurer

Emnerne behandles i lærebogen på siderne 142 til 185

Funktioner blev opfundet fordi nogle programmører fandt ud af, at de kunne spare en mængde indtastninger, hvis de kunne organisere kode, der lignede hinanden på en sådan måde, at man ved et kald kunne få programmet til at gentage en bestemt gruppe kommandoer igen og igen.
Funktioner giver samtidig mulighed for at strukturere den kode man skriver.
Det blev efterhånden til at struktureringen førte til separate programstykker, som kunne kører selvstændigt. I C skelner man normalt ikke mellem funktioner og procedurer, men jeg vil bibeholde den opdeling, der anvendes i DELPHI (PASCAL). Procedurer er funktioner, som udfører et selvstændigt stykke arbejde, mens funktioner er funktioner, der returnerer en værdi.
Efterhånden som ideen med funktioner udviklede sig blev det klart at det ville være en stor fordel hvis man kunne indkapsle variabler i funktionerne, således at de fik der eget selvstændige liv inde i funktionerne. Når man kan indkapsle variabler inde i funktionerne bliver livet lidt lettere for programmøren, idet denne så kan anvende de samme variabelnavne i forskellige funktioner. Anvendelse af funktioner gør det således meget lettere at konstruere store programmer. Ideen med funktioner vises grafisk herunder.

 

Lad os starte med at lave et simpelt program med en procedure:

Eksempel 1
// Procedure som tegner en linie med firkanter

void linie(void);

void main(void)
{
   char a = 'b';
   printf("\n");
   linie();
   printf("\x8b Henning Karlby \x8b\n");
   linie();
   a = getch();
}

void linie(void)
{
   int j;
   for (j = 1; j<=24; j++)
      printf("\x8b");
   printf("\n");
}

Der er nu lavet et program, som kalder en funktion, ved nærmere eftersyn ligner den nye funktion meget funktionen main().
Den første linie i programmet er en deklaration af funktionen. En funktion kan deklareres på nøjagtig samme måde, som vi tidligere har deklareret variabler. Først skrives typen på funktionen, derpå følger funktionens navn, som igen efterfølges af de argumenter, som funktionen anvender. Prototypen afsluttes med et semikolon i modsætning til en almindelig funktionserklæring. Når man deklarerer en funktion, kaldes det  i C-programmering at oprette en prototype, andre programmer, som Pascal, kalder det at forwarde funktionen. Når man har oprettet en prototype, kan funktionen derpå skrives et vilkårligt sted i programmet. Det anbefales, at man afholder sig fra at anvende prototyper, idet det kan føre til uoverskuelig programmering, ligesom det bliver muligt at lave rekursive kald til funktioner, med risiko for at man derpå glemmer at terminere (afslutte) disse. I alle de følgende programmer vil der blive forsøgt at opbygge en struktur, således at man undgår at anvende prototyper.
Main() har vi allerede arbejdet med i er stykke tid. Der er principielt heller ikke forskel på main() og andre funktioner. Kun er det et krav til ethvert program, at det indeholder en funktion, som hedder main(), alle øvrige funktioner kan programmøren frit navngive. Bemærk, der er ikke noget semikolon efter deklarationen af funktionsnavnet.
Ovenstående funktion vil i Delphi have været kaldt en procedure, fordi den ikke returnere en værdi, men blot udfører et arbejde på det tidspunkt den bliver kaldt ind til det.
Når man siger, at man kalder en funktion, betyder det rent faktisk, at man får funktionen udført.
Uanset hvor i et program funktionen main() er placeret, vil den altid blive udført som den første funktion i et C-program.
I funktionen line() anvendes variablen j, hvis man i main() skriver printf("Her udskrives j = %d", j); ville compileren melde fejl med at j var en ukendt variabel. Når den er ukendt for main() er det fordi variablen er erklæret lokalt inde i line(). Man taler om en variabels rækkevidde. Desuden taler man om en variabels lifetime, i en senere lektion vendes der tilbage til dette. Variablen oprettes, når funktionen kaldes og nedlægges igen i samme øjeblik, programmet forlader funktionen.
Den næste funktion, er det, man også i Delphi kalder en funktion, dvs. at det nu handler om en funktion, som returnerer en værdi. I dette tilfælde er getche() "gemt" i funktionen getlc(), dvs. at funktionen kaldes lige efter, der på skærmen er skrevet, at der skal indtastes et bogstav. Det er således funktionen, som står klar til at modtage indtastningen. Når funktionen har modtaget indtastningen, konverterer funktionen tegnet til et lille bogstav, hvis det er et stort bogstav, der indtastes.

Eksempel 2
char getlc(void)
{
   char ch;
   ch = getche();
   if ( ch>64 && ch<91 )
      ch = ch + 32;
   return (ch);
}

void main(void)
{
   char chlc;
   printf("\nIndtast et a eller et b: ");
   chlc = getlc();
   switch (chlc)
   {
      case 'a':
         printf("\nDu indtastedede et a! ");
         break;
      case 'b' :
         printf("\nDu indtastedede et b! ");
         break;
      default :
         printf("\nDu indtastede et forkert bogstav! ");
   }
   chlc = getch();
}

Bemærk, at jeg brugte return til at returnere værdien, som getlc() genererede. I ovenstående program og i de følgende skal I selv huske at #include de nødvendige biblioteker.

return kan kun returnere én værdi, hvis en funktion skal returnere mere end en værdi, skal der bruges en anden mekanisme, den mekanisme vendes der tilbage til i lektionen om pointere.

Funktionen herover var ikke særligt fleksibel. Den gør, hvad den bliver bedt om på præcis den samme måde hver gang, der er ingen mulighed for at ændre på, hvad funktionen udfører uden at ændre på koden. Hvis man skal ændre på, hvad funktionen udfører, skal man anvende en mekanisme, hvor funktionen forsynes med et argument. Argumenter har du allerede anvendt i funktionerne printf() og i scanf(). Formateringstegn mv. er funktionernes argumenter.
Lad os nu se på et eksempel, hvor funktionen kaldes med et argument, dvs. at funktionen udfører forskellige opgaver afhængig af, hvilket argument vi forsyner funktionen med.

Eksempel 3
void bar(int score)
{
   int j;
   for( j = 1; j <= score; j++ )
      printf("\x23");
   printf("\n");
}

void main(void)
{
   printf("\n");
   printf("Thomas \t");
   bar(27);
   printf("Lise \t");
   bar(41);
   printf("Peter \t");
   bar(34);
   printf("Poul \t");
   bar(22);
   printf("Lotte \t");
   bar(12);
   getch();
}

Ovenstående eksempel tegner en bjælke af forskellig længde ved de forskellige navne.
Pas på det logiske udtryk i for-sætningen i proceduren bar().
Lad os prøve at udvikle en funktion selv. Vi kunne tænke os at have en funktion, der kan uddrage kvadratroden af et tal.
Hvis vi har et tal og dividerer det med et tilfældigt tal, så får vi et tal på den modsatte side af kvadratroden.
Eksempel kvadratroden af 100 er 10.
Hvis vi dividerer 100 med 2 får vi 50. 10 ligger mellem 2 og halvtreds.
Vi lægger nu 50 og 2 sammen, resultatet dividerer vi med 2 så får vi 26, det runder jeg af til 25 for så kan jeg regne videre med hovedregning. 100 divideret med 25 giver 4, det søgte tal 10 befinder sig stadig mellem de to tal. Jeg lægger nu 25 og 4 sammen og dividere med 2 nu afrunder jeg til 15.
100 divideret med 15 giver ca. 7, 15 plus 7 er 22 divideret med 2 er 11. 100 divideret med 11 er 9, 9 plus 11 er 20 divideret med 2 er 10 - færdig.

Nu skal vi have lavet en funktion der kan klare opgaven. Den kommer her:

Eksempel 4
float kvrod (int n)
{
   float a = 2.0;
   float b = 1.5;
   float c = 2.0;

   while ( ( c - b ) > 0.001 )// find ud af hvor lille
   {                          // grænsen skal være for
      b = n/a;                // at 25.0 giver 5.00000
      c = (a + b)/2;
      a = c;
      if (b > c) // sørger for at resultatet også bliver
      {
         c = -c; // vurderet rigtigt første gang
         b = -b; // dvs. while løkken kører stabilt
      }
   }
   return b;
} // Slut på Funktionen kvrod()

Vi kan nu lave en lille regnemaskine, hvor vi anvender funktioner, der igen anvender funktioner osv. I det følgende program er der anvendt prototyper, dette er for at vise, hvorledes det gøres, hvis man har mange funktioner, man ønsker at erklære.

Eksempel 5
// prototyper
int sumkv(int, int);
int kv(int);
int sum(int, int);
float kvrod(int);
// prototyper slut

void main(void)
{
   int num1, num2;
   printf ("\n Indtast 2 tal: ");
   scanf("%d %d", &num1, &num2);
   printf("\nSummen af kvadratet for de to tal er: %d",
                             sumkv(num1, num2));
   printf("\nKvadratroden af tallet herover er: %f",
                             kvrod(sumkv(num1, num2)));
   getch();
}

int sumkv( int j, int k )
{
   return ( sum ( kv(j), kv(k) ) );
}

int kv( int x )
{
   return (x * x);
}

int sum( int u, int v )
{
   return ( u + v );
}

float kvrod (int n)
{
   float a = 2.0;
   float b = 1.5;
   float c = 2.0;
   while ( ( c - b ) > 0.00001 )
   {
      b = n/a;
      c = (a + b)/2;
      a = c;
      if (b > c)
      {   c = -c;
          b = -b;
      }
   }
   return b;
}

Indtil nu har vi kun anvendt lokale variabler, dvs. variabler som kun har liv inde i funktionen. Sådanne variabler kaldes lokale eller formelle variable. Når funktionen kaldes udskiftes de formelle variabler med de aktuelle, dvs. det er de aktuelle, der anvendes inde i funktionen på de steder, hvor de formelle navne står.
Så vidt det er muligt, bør man altid anvende lokale variabler, det giver den mest overskuelige programmering, men i nogle tilfælde bliver programmeringen for stiv, hvis man udelukkende vil gennemføre den med lokale variabler. Man kan så ty til globale variable. Globale variable er variabler, som defineres i toppen af programmet, og som derved kan nås af alle procedurer i programmet. Globale variabler kaldes også externe (external) variabler, fordi de deklareres helt uden for alle funktioner. Lokale variabler kaldes også automatiske variabler, fordi de automatisk oprettes hver gang funktionen, hvor de er erklæret, anvendes og automatisk nedlægges igen, når funktionen termineres.

Eksempel 6
int tal; // Global variabel
void ligeulige(void)
{
   if ( tal % 2 )
      printf("\nTallet er ulige");
   else
      printf("\nTallet er lige");
}

void negativ(void)
{
   if ( tal < 0 )
      printf("\nTallet er negativt");
   else
      printf("\nTallet er positivt");
}

void main(void)
{
   char a;
        printf("\nIndtast et heltal: ");
   scanf("%d", &tal);
   ligeulige();
   negativ();
   a = getch();
}

Hvis den globale variabel skal være konstant, dvs. at den ikke må kunne ændres under afviklingen af programmet, skal den erklæres i toppen af programmet på samme måde, som den globale med den ændring at der tilføjes det reserverede ord static, det ser således ud:

static int pi = 3.1415926;

Pas på! En variabel kan god erklæres konstant og så alligevel ændres under afviklingen.

Eksempel 7
int func(void)
{
   static int k;
   return (++k);
}

void main(void)
{
   int j;
   for ( j = 0; j < 4; j++ )
   printf("\nDu har kaldt konstantfunktionen %d gange!", func() );
   getch();
}

Normalt forsvinder en lokal variabels værdi, når en funktion afsluttes, dette er ikke tilfældet med statiske konstanter, derfor kan man få ovenstående funktion til at tælle hvor mange gange, funktionen har været kaldt.
Der kan fortælles meget mere om lifetime og visibility af variabler, samt hvorfor ovenstående funktion virker, dette vendes der tilbage til i en senere lektion.

Til sidst skal vi se på funktionen #define

#define PI 3.1415926; definerer konstanten PI.

Define kan også anvendes til at oprette en makro.

f.eks.

#define PR(n) printf("%.2f\n", n);

Hvis num1 = 3.0 og num2 = 34.6, kan man lave en kort udskriftskommando nu

PR(num1); giver 3.00 og PR(num2); giver 34.60.

#define Areal_Cirkel(radius) (PI*radius*radius);

#define Areal_Firkant(h, b) (h*b);

Øvelse 1
Prøv at ændre eksempel 5 (regnemaskinen) således at programmet fortsætter med at køre, indtil der tastes et RETURN i stedet for tal, eller stop programmet ved at indtaste Q (Quit).

Øvelse 2
Konstruer et program, der anvender funktionen bar(int) fra eksempel 3. Programmet skal modtage en indtastning fra brugeren og udskrive en tekst med antallet, hvorefter der skal udskrives en bjælke med det indtastede antal.

Eksempler og løsningerne kan hentes her
Eksempel 1
Eksempel 2
Eksempel 3
Eksempel 4
Eksempel 5
Eksempel 6
Eksempel 7
Øvelse 1
Øvelse 2