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

Aulas teóricas

Artur Miguel Dias



Teórica 06 (30/out/2017)

Leitura com preenchimento parcial de vetores
Definições typedef.
Registos.
Vetores de registos.

Leitura com preenchimento parcial de vetores

Na aula anterior vimos alguns exemplos de leitura de sequências de valores para dentro de vetores. Mas nesses exemplos assumia-se que o utilizador introduzia um número de elementos exatamente igual à capacidade do vetor. Ora o que acontece geralmente é que o utilizador quer introduzir um número de elementos inferior à capacidade de cada vetor, ficando o vetor parcialmente preenchido.

Eis uma forma de resolver o problema (usando um for com break), onde se assume que o utilizador introduz uma marca de fim para indicar que já não quer introduzir mais valores. Repare que agora a função retorna um inteiro para informar qual a quantidade de valores que ficaram realmente no vetor. A parte não lida do vetor fica indefinida e o conteúdo dessa parte não preenchida costuma ser coloquialmente designado de lixo.

Eis outra forma de ler um vetor com preenchimento parcial. Agora usa-se um do-while. Qualquer das duas maneiras está certa, e ainda se podia programar de outras maneiras. Mas a versão com o for parece ser a mais simples de entender.

Este estilo de leitura "até aparecer uma marca de fim" é já conhecido do problema da Mercearia da Esquina, da aula teórica anterior.



Definição de nomes de tipo - typedef

Na linguagem C, a palavra reservada typedef permite ao programador definir novos nomes de tipo. Convém que esses nomes comecem por uma letra maiúscula porque esses nomes merecem destaque num programa.

Por exemplo, a seguinte definição torna o nome Euro um sinónimo de int:

Depois de definido, o tipo Euro pode ser usado exatamente da mesma maneira do que o o tipo int, nomeadamente na definição de variáveis, parâmetros e resultados de funções, assim como em casts. Exemplos: Atenção, que uma definição typedef não cria realmente nenhum tipo novo. Em vez disso, dá apenas um novo nome a um tipo existente.

Não interessa muito usar um typedef para inventar nomes alternativos para tipos que já têm um nome, como fizemos no exemplo atrás. Realmente este exemplo de definição do tipo Euro tem pouco interesse prático pois não impede a ocorrência de misturas entre Euros e ints.

Contudo, a construção typedef revela-se extremamente útil para dar nomes a tipos que à partida não têm nome associado, como é o caso dos tipos dos vetores. Se bem usado, o typedef torna os programas mais claros.

Por exemplo, podemos dar um nome - por exemplo Notas ao tipo da matriz de notas da aula anterior. A definição fica assim:

O nome de tipo Notas pode agora ser diretamente usado na definição de variáveis e parâmetros de funções.

Sintaxe das definições typedef

A forma sintática do typedef foi concebida para ser fácil de perceber. É usada a mesma sintaxe da definição de variáveis, simplesmente com a palavra typedef adicionada atrás.

Por exemplo, a linha seguinte define uma variável chamada Notas:

Ao acrescentar atrás a palavra typedef, a linha seguinte já define um tipo chamado Notas:

É só isto! Usar o typedef é fácil!



Registos

Vamos agora mudar de assunto.

Os vetores, estudados na aula anterior, permitem agrupar dados, todos do mesmo tipo (ou seja, homogéneos).

Mas, muitas vezes, precisamos de agrupar dados de tipos diferentes (ou seja, heterogéneos). Por exemplo, podemos querer:

Chamamos registo a um agregado de dados, possivelmente heterogéneos. Num registo, o acesso aos dados é efetuado usando etiquetas, pois isso é o que se adequa melhor à natureza heterogénea dos dados (usar um índice faria menos sentido).

Os registos permitem tratar como sendo uma unidade um grupo de elementos de dados relacionados. Os registos foram inventados para ajudar o programador a organizar melhor os seus dados e a tornar os programas mais racionais e compreensíveis.

Definição dum tipo registo

Em C, a forma moderna de definir um tipo de registo utiliza sempre um typedef. É assim que iremos definir os nossos registos, nesta disciplina.

Eis a definição dum tipo registo, chamado Aluno, para representar alunos:

Na definição anterior, cada elemento de dados constituintes chama-se um campo e é identificado por uma etiqueta. Neste exemplo, os registo de tipo Aluno têm quatro campos.

