Pular para conteúdo

Semana 09 — Matrizes (arranjos bidimensionais)

Nesta semana

Unidade 2 · Seg 05/10 · Qua 07/10 · Sex 09/10

Na Semana 07 você aprendeu os vetores (arranjos de uma dimensão): uma fileira de caixas do mesmo tipo, acessadas por um índice. Muitos problemas do mundo real, porém, não são uma fileira — são uma tabela: as notas de vários alunos em várias provas, um tabuleiro de jogo, os pixels de uma imagem. Para representar tabelas, o C oferece as matrizes (arranjos bidimensionais): uma grade de linhas e colunas, acessada por dois índices.

Esta é a última semana da Unidade 2. Além de dominar a declaração, o acesso e o percurso de matrizes, você vai entender como elas ficam de fato na memória — um detalhe que parece técnico, mas que explica por que passar uma matriz para uma função tem uma regra peculiar. Esse mesmo detalhe é a ponte perfeita para a Unidade 3 (memória e ponteiros), que começa na semana que vem.

🎯 Objetivos de aprendizagem

Ao final desta semana, você deverá ser capaz de:

  • explicar por que e quando uma matriz (arranjo bidimensional) é a estrutura de dados adequada, reconhecendo problemas com natureza de tabela;
  • declarar, inicializar e acessar matrizes em C, entendendo o papel dos dois índices (m[i][j], linha e coluna) e a faixa válida de cada um;
  • explicar como uma matriz é armazenada na memória (linear, por linhas — row-major) e relacionar isso com o que virá na Unidade 3;
  • percorrer matrizes com laços aninhados, lendo e imprimindo em formato de grade;
  • implementar operações típicas: soma de matrizes, diagonal principal, transposta e (opcionalmente) multiplicação;
  • passar matrizes como parâmetros de funções, sabendo que é obrigatório informar o número de colunas, e explicar por quê.

📖 Segunda — Estudo do conteúdo

1. Motivação: quando uma fileira não basta

Um vetor resolve muito bem problemas com uma lista linear de dados: as notas de uma turma, as temperaturas de um mês. Mas repare nestes três problemas:

  • as notas de 30 alunos em 4 provas cada;
  • um tabuleiro de jogo da velha (3 × 3) ou de xadrez (8 × 8);
  • uma imagem digital, que é uma grade de pontos (pixels).

Todos têm a mesma natureza: são tabelas, organizadas em linhas e colunas. Você até poderia tentar representar as notas com 4 vetores separados (prova1, prova2, …), mas isso fica impraticável — e se fossem 10 provas? A estrutura certa é a matriz: um único arranjo com duas dimensões.

Pense na tabela de notas:

aluno \ prova Prova 1 Prova 2 Prova 3 Prova 4
aluno 0 7.0 8.5 6.0 9.0
aluno 1 5.5 6.0 7.5 8.0
aluno 2 9.0 9.5 8.0 10.0

São 3 linhas (os alunos) e 4 colunas (as provas). Para localizar uma nota específica, você precisa de duas coordenadas: "linha 1, coluna 2". É exatamente essa ideia que a matriz captura.

Curiosidade 💡 — matriz na matemática × matriz em programação

O nome vem da álgebra linear, onde uma matriz é uma tabela de números com regras próprias (soma, multiplicação, transposta — que veremos adiante). Em programação, a matriz é a estrutura de dados que representa essa tabela na memória. Nem toda matriz de programação guarda "números de álgebra": um tabuleiro pode guardar 'X' e 'O', e uma imagem, intensidades de cor. A ideia de grade indexada por linha e coluna é que é comum a tudo.

2. Declaração e inicialização

A declaração de uma matriz informa o tipo dos elementos e o número de linhas e de colunas, cada um entre seus próprios colchetes:

int m[3][4];   // uma matriz de inteiros com 3 LINHAS e 4 COLUNAS (12 elementos)

A convenção que usaremos o tempo todo: o primeiro colchete é a linha, o segundo é a coluna. Assim, int m[3][4] é uma tabela com 3 linhas e 4 colunas, capaz de guardar 3 * 4 = 12 valores inteiros.

Podemos inicializar a matriz no momento da declaração, usando chaves aninhadas: um par externo { } para a matriz toda, e um par interno { } para cada linha:

