Kapitel 8
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 

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 - og med forskellige udfordringer.
#include <stdlib.h>
#include <stdio.h>

double f (double p1, int p2){
  return p1 + p2;                                       // Type of p2 is converted.
}

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

  printf("The first result is %f\n",  f(d, i));         // Types are OK.

  printf("The second result is %f\n", f(i+j, d-e));     // Types are converted.

  printf("The third result is %d\n",  f(d-e, 0));       // Type error in the
                                                        // the executing program:
                                                        // Printing expected: 0.1
                                                        // Wrong result.
  return 0;
}

Program: Output fra ovenstående program.
The first result is 3.500000
The second result is 6.000000
The third result is -1717986912

Henvisning

Statisk typesikkerhed: Typefejl opdages før programmet udføres

Typer i C
Slide Indhold Stikord
Referencer 

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 et heltalsinterval

    • Enumeration typer er 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

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.

  • Dataabstration

    • Der abstraheres fra detaljer i datarepræsentation

      • Repræsentationsuafhængighed

    • Data tilgås via udvalgte funktioner (operationer).

    • Mange typer i C kan opfattes som abstrakte datatyper

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

Dataabstraktion er et nøgleemne i objekt-orienteret programmering.


Fundamentale C datatyper

Oversigt over typer i C
Slide Indhold Stikord
Referencer 

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

  • Skalar typer

    • Numeriske typer

      • Heltal (integral types)

        • short int, int, long int, char

        • enumeration typer

      • Reelle tal (floating-point typer)

        • float, double, long double

    • Pointer typer

  • Sammensatte typer (aggregerede typer).

    • Array typer

      • Tekststrenge

    • Record typer (struct)

Heltalstyper
Slide Indhold Stikord
Referencer 

Heltal dækker også over tegn og boolske værdier 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 shortU%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;

  // Printing the 7 variables of different types:
  printf("int: %i, unsigned: %u, long: %li, unsigned long: %lu\n",
          i, ui, l, ul);
  printf("short: %hi, unsigned short: %hu, char: %c\n\n",
          s, us, c);

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

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

Program: Et program der 'udregner' bytestørrelsen af heltalstyperne.
/* Compute the sizes 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:%3u bytes\n", sizeof(int));
   printf("      unsigned:%3u bytes\n", sizeof(unsigned));
   printf("          long:%3u bytes\n", sizeof(long));
   printf(" unsigned long:%3u bytes\n", sizeof(unsigned long));
   printf("         short:%3u bytes\n", sizeof(short));
   printf("unsigned short:%3u bytes\n", sizeof(unsigned short));
   printf("          char:%3u byte \n", sizeof(char));

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

Program: Output fra programmet (gcc med Cygwin på min Windows maskine).
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: Output fra programmet (gcc på en Linux filserver).
Here are the sizes of some integral types:

           int:  4 bytes
      unsigned:  4 bytes
          long:  8 bytes
 unsigned long:  8 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 (gcc med Cygwin på min Windows maskine).
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 

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};

/* Tedious function. An alternative version appears later in this lecture */
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;
}  

Henvisning

Program: En funktion prnt_day 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 en anvendelse af next_day_of og prnt_day i main.
#include <stdio.h>

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

/* Tedious function. An alternative version appears later in this lecture */
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;
}    

Program: Output fra programmet.
Day1 is 6
Day1 is also Saturday
Three days after day1: Tuesday

I et C program bidrager anvendelse af enumeration typer primært til større læsbarhed

Enumeration types (2)
Slide Indhold Stikord
Referencer 

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 syntaks tilfælde tildeles name1 værdien 0, name2 værdien 1, etc.

    • I det andet syntaks 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;
}    

Program: Et program som både bruger enumeration typer 7-trins og 13-skala karakterer - ULOVLIGT.
/* Notice: The errors are not revealed by all compilers. */

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

enum grade_simple {not_passed, passed};

