Resolução de Problemas Usando a Linguagem C
Neste documento apresentam-se sugestões e regras que o autor julga fazerem sentido mas que estão, em grande medida, sujeitas a discussão. O bom senso ditará quais aplicar e quais (e quando) por em causa. O documento está dividido em três secções. A primeira é sobre resolução de problemas em geral. A segunda sobre resolução de problemas usando linguagens de programação. A terceira sobre resolução de problemas usando a linguagem C. Deve se lido em sequência. O estilo é propositadamente sintético.
Ah! E o documento ainda não está pronto...
No que segue abaixo "ferramenta" é qualquer instrumento, real ou abstracto, que pode ser usado para atingir resultados definidos. No caso da programação, "ferramenta" pode significar uma classe, um objecto, um módulo, uma função, uma estrutura de dados, um pedaço de código, uma macro (em C), um algoritmo, um programa, etc.
Isto é, quando justificável, misture um pouco de abordagem ascendente na abordagem descendente (ver acetatos de Programação I).
Resolver um problema usando a abordagem ascendente corresponde a complementar ("artilhar") com novas ferramentas (estruturar de dados, funções, módulos, etc.) a "caixa de ferramentas" que possui (a linguagem em uso e respectivas bibliotecas) antes de passar à resolução propriamente dita, com as ferramentas disponíveis..
Encapsular significa ocultar os pormenores de implementação duma ferramenta do seu utilizador. O encapsulamento também é conhecido por ocultação de dados ou informação.
Na metáfora do relógio você tem um papel aparentemente ambíguo: umas vezes é o construtor (2., 3. e 4.) e outras o utilizador (1.). Esta duplicidade de papeis acompanhá-lo-á sempre ao longo da sua vida de programador: as ferramentas que constroi num dia utiliza no outro. Não esqueça, no entanto, que jamais trabalhará sozinho: fará sempre parte duma equipa. Outros humanos utilizarão as ferramentas que construir. Lembre-se sempre disso. Proteja-se dos erros dos outros ou dos seus próprios, agindo em cada instante como construtor ou como utilizador, conforme apropriado. Não misture os papeis.
A ocultação de dados ou de informação (como sugerido acima) tem várias vantagens:
Na Secção 1 falou-se de técnicas de resolução de problemas. Não se especificou a índole dos problemas a resolver nem os recursos a usar na sua resolução. Assim, os conselhos dados são aplicáveis a (quase) qualquer tipo de problemas, em (quase) qualquer circunstância, com (quase) quaisquer recursos.
Nesta secção ser-se-á um pouco mais específico: os recursos a usar são um computador e uma linguagem de programação e os problemas são os que se podem resolver com estes recursos (a classe de problemas resolúvel com um computador e uma linguagem é bastante maior do que parece: lembre-se que ainda está em discussão se tais recursos são suficientes para emular a inteligência humana). Os exemplos serão dados na linguagem C, embora os conselhos sejam aplicáveis a qualquer linguagem de programação, ou melhor, a qualquer linguagem de programação imperativa, conceito que aprenderá mais tarde. Algumas notas, identificadas com [OO], referem-se unicamente a linguagens orientadas para objectos, sendo nesse caso as linguagens Java ou C++ usadas para exemplificar.
Muitos alunos não têm dificuldade em perceber o problema a resolver nem em propor uma boa solução. A maior dificuldade está em formalizar a solução numa linguagem não natural: a linguagem de programação. Lembre-se que, numa linguagem de programação (pelo menos nas imperativas e procedurais):
Exemplo: double sin(double)
Exemplo: int fscanf(FILE *, const char *, ...)
Uma linguagem serve para comunicar. Uma linguagem de programação serve para comunicar, por ordem de importância:
O ênfase deve ser posto na legibilidade e clareza:
A clareza dum programa depende de dois factores que, ironicamente, são irrelevantes para o compilador: os nomes dos objectos usados e a disposição do texto.
Use nomes apropriados para variáveis, funções e procedimentos ([OO] e/ou métodos), tipos, etc. Nas regras gerais abaixo, uma "contracção" consiste, normalmente, na eliminação de artigos, conjunções e preposições. Tenha, no entanto, cuidado com as contrações e abreviações: será que outros, ou você próprio daqui a uns meses, as percebem? Por complementos entende-se os complementos directo e indirecto duma frase.
Exemplos:
void mostraMenuDeAbertura(void);
#define COMPRIMENTO_MAXIMO_DOS_NOMES 200
Exemplos:
char nomeDoUtilizador[20]; /* a variável 'nomedoUtilizador' guarda o nome do utilizador. */
int numeroDeAlunos; /* a variável 'numeroDeAlunos' guarda o número de alunos. */
int numeroDeAlunosNaTurma; /* a variável 'numeroDeAlunosNaTurma' guarda o número de alunos na turma. */
#define COMPRIMENTO_MAXIMO_DOS_NOMES /* a constante 'COMPRIMENTO_MAXIMO_DOS_NOMES' representa o comprimento máximo de qualquer nome. */
Exemplos:
Logico haMaior; /* a variável 'haMaior' indica se há um elemento maior na lista; if(haMaior) lê-se "se há maior...(na lista)" (neste caso admite-se que o complemento "na lista" é dedutível pelo contexto.) */
Exemplo:
typedef struct /* Lista */ { long comprimento; /* "da lista" está implícito. */ Elemento *primeiroElemento; /* idem. */ } Lista;
Nota: Em C é de toda a conveniência usar typedef para dar nomes curtos (sem o qualificador struct) às estruturas.
Exemplo [OO] Java:
class Lista { private class Elemento { /* "duma lista" está implícito. */ ... } private long comprimento; /* "da lista" está implícito. */ private Elemento *primeiroElemento; /* idem */ }
Exemplos:
size_t comprimento(const char *cadeia); /* a função 'comprimento' calcula o comprimento duma cadeia. */
Exemplos:
Logico LSTvazia(Lista l); /* a função 'LSTvazia()' verifica se a lista l está vazia. */
Exemplo:
LSTinsere(Lista l, Ldados d); /* o procedimento 'LSTinsere()' insere os dados 'd' na lista 'l'. */
Exemplo Java:
class Forma2D { public double área(); /* calcula área ("da forma 2D" está implícito). */ }
Exemplo Java:
class Forma2D { public double roda(double ângulo); /* roda ("a forma 2D") de "ângulo" graus. */ }
Exemplo em português:
"Ir para casa e ver televisão"
-> "Ir para casa. Ver televisão"
(i.e., use frases curtas)
Exemplo:
CAD...(...) /* funções que lidam com cadeias. */
LST...(...) /* funções que lidam com listas. */
Estes prefixos, para além de facilitarem a leitura dos programas, evitam a colisão de nomes. Por exemplo, se não existissem prefixos num programa lidando com cadeias e com listas como distinguir a função que calcula o comprimento duma cadeia daquela que calcula o comprimento duma lista?
Os prefixos devem ser usados também em variáveis globais, contantes, tipos, enumerações, etc. declarados/definidos no ficheiro de cabeçalho do módulo.
[OO] Nas linguagens orientadas para objectos muitos destes problemas de colisão de nomes não se põem ou estão simplificados. Assim, as funções em C que lidam com listas, por exemplo, devem ser precedidas dum prefixo identificador, tal como LST. Em C++ e Java, pelo contrário, tal prefixo é indesejável, pois as funções (métodos neste caso) pertencem à classe Lista, sendo esta pertença evidente pela forma como se invocam (v.g., Lista lista; ...lista.comprimento()...).
Faltam tipos, estruturas, classes, etc.
Indentação: será para indicar frases subordinadas?
Não usar mais do que três ou quatro níveis de indentação. Indício de má estruturação de funções.
Recomendar um de dois estilos de indentação.
Recomendar espaços em branco entre operadores e operandos. Mas não entre operandos e () ou [].
Recomendar linhas em branco onde clarificar o código. Sempre entre declarações e intruções. Excepto nas trocas e casos semelhantes.
Linhas em branco antes de comentários de bloco.
Falar dos vários estilos de comentário. Dizer que funcionam: como prólogos ou introduções, e como apartes ou explicações ou parênteses (embebidos).
Falar dos contratos da função: pressupostos nos argumentos -> resultados (pré-condições e pós-condições). Comentários com essa preocupação.
Suponha o seguinte programa. Que faz ele?
#include <stdio.h> #include <stdlib.h> typedef struct { int n, d; } fr; int mdc(int v1, int v2) { while(v2 != 0) { int aux = v2; v2 = v1 % v2; v1 = aux; } return v1 == 0 ? 1 : v1; } fr red(fr v) { int div = mdc(v.n, v.d); v.n /= div; v.d /= div; return v; } fr le(void) { fr v; if(scanf("%d/%d", &v.n, &v.d) != 2) exit(EXIT_FAILURE); return v; } void escr(fr v) { printf("%d/%d", v.n, v.d); } int main(void) { fr v = le(); v = red(v); escr(v); return EXIT_SUCCESS; }
Teve dificuldade em seguir? Experimente a versão seguinte:
#include <stdio.h> #include <stdlib.h>
typedef struct { int numerador, denominador; } fraccao;
/* * Pressupostos: Nenhum. * Resultado: Devolve o maximoDivisorComum dos argumentos (se * forem ambos nulos devolve 1). */ int maximoDivisorComum(int valor1, int valor2) { while(valor2 != 0) { int valorAuxiliar = valor2; valor2 = valor1 % valor2; valor1 = valorAuxiliar; } return valor1 == 0 ? 1 : valor1; }
/* * Pressupostos: Nenhum. * Resultado: Devolve a versao reduzida da fraccao passada * como argumento. */ fraccao reduzida(fraccao valor) { int divisor = maximoDivisorComum(valor.numerador, valor.denominador); valor.numerador /= divisor; valor.denominador /= divisor;
return valor; }
/* * Pressupostos: Tem de existir dois inteiros separados por * '/' na entrada. De outra forma o programa aborta. * Resultado: Devolve uma fraccao lida da entrada. */ fraccao le(void) { fraccao valor; if(scanf("%d/%d", &valor.numerador, &valor.denominador) != 2) exit(EXIT_FAILURE);
return valor; }
/* * Pressupostos: Nenhum. * Resultado: Aparece no ecra a representacao da fraccao passada * como argumento. */ void escreve(fraccao valor) { printf("%d/%d", valor.numerador, valor.denominador); }
/* * Pressupostos: Tem de existir dois inteiros separados por * '/' na entrada. De outra forma o programa aborta. * Resultado: Aparece no ecra a representacao reduzida duma * fraccao dada pelo utilizador (lida da entrada). */ int main(void) { fraccao valor = le(); valor = reduzida(valor); escreve(valor);
return EXIT_SUCCESS; }
Mais fácil, não? Isso deve-se a que, apesar de estruturadas da mesma forma, a primeira versão não segue as orientações dadas neste documento, enquanto a segunda segue.
A completar..
Página
concebida e mantida por Eng. Manuel Menezes de Sequeira (última actualização 2006/07/07) Copyright © 1996-2001 ISCTE |