Kapitel 5
Datatyper

Kurt Nørmark ©
Institut for Datalogi, Aalborg Universitet


Sammendrag
Forrige lektion Næste lektion
Stikord Referencer Indhold
Emnet for denne lektion er datatyper. Efter en oversigt over typerne i C tager vi endnu et kig på heltal og flydende tal. Som noget nyt studerer vi enumerationtyper og typedefinition. Også mulighederne for at konvertere en type til en anden kommer vi omkring. Dernæst ser vi på scopebegrebet og storage classes i C. Vi ser dernæst overordnet og begrebsmæssigt på arrays, som er et eksempel på en sammensat datatype. Vi slutter af med en kort introduktion til abstrakte datatyper.


Typer

Typer
Slide Indhold Stikord
Referencer Lærebog 

Begrebet type: En type er en mængde af værdier med fælles egenskaber

  • Hvorfor anvende typer?

    • Typer forbedrer programmets læsbarhed og forståelighed

    • Typer gør det muligt for compileren at generere bedre og mere effektiv kode

    • Typer gør det muligt at opdage fejl i programmet - typisk under oversættelsen

Program: Et C program med typer.
#include <stdlib.h>

double f (double p1, int p2){
  if (p2 == 0)
    return p1 * 2.0;
  else
    return p1 - p2;
}

int main(void){
  int i = 1, j = 5;
  double d = 2.0, e = 6.0;

  printf("The first result is %lf\n", f(e-d, i+j));
  printf("The second result is %lf\n", f(d-e, 0));

  return 0;
}

Program: Output fra ovenstående program.
The first result is -2.000000
The second result is -8.000000

Henvisning

En type udgør en klassificering af data som fortæller compilereren hvordan programmøren ønsker at fortolke data

Typer i C
Slide Indhold Stikord
Referencer Lærebog 

ANSI C har en relativ løs skelnen mellem værdier i forskellige typer

  • Eksempler på løshed i C's typesystem:

    • Typen boolean er indlejret i de numeriske typer

    • Typen char er reelt et heltalsinterval

    • Enumeration typer er reelt heltalstyper

      • Vi møder enumeration typer senere i denne lektion

    • De fleste numeriske typer kan implicit konverteres til hinanden

Det traditionelle C sprog har en endnu mere løs omgang med typer end ANSI C


Fundamentale C datatyper

Oversigt over typer i C
Slide Indhold Stikord
Referencer Lærebog 

  • Typer i C

    • Typen void svarende til den tomme mængde uden værdier.

    • Skalar typer

      • Aritmetiske typer

        • Heltalstyper (integral types)

          • short, int, long, char

          • enumeration typer

        • Floating-point typer

          • float, double, long double

      • Pointer typer

    • Sammensatte typer (aggregerede typer).

      • Array typer

      • Record typer ( structure types)

Heltalstyper
Slide Indhold Stikord
Referencer Lærebog 

Overskriften dækker foruden de egentlige heltal også tegn og booleans i C

Tabel.
TypeKort typenavnSuffixprintf conv. tegnscanf conv. tegnEksempel
signed intintintet%d eller %i%d eller %i-123
unsigned intunsignedu eller U%u%u123U
signed long intlongl eller L%li%li-123456L
unsigned long intunsigned longlu eller LU%lu%lu123456LU
signed short intshortintet%hi%hi-12
unsigned short intunsigned shortintet%hu%hu12U
charchar-%c%c'a' eller 97
 

Henvisning

Program: Et C program som illustrerer ovenstående.
#include <stdio.h>

int main(void) {
  int i = -123;
  unsigned ui = 123U;
  long l = -123456L;
  unsigned long ul = 123456LU;
  short s = -12;
  unsigned short us = 12U;
  char c = 97;

  printf("int: %i, unsigned: %u, long: %li, unsigned long: %lu\n",
          i, ui, l, ul);
  printf("short: %hi, unsigned short: %hu, char: %c\n",
          s, us, c);

  printf("Enter integer numbers: ");
  scanf(" %i %u %li %lu %hi %hu %c", &i, &ui, &l, &ul, &s, 
        &us, &c);

  printf("int: %i, unsigned: %u, long: %li, unsigned long: %lu\n",
          i, ui, l, ul);
  printf("short: %hi, unsigned short: %hu, char: %c\n", s, us, c);
  
  return 0;
}

