Frem til nå har vi lært om variabler og brukt enkle variabler av typer som int og float. Disse har vi brukt til å lagre ting som aldre, temperaturer, osv. I dette kapittelet skal vi se nærmere på hvordan vi kan koble variabler sammen til en helhet: en datastruktur.

Tenk deg at vi har et program som lagrer musikkalbumer i en database. Informasjonen som lagres i databasen er f.eks. albumets navn, utgivelsesår, artist, og annen relevant informasjon. Vi kan konstruere en slik database ved å ha flere separate tabeller: En tabell med albumnavnene, en med artistene og en med utgivelsesår. For at dette skal fungere, må vi passe på at albumnavnet blir lagret i albumnavntabellen i samme indeks om artistnavnet. Hvis ikke vil vi jo ikke vite hvilke som hører sammen fordi de ikke deler samme indeksnummer. Dette fører raskt til forvirring og unødvendig kompleksitet, for vi har nemlig en bedre måte å gjøre på: Vi kan definere vår egen datastruktur der navn, artist og utgivelsesår er samlet under én og samme ting i stedet for tre.


Definering av datastrukturer, instanser

rediger

Rent grunnleggende og syntaktisk er en datastruktur en rekke variabler som samles i en helhet, med et felles navn. De forskjellige variablene som strukturen består av, kalles medlemmer eller felt. I C bruker vi struct for å definere strukturer. Skjelettformen er slik:

struct navn { medlemsdefinisjoner } variabel;

Den siste delen av definisjonen, variabel er valgfri. Den spesifiserer en eventuell variabel som skal opprettes av den datastrukturen. Dette er noe vi så å si aldri vil bruke, så fra nå av skal vi utelate det siste feltet i definisjonen.

La oss tenke oss programmet jeg brukte som innledning til dette kapittelet som et eksempel på hvordan en datastrukturdefinisjon kan se ut:

struct Album {
	char navn[50];
	char artist[50];
	int utgitt;
};

Det er en ting som er viktig å merke seg. Når vi definerer en struktur slik som her, oppretter vi den ikke. Vi sier bare til kompilatoren at dette er en ny datatype den kan opprette variabler med. I datastruktursammenheng kaller vi variablene man kan lage for instanser av den datastrukturen. Det vi lager på en måte skjelettet til hvordan en instans av denne typen, i dette tilfellet Album, skal "se ut". Hadde vi inkludert den siste delen av strukturdefinisjonen, som jeg tidligere sa vi aldri kommer til å gjøre, ville det blitt opprettet en instans av strukturen, men på den annen side kunne vi ikke brukt strukturdefinisjonen på for å opprette andre variabler, noe vi gjør slik:

struct Album album1;

Her lager vi altså en instans av Album kalt album1. Merk at vi her ikke trenger å oppgi annet enn struct for at kompilatoren skal vite at det er strukturen Album det er snakk om, og ikke noe annet. Vi trenger heller ikke å oppgi noe annet om strukturen, siden dette ble definert ovenfor.


Tilgang til felt, strcpy, tabeller av strukturer

rediger

Hvordan aksesserer vi feltene i strukturen? Vi har to måter: Dersom vi vil sette innholdet til noe med en gang, kan vi initialisere den med en gang, med { og } slik som vi gjør med tabeller når vi oppretter dem:

struct Album album1 = {"Et album", "Aner ikke", 2007};

Her blir de forskjellige feltene gitt verdier i samme rekkefølge som de er definert i strukturen; "Et album" går til navn-feltet, "Aner ikke" til artist-feltet og 2007 til utgitt-feltet. Dersom vi senere vil sette et felt til en verdi (f.eks. om vi spør brukeren om å oppgi den, eller vi vil sette den på nytt senere) må vi bruke .-operatøren som brukes slik:

variabel.felt

Der variabel er instansen av datastrukturen (eller variabelen om du vil) og felt er det feltet i strukturen du referer til. Om vi f.eks. vil sette utgivelsesåret til album1 til 2007 gjør vi slik:

