Tablouri bidimensionale (matrice) în C

Un tablou de tablouri: date așezate pe rânduri și coloane, accesate cu doi indici, m[rând][coloană].

Sintaxă
tip nume[randuri][coloane];
tip nume[][coloane] = {{...}, {...}};
void f(size_t r, size_t c, tip m[r][c]);   // C99

Ce face Tablouri bidimensionale (matrice)

O matrice în C este un tablou ale cărui elemente sunt tot tablouri. int m[3][4]; înseamnă 3 rânduri a câte 4 elemente. Accesezi un element cu m[rând][coloană], ambii indici pornind de la 0.

În memorie, rândurile stau unul după altul (ordinea pe rânduri). De aceea, la inițializare și la parametrii unei funcții poți omite doar prima dimensiune; numărul de coloane trebuie să fie cunoscut.

Din C99, o funcție poate primi dimensiunile ca parametri înaintea matricei: void afiseaza(size_t r, size_t c, int m[r][c]). Așa aceeași funcție merge pentru matrice de mărimi diferite.

Exemple

Exemplul 1: Notele a trei elevi la patru materii

Fiecare rând este un elev, fiecare coloană o materie. Bucla exterioară trece prin rânduri, cea interioară prin coloane.

#include <stdio.h>

int main(void) {
    const char *elevi[] = {"Ana", "Mihai", "Ioana"};
    int note[3][4] = {
        {9, 8, 10, 7},
        {6, 7, 8, 9},
        {10, 10, 9, 10},
    };
    for (int i = 0; i < 3; i++) {
        int suma = 0;
        for (int j = 0; j < 4; j++) {
            suma += note[i][j];
        }
        printf("%-6s media %.2f\n", elevi[i], suma / 4.0);
    }
    return 0;
}

Rezultat

Ana    media 8.50
Mihai  media 7.50
Ioana  media 9.75

Exemplul 2: O funcție pentru matrice de orice mărime

Dimensiunile sunt trimise înaintea matricei, așa că parametrul m[r][c] știe câte coloane are fiecare rând.

#include <stdio.h>

void afiseaza(size_t r, size_t c, int m[r][c]) {
    for (size_t i = 0; i < r; i++) {
        for (size_t j = 0; j < c; j++) {
            printf("%3d", m[i][j]);
        }
        printf("\n");
    }
}

int main(void) {
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int b[3][2] = {{7, 8}, {9, 10}, {11, 12}};
    afiseaza(2, 3, a);
    printf("--\n");
    afiseaza(3, 2, b);
    return 0;
}

Rezultat

  1  2  3
  4  5  6
--
  7  8
  9 10
 11 12

Exemplul 3: Rândurile sunt unul după altul în memorie

Dimensiunea totală este rânduri × coloane × dimensiunea unui element. Un rând întreg (m[0]) are 3 × 4 octeți.

#include <stdio.h>

int main(void) {
    int m[2][3] = {{1, 2, 3}, {4, 5, 6}};
    printf("sizeof m    = %zu\n", sizeof m);
    printf("sizeof m[0] = %zu\n", sizeof m[0]);
    int *p = &m[0][0];
    for (int k = 0; k < 6; k++) {
        printf("%d ", p[k]);
    }
    printf("\n");
    return 0;
}

Rezultat

sizeof m    = 24
sizeof m[0] = 12
1 2 3 4 5 6

Greșeli frecvente

  • Scrii m[i, j] ca în alte limbaje. În C, virgula este operatorul virgulă: expresia înseamnă m[j]. Scrie m[i][j].
  • Declari parametrul ca int m[][]. Compilatorul trebuie să știe numărul de coloane: int m[][4] sau varianta cu dimensiuni variabile int m[r][c].
  • Confunzi o matrice cu un tablou de pointeri (int **). int m[3][4] nu se poate trimite unei funcții care așteaptă int **: sunt tipuri diferite, cu așezare diferită în memorie.

Comenzi înrudite

  • Tablouri (array): Un șir de elemente de același tip, așezate unul după altul în memorie și accesate prin index, de la 0.
  • Pointer la pointer (**): Un pointer care păstrează adresa altui pointer: util când o funcție trebuie să schimbe un pointer sau pentru tablouri de șiruri.
  • for: Bucla cu contor: inițializare, condiție verificată înainte de fiecare pas și actualizare, toate pe un singur rând.
  • malloc: Cere sistemului un bloc de memorie de mărimea dată, în timpul rulării, și returnează adresa lui sau NULL dacă nu reușește.