7. Lektion
Denne lektion handler om Strukturer (Records)
Emnerne behandles i lærebogen på siderne 380 til 410
Hvad er strukturer?
Vi har set, hvordan en variabel kan indeholde enkelt data og hvordan arrays kan
indeholde en gruppe af data, blot disse data er af samme type. Disse
variabeltyper kan klare en meget stor mængde af de data, man træffer på i
beregninger og tekster. Men vil man f.eks. oprette en database med oplysninger
om bøger på et bibliotek eller en database med medarbejderne i et firma, så
bliver det meget kompliceret at programmer med de ovennævnte variabeltyper.
Til at løse problemer med data af forskellig type, som skal høre sammen, har C en speciel datatype. Den kaldes en struktur (struct). En struktur er det, der i en normal database kaldes en post, hver af de variabler, som indgår i en post, kaldes i databasen for felter. En samling af poster udgør en database. Hvis databasen kun består af en "stabel" poster, som alle indeholder de samme typer data, kaldes databasen ofte en flad database. Dette henviser til, at hver post kan udskrives på en linie for sig, en database, som er udskrevet på et antal linier, på tabelform, kaldes en databasetabel. Hvis en database består af flere tabeller, hvor de enkelte tabeller, hver især har et og kun et felt til fælles, kaldes en relationsdatabase.
En struktur i C er det samme, som man i Pascal kalder en Record.
Poster er ikke kun nyttige, fordi de kan indeholde forskellige datatyper, men også fordi de er grundlaget for mere complexe datatyper, som f.eks. lænkede lister.
Indtastning af data til en post
Lad os se på et lille simpelt eksempel. Opgaven går ud på at konstruere et
program, hvor der kan indtastes data og hvor disse data udskrives til skærmen.
Eksempel 1
void main(void)
{
struct personel
{
char navn[30];
int agentnum;
};
struct personel agent1;
struct personel agent2;
char talstr[81];
printf("\nIndtast første agents navn : ");
gets(agent1.navn);
printf("\nIndtast første agents nummer max. 3 tal: ");
gets(talstr);
agent1.agentnum = atoi(talstr);
printf("\nIndtast anden agents navn : ");
gets(agent2.navn);
printf("\nIndtast anden agents nummer max. 3 tal: ");
gets(talstr);
agent2.agentnum = atoi(talstr);
printf("\nHer følger listen med agenter!\n");
printf("\t Første navn: %s \n", agent1.navn);
printf("\t Første nummer: %03d \n", agent1.agentnum);
printf("\t Andet navn: %s \n", agent2.navn);
printf("\t Andet nummer: %03d \n", agent2.agentnum);
}
Det reserverede ord, som erklærer posten, er struct, derpå følger navnet
på postvariablen i eksempel 1 er det grundlæggende postnavn personel.
Personel er en posttype. Posttypen indeholder 2 poster henholdsvis et navn, som er en streng på 30 tegn
og et nummer, som er af typen heltal. Efter erklæringen af posttypen, erklæres to nye variabler nemlig agent1 og agent2. Disse er af typen
struct og personel. Dette betyder, at de to nye variabler
bliver poster. De kommer til at indeholde de samme variabler, som posttypen.
Når man skal placere et navn i agent1's
navnefelt, bruger man funktionen gets() sammen med variabelnavnet. Det kommer
til at se således ud:
gets(agent1.navn);
Der er principielt ingen forskel på tildeling af værdier til "almindelige
variabler" og postvariabler. Der er dog en lille forskel, det er den
måde, som et variabelnavn skrives på. Variabelnavnet består af to dele første
del er navnet på posten og anden del er navnet på det felt i posten,
som skal modtage værdien. De to dele bindes sammen af operatoren punktum.
Eksempel 1 lider af den samme skavank, som nogle af de programmer, vi konstruerede i de første lektioner. Der skal oprettes en variabel for hver person, vi vil indtaste. Det giver i det lange løb en uoverkommelig programmeringsindsats. For at komme ud over dette problem kan man f.eks. igen vende tilbage til arrays. Men denne gang er det array med poster, det vender jeg tilbage til senere i lektionen.
Posters forhold til andre programelementer
Indtil nu har vi kun set på posterne og deres
interne forhold. Det næste, jeg kommer til, er, hvordan poster forholder sig
til funktioner og procedurer. Dernæst ser jeg på, hvorledes poster kan
sættes sammen i array's. Til slut vil jeg vise, hvordan man kan få fat i
værdierne ved hjælp af pointere.
Poster og funktioner
Poster kan returneres fra funktioner og anvendes
som argumenter til funktioner, nøjagtig som andre variabler. Når man anvender poster i forbindelse med kald af funktioner og returnering af
poster fra
funktioner, bliver det muligt at konstruere meget kraftige funktioner, ligesom det
bliver muligt at udarbejde klart strukturerede programmer.
Lad os se på agent-programmet, vi anvendte i eksempel 1.
Eksempel 2
struct personel
{
char navn[30];
int agentnum;
};
struct personel nytnavn()
{
char talstr[81];
struct personel agent;
printf("\nNy agent\nIntast navn:");
gets(agent.navn);
printf("\nIndtast nummer max. 3 tal: ");
gets(talstr);
agent.agentnum = atoi(talstr);
return agent;
}
void liste(struct personel alle) // Modtag et struct-argument fra main()
{
printf("\nAgent:\n"); // Udskriv posten
printf("\tNavn : %s\n", alle.navn);
printf("\tNummer: %03d\n", alle.agentnum);
}
void main(void)
{
struct personel agent1; // definer en struct-variabel
struct personel agent2; // definer en til
agent1 = nytnavn(); // Hent data for den første agent
agent2 = nytnavn(); // Hent data for den næste agent
liste(agent1); // Udskriv data for den første agent
liste(agent2); // Udskriv data for den næste agent
}
Fordi begge funktioner og main() skal anvende posten personel, er den
defineret i uden for alle funktioner og procedurer, dvs. den er erklæret
globalt. Både funktioner og main() definerer lokale variabler på baggrund af
den globale post.
Returnering af postværdier
Funktionen nytnavn() kaldes fra main(), for at få indtastet de to agenters data.
Funktionen placerer midlertidigt indtastningerne i en variabel med navnet agent,
derpå anvendes return, til at returnere de indtastede værdier til variablerne i
main(). Funktionen nytnavn() skal erklæres til at være af typen struct fordi
den skal returnere en værdi af denne type.
Anvendelse af struct som argument til en funktion
Når main() modtager postvariablen fra nytnavn() placeres værdien i
henholdsvis agent1 og agent2.. Derpå kalder main() funktionen list() for at få
udskrevet de værdier, der står i agent1 og i agent2. For at aktivere funktionen
skal postværdierne indsættes i list()-funktionen som argument. Når det er
gjort, udskrives indholdet af variablerne, som om det havde været variabler,
som dem vi har set i de tidligere lektioner.
Array af poster
Vores viden skulle nu være nok til, at vi kan definere array's af poster. I
det følgende eksempel 3, er der indbygget en lille menu funktion, hvor brugeren
kan vælge, hvilke funktioner programmet skal udføre, endvidere gemmes data i
programmet i et array af poster. Hvis brugeren indtaster et i (lille I), vil
det være muligt at indtaste data til programmet, hvis brugeren vælger l (lille
L), vil data blive udskrevet som en liste på skærmen. I denne opgave er
desuden tilføjet et sæt data mere til posten, det er agentens højde, der er
erklæret som et reelt tal.
Eksempel 3
/* ---- define ---------- */
#define True 1
/* --------- globale variabler ----------- */
struct personel
{
char navn[30];
int agentnum;
float hojde;
};
struct personel agent[50];
int n = 0;
void nytnavn(void)
{
char talstr[81];
printf("\nListe %d. \nIntast navn:", n+1);
gets(agent[n].navn);
printf("\nIndtast nummer max. 3 tal: ");
gets(talstr);
agent[n].agentnum = atoi(talstr);
printf("\nIndtast højde i m: ");
gets(talstr);
agent[n++].hojde = atof(talstr);
}
void listalle(void)
{
int j;
if (n<1)
printf("\nListen er tom\n");
for (j = 0; j < n; j++)
{
printf("\nListenummer er %d\n", j+1);
printf("\tNavn : %s\n", agent[j].navn);
printf("\tNummer: %03d\n", agent[j].agentnum);
printf("\tNummer: %5.2f\n", agent[j].hojde);
}
}
void main(void)
{
char ch;
while ( True)
{
printf("\nTast 'e' for at indtaste en ny agent,");
printf("\n 'l' for at liste alle,");
printf("\n 'q' for at stoppe:");
ch = getche();
switch (ch)
{
case 'e': nytnavn(); break;
case 'l': listalle(); break;
case 'q': exit(0);
default : puts("\nIndtast kun lovlige bogstavet!");
} // slut på switch
} // slut på while
} // slut på main
Definering af array af poster
Bemærk, hvordan syntaksen er for definering af et array af poster.
struct personel agent[50];
Sætningen sætter plads af i hukommelsen til 50 poster af typen personel. I eksempel 3 er posten erklæret globalt, således at alle funktionerne har tilgang til posterne.
Tilgang til variabler i et array af strukturer
De enkelte variabler i posterne i array'et anvendes ved at referere til postens
variabelnavn, agent, efterfulgt af agentens, index, derpå følger et punktum og
afsluttende med feltnavnet. I dette eksempel ser det således ud:
agent[n].navn
Udtrykket agent[n].navn henviser til feltet navn, som er tilknyttet den n'te post i databasen.
Det eneste, der nu mangler i, at vi kan oprette en brugbar database, er at vi kan gemme de indtastede data i en fil. Dette vender jeg tilbage til i en senere lektion.
Eksempel 4
void main(void)
{
struct xx
{
int num1;
char ch1;
};
struct xx xx1; // erklæring af en post
struct xx *ptr; // erklæring af en pointer til en post
ptr = &xx1; // tilskrivning af adressen på en post til ptr
ptr->num1 = 303; // indsætning af en værdi i et felt på en post
ptr->ch1 = 'Q'; // indsætning af værdi - do -
printf("\nptr->num1 = %4d ", ptr->num1);
printf("\nptr->ch1 = %4c ", ptr->ch1);
}
I eksemplet er der erklæret en posttype med navnet xx, denne
anvendes til at erklære en postvariabel med navnet xx1. Derpå kommer en ny
linie, i den erklæres en postvariabel af type pointer til en post (struct'en xx).
Næste trin bliver, at adressen på posten xx1 tilskrives til
pointervariablen ptr.
ptr = &xx1;
Dvs. hvis variablen xx1 er placeret i hukommelsen i 8032, vil adressen 8032 derefter være placeret i ptr også. Nu er det muligt at henvise direkte til et felt i en post ved at anvende en pointer i stedet for at henvise til postnavn.feltnavn. Man kan som tidligere nævnt henvise til et felt ved f.eks. at skrive xx.num1, man får derved fat i variablen (feltet) num1 i posten xx. Hvis man vil anvende pointere skulle man tro at det var muligt at skrive ptr.num1, men det virker ikke. Grunden er at ptr ikke er en post, men en pointer til en post. Operatoren punktum kræver at det man skriver til venstre er et postnavn. Da vi har defineret en post som xx1 og derfor kan henvise med sætningen xx1.num1, burde vi kunne indsætte (*ptr).num1, det viser sig da også at virke. Men det er ikke særlig brugervenligt, så C's fædre har da også fundet en anden måde at skrive det på. Det er indført en ny operator som består af to tegn, det er: ->, operatoren kaldes en pil-operator. Udtrykket bliver herefter: ptr->num1. Det har nøjagtig samme virkning som at skrive (*ptr).num1.
Her kan hentes et program, som opretter et spil kort og som derpå blander kortene og udskriver de blandede kort i to kolonner.
| Operatoren . (punktum) sammenbinder postennavnet med feltnavnet, operatoren -> (minus og større end) forbinder en pointer til en post med feltnavnet. |
Øvelse 1
Prøv at ændre eksempel 3, således at der anvendes
pointere, til at henvise til felterne i stedet for at anvende direkte anvisning
som i eksemplet.
Eksempler og løsningerne kan hentes her
Eksempel 1
Eksempel 2
Eksempel 3
Eksempel 4
Øvelse 1