6.2. Initializarea tablourilor

2019/02/28 in Programare in C

Tablourile, ca si variabilele simple, pot fi initializate. Tablourile globale se initializeaza prin definitiile lor. Tablourile statice si automatice se initializeaza prin declaratiile lor.

In limbajul C, in toate cazurile, initializarile se fac prin expresii constante.

Tablourile globale si statice se initializeaza la compilare. De aceea, la lansarea programului, elementele au ca valori, valorile expresiilor cu care au fost initializate.

Tablourile automatice se initializeaza la executie, de fiecare data cand se apeleaza functia in care sunt declarate. In unele versiuni ale limbajului C nu se pot initializa tablourile automatice.

Initializarea unui tablou unidimensional se realizeaza printr-o constructie de forma:

tip nume[ec] = {ec0, ec1, ...,eci};

sau

static tip nume[ec] = {ec0, ec1, ...,eci};

daca tabloul este static.

Prin ec0, ec1, ...,eci am notat expresii constante.

Prin aceste constructii, elementul nume[k] se initializeaza cu valoarea expresiei constante eck, pentru k = 0,1,2, ...,i.

In general, i <= ec-1. Daca i >= ec, atunci constructia este eronata.

Daca i < ec-1, atunci elementele:

nume[i+1], nume [i+2], ..., nume[ec-1]

raman neinitializate.

Elementele neinitializate ale unui tablou global sau static au in mod implicit valoarea initiala egala cu zero.

Elementele neinitializate ale unui tablou automatic au valoari initiale nedefinite.

In cazul in care se initializeaza toate elementele unui tablou unidimensional, se poate omite expresia din parantezele patrate care defineste numarul elementelor tabloului. Deci constructiile:

tip nume[] = {ec0, ec1, ...,ecn};

sau

static tip nume[] = {ec0, ec1, ...,ecn};

sunt corecte si in ambele cazuri tablourile au n+1 elemente, iar elementul nume[i] este initializat cu valoarea expresiei eci.

Trebuie mentionat ca, la fel ca si in cazul variabilelor simple, daca expresia de initializare are un tip diferit de cel al tabloului, atunci valoarea ei se converteste spre tipul tabloului inainte de a fi atribuita elementului pe care il initializeaza.

Observatie:

Intr-o declaratie sau definitie de tablou unidimensional se poate omite expresia care defineste numarul elementelor tabloului in urmatoarele cazuri:

  1. Definitia sau declaratia de tablou contine elemente constante pentru initializarea fiecarui element al tabloului;
  2. Declaratia se refera la un tablou unidimensional care este parametru formal.
  3. Declaratia de tablou extern unidimensional: extern int tab[];

Exemple:

  1. int tab[10] = {0,1,2,3,4,5,6,7,8,9};
    Prin aceasta definitie / declaratie, lui tab[i] i se atribuie valoarea i.
  2. int tab[] = {0,1,2,3,4,5,6,7,8,9};
    Ca efect, ceasta constructie este identica cu cea precedenta.
  3. double d[20] = {0,1,-1,3,-2};
    Primele 5 elemente ale tabloului d se initializeaza astfel:
    d[0] = 0;
    d[0] = 1;
    d[0] = -1;
    d[0] = 3;
    d[0] = -2;
    Celelalte elemente ale tabloului d[5], d[6], ..., d[19] au valoarea initiala zero daca d este tablou global si o valoare initiala imprevizibila daca tabloul este automatic.
  4. #define V ('A'-10)
    static int chexa[] = {'A'-V, 'B'-V, 'C'-V, 'D'-V, 'E'-V, 'F'-V};
    Elementele tabloului chexa se initializeaza astfel:
    chexa[0] = 'A' - ('A'-10) = 10;
    chexa[1] = = 'B' - ('A'-10) = 11;
    chexa[2] = 12;
    chexa[3] = 13;
    chexa[4] = 14;
    chexa[5] = 15;
  5. char er[] = {'e', 'r', 'o', 'a', 'r', 'e', '\0'};
    Tabloul are 7 elemente care se initializeaza astfel:
    er[0] = 'e';
    er[1] = 'r';
    er[2] = 'o';
    er[3] = 'a';
    er[4] = 'r';
    er[5] = 'e';
    er[6] = '\0';
    Se observa ca tabloul er pastreaza sirul de caractere "eroare", inclusiv caracterul NUL care termina orice sir de caractere.

Avand in vedere ca initializarea tablourilor de tip char se utilizeaza frecvent, autorii limbajului C au introdus o simplificare pentru initializarea lor, astfel se pot utiliza una din constructiile:

char nume[ec] = sir;

sau

static char nume[ec] = sir;