Program: Et program der 'udregner' bytestørrelsen af heltalstyperne.
/* Compute the size of some fundamental types. */

#include <stdio.h>

int main(void)
{
   printf("\n");
   printf("Here are the sizes of some integral types:\n\n");

   printf("           int:%3d bytes\n", sizeof(int));
   printf("      unsigned:%3d bytes\n", sizeof(unsigned));
   printf("          long:%3d bytes\n", sizeof(long));
   printf(" unsigned long:%3d bytes\n", sizeof(unsigned long));
   printf("         short:%3d bytes\n", sizeof(short));
   printf("unsigned short:%3d bytes\n", sizeof(unsigned short));
   printf("          char:%3d byte \n", sizeof(char));

   printf("\n");
   return 0;
}

Program: Output fra programmet.
Here are the sizes of some integral types:

           int:  4 bytes
      unsigned:  4 bytes
          long:  4 bytes
 unsigned long:  4 bytes
         short:  2 bytes
unsigned short:  2 bytes
          char:  1 byte 

Program: Et program der tilgår konstanter i limits.h.
#include <stdio.h>
#include <limits.h>
int main(void) {

  printf("Min. int:            %12i  Max. int:           %12i\n", 
          INT_MIN, INT_MAX);

  printf("Min. unsigned int:   %12u  Max. unsigned int:  %12u\n", 
          0, UINT_MAX);

  printf("Min. long:           %12li  Max. long:          %12li\n", 
          LONG_MIN, LONG_MAX);

  printf("Min. unsigned long:  %12lu  Max. unsigned long: %12lu\n", 
          0, ULONG_MAX);

  printf("Min. short:          %12hi  Max. short:         %12hi\n",
          SHRT_MIN, SHRT_MAX);

  printf("Min. unsigned short: %12hu  Max. short:         %12hu\n",
          0, USHRT_MAX);

  return 0;
}

Program: Output fra programmet.
Min. int:             -2147483648  Max. int:             2147483647
Min. unsigned int:              0  Max. unsigned int:    4294967295
Min. long:            -2147483648  Max. long:            2147483647
Min. unsigned long:             0  Max. unsigned long:   4294967295
Min. short:                -32768  Max. short:                32767
Min. unsigned short:            0  Max. short:                65535

Enumeration types (1)
Slide Indhold Stikord
Referencer Lærebog 

Begrebet enumeration type: En enumeration type er en endelig mængde af heltal som er knyttet til enumeration konstanter
Begrebet enumeration konstant: En enumeration konstant (enumerator) er et navn, som på mange måder ligner en variabel

Syntax: Syntaktisk definition af to mulige former af enumeration typer i C

enum tag {name1, name2, ... namei}

enum tag {name1=expr1, name2=expr2, ... namei=expri}

Program: En enumeration type enum days og en funktion next_day_of.
enum days {sunday, monday, tuesday, wednesday, thursday, 
           friday, saturday};

enum days  next_day_of(enum days  d){
  enum days next_day;
  switch (d){
    case sunday: next_day = monday; 
      break;
    case monday: next_day = tuesday;
      break;
    case tuesday: next_day = wednesday;
      break;
    case wednesday: next_day = thursday;
      break;
    case thursday: next_day = friday;
      break;
    case friday: next_day = saturday;
      break;
    case saturday: next_day = sunday;
      break;
  }
  return next_day;
}  

Program: En funktion der udskriver det symbolske navn på en dag.
void prnt_day(enum days d){
  switch (d) {
    case sunday: printf("Sunday");
       break;
    case monday: printf("Monday");
       break;
    case tuesday: printf("Tuesday");
       break;
    case wednesday: printf("Wednesday");
       break;
    case thursday: printf("Thursday");
       break;
    case friday: printf("Friday");
       break;
    case saturday: printf("Saturday");
       break;
  }
}   

Program: Det samlede program - inklusive main.
#include <stdio.h>

enum days {sunday, monday, tuesday, wednesday, thursday, 
           friday, saturday};

enum days  next_day_of(enum days  d){
  enum days next_day;
  switch (d){
    case sunday: next_day = monday; 
      break;
    case monday: next_day = tuesday;
      break;
    case tuesday: next_day = wednesday;
      break;
    case wednesday: next_day = thursday;
      break;
    case thursday: next_day = friday;
      break;
    case friday: next_day = saturday;
      break;
    case saturday: next_day = sunday;
      break;
  }
  return next_day;
}  

