Exame de Programação Estruturada (A) 2006.06.16 1. Resposta: UMA RESOLUÇÃO (VERSÃO A) static int indicevogal(int c) { switch(c) { case A : case a : return 0; case E : case e : return 1; case I : case i : return 2; case O : case o : return 3; case U : case u : return 4; default: return -1; void frequencia(char *nomefin) { int c, n, i; struct { char vog; int num; freqabs[5] = {{ A,0, { E,0, { I,0, { O,0, { U,0; FILE *fp = fopen(nomefin,"r"); if (fp == NULL) { printf("erro na abertura do ficheiro %s\n", nomefin); exit(1); n = 0; while ((c = fgetc(fp))!= EOF) { if ((i = indicevogal(c))!= -1) freqabs[i].num++; n++; fclose(fp); if (n!= 0) { for (i=0; i<5; i++) printf("%c: %3.0f\045\n",freqabs[i].vog,(100.0*freqabs[i].num)/n); RESPOSTA ALTERNATIVA: void frequencia(char *nomefin) { int freq[256]={0, n, c; char vogal[5]={ a, e, i, o, u ; FILE *fp = fopen(nomefin,"r"); if (fp == NULL) { printf("erro na abertura do ficheiro %s\n", nomefin); exit(1);
n = 0; while ((c = fgetc(fp))!= EOF) { freq[c]++; n++; fclose(fp); if (n!= 0) { for (c=0; c<5; c++) printf("%c: %3.0f%%\n",vogal[c],(100.0*freq[(unsigned int)vogal[c]])/n); 2. Resposta: #ifndef FRAC_H #define FRAC_H #include <stdio.h> typedef struct { int num, den; FRAC; FRAC converter(int); int zero(frac); // retorna 0 se n~ao for void imprimefrac(frac); FRAC soma(frac,frac); FRAC diferenca(frac,frac); FRAC produto(frac,frac); FRAC quociente(frac,frac); 3. Resposta: #define DIGITO(D) ((D) >= 0 && (D) <= 9 ) FRAC avalia(struct arv *expr) { // assume expr!= NULL e árvore bem construída FRAC x, y; if (DIGITO(VALOR(expr))) return converter(valor(expr)- 0 ); x = avalia(esquerda(expr)); y = avalia(direita(expr)); switch (VALOR(expr)) { case * : return produto(x,y); case + : return soma(x,y); case - : return diferenca(x,y); default: if (zero(y)) { printf("erro divisao por 0\n"); exit(1); return quociente(x,y); 4. Resposta: O conteúdo de main.c #include <stdio.h> #include <stdlib.h> #include "frac.h"
#include "binarvores.h" #include "expressoes.h" int main() { struct arv *expr; char seq[30]; scanf("%s",seq); expr = constroi_arv_expr(seq); // sup~oe-se definida em expressoes.h if (expr!= NULL) { imprimefrac(avalia(expr)); putchar( \n ); return 0; O conteúdo de expressoes.h #ifndef EXPRESSOES_H #define EXPRESSOES_H #include <stdio.h> #include <stdlib.h> #include "frac.h" #include "binarvores.h" FRAC avalia(struct arv *); struct arv *constroi_arv_expr(char *); void imprime_expr(struct arv *); O ficheiro expressoes.c começaria por include "expressoes.h" e teria a seguir o código das funções avalia, constroi_arv_expr e imprime_expr, bem como de funções ou macros auxiliares, que venham a ser definidas para as implementar (como por exemplo, DIGITO(P)). O ficheiro binarvores.h teria essencialmente o que se descreve no enunciado e em binarvores.c estaria o código das funções declaradas em binarvores.h (e doutras auxiliares que eventualmente sejam definidas para as implementar). FACULTATIVO Por exemplo, em binarvores.h teria #ifndef BINARVORES_H #define BINARVORES_H #include <stdlib.h> struct arv { char v; struct arv *esq, *dir; ; #define VALOR(P) ((P) -> v); #define ESQUERDA(P) ((P) -> esq); #define DIREITA(P) ((P) -> dir); struct arv *cria_no_arv(char val,struct arv *esquerdo,struct arv *direito); struct arv *remove_no_arv(struct arv *raiz,struct arv *no); e em binarvores.c teria #include "binarvores.h" e a implementação de cria_no_arv e de remove_no_arv.
Teria ainda o ficheiro frac.c onde colocaria #include "frac.h" e o código das funções converter,...,produto, quociente declaradas em frac.h. O conteúdo do ficheiro Makefile era: progr: binarvores.o expressoes.o main.o frac.o gcc -o progr binarvores.o expressoes.o main.o frac.o frac.o: frac.c frac.h gcc -c frac.c binarvores.o: binarvores.c binarvores.h gcc -c binarvores.c expressoes.o: frac.h binarvores.h expressoes.h expressoes.c gcc -c expressoes.c main.o: frac.h binarvores.h expressoes.h main.c gcc -c main.c clean: rm *.o prog 5. (a) Resposta: typedef struct no {int prox; struct no *prox; GRUPO, *PTR_GRUPO; (b) Resposta: int i = grupos[ G - A ] -> pos; Coloca em i o índice da posição que o primeiro elemento do grupo definido pela inicial G ocupa em alunos. printf("%c\n", alunos[i][11]); Imprime o caracter G (primeiro caracter do nome do aluno atrás referido). printf("%s\n", &alunos[i][11]); Imprime o nome do aluno que está na primeira posição do grupo G. PTR_GRUPO lst = grupos[ G - A ]; Coloca em lst o endereço do primeiro nó da lista que define o grupo G. printf("%s\n", alunos[lst -> prox ->pos]); Imprime a informação disponível sobre o aluno que está na segunda posição no grupo G. (c) Resposta: static void ordena(char *alunos[],int nalunos,struct no { int i; agrupa(alunos,nalunos,grupos); for (i=0; i<26; i++) grupos[i] = ordenagrupo(grupos[i],alunos); *grupos[26])
(d) Resposta: void ordena_imprime(char *alunos[],int nalunos) { int i; PTR_GRUPO grupos[26], aux; ordena(alunos,nalunos,grupos); for(i=0; i<26; i++) { aux = grupos[i]; while(aux!= NULL) { printf("%s\n", alunos[aux -> pos]); aux = aux -> prox; (e) Resposta: static void agrupa(char *alunos[],int nalunos, PTR_GRUPO grupos[26]) { int i; for (i=0; i < 26; i++) grupos[i] = NULL; for (i=0; i < nalunos; i++) grupos[alunos[i][11]- A ] = cria_no(i,grupos[alunos[i][11]- A ]); static PTR_GRUPO cria_no(int i, PTR_GRUPO g) { PTR_GRUPO novo = malloc(sizeof(grupo)); if (novo!= NULL) { novo -> pos = i; novo -> prox = g; return novo; printf("sem memoria\n"); exit(1); (f) Resposta: Por adaptação do algoritmo quicksort dado na disciplina. static PTR_GRUPO ordenagrupo(ptr_grupo lstg,char *alunos[]) { PTR_GRUPO lmenor, lmaior_ig; if (lstg == NULL) return NULL; parte(lstg -> prox, lstg -> pos, &lmenor, &lmaior_ig,alunos); lmenor = ordenagrupo(lmenor,alunos); lmaior_ig = ordenagrupo(lmaior_ig,alunos); lstg -> prox = lmaior_ig; return concatena(lmenor,lstg); static void parte(ptr_grupo g,int i,ptr_grupo *lm,ptr_grupo *lmi,char *als[]) { *lm = *lmi = NULL; PTR_GRUPO aux; while(g!= NULL) { aux = g -> prox; if (ordena_par(als[g->pos],als[i]) < 0) { g -> prox = *lm; *lm = g; else { g -> prox = *lmi; *lmi = g; g = aux;
PTR_GRUPO concatena(ptr_grupo lst1, PTR_GRUPO lst2) { if (lst1 == NULL) return lst2; if (lst2 == NULL) return lst1; lst1 -> prox = concatena(lst1 ->prox, lst2); return lst1; (g) Resposta: Colocar #include <string.h> no cabeçalho do módulo. // #define POR_NOMES #ifdef POR_NOMES int ordena_par(char *x,char *y) { // por nomes (crescente) return strcmp(x+11,y+11); // ou return strcmp(&x[11],&y[11]); #else int ordena_par(char *x,char *y) { // por codigos (decrescente) int rescomp; x[10] = y[10] = \0 ; rescomp = strcmp(x,y); x[10] = y[10] = ; return -rescomp; As duas versões ficam protegidas da forma indicada atrás por directivas para o préprocessador: #ifdef POR_NOMES... #else.... Se for retirado o comentário de // #define POR_NOMES e compilado o programa, a ordenação será por ordem lexicográfica crescente de nomes. Caso contrário, é por ordem decrescente de códigos. Em alternativa, para evitar recompilar o programa e/ou para poder efectuar ordenações por vários critérios no mesmo programa, poder-se-ia ter colocado mais um parâmetro nas funções ordenagrupo, parte, ordena e ordena_imprime, para passar a ter (um apontador para) a função de ordenação como parâmetro dessas funções. Por exemplo, void ordena_imprime(char *alunos[],int nalunos,int (*ordlin)(char*, char*)) A função parte passaria a ser declarada por static void parte(ptr_grupo g,int i,ptr_grupo *lm,ptr_grupo *lmi, char *als[], int (*ordlin)(char*, char*)) e, na sua definição, substituiriamos a instrução por if (ordena_par(als[g->pos],als[i]) < 0) { if (ordlin(als[g->pos],als[i]) < 0) { Deste modo, é possível definir vários critérios para ordenar duas linhas (implementados por funções com nomes distintos) e especificar em cada chamada de ordena_imprime qual a função que fará a ordenação de duas linhas. Por exemplo, se as funções que implementam os dois critérios acima tratados passassem a ser designadas por ordena1 e ordena2, podiamos chamar consecutivamente: ordena_imprime(alunos,nalunos,ordena1); ordena_imprime(alunos,nalunos,ordena2); (FIM)