>> Inapoi <<

========
Capitolul 6
========

=====================
Tipuri fundamentale de date
=====================


---------------------------
Declaratii si expresii
---------------------------

Variabilele si constantele sunt obiecte cu care se lucreaza intr-un program. 
In C, toate variabilele trebuie declarate inainte de a fi folosite. 
Declaratiile au doua scopuri:

1. spun compilatorului cat spatiu de memorie trebuie rezervat pentru memorarea
   acelor variabile;
2. permit compilatorului sa instruiasca masina pentru a face  operatiile 
   specifice corect.
   
De exemplu, in expresia a + b, operatorul + este aplicat pentru doua variabile.
Masina executa in mod diferit adunarea pentru variabile de tip "int" si pentru 
variabile de tip "float". Bineinteles, pentru programator aceste conventii sunt
transparente (se mai spune ca "+" este operator de supraincarcare). Expresiile 
sunt combinatii (cu inteles) de constante, variabile si apeluri de functii. 
Majoritatea expresiilor (cum ar fi, de exemplu, variabilele) au si valoare si 
tip. In multe situatii, valoarea returnata depinde in principal de tipul 
expresiei.


------------------------------------
Tipuri fundamentale de date
------------------------------------
Avem urmatoarele tipuri fundamentale de date (scriere intreaga - lunga):

    char                         signed char         unsigned char
    signed short int             signed int          signed long int
    unsigned short int           unsigned int        unsigned long int
    float                        double              long double
    
Toate acestea sunt cuvinte rezervate, deci nu se pot folosi ca nume de 
variabile. Alte tipuri de date, cum ar fi vectorii si pointerii, sunt derivate
din tipurile fundamentale.

De obicei, cuvantul rezervat "signed" nu se mai scrie. De exemplu, "signed int"
este echivalent cu "int". De asemenea, cuvintele "short int", "long int" si 
"unsigned int" pot fi prescurtate, de obicei, ca "short", "long" si "unsigned". 
Cu aceste conventii, tabelul de mai sus se mai poate scrie:

    char                        signed char          unsigned char
    short                       int                  long
    unsigned short              unsigned             unsigned long
    float                       double               long double

Tipurile fundamentale se pot grupa dupa functionalitate:

1. tipurile integrale sunt cele care sunt folosite pentru reprezentarea 
   valorilor intregi;
2. tipurile reale sunt cele care sunt folosite pentru reprezentarea 
   valorilor reale;
3. tipurile aritmetice sunt tipuri integrale sau reale.

Acestea sunt:

Tipuri integrale:
        char                       signed char        unsigned char
        short                      int                long
        unsigned short             unsigned           unsigned long
        
Tipuri reale:        
        float                      double             long double


------------------------------
Caractere si tipul "char"
------------------------------
In C, variabilele de orice tip integral pot fi folosite pentru reprezentarea 
caracterelor. In particular, variabilele de tip "char" si "int" se folosesc 
pentru acest scop. Am vazut in capitolul precedent ca atunci cand dorim sa 
comparam o variabila cu EOF, atunci trebuie sa declaram acea variabila de tip 
"int", si nu de tip "char". Constante cum ar fi 'a', '+' pe care le gandim ca 
fiind caractere sunt de tip "int", si nu de tip "char". Retineti ca nu exista 
constante de tip "char" !!!

Reamintim ca toate caracterele sunt tratate ca "intregi mici", si reciproc, 
intregii mici sunt tratati ca niste caractere. In particular, orice expresie 
integrala poate fi afisata in format intreg sau caracter.

-----------
Exemplu: Presupunem ca avem o "bucata" de cod C:
-----------
        char  c = 'a';          /* 'a' are codul ASCII 97 */
        int   i = 65;           /* 65 este codul ASCII pentru 'A' */
        
        printf("%c", c + 1);    /* este afisat b  */
        printf("%d", c + 2);    /* este afisat 99 */
        printf("%c", i + 3);    /* este afisat D  */
        
In C, fiecare caracter este memorat pe un octet de memorie. Pe aproape toate 
masinile, un octet este compus din 8 biti. Fie declaratia 

                char c = 'a';
                
Putem gandi ca "c" este memorat pe un octet astfel

                ---------------------------------
                | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
                ---------------------------------
                  7   6   5   4   3   2   1   0
                  
Fiecare celula reprezinta un bit si fiecare bit este numerotat (incepand cu cel
mai putin semnificativ). Bitii care formeaza un octet sunt fie "on", fie "off",
aceste stari fiind reprezentate prin 1 si 0 respectiv. Acesta ne conduce sa 
gandim fiecare octet din memorie ca un sir de 8 cifre binare (se mai numesc 
siruri de biti). 

Astfel variabila "c" poate fi gandita ca sirul de biti 

                01100001
                