void prnt_day(enum days d){
  switch (d) {
    case sunday: printf("Sunday");
       break;
    case monday: printf("Monday");
       break;
    case tuesday: printf("Tuesday");
       break;
    case wednesday: printf("Wednesday");
       break;
    case thursday: printf("Thursday");
       break;
    case friday: printf("Friday");
       break;
    case saturday: printf("Saturday");
       break;
  }
}   

int main(void){
  
  enum days day1 = saturday,  another_day;
  int i;

  printf("Day1 is %d\n", day1);

  printf("Day1 is also "); prnt_day(day1); printf("\n");

  another_day = day1;
  for(i = 1; i <= 3; i++)
    another_day = next_day_of(another_day);
  
  printf("Three days after day1: ");  prnt_day(another_day); 
  printf("\n");

  return 0;
}    

I et C program bidrager anvendelse af enumeration typer primært til større læsbarhed - og lettere programforståelse

Enumeration types (2)
Slide Indhold Stikord
Referencer Lærebog 

Syntax: Syntaktisk definition af to mulige former af enumeration typer i C

enum tag {name1, name2, ... namei}

enum tag {name1=expr1, name2=expr2, ... namei=expri}

  • Regler om betydningen af enumeration typer og konstanter

    • Enumeration konstanter har samme status som variable og må som sådan kun defineres én gang i det samme scope

    • I det første tilfælde tildeles name1 værdien 0, name2 værdien 1, etc.

    • I det andet tilfælde bestemmer programmøren hvilke heltalsværdier de enkelte enumeration konstanter tildeles

      • Der er mulighed for at to eller flere konstanter i samme enumeration type har samme værdi

Program: Et eksempel på et program som bruger enumeration typer til karakterskalaer.
#include <stdio.h>

enum grade_simple {not_passed, passed};

enum grade_13 {nul_nul = 0, nul_tre = 3, fem = 5, seks,
               syv, otte, ni, ti, elleve, tretten = 13};

enum grade_simple convert_grade_13_to_simple_grade 
                                     (enum grade_13 g){
  enum grade_simple result; 

  if (g <= fem)
    result = not_passed;
  else 
    result = passed;
  
  return result;
}

void prnt_grade_simple(enum grade_simple g){
  switch (g) {
    case not_passed: printf("Not passed");
      break;
    case passed: printf("Passed");
      break;
  }
}  
    
int main(void){
  
  int grade_number; 
 
  printf("Enter '13 skala' grade: ");
  scanf(" %d", &grade_number);

  prnt_grade_simple(
         convert_grade_13_to_simple_grade(grade_number));
  printf("\n");

  return 0;
}    

Enumeration types (3)
Slide Indhold Stikord
Referencer 

Blanding af værdier i forskellige enumeration typer bør foranledige en typefejl...

Program: Et program med enum days og enum colors.
#include <stdio.h>
#include <stdlib.h>

enum days {sunday, monday, tuesday, wednesday, thursday, 
           friday, saturday};

enum colors {red, green, blue, yellow};

int day_function (enum days some_day){
  /* some day calculation */
  return 2;
}

int color_function (enum colors some_color){
  /* some color calculation */
  return 1;
}

int main(void){

  enum days d = sunday;
  enum colors c = yellow;

  day_function(c);
  color_function(d);
}  

... men ikke nødvendigvis i C

Enumeration typer i andre sprog
Slide Indhold Stikord
Referencer Lærebog 

Enumeration typer i C er anderledes end tilsvarende typer i de fleste andre programmeringssprog

  • Enumeration typer i Pascal-lignende sprog:

    • Værdien af enumeration konstanterne er nye, entydige værdier - ikke heltal

    • Ordningen af enumeration konstanterne er af betydning

      • Efterfølger og forgænger funktioner af enumeration konstanter giver mening

Henvisning

Java er først på det seneste begyndt at understøtte enumeration typer

Floating point typer (1)
Slide Indhold Stikord
Referencer Lærebog 

Tabel.
TypeSuffixprintf conv. tegnscanf conv. tegnEksempel
floatf eller F%f%f5.4F
doubleintet%f%lf5.4
long doublel eller L%Lf%Lf5.4L
 

Program: Et C program som illustrerer ovenstående.
#include <stdio.h>

