Arrays em C
LidoNesta aula, você aprenderá sobre arrays em C, desde a declaração e acesso até a inicialização, entendendo seu tamanho fixo e a relação fundamental com ponteiros. Serão apresentados exemplos práticos e exercícios para consolidar o conhecimento.
Arrays (ou vetores) são estruturas de dados fundamentais na linguagem C, que permitem armazenar uma sequência de elementos do mesmo tipo em posições contíguas de memória. Dominar arrays é essencial para desenvolver programas eficientes e compreender como a memória é manipulada em C.
Nesta aula, vamos explorar desde a declaração e o acesso aos elementos, passando pela inicialização, até a estreita relação entre arrays e ponteiros, que é uma das características mais poderosas (e às vezes confusas) da linguagem.
Declaração e acesso
Em C, um array é declarado especificando o tipo dos elementos, o nome do array e o tamanho entre colchetes. O tamanho deve ser uma constante inteira (ou uma expressão constante) e é definido em tempo de compilação. Por exemplo, int numeros[5]; declara um array de 5 inteiros.
O acesso aos elementos é feito através do índice, que começa em 0. Assim, o primeiro elemento é numeros[0], o segundo numeros[1], e assim por diante. O último elemento válido é numeros[4] no exemplo acima. Tentar acessar um índice fora dos limites (como numeros[5]) é um erro de programação que pode causar comportamento indefinido, como corromper memória ou causar falhas de segmentação.
#include <stdio.h>
int main() {
int numeros[5]; // declara array de 5 inteiros
// Atribuindo valores
numeros[0] = 10;
numeros[1] = 20;
numeros[2] = 30;
numeros[3] = 40;
numeros[4] = 50;
// Acessando e imprimindo
for (int i = 0; i < 5; i++) {
printf("numeros[%d] = %d\n", i, numeros[i]);
}
return 0;
}
É importante notar que o índice pode ser qualquer expressão inteira, inclusive variáveis e expressões aritméticas. Isso permite percorrer o array com loops, como mostrado acima.
Inicialização
Arrays podem ser inicializados no momento da declaração, fornecendo uma lista de valores entre chaves. Se você fornecer menos valores do que o tamanho do array, os elementos restantes serão inicializados com zero (para tipos numéricos). Se você fornecer mais valores do que o tamanho declarado, o compilador emitirá um erro.
int vetor[5] = {1, 2, 3}; // vetor[3] e vetor[4] serão 0
int outro[] = {10, 20, 30}; // o tamanho é inferido como 3
Também é possível inicializar arrays de caracteres (strings) com literais de string. Nesse caso, o array inclui automaticamente o caractere nulo '\0' no final, a menos que o tamanho seja especificado exatamente sem espaço para ele (o que não é recomendado).
char palavra[] = "Olá"; // tamanho 4 (O, l, á, \0)
char nome[10] = "João"; // os caracteres restantes são zerados
Para arrays multidimensionais, a inicialização é feita com chaves aninhadas. Por exemplo:
int matriz[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
Quando você inicializa um array, pode omitir o primeiro tamanho (o das linhas) e o compilador o deduz a partir da lista de inicializadores, mas os tamanhos subsequentes devem ser especificados.
Tamanho fixo
Uma característica importante dos arrays em C é que seu tamanho é fixo durante todo o tempo de execução. O tamanho é definido em tempo de compilação (a menos que você use alocação dinâmica, que veremos em aulas futuras). Isso significa que você não pode aumentar ou diminuir o tamanho de um array após a sua criação.
Consequentemente, ao declarar um array, você precisa saber exatamente quantos elementos ele deve armazenar. Isso pode ser um desafio quando o número de elementos só é conhecido em tempo de execução. Nesses casos, é comum alocar um array com um tamanho máximo possível ou usar alocação dinâmica com malloc e realloc.
Outra consequência é que você deve ter cuidado para não ultrapassar os limites do array. O compilador não verifica automaticamente os limites, então o programador é responsável por garantir que os índices sejam válidos.
#define TAMANHO 10
int main() {
int dados[TAMANHO];
// Preenchendo com os quadrados dos índices
for (int i = 0; i < TAMANHO; i++) {
dados[i] = i * i;
}
// Exibindo
for (int i = 0; i < TAMANHO; i++) {
printf("%d ", dados[i]);
}
return 0;
}
É possível obter o número de elementos de um array em tempo de compilação usando o operador sizeof. Por exemplo, sizeof(dados) / sizeof(dados[0]) retorna o número de elementos, pois sizeof(dados) é o tamanho total em bytes e sizeof(dados[0]) é o tamanho de um elemento. Essa técnica é útil para evitar hard-coded de tamanhos em loops.
Relação com ponteiros
Em C, arrays e ponteiros estão intimamente relacionados. Na maioria dos contextos, o nome de um array é convertido em um ponteiro para o seu primeiro elemento. Por exemplo, se você tem int arr[5], o nome arr pode ser usado como um ponteiro para int apontando para arr[0].
Isso significa que você pode usar aritmética de ponteiros para acessar os elementos do array. Por exemplo, *(arr + i) é equivalente a arr[i]. Na verdade, a expressão arr[i] é traduzida pelo compilador para *(arr + i). Essa equivalência é tão forte que você pode escrever i[arr], embora isso seja incomum e confuso.
#include <stdio.h>
int main() {
int vetor[4] = {10, 20, 30, 40};
int *ptr = vetor; // ptr aponta para o primeiro elemento
printf("Primeiro elemento: %d\n", *ptr); // 10
printf("Segundo elemento: %d\n", *(ptr + 1)); // 20
printf("Endereço do primeiro: %p\n", (void*)ptr);
printf("Endereço do segundo: %p\n", (void*)(ptr + 1));
return 0;
}
É importante notar que, apesar dessa relação, arrays e ponteiros não são a mesma coisa. Um array é um bloco de memória contíguo com tamanho fixo, enquanto um ponteiro é uma variável que armazena um endereço. Por exemplo, você não pode atribuir um array a outro array diretamente (arr1 = arr2 é inválido), mas pode atribuir ponteiros.
Quando você passa um array para uma função, na verdade está passando um ponteiro para o primeiro elemento. Portanto, a função não conhece o tamanho do array, a menos que você o informe explicitamente.
void imprimir(int *arr, int tamanho) {
for (int i = 0; i < tamanho; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int dados[5] = {1, 2, 3, 4, 5};
imprimir(dados, 5); // passa o array como ponteiro
return 0;
}
Boas práticas e observações finais
Ao trabalhar com arrays, sempre verifique os limites para evitar estouro de buffer, que pode levar a vulnerabilidades de segurança. Prefira usar constantes simbólicas (como #define ou const int) para definir o tamanho, em vez de números mágicos espalhados pelo código.
Lembre-se de que o tamanho de um array é fixo; se precisar de uma coleção dinâmica, estude alocação dinâmica de memória (malloc, calloc, realloc) e estruturas de dados como listas ligadas.
Quando usar sizeof com arrays, tenha cuidado: se o array for passado para uma função, ele decai para um ponteiro, e sizeof retornará o tamanho do ponteiro, não do array. Portanto, sempre passe o tamanho como parâmetro.
Exercícios
- Declare um array de 10 inteiros e preencha-o com os números de 1 a 10. Em seguida, imprima o array na ordem inversa.
- Escreva um programa que declare um array de 5 floats, inicialize-o com valores à sua escolha e calcule a média dos elementos. Imprima a média com duas casas decimais.
- Dado o array
int arr[4] = {5, 10, 15, 20}, use aritmética de ponteiros para imprimir cada elemento e seu endereço de memória. - Crie uma função que receba um array de inteiros e seu tamanho, e retorne o maior elemento. No
main, teste a função com um array de 7 elementos. - Explique a diferença entre
int *ptr;eint arr[5];. Em particular, o que acontece quando você usasizeofem cada um?
#include <stdio.h>
int main() {
int numeros[10];
for (int i = 0; i < 10; i++) {
numeros[i] = i + 1;
}
for (int i = 9; i >= 0; i--) {
printf("%d ", numeros[i]);
}
return 0;
}
#include <stdio.h>
int main() {
float valores[5] = {1.5, 2.5, 3.5, 4.5, 5.5};
float soma = 0.0;
for (int i = 0; i < 5; i++) {
soma += valores[i];
}
float media = soma / 5;
printf("Média: %.2f\n", media);
return 0;
}
#include <stdio.h>
int main() {
int arr[4] = {5, 10, 15, 20};
int *ptr = arr;
for (int i = 0; i < 4; i++) {
printf("Elemento %d: %d, endereço: %p\n", i, *(ptr + i), (void*)(ptr + i));
}
return 0;
}
#include <stdio.h>
int maior_elemento(int *arr, int tamanho) {
int maior = arr[0];
for (int i = 1; i < tamanho; i++) {
if (arr[i] > maior) {
maior = arr[i];
}
}
return maior;
}
int main() {
int dados[7] = {3, 5, 7, 2, 8, -1, 4};
int maior = maior_elemento(dados, 7);
printf("Maior elemento: %d\n", maior);
return 0;
}
int *ptr; declara um ponteiro para inteiro. Ele pode apontar para qualquer local de memória e seu tamanho é o tamanho de um ponteiro (geralmente 8 bytes em sistemas 64-bit). int arr[5]; declara um array de 5 inteiros. O nome arr é um endereço constante (não pode ser modificado) para o primeiro elemento, e sizeof(arr) retorna o tamanho total em bytes (5 * sizeof(int)). Já sizeof(ptr) retorna o tamanho do ponteiro, não do bloco para o qual aponta.