unde prin sir se intelege o succesiune de caractere delimitata prin ghilimele. Prin aceste constructii, elementele tabloului nume se initializeaza cu codurile ASCII ale caracterelor din compunerea sirului de caractere sir, iar dupa ultimul caracter al sirului se memoreaza caracterul NUL. Expresia constanta ec, daca este prezenta, atunci ea este cel putin egala cu numarul caracterelor proprii ale sirului marit cu unu.

Din cele de mai sus rezulta ca initializarea tabloului er de la exemplul 5 se poate realiza folosind constructia:

char er[] = "eroare";

Tablourile multidimensionale pot fi si ele initializate prin constructii analoge. Asa de exemplu, un tablou bidimensional se poate initializa printr-o constructie de forma:

tip nume[n][m] = {
{ec11, ec12, ..., ec1m},
{ec21, ec22, ..., ec2m},
...
{ecn1, ecn2, ..., ecnm}
};

unde:

n, m, ecij pentru i=1,2,...,n si j=1,2,...,m sunt expresii constante.

Numarul expresiilor constante poate fi mai mic decat m in oricare din acoladele corespunzatoare celor n linii ale tabloului bidimensional.

In cazul tablourilor statice, declaratia este precedata de cuvantul cheie static.

Intr-o declaratie sau definitie de tablou bidimensional se poate omite numai expresia constanta din prima paranteza patrata, adica se poate omite numai n din constructia de mai sus.

In general, intr-o declaratie sau definitie de tablou multidimensional se poate omite numai expresia constanta din prima paranteza patrata, adica limita superioara pentru primul indice. Ea poate fi omisa in una din cele trei cazuri amintite la tablourile unidimensionale:

Exemple:

  1. int tab[3][4] = {
    {-1,0,1,-1},
    {-1,0,1,2},
    {10,20,30,40}
    };
    Prin aceasta constructie, elementele tabloului tab se initializeaza cu valorile:
    tab[0][0] = -1, tab[0][1] = 0, tab[0][2] = 1, tab[0][3] = -1;
    tab[1][0] = -1, tab[1][1] = 0, tab[1][2] = 1, tab[1][3] = 2;
    tab[2][0] = 10, tab[2][1] = 20, tab[2][2] = 30, tab[2][3] = 40;
    
  2. int tab[][4] = {
    {-1,0,1,-1},
    {-1,0,1,2},
    {10,20,30,40}
    };
    Ca efect, aceasta constructie este identica cu cea precedenta.
  3. double t[][3] = {
    {0,1},
    {-1},
    {1,2,3}
    };
    t reprezinta o matrice de ordinul 3*3 ale carei elemente se initializeaza astfel:
    t[0][0] = 0, t[0][1] = 1;
    t[1][0] = -1;
    t[2][0] = 1, t[2][1] = 2, t[2][2] = 3.
    Elementele t[0][2], t[1][1] si t[1][2] nu sunt initializate. Ele au valoarea initiala zero daca tabloul este global, sau valori nedefinite daca tabloul este automatic.

Constructiile utilizate pentru tablourile bidimensionale se extind imediat pentru tablouri cu un numar mai mare de dimensiuni.

Exemplu:

double t[][3][2] = {
{{100,200}, {-1,1}, {0,1}},
{{123,456}, {789,10}, {11,12}}
};

Exercitii:

6.2. Sa se scrie un program care care citeste un numar pozitiv ce reprezinta o suma exprimata in lei. Se cere sa se afiseze numarul minim de bancnote si monede de 500 lei, 100 lei, 50 lei etc. necesare pentru a exprima suma respectiva.

Procesul de calcul a fost descris la exercitiul 4.21. (Programul 075). In cazul de fata se va face initializarea tabloului ban prin declaratia sa.

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

main()
{
    int ban[] = {1,5,10,50,100,200,500};
    int i = sizeof ban / sizeof(int) - 1;
    long s, q;
    char t[255];

    for( ; ; ){
        printf("Suma = ");
        if(gets(t) == NULL) {
            printf("S-a tastat EOF\n");
            exit(1);
        }
        if (sscanf(t, "%ld", &s) == 1 && s > 0)
                break;
        printf("Nu s-a tastat un intreg pozitiv\n");
    }
	do {
        q = s / ban[i];
        if (q)
            if (q == 1)
                printf("o bancnota de %d lei\n", ban[i]);
            else
                printf("%ld bancnote de %d lei\n", q, ban[i]);
	} while (s %= ban[i--]);
}

Observatie: La declararea lui i s-a facut initializare cu ajutorul expresiei:

i = sizeof ban / sizeof(int) - 1

Aceasta expresie are ca valoarea numarului elementelor tabloului ban micsorat cu 1, adica indicele ultimului element al tabloului. Intr-adevar sizeof ban are ca valoare dimensiunea in octeti a zonei de memorie alocata tabloului ban, iar sizeof(int) are ca valoare numarul octetilor necesari pentru a reprezenta o data de tip int. Raportul lor ne da chiar numarul elementelor tabloului ban.