int main(void) {

  float f = 123.456F;
  double d = 123.456;
  long double ld = 123.456L;

  printf("float: %f, double: %f, long double: %Lf\n", f, d, ld);

  printf("Enter new values: ");
  scanf(" %f %lf %Lf",&f, &d, &ld);
  printf("float: %f, double: %f, long double: %Lf\n", f, d, ld);
  
  return 0;
}

Program: Et program der 'udregner' bytestørrelsen af float typerne.
/* Compute the size of some fundamental types. */

#include <stdio.h>

int main(void)
{
   printf("\n");
   printf("Here are the sizes of some floating types:\n\n");

   printf("      float:%3d bytes\n", sizeof(float));
   printf("     double:%3d bytes\n", sizeof(double));
   printf("long double:%3d bytes\n", sizeof(long double));

   printf("\n");
   return 0;
}

Program: Output fra programmet.
Here are the sizes of some floating types:

      float:  4 bytes
     double:  8 bytes
long double: 12 bytes

Program: Et program der tilgår konstanter i floats.h.
#include <stdio.h>
#include <float.h>
int main(void) {

 printf("Min. float:       %20.16e  \nMax. float:       %20.16e\n\n", 
         FLT_MIN, FLT_MAX);

 printf("Min. double:      %20.16le \nMax. double:      %20.16le\n\n", 
         DBL_MIN, DBL_MAX);

 printf("Min. long double: %26.20Le \nMax. long double: %26.20Le\n\n", 
         LDBL_MIN, LDBL_MAX);


  return 0;
}

Program: Output fra programmet.
Min. float:       1.1754943508222875e-38  
Max. float:       3.4028234663852886e+38

Min. double:      2.2250738585072014e-308 
Max. double:      1.7976931348623157e+308

Min. long double: 3.36210314311209350626e-4932 
Max. long double: 1.18973149535723176502e+4932

Floating point typer (2)
Slide Indhold Stikord
Referencer Lærebog 

  • Andre egenskaber af floating point typer:

    • Notation

      • Decimal: 12345.6789

      • Eksponential: 0.1234567e-89

      • En flydende tal konstant skal enten indeholde decimal punktet eller eksponential delen (eller begge)

    • Præcision: Antallet af signifikante cifre i tallet

      • float: typisk 6 cifre

      • double: typisk 15 cifre

      • long double: typisk 20 cifre

    • Interval (range): Største og mindste mulige positive værdier

      • float: Typisk 10-38 til 1038

      • double: Typisk 10-308 til 10308

      • long double: Typisk 10-4932 til 104932


Typekonvertering og typedef

Implicit typekonvertering
Slide Indhold Stikord
Referencer Lærebog 

C foretager en række typekonverteringer 'bag ryggen på programmøren'

  • "Integral promotion"

    • Udtryk af heltalstype hvori der indgår short eller char værdier konverteres til en værdi i typen int.

    • Eksempel: Hvis x og y er af typen short er værdien af x + y int.

  • "Usual arithmetic conversions" - widening

    • Konvertering af en mindre præcis værdi til en tilsvarende mere præcis værdi således at begge operander får samme type

    • Der mistes ikke information og præcision

  • Narrowing - demotion

    • Konvertering af mere præcis værdi til en mindre præcis værdi

    • Der mistes information

Program: Eksempler på implicitte typekonverteringer.
#include <stdio.h>

int main(void) {

  short s = 12; char c = 'a';
  double d = 123456.7; float f = 4322.1;
  int i;
 
  printf("c - s = %i is converted to int\n", c - s);
  /* The type of c - s is promoted to int */
  
  printf("d + f = %f is converted to a double\n", d + f);
  /* f is converted to double before adding the numbers */

  i = d;
  printf("In assigning d to i %f is demoted to the int %i\n", d, i);
  /* d is converted to an int - information is lost */
  
  return 0;
}

Program: Output fra programmet.
c - s = 85 is converted to int
d + f = 127778.800098 is converted to a double
In assigning d to i 123456.700000 is demoted to the int 123456

Eksplicit typekonvertering
Slide Indhold Stikord
Referencer Lærebog 

I C er det muligt for programmøren at konvertere værdien af et udtryk til en anden type ved brug af en såkaldt cast operator

Syntax: Syntaktisk definition af casting - eksplicit typekonvertering i C

