.. | ||
.#testes.c | ||
diagrama.txt | ||
inicializa.c | ||
notations.c | ||
README.org | ||
string.c | ||
testes.c |
Curso Básico da Linguagem C
Aula 7: Vetores, ponteiros e strings
Parte do conteúdo desta aula foi antecipado nas anotações da aula 6!
Embora sejam conceitos totalmente distintos, existem relações importantes entre vetores e ponteiros que nós precisamos conhecer.
Notações de acesso
Tanto ponteiros quanto vetores (e seus elementos) podem ser acessados de duas formas…
Acesso | Aritmética | Subscrito |
---|---|---|
Valor/Elemento | *(nome + índice) |
nome[índice] |
Endereço | nome + índice |
&nome[índice] |
Exemplo
Dados o vetor e o ponteiro abaixo…
char v[] = {65, 66, 67, 68, 69, 0}; // Vetor de caracteres (bytes)
char *ptr = "FGHIJ"; // Ponteiro para uma string em .rodata (read only)
Impressão do endereço de um caractere…
// Impressões do vetor...
printf("v[2] : %c (%d) --> &v[2]: %p\n", v[2], v[2], &v[2]);
printf("*(v + 2): %c (%d) --> v + 2: %p\n", *(v + 2), *(v + 2), v + 2);
// Impressões do ponteiro...
printf("p[2] : %c (%d) --> &p[2]: %p\n", p[2], p[2], &p[2]);
printf("*(p + 2): %c (%d) --> p + 2: %p\n", *(p + 2), *(p + 2), p + 2);
Saída do programa…
v[2] : C (67) --> &v[2]: 0x7ffda53a16c4 *(v + 2): C (67) --> v + 2: 0x7ffda53a16c4 p[2] : H (72) --> &p[2]: 0x562d0c93100a *(p + 2): H (72) --> p + 2: 0x562d0c93100a
Vetores e funções
Observe esta função:
int array_sum(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++)
sum += arr[i]; // arr é um ponteiro, não um vetor!
return sum;
}
- Funções não podem receber vetores como argumentos, apenas seus endereços.
- Escreve-se
int arr[]
apenas para indicar que o argumento esperado é o endereço de um vetor, já quearr
será compilado como um ponteiro. - Do mesmo modo, utiliza-se
arr[i]
, na iteração, para manter a coerência semântica, já que a notaçãonome[índice]
também pode ser utilizada com ponteiros.
Este mesmo exemplo poderia ser escrito apenas com notações de ponteiros e aritmética de ponteiros:
int array_sum(int *arr, int size) {
int sum = 0;
for (int i = 0; i < size; i++)
sum += *(arr + i); // Notação aritmética!
return sum;
}
Em ambos os casos, a função lidará com o ponteiro (arr
) que recebeu como
argumento o endereço de um vetor. O vetor em si, jamais será convertido
em ponteiro e nem tampouco "decairá para um ponteiro"!
Strings são vetores de caracteres
Strings são vetores de caracteres terminados com o caractere nulo (\0
).
Declaração de um vetor para receber uma string…
char str[TAMANHO];
Quando o vetor é declarado sem ser inicializado, é obrigatório definir a quantidade de elementos que o vetor receberá.
Declaração e inicialização de um vetor com uma string…
char str[] = {'b', 'a', 'c', 'a', 'n', 'a', '\0'};
Quando o vetor é inicializado, a quantidade de elementos pode ser omitida para ser definida na compilação.
A presença da terminação do caractere terminador
'\0'
é que define uma cadeia de caracteres como uma string, ou seja: uma cadeia de bytes que pode ser utilizada com segurança em funções que procuram o terminador para determinar o fim do processamento da cadeia de caracteres.
Declaração e inicialização de um vetor com uma string literal…
char str[] = "bacana";
Quando a string literal "bacana"
é utilizada na inicialização do vetor,
o compilador copia cada um de seus bytes para o vetor e inclui o terminador
nulo ('\0') como último elemento.
Inicialização de um ponteiro com uma string literal…
char *ptr = "cabana";
Ponteiros são variáveis que recebem endereços como argumentos. Sendo assim,
o compilador copia os bytes da string literal "cabana"
e inclui o terminador
nulo na seção .rodata
(read only data) como um vetor de caracteres de 7
elementos e passa o endereço do primeiro byte para o ponteiro.
O endereço de uma string, que é um vetor, é o endereço de seu primeiro byte.
Inicializadores escalares e agregados
Ponteiros são variáveis e, portanto, requerem inicializadores escalares: no caso, especificamente endereços.
Todos esses dados são escalares e podem inicializar variáveis:
// Variável inicializada com o valor '10' (expressão constante)...
int a = 10;
// Ponteiro inicializado com valor '&a' (endereço de 'a')...
int *pa = &a;
// Ponteiro inicializado com o endereço do primeiro byte de uma string...
char *ps = "uma string literal";
Vetores, por sua vez, são estruturas que agrupam vários dados de mesmo tipo e, por isso, requerem inicializadores agregados.
Todos esses dados são agregados de valores e podem inicializar vetores…
// Vetor inicializado com uma lista de 5 valores...
int n[5] = {1, 2, 3, 4, 5};
// Vetor inicializado com 5 valores '0'...
int z[5] = {0};
// Vetor inicializado com 5 caracteres (bytes)...
char c[] = {'a', 'b', 'c', 'd', '\0'}; // Quantidade de elementos definida na compilação...
// Vetor inicializado com 7 caracteres (bytes)...
char s[] = "qwerty"; // O sétimo byte é o caractere nulo (\0 == 0x00)...
Mas, repare nessas linhas…
char *ps = "uma string literal";
char s[] = "qwerty";
As strings literais são dados agregados do tipo char *
, mas podem ser
utilizadas para inicializar tanto variáveis quanto vetores: tudo depende
do contexto…
long a = (long)"uma string"; // Endereço da string modelado para long int.
char *p = "outra string"; // O endereço da string é do tipo 'char[N bytes]'.
char s[] = "mais uma"; // O endereço da string será o endereço do vetor,
// que só recebe os caracteres como elementos.
char f[] = {'o', 'i', '\0'}; // Equivale e inicializar com "oi".
Notas:
- Por ser uma estrutura de dados, o "tipo" de um vetor é frequentemente descrito
como
TIPO[TAMANHO]
, mas isso não é um tipo da linguagem C – é apenas uma forma utilizada para expressar a ideia de um dado que é composto por uma certa quantidade de elementos de um determinado tipo. Sendo um vetor de caracteres, uma string literal na memória, por exemplo, seria descrita comochar[TAMANHO]
. - Quando atribuída a um elemento escalar, como uma variável, um ponteiro ou o
argumento de uma função, a string literal expressa o endereço de seu primeiro
byte, ou seja, o endereço do primeiro elemento de um vetor do tipo
char
.
Vetores de comprimentos variáveis (VLA)
A alocação do espaço ocupado por um vetor é feita estaticamente em tempo de compilação, o que nos impediria de declarar e inicializar vetores com uma quantidade de elementos obtida pela avaliação de uma variável ou pelo retorno de uma função, por exemplo: isso é chamado de VLA (variable-length array).
O GCC, porém, implementa o suporte a VLA na forma de uma extensão habilitada por padrão, possibilitando a declaração de vetores com número de elementos obtidos dinamicamente, mas não a sua inicialização:
int size = 10;
int a[size]; // Permitido no GCC...
int v[size] = {0}; // Proibido!
Outros compiladores podem não suportar VLAs:
Compilador | Suporte a VLAs | Observações |
---|---|---|
MSVC (cl.exe) | Não suporta | Gera erro de compilação |
GCC (Linux/Mingw-w64) | Suporta | VLAs alocados na pilha |
Clang (MinGW/WSL) | Suporta | Mesmo comportamento do Linux |
Clang-cl (modo MSVC) | Não suporta | Igual ao MSVC |