C Arrays Multidimensionais

Na aula de Arrays, a Ana guardou uma nota por aluno numa fileira. Mas no fim do bimestre cada aluno tem quatro provas. O que ela tem agora é uma tabela, como uma planilha: uma linha para cada aluno, uma coluna para cada prova. O C guarda tabelas assim com um array de duas dimensões (um array bidimensional, ou matriz, como na matemática): int notas[3][4], 3 linhas e 4 colunas. Tabuleiro de jogo, mapa de assentos do cinema, vendas por semana: tudo isso cabe em linhas e colunas.

Você vai aprender
  • Como criar uma tabela (int notas[3][4]) e acessar uma célula com dois índices: notas[linha][coluna].
  • Como percorrer a tabela com dois for, um dentro do outro.
  • Como somar uma linha (a média de um aluno) e uma coluna (a média de uma prova).
  • Como a tabela fica guardada na memória: linha depois de linha.
  • As formas de dar valores iniciais e qual tamanho pode ficar vazio.
  • Tabelas de char (jogo da velha, lista de palavras), três dimensões e como passar uma tabela para uma função.
  • Os erros clássicos: linha e coluna trocadas e o notas[1, 2].

Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. Esta aula usa muito o que você viu em Arrays e em Laços Aninhados.


A ideia: o prédio com andares e apartamentos

Num prédio, para achar um apartamento você precisa de dois números: o andar e a porta. "Andar 2, porta 3." Todos os andares têm o mesmo número de portas. Um array de duas dimensões é esse prédio:

  • o primeiro índice escolhe o andar, a linha da tabela;
  • o segundo índice escolhe a porta dentro do andar, a coluna;
  • todas as linhas têm o mesmo número de colunas, e todas as células guardam o mesmo tipo.

E, como num array comum, os dois índices começam em 0: o térreo é a linha 0 e a primeira porta é a coluna 0.


1. A primeira tabela

As notas de 3 alunos em 4 provas:

Coluna 0 (prova 1)Coluna 1 (prova 2)Coluna 2 (prova 3)Coluna 3 (prova 4)
Linha 0 (aluno 1)7869
Linha 1 (aluno 2)5678
Linha 2 (aluno 3)109910
#include <stdio.h>

int main(void) {
    int notas[3][4] = {
        {7, 8, 6, 9},
        {5, 6, 7, 8},
        {10, 9, 9, 10}
    };
    printf("Aluno 2, prova 3: %d\n", notas[1][2]);
    printf("Aluno 3, prova 1: %d\n", notas[2][0]);
    notas[1][0] = 7;
    printf("Depois da revisão, aluno 2, prova 1: %d\n", notas[1][0]);
    return 0;
}
int notas[3][4]
Uma tabela de int com 3 linhas e 4 colunas: 3 × 4 = 12 células. O primeiro número é sempre o de linhas.
{ {7, 8, 6, 9}, … }
Chaves dentro de chaves: cada par de chaves de dentro é uma linha. Escrever uma linha por linha do código deixa o programa parecido com a tabela.
notas[1][2]
Linha 1 (o segundo aluno), coluna 2 (a terceira prova): vale 7, a célula em negrito na tabela acima.
notas[2][0]
Terceiro aluno, primeira prova: 10.
notas[1][0] = 7;
Cada célula funciona como uma variável comum: recebe valor com =. A primeira prova do aluno 2 foi revisada de 5 para 7.
$ gcc -std=c23 -Wall -Wextra planilha.c -o planilha
$ ./planilha
Aluno 2, prova 3: 7
Aluno 3, prova 1: 10
Depois da revisão, aluno 2, prova 1: 7
Linha primeiro, coluna depois

Leia notas[1][2] como "na linha 1, a coluna 2". A ordem é a mesma da criação: [3][4] são 3 linhas de 4 colunas, então o primeiro índice vai de 0 a 2 e o segundo, de 0 a 3. Trocar a ordem é o erro mais comum desta aula (veja a seção 7).