6.3. Sa se scrie o functie care afiseaza elementele unui tablou de tip char si citeste un intreg de tip int.

Functia are parametrul text care este un tablou unidimensional de tip char. Ea afiseaza sirul de caractere pastrat in tabloul text inainte de a citi intregul. Intregul citit se atribuie variabilei globale v_int. Functia returneaza valoarea zero la intalnirea sfarsitului de fisier si 1 in caz contrar. La eroare se reia citirea intregului dupa retastarea acestuia de catre operator.

int cit_int(char text[])
{
  char t[255];
  char texter[] = "nu s-a tastat un intreg\n";
  extern int v_int;

  for( ; ; ) {
    printf(text);
    if(gets(t) == NULL)
      return 0;
    if(sscant(t, "%d", &v_int) == 1)
      return 1;
    printf(texter);
  }
}

6.4. Sa se scrie o functie care citeste un intreg de tip int care apartine unui interval dat.

Functia are parametrii:

text tablou unidimensional de tip char care are aceeasi semnificatie ca in exercitiul precedent;
inf intreg de tip int care reprezinta limita inferioara a intervalului caruia trebuie sa-i apartina numarul citit;
sup intreg de tip int care reprezinta limita superioara a intervalului caruia trebuie sa-i apartina numarul citit.

Functia atribuie intregul citit variabilei globale v_int. Ea returneaza valoarea 0 la intalnirea EOF si 1 in caz contrar.

int cit_int(char []); /*prototip*/
int cit_int_lim(char text[], int inf, int sup)
{
  extern int v_int;

  for( ; ; ) {
    if(cit_int(text) == 0)
      return 0;
    if(v_int >= inf && v_int <= sup)
      return 1;
    printf("intregul tastat nu apartine intervalului:");
    printf("[%d, %d]\n", inf, sup);
    printf("se reia citirea\n");
  }
}

6.5. Sa se scrie o functie care valideaza o data calendaristica.

Functia are 3 parametri de tip int:

zi numarul zilei din cadrul lunii;
luna numarul lunii;
an anul calendaristic

Functia returneaza valoarea 1 daca data calendaristica este valida si 0 in caz contrar.

In exercitiile prezentate pe link.ro se presupune ca anul calendaristic apartine intervalului [1600, 4900]. In acest caz, anul este bisect daca expresia:

an%4 == 0 && an%100 != 0 || an%400 == 0

are valoarea adevarat (vezi exercitiul 3.12. - Programul 037).

Functia utilizeaza un tablou global de tip int ale carui elemente au ca valori numarul zilelor din lunile calendaristice. Numele acestui tablou este nrzile.

nrzile[i] are ca valoare numarul zilelor din luna a i-a (i=1,2,3,...,12).

int v_calend (int zi, int luna, int an)
{
  extern int nrzile[];

  if(an < 1600 || an > 4900) {
    printf("anul nu este in intervalul [1600, 4900]");
    return 0;
  }
  if(luna < 1 || luna > 12) {
    printf("luna = %d eronata\n", luna);
    return 0;
  }
  if(zi < 1 || zi > nrzile[luna] + (luna == 2 && (an%4 == 0 && an%100 || an%400 == 0))) {
    printf("ziua = %d eronata\n", zi);
    return 0;
  }
  return 1;
}

Observatie: Elementul nrzile[2] are ca valoare numarul zilelor din luna februarie pentru un an nebisect, deci 28.

Daca anul este bisect, atunci expresia: (1) an%4 == 0 && an%100 || an%400 == 0 are valoarea 1. Aceasta nu este valabil si pentru celelalte luni. Deci nrzile[luna] se mareste cu 1 cand expresia (1) are valoarea 1 si cand expresia (2) luna ==2 este adevarata. Cu alte cuvinte, numarul zilelor din februarie se mareste cu 1, cand atat expresie (1) cat si expresie (2) sunt adevarate si numai atunci. Aceasta inseamna ca expresia (1)&&(2) are valoarea 1 atunci si numai atunci cand anul este bisect si luna este februarie.

6.6. Sa se scrie o functie care determina dintr-o data calendaristica de forma zi, luna, an numarul zilei din an pentru data respectiva.

Functia returneaza numarul determinat din parametrii zi, luna si an.

Daca, de exemplu, data calendaristica este 1 martie 1994, atunci ea reprezinta ziua 60 din anul respectiv. Intr-adevar, pana la 1 martie 1994 au trecut lunile ianuarie si februarie, deci impreuna cu 1 martie sunt:

31 + 28 + 1 = 60 zile

Intr-un an bisect, data de 1 martie este ziua 61 din anul respectiv.

Procesul de calcul consta din insumarea zilelor din lunile precedente lunii calendaristice definita de luna, la ziua curenta definita de zi.

