Introdução à Programação B (2017/2018)

Aulas teóricas

Artur Miguel Dias



Teórica 10 (27/nov/2017)

Técnicas de processamento de ficheiros (conclusão).
Designadores de formato.
Revisão das operações básicas de atribuição e comparação.
Revisão do operador &.
Revisão do essencial sobre as funções (e mais umas pequenas novidades).
Passagem de funções como parâmetro.
Assuntos não estudados na cadeira.
Apresentação do projeto.

Técnicas de processamento de ficheiros (conclusão)

Este é um assunto da máxima importância nesta cadeira. No 2º teste e no exame vão de certeza surgir problemas parecidos com os que foram discutidos na aula anterior e no início desta.

Vamos completar a discussão do problema do final da aula anterior enriquecendo esse problema com mais um objetivo, o último desta lista:

Repare que o novo objetivo reforça a necessidade de carregar o ficheiro num vetor. Realmente, para determinar a média de golos é preciso examinar todos os dados. Só depois de se conhecer a média é que se pode começar a contar o número de jogadores que interessam.

Neste caso é impossível escrever o problema fazendo uma passagem simples pelo dados. Nem mesmo alterando a ordem da escrita das respostas, seria possível resolver este problema com uma única passagem pelos dados.

Eis uma solução para o problema global, agora com quatro objetivos:

Este exemplo encerra a discussão sobre ficheiros.



Designadores de formato

Os principais designadores de formato que podem ser usados nas funções printf, fprintf, sprintf, scanf, fscanf, sscanf são os seguintes: Uma novidade: No caso das funções printf, fprintf e sprintf, podem ser adicionados aos designadores de formato um comprimento e uma precisão, de acordo com o formato geral: Por exemplo: O comprimento indica:

A precisão indica:

Para exemplificar, a seguinte função produz o seguinte output:

Revisão das operações básicas de atribuição e comparação

Vamos relembrar a que tipos de dados as operações básicas de atribuição e comparação se podem aplicar. É importante fazer isso para ficarmos com as ideias bem sistematizadas.

Atribuição =

A operação de atribuição pode ser usada para copiar valores dos seguintes tipos:

A operação de atribuição não se aplica a:

Mas no caso das strings (um caso particular dos vetores) existe uma operação de cópia que se chama strcpy.

Se precisarmos de efetuar atribuições com vetores ou com ficheiros, temos de ser nós a programar essa operação.

Igualdade == e desigualdades !=, <, >, <=, >=

Estas operações podem ser usada para comparar valores dos seguintes tipos: Estas operações não se aplicam a:

Mas no caso das strings (um caso particular dos vetores), a função strcmp permite comparar strings.

Se precisarmos de comparar registos, vetores ou ficheiros, temos de ser nós a programar as operações.



Revisão do operador &

O operador & é usado nos argumentos da função scanf que sejam variáveis de tipo Não se usa o & quando o argumento do scanf for uma variável de tipo string.

Como vimos na teórica 8, o operador & também é usado para representar o segmento final duma string, a começar na posição de índice i:



Revisão do essencial sobre as funções (e mais umas pequenas novidades)

Na linguagem C todo o código executável está contido em funções.

Para obter um programa bem organizado e legível, o segredo está em identificar e definir quais as funções necessárias para escrever o programa. Cada função deve ser simples e resolver apenas um pequeno subproblema. Um bom programa é geralmente constituído por um grande número de funções pequenas.

Uma função pode ter qualquer número de argumentos, incluindo zero argumentos. Para indicar ausência de argumentos, usa-se o tipo void no cabeçalho da função, na zona dos argumentos.

Uma função pode ter zero ou um resultados. Para indicar ausência de resultado, usa-se o tipo void no cabeçalho da função, na zona do resultado.

Agora, duas pequenas novidades: declarações e variáveis estáticas.

Declarações

Só devemos chamar funções definidas mais atrás, no mesmo programa. Agora uma novidade: Se for indispensável fazer uma chamada para a frente, temos de declarar a função chamada antes do seu ponto da invocação. A declaração consiste simplesmente no cabeçalho da função, terminado por um ";".

O conceito de declaração é mais fraco que o conceito de definição. Uma declaração não define nada, só informa que existe uma definição noutra parte do programa, neste caso mais adiante no programa.

Veja o seguinte exemplo:

Num programa com vários ficheiros (algo com que nós não trabalhamos nesta cadeira), uma declaração também pode ser usada para informar sobre uma definição que existe noutro ficheiro.

Variáveis estáticas

Como sabemos, dentro duma função podem ser definidas diversas variáveis locais. Essas variáveis são criadas quando a função é ativada e desaparecem quando a função termina. Portanto os valores das variáveis locais perdem-se entre entre diferentes chamadas duma função.