Definição duma variável dum tipo registo

A definição duma variável de tipo aluno não tem nada que saber: Também é possível inicializar a variável no ponto da definição, o que é muito prático:

Acesso aos campos dum registo

Cada campo dum registo é acedido usando duma etiqueta, através do operador de seleção '.'. Por exemplo, o nome do aluno guardado na variável a é acedido usando a notação:

O acesso a um campo dum registo faz-se usando uma expressão da forma

A seguinte função, escreve na consola todos os dados dum aluno:

Modificação dos campos dum registo

Para modificar, digamos, o turno prático dum aluno a, usa-se uma atribuição assim:

Em geral, a alteração dum campo dum registo faz-se usando uma expressão da forma

A seguinte função, cria um novo registo, preenche todos os seus campos e retorna o registo preenchido:

Cópia de registos

Vimos na aula anterior que para efetuar uma cópia entre dois vetores do mesmo tipo é necessário copiar as componentes respetivas uma a uma, geralmente usando um ciclo for.

Mas a cópia entre dois registos do mesmo tipo é muito mais simples. Basta usar diretamente a instrução se atribuição, e já está!

A forma geral da cópia de registos é a seguinte:

Comparação de registos

O teste de igualdade entre registos não está predefinido na linguagem C. Para comparar registos dum dado tipo, geralmente programa-se uma função booleana semelhante a esta: Este é um exemplo duma função "pessimista" (i.e. escrita com uma atitude pessimista). A função vai à procura de todas as circunstâncias em que pode produzir false. Só depois de todos esses testes falharem é que retorna true.

Mais exemplos de tipos registo

Os seguintes exemplos mostram que os tipos registo são duma imensa utilidade. Toda a gente também concordará que é enorme a clareza que este género de definições trazem para os nossos programas!

O seguinte tipo registo permite representar números racionais representados usando frações:

O seguinte tipo registo serve para representar pontos a duas dimensões: O seguinte tipo registo permite representar parábolas, usando os coeficientes dum polinómio de 2º grau: O seguinte tipo registo permite representar círculos: O seguinte tipo registo permite representar datas de calendário: O seguinte tipo registo permite representar triângulos:

Definição de tipos registos à maneira antiga (nota histórica)

Sabemos que a linguagem C tem evoluído ao longo dos tempos. Acontece que as definições typedef não estavam disponíveis nas versões mais antigas do C e nessa altura já existiam tipos registo.

Então, como é que se definem tipos registo sem usar typedef, à maneira antiga? Vejamos um exemplo:

Para definir um tipo registo aluno à maneira antiga escreve-se assim:

Repare: não se usa a palavra typedef e escreve-se o nome do tipo à frente da palavra struct. Esta é a primeira diferença.

Segunda diferença: para definir variáveis ou argumentos, não basta escrever Aluno; é preciso escrever struct Aluno. Fora isto, tudo o resto é igual.

Veja como fica a função print_aluno:

Não usaremos na nossa cadeira essa forma antiga de definir tipos registo. Mas convém estar a par do assunto porque há livros antigos e páginas Web antigas com código C onde os tipos registo são definidos dessa maneira.



Vetores de registos

Os vetores de registos são muito usados na prática. Há inúmeros aspetos do mundo real que são representados de forma natural usando vetores de registos.

Por exemplo, pode usar-se vetores de registos para representar:

Estude demoradamente o exemplo que se segue. Você vai precisar de aprender a escrever programas como este, onde se usam vetores de registos.

Programa para ler os alunos duma cadeira e determinar o aluno com melhor média

Neste programa, uma cadeira é representado por um vetor de alunos. Estamos a pensar numa cadeira arbitrária, não necessariamente apenas na cadeira IP-B. Assume-se que a cadeira tem três fatores de avaliação e que a nota de cada aluno é a média de três notas intermédias (o caso da cadeira IP-B seria um pouco mais complicado, mas aqui estamos a tentar simplificar o enunciado do problema).

Este programa é pouco realista na medida em que pede ao utilizador a informação relativa a todos os alunos. Mais tarde, iremos aprender a ler essa informação a partir de ficheiro.

Estude este programa atentamente, porque ele incorpora muita sabedoria de programação. Em alguns exercícios das aulas práticas, você vai querer imitar este estilo.



#90