1. Homepage of Dr. Zoltán Porkoláb
    1. Home
    2. Archive
  2. Teaching
    1. Timetable
    2. Bolyai College
    3. C++ (for mathematicians)
    4. Imperative programming (BSc)
    5. Multiparadigm programming (MSc)
    6. Programming (MSc Aut. Sys.)
    7. Programming languages (PhD)
    8. Software technology lab
    9. Theses proposals (BSc and MSc)
  3. Research
    1. CodeChecker
    2. CodeCompass
    3. Templight
    4. Projects
    5. Conferences
    6. Publications
    7. PhD students
  4. Affiliations
    1. Dept. of Programming Languages and Compilers
    2. Ericsson Hungary Ltd

Imperatív programozás 2.

Statikus típusrendszer

Statikus vagy dinamikus típusrendszer

A programozási nyelvek egy részénél a fordítóprogram már a fordítási időben minden egyes részkifejezésről el tudja dönteni, hogy az milyen típusú. Ezeket a nyelveket statikus típusrendszer-rel rendelkezőnek nevezzük. Ennek vannak előnyei, hiszen a nyelv alaposabb ellenőrzéseket tud végrehajtani és optimálisabb kódot is tud generálni. Ilyen nyelv a Fortran, Algol, C, Pascal, C++, Java, C#, Go.

Más nyelveknél, legtöbbször az interpretált nyelveknél, egy változó idővel más típusú értékekre is hivatkozhat. Ilyenkor a fordító futási időben kezeli a típusinformációkat. Ezt dinamikus típusrendszer-nek nevezzük. Ilyen nyelv pl. a Python.

Mindez nem jelenti, hogy a dinamikus típusrendszer nem ellenőrheti a típusok alkalmazását, sőt helytelen alkalmazás hibát okozhat. Azokat a nyelveket, ahol ilyen hibák előfordulnak erősen típusos-nak nevezzük, szemben a gyengén típusos nyelvekkel.

A C erősen típusos statikus típusrendszerrel rendelkező nyelv, a Python erősen típusos dinamikus típusrendszerű.

A második C program: Fahrenheit - Celsius konverzió

(avagy A jó, a rossz és a csúf imdb)

A feladat -100 és +400 közötti Fahrenheit értékek Celsius megfelelőinek kiírása 100-as lépésközzel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 *  BAD VERSION !!!
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 100F  
 */
#include <stdio.h>
int main()
{
  int fahr;

  for ( fahr = -100; fahr <= 400; fahr += 100 )
  {
    printf("Fahr = %d,\tCels = %d\n",fahr,5/9*(fahr-32));
  }
  return 0;
}
$ gcc -ansi -pedantic -W -Wall -std=c11 fahrenheit.c -o fahrenheit

$ ./fahrenheit
Fahr = -100,	Cels = 0
Fahr = 0,	Cels = 0
Fahr = 100,	Cels = 0
Fahr = 200,	Cels = 0
Fahr = 300,	Cels = 0
Fahr = 400,	Cels = 0

A hiba oka, hogy a statikus típusrendszerben a fordító fordítási időben eldönti, hogy mi az 5/9 részkifejezés típusa. Mivel 5 és 9 típusa is int, ez lesz az eredmény típusa is. A konkrét számértékek közömbösek. Az így kapott egész osztás eredménye pedig 0.

Mi más lehetne két egész szám hányadosa? Bizonyos programozási nyelvek más jelölést használnak az egész és a lebegőpontos osztás jelölésére. A Pascal pl. a div operátort használja egész, és a / operátort lebegőpontos eredmény létrehozásásra. A Python3-ban a / lebegőpontos eredményt ad (a Python2-ben még nem!). Viszont ezek a példák sem kivételek: a Pascal és Python3 / művelete mindig lebegőpontos eredményt ad, akkor is, ha matematikailag a hányados egész lenne, pl. 4/2.

Próbáljuk ki 5/9 helyett az 5./9. kifejezést. (Valójában elég lenne 5./9 is, mert ha az egyik operátor lebegőpontos, akkor a C a másikat is azzá konvertálja).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 *  BAD VERSION !!!
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 100F  
 */