(typeName) expression

Henvisning

Program: Et program med eksempler på casts.
#include <stdio.h>

int main(void) {

  long y;
  float x;
  double z;

  y = (long) ('A' + 10);
  x = (float) ((int) y + 1);
  z = (double) (x = 77);

  printf("y: %li, x: %f, z: %f", y, x, z);
  return 0;
}

Program: Output fra programmet.
y: 75, x: 77.000000, z: 77.000000

Program: Funktionen next_day_of omskrevet med brug af casts.
enum days next_day_of(enum days d){
  return (enum days) (((int) d + 1) % 7);
}

Henvisning

Navngivne typer med typedef
Slide Indhold Stikord
Referencer Lærebog 

I C er det muligt at tildele en type et bestemt navn

I tilfælde af komplicerede typer kan dette øge læsbarheden af et program

Syntax: Syntaktisk definition af casting - eksplicit typekonvertering i C

typedef oldType newType

Program: En omskrivning af ugedags programmet som benytter typedef.
#include <stdio.h>

enum days {sunday, monday, tuesday, wednesday, thursday, 
           friday, saturday};
typedef enum days days;

days  next_day_of(days  d){
  return ( days ) (((int) d + 1) % 7);
}  

void prnt_day(days  d){
  switch (d) {
    case sunday: printf("Sunday");
       break;
    case monday: printf("Monday");
       break;
    case tuesday: printf("Tuesday");
       break;
    case wednesday: printf("Wednesday");
       break;
    case thursday: printf("Thursday");
       break;
    case friday: printf("Friday");
       break;
    case saturday: printf("Saturday");
       break;
  }
}   

int main(void){
  
  days  day1 = saturday,  another_day;
  int i;

  printf("Day1 is %d\n", day1);

  printf("Day1 is also "); prnt_day(day1); printf("\n");

  another_day = day1;
  for(i = 1; i <= 3; i++)
    another_day = next_day_of(another_day);
  
  printf("Three days after day1: ");  prnt_day(another_day); 
  printf("\n");

  return 0;
}    

Henvisning


Scope og storage classes

Scope
Slide Indhold Stikord
Referencer Lærebog 

Scope reglerne bruges til at afgøre i hvilke programdele et navn er gyldigt.

På dansk kan vi bruge ordet 'virkefelt' i stedet for 'scope'.

I store programmer bruges det samme navn ofte i forskellige scopes med forskellige betydninger.

Begrebet scope: Scope af et navn er de dele af en programtekst hvor navnet er kendt og tilgængeligt

  • De mest basale scoperegler i C

    • Scopet af et navn er den blok hvori den er erklæret

    • Navne i en blok skygger for tilsvarende navne i omkringliggende blokke

      • Introducerer huller i scope

Program: Illustration af scope i tre indlejrede blokke.
#include <stdio.h>

int main(void) {  

  int a = 5, b = 7, c;

  c = a + b;

  { 
    int b = 10, c;

    {
      int a = 3, c;
      c = a + b;  
      printf("Inner:  a + b = %d + %d = %d\n", a, b, c);
    }

    c = a + b;  
    printf("Middle: a + b = %d + %d = %d\n", a, b, c);
  }   

  c = a + b;  
  printf("Outer:  a + b = %d + %d = %d\n", a, b, c);
  
  return 0;
}   

Program: Output fra programmet.
Inner:  a + b = 3 + 10 = 13
Middle: a + b = 5 + 10 = 15
Outer:  a + b = 5 + 7 = 12

Henvisning

Storage class auto
Slide Indhold Stikord
Referencer Lærebog 

Storage class af variable og funktioner medvirker til bestemmelse af disses scope

Syntax: Syntaktisk definition af storage classes i variabel erklæringer

storageClass type var1, var2, ..., vari

Begrebet auto: Variable med storage class auto kaldes automatiske variable
Begrebet automatisk variabel: En automatisk variabel er lokal i en blok, dvs den skabes når blokken aktiveres og nedlægges når blokken forlades

  • Storage class auto

    • Default storage class i blokke, herunder kroppe af funktioner

      • Hvis man ikke angiver storage class af variable i blokke er den således auto

    • Man kan angive storage class auto eksplicit med brug af nøgleordet auto

Henvisning

Storage class static af lokale variable
Slide Indhold Stikord
Referencer Lærebog 