album1.utgitt = 2007;

Man skulle da tro at følgende også var mulig:

album1.navn = "Trallala";

Det er det imidlertid ikke. Hvis vi tenker oss om, er det logisk. "Trallala" er en streng av tegn -- en tabell med en rekke elementer. I C kan ikke = operatøren brukes til å tilordne hele tabeller verdier på én gang, man må enten gjøre det i en løkke (ved å ta ett og ett element fra den ene tabellen og over til den andre), eller vi kan bruke en ferdig funksjon for det. Headerfila string.h er full av deklarasjoner for slike funksjoner, og deriblant en funksjon vi vil se nærmere på; strcpy.

strcpy kopierer en streng fra et sted til et annet. Det første argumentet er målet og det andre er kilden. Med andre ord gjør vi slik for å kopiere en "Trallala" over i album1.navn:

strcpy(album1.navn, "Trallala");

Merk at vi må inkludere "string.h" for å bruke strcpy!

Et komplett program for å oppsummere så langt:

#include <stdio.h>
#include <string.h>

struct Album {
	char navn[50];
	char artist[50];
	int utgitt;
};

int main(void)
{
	struct Album album1;    /* vi oppretter et nytt album uten å sette felt */
	struct Album album2 = {"A Farewell to Kings", "Rush", 1977};  /* vi oppretter et album og gir verdier med en gang */

	strcpy(album1.navn, "Moving Pictures"); /* sett navnet */
	strcpy(album1.artist, "Rush"); /* sett artisten */
	album1.utgitt = 1981; /* sett utgivelsesår */

	printf("%s - %s (%d)\n", album1.artist, album1.navn, album1.utgitt);
	return 0;
}

Her oppretter vi en instans av album1 og setter de tre feltene dens. Deretter printer vi ut igjen informasjonen. Merk hvordan vi nå har en logisk sammenheng mellom albumet og datarepresentasjonen av det -- det er blitt en ting, et objekt, med sine respektive attributter eller "preg" som navn, artist og utgivelsesår. Dette er faktisk noe av det som legger grunnlaget for objektorientert programmering, men noe utover dette kan man ikke gjøre i C (det er her C++ forskjelliggjør seg fra C på en markant måte.)

Akkurat som vi kan samle en rekke variabler av f.eks. typen int i tabeller kan vi også gjøre det samme med datastrukturer. Hvis vi tenker oss at vi vil lage en enkel albumdatabase, vil det være naturlig å legge albuminstansene i en tabell. Det gjør vi enkelt og greit slik:

struct Album albumdb[100];

Her oppretter vi en tabell med i alt 100 elementer av typen struct Album. For å f.eks. sette utgivelsesåret til album nr. 23 i databasen skriver vi da følgende:

albumdb[22].utgitt = 1976;

Eller vi kan sette navnet:

strcpy(albumdb[22].navn, "Sad Wings of Destiny");

Selvsagt kan vi også lagre inndata i strukturmedlemmer, akkurat som vanlig:

fgets(albumdb[22].artist, sizeof(albumdb[22].artist), stdin);

Typedefinisjoner

rediger

Vi kan også forenkle navnene på datatyper i C. Noen ganger kan det være tungvint å måtte skrive unsigned short int om man bruker den datatypen mye. Hva om vi kunne gi den et nytt og kortere navn? Dette kan man gjøre med nøkkelordet typedef:

typedef unsigned short int usint;

Her definerer vi usint til å være et navn på datatypen unsigned short int. Da kan vi definere variabler på følgende måte:

usint a = 3, b = 9;

Dette er akkurat det samme som om den oppgitte datatypen hadde vært unsigned short int. Vi kan gjøre det samme med strukturer:

typedef struct {
	char navn[50];
	char artist[50];
	int utgitt;
} album;

Her definerer vi datatypen album til å være struct { ... }. Vi kan nå bruke den til å definere variabler:

album album1, album2;