Mai general, fiecare cuvant masina poate fi gandit ca un sir de cifre binare 
grupate in octeti.

Un sir de cifre binare poate fi deci gandit ca un numar binar (adica in baza 2).
Fara a intra in detalii matematice (teorema bazei de numeratie) vom face doar 
un exemplu: 

-----------
Exemplu:
-----------
Valoarea lui "c" este numarul 01100001 (in baza 2)

  1 x 2^6 + 1 x 2^5 + 0 x 2^4 + 0 x 2^3 + 0 x 2^2 + 0 x 2^1 + 1 x 2^0
  
care inseamna 64 + 32 + 1 = 97 in notatia zecimala (in baza 10).

ANSI C pune la dispozitie trei tipuri referitoare la caractere:

        char            signed char             unsigned char
        
De obicei, tipul "char" este echivalent cu "signed char" sau "unsigned char", 
depinzand de compilator. Fiecare din aceste trei tipuri se memoreaza pe un 
octet (deci poate "tine" 256 valori distincte). Pentru "signed char", valorile
sunt intre -128 si 127, iar pentru "unsigned char" intre 0 si 255.


-----------------------
Tipul de date "int"
-----------------------
Tipul de date "int" este cel mai folosit tip din limbajul C. Acest tip, 
impreuna cu alte tipuri integrale (cum ar fi: "char", "short" si "long") este 
desemnat pentru lucrul cu valori intregi reprezentabile pe o masina. 

In matematica, numerele naturale sunt 0, 1, 2, 3, ..., care impreuna cu cele 
negative (corespunzatoare) formeaza numerele intregi. Pe o masina, se pot 
reprezenta (folosind un tip integral) numai o submultime finita a acestor 
numere. 

De obicei, un cuvant se memoreaza pe un cuvant masina. Anumite calculatoare 
folosesc cuvante de 2 octeti (=16 biti), altele 4 octeti (=32 biti). 

-----------
Exemple:
-----------
1. Masini ce folosesc cuvinte memorate pe 2 octeti: PC
2. Masini ce folosesc cuvinte memorate pe 4 octeti: Apollo, Hewlett-Packard, 
   Next, Silicon Graphics, Sun, etc.
   
Presupunem ca lucram pe un calculator care lucreaza pe 4 octeti. Aceasta 
implica ca un cuvant are 32 biti, deci poate "tine" 2^{32} valori distincte. 
Jumatate sunt folosite pentru reprezentarea numerelor negative si cealalta 
jumatate pentru pozitive:

  - 2^{31}, -2^{31}+1,..., -2, -1, 0, 1, 2, ..., 2^{31}-1
  
Daca lucram pe un calculator unde memorarea unui cuvant se face pe 2 octeti, 
atunci putem memora 2^{16} valori distincte.

Valoarea cea mai mare, a tipului "int" este data de constanta MAXINT. Evident 
cea mai mica valoare va fi -MAXINT-1. 

Daca se incearca, de exemplu, adunarea a doua numere (si se depaseste aceasta 
valoare), atunci se va primi un mesaj "integer overflow". 


-------------------------------------------------------------
Tipurile integrale "short", "long" si "unsigned"
-------------------------------------------------------------
De obicei, tipul "short" se memoreaza pe doi octeti si tipul "long" pe patru 
octeti. Astfel, pe masinile in care cuvintele au patru octeti, lungimea tipului
"int" este aceeasi cu lungimea tipului "long", iar pe masinile in care 
cuvintele au doi octeti, lungimea tipului "int" este egala cu lungimea tipului
"short". Constantele predefinite MAXSHORT si MAXLONG (in unele implementari 
LONG_MAX) caracterizeaza lungimea acestor tipuri. De obicei, MAXSHORT=2^{15} si
MAXLONG=2^{31}. Astfel, daca "s" este o variabila de tip "short", atunci

               - MAXSHORT <= s <= MAXSHORT-1

Daca "l" este o variabila de tip "long", atunci

               - MAXLONG <= s <= MAXLONG-1

In ceea ce priveste tipul "unsigned", acesta este memorat pe acelasi numar de 
octeti ca si tipul "int". Daca "u" este o variabila de tip "unsigned", atunci 

                0 <= u <= 2*MAXINT-1
                

---------------
Tipuri reale
---------------
ANSI C contine trei tipuri reale: "float", "double" si "long double". 
Variabilele de acest tip vor putea tine valori reale, cum ar fi:

                0.001   2.0     3.14159
                
Aceasta notatie se numeste notatie zecimala, deoarece contine punctul zecimal. Mai exista si notatia exponentiala. De exemplu,

        1.234567e5 corespunde cu 1.234567 x 10^5=123456.7
        