int m[3][4] = {
    {7, 8, 6, 9},     // linha 0
    {5, 6, 7, 8},     // linha 1
    {9, 9, 8, 10}     // linha 2
};

As chaves internas deixam claro onde termina uma linha e começa a outra — e é a forma que você deve preferir, por ser legível. O C também aceita uma lista "achatada" ({7,8,6,9,5,6,7,8,9,9,8,10}), preenchida linha a linha, mas isso esconde a estrutura e convida a erros.

Inicializar tudo com zero

Para começar uma matriz inteira zerada, um atalho clássico:

int tabuleiro[3][3] = {0};   // TODOS os 9 elementos viram 0
Quando você fornece menos valores do que a matriz comporta, os que faltam são preenchidos com zero. Fora esse caso especial, vale a mesma regra da Semana 01: uma matriz declarada sem inicialização contém lixo de memória.

3. Acesso: os dois índices m[i][j]

Para ler ou escrever um elemento, você usa os dois índices:

m[i][j]   // elemento da LINHA i, COLUNA j

Assim como nos vetores, os índices começam em 0. Numa matriz int m[3][4]:

  • o índice de linha i vai de 0 a 2 (isto é, 0 a linhas - 1);
  • o índice de coluna j vai de 0 a 3 (isto é, 0 a colunas - 1).
int m[3][4] = {
    {7, 8, 6, 9},
    {5, 6, 7, 8},
    {9, 9, 8, 10}
};

printf("%d\n", m[0][0]);   // 7   -> primeira linha, primeira coluna
printf("%d\n", m[1][2]);   // 7   -> linha 1, coluna 2
printf("%d\n", m[2][3]);   // 10  -> ultima linha, ultima coluna

m[0][1] = 100;             // altera o elemento da linha 0, coluna 2? NAO: linha 0, coluna 1

Visualmente, os índices funcionam como coordenadas de uma grade:

flowchart TB
    subgraph grade["m[3][4] — [linha][coluna]"]
    direction TB
    L0["m[0][0]  m[0][1]  m[0][2]  m[0][3]"]
    L1["m[1][0]  m[1][1]  m[1][2]  m[1][3]"]
    L2["m[2][0]  m[2][1]  m[2][2]  m[2][3]"]
    end

Estourar os limites da matriz é comportamento indefinido

Exatamente como nos vetores, o C não verifica se o índice é válido. Acessar m[3][0] numa matriz int m[3][4] (não existe linha 3!) ou m[0][4] (não existe coluna 4!) é comportamento indefinido: o programa pode dar resultado errado, imprimir lixo ou travar. Você é responsável por manter i em [0, linhas-1] e j em [0, colunas-1].

4. Como a matriz fica na memória

Aqui está o detalhe que amarra o resto da semana. A memória do computador é, no fundo, linear: uma longa sequência de bytes, um após o outro (lembre da Semana 01). Mas uma matriz é bidimensional. Como conciliar as duas coisas?

O C guarda a matriz linha por linha, uma emendada na outra — a linha 0 inteira, depois a linha 1 inteira, depois a linha 2. Essa organização tem nome: armazenamento por linhas, ou row-major (do inglês, "linha primeiro").

Para a matriz int m[3][4], a memória fica assim, do início ao fim:

flowchart LR
    subgraph mem["Memoria (enderecos crescentes) — armazenamento row-major"]
    direction LR
    A["m[0][0]"] --> B["m[0][1]"] --> C["m[0][2]"] --> D["m[0][3]"]
    D --> E["m[1][0]"] --> F["m[1][1]"] --> G["m[1][2]"] --> H["m[1][3]"]
    H --> I["m[2][0]"] --> J["m[2][1]"] --> K["m[2][2]"] --> L["m[2][3]"]
    end

Repare: logo depois de m[0][3] (fim da linha 0) vem m[1][0] (início da linha 1). As linhas não estão "espalhadas" — estão encadeadas na memória.

Curiosidade 💡 — row-major, e um gancho para a Unidade 3

Se a matriz é linear na memória, então o computador precisa de uma fórmula para, dado m[i][j], calcular o "endereço" correto. Com C colunas por linha, o elemento m[i][j] fica na posição linear:

