Tipos Abstratos de Dados
Nos nossos projetos, vamos notar que vamos tentar usar as mesmas estruturas com tipos de dados diferentes: pensando em listas ligadas, por exemplo, queremos que a sua implementação seja idealmente independente do tipo de dados com que estamos a trabalhar. Para isso, implementamos abstrações no nosso código, com vista a que os detalhes de implementação não sejam dependentes de um só tipo de dados. Cada operação deve ser uma espécie de "módulo", reutilizável por qualquer tipo.
São acima de tudo soluções elegantes que nos permitem separar o alto do baixo nível: as interfaces (vulgo cabeçalhos de funções) com que interagimos para efetuar operações está separada da manutenção da implementação.
Por norma, e para manter camadas de abstração (estruturas de dados diferentes não têm de conhecer a implementação de outras estruturas), costumamos implementar cada um destes tipos abstratos de dados em ficheiros diferentes, que funcionam como módulos. Cada módulo deve incluir a especificação e documentação do tipo de dados que o acompanha, bem como um conjunto de funcionalidades bem definido e coerente.
Geralmente, os módulos andam "aos pares": um módulo costuma ser composto por um ficheiro .h
, header file, onde consta a documentação e especificação das operações do módulo, bem como possíveis definições de constantes que sejam necessárias para a implementação da estrutura em questão e a própria definição da estrutura. O .c
irá conter o source code, a implementação em si. Um módulo pode, claro, ser dividido em vários ficheiros (pode dar jeito para uma maior separação de "propósitos", por exemplo).
Nos slides costumam constar implementações de filas e pilhas recorrendo a modelos abstratos.
Regras de Scope
Em C, todas as variáveis têm um scope, modo de acessibilidade e visibilidade. Podem ter 3 estados:
-
Bloco - variáveis locais;
-
Ficheiro - variáveis globais, apenas visíveis dentro do ficheiro onde são declaradas;
-
Programa - variáveis globais visíveis em múltiplos ficheiros.
Block Scope
Variáveis apenas visíveis dentro do scope (delimitada por { }
) em que foram declaradas. Existem ainda variáveis static
, que apesar de continuarem a ser apenas visíveis dentro do respetivo scope, são inicalizadas apenas uma vez no decorrer do programa. Isto é, se a função passar duas vezes por um dado bloco, a variável na segunda passagem inicia com o valor que terminou a primeira passagem, não com o valor de inicialização.
int soma(int v[], int n) {
int i;
static int soma = 0;
for (i = 0; i < n; i++) {
soma += v[i];
}
return soma;
}
Variáveis Globais
Variáveis visíveis em qualquer função, já que não têm um scope associado por definição. Podem ser utilizadas por qualquer função.
int acumulador;
void soma(int valor) {
acumulador += valor;
}
Podemos definir variáveis globais como estáticas, por forma a limitar o seu scope ao ficheiro em que estão definidas. Mais ainda, também podemos definir funções estáticas: funções que só podem ser chamadas por outras funções que sejam definidas até ao fim do ficheiro em que ela foi.
static int acumulador;
static void soma(int valor) {
acumulador += valor;
}
// mais funções, que podem ou não chamar soma