Vetores
Variáveis homogêneas compostas são um conjunto de valores de um mesmo tipo. No quesito dimensão podem ser classificadas como unidimensional (vetores) e multidimensional (matrizes).
Introdução
Um vetor, também conhecido como array, é uma estrutura de dados unidimensional e homogênea que representa uma coleção de variáveis do mesmo tipo. Cada variável dentro desse conjunto é denominada como um elemento do vetor.
Cada elemento do vetor é identificado por um índice, que é um número indicando a posição do elemento dentro do array. O primeiro elemento tem um índice de 0, e o índice aumenta sequencialmente para os elementos subsequentes.
Sintaxe:
<tipo> <nome do vetor>[<tamanho>];
- O termo
tamanho
define a capacidade do vetor, ou seja, quantos elementos ele pode armazenar. O tamanho de um vetor é fixo (por isso chamado de vetor estático), ou seja, após a sua definição não é possível exceder essa capacidade.Não há verificação automática se o tamanho do array é ultrapassado; a responsabilidade é do programador.
- Os índices variam de
0
atamanho - 1
.
Exemplos:
float nums = {0.5f, 1.6f, 5.9f};
Nesse exemplo, quando o tamanho do vetor não é especificado explicitamente, mas os valores são atribuídos na declaração, então a linguagem inferirá o tamanho do vetor com base no número de elementos fornecidos.
#include <stdio.h>
int main(int argc, char const *argv[])
{
float numbers[3] = {0.5f, 5.0f, 10.14f};
for (int i = 0; i < 3; i++) {
printf("%f ", numbers[i]);
}
printf("\n");
return 0;
}
Resultado:
0.500000 5.000000 10.140000
Alocando memória para vetores
Quando se trata da alocação de memória para variáveis compostas, como vetores, a disposição é sequencial na memória. O endereço mais baixo corresponde ao primeiro elemento do vetor, ou seja, aquele com índice 0.
Antes de explorar a alocação de memória para vetores, é importante entender as distinções fundamentais entre uma variável simples e uma composta.
- Variável simples: possui um único endereço de memória e armazena apenas um dado por vez.
- Variável composta: armazena N valores sequencialmente na memória, onde N é o número de elementos do vetor.
Para encontrar o endereço de memória de um elemento específico, tendo o endereço do primeiro elemento como ponto de partida, é possível calcular o endereço usando a seguinte fórmula:
Onde:
i
é a posição do elemento.E[i]
é o endereço de memória desse elemento.E[0]
é o endereço de memória do primeiro elemento do vetor.
Aqui está o cálculo do endereço de cada elemento de um vetor com capacidade para 6 elementos:
Este exemplo assume que cada elemento ocupa exatamente 1 byte.
Inicialização de vetores
A seguir estão listadas as diferentes abordagens de inicialização de vetores em C, os quais oferecem flexibilidade para atribuir valores às posições do vetor de acordo com as necessidades do programa.
-
Inicialização Direta:
float numbers[3] = {0.5f, 5.0f, 10.14f};
Neste exemplo, todas as posições do vetor recebem valores.
-
Inicialização em Posições Específicas:
float numbers[3] = {[2] = 99};
Aqui, somente a posição 2 do vetor recebe um valor durante a inicialização.
-
Inicialização em Posições Não Sequenciais:
float numbers[5] = {[2] = 7.2f, [4] = 1.1f};
O vetor é inicializado com valores nas posições 2 e 4.
-
Tamanho Inferido a partir de Índices:
float numbers[] = {[1] = 3.14f, [3] = 2.5f};
O tamanho do vetor é definido pelo maior índice especificado (neste caso, 3).
-
Inicialização com Intervalo:
int squares[10] = {[0 ... 9] = 25};
A notação de intervalo permite a inicialização de um intervalo de posições com o mesmo valor.
-
Mistura de Valores e Posições Específicas:
int numbers[5] = {1, 2, [4] = 5};
Nesse exemplo, as posições 0, 1 e 4 do vetor recebem valores.
Tipos de vetores
Em linguagem C, os vetores são estruturas de dados utilizadas para armazenar uma coleção de elementos do mesmo tipo. Existem diferentes tipos de vetores que oferecem flexibilidade em relação ao tamanho e alocação de memória:
-
Variable-length array (VLA): Um VLA permite definir o tamanho de um vetor em tempo de execução, ou seja, durante a execução do programa. No entanto, essa funcionalidade não está presente em todas as versões da linguagem C e em todos os compiladores. Em alguns casos, o uso de VLA pode ser opcional ou até mesmo não suportado.
-
Vetor Dinâmico (Dynamic Array): Vetores dinâmicos não são uma característica nativa da linguagem C. Eles são geralmente implementados usando alocação dinâmica de memória, como funções
malloc
efree
. Isso permite criar vetores cujo tamanho pode ser definido em tempo de execução e pode ser alterado conforme necessário. -
Vetor Alocado Dinamicamente: Esses vetores também utilizam alocação dinâmica de memória para criar vetores cujo tamanho pode ser definido em tempo de execução e ajustado conforme necessário. A alocação e liberação de memória são feitas manualmente pelo programador usando funções como
malloc
efree
.
Definição da Dimensão do Vetor
Em C, o tamanho de um vetor deve ser definido em tempo de compilação, e não em tempo de execução. A dimensão do vetor é especificada por um número literal inteiro ou por uma macro que é convertida em um valor literal inteiro pelo pré-processador.
Exemplos:
- Número Literal Inteiro:
int vetor[5]; // Vetor com tamanho 5
- Macro Convertida para Valor Literal:
#define TAMANHO 10
int outroVetor[TAMANHO]; // Vetor com tamanho 10
É importante notar que vetores dinâmicos e alocação dinâmica de memória oferecem alternativas para lidar com tamanhos variáveis em tempo de execução. VLA é uma opção em algumas versões do C, mas pode ser limitado ou não suportado em outras. Portanto, a escolha do tipo de vetor depende da versão da linguagem e das necessidades específicas do programa.
Tamanho do vetor
Ultrapassar a capacidade de um vetor
Em linguagem C, não existe um mecanismo automático que avise quando o limite de um vetor foi excedido. Isso significa que, se ultrapassarmos os limites de um vetor durante uma operação de atribuição, os valores excedentes irão sobrescrever outras áreas de memória, potencialmente armazenando dados em locais indesejados.
A razão por trás disso é que, ao declarar um vetor, você está alocando uma determinada quantidade de memória contígua para armazenar os elementos desse vetor. Se você tentar atribuir valores a posições além do limite definido para o vetor, os valores serão escritos na memória adjacente, que pode conter outras variáveis ou dados do programa.
Esse comportamento indesejado geralmente resulta em comportamento indefinido ou erros durante a execução do programa, incluindo falhas inesperadas e resultados incorretos. Portanto, é crucial garantir que você não ultrapasse os limites de um vetor.
A responsabilidade de verificar e garantir que os limites do vetor não sejam excedidos é do programador. Uma abordagem comum para evitar esse problema é não permitir que o usuário insira dados que ultrapassem o tamanho do vetor. Além disso, o uso de verificações de limites adequadas em loops e operações de atribuição é essencial para garantir a integridade dos dados e a estabilidade do programa.
Cálculo do tamanho do vetor
O cálculo do tamanho de um vetor em C envolve o uso da função sizeof()
e a divisão do tamanho total alocado pela quantidade de bytes ocupada por cada elemento do vetor.
A função sizeof(var)
retorna o tamanho total alocado na memória para a variável var
, em bytes. No entanto, isso não nos fornece diretamente o número de elementos que cabem no vetor, mas sim a quantidade total de memória que o vetor ocupa.
Para calcular o tamanho efetivo do vetor, ou seja, a quantidade de elementos que ele pode armazenar, realizamos a divisão do resultado de sizeof(var)
pelo tamanho, em bytes, do tipo de dado que compõe o vetor. Por exemplo, se var
é um vetor de inteiros (int
), e cada int
ocupa 4 bytes (o tamanho pode variar dependendo do sistema e do compilador), o cálculo do tamanho do vetor de inteiros é dado por:
size_t <tamanho do vetor> = sizeof(var) / sizeof(int)
O tipo de retorno é
unsigned long long
, também pode ser usadosize_t
(typedef
), pois o tamanho de um array não admite valores negativos, por isso é definido comounsigned
no tipo do retorno.
Isso resultará no número de elementos do vetor, que é a quantidade que efetivamente pode ser armazenada.
É importante notar que esse cálculo assume que todos os elementos do vetor têm o mesmo tamanho, o que é verdadeiro para vetores de tipos de dados simples. No entanto, para estruturas de dados mais complexas, onde os elementos podem ter tamanhos diferentes, esse cálculo não é diretamente aplicável.
Vetores como parâmetros
Em C, quando um vetor é passado como argumento para uma função, ele é convertido automaticamente em um ponteiro para o primeiro elemento do vetor. Esse processo é conhecido como “array to pointer decay” ou “decaimento do vetor para ponteiro”. Isso significa que a função recebe um ponteiro para o primeiro elemento do vetor e não uma cópia do vetor em si.
Essa conversão ocorre porque vetores são geralmente representados como sequências contíguas de memória, e passar um ponteiro é mais eficiente em termos de uso de memória e desempenho do que passar todo o vetor.
Aqui está um exemplo que ilustra esse conceito:
#include <stdio.h>
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
// O vetor "numbers" é passado como parâmetro para a função
// "printArray", mas na função, ele é tratado como um ponteiro
printArray(numbers, 5);
return 0;
}
Nesse exemplo, a função printArray
recebe um vetor arr
como seu primeiro parâmetro e um tamanho size
como seu segundo parâmetro. No entanto, na verdade, arr
é tratado como um ponteiro para inteiros, e o tamanho é necessário para saber quantos elementos percorrer.
Ao executar esse código, a saída será:
1 2 3 4 5
Isso demonstra como um vetor é automaticamente convertido em um ponteiro ao ser passado como argumento para uma função em C.