posicao = i * C + j

Ou seja: "pule i linhas inteiras (cada uma com C elementos) e depois avance j dentro da linha". Guarde essa conta — ela é a razão de tudo que veremos na seção 8 (matrizes em funções). E, mais fundo, é ela que os ponteiros da Unidade 3 vão revelar: m[i][j] é, nos bastidores, aritmética de endereços. Na Semana 10 você vai reencontrar essa mesma ideia de outro ângulo.

5. Percorrendo uma matriz: laços aninhados

Para visitar todos os elementos de um vetor, você usou um laço for (Semana 03). Para visitar todos os elementos de uma matriz, você precisa de dois laços, um dentro do outro — os laços aninhados que você já treinou na Semana 03:

  • o laço externo percorre as linhas (varia i de 0 a linhas-1);
  • o laço interno percorre as colunas daquela linha (varia j de 0 a colunas-1).
#define LINHAS 3
#define COLUNAS 4

int m[LINHAS][COLUNAS] = {
    {7, 8, 6, 9},
    {5, 6, 7, 8},
    {9, 9, 8, 10}
};

for (int i = 0; i < LINHAS; i++) {        // para cada linha
    for (int j = 0; j < COLUNAS; j++) {   // percorre as colunas daquela linha
        printf("m[%d][%d] = %d\n", i, j, m[i][j]);
    }
}

A ordem em que os elementos são visitados por esse par de laços — m[0][0], m[0][1], …, m[0][3], m[1][0], … — é exatamente a ordem em que eles estão na memória (seção 4). Não é coincidência: percorrer com o índice de coluna variando "por dentro" segue o armazenamento row-major.

Sempre use constantes para as dimensões

Definir #define LINHAS 3 e #define COLUNAS 4 (ou constantes const) e usá-las tanto na declaração quanto nos laços evita um erro clássico: mudar o tamanho da matriz e esquecer de ajustar a condição de parada de algum for. Com constantes, você muda em um lugar só.

6. Lendo e escrevendo matrizes

Ler uma matriz do teclado é o mesmo padrão de laços aninhados, agora com scanf. Repare que o & continua obrigatório (Semana 01): precisamos passar o endereço de cada elemento.

le_matriz.c
#include <stdio.h>

#define LINHAS 3
#define COLUNAS 4

int main(void) {
    int m[LINHAS][COLUNAS];

    // LEITURA
    printf("Digite %d valores (%d linhas x %d colunas):\n", LINHAS * COLUNAS, LINHAS, COLUNAS);
    for (int i = 0; i < LINHAS; i++) {
        for (int j = 0; j < COLUNAS; j++) {
            scanf("%d", &m[i][j]);   // &m[i][j]: endereco do elemento
        }
    }

    // IMPRESSAO em formato de grade
    printf("\nMatriz lida:\n");
    for (int i = 0; i < LINHAS; i++) {
        for (int j = 0; j < COLUNAS; j++) {
            printf("%4d", m[i][j]);   // %4d: largura 4, alinha as colunas
        }
        printf("\n");                 // ao FIM de cada linha, quebra a linha
    }

    return 0;
}

Dois detalhes de impressão em grade que fazem toda a diferença:

  • o printf("%4d", ...) reserva uma largura mínima de 4 para cada número, deixando as colunas alinhadas (revisão da Semana 01, seção 6.1);
  • o printf("\n") fica dentro do laço externo, mas fora do interno — assim ele executa uma vez por linha, no ponto certo para quebrar a grade.

Leitura de double em matriz também usa %lf

Se a matriz for de double (por exemplo, notas), lembre da regra da Semana 01: na leitura use %lf, na impressão basta %f:

double notas[3][4];
scanf("%lf", &notas[i][j]);       // leitura
printf("%6.2f", notas[i][j]);     // impressao com 2 casas, largura 6

7. Operações típicas com matrizes

Com o percurso dominado, quase toda operação vira uma variação dos laços aninhados. Veja as mais importantes.

7.1 Soma de duas matrizes

A soma só faz sentido entre matrizes do mesmo tamanho: o resultado tem, em cada posição, a soma dos elementos correspondentes — C[i][j] = A[i][j] + B[i][j].