#include <stdio.h>
int main()
{
  int fahr;

  for ( fahr = -100; fahr <= 400; fahr += 100 )
  {
    printf("Fahr = %d,\tCels = %d\n",fahr,5./9.*(fahr-32));
  }
  return 0;
}
$ gcc -ansi -pedantic -W -Wall fahrenheit.c -o fahrenheit
fahrenheit.c: In function ‘main’:
fahrenheit.c:17:5: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘double’ [-Wformat=]
     printf( "Fahr = %d,\tCels = %d\n", fahr, 5./9.*(fahr-32) );
     ^

$ ./fahrenheit 
Fahr = -100,	Cels = 913552376
Fahr = 0,	Cels = -722576928
Fahr = 100,	Cels = -722576928
Fahr = 200,	Cels = -722576928
Fahr = 300,	Cels = -722576928
Fahr = 400,	Cels = -722576928

Még mindig hibás a program. Most a Celsius értéket helyesen számoltuk ki, a típusa double, de a kiíráskor egész számként próbáljuk kiírni a %d formátummal. Egy pl. 8 bájtos lebegőpontos számot adunk át paraméterként és az első 4 bájtját próbáljuk egész számkét értelmezni. Ez értelemszerűen hibához vezet.

A C nyelvben paraméterátadáskor csak akkor történik konverzió, ha a hívott függvényt teljes paraméterlistával előzetesen deklaráljuk. A printf esetében a paraméterek feloldása futási időben történik.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 *  UGLY VERSION
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 100F  
 */
#include <stdio.h>
int main()
{
  int fahr;

  for ( fahr = -100; fahr <= 400; fahr += 100 )
  {
    printf("Fahr = %d,\tCels = %f\n",fahr,5./9.*(fahr-32));
  }
  return 0;
}
$ gcc -ansi -pedantic -W -Wall fahrenheit.c -o fahrenheit

$ ./fahrenheit 
Fahr = -100,	Cels = -73.333333
Fahr = 0,	Cels = -17.777778
Fahr = 100,	Cels = 37.777778
Fahr = 200,	Cels = 93.333333
Fahr = 300,	Cels = 148.888889
Fahr = 400,	Cels = 204.444444

Most már működik, de az input nem szépen formázott. Ráadásul a program közepén van egy bonyolult képlet. Refaktoráljuk ki ezt a képletet egy önálló függvénybe.

Figyeljük meg, hogy a függvény szignatúrája (signature) double fahr2cels(double), ezért az int típusú aktuális paraméter lebegőpontossá konvertálva adódik át. A %7.2f formátum 7 karakter szélességben, 2 tizedesre kerekítve írja ki az eredményt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
 *  OK
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 100F  
 */
#include <stdio.h>
double fahr2cels( double f)
{
  return 5./9. * (f-32);
}
int main()
{
  int fahr;

  for ( fahr = -100; fahr <= 400; fahr += 100 )
  {
    printf("Fahr = %4d,\tCels = %7.2f\n",
                       fahr, fahr2cels(fahr));
  }
  return 0;
}
$ gcc -ansi -pedantic -W -Wall fahrenheit.c -o fahrenheit

$ ./fahrenheit 
Fahr = -100,	Cels =  -73.33
Fahr =    0,	Cels =  -17.78
Fahr =  100,	Cels =   37.78
Fahr =  200,	Cels =   93.33
Fahr =  300,	Cels =  148.89
Fahr =  400,	Cels =  204.44

A programot még tovább javíthatjuk a kódban szereplő mágikus konstansok kiemelésével. Ebben a verzióban előfordító direktívákat (preprocessor directive) alkalmazunk, hogy a program konstansait megadjuk.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 *  OK, with #define
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 100F  
 */
#include <stdio.h>
#define LOWER -100
#define UPPER  400
#define STEP   100
double fahr2cels( double f)
{
  return 5./9. * (f-32);
}
int main()
{
  int fahr;

  for ( fahr = LOWER; fahr <= UPPER; fahr += STEP )
  {
    printf( "Fahr = %4d,\tCels = %7.2f\n", 
                        fahr, fahr2cels(fahr) );
  }
  return 0;
}

A következő verzióban névvel ellátott konstansokat alkalmazunk ugyanerre.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 *  OK, with const
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 100F  
 */
#include <stdio.h>
const int lower = -100;
const int upper =  400;
const int step  =  100;
double fahr2cels( double f)
{
  return 5./9. * (f-32);
}
int main()
{
  int fahr;

  for ( fahr = lower; fahr <= upper; fahr += step )
  {
    printf( "Fahr = %4d,\tCels = %7.2f\n", 
                        fahr, fahr2cels(fahr) );
  }
  return 0;
}