2.2. Parameterlister og datatyper

I foregående afsnit afleverede vi én oplysning til en funktion. I dette afsnit skal vi aflevere flere oplysninger og desuden se, hvordan en funktion kan udføre sit arbejde på en så robust og fornuftig måde, at vi ikke behøver at spekulere på, om programmet bliver ustabilt, hvis brugeren foretager sig noget tåbeligt.

Vi vil foreløbig nøjes med at bruge de indbyggede datatyper, men det er på ingen måde en begrænsning for C programmøren, at sproget "kun har" int, double, char etc, for vi kan jo opbygge andre datatyper ved hjælp af brugerdefinerede datatyper, struct, og vi kan manipulere de enkelte bits ved hjælp af bitvise operatorer.

På den måde kan man bygge udmærkede repræsentationer af de datatyper, som findes i andre sprog, men som savnes i C. For eksempel kan man opbygge en tegnbaseret repræsentation af tal, ligesom det findes i Lisp. Ved hjælp af denne repræsentation kan man opnå mange hundrede (tusinder) decimalers præcision i beregninger.

2.2.1. Datatyper og beregninger

Den grundlæggende datatype i C sproget er int, en forkortelse for integer, heltal. Størrelsen af denne datatype er ikke altid den samme, på en 8 bit computer er det 16 bit (hmm, fik du den?) og på en 64 bit computer er det 64 bit. Hvis man vil foretage 64-bit beregninger på en 8-bit computer, så kan det godt lade sig gøre, det vil blot være væsentligt langsommere end hvis man nøjes med at benytte maskinens "medfødte" datatype. Det kan implementeres som en oversætterfunktionalitet, eller det kan være, at det er implementeret som maskin-instruktioner, der benytter flere registre. Intel 386-arkitekturen benytter to registre til 64-bit aritmetik, men har dog ikke ægte 64-bit division. Det må man så lave på en anden måde.

Lad os prøve at foretage nogle beregninger med heltal. Det må gerne være nogle simple beregninger, sådan at vi nemt kan kontrollere, om programmet regner rigtigt. Sidenhen kan vi ændre det til noget mere imponerende.

2.2.1.1. En procentberegning

Eksempel 2-4. HELLO - beregning.


/* procent.c beregner en procentdel af et givet tal. */

#include <stdio.h>

int main()
{
    int procent = 17;
    int kroner = 100;
    int resultat;

    resultat = kroner * procent / 100;

    printf("resultat er: %d\n", resultat);

    return 0;
}

/* end of file procent.c */

Det er en stor fordel, at resultatet er nemt at kontrollere, brug simple tal indtil programmet er stabilt.

Vi har indført nogle variable - kroner, procent, resultat. Det er simpelt hen kasser med tal i. Der er straks fyldt værdier i kasserne.

Navnene er valgt sådan, at man kan regne ud, hvad meningen er med variabelen.

 
int kroner = 100;

betyder, at vi reserverer en plads til en heltalsvariabel og straks fylder tallet 100 i.

Selve beregningen kan skrives i programmet næsten på samme måde, som man ville skrive formelen på et stykke papir. Hvis man er vant til, at (x y) betyder x gange y, så skal man selvfølgelig passe på at man ikke glemmer multiplikations operatoren '*'.

resultat = kroner * procent / 100;

resultat er navnet på den "kasse", hvor resultatet skal gemmes, og den kaldes en "left value", venstre-værdi, fordi den kan stå på venstre side af et lighedstegn eller assignment (tildelings) operator. Venstre side af assignment operatoren skal være et udtryk, der kan evalueres som en adresse, ellers kan man jo ikke komme til at gemme resultatet af vejen. Hvad der måtte ligge af interessante værdier i sådan en "kasse", forsvinder efter et assignment.

printf() får at vide, at den skal skrive variablen "resultat" ud ved hjælp af procent-tegnet efterfulgt af 'd'. Nu får printf() ikke én, men to oplysninger, nemlig format string'en mellem double quotes, (gåseøjne) og talvariabelen resultat.

"resultat er: %d\n"   <== string-var
"resultat er: %d\n", resultat <== string-var, tal-var

Den anden linje kaldes en liste eller en parameter liste ; de to medlemmer er adskilt af et komma, komma er liste-operator.[1]

Det er nok lidt for besværligt at rette i programmet her, hver gang jeg vil udføre en procent beregning! Så i stedet laver vi i næste afsnit et program, som prompter for (beder om input af) det tal, der skal beregnes procent af. Dermed forlader vi "Hello-world" sfæren og bevæger os ind i fejlmulighedernes paradis, interaktive programmer.

2.2.2. Prompt og input

