Até agora, cada dado morava em sua própria variável. Mas e se você precisa guardar as notas de trinta alunos, ou as temperaturas de um ano inteiro? Declarar nota1, nota2, ..., nota30 seria absurdo. Para isso existem os vetores (ou arrays): uma sequência de valores do mesmo tipo, guardados lado a lado na memória. E é justamente esse "lado a lado" que torna este artigo tão importante — ele é a ponte direta para entender ponteiros, o tema do próximo capítulo.
Declarando e acessando um vetor
Um vetor é declarado com o tipo, o nome e, entre colchetes, o número de elementos:
#include <stdio.h>
int main(void) {
int notas[5]; // reserva espaço para 5 inteiros
notas[0] = 8;
notas[1] = 6;
notas[2] = 9;
notas[3] = 7;
notas[4] = 10;
printf("Primeira nota: %d\n", notas[0]);
printf("Última nota: %d\n", notas[4]);
return 0;
}
O detalhe que confunde todo iniciante: os índices começam em zero. Um vetor de 5 elementos tem índices de 0 a 4 — nunca 5. O primeiro elemento é notas[0]; o último é notas[4]. Guarde isso com força, porque o erro de acessar notas[5] é um dos mais comuns e perigosos do C.
Inicializando de uma vez
Você pode preencher o vetor na própria declaração, com chaves:
#include <stdio.h>
int main(void) {
int notas[5] = {8, 6, 9, 7, 10};
// Se você omitir o tamanho, o compilador conta para você:
double precos[] = {1.50, 2.75, 0.99}; // tamanho 3, automático
printf("%d %.2f\n", notas[2], precos[1]); // 9 2.75
return 0;
}
Se você fornecer menos valores que o tamanho declarado, o resto é preenchido com zeros — um truque útil: int zeros[100] = {0}; cria cem zeros.
Vetores e laços: feitos um para o outro
A verdadeira força dos vetores aparece quando os combinamos com laços. Aquela contagem "de 0 a n−1" que praticamos no artigo anterior existe exatamente por causa disto:
#include <stdio.h>
int main(void) {
int notas[5] = {8, 6, 9, 7, 10};
int soma = 0;
for (int i = 0; i < 5; i++) {
soma += notas[i];
}
double media = (double) soma / 5;
printf("Soma: %d, Média: %.2f\n", soma, media);
return 0;
}
Repare no (double) soma — isso é um cast, uma conversão de tipo explícita. Sem ele, soma / 5 seria uma divisão inteira (resultado 8, perdendo as casas decimais). Ao converter soma para double antes, forçamos a divisão a ser de ponto flutuante. É outra daquelas sutilezas do C que o compilador não avisa.
O calcanhar de Aquiles: o C não vigia as fronteiras
Aqui está a característica mais perigosa dos vetores em C, e preciso que você a leve a sério. O C não verifica se o índice é válido. Se o vetor tem 5 elementos e você acessa a posição 10, o C não reclama — ele simplesmente lê ou escreve no pedaço de memória que estiver ali, seja lá o que for:
int v[5] = {1, 2, 3, 4, 5};
v[10] = 99; // desastre silencioso: escreve fora do vetor
printf("%d\n", v[7]); // lê lixo, ou trava o programa
Isso é o que se chama de comportamento indefinido (undefined behavior). O programa pode continuar como se nada fosse, pode imprimir lixo, pode travar na hora, ou — pior — travar horas depois, num ponto totalmente diferente do código. É a fonte número um de bugs e vulnerabilidades em C. A responsabilidade de respeitar os limites é inteiramente sua. Lembre-se: o C confia em você, e essa confiança tem um preço.
Um aviso antes de testar isto online: um playground roda seu código uma vez só, num compilador e num sistema só — e o resultado parece estável, que é justamente a impressão errada. Para ver o problema ser detectado em vez de mascarado, use o Compiler Explorer com a flag -fsanitize=address: o acesso fora de faixa passa a interromper o programa com um relatório apontando a linha. É a ferramenta que a Fase 6 do curso aprofunda.
O tamanho de um vetor com sizeof
Como o C não guarda o tamanho junto do vetor, um truque comum é calculá-lo com sizeof: o tamanho total dividido pelo tamanho de um elemento:
#include <stdio.h>
int main(void) {
int v[] = {10, 20, 30, 40, 50, 60};
int tamanho = sizeof(v) / sizeof(v[0]);
printf("O vetor tem %d elementos.\n", tamanho); // 6
for (int i = 0; i < tamanho; i++) {
printf("%d ", v[i]);
}
printf("\n");
return 0;
}
Atenção a uma pegadinha importante: esse truque só funciona no mesmo escopo em que o vetor foi declarado. Quando você passa um vetor para uma função, ele "vira" um ponteiro e perde a informação de tamanho — e aí o sizeof mente. Esse fenômeno, o array decay, é a razão pela qual precisamos passar o tamanho como argumento separado. É também a maior pista de que vetor e ponteiro são, no fundo, quase a mesma coisa.
Passando vetores para funções
Por causa desse decaimento, a convenção em C é sempre passar o vetor e o seu tamanho:
#include <stdio.h>
int maior_elemento(int v[], int n) {
int maior = v[0];
for (int i = 1; i < n; i++) {
if (v[i] > maior) {
maior = v[i];
}
}
return maior;
}
int main(void) {
int numeros[] = {23, 8, 42, 15, 4, 16};
int n = sizeof(numeros) / sizeof(numeros[0]);
printf("Maior elemento: %d\n", maior_elemento(numeros, n));
return 0;
}
Saída: Maior elemento: 42
E aqui aparece algo curioso, que só entenderemos de verdade no próximo artigo: ao contrário das variáveis simples (que são copiadas), o vetor não é copiado ao ser passado. A função recebe acesso ao vetor original e poderia modificá-lo. Isso quebra a regra de "passagem por valor" que aprendemos? Não — na verdade, ela a confirma, porque o que é copiado é o endereço do vetor, não seu conteúdo. Segure essa ideia: ela é a chave dos ponteiros.
Vetores de duas dimensões
Vetores podem ter mais de uma dimensão — úteis para matrizes, tabuleiros e imagens:
#include <stdio.h>
int main(void) {
int matriz[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matriz[i][j]);
}
printf("\n");
}
return 0;
}
matriz[i][j] acessa a linha i, coluna j. Por baixo dos panos, mesmo uma matriz é armazenada como uma sequência linear e contígua na memória — o C apenas calcula a posição correta para você.
O que vem a seguir
Hoje demos nosso primeiro passo dentro da memória: aprendemos que um vetor é uma fileira de valores dispostos lado a lado, e vimos pistas de que ele guarda uma relação profunda com endereços de memória. No próximo artigo, vamos encarar de frente o conceito mais poderoso — e mais temido — do C: os ponteiros. Você vai descobrir o que é um endereço de memória, como guardá-lo numa variável, e por que quase tudo que fizemos até aqui estava, silenciosamente, preparando você para este momento.
Fontes e leituras recomendadas
- The C Programming Language (K&R), Kernighan & Ritchie — Cap. 5, Pointers and Arrays (a relação vetor/ponteiro)
- cppreference — Array declaration — https://en.cppreference.com/w/c/language/array
- Modern C, Jens Gustedt — capítulo sobre vetores e o array decay — https://gustedt.gitlabpages.inria.fr/modern-c/
- AddressSanitizer — ferramenta que detecta acessos fora dos limites em tempo de execução — https://github.com/google/sanitizers/wiki/AddressSanitizer
- CERT C Coding Standard — ARR30-C, sobre índices de vetores válidos — https://wiki.sei.cmu.edu/confluence/display/c
Exercícios
Exercício 1
Declare um vetor com os números {5, 12, 8, 130, 44} e escreva um laço que imprima cada elemento com seu índice, no formato [0] = 5.
Ver resposta
✓ Resposta:
#include <stdio.h>
int main(void) {
int v[] = {5, 12, 8, 130, 44};
int n = sizeof(v) / sizeof(v[0]);
for (int i = 0; i < n; i++) {
printf("[%d] = %d\n", i, v[i]);
}
return 0;
}
Exercício 2
Escreva uma função double media(double v[], int n) que receba um vetor de double e seu tamanho e devolva a média dos elementos.
Ver resposta
✓ Resposta: A média é a soma dividida pela quantidade; como v já é double, não há problema de divisão inteira, mas convém garantir n > 0:
double media(double v[], int n) {
double soma = 0.0;
for (int i = 0; i < n; i++) {
soma += v[i];
}
return (n > 0) ? soma / n : 0.0;
}
Exercício 3
Escreva uma função int contar_maiores(int v[], int n, int limite) que conte quantos elementos do vetor são estritamente maiores que limite.
Ver resposta
✓ Resposta:
int contar_maiores(int v[], int n, int limite) {
int qtd = 0;
for (int i = 0; i < n; i++) {
if (v[i] > limite) {
qtd++;
}
}
return qtd;
}
Exercício 4
Compile este trecho com gcc -fsanitize=address e rode: int v[3] = {1,2,3}; printf("%d\n", v[5]);. O que o AddressSanitizer relata? Por que isso não apareceria sem a ferramenta?
Ver resposta
✓ Resposta: O AddressSanitizer relata um erro do tipo "stack-buffer-overflow" (ou global-buffer-overflow), apontando a leitura de v[5] num vetor de 3 posições, com a linha exata. Sem a ferramenta, o acesso a v[5] é comportamento indefinido: o C simplesmente lê o que estiver naquele endereço e segue em frente, muitas vezes sem travar — por isso o bug passa despercebido. O sanitizer instrumenta a memória para flagrar o acesso inválido no momento em que ele ocorre.
Exercício 5
Crie uma matriz 3×3 de inteiros, preencha-a com uma tabuada (m[i][j] = (i+1)*(j+1)) usando laços aninhados e imprima-a em formato de grade.
Ver resposta
✓ Resposta:
#include <stdio.h>
int main(void) {
int m[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
m[i][j] = (i + 1) * (j + 1);
}
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%2d ", m[i][j]); // %2d alinha em 2 colunas
}
printf("\n");
}
return 0;
}
Saída:
1 2 3
2 4 6
3 6 9