Functia de fata presupune ca data calendaristica este valida.

int zi_din_an (int zi, int luna, int an)
{
  extern int nrzile[];
  int bisect = an%4 == 0 && an%100 || an%400 == 0;
  int i;

  for(i=1; i<luna; i++)
    zi += nrzile[i] + (i == 2 && bisect);
  return zi;
}

6.7. Sa se scrie o functie care dintr-o data calendaristica definita prin numarul zilei din an si respectiv, determina luna si ziua din luna respectiva.

Aceasta functie este inversa functiei zi_din_an definita in exercitiul precedent. Ea defineste doua valori: numarul lunii si numarul zilei din luna respectiva.

Functia poate fi realizata in una din variantele:

Functia realizata mai jos utilizeaza parametrul tzzll.

void luna_si_ziua (int zz, int an, int tzzll[])
{
  int bisect = an%4 == 0 && an%100 || an%400 == 0;
  int i;
  extern int nrzile[];

  for(i=1; zz>nrzile[i] + (i == 2 && bisect); i++)
    zz -= (nrzile[i] + (i == 2 && bisect));
  tzzll[0] = zz;
  tzzll[1] = i;
}

6.8. Sa se scrie o functie care citeste o data calendaristica compusa din zi, luna si an.

Functia valideaza data calendaristica respectiva. Ea returneaza valoarea:

0 la intalnirea EOF;
1 altfel (data calendaristica corecta).

Are un parametru tzzllaa, care este un tablou de tip int. La revenire, elementele acestui tablou au valorile:

tzzllaa[0] ziua citita;
tzzllaa[1] luna citita;
tzzllaa[2] anul citit.

int cit_data_calend (int tzzllaa[])
{
  extern int v_int;
  static char ziua[] = "ziua: ";
  static char luna[] = "luna: ";
  static char anul[] = "anul: ";
  static char eroare[] = "s-a tastat EOF";

  for( ; ; ) {
  
    if(cit_int_lim(ziua, 1, 31) == 0){
      printf("%s\n", eroare);
      return 0;
    }
    tzzllaa[0] = v_int;
	
    if(cit_int_lim(luna, 1, 12) == 0){
      printf("%s\n", eroare);
      return 0;
    }
    tzzllaa[1] = v_int;
	
    if(cit_int_lim(anul, 1600, 4900) == 0){
      printf("%s\n", eroare);
      return 0;
    }
    tzzllaa[2] = v_int;
	
    if(v_calend(tzzllaa[0], tzzllaa[1], tzzllaa[2]))
      return 1;
    printf("data calendaristica este eronata\n");
    printf("se reia citirea datei calendaristice\n");
  }
}

6.9. Sa se scrie un program care care citeste o data calendaristica si afiseaza data calendaristica pentru ziua urmatoare.

Programul se realizeaza conform urmatorilor pasi:

  1. Se citeste si se valideaza data calendaristica prin apelul functiei v_calend;
  2. Daca data citita este 31 decembrie dintr-un anumit an, atunci ziua urmatoareeste 1 ianuarie din anul urmator, apoi se trece la pasul 6.
    Altfel se trece la pasul 3;
  3. Se determina ziua din an (functia zi_din_an);
  4. Se incrementeaza ziua din an;
  5. Se determina luna si ziua din luna (functia luna_si_ziua);
  6. Se afiseaza data calendaristica a zilei urmatoare.
#include <stdio.h>
#include <stdlib.h>
#include "FUNCTIA092A.C" /*functia cit_int */
#include "FUNCTIA092B.C" /*functia cit_int_lim */
#include "FUNCTIA092C.C" /*functia v_calend */
#include "FUNCTIA092D.C" /*functia zi_din_an */
#include "FUNCTIA092E.C" /*functia luna_si_ziua */
#include "FUNCTIA092F.C" /*functia cit_data_calend */

/* variabile globale */
int v_int;
int nrzile[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

main()
{
  int data_calend[3];
  int zl[2];

  /* citeste si valideaza data calendaristica */
  if(cit_data_calend(data_calend) == 0)
    exit(1);
  if(data_calend[0] == 31 && data_calend[1] == 12) {
  /* 31 decembrie, ziua urmatoare este 1 ianuarie din anul urmator */
    zl[0] = 1; /* 1 */
    zl[1] = 1; /* ianuarie */
    data_calend[2]++; /* anul urmator */
  }
  else /* se determina ziua urmatoare */
    luna_si_ziua(zi_din_an(data_calend[0],data_calend[1],data_calend[2])+1,data_calend[2],zl);
  /* afiseaza data calendaristica a zilei urmatoare */
  printf("ziua: %d\tluna: %d\tanul: %d\n", zl[0], zl[1], data_calend[2]);
}

7. Programare modulara