enum grade_7 {minus_tre = -3, nul_nul = 0, nul_to = 2, fire = 4,
              syv = 7, ti = 10, tolv = 12};

enum grade_13 {nul_nul = 0, nul_tre = 3, fem = 5,     /* Compile error:      */
               seks, syv, otte, ni, ti, elleve,       /* nul_nul, syv and ti */
               tretten = 13};                         /* are redeclared.     */

enum grade_7 convert_grade_13_to_grade_7 (enum grade_13 g13){
  enum grade_7 g7; 

  switch(g13){
    case nul_nul:   g7 = minus_tre; break;
    case nul_tre:   g7 = nul_nul; break;
    case fem:       g7 = nul_nul; break;
    case seks:      g7 = nul_to; break;
    case syv:       g7 = fire; break;
    case otte:      g7 = syv; break;
    case ni:        g7 = syv; break;
    case ti:        g7 = ti; break;
    case elleve:    g7 = tolv; break;
    case tretten:   g7 = tolv; break;
    default:
      printf("Illegal grade in grade_13 encountered. Bye.");
      exit(EXIT_FAILURE);
  }

  return g7;
}

void prnt_grade_7(enum grade_7 g){
  switch (g) {
    case minus_tre: printf("minus-tre"); break;
    case nul_nul: printf("nul-nul"); break;
    case nul_to: printf("nul-to"); break;
    case fire: printf("fire"); break;
    case syv: printf("syv"); break;
    case ti: printf("ti"); break;
    case tolv: printf("tolv"); break;
    default:
        printf("Illegal grade in grade_7 encountered. Bye.");
        exit(EXIT_FAILURE);
  }
}  
    
int main(void){
  
  int grade_number; 
 
  printf("Enter '13 skala' grade: ");
  scanf(" %d", &grade_number);

  printf("7-step grade is: ");
  prnt_grade_7(
         convert_grade_13_to_grade_7(grade_number));
  printf("\n");

  return 0;
}    

Program: Possible compilation - error messages.
$ gcc grades-7-13.c
grades-7-13.c:11:26: error: redeclaration of enumerator 'nul_nul'
grades-7-13.c:8:41: note: previous definition of 'nul_nul' was here
grades-7-13.c:12:22: error: redeclaration of enumerator 'syv'
grades-7-13.c:9:21: note: previous definition of 'syv' was here
grades-7-13.c:12:37: error: redeclaration of enumerator 'ti'
grades-7-13.c:9:29: note: previous definition of 'ti' was here

Program: Et program som både bruger enumeration typer 7-trins og 13-skala karakterer - Nu lovlig, men med tunge navne.
/* Now OK - but tedious */

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

enum grade_simple {not_passed, passed};

enum grade_7 {g7_minus_tre = -3, g7_nul_nul = 0, g7_nul_to = 2, g7_fire = 4,
              g7_syv = 7, g7_ti = 10, g7_tolv = 12};

enum grade_13 {g13_nul_nul = 0, g13_nul_tre = 3, g13_fem = 5,   
               g13_seks, g13_syv, g13_otte, g13_ni, g13_ti, g13_elleve, 
               g13_tretten = 13};                         

enum grade_7 convert_grade_13_to_grade_7 (enum grade_13 g13){
  enum grade_7 g7; 

  switch(g13){
    case g13_nul_nul:   g7 = g7_minus_tre; break;
    case g13_nul_tre:   g7 = g7_nul_nul; break;
    case g13_fem:       g7 = g7_nul_nul; break;
    case g13_seks:      g7 = g7_nul_to; break;
    case g13_syv:       g7 = g7_fire; break;
    case g13_otte:      g7 = g7_syv; break;
    case g13_ni:        g7 = g7_syv; break;
    case g13_ti:        g7 = g7_ti; break;
    case g13_elleve:    g7 = g7_tolv; break;
    case g13_tretten:   g7 = g7_tolv; break;
    default:
      printf("Illegal grade in grade_13 encountered. Bye.");
      exit(EXIT_FAILURE);
  }