2. Percorrer a tabela com dois for

Para passar por todas as células, use dois laços, um dentro do outro (aula Laços Aninhados): o de fora escolhe a linha e o de dentro anda pelas colunas dessa linha.

#include <stdio.h>

int main(void) {
    int notas[3][4] = {
        {7, 8, 6, 9},
        {5, 6, 7, 8},
        {10, 9, 9, 10}
    };
    for (int linha = 0; linha < 3; linha++) {
        printf("Aluno %d:", linha + 1);
        for (int coluna = 0; coluna < 4; coluna++) {
            printf(" %3d", notas[linha][coluna]);
        }
        printf("\n");
    }
    return 0;
}
for (int linha = 0; linha < 3; linha++)
O laço de fora: uma volta por aluno. O limite é o número de linhas, 3.
printf("Aluno %d:", linha + 1);
O começo da linha na tela, uma vez por aluno.
for (int coluna = 0; coluna < 4; coluna++)
O laço de dentro: roda inteiro (4 voltas) a cada volta do de fora. O limite é o número de colunas, 4.
%3d
Cada número ocupa 3 casas, alinhado à direita, para as colunas ficarem retas (aula Formatos do printf).
printf("\n");
Depois do laço de dentro: terminou a linha da tabela, pula a linha na tela.
$ gcc -std=c23 -Wall -Wextra tabela.c -o tabela
$ ./tabela
Aluno 1:   7   8   6   9
Aluno 2:   5   6   7   8
Aluno 3:  10   9   9  10

Nomes como linha e coluna (ou l e c) ajudam muito. Com i e j, que você vai ver em muito código, é fácil trocar um pelo outro sem perceber.


3. Somar uma linha e somar uma coluna

A Ana quer duas coisas: a média de cada aluno (somar uma linha) e a média de cada prova (somar uma coluna). A diferença é só qual laço fica por fora.

#include <stdio.h>

#define ALUNOS 3
#define PROVAS 4

int main(void) {
    int notas[ALUNOS][PROVAS] = {
        {7, 8, 6, 9},
        {5, 6, 7, 8},
        {10, 9, 9, 10}
    };

    printf("Média de cada aluno:\n");
    for (int a = 0; a < ALUNOS; a++) {
        int soma = 0;
        for (int p = 0; p < PROVAS; p++) {
            soma += notas[a][p];
        }
        printf("  aluno %d: %.2f\n", a + 1, (double) soma / PROVAS);
    }

    printf("Média de cada prova:\n");
    for (int p = 0; p < PROVAS; p++) {
        int soma = 0;
        for (int a = 0; a < ALUNOS; a++) {
            soma += notas[a][p];
        }
        printf("  prova %d: %.2f\n", p + 1, (double) soma / ALUNOS);
    }
    return 0;
}
#define ALUNOS 3 / #define PROVAS 4
Os dois tamanhos com nome (aula Constantes). Com tabelas, isso evita a confusão "o 3 era de linhas ou de colunas?".
int soma = 0; (dentro do laço de fora)
Cada aluno começa uma soma nova. Se a soma fosse criada antes dos laços, o aluno 2 começaria com a soma do aluno 1.
notas[a][p] no primeiro bloco
O de fora é o aluno (linha) e o de dentro, a prova: soma uma linha.
notas[a][p] no segundo bloco
Os laços trocaram de lugar (a prova por fora), mas o acesso continua notas[a][p], linha e depois coluna: soma uma coluna.
$ gcc -std=c23 -Wall -Wextra medias.c -o medias
$ ./medias
Média de cada aluno:
  aluno 1: 7.50
  aluno 2: 6.50
  aluno 3: 9.50
Média de cada prova:
  prova 1: 7.33
  prova 2: 7.67
  prova 3: 7.33
  prova 4: 9.00