E se for preciso usar uma variável local que não perca o seu valor entre diferentes chamadas da função? Será possível fazer tal coisa? Sim, nesse caso define-se a variável local como sendo static.

Veja o seguinte interessante exemplo que usa o gerador de números aleatórios do C. Da primeira vez que a função ao_calhas é chamada, o gerador é inicializado através duma chamada à função srand. Isso só acontece mesmo da primeira vez. Em todas as chamadas, os valores aleatórios geram-se usando a função rand.

A função anterior pode ter diversas utilizações. Pode ser usada por empresas de sondagens para escolher os entrevistados de forma aleatória. Também pode ser usada em jogos para dar aos personagens virtuais dos jogos a capacidade de tomar decisões (é verdade que essas decisões são tomadas "ao calhas", mas o que interessa é que elas sejam imprevisíveis para o jogador humano). Se você não sabia que era assim que se fazia, fica agora a saber como é...


Passagem de funções como parâmetro

Este é um tópico um pouco mais avançado que não conta para avaliação. Mas quem gostar deste mecanismo e o quiser usar, tem essa liberdade.

Nas nossas funções, estamos habituados a usar parâmetros dos mais diversos tipos nas nossas funções. Até agora já usámos parâmetros das seguintes categorias: inteiros, reais, carateres, booleanos, registos, vetores, strings e ficheiros.

No entanto, estes casos não esgotam as possibilidades da linguagem C. Há um caso muito interessante e de alguma utilidade que ainda não foi estudado, o caso em que um parâmetro é de tipo funcional (ou seja, o caso em que uma função recebe outra função como parâmetro).

Primeiro exemplo

Para usar este mecanismo, convém definir primeiro o tipo funcional em causa. Por exemplo, a seguinte definição introduz o tipo IFun que representa o conjunto de todas as funções com um argumento inteiro e com resultado inteiro. Em Matemática, o conjunto de todas as função com domínio inteiro e contradomínio inteiro costuma representar-se assim: onde Z é o conjunto de todos os números relativos.

Para exemplificar, eis duas funções de tipo IFun:

Vejamos agora um exemplo de função com argumento de tipo IFun:

A função somar recebe uma função f e dois valores inteiros a e b. Calcula o valor da seguinte expressão:

Agora, dois exemplos de chamadas da função somar:

A função testar calcula os valores das seguintes expressões:

Segundo exemplo

Consideremos o seguinte tipo, que representa pontos do espaço real a três dimensões: As seguintes três funções recebem um vetor de pontos e determinam qual o índice do ponto com x máximo, com y máximo e com z máximo. São três funções muito idênticas, porque entre elas só muda o nome do campo usado. Existe alguma forma de evitar ter tanto código praticamente repetido?

Existe sim. Temos de passar como parâmetro a indicação do campo a usar. A melhor forma de fazer isso é usando uma função de seleção do campo pretendido.

Comecemos por definir o tipo das funções que produzem um valor real a partir dum ponto:

Agora escrevemos três funções de tipo PFun para aceder aceder a cada um dos campos dum ponto:

Agora podemos escrever uma função max, completamente geral, que recebe por parâmetro a indicação do campo a usar:

Para terminar, vejamos três chamadas da função max:

Inicialmente tínhamos três funções muito parecidas e agora ficámos com 4 funções. Será que ganhámos alguma coisa?

Ganhámos sim! Três das novas funções são triviais, pois limitam-se a aceder a um campo dum ponto. A quarta função, com código mais complicado, só precisou de ser escrita uma vez, e não três vezes com variantes quase idênticas.

Terceiro exemplo

Para mostrar como é flexível o mecanismo da passagem de funções como parâmetro, vamos discutir ainda mais um caso. Vamos generalizar a função max, para obter uma única função capaz de determinar extremos em geral: por exemplo, o ponto máximo para a coordenada y, o ponto mínimo para a coordenada z, o ponto mínimo para a soma das três coordenadas, etc.

A nova função vai chamar-se extremo e vai receber como parâmetro uma função que descreve o critério de comparação a usar. Eis o tipo das funções de comparação entre pontos:

Agora vamos escrever três funções de tipo PLessFun para descrever os três critérios de ordenação referidos:

Agora a função extremo, completamente geral, que recebe por parâmetro a indicação do critério de comparação a usar: O teste final:

Assuntos não estudados na cadeira

O principal objetivo desta cadeira de Introdução à Programação é ensinar os alunos a resolver problemas pequenos usando um subconjunto rico da linguagem C.

Sobre o que não foi estudado na cadeira convém informar o seguinte:



#80