Input fortjener et kapitel for sig. Men lad os alligevel her skrive et program, som læser inddata fra tastaturet, og som godt nok forventer, at der bliver tastet tal (cifre) ind, men på den anden side ikke tager skade af, at brugeren indtaster noget helt andet - eventuelt skubber en bog henover tastaturet, så der kommer input i store mængder.

2.2.2.1. Input af tal og beregning

Eksempel 2-5. Input og beregning.


/* procent2.c prompt for tal og beregn procentdel. */

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int procent = 17;
    int kroner;
    int resultat;
    char inputlinje[800];

    printf("BEREGNING AF %d %%\n",procent);
    while (printf("Input tal:"), fgets(inputlinje,800,stdin)!=NULL) {
      kroner = atoi(inputlinje);
      resultat = kroner * procent / 100;
      printf("resultat er: %d\n", resultat);
    }
    return 0;
}

/* end of file procent2.c */

I nogle C-bibliotek implementationer skal man flushe printf-output hvis der ikke er en "newline" til sidst:

... printf("Input tal:"), fflush(stdout),

Denne måde at kæde expressions sammen med et komma er bestemt ikke den mest pædagogiske. Det er faktisk grimt! Men når jeg nu har gjort det alligevel, så er det for at understrege, at C sprogets liste-operator (komma'et) er anvendelig i mange sammenhæng. Men lad nu være med at skrive for meget komatøs kode!

Det er værdien af det sidste expression, som er afgørende for, om expression-listen evaluerer til sand eller falsk.

Input læses af fgets(3). Den skal have 3 oplysninger, adressen på den buffer, som den må lægge characters i, længden på bufferen (den læser maximalt længde - 1 og afslutter string med en null-byte) og den fil, som den skal læse input fra. Her anvendes "kodeordet" stdin, som er defineret i <stdio.h>.

fgets(3) returnerer NULL hvis der ikke er mere input. Dette kan ske hvis brugeren taster "symbolsk end-of-file", der som regel sættes til ^D med stty kommandoen. Men hvis input er omdirigeret, så sker det jo som forventet, når man rammer slutningen på filen.

Der er includeret en fil mere, stdlib.h, som indeholder prototype til atoi - Ascii TO Integer conversion.[2]

#define __P(args)  args

extern int atoi __P ((__const char *__nptr));
/* __P(args) er en kommando til præprocessoren, det kaldes
 * en macro, og den har til formål at gøre
 * bibliotek funktionerne brugbare sammen med mange
 * forskellige C-oversættere. Nogle varianter af C-oversættere kan
 * ikke forstå komplicerede prototypeerklæringer.
 */
/* derfor svarer denne prototype til:
extern int atoi(const char* string_som_skal_converteres);
 */

Det smukke i procent2.c er, at man ikke kan få programmet til at gå ned ved ondskabsful indtastning, når der promptes for et tal. Man kan godt få det til at regne forkert, hvis man indtaster et tal, som er større end 126 mio. Men dels er fgets() en robust funktion, som ikke laver buffer overflow, selv om brugeren indtaster 2 Gb data, og atoi(char*) er en robust konverteringsrutine, som ikke brokker sig, hvis input ikke er cifre. Hvis man indtaster bogstaver, ignoreres de, og der returneres 0.

88kr. vil blive konverteret til heltallet 88, hvilket svarer til, hvad man forventer. Senere vil vi lave en indtastningsrutine, som giver brugeren en warning, hvis han ikke indtaster tal.

Det er programmørens ansvar at sørge for den rigtige længdeangivelse til fgets' anden parameter. Det er lettere at holde styr på den slags, hvis man benytter preprocessor macro'er til at definere symboler for konstant-talværdier.

#define MAXLINJE 800
char inputlinje[MAXLINJE];

main()
{
    fgets(inputlinje, MAXLINJE, stdin);
    return 0;
}

Ikke semikolon i #define linjen, det er ikke et statement.

Øvelser for de lidt mere erfarne: Prøv at ændre programmet, så der anvendes double precision floating point variable. Prøv også at tilføje en kontrolberegning, som finder ud af, om der har været overflow. Hvis der er overflow, så skriv en fejlmeddelelse til brugeren i stedet for at skrive resultatet, men stop ikke programmet.

Prøv også at skriv en version, hvor man kan indtaste procentsatsen først, eller et, som udskriver værdien af kroner i både australske, canadiske og US dollar.

Lav et program, som udskriver flere valutaer pr. linje i en tabel, f.eks. svarende til kr. 100, 200, 300, 400, 500 ... )

Slutbemærkning:

[1]

Parametre er oplysninger til en funktion.

[2]

Sørg for, at du virkelig ved, hvad ascii er for noget!