#define N 3

int A[N][N], B[N][N], C[N][N];

// ... (supondo A e B ja preenchidas) ...

for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
        C[i][j] = A[i][j] + B[i][j];
    }
}

7.2 Diagonal principal

Numa matriz quadrada (mesmo número de linhas e colunas), a diagonal principal é formada pelos elementos em que a linha é igual à coluna: m[0][0], m[1][1], m[2][2], … — ou seja, os elementos com i == j.

int soma_diagonal = 0;
for (int i = 0; i < N; i++) {
    soma_diagonal += m[i][i];   // i == j: basta usar o mesmo indice
}
printf("Soma da diagonal principal: %d\n", soma_diagonal);

Repare que, para a diagonal principal, um único laço basta: como j acompanha i, o acesso é m[i][i]. (A diagonal secundária, dos cantos m[0][N-1] até m[N-1][0], é acessada por m[i][N-1-i] — fica como desafio na sexta.)

7.3 Transposta

A transposta de uma matriz troca linhas por colunas: o elemento que estava em [i][j] vai para [j][i]. A transposta de uma matriz L x C é uma matriz C x L.

#define L 2
#define C 3

int m[L][C]  = { {1, 2, 3}, {4, 5, 6} };
int t[C][L];   // ATENCAO: dimensoes trocadas

for (int i = 0; i < L; i++) {
    for (int j = 0; j < C; j++) {
        t[j][i] = m[i][j];   // linha vira coluna
    }
}

Se m era

1 2 3
4 5 6

então t fica

1 4
2 5
3 6

7.4 Multiplicação de matrizes (opcional)

A multiplicação é a operação mais "densa": cada elemento do resultado é a soma dos produtos de uma linha de A por uma coluna de B. Ela exige que o número de colunas de A seja igual ao número de linhas de B. Para matrizes quadradas N x N, precisamos de três laços aninhados:

#define N 3

int A[N][N], B[N][N], C[N][N];
// ... A e B preenchidas ...

for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
        C[i][j] = 0;                       // zera o acumulador desta posicao
        for (int k = 0; k < N; k++) {
            C[i][j] += A[i][k] * B[k][j];  // linha i de A x coluna j de B
        }
    }
}

Não se preocupe em decorar esse padrão agora; o importante é perceber que, à medida que o problema fica mais rico, os laços aninhados continuam sendo a ferramenta central.

8. Matrizes como parâmetros de funções

Aqui aplicamos, de forma prática, tudo que vimos na seção 4. Assim como os vetores, uma matriz é passada para funções de um jeito especial — e há uma regra que não pode ser esquecida.

Veja uma função que imprime uma matriz de 4 colunas:

void imprime(int m[][4], int linhas) {   // o numero de COLUNAS (4) e OBRIGATORIO
    for (int i = 0; i < linhas; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%4d", m[i][j]);
        }
        printf("\n");
    }
}

Note o parâmetro int m[][4]: o número de linhas pode ficar vazio ([]), mas o número de colunas (4) é obrigatório. Por quê?

Volte à fórmula da seção 4: para encontrar m[i][j] na memória linear, o computador calcula i * COLUNAS + j. Ou seja, para "pular" da linha i para a linha i+1, ele precisa saber quantos elementos tem cada linha — precisa saber o número de colunas. Sem esse número, o compilador não tem como calcular o salto de uma linha para a próxima, e o acesso m[i][j] seria impossível. O número de linhas, por outro lado, não entra nessa conta: por isso pode ser omitido (e costumamos passá-lo à parte, como int linhas).

Curiosidade 💡 — por que só as colunas são obrigatórias

Pense na conta posicao = i * COLUNAS + j. O número de colunas é o "tamanho do passo" para trocar de linha — ele aparece na multiplicação, então o compilador precisa dele. O número de linhas só diria "quando parar", e isso quem controla é o seu laço (i < linhas), não o compilador. É por isso que int m[][4] funciona, mas int m[][] (sem coluna) não compila.

Um programa completo, usando a função:

matriz_funcao.c
#include <stdio.h>

#define LINHAS 3
#define COLUNAS 4

