#+title: Curso Básico da Linguagem C
#+subtitle: Aula 6: Vetores
#+author: Blau Araujo
#+startup: show2levels
#+options: toc:3

* Aula 6: Vetores

[[https://youtu.be/W5TGNQYFs4E][Vídeo desta aula]]

Vetores são estruturas de dados que agrupam valores do mesmo tipo na forma
de listas. 

- Cada valor em um vetor é um /elemento/.
- O nome do vetor é o endereço do seu primeiro elemento.
- Cada elemento é identificado por um índice escrito entra colchetes após o nome.
- A representação do índice, incluindo os colchetes, é chamada de /subscrito/.
  
#+begin_quote
Embora os termos sejam intercambiáveis, um /vetor/ é, mais precisamente, uma
/matriz unidimensional/, ou seja, uma lista de valores. Já o termo /matriz/ e
mais utilizado quando nos referimos a um vetor cujos elementos são outros
vetores, o que forma uma /matriz multidimensional/. Finalmente, /array/ é só
o termo em inglês para /matriz/.
#+end_quote

** Utilidade dos vetores

Quando estamos lidando com conjuntos de dados relacionados, é preferível
trabalhar com vetores em vez de variáveis, porque isso possibilita uma
melhor organização e estruturação dos dados, além de facilitar a sua
manipulação.

*Exemplo:*

#+begin_src c
float nota1 = 7.5;
float nota2 = 9.0;
float nota3 = 8.3;
float nota4 = 8.6;

float soma = nota1 + nota2 + nota3 + nota4;
float media = soma / 4;
#+end_src

*Com vetores...*

#+begin_src c
float notas[4];

notas[0] = 7.5;
notas[1] = 9.0;
notas[2] = 8.3;
notas[3] = 8.6;

float soma = 0;
for (int i = 0; i < 4; i++) soma += notas[i];

float media = soma / 4;    
#+end_src

Nós também poderíamos inicializar o vetor na declaração:

#+begin_src c
float notas[4] = {7.5, 9.0, 8.3, 8.6};

float soma = 0;
for (int i = 0; i < 4; i++) soma += notas[i];

float media = soma / 4;    
#+end_src

As diferenças entre os exemplos com vetores e com variáveis separadas são...

*Semanticamente:*

- Uma variável expressa e dá significado a um valor.
- Um vetor expressa e dá significado a um conjunto relacionado de valores.

*Sintaticamente:*

- Para obter o valor associado à variável, nós avaliamos o nome da variável.
- Para obter o valor do primeiro elemento de um vetor, nós derreferenciamos o
  nome do vetor (=*<vetor>=).
- Para obter o endereço do valor associado a uma variável, nós usamos =&<var>=.
- Para obter o endereço do primeiro elemento do vetor, nós avaliamos o nome do
  vetor.
- A operação =&<vetor>= avalia o endereço de todo o conjunto de elementos do
  vetor.
- Para obter os valores dos demais elementos, nós somamos =*(<vetor> + índice)=.
- A linguagem C implementa uma notação mais semântica para acesso aos valores
  dos elementos na forma de um /subscrito/ para a escrita de um índice após
  o nome do vetor (=<vetor>[índice]=).

*Pragmaticamente:*

Como os índices são numéricos, nós podemos iterar os elementos através
de operações com expressões que avaliem valores compatíveis com o tipo =int=.
Isso facilita muito o acesso e a manipulação de valores, especialmente em
situações onde temos que lidar com grandes quantidades de dados.

#+begin_quote
Quantidade de dados não é, necessariamente, a mesma coisa que /volume de dados/! 
#+end_quote

** Declaração e inicialização de vetores

Para declarar um vetor, nós definimos os tipos dos seus elementos, seu nome
e um /subscrito/ (um par de colchetes) contendo a quantidade de elementos que
o vetor receberá:

#+begin_src c
float notas[4];
#+end_src

Enquanto não forem definidos, os valores nos elementos será o que houver na
quantidade de bytes reservada a partir do endereço do vetor (/lixo de memória/).

Nós também podemos inicializar um vetor na sua declaração:

#+begin_src c
float notas[4] = {7.5, 9.0, 8.3, 8.6};
#+end_src

*** Tamanho de um vetor em tempo de compilação

Quando um vetor é inicializado sem a definição da quantidade de elementos
em seu subscrito, o compilador obtém essa quantidade da lista de valores:

#+begin_src c
float notas[] = {7.5, 9.0, 8.3, 8.6}; // 4 elementos...
#+end_src

*Importante!*

Isso só vale para a inicialização de vetores nas suas declarações:

#+begin_src c
float notas[];  // Errado!
#+end_src

*** Inicialização parcial

Quando a quantidade de elementos é definida na inicialização do vetor,
a ausência de valores correspondentes na lista de elementos é preenchida
com zeros:

#+begin_src c
int num[5] = {1, 2}; // => {1, 2, 0, 0, 0}
#+end_src

O que possibilita, inclusive, inicializar um vetor /zerado/:

#+begin_src c
int num[5] = {0}; // => {0, 0, 0, 0, 0}
#+end_src

** Acesso aos elementos de um vetor

O acesso para manipulação dos elementos de um vetor é feita através
de seus índices, levando em conta que o primeiro elemento sempre
terá índice =0=:

#+begin_src c
int num_list[5];
/* ... */
num_list[0] = 23; // Definição do valor do elemento 0 (primeiro elemento).
/* ... */
num_list[4] = 19; // Alteração do elemento 4 (último elemento).
#+end_src

** Iteração pelos elementos

Nós podemos iterar os elementos de um vetor em /loops/:

#+begin_src c
#include <stdio.h>

int main() {

    int v[5] = {0};

    v[0] = 23;
    v[4] = 19;

    for (int i = 0; i < 5; i ++)
        printf("v[%d]: %d\n", i, v[i]);

    putchar('\n');

    for (int i = 0; i < 5; i ++) {
        v[i] = i + 1;
        printf("v[%d]: %d\n", i, v[i]);
    }

    return 0;
}
#+end_src

*Compilando e executando...*

#+begin_example
:~$ testes.c
:~$ a.out
v[0]: 23
v[1]: 0
v[2]: 0
v[3]: 0
v[4]: 19

v[0]: 1
v[1]: 2
v[2]: 3
v[3]: 4
v[4]: 5
#+end_example

** Vetores e funções

Vetores não podem ser passados para funções, mas os seus endereços podem:

#+begin_src c
#include <stdio.h>

void print_array(int v[], int size) {
    for (int i = 0; i < size; i++)
        printf("v[%d] => %d\n", i, v[i]);
}

int main() {

    int vetor[5];

    for (int i = 0; i < 5; i ++) vetor[i] = i + 1;

    print_array(vetor, 5);
}
#+end_src

*Compilando e executando...*

#+begin_example
:~$ testes.c
:~$ a.out
v[0] => 1
v[1] => 2
v[2] => 3
v[3] => 4
v[4] => 5
#+end_example

#+begin_quote
*Importante!* É preciso ter cuidado com a quantidade de elementos de um vetor,
pois o =gcc= não emite avisos nem erros quando tentamos ler ou escrever dados
fora dos limites do vetor, o que pode causar muitos problemas.
#+end_quote

Ainda no exemplo, observe que a função =print_array= tem o parâmetro =int v[]=, que
se parece com a notação de um vetor, mas esta é só uma sintaxe utilizada na
linguagem C para representar que o argumento esperado é o *endereço* de um valor
de determinado tipo (no caso, =int=) e que esse endereço, semanticamente, será o
de um vetor.

Essa sintaxe tem exatamente o mesmo efeito de um ponteiro como parâmetro:

#+begin_src c
void print_array(int *v, int size) {
    for (int i = 0; i < size; i++)
        printf("v[%d] => %d\n", i, v[i]); // Notação de vetor...
}
#+end_src

Isso também vale para todas as expressões que utilizem a notação de vetores,
porque =nome[índice]= é uma representação semântica que será interpretada como
=*(nome + índice)= pelo compilador.

Logo, a função também poderia ser escrita assim:

#+begin_src c
void print_array(int *v, int size) {
    for (int i = 0; i < size; i++)
        printf("v[%d] => %d\n", i, *(v + i)); // Notação de ponteiro...
}
#+end_src

*** Relação entre vetores e ponteiros

Um equívoco muito comum, é a ideia de que vetores /"decairiam"/ para
ponteiros. Isso simplesmente não é verdade: ponteiros são *variáveis* que
recebem endereços como valores, enquanto vetores são, e sempre serão,
endereços, onde quer que seus nomes sejam utilizados.

#+begin_src c
#include <stdio.h>

int main() {

    int v[4] = {0};
    int *p = v;

    printf("vetor    : %p\n", v);
    printf("&vetor   : %p\n", &v);
    printf("ponteiro : %p\n", p);
    printf("&ponteiro: %p\n", &p);

    return 0;
}
#+end_src

*Compilando e executando...*

#+begin_example
:~$ gcc testes.c
:~$ ./a.out
vetor    : 0x7fff16832290 <-- Endereço de v[0]
&vetor   : 0x7fff16832290 <-- Endereço expresso por v
ponteiro : 0x7fff16832290 <-- Valor no ponteiro (endereço expresso por v) 
&ponteiro: 0x7fff16832288 <-- Endereço do valor no ponteiro 
#+end_example

Nos parâmetros de funções, =TIPO nome[]= é uma sintaxe alternativa para
=TIPO *nome= e ambas representam a declaração de um ponteiro que receberá,
como sempre, um endereço, não uma cópia de um vetor.

Do mesmo modo, o uso da notação de vetores em expressões é apenas uma
sintaxe alternativa para operações realizadas com endereços: é por isso
que =vetor[i]= é o mesmo que =*(vetor + i)=.

** Vetores de caracteres (strings)

Strings são vetores de caracteres (cadeias de bytes) terminados com o
caractere nulo (=\0=).

Portanto...

#+begin_src c
char string[] = "banana";
#+end_src

É o mesmo que...

#+begin_src c
char string[] = {'b', 'a', 'n', 'a', 'n', 'a', '\0};
#+end_src

*** Mais diferenças entre vetores e ponteiros

Observe o exemplo...

#+begin_src c
#include <stdio.h>

int main() {

    char vstr[] = "bacana";
    char *pstr = "calado";

    printf("%p --> %s\n", vstr, vstr);
    printf("%p --> %s\n", pstr, pstr);
    
    return 0;
}
#+end_src

Essas strings são diferentes:

#+begin_src c
char vstr[] = "bacana";
char *pstr = "calado";
#+end_src

Enquanto o vetor =vstr= recebeu uma lista de caracteres, o ponteiro =pstr=
recebeu o endereço para um dado numa região do espaço de memória,
atribuído pelo sistema operacional ao programa, chamada de =.rodata=,
onde são armazenados os dados que não podem ser alterados (/read only/).

Isso pode ser verificado despejando o conteúdo do binário do programa
com o utilitário =readelf=:

#+begin_example
:~$ readelf -x .rodata a.out

Despejo máximo da secção ".rodata":
  0x00002000 01000200 63616c61 646f0025 70202d2d ....calado.%p --
  0x00002010 3e202573 0a00                       > %s..
#+end_example

Veja que apenas a string =calado= aparece nesta seção do binário.

A string =bacana=, por sua vez poderá ser encontrada na seção =.text=
do binário, que é onde nós encontramos o código da função =main=:

#+begin_example
:~$ readelf -x .text a.out

Despejo máximo da secção ".text":
  0x00001050 31ed4989 d15e4889 e24883e4 f0505445 1.I..^H..H...PTE
  0x00001060 31c031c9 488d3dce 000000ff 154f2f00 1.1.H.=......O/.
  0x00001070 00f4662e 0f1f8400 00000000 0f1f4000 ..f...........@.
  0x00001080 488d3d91 2f000048 8d058a2f 00004839 H.=./..H.../..H9
  0x00001090 f8741548 8b052e2f 00004885 c07409ff .t.H.../..H..t..
  0x000010a0 e00f1f80 00000000 c30f1f80 00000000 ................
  0x000010b0 488d3d61 2f000048 8d355a2f 00004829 H.=a/..H.5Z/..H)
  0x000010c0 fe4889f0 48c1ee3f 48c1f803 4801c648 .H..H..?H...H..H
  0x000010d0 d1fe7414 488b05fd 2e000048 85c07408 ..t.H......H..t.
  0x000010e0 ffe0660f 1f440000 c30f1f80 00000000 ..f..D..........
  0x000010f0 f30f1efa 803d1d2f 00000075 2b554883 .....=./...u+UH.
  0x00001100 3dda2e00 00004889 e5740c48 8b3dfe2e =.....H..t.H.=..
  0x00001110 0000e829 ffffffe8 64ffffff c605f52e ...)....d.......
  0x00001120 0000015d c30f1f00 c30f1f80 00000000 ...]............
  0x00001130 f30f1efa e977ffff ff554889 e54883ec .....w...UH..H..
  0x00001140 10c745f1 62616361 c745f461 6e610048 ..E.baca.E.ana.H <-- Aqui!
  0x00001150 8d05ae0e 00004889 45f8488d 55f1488d ......H.E.H.U.H.
  0x00001160 45f14889 c6488d05 9f0e0000 4889c7b8 E.H..H......H...
  0x00001170 00000000 e8b7feff ff488b55 f8488b45 .........H.U.H.E
  0x00001180 f84889c6 488d0580 0e000048 89c7b800 .H..H......H....
  0x00001190 000000e8 98feffff b8000000 00c9c3   ...............
#+end_example

#+begin_quote
As seções do binário e os segmentos do layout de memória serão assunto
de uma aula futura.
#+end_quote

As consequências disso, são:

- Os caracteres de =calado= não poderão ser alterados por aritmética
  de ponteiros.
- A tentativa de alterar dados em =.rodata= não resultará em erros
  de compilação, mas causará /falha de segmentação/ (escrita em
  segmentos não autorizados da memória).
- Enquanto o endereço em =pstr= será de um dado em =.rodata=, o endereço
  de =vstr[0]= estará na /pilha/ do espaço de memória, onde alterações
  são permitidas. 

A compilação e execução do exemplo deixa isso bem claro:

#+begin_example
:~$ gcc testes.c
:~$ a.out
0x7ffc25b8c0b1 --> bacana   <-- endereço na pilha
0x558add3cc004 --> calado   <-- endereço em .rodata
#+end_example