Pe majoritatea masinilor, tipul "float" se memoreaza pe 4 octeti, iar tipul 
"double" pe 8 octeti. Asta inseamna ca o variabila de tipul "float" poate avea
6 zecimale, iar o variabila de tipul "double" poate avea 15 zecimale. Astfel, 
o variabila de tipul "float" are forma 

                0,d_1 d_2 d_3 d_4 d_5 d_6 x 10^{n}

unde -38 <= n <= 38.

Asemanator, o variabila de tipul "double" are forma 

                0,d_1 d_2 ... d_{15} x 10^{n}

unde -308 <= n <= 308.

Astfel, instructiunea

            x = 123.45123451234512345;  /* 20 cifre semnificative */
            
va implica atribuirea lui x a valorii

            0.123451234512345 x 10^3   (15 cifre semnificative)
            
In ANSI C, pentru varibilele de tip "long double" se aloca mai multa memorie. 
Insa sunt compilatoare care trateaza acest exact tip exact ca si "double".


---------------------------
Operatorul "sizeof()"
---------------------------
C pune la dispozitie operatorul "sizeof()" pentru determinarea numarului de 
octeti necesari memorarii unui obiect. Acesta are aceeasi prioritate si 
asociativitate ca si ceilalti operatori unari. O expresie de forma

                sizeof(obiect)
                
returneaza un intreg car reprezinta numarul de octeti necesari pentru memorarea
obiectului in memorie. Un obiect poate fi un tip, cum ar fi "int" sau "float",
sau poate fi o expresie, cum ar fi a + b, sau poate fi un sir sau o structura.

-----------
Exemplu: Calculul numarului de octeti pentru cateva tipuri
-----------
#include 

main()
 {
  printf("Lungimea catorva tipuri fundamentale.\n\n");
  printf("       char:%3d octeti \n", sizeof(char));
  printf("      short:%3d octeti \n", sizeof(short));
  printf("        int:%3d octeti \n", sizeof(int));
  printf("       long:%3d octeti \n", sizeof(long));
  printf("   unsigned:%3d octeti \n", sizeof(unsigned));
  printf("      float:%3d octeti \n", sizeof(float));
  printf("     double:%3d octeti \n", sizeof(double));
  printf("long double:%3d octeti \n", sizeof(long double));  
 }
 
Din moment ce limbajul C este flexibil in ceea ce priveste necesarul de memorie
pentru tipurile fundamentale, situatiile pot sa difere de la o masina la alta.
Totusi, aceasta garanteaza ca:

           sizeof(char) = 1
           sizeof(short) <= sizeof(int) <= sizeof(long)
           sizeof(signed) <= sizeof(unsigned) <= sizeof(int)
           sizeof(float) <= sizeof(double) <= sizeof(long double)

"sizeof()" nu este o functie (chiar daca contine paranteze atunci cand ne 
referim la tipuri), ci este un operator. De exemplu:

        sizeof(a + b + 7.7) este echivalent cu sizeof a + b + 7.7
        

-------------------------
Functii matematice
-------------------------
Nu exista functii matematice implicite (in compilatorul C), ci acestea sunt 
descrise in biblioteci. De exemplu, functiile

        sqrt()  pow()  exp()  log()  sin()  cos()  tan()
        
sunt definite in biblioteca . Toate aceste functii, cu exceptia lui
"power()" au un argument de tip "double" si returneaza o valoare de tip 
"double". Functia "power()" are doua argumente de tip "double" si returneaza o
valoare de tip "double".


---------------------------------------
Conversii implicite si explicite
e---------------------------------------
O expresie aritmetica, cum ar fi "x + y", are si valoare si tip. De exemplu, 
daca "x" si "y" au tipul "int", atunci expresia "x + y" are tipul "int". Dar, 
daca "x" si "y" au ambele tipul "short", atunci "x + y" este de tip "int", si 
nu "short". Aceasta se intampla deoarece in orice expresie, "short" se 
converteste la "int".


-------------------------
Conversia la intreg
------------------------
Un "char" sau "short", ori "signed" sau "unsigned", ori un tip enumerare (vom 
reveni) poate fi folosit in orice expresie unde poate fi folosit "int" sau 
"unsigned int". Daca toate valorile tipului original pot fi reprezentate de un
"int", atunci valoarea acesteia se va converti la "int"; altfel se va converti
la  "unsigned int". Aceasta se numeste "conversie la intreg". 

-----------
Exemplu:
-----------

                char c = 'A';
                
                printf("%c\n", c);
                
Variabila "c" apare ca argument al functiei "printf()". Cu toate acestea, 
deoarece are loc conversia la intreg, tipul expresiei "c" este "int", si nu 
"char".