Repare na regra de ouro: trocar a ordem dos laços não troca a ordem dos índices. O primeiro índice é sempre a linha, não importa qual laço está por fora.

Experimente: entrou um aluno novo na turma. Mude ALUNOS para 4 e acrescente uma quarta linha na tabela, por exemplo {6, 7, 8, 9}. Os laços se ajustam sozinhos. O que acontece com a média da prova 1? E se você mudar ALUNOS para 4 sem acrescentar a linha? (Dica: o que falta vira zero, como em Arrays.)

#include <stdio.h>

#define ALUNOS 3
#define PROVAS 4

int main(void) {
    int notas[ALUNOS][PROVAS] = {
        {7, 8, 6, 9},
        {5, 6, 7, 8},
        {10, 9, 9, 10}
    };
    for (int p = 0; p < PROVAS; p++) {
        int soma = 0;
        for (int a = 0; a < ALUNOS; a++) {
            soma += notas[a][p];
        }
        printf("prova %d: %.2f\n", p + 1, (double) soma / ALUNOS);
    }
    return 0;
}

4. Como a tabela fica na memória

A memória do computador não tem linhas e colunas: é uma fileira só de bytes. O C guarda a tabela linha depois de linha: primeiro as 4 células da linha 0, logo depois as 4 da linha 1, e assim por diante. Em inglês isso se chama row-major order, "ordem por linhas".

Posição na memória01234567891011
Célula[0][0][0][1][0][2][0][3][1][0][1][1][1][2][1][3][2][0][2][1][2][2][2][3]
Valor78695678109910

Para achar notas[l][c], o computador calcula a posição l × 4 + c: pula l linhas inteiras de 4 e anda c células. notas[1][2] é a posição 1 × 4 + 2 = 6, que guarda 7. É por isso que o compilador precisa saber quantas colunas a tabela tem: sem esse 4, ele não consegue fazer a conta.

Na verdade, para o C, int notas[3][4] é um array de 3 elementos, em que cada elemento é um array de 4 int. O sizeof mostra isso:

#include <stdio.h>