  return g7;
}

void prnt_grade_7(enum grade_7 g){
  switch (g) {
    case g7_minus_tre: printf("minus-tre"); break;
    case g7_nul_nul: printf("nul-nul"); break;
    case g7_nul_to: printf("nul-to"); break;
    case g7_fire: printf("fire"); break;
    case g7_syv: printf("syv"); break;
    case g7_ti: printf("ti"); break;
    case g7_tolv: printf("tolv"); break;
    default:
        printf("Illegal grade in grade_7 encountered. Bye.");
        exit(EXIT_FAILURE);
  }
}  
    
int main(void){
  
  int grade_number; 
 
  printf("Enter '13 skala' grade: ");
  scanf(" %d", &grade_number);

  printf("7-step grade is: ");
  prnt_grade_7(
         convert_grade_13_to_grade_7((enum grade_13)grade_number));    /* Notice the type cast */
  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 en blanding af days og enum colors - ingen compileringsfejl.
#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 some_day = sunday;
  enum colors some_color = yellow;

  day_function(some_color);     /* Passing a color in place of a day   */
  color_function(some_day);     /* Passing a day in place of a color   */
                                /* No compile time, nor run time error */
  return 0;
}  

... men ikke nødvendigvis i C

Opgave 8.2. En tilfældig menu

Definer tre forskellige enumeration typer for hhv. forret, hovedret og dessert.

Enumeration typen forret skal indholde værdier for guacamole, tarteletter, lakserulle og græskarsuppe.

Enumeration typen hovedret skal indholde værdier for gyldenkål, hakkebøf, gullash og forloren hare.

Enumeration typen dessert skal indholde værdier for pandekager med is, gulerodskage, choklademousse, og citronfromage.

Undgå dog danske bogstaver i dit C program. Det giver kun besvær!

Skriv en funktion maaltid, som vælger og udskriver én tilfældig ret i hver af de tre kategorier. Læs om "tilfældige tal" i C her.

Kald funktionen maaltid 25 gange i main, med det formål at få genereret et menukort af 25 kombinationer af forret, hovedret og dessert.

Overvej om der skal defineres flere små funktioner, som nedbryder det samlede problem i delproblemer.

Denne opgave er inspireret af en tilsvarende opgave i bogen 'C by Dissection'. Benyttet med tilladelse fra forlaget.

Enumerated types in Standard C act as little more than slightly more readable ways to name integer constants [Harbison & Steele]

As a matter of style, we suggest that programmers treat enumerated types as different from integers and not mix them in integer expressions without using casts [Harbison & Steele]

Henvisning

Enumeration typer i andre sprog
Slide Indhold Stikord
Referencer 

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

  • Enumeration typer i Pascal

    • 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

  • Enumeration typer i C#

    • Enumeration konstanter kan printes på en naturlig måde

    • To enumeration typer, der indeholder samme enumeration konstant, kan sam-eksistere

  • Enumeration typer i Java

    • Java understøttede oprindeligt slet ikke enumeration typer

    • Java 5 introducerede enumeration types

    • I Java er enumeration typer ikke aritmetiske typer, men compiler-genererede klasser

Floating point typer (1)
Slide Indhold Stikord
Referencer 

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;

  // Printing the 3 variables of different types:
  printf("float: %f, double: %f, long double: %Lf\n", f, d, ld);

  // Scanning the 3 variables:
  printf("Enter new values: ");
  scanf(" %f %lf %Lf",&f, &d, &ld);

  // Printing again:
  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:%3u bytes\n", sizeof(float));
   printf("     double:%3u bytes\n", sizeof(double));
   printf("long double:%3u bytes\n", sizeof(long double));

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

Program: Output fra programmet (gcc med Cygwin på min Windows maskine).
Here are the sizes of some floating types:

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

