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