#+title: Curso Básico da Linguagem C #+subtitle: Aula 7: Vetores, ponteiros e strings #+author: Blau Araujo #+startup: show2levels #+options: toc:3 * Aula 7: Vetores, ponteiros e strings [[https://youtu.be/hhySl3ClTLE][Vídeo desta aula]] #+begin_quote Parte do conteúdo desta aula foi antecipado nas [[../06-vetores/README.org][anotações da aula 6]]! #+end_quote 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...* #+begin_src c char v[] = {65, 66, 67, 68, 69, 0}; // Vetor de caracteres (bytes) char *ptr = "FGHIJ"; // Ponteiro para uma string em .rodata (read only) #+end_src *Impressão do endereço de um caractere...* #+begin_src c // 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); #+end_src *Saída do programa...* #+begin_example 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 #+end_example ** Vetores e funções Observe esta função: #+begin_src c 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; } #+end_src - 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á que =arr= 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ção =nome[í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: #+begin_src c 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; } #+end_src 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...* #+begin_src c char str[TAMANHO]; #+end_src 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...* #+begin_src c char str[] = {'b', 'a', 'c', 'a', 'n', 'a', '\0'}; #+end_src Quando o vetor é inicializado, a quantidade de elementos pode ser omitida para ser definida na compilação. #+begin_quote 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. #+end_quote *Declaração e inicialização de um vetor com uma string literal...* #+begin_src c char str[] = "bacana"; #+end_src 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...* #+begin_src c char *ptr = "cabana"; #+end_src 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. #+begin_quote O endereço de uma string, que é um vetor, é o endereço de seu primeiro byte. #+end_quote ** 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: #+begin_src c // 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"; #+end_src 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... #+begin_src c // 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)... #+end_src Mas, repare nessas linhas... #+begin_src c char *ps = "uma string literal"; char s[] = "qwerty"; #+end_src 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... #+begin_src c 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". #+end_src *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 como =char[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: #+begin_src c int size = 10; int a[size]; // Permitido no GCC... int v[size] = {0}; // Proibido! #+end_src 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 |