int main(void) {
    int notas[3][4] = {
        {7, 8, 6, 9},
        {5, 6, 7, 8},
        {10, 9, 9, 10}
    };
    printf("sizeof notas       = %zu bytes\n", sizeof notas);
    printf("sizeof notas[0]    = %zu bytes (uma linha)\n", sizeof notas[0]);
    printf("sizeof notas[0][0] = %zu bytes (um elemento)\n", sizeof notas[0][0]);
    printf("linhas  = %zu\n", sizeof notas / sizeof notas[0]);
    printf("colunas = %zu\n", sizeof notas[0] / sizeof notas[0][0]);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra memoria.c -o memoria
$ ./memoria
sizeof notas       = 48 bytes
sizeof notas[0]    = 16 bytes (uma linha)
sizeof notas[0][0] = 4 bytes (um elemento)
linhas  = 3
colunas = 4
sizeof notas
A tabela inteira: 12 células × 4 bytes = 48.
sizeof notas[0]
notas[0], com um índice só, é a linha 0 inteira: um array de 4 int, 16 bytes.
sizeof notas / sizeof notas[0]
48 ÷ 16 = 3 linhas. É a mesma conta de Tamanho dos Tipos, um nível acima.
sizeof notas[0] / sizeof notas[0][0]
16 ÷ 4 = 4 colunas.

Uma prova de que as linhas ficam coladas: num int m[2][3] = {{1, 2, 3}, {4, 5, 6}}, pedir m[0][3] (a "quarta coluna" de uma linha que só tem 3) mostrou m[0][3] = 4 no gcc, o primeiro valor da linha 1. Não faça isso em programa de verdade: a coluna 3 não existe e o acesso é comportamento indefinido (aula Comportamento Indefinido). O Clang do editor avisa: warning: array index 3 is past the end of the array (that has type 'int[3]'); o gcc não disse nada, nem com -O2.


5. As formas de dar valores iniciais

#include <stdio.h>

int main(void) {
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int b[2][3] = {1, 2, 3, 4, 5, 6};
    int c[2][3] = {{1}, {4, 5}};
    int d[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int e[2][3] = {};

    printf("a: %d %d %d / %d %d %d\n", a[0][0], a[0][1], a[0][2], a[1][0], a[1][1], a[1][2]);
    printf("b: %d %d %d / %d %d %d\n", b[0][0], b[0][1], b[0][2], b[1][0], b[1][1], b[1][2]);
    printf("c: %d %d %d / %d %d %d\n", c[0][0], c[0][1], c[0][2], c[1][0], c[1][1], c[1][2]);
    printf("d tem %zu linhas\n", sizeof d / sizeof d[0]);
    printf("e: %d %d %d / %d %d %d\n", e[0][0], e[0][1], e[0][2], e[1][0], e[1][1], e[1][2]);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra formas.c -o formas
formas.c: In function ‘main’:
formas.c:5:19: warning: missing braces around initializer [-Wmissing-braces]
    5 |     int b[2][3] = {1, 2, 3, 4, 5, 6};
      |                   ^
      |                    {      } {      }
$ ./formas
a: 1 2 3 / 4 5 6
b: 1 2 3 / 4 5 6
c: 1 0 0 / 4 5 0
d tem 3 linhas
e: 0 0 0 / 0 0 0
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
A forma completa: uma chave por linha. É a recomendada.
int b[2][3] = {1, 2, 3, 4, 5, 6};
Sem as chaves de dentro, os valores enchem a memória na ordem (linha 0, depois linha 1). Funciona, mas o gcc avisa missing braces around initializer, "faltam chaves no inicializador", e até desenha onde elas iriam: { } { }.
int c[2][3] = {{1}, {4, 5}};
Cada linha pode ter menos valores: o que falta naquela linha vira zero. Aqui as chaves fazem diferença: sem elas, {1, 4, 5} encheria a linha 0 inteira.
int d[][3] = {…};
O primeiro tamanho pode ficar vazio: o compilador conta as linhas (3). O das colunas, não (seção 7).
int e[2][3] = {};
Tudo zero C23. Antes do C23, = {0}.

6. Outras tabelas

Tabela de char: o jogo da velha

Qualquer tipo serve. Um tabuleiro de jogo da velha é uma tabela 3 × 3 de char, com 'X', 'O' ou espaço em cada casa (aula Caracteres):

#include <stdio.h>

int main(void) {
    char tabuleiro[3][3] = {
        {'X', 'O', ' '},
        {' ', 'X', ' '},
        {'O', ' ', ' '}
    };
    tabuleiro[2][2] = 'X';
    for (int l = 0; l < 3; l++) {
        printf(" %c | %c | %c\n", tabuleiro[l][0], tabuleiro[l][1], tabuleiro[l][2]);
        if (l < 2) {
            printf("---+---+---\n");
        }
    }
    if (tabuleiro[0][0] == 'X' && tabuleiro[1][1] == 'X' && tabuleiro[2][2] == 'X') {
        printf("X venceu na diagonal!\n");
    }
    return 0;
}
tabuleiro[2][2] = 'X';
A jogada: linha 2, coluna 2, o canto de baixo à direita.
if (l < 2)
Desenha a linha separadora entre as linhas do tabuleiro, mas não depois da última.
[0][0], [1][1], [2][2]
A diagonal principal: as casas em que linha e coluna são iguais.
$ gcc -std=c23 -Wall -Wextra velha.c -o velha
$ ./velha
 X | O |
---+---+---
   | X |
---+---+---
 O |   | X
X venceu na diagonal!

Missão: é a vez do X. Faça a jogada que fecha a linha do meio (a segunda linha) e rode. O programa tem de mostrar X venceu na linha 2!. Qual é a linha e qual é a coluna da casa vazia do meio?

#include <stdio.h>

int main(void) {
    char tabuleiro[3][3] = {
        {'O', ' ', 'O'},
        {'X', ' ', 'X'},
        {' ', ' ', ' '}
    };
    // sua jogada aqui

    for (int l = 0; l < 3; l++) {
        printf(" %c | %c | %c\n", tabuleiro[l][0], tabuleiro[l][1], tabuleiro[l][2]);
    }
    for (int l = 0; l < 3; l++) {
        if (tabuleiro[l][0] == 'X' && tabuleiro[l][1] == 'X' && tabuleiro[l][2] == 'X') {
            printf("X venceu na linha %d!\n", l + 1);
        }
    }
    return 0;
}
#include <stdio.h>

int main(void) {
    char tabuleiro[3][3] = {
        {'O', ' ', 'O'},
        {'X', ' ', 'X'},
        {' ', ' ', ' '}
    };
    tabuleiro[1][1] = 'X';

    for (int l = 0; l < 3; l++) {
        printf(" %c | %c | %c\n", tabuleiro[l][0], tabuleiro[l][1], tabuleiro[l][2]);
    }
    for (int l = 0; l < 3; l++) {
        if (tabuleiro[l][0] == 'X' && tabuleiro[l][1] == 'X' && tabuleiro[l][2] == 'X') {
            printf("X venceu na linha %d!\n", l + 1);
        }
    }
    return 0;
}

Uma lista de palavras

Uma palavra em C é um array de char com um '\0' no fim, a string, assunto da próxima aula (Strings). Uma lista de palavras, então, é uma tabela de char: cada linha é uma palavra.

#include <stdio.h>

int main(void) {
    char dias[7][4] = {"dom", "seg", "ter", "qua", "qui", "sex", "sab"};
    for (int i = 0; i < 7; i++) {
        printf("%d: %s\n", i, dias[i]);
    }
    printf("A segunda letra de \"%s\" é '%c'\n", dias[1], dias[1][1]);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra dias.c -o dias
$ ./dias
0: dom
1: seg
2: ter
3: qua
4: qui
5: sex
6: sab
A segunda letra de "seg" é 'e'
char dias[7][4]
7 linhas (os dias) de 4 char: 3 letras + o '\0' que marca o fim de cada palavra.
dias[i] com %s
Com um índice, dias[i] é a linha inteira, a palavra, e o %s a mostra.
dias[1][1]
Com dois índices, uma letra: a da coluna 1 da linha 1, o 'e' de "seg".

Todas as linhas têm o mesmo tamanho, então a coluna precisa caber a maior palavra. Por isso escrevemos "sab" sem acento: o á ocupa 2 bytes e "sáb" não caberia em 4 (veja Acentos e UTF-8). Para listas de palavras de tamanhos muito diferentes, existe outro jeito, um array de ponteiros, em Ponteiros e Strings.

Três dimensões (ou mais)

O C aceita quantos colchetes você quiser. A lojinha da Ana tem 2 filiais e anota as vendas de cada semana (4) de cada mês (3): vendas[loja][mes][semana].

#include <stdio.h>

int main(void) {
    // 2 lojas, 3 meses, 4 semanas
    int vendas[2][3][4] = {
        {{5, 3, 4, 6}, {2, 2, 3, 1}, {7, 8, 6, 9}},
        {{1, 0, 2, 3}, {4, 4, 5, 2}, {3, 3, 3, 3}}
    };
    int total = 0;
    for (int loja = 0; loja < 2; loja++) {
        for (int mes = 0; mes < 3; mes++) {
            for (int semana = 0; semana < 4; semana++) {
                total += vendas[loja][mes][semana];
            }
        }
    }
    printf("Loja 2, mês 1, semana 4: %d\n", vendas[1][0][3]);
    printf("Total vendido: %d\n", total);
    printf("O array tem %zu bytes\n", sizeof vendas);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra tres.c -o tres
$ ./tres
Loja 2, mês 1, semana 4: 3
Total vendido: 89
O array tem 96 bytes       « 2 × 3 × 4 = 24 células × 4 bytes

Cada dimensão a mais pede mais um laço e multiplica o tamanho. Na prática, três dimensões já são raras; acima disso, quase sempre há um jeito mais claro de organizar os dados, como um array de structs.

Passar uma tabela para uma função

Funções têm aula própria (Funções), mas vale saber desde já: quando uma função recebe uma tabela, o parâmetro tem de dizer o número de colunas. O de linhas pode ficar vazio e vai num parâmetro à parte:

#include <stdio.h>

void mostra(int linhas, int m[][4]) {
    for (int l = 0; l < linhas; l++) {
        for (int c = 0; c < 4; c++) {
            printf(" %3d", m[l][c]);
        }
        printf("\n");
    }
}

int main(void) {
    int notas[3][4] = {{7, 8, 6, 9}, {5, 6, 7, 8}, {10, 9, 9, 10}};
    mostra(3, notas);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra funcao.c -o funcao
$ ./funcao
   7   8   6   9
   5   6   7   8
  10   9   9  10

É a conta da seção 4: para achar m[l][c], a função precisa saber quantas células pular por linha. Sem o 4 (int m[][]), o gcc recusa com o mesmo erro da seção 7. Os detalhes de como o array chega à função estão em Parâmetros e Ponteiros e Arrays.


7. Quando dá errado

Linha e coluna trocadas

A Ana quer somar todas as notas da tabela 3 × 4, mas inverteu os limites dos laços: a linha vai até 4 e a coluna até 3.

#include <stdio.h>

int main(void) {
    int notas[3][4] = {
        {7, 8, 6, 9},
        {5, 6, 7, 8},
        {10, 9, 9, 10}
    };
    int soma = 0;
    for (int linha = 0; linha < 4; linha++) {
        for (int coluna = 0; coluna < 3; coluna++) {
            soma += notas[linha][coluna];
        }
    }
    printf("Soma de todas as notas: %d\n", soma);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra trocado.c -o trocado     « nenhum aviso
$ ./trocado
Soma de todas as notas: 481894723

A soma certa é 94. O laço pulou a última coluna de cada aluno e, na volta linha = 3, leu uma "quarta linha" que não existe, cheia do que estava na memória depois da tabela. Nenhum aviso. Como em Arrays, o gcc só percebe com -O2 (warning: iteration 3 invokes undefined behavior, "a volta 3 provoca comportamento indefinido"), e o -fsanitize=address pega na hora de rodar:

$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address trocado.c -o trocado
$ ./trocado
=================================================================
==429329==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b4715bf0060 at pc 0x55749b41165c bp 0x7fff2ed552e0 sp 0x7fff2ed552d0
READ of size 4 at 0x7b4715bf0060 thread T0
    #0 0x55749b41165b in main /home/ana/c/trocado.c:12
…
  This frame has 1 object(s):
    [48, 96) 'notas' (line 4) <== Memory access at offset 96 overflows this variable
…

A tabela ocupa os bytes 48 a 95 (os 48 bytes da seção 4), e a leitura foi no 96, logo depois do fim: é o notas[3][0].

No editor do guia

No editor, o mesmo trocado.c mostrou Soma de todas as notas: 67, sem aviso nem erro: lá, o acesso fora do array passa calado e lê o que houver por perto. Outro número errado, mas que poderia passar por certo. Use LINHAS e COLUNAS com nome (como ALUNOS e PROVAS na seção 3) e esse erro fica muito mais difícil de cometer.

A vírgula: notas[1, 2]

Quem vem de outras linguagens (Python com NumPy, Pascal, C#) escreve os dois índices com vírgula. Em C, isso compila, mas faz outra coisa:

#include <stdio.h>

int main(void) {
    int notas[3][4] = {
        {7, 8, 6, 9},
        {5, 6, 7, 8},
        {10, 9, 9, 10}
    };
    printf("%d\n", notas[1, 2]);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra virgula.c -o virgula
virgula.c: In function ‘main’:
virgula.c:9:27: warning: left-hand operand of comma expression has no effect [-Wunused-value]
    9 |     printf("%d\n", notas[1, 2]);
      |                           ^
virgula.c:9:14: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
    9 |     printf("%d\n", notas[1, 2]);
      |             ~^     ~~~~~~~~~~~
      |              |          |
      |              int        int *
      |             %ls
$ ./virgula
-1373519120
left-hand operand of comma expression has no effect
"O operando da esquerda da expressão com vírgula não tem efeito." Dentro dos colchetes, 1, 2 é o operador vírgula do C: ele calcula o 1, joga fora e fica com o 2. Então notas[1, 2] é notas[2].
argument 2 has type ‘int *’
E notas[2], com um índice só, é a linha 2 inteira, não um número. O printf recebeu um endereço (int *, um ponteiro) e mostrou um número sem sentido. Ignore o %ls sugerido: o conserto é outro.

O conserto: um par de colchetes para cada índice, notas[1][2].

As mensagens

error: array type has incomplete element type ‘int[]’

O que significa: você deixou vazio o tamanho das colunas (int m[][] = … ou o parâmetro int m[][]). A nota que vem junto diz tudo: declaration of ‘m’ as multidimensional array must have bounds for all dimensions except the first, "um array multidimensional precisa de tamanho em todas as dimensões, menos a primeira". Sem o número de colunas, o compilador não sabe onde cada linha começa. (O Clang do editor diz array has incomplete element type 'int[]'.)

Como consertar: escreva as colunas: int m[][2] = {{1, 2}, {3, 4}};.

warning: missing braces around initializer [-Wmissing-braces]

O que significa: os valores não estão separados por linha ({1, 2, 3, 4, 5, 6}). Funciona, mas é fácil errar onde cada linha começa.

Como consertar: uma chave por linha: {{1, 2, 3}, {4, 5, 6}}.

warning: excess elements in array initializer com note: (near initialization for ‘m[0]’)

O que significa: uma linha tem mais valores que colunas ({1, 2, 3, 4} numa tabela de 3 colunas). O m[0] da nota diz qual linha: a 0. O valor de sobra é descartado.

Como consertar: aumente as colunas ou tire o valor.

warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’

O que significa: faltou um índice (notas[1] em vez de notas[1][2]) ou você usou vírgula (notas[1, 2]). Com um índice só, você tem uma linha inteira, não uma célula.

Como consertar: dois pares de colchetes: notas[linha][coluna].

Soma ou média com um valor absurdo, sem nenhum aviso

O que significa: algum índice passou do fim, quase sempre linhas e colunas trocadas nos limites dos laços.

Como consertar: confira se o laço do primeiro índice vai até o número de linhas e o do segundo até o de colunas. Dê nome aos tamanhos com #define. Para achar a linha culpada, compile com -g -fsanitize=address.


Referência rápida

EscritaO que faz
int m[3][4];Tabela de 3 linhas e 4 colunas (12 int)
int m[2][3] = {{1, 2, 3}, {4, 5, 6}};Uma chave por linha (a forma recomendada)
int m[2][3] = {{1}, {4, 5}};O que falta em cada linha vira zero
int m[][3] = {…};O compilador conta as linhas; as colunas são obrigatórias
int m[3][4] = {};Tudo zero (C23; antes, = {0})
m[l][c]A célula da linha l, coluna c
m[l]A linha l inteira (um array de 4)
for (l…) for (c…) m[l][c]Percorre linha por linha
sizeof m / sizeof m[0]Número de linhas
sizeof m[0] / sizeof m[0][0]Número de colunas
char palavras[7][4]7 palavras de até 3 letras + '\0'
void f(int linhas, int m[][4])Função que recebe uma tabela de 4 colunas
m[1, 2]Errado: é m[2] (operador vírgula). Use m[1][2]

Exercícios

1. Quantas células?

Quantos int cabem em int assentos[10][12];? Escreva só o número.

células

2. O canto

Complete para acessar a célula do canto de baixo à direita de int m[5][8]:

m[][]

3. O tamanho obrigatório

Complete a declaração para o compilador contar as linhas sozinho:

int m[][] = {{1, 2}, {3, 4}, {5, 6}};

4. Em int m[2][3] = {{1, 2, 3}, {4, 5, 6}};, quanto vale m[1][0]?

5. Em que ordem as células de int m[2][2] ficam na memória?

6. O que m[1, 2] faz em C?

7. Para somar uma coluna de notas[ALUNOS][PROVAS], qual laço fica por dentro?

Missão final: o produto mais vendido

Missão: a lojinha da Ana vende 3 produtos e anotou quantas unidades de cada um saíram em 4 semanas. Cada linha é um produto e cada coluna é uma semana. Complete o laço de dentro para somar a linha de cada produto e, depois, guarde em campeao o produto com o maior total. Sem avisos.

Produto 1: 22
Produto 2: 34
Produto 3: 10
Mais vendido: produto 2
#include <stdio.h>

#define PRODUTOS 3
#define SEMANAS 4

int main(void) {
    int vendas[PRODUTOS][SEMANAS] = {
        {5, 7, 4, 6},
        {9, 8, 10, 7},
        {3, 2, 4, 1}
    };
    int campeao = 0;
    int maior_total = 0;

    for (int p = 0; p < PRODUTOS; p++) {
        int total = 0;
        // 1. some as semanas do produto p

        printf("Produto %d: %d\n", p + 1, total);
        // 2. se total for o maior até agora, guarde p em campeao

    }
    printf("Mais vendido: produto %d\n", campeao + 1);
    return 0;
}
#include <stdio.h>

#define PRODUTOS 3
#define SEMANAS 4

int main(void) {
    int vendas[PRODUTOS][SEMANAS] = {
        {5, 7, 4, 6},
        {9, 8, 10, 7},
        {3, 2, 4, 1}
    };
    int campeao = 0;
    int maior_total = 0;

    for (int p = 0; p < PRODUTOS; p++) {
        int total = 0;
        for (int s = 0; s < SEMANAS; s++) {
            total += vendas[p][s];
        }
        printf("Produto %d: %d\n", p + 1, total);
        if (total > maior_total) {
            maior_total = total;
            campeao = p;
        }
    }
    printf("Mais vendido: produto %d\n", campeao + 1);
    return 0;
}

Aqui o "campeão provisório" pode começar em 0, ao contrário do que a aula Arrays recomenda, porque vendas nunca são negativas. O campeao guarda o índice (0, 1 ou 2), e o + 1 só aparece na hora de mostrar.


Resumo

  • Um array de duas dimensões é uma tabela: int m[3][4] tem 3 linhas e 4 colunas. Acesse com m[linha][coluna], os dois começando em 0.
  • Percorra com dois for: o de fora para as linhas e o de dentro para as colunas. Trocar a ordem dos laços (para somar colunas) não troca a ordem dos índices.
  • Na memória, a tabela é uma fileira só, linha depois de linha. Para o C, m é um array de linhas, e m[l] é uma linha inteira.
  • Inicialize com uma chave por linha. Só o primeiro tamanho pode ficar vazio; o das colunas é obrigatório, também nos parâmetros de função.
  • Tabelas servem para qualquer tipo: char para tabuleiros e listas de palavras; mais colchetes dão três dimensões ou mais.
  • Dê nome aos tamanhos (#define LINHAS, COLUNAS). Limites trocados leem fora da tabela sem aviso, e m[1, 2] é o operador vírgula: use m[1][2].