void imprime(int m[][COLUNAS], int linhas) {   // colunas obrigatorias
    for (int i = 0; i < linhas; i++) {
        for (int j = 0; j < COLUNAS; j++) {
            printf("%4d", m[i][j]);
        }
        printf("\n");
    }
}

void dobra_tudo(int m[][COLUNAS], int linhas) {
    for (int i = 0; i < linhas; i++) {
        for (int j = 0; j < COLUNAS; j++) {
            m[i][j] *= 2;      // ALTERA a matriz original de quem chamou!
        }
    }
}

int main(void) {
    int m[LINHAS][COLUNAS] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    printf("Original:\n");
    imprime(m, LINHAS);

    dobra_tudo(m, LINHAS);

    printf("\nDepois de dobrar:\n");
    imprime(m, LINHAS);        // as alteracoes PERSISTIRAM

    return 0;
}

E aqui está a segunda grande diferença em relação aos tipos básicos. Na Semana 04 você viu a passagem por valor: alterar um int dentro de uma função não afeta o original. Com matrizes (como com vetores), acontece o contrário: ao passar uma matriz, o que a função recebe é, na prática, o endereço de onde a matriz começa na memória — não uma cópia. Portanto, alterações feitas dentro da função PERSISTEM na matriz original, como mostra o dobra_tudo acima.

Isso não contradiz a 'passagem por valor' da Semana 04?

Não contradiz — apenas antecipa o próximo grande tema. O nome de uma matriz, quando passado a uma função, "decai" para o endereço do seu primeiro elemento. É o mesmo mecanismo por trás do & do scanf, que você usa desde a Semana 01, e da promessa que fizemos na Semana 04 sobre "alterar a variável original". O nome desse mecanismo é ponteiro, e ele é o coração da Unidade 3, que começa na Semana 10. Por ora, guarde a regra prática: matriz passada para função pode ser modificada por ela.

Curiosidade 💡 — e se eu quiser mais de duas dimensões?

O C não para nas matrizes. Você pode declarar arranjos com três ou mais dimensões, bastando acrescentar colchetes:

int cubo[2][3][4];   // 3 dimensoes: 2 x 3 x 4 = 24 elementos
Isso serve, por exemplo, para representar um conjunto de imagens (imagem × linha × coluna) ou dados ao longo do tempo. O percurso passa a exigir três laços aninhados, e — pela mesma razão da seção 8 — ao passar para uma função todas as dimensões, exceto a primeira, são obrigatórias (int c[][3][4]). Na prática, duas dimensões já cobrem a grande maioria dos problemas do curso.

9. Erros comuns desta semana (resumão)

Checklist de armadilhas

  • Trocar a ordem dos índices: m[coluna][linha] em vez de m[linha][coluna].
  • Estourar limites: usar índice linhas ou colunas (o válido vai só até tamanho - 1).
  • Colocar o printf("\n") no lugar errado (dentro do laço interno, quebrando a grade a cada número).
  • Esquecer o & no scanf de cada elemento (scanf("%d", &m[i][j])).
  • Usar %f em vez de %lf na leitura de matriz de double.
  • Omitir o número de colunas no parâmetro de função (int m[][] não compila).
  • Esquecer que alterações feitas na matriz dentro da função persistem.
  • Na transposta, esquecer de trocar as dimensões da matriz de destino.
  • Usar matriz não inicializada (contém lixo, como qualquer variável — Semana 01).

👥 Quarta — Encontro

Chegue ao encontro tendo estudado o material acima e tendo executado os exemplos — principalmente o le_matriz.c e o matriz_funcao.c. O encontro é para consolidar e tirar dúvidas, não para ver o conteúdo pela primeira vez. Roteiro previsto:

  1. Revisão relâmpago: declaração m[L][C], os dois índices, faixa válida e o percurso com laços aninhados.
  2. Desenhando a memória: vamos desenhar, no quadro, o armazenamento row-major e a conta i * COLUNAS + j, ligando-a diretamente à regra do parâmetro de função.
  3. Construção ao vivo do problema-guia abaixo, praticando a modelagem: identificar entrada, processamento e saída, e decompor em funções (Semana 04).
  4. Galeria de erros: provocamos de propósito os erros da seção 9 (índices trocados, \n fora de lugar, coluna omitida no parâmetro) para você aprender a lê-los.