--------------------------------------
Conversiile aritmetice uzuale
--------------------------------------
Conversiile aritmetice pot apare cand sunt evaluati operanzii unui operator 
binar. 

------------
Exemplu:
------------
Presupunem ca "i" este "int" si "f" este un "float". In expresia "i + f", 
operandul "i" se converteste la "float" si deci expresia "i + f" va intoarce 
tipul "float".

Aceste reguli se numesc "conversii aritmetice uzuale". Iata urmatorul 
"algoritm":

daca un operand este de tip "long double" atunci
  si celalalt operand va fi convertit la tipul "long double"
altfel 
  daca un operand este de tip "double" atunci
    si celalalt operand va fi convertit la tipul "double"
  altfel 
   daca un operand este de tip "float" atunci
      si celalalt operand va fi convertit la tipul "float"
    altfel 
      /***** au loc conversiile la intreg *****/
      daca un operand este de tip "unsigned long" atunci
        si celalalt operand va fi convertit la tipul "unsigned long"
      altfel 
        daca un operand are tipul "long" si celalalt "unsigned" atunci
          - daca un "long" poate reprezenta toate valorile "unsigned" atunci
              operandul de tip "unsigned" se va converti la "long"
          - daca un "long" nu poate reprezenta toate valorile "unsigned" atunci
              ambii operanzi se vor converti la "unsigned long"
        altfel
          daca un operand are tipul "long" atunci
            celalalt operand se converteste la "long"
          altfel
            daca un operator are tipul "unsigned" atunci
              celalalt operand va fi convertit la "unsigned"
            altfel
              ambii operanzi vor avea tipul "int"

-----------
Exemplu: Presupunem ca avem declaratiile:
-----------             
                char c;
                short s;
                int i;
                unsigned u;
                unsigned long ul;
                float f;
                double d;
                long double ld;
                
Atunci avem urmatoarele valori pentru tipurile expresiilor de mai jos:

--------------------------------------------------------------------
|    Expresie     Tip       |      Expresie      Tip               |
--------------------------------------------------------------------
|   c - s / i      int            u * 7 - i     unsigned           |
|  u * 2.0 - i    double          f * 7 - i     float              |
|     c + 3        int            7 * s * ul    unsigned long      |
|     c + 5.0     double          ld + c        long double        |
|     d + s       double          u - ul        unsigned long      |
|   2 * i / l      long           u - l         dependent de sistem|
--------------------------------------------------------------------


------------------------
Conversii explicite
------------------------
Daca "i" este de tip "int", atunci 

                (double) i
                
va converti valoarea lui "i" astfel incat expresia sa aiba tipul "double". 
Variabila "i" ramane neschimbata. Conversiile se pot aplica si expresiilor.

-----------
Exemple:
-----------
                (long) ('A' + 1.0)
                x = (float) ((int) y + 1)
                (double) (x = 77)
                
Operatorul de conversie de tip (cast) este un operator unar care are aceeasi 
prioritate si asociativitate (de la dreapta la stanga) ca alti operatori unari.

------------
Exemplu:
------------
Expresia
          (float) i + 3    este echivalenta cu ((float) i) + 3
          
pentru ca operatorul "cast" are prioritate mai mare decat "+".


---------------------------------------
Erori de programare frecvente
---------------------------------------
Presupunem ca suntem pe o masina care lucreaza folosind cuvinte memorate pe doi
octeti. Consideram urmatorul exemplu:

-----------
Exemplu:
-----------
      int a = 1, b = 1776, c = 32000;
                
      printf("%d\n", a + b + c);   /* eroare: va fi afisat -31759 */
      
Un mod de a repara aceasta greseala este inlocuirea instructiunii "printf()" cu:

   printf("%d\n", (long) a + b + c);   /* va fi afisat 33777 */
   

----------------------------------------------
Exercitii propuse spre implementare
----------------------------------------------
1. Presupunem ca depunem o suma (depozit la termen) intr-o banca care ofera o 
dobanda de 38 % (de exemplu) pe an. Sa se calculeze suma finala dupa un anumit
numar de ani (se va tine cont de "dobanda la dobanda"). 

2. Scrieti o functie C utilizator care sa simuleze functia "power(m, n)" pentru
m intreg si n natural. Cate inmultiri are functia ?

3. Sa se verifice care din urmatoarele numere este mai mare: 
                pi^e    sau    e^{pi}

unde "pi"=3.14159265358979324 si "e"=2.71828182845904524.

4. Sa se scrie un program C care aproximeaza "pi" si "e" cu un anumit numar de
zecimale.
Idei: Pentru calculul lui "e", puteti folosi convergenta sirului 
        1+\sum 1/n! -> e
      Pentru calculul lui "pi", puteti folosi convergenta sirului
        \sum 1/k^2 -> pi^2/6