I blokke er storage class static et alternativ til storage class auto

Begrebet statisk variabel: En statisk variabel i en blok beholder sin værdi fra en aktivering af blokken til den næste

  • Storage class static         - lokale variable i en blok

    • Skal angives med static som storageClass

    • Variabel initialiseringen udføres ved første aktivering

Program: Illustration af statiske lokale variable - en funktion der husker forrige returværdi.
#include <stdio.h>

int accumulating_f (int input){
  int result; 
  static int previous_result = 1;

  if (previous_result == 1)
    result = input;
  else
    result = previous_result * input;

  previous_result = result;
  return result;
}

int main(void) {  
  int i;
 
  for (i = 0; i < 10; i++)
    printf("accumulating_f(%d) = %d\n", 3, accumulating_f(3));   
  
  return 0;
}   

Program: Output fra programmet.
accumulating_f(3) = 3
accumulating_f(3) = 9
accumulating_f(3) = 27
accumulating_f(3) = 81
accumulating_f(3) = 243
accumulating_f(3) = 729
accumulating_f(3) = 2187
accumulating_f(3) = 6561
accumulating_f(3) = 19683
accumulating_f(3) = 59049

Statiske lokale variable er et alternativ til brug af globale variable.

En statisk lokal variabel bevarer sin værdi fra kald til kald - men er usynlig uden for funktionen.

Storage class extern
Slide Indhold Stikord
Referencer Lærebog 

Begrebet extern: Variable og funktioner med storage class extern kaldes eksterne variable
Begrebet ekstern variabel: En ekstern variabel eller funktion er global tilgængelig i hele programmet

  • Storage class extern

    • Default storage class af variable som erklæres uden for funktioner

    • Default storage class af alle funktioner er også extern

    • "External linkage"

Storage class static af eksterne variable
Slide Indhold Stikord
Referencer Lærebog 

For globale variable virker storage class static som en scope begrænsning i forhold til storage class extern.

Statiske globale variable er private i den aktuelle kildefil.

Begrebet statisk variabel: En statisk variabel på globalt niveau er kun synlig i den aktuelle kildefil

  • Storage class static         - globale variable

    • Skal angives med static som storage class

    • Synlighed i den aktuelle kompileringsenhed - kildefil

    • Kun synlig fra det punkt i filen hvor variablen er erklæret

    • "Internal linkage"

Funktioner kan også være statiske, og dermed private i en kildefil.

Synlighedskontrol er vigtig i store programmer.

Synlighedskontrol er meget vigtig i objekt-orienteret programmering.


Abstrakte datatyper

Abstrakte datatyper
Slide Indhold Stikord
Referencer 

Begrebet abstrakt datatype: En abstrakt datatype er en mængde af værdier og en tilhørende mængde af operationer på disse værdierVærdierne i en abstrakt datatype kaldes ofte objekter. Dette er specielt tilfældet i det objekt-orienterede programmeringsparadigme, hvor ideen om abstrakte datatyper er helt central.

  • Dataabstraktion:

    • Logisk sammenhørende værdier grupperes og indkapsles på passende vis

      • Sådanne værdier kaldes ofte for objekter

    • Operationer på objekterne knyttes tæt til disse

      • Om muligt flytter operationerne ind i datatypen, hvilket bringer os fra records til klasser

    • Synlighed af objektets egenskaber (data og operationer) afklares

      • Det er attraktivt at flest mulige egenskaber holdes private

    • Objektets grænseflade til andre objekter udarbejdes omhyggeligt

      • Grænsefladen udgøres af en udvalgt mængde af operationer

I en senere lektion vil vi studere abstrakte datatyper og dataabstraktion i yderligere detalje.

Dataabstraktion er nøgleemnet i objekt-orienteret programmering.


Samlede referencer
Indhold Stikord
Foldoc: type
Operatorer, prioriteter og associeringer
Enumerationtyper i Java
Operator prioriteringer i C
Den oprindelige definition af next_day_of
Samme program tidligere i lektionen
Side med lignende program
Blokke

 

Kapitel 5: Datatyper
Kursets hjemmeside     Forfatteren's hjemmeside     Om frembringelsen af disse sider     Forrige lektion (top)     Næste lektion (top)     Forrige lektion (bund)     Næste lektion (bund)     
Genereret: 7. Juli 2010, 15:11:13