Program: Output fra programmet (gcc på en Linux filserver).
Here are the sizes of some floating types:

      float:  4 bytes
     double:  8 bytes
long double: 16 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 (gcc med Cygwin på min Windows maskine).
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 

  • 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 

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

  • Integral promotion

    • Udtryk af heltalstype hvor i 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 af typen 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

    • 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';     /* c is an one byte integer with the value 97 */

  double d = 123456.7;
  float f = 4322.1F;

  int i;
 
  printf("c - s = %i is converted to int\n", c - s);
  /* INTEGRAL PROMOTION: The type of c - s is promoted to int */
  
  printf("d + f = %f is converted to a double\n", d + f);
  /* WIDENING: 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);
  /* NARROWING: 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 

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) {

  char c;
  long int y;
  float x;
  double z;

  c = 'A' + 10;                  /* 'A' is char 65. 'A' + 10 is the character 'K'. c becomes 'K' */
  y = (long) (c);                /* y becomes 75L */
  x = (float) ((int) y + 1);     /* x becomes 76.0F in type float */
  z = (double) (x);              /* z becomes 76.0  in type double */

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

Program: Output fra programmet.
c: K, y: 75, x: 76.000000, z: 76.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 

I C er det muligt at tildele en type et nyt 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

Program: En alternativ anvendelse af typedef.
#include <stdio.h>

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

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

void prnt_day(weekdays  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){
  
  weekdays  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;
}    

Program: En endnu simplere alternativ anvendelse af typedef.
#include <stdio.h>

typedef enum days {sunday, monday, tuesday, wednesday, thursday, 
                   friday, saturday} 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;
}    

Program: Brug af typedef i forbindelse med funktionstyper.
#include <stdio.h>

typedef double (*combinator_fn)(double, double);

double combine_right(double a, double b, double c, double d, combinator_fn combiner){
  return combiner(a, combiner(b, combiner (c, d)));
}

double combine_left(double a, double b, double c, double d, 
                    combinator_fn combiner){
  return combiner(combiner(combiner(a, b), c), d);
}


double max (double a, double b){
  return a > b ? a : b;
}

double min (double a, double b){
  return a > b ? b : a;
}

double plus(double a, double b){
  return a + b;
}

double minus(double a, double b){
  return a - b;
}

int main(void) {
  double result;

  result = combine_right(5, 7, 8, 11, minus);  /* minus(5, minus(7, minus(8, 11))) = 5 - (7 - (8 - 11)) = -5 */
  printf("Minus combination result: %f\n", result);

  result = combine_right(5, 7, 8, 11, plus); 
  printf("Plus combination result: %f\n", result);

  result = combine_right(5, 7, 8, 11, min); 
  printf("Min combination result: %f\n", result);

  result = combine_right(5, 7, 8, 11, max); 
  printf("Max combination result: %f\n", result);

  return 0;
}


Scope

Scope
Slide Indhold Stikord
Referencer 

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

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

I mange 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 hvor i den er erklæret

      • Mere præcist: Fra stedet for navnet er erklæret til udløbet af blokken

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

      • Introducerer huller i scope

Scope - eksempler
Slide Indhold Stikord
Referencer 

Illustration af scope og scoperegler gennem programeksempler

Program: Illustration af basale scoperegler.
/* We care about the scope of f, local and g.
   The program also illustrates a couple of scope-related problems */

#include <stdio.h>

void f(int a);           /* Scope of f includes main */

int main(void) {
  double local = 5.1;    /* Scope of local is the main block */

  f(4);
  g(local, 6.0F);        /* ERROR: The scope of g does not include this point */
                    
  return 0;
}

void f(int arg){
  int local = 6;         /* The scope of local is the block of f     */
  f(arg - local);        /* OK - both f, arg, and local are in scope */
}

void g(double f, float arg){
  f(5);                  /* ERROR: The scope of the function f */
                         /* does not include this point.       */
                         /* Hole in the scope of function f    */
}

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

int main(void) 
{  
  int a = 5, b = 7, c;

  { 
    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);
   
    a = 9;
  }   

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

Program: Samme program - med kommentarer der afslører værdierne af a, b og c.
#include <stdio.h>

int main(void) 
{  
  int a = 5, b = 7, c;

  { 
    int b = 10, c;

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

    c = a + b;  
    printf("Middle: a + b = %d + %d = %d\n", a, b, c);    /* a: 5, b: 10, c: 15 */
   
    a = 9;                                                /* Outer a assigned!  */
  }   

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

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

Henvisning


Storage classes

Oversigt over storage classes
Slide Indhold Stikord
Referencer 

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

  • auto

    • Default storage class af lokale variable.

    • Indikerer at variablen opstår og nedlægges automatisk i sin blok.

  • register

    • Variant af auto.

    • Indikerer at der skal være hurtig adgang til variablen.

  • extern

    • Indikerer at navnet er global.

    • Navnet kendes af linkeren.

  • static

    • Enten: En synligheds begrænsning - navnet kendes ikke af linkeren.

    • Eller: En mulighed for at lokale variable overlever fra det ene kalde til det andet.

Storage class auto
Slide Indhold Stikord
Referencer 

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

Anvendes sjældent eksplicit i C programmer - default storage class af alle variable i blokke

Storage class static af lokale variable
Slide Indhold Stikord
Referencer 

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: Motivation - Hvad udskriver dette underlige program.
#include <stdio.h>

int accumulating_f (int input){
  int result; 
  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) = 3
accumulating_f(3) = 3
accumulating_f(3) = 3
accumulating_f(3) = 3
accumulating_f(3) = 3
accumulating_f(3) = 3
accumulating_f(3) = 3
accumulating_f(3) = 3
accumulating_f(3) = 3

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 

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

Program: Et separat c program med adgang til den globale variabel.
// In file external-prog-other.c

extern int gv;  // A declaration of gv.
                // Does not allocate storage.
                // gv is defined elsewhere.
void h(void){
  // ...
  gv++;
  // ...
}

Program: Et program med en global variabel gv.
// In file external-prog.c

#include <stdio.h>

int gv = 5;  // A global variable. 
             // The definition which allocates storage.
             // Storage class external per default.

void h(void); 

void f (void){
  // ...
  gv += 2;
  // ...
}

void g (void){
  // ...
  printf("The global variable gv = %d\n", gv  );
  // ...
}
  
  

int main(void) {
        // gv is 5
  f();  //       7
  h();  //       8
  g();  
  
  return 0;
}

Program: Compilation of the programs.
gcc -c external-prog-other.c 
gcc external-prog.c external-prog-other.o

Program: Program output.
The global variable gv = 8

Storage class static af eksterne variable
Slide Indhold Stikord
Referencer 

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.


Tilfældige Tal

Tilfældige tal i C
Slide Indhold Stikord
Referencer 

Det er meget vanskeligt at generere tilfældige tal på en computer

I C tilbydes pseudo tilfældige tal gennem funktionen rand fra stdlib.h

Program: Et program der simulerer et antal kast med en terning.
#include <stdio.h>
#include <stdlib.h> 
#include <time.h>

#define DIE_MAX_EYES 6

int roll_die(void){
  return (rand() % DIE_MAX_EYES) + 1;
}

int main(void) {
  int i;

  srand(time(NULL));  // Seeding the random number generator.
                      // Do this only once!

  for(i = 1; i <= 10; i++)
    printf("The die shows: %d\n", roll_die());
  
  return 0;
}

  • srand

    • Kaldet srand(seed) initialiserer random generatoren

    • En bestemt seed værdi forudbestemmer alle fremtidige "tilfældige" tal

    • Typen af seed er unsigned int

    • srand skal normalt kun kaldes én gang i et program

  • rand

    • Kaldet rand() returnerer et pseudo-tilfældigt heltal.

    • Tallet er beregnet ud fra seed og det forrige tilfældige tal

    • Tallet returneret af rand er mellem 0 og RAND_MAX, af typen int

Flere Opgaver
Slide Indhold Stikord
Referencer 

Henvisning

Opgave 8.3. Tegning af geometriske primitiver - PPM grafik

I denne opgave vil vi - trin for trin - tilføje nye nyttige funktioner, som arbejder på PPM billeder. De første er forholdsvis simple. De senere er lidt mere udfordrende. I denne opgave er billedet tilgængelig i en passende datastruktur, som vi dog ikke har behov for at gå i dybden med på nuværende tidspunkt.

Der findes en video som giver et oplæg til denne opgave. Der findes også en video der introducerer det billedformat som vi arbejder med. Det er kun de første 9 minutter af denne video der er relevant for dette kursus.

Arbejdet vil basere sig på nogle allerede programmerede funktioner, som kan aflæse og sætte én pixel i et PPM billede. Du kan også tænke på en pixel som en farve.

Der findes en zip-fil, som indeholder alt det nødvendige for at komme igang. README filen giver anvisninger på hvordan du kan compilere programmerne.

Nogle af jer har måske allerede lavet lidt forarbejde, nemlig funktioner som arbejder med RGB pixels. Hvis dette ikke er filfældet indholder zip filen min version af pixel funktionerne.

Det anbefales først at læse ppm.h og det simple eksempel på anvendelse af nogle af funktionerne fra ppm.h. Denne anvendelse genererer dette billede.

Programmer nu følgende:

  • En funktion der tegner en vandret linie med en bestemt farve i et ppm billede.
  • En funktion der tegner en lodret linie med en bestemt farve.
  • Lav varianter af ovenstående som også tilbyder tegning af vandrette/lodrette linier med en bestemt tykkelse.
  • Lav evt. varianter af ovenstående som tilbyder tegning af vandrette/lodrette stiplede linier.
  • En funktion der tegner en ikke-udfyldt rektangel (parallel med akserne) med en bestemt farve.
  • En funktion der tegner en udfyldt rektangel (parallel med akserne) med en bestemt farve.
  • En funktion der tegner en vilkårlig linie med en bestemt farve. Eksperimenter med denne funktion - og find selv frem til udfordringen!
  • En funktion der tegner en cirkel med en bestemt radius, og med en bestemt farve - ikke-udfyldt, dernæst udfyldt.

Arbejdet kan fortsættes med endnu flere primitiver, i forskellige retninger. Tegning af stiplede linier er en mulighed. Eller indnu mere ambitiøst (for de særligt inkarnerede) indsættelse af tegn (fra en bestemt font) i et ppm billede.

Det er naturligvis vigtigt, at du kan se den grafik som genereres af dit program. Her er et antal muligheder, som jeg ved virker:

  • Hvis du åbner en Portable Pixmax fil med Emacs, vises grafikken. Både PPM og PNM extension virker.
  • GIMP (GNU Image Manipulation Program) kan præsentere billederne.
  • Image Magick kan konvertere PPM og PNM filer til andre grafik formater, f.eks. GIF og JPG. I en Windows command prompt skrives: convert fil.pnm fil.jpg, efter at ImageMagick er installeret på din maskine.


Samlede referencer
Indhold Stikord
Foldoc: type
Operatorer, prioriteter og associeringer
En meget mere kompakt definition af next_day_of
Type konverteringer til og fra enumeration typer
Operator prioriteringer i C
Den oprindelige definition af next_day_of
Samme program tidligere i lektionen
Side med lignende program
Blokke
Slide med diskussion af PPM og Pixel funktioner

 

Kapitel 8: 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: 9. maj 2022, 13:59:35