12. Estrutura de um programa em C¶
Um programa em C é organizado como uma sequência de funções e variáveis.
As funções não podem conter outras funções mas podem conter variáveis.
Qualquer variável ou função deve ser declarada antes de ser invocada.
O ponto de entrada de execução num programa é a função main (em certas circunstâncias pode ter outro nome).
A linguagem C dispõe de uma biblioteca normalizada, essencialmente contendo código de funções.
12.1. Variáveis¶
As variáveis definidas no interior de funções chamam-se variáveis locais. As outras chamam-se variáveis globais e podem ser acedidas por todas as funções. As variáveis locais também têm a designação de variáveis automaticas. São as declaradas no âmbito de um bloco (abrir e fechar chavetas). Têm existência temporária – são criadas durante a execução do bloco e descartadas à saída do bloco.
1 int x = 10;
2
3 int f(char c)
4 {
5 int g = ' ';
6 return c + g;
7 }
8
9 int main()
10 {
11 char y = 'a';
12 if (x == 2) {
13 bool z = true;
14 }
15 f(y);
16 }
No exemplo, a variável x é global, y e z são locais.
A variável z só tem existência enquanto o processamento estiver a decorrer
entre as linhas 12 e 13.
A variável y tem existência enquanto o processamento estiver a decorrer entre as linhas 10 e 16.
As variáveis são também classificadas como variáveis estáticas ou automáticas. As automáticas são as variáveis locais que são criadas e eliminadas à medida que a execução do programa entra e sai do âmbito do bloco. As variáveis estáticas são aquelas que estão permanentemente alocadas em memória – têm um tempo de vida igual ao do programa. É o caso das variáveis globais.
As variáveis locais, há dos dois tipos, as estáticas e as automáticas. As estáticas são assinaladas com a colocação da palavra static antes da definição.
void f()
{
static int i;
int j;
}
No exemplo acima, i é uma variável estática tem uma localização em memória permanenete e exclusiva, como as variáveis globais. É criada no arranque do programa, inicializada com zero. A variável j, é criada e eliminada à entrada e à saída da função f, com valor inicial indefinido.
12.2. Exemplo de programa¶
O objetivo do programa é contar as ocorrências das vogais em frases.
É baseado na função histogram_vowel que recebe a frase a analisar
e um array para recolher as contagens das vogais.
Esta função tem a seguinte declaração:
void histogram_vowel(char phrase[], uint16_t max_letters, uint16_t occurrences[5])
O primeiro parâmetro – phrase – representa um array de caracteres
com a frase a processar.
O segundo parâmetro – max_letters –, inteiro natural representado a 16 bits,
indica o número máximo de caracteres a processar.
O terceiro parâmetro – occurrences – é um array com cinco posições
de inteiros naturais, em que cada posição recebe a contagem de cada uma das vogais.
1char phrase1[] = "aeiou";
2char phrase2[] = "a ee iii oooo uuuuu";
3
4uint16_t occurrences1[SIZE];
5uint16_t occurrences2[SIZE];
6uint16_t occurrences3[SIZE];
7
8int main(void)
9{
10 histogram_vowel(phrase1, 15, occurrences1);
11 histogram_vowel(phrase2, 19, occurrences2);
12 histogram_vowel("Hello world", 7, occurrences3);
13}
Na Listagem 12.1, nas linhas 1 a 3, definem-se três frases.
São arrays de caracteres,
inicializados com o código dos caracteres que compõem as strings,
cujas dimensões são iguais às dimensões das strings mais um – o terminador '\0'.
Os arrays phrase1 e phrase2 têm a dimensão vinte
e o array phrase3 tem a dimensão seis.
Nas linhas 5 a 7, definem-se três array para receberem as contagens. Os elementos
destes arrays são do tipo inteiro natural, representados a 16 bits (uint16_t).
Em linguagem C, as variáveis de localização estática sem valor inicial explícito, são inicializadas com zero. É o caso destes três arrays, ambos, e todas as suas posições, são inicializadas com o valor zero.
O símbolo SIZE, que aparece na definição da dimensão destes arrays,
é uma macro definida de seguinte forma:
#define SIZE 5
Nas linhas 11 a 13, invoca-se a função histogram_vowel para cada uma das frases.
Na linha 11, é invocada com o array phrase1 como primeiro argumento,
o valor 18 como número máximo da caracteres a processar e por último o array ocurrencies1.
1void histogram_vowel(char phrase[], uint16_t max_letters, uint16_t occurrences[5])
2{
3 for (uint16_t i = 0; phrase[i] != '\0' && i < max_letters; i++) {
4 int16_t idx;
5 if ((idx = find_vowel(phrase[i])) != -1)
6 occurrences[idx]++;
7 }
8}
O corpo principal da função histogram_vowel consiste em iterar sobre as posições do array phrase,
a começar na primeira posição – uint16_t i = 0 –
até se encontrar o terminador da string – phrase[i] != '\0' –
ou se atingir o máximo de iterações – i < max_letters.
Em cada iteração, é invocada a função find_vowel,
que recebe o código de uma letra e a classifica.
A classificação é retornada como valor da função, na forma de um inteiro relativo,
representado a 16 bits.
Se a letra for uma vogal, é retornado o valor 0 para 'a', 1 para 'e'
e assim sucessivamente até, 4 para 'u'.
Se a letra não for uma vogal, é retornado -1.
int16_t find_vowel(char letter)
A invocação de find_vowel está embebida na expressão de afetação
idx = find_vowel(phrase[i]), cujo valor é o valor retornado por find_vowel.
Esse valor é afetado a idx e depois comparado com a constante literal -1 (linha 5).
Se for diferente é contabilizada a ocorrência de uma vogal (linha 6).
A indicação [5] na declaração do parâmetro uint16_t occurrences[5] é meramente ilustrativa.
Não tem qualquer consequência no funcionamento do programa.
Essa declaração é equivalente a uint16_t occurrences[] (sem indicação de dimensão).
A única informação passada como argumento num parâmetro do tipo array,
como phrase ou occurrences,
é o local da memória onde estão alojados os arrays.
A sub-expressão ocurrencies[idx]
dá acesso direto à posição do array utilizado como argumento de histogram_vowel.
Na invocação da linha 10 na Listagem 12.1, é o array occurrencies1.
O valor de idx situa-se entre 0 e 4.
O array utilizado como argumento deve ter pelo menos 5 posições.
Na compilação de uma função,
o compilador da linguagem C não realiza qualquer verificação
da ultrapassagem dos limites de um array passado em parâmetro,
nem ativa qualquer mecanismo para verificação em execução.
Na hipótese de o array passado como argumento em occurrences
ter uma dimensão inferior a 5,
uma escrita na posição 4 consumaria um acesso inválido à memória,
com efeito imprevisível.
- Poder-se-ão alvitrar alguns efeitos:
o endereço de memória acedido é viável e não está atribuido a outra variável – o programa tem um comportamento «normal»;
o endereço de memória acedido não é viável – o programa poderá terminar abruptamente ou o acesso ser ignorado;
o endereço de memória acedido está atribuido a outra variável – o efeito notar-se-á mais tarde, com comportamentos incompreensiveis no contexto de utilização dessa variável.
1int16_t find_vowel(char letter)
2{
3 switch (letter) {
4 case 'a':
5 return 0;
6 case 'e':
7 return 1;
8 case 'i':
9 return 2;
10 case 'o':
11 return 3;
12 case 'u':
13 return 4;
14 default:
15 return -1;
16 }
17}
|
1int16_t find_vowel(char letter)
2{
3 int16_t result = -1;
4 switch (letter) {
5 case 'a':
6 result = 0;
7 break;
8 case 'e':
9 result = 1;
10 break;
11 case 'i':
12 result = 2;
13 break;
14 case 'o':
15 result = 3;
16 break;
17 case 'u':
18 result = 4;
19 }
20 return result;
21}
|
Nas Listagem 12.3 e Listagem 12.4 a função find_vowel baseia-se na utilização de uma
estrutura switch/case.
As notações de caracteres entre plicas ' ',
representam o código numérico do caractere indicado. Por exemplo, 'a'
origina o número 97, se se utilizar a codificação ASCII.
Conforme o valor numérico em letter, a construção switch realiza um salto
para a linha case com valor numérico igual.
Na Listagem 12.3, no caso do valor numérico de letter
não estar contemplado em nenhum case o salto dá-se para default.
Em qualquer caso é executado imediatamente o retorno da função
com o valor de classificação como argumento de return.
Na Listagem 12.4, aplica-se o princípio «uma entrada - uma saída».
A variável result é inicialmente afetada com -1, o valor que representa «não vogal»,
depois será afetada com a classificação adequada no respetivo case, se for vogal.
A quebra break impede que o processamento siga para os casos seguintes.
No caso de letter não ser uma vogal a estrutura de switch não realiza qualquer processamento.
Exercício: Programar uma versão da função find_vowel que seja indiferente a letras maiúsculas e a minúsculas.
Código completo: vowels.c