Problema-guia do encontro — boletim de uma turma: ler as notas de A alunos em P provas (uma matriz A x P de double), e então: (a) imprimir a média de cada aluno (média de cada linha); (b) imprimir a média de cada prova (média de cada coluna); (c) informar a maior nota da turma e em que aluno/prova ela ocorreu.

boletim.c
#include <stdio.h>

#define ALUNOS 3
#define PROVAS 4

// Le a matriz de notas do teclado (altera a matriz de quem chamou)
void le_notas(double notas[][PROVAS], int alunos) {
    for (int i = 0; i < alunos; i++) {
        printf("Notas do aluno %d (%d provas): ", i, PROVAS);
        for (int j = 0; j < PROVAS; j++) {
            scanf("%lf", &notas[i][j]);   // double na leitura: %lf
        }
    }
}

// Media de cada aluno = media de cada LINHA
void medias_por_aluno(double notas[][PROVAS], int alunos) {
    for (int i = 0; i < alunos; i++) {
        double soma = 0.0;
        for (int j = 0; j < PROVAS; j++) {
            soma += notas[i][j];
        }
        printf("Aluno %d: media = %.2f\n", i, soma / PROVAS);
    }
}

// Media de cada prova = media de cada COLUNA (laco externo nas colunas!)
void medias_por_prova(double notas[][PROVAS], int alunos) {
    for (int j = 0; j < PROVAS; j++) {
        double soma = 0.0;
        for (int i = 0; i < alunos; i++) {
            soma += notas[i][j];
        }
        printf("Prova %d: media = %.2f\n", j, soma / alunos);
    }
}

int main(void) {
    double notas[ALUNOS][PROVAS];

    le_notas(notas, ALUNOS);

    printf("\n== Medias por aluno ==\n");
    medias_por_aluno(notas, ALUNOS);

    printf("\n== Medias por prova ==\n");
    medias_por_prova(notas, ALUNOS);

    // maior nota e sua posicao
    double maior = notas[0][0];
    int lin = 0, col = 0;
    for (int i = 0; i < ALUNOS; i++) {
        for (int j = 0; j < PROVAS; j++) {
            if (notas[i][j] > maior) {
                maior = notas[i][j];
                lin = i;
                col = j;
            }
        }
    }
    printf("\nMaior nota: %.2f (aluno %d, prova %d)\n", maior, lin, col);

    return 0;
}

Perguntas para pensar antes do encontro: por que, em medias_por_prova, o laço externo varia a coluna e o interno varia a linha (o oposto do usual)? Por que a função le_notas consegue preencher a matriz da main (lembre da seção 8)?


✍️ Sexta — Estudo dirigido

Reserve este tempo para praticar de verdade. O objetivo não é "ver a resposta", e sim escrever, errar, corrigir e executar. Matrizes são um daqueles tópicos que só "caem a ficha" com as mãos no teclado.

Exercícios guiados (Beecrowd)

Resolva, em C, problemas que trabalham matrizes. Sugestão de ordem (dificuldade crescente):

Problema O que exercita
1181 Linha ler uma matriz e imprimir uma linha escolhida
1182 Coluna ler uma matriz e imprimir uma coluna escolhida
1183 Above the Main Diagonal percorrer a região acima da diagonal principal (j > i)
1184 Below the Main Diagonal percorrer a região abaixo da diagonal principal (j < i)
1187 Top Area somar uma região triangular da matriz (combina condições sobre i e j)
1190 Right Area outra região triangular — treina as condições sobre índices
1435 Square Matrix I gerar uma matriz quadrada com um padrão e imprimi-la formatada
1478 Square Matrix II variação com outro padrão de preenchimento
1534 Array 123 montar/imprimir uma matriz seguindo um padrão dado

A maioria destes problemas é 'percorrer + condição'

Repare no padrão comum: quase todos pedem para percorrer a matriz com laços aninhados e agir apenas em certas posições, definidas por uma condição sobre i e j (ex.: i == j na diagonal; j > i acima dela; i + j == N - 1 na diagonal secundária). Domine essas condições de região e você resolve a família inteira.

Desafios para escrever do zero

  1. Diagonais: ler uma matriz quadrada N x N e imprimir a soma da diagonal principal (m[i][i]) e a soma da diagonal secundária (m[i][N-1-i]).
  2. Maior elemento e posição: ler uma matriz e imprimir o maior elemento e a sua posição (linha e coluna). (Dica: guarde maior, lin e col, como no boletim.)
  3. Matriz simétrica: ler uma matriz quadrada e verificar se ela é simétrica — isto é, se m[i][j] == m[j][i] para todos os pares. (Dica: basta encontrar um par que falhe para concluir que não é.)
  4. Somas por linha e por coluna: ler uma matriz e imprimir a soma de cada linha e a soma de cada coluna. (Reveja o boletim: a soma por coluna inverte a ordem dos laços.)
  5. Matriz identidade: ler N e gerar e imprimir a matriz identidade N x N (1 na diagonal principal, 0 no resto). (Dica: m[i][j] = (i == j) ? 1 : 0;.)

Para investigar (curiosidade prática)

  • Imprima os endereços de alguns elementos e observe o armazenamento linear em ação:
    int m[3][4];
    printf("&m[0][0] = %p\n", (void *) &m[0][0]);
    printf("&m[0][1] = %p\n", (void *) &m[0][1]);
    printf("&m[1][0] = %p\n", (void *) &m[1][0]);
    
    Qual é a diferença (em bytes) entre &m[0][0] e &m[0][1]? E entre &m[0][0] e &m[1][0]? Relacione com o sizeof(int) e com a fórmula i * COLUNAS + j da seção 4. Este pequeno experimento é, na verdade, o seu primeiro contato com ponteiros — o tema da próxima unidade.

✅ Checklist de autoavaliação

Marque com sinceridade — se algum item não estiver ✅, volte à seção correspondente.

  • Reconheço quando um problema tem natureza de tabela e pede uma matriz.
  • Sei declarar e inicializar int m[L][C], inclusive zerar com {0}.
  • Uso corretamente os dois índices m[i][j] (linha, coluna) e respeito os limites.
  • Explico o armazenamento linear por linhas (row-major) e a fórmula i * COLUNAS + j.
  • Percorro uma matriz com laços aninhados e imprimo em grade (com o \n no lugar certo).
  • Leio uma matriz com scanf (&m[i][j], e %lf para double).
  • Implemento soma, diagonal principal e transposta.
  • Passo matriz para função sabendo que o número de colunas é obrigatório — e explico por quê.
  • Sei que alterações feitas na matriz dentro de uma função persistem.

Avaliação 2 (A2)

Com esta semana encerramos a Unidade 2. A Avaliação 2 (A2) cobre o conteúdo das semanas S5 a S9 — modularização, recursão, busca e ordenação, vetores e, agora, matrizes. A data da A2 está no plano de curso; confira-a com antecedência e organize sua revisão. A melhor preparação é refazer os exercícios de cada semana, especialmente aqueles que ficaram difíceis na primeira vez.

🔗 Referências e para se aprofundar

  • Material 2026.1 — Aula 06: Matrizes.
  • BACKES, André. Linguagem C: completa e descomplicada (arranjos multidimensionais / matrizes).
  • KERNIGHAN, B.; RITCHIE, D. The C Programming Language, cap. 5 (arrays multidimensionais) e seção sobre passagem de arrays a funções.
  • MIZRAHI, Victorine V. Treinamento em Linguagem C (vetores e matrizes).
  • Referência: cppreference — arrays (inclui arranjos multidimensionais e a regra de "decaimento" para ponteiro).
  • Prática: Beecrowd.

Antes de seguir para a Semana 10 — começa a Unidade 3

Você notou que, o tempo todo nesta semana, esbarramos em endereços de memória: no & do scanf, no armazenamento row-major, na regra das colunas em funções e no fato de a matriz ser modificável dentro de uma função. Tudo isso aponta para o mesmo lugar. Na Semana 10 abrimos a Unidade 3 e vamos, finalmente, olhar de frente o que é a memória e o que são os ponteiros — o mecanismo que explica, de uma vez, o &, os vetores, as matrizes e a passagem "por referência" que prometemos desde a Semana 04. É a peça que faltava para você entender o C "por dentro".