pi1415 tudo
TRANSCRIPT
![Page 1: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/1.jpg)
Programação Imperativa
Lição n.º 1 Preliminares
![Page 2: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/2.jpg)
Preliminares • Apresentação. • A programação na LEI. • O que é um computador? • O que é um programa? • Linguagens de programação. • A linguagem de programação C. • Bibliografia.
18/12/14 Programação Imperativa 2
![Page 3: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/3.jpg)
Apresentação • Aulas teóricas às segundas-feiras, das 9:00 às
10:00 e às quintas-feiras, das 8:30 às 9:30, no anfiteatro 1.8.1, no edifício 8.
• Aulas práticas para várias turmas. • Professor das teóricas: Pedro Guerreiro. • Professoras das práticas: Margarida Madeira,
Noélia Correia e Cristina Vieira. • Avaliação ao longo do funcionamento e exame
final. • Página na tutoria: http://goo.gl/1B56WO.
18/12/14 Programação Imperativa 3
![Page 4: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/4.jpg)
A programação na LEI • Programação Imperativa. • Laboratório de Programação. • Programação Orientada por Objetos. • Algoritmos e Estruturas de Dados. • Bases de Dados. • Computação Gráfica. • Desenvolvimento de Aplicações para a Web. • Compiladores. • Inteligência Artificial. • ... 18/12/14 Programação Imperativa 4
![Page 5: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/5.jpg)
A programação na LEI • Programação Imperativa. • Laboratório de Programação. • Programação Orientada por Objetos. • Algoritmos e Estruturas de Dados. • Bases de Dados. • Computação Gráfica. • Desenvolvimento de Aplicações para a Web. • Compiladores. • Inteligência Artificial. • ... 18/12/14 Programação Imperativa 5
![Page 6: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/6.jpg)
O que é um computador?
18/12/14 Programação Imperativa 6
ENIAC (1946)
UNIVAC I (1951)
![Page 7: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/7.jpg)
O que é um computador? (2)
18/12/14 Programação Imperativa 7
IBM 360 (1965)
DG Eclipse MV/8000 (1980) DEC VAX-11/780 (1978)
PDP 11/70 (1975)
![Page 8: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/8.jpg)
O que é um computador? (3)
18/12/14 Programação Imperativa 8
IBM PC 5150 (12 de Agosto de 1981)
Apple Macintosh (24 de Janeiro de 1984)
![Page 9: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/9.jpg)
O que é um computador? (4)
18/12/14 Programação Imperativa 9
Computador “torre” Computador “laptop”
![Page 10: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/10.jpg)
O que é um computador? (5)
18/12/14 Programação Imperativa 10
Computador “torre” Computador “laptop”
Surface Pro 3 e Macbook Air
![Page 11: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/11.jpg)
E ainda...
18/12/14 Programação Imperativa 11
Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994).
I'm sorry, Dave. I'm afraid I can't do that..
HAL 9000
![Page 12: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/12.jpg)
“Definição” de computador
A device that consists of one or more associated processing units and peripheral units, that is controlled by internally stored programs, and that can perform substantial computations, including numerous arithmetic operations, or logic operations, without human intervention during a run.
18/12/14 Programação Imperativa 12
Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994).
![Page 13: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/13.jpg)
O que é um programa? • Um programa é uma sequência de instruções
que um computador executará automatica-mente, para levar a cabo uma determinada tarefa.
• As instruções são executadas sequencial-mente, primeiro a primeira instrução do programa, depois a segunda, e assim por diante até ao fim do programa, exceto no caso das instruções de salto, as quais permitem “saltar” (condicionalmente ou não) para outra instrução, mais à frente ou mais atrás.
18/12/14 Programação Imperativa 13
![Page 14: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/14.jpg)
Como são os programas? • Os programas são texto, isto é, sequências de
frases, formadas por palavras, formadas por carateres.
• Os programas são escritos por pessoas ou por outros programas.
• Cada programa é escrito numa linguagem de programação.
• Os compiladores são programas que traduzem um programa escrito numa linguagem para outra linguagem que o computador é capaz de processar mais eficientemente.
18/12/14 Programação Imperativa 14
![Page 15: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/15.jpg)
Programação imperativa • A programação imperativa é um estilo de
programação que reflete a ideia fundamental de que as instruções constituem ordens que o computador deve cumprir: read, write, call, stop, wait, add, connect, perform, etc.
• À programação imperativa contrapõe-se a programação funcional, para a qual um programa é a descrição de uma função (no sentido da matemática); executar o programa é avaliar a função para argumentos dados, a fim de obter os correspondentes resultados.
18/12/14 Programação Imperativa 15
![Page 16: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/16.jpg)
Linguagens de programação • Os programas são escritos usando linguagens
de programação. • Cada linguagem de programação é um
conjunto de regras definidas inequivocamente num documento de referência.
• Há regras sintáticas (que exprimem as maneiras válidas de escrever programas) e regras semânticas (que exprimem o significado operacional dos programas).
• Há ainda regras de estilo, peculiares de cada organização.
18/12/14 Programação Imperativa 16
![Page 17: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/17.jpg)
Principais linguagens
18/12/14 Programação Imperativa 17
![Page 18: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/18.jpg)
A linguagem de programação C • Em Programação Imperativa programaremos
em C. • A linguagem C foi inventada por
Dennis Ritchie, nos Laboratórios Bell, em 1972.
• A linguagem C provém da linguagem B, a qual provinha da linguagem BCPL, a qual provinha da linguagem CPL, a qual provinha do Algol 60.
• A linguagem C influenciou diretamente as linguagens C++, Java, Objective C, C# e, mais ou menos diretamente, muitas outras.
18/12/14 Programação Imperativa 18
![Page 19: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/19.jpg)
Evolução do C • 1972: invenção do C. • 1989: normalização ANSI C, ou C89. • 1990: normalização ISO C, ou C90, igual à
anterior. • 1999: normalização ISO, C99. • 2011: normalização ISO, C11.
18/12/14 Programação Imperativa 19
![Page 20: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/20.jpg)
Bibliografia
18/12/14 Programação Imperativa 20
![Page 21: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/21.jpg)
Programação Imperativa
Lição n.º 2 Programação com C
![Page 22: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/22.jpg)
Programação com C • Problemas de programação. • Decomposição funcional. • Funções em C. • Funções de teste.
18/12/14 Programação Imperativa 22
![Page 23: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/23.jpg)
Problemas de programação • Tipicamente, a tarefa de um programador é
escrever programas para realizar determinadas tarefa, ou para resolver determinados problemas.
• Problema de hoje: escrever um programa C para calcular a nota final em Programação Imperativa, dada a nota da parte prática e a nota do exame.
18/12/14 Programação Imperativa 23
![Page 24: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/24.jpg)
Problema da nota final • A nota final é a média ponderada da nota da
parte prática e da nota do exame, com pesos 30% e 70%, respetivamente.
• Mas se a nota do exame for menor que 8.5, a nota final é a nota do exame.
• As notas são expressas na escala de 0 a 20. • A notas da parte prática e do exame são
expressas com uma casa decimal. • A nota final é expressa na forma de um
número inteiro, obtido por arredondamento do resultado dos cálculos.
18/12/14 Programação Imperativa 24
![Page 25: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/25.jpg)
Funções identificadas no enunciado • A função para a média ponderada da nota da
parte prática e da nota do exame. • A função que se ocupa do caso em que a nota
do exame é menor do que 8.5. • A função que arredonda (para o número
inteiro mais próximo) o resultado dos cálculos. (Esta é um exemplo das tais funções gerais)
18/12/14 Programação Imperativa 25
Esta é um caso das tais funções gerais.
![Page 26: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/26.jpg)
Ambiente de programação • Programaremos escrevendo os nossos
programas num editor de texto e compilando numa janela de comando.
• Teremos numa janela o editor e noutra a janela de comando:
18/12/14 Programação Imperativa 26
A janela de comando está colocada na diretoria onde guardamos os programas.
![Page 27: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/27.jpg)
Média ponderada • Se x representar a nota da prática e y a nota
do exame, a média ponderada desses dois valores, usando os pesos 30% e 70%, é dada pela expressão x * 0.3 + y * 0.7.
• As variáveis x e y denotam números reais, com parte decimal.
• Em C, os números reais são representados pelo tipo double.
• A função para a média ponderada terá dois argumentos de tipo double e o resultado também é de tipo double.
18/12/14 Programação Imperativa 27
![Page 28: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/28.jpg)
Função weighted_average • Observe:
• Usamos aqueles nomes lab e exam para deixar
claro o significado dos argumentos.
18/12/14 Programação Imperativa 28
double weighted_average(double lab, double exam) { return lab * 0.3 + exam * 0.7; }
O compilador dá erro, indicando que o programa não tem uma função main.
![Page 29: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/29.jpg)
Função de teste • Escrevamos uma função de teste para
exercitar a função weighted_average:
18/12/14 Programação Imperativa 29
void test_weighted_average(void) { double lb; double ex; scanf("%lf%lf", &lb, &ex); double z = weighted_average(lb, ex); printf("%f\n", z); }
O compilador continuaria a dar erro, porque continua a faltar a função main.
![Page 30: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/30.jpg)
Função main • A função main chama a função de teste:
18/12/14 Programação Imperativa 30
int main(void) { test_weighted_average(); return 0; }
O compilador dá outro erro agora (na verdade, trata-se de um warning...) e sugere que incluamos o “header” <stdio.h>.
![Page 31: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/31.jpg)
Programa completo
18/12/14 Programação Imperativa 31
#include <stdio.h> double weighted_average(double lab, double exam) { return lab * 0.3 + exam * 0.7; } void test_weighted_average(void) { double lb; double ex; scanf("%lf%lf", &lb, &ex); double z = weighted_average(lb, ex); printf("%f\n", z); } int main(void) { test_weighted_average(); return 0; }
Este é o programa completo. Tem uma função de cálculo, uma função de teste e a função main. À cabeça vem a diretiva #include <stdio.h>.
![Page 32: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/32.jpg)
Experimentando • Compilamos e corremos na janela de comando:
18/12/14 Programação Imperativa 32
sources pedro$ gcc -Wall nota_final.c sources pedro$ ./a.out 10 12 11.400000 sources pedro$ ./a.out 15 18 17.100000 sources pedro$ ./a.out 17.2 14.5 15.310000 sources pedro$ ./a.out 14.8 7.1 9.410000 sources pedro$
De cada vez que corremos o programa, só fazemos uma experiência.
![Page 33: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/33.jpg)
void test_weighted_average(void) { double lb; double ex; scanf("%lf%lf", &lb, &ex); double z = weighted_average(lb, ex); printf("%f\n", z); test_weighted_average(); }
Experimentando repetidamente • É simples: depois de escrever o resultado,
chamamos a função de teste, de novo:
18/12/14 Programação Imperativa 33
sources pedro$ ./a.out 12.9 10.0 10.870000 8.5 12.7 11.440000 14.8 12.0 12.840000 ^C sources pedro$
Paramos o programa, interrompendo-o, com ctrl-C.
![Page 34: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/34.jpg)
Experimentando repetidamente, melhor • Em vez de interromper o programa à bruta,
com ctrl-C, é melhor deixar o programa seguir quando acabarem os dados.
• Neste caso, o programa seguirá, mas como não há mais nada que fazer, terminará imediatamente.
• O fim dos dados é assinalado com ctrl-Z em Windows e com ctrl-D em Linux/MacOS.
• O ctrl-C é usado para interromper um programa que disparatou ou um programa que chamámos por engano, não para fazer um programa terminar normalmente.
18/12/14 Programação Imperativa 34
![Page 35: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/35.jpg)
Ciclo de teste • Observe com muita atenção:
18/12/14 Programação Imperativa 35
void test_weighted_average(void) { double lb; double ex; while (scanf("%lf%lf", &lb, &ex) != EOF) { double z = weighted_average(lb, ex); printf("%f\n", z); } }
sources pedro$ ./a.out 12 15 14.100000 13.8 19.0 17.440000 10.1 19.9 16.960000 sources pedro$
Eu terei dado ctrl-D para assinalar o fim dos dados, mas o ctrl-D não é ecoado.
![Page 36: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/36.jpg)
Função da nota exata • A média ponderada nem sempre dá a nota; só
dá quando a nota do exame é maior ou igual a 8.5
• Caso contrário, o resultado é a nota do exame.
• Observe:
18/12/14 Programação Imperativa 36
double grade(double lab, double exam) { return exam >= 8.5 ? weighted_average(lab, exam) : exam; }
Atenção aos operadores ponto de interrogação e dois pontos!
![Page 37: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/37.jpg)
Função de teste para a nota exata • Para controlo, incluímos também uma
chamada à função weighted_average:
18/12/14 Programação Imperativa 37
void test_grade(void) { double lb; double ex; while (scanf("%lf%lf", &lb, &ex) != EOF) { double v = weighted_average(lb, ex); printf("%f\n", v); double z = grade(lb, ex); printf("%f\n", z); } }
![Page 38: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/38.jpg)
A nova função main • A função main chama agora a nova função de
teste. • A anterior função de teste continua lá, mas
comentada:
18/12/14 Programação Imperativa 38
int main(void) { // test_weighted_average(); test_grade(); return 0; }
Tipicamente, as funções main são assim: chamam uma de várias funções de teste, estando as outras comentadas, para poderem facilmente ser reativadas, se necessário.
![Page 39: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/39.jpg)
Experimentando a nota exata • Eis uma sessão de
experimentação, usando a nova função main:
18/12/14 Programação Imperativa 39
sources pedro$ ./a.out 14 10 11.200000 11.200000 16 8 10.400000 8.000000 16 8.4 10.680000 8.400000 16 8.5 10.750000 10.750000 19 6.2 10.040000 6.200000 sources pedro$
Confirmamos que nos casos em que a nota do exame é menor que 8.5, as duas funções dão resultados diferentes.
![Page 40: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/40.jpg)
Conclusão • Já conseguimos calcular a nota exata, isto é, a
nota calculada com toda a precisão. • Falta calcular a nota final, arredondada. • Note que as funções presume, que os valores
dos argumentos fazem sentido, isto é, que são números reais entre 0.0 e 20.0., expressos com uma casa decimal, mas o programa não controla isso, e calcula cegamente.
• Aliás, se na função de teste fornecermos “lixo”, isto é, sequências de carateres que não constituem números decimais, o programa estoira ingloriamente.
18/12/14 Programação Imperativa 40
![Page 41: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/41.jpg)
Programação Imperativa
Lição n.º 3 Operações aritméticas
![Page 42: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/42.jpg)
Programação com C • Aritmética em C. • Aritmética int. • Aritmética double. • Aritmética mista. • Funções matemáticas de biblioteca. • Funções max e min.
18/12/14 Programação Imperativa 42
![Page 43: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/43.jpg)
Aritmética em C • As regras da aritmética do C são semelhantes
às da aritmética da matemática, que aprendemos na escola primária.
• Mas há diferenças subtis, que frequentemente nos apanham desprevenidos.
• Primeira observação importante: os números inteiros são representados pelo tipo int, mas o tipo int não representa todos os números inteiros!
• Só representa os número inteiros do intervalo [-2147483648..2147483647].
18/12/14 Programação Imperativa 43
![Page 44: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/44.jpg)
Testando a adição de ints • Eis um programa com uma função de teste que faz
repetidamente a adição de dois números int:
18/12/14 Programação Imperativa 44
#include <stdio.h> void test_addition(void) { int x; int y; while (scanf("%d%d", &x, &y) != EOF) { int z = x + y; printf("%d\n", z); } } int main(void) { test_addition(); return 0; }
sources pedro$ ./a.out 3 7 10 3000 7000 10000 3000000 7000000 10000000 3000000000 7000000000 1410065408 2000000000 1 2000000001 2000000000 2000000000 -294967296 3000000000 0 -1294967296
Conclusão: quando uma das parcelas ou o resultado sai do intervalo dos int, está tudo estragado.
![Page 45: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/45.jpg)
[-2147483648..2147483647] ou [-231..231-1] • Em C, cada número int ocupa uma palavra de
32 bits. • A sequência dos valores dos bits corresponde
à representação binária do número. • Logo, com 32 bits, podem ser representados
no máximo 232 = 4294967296 números diferentes.
• Metade serão negativos, um é o zero e metade menos um serão positivos.
• Por isso, o intervalo dos números int é [-231..231-1], ou [-2147483648..2147483647].
18/12/14 Programação Imperativa 45
![Page 46: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/46.jpg)
Overflow • Há overflow de inteiros quando o resultado de
um cálculo com números inteiros cai fora do intervalos dos números int.
• Quando há overflow, os cálculos aritméticos ficam errados, irremediavelmente.
18/12/14 Programação Imperativa 46
sources pedro$ ./a.out 2147483647 1 -2147483648 2147483647 10 -2147483639 2147483647 20 -2147483629 -2147483648 -1 2147483647
Repare, 2147483647 + 1 dá -2147483648. É como se o sucessor do maior número fosse o menor número. Analogamente -2147483648 – 1 dá 2147483647, como se o predecessor do menor número fosse o maior número.
![Page 47: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/47.jpg)
Operações aritméticas, tipo int • Adição: x + y • Subtração: x – y • Multiplicação: x * y • Quociente da divisão inteira: x / y • Resto da divisão inteira: x % y
18/12/14 Programação Imperativa 47
Cuidados: • Não deixar dar overflow. • Não deixar o divisor ser zero. Se o divisor for zero,
o programa estoira. • Não usar operandos com valor negativo na
operação resto da divisão inteira.
Note bem: ambos os operandos, x e y, representam expressões cujo valor é um número int. O resultado, se houver, é um valor de tipo int.
![Page 48: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/48.jpg)
Testando as operações aritméticas, int
18/12/14 Programação Imperativa 48
void test_operations_int(void) { int x; int y; while (scanf("%d%d", &x, &y) != EOF) { int z1 = x + y; printf("%d\n", z1); int z2 = x - y; printf("%d\n", z2); int z3 = x * y; printf("%d\n", z3); int z4 = x / y; printf("%d\n", z4); int z5 = x % y; printf("%d\n", z5); } }
sources pedro$ ./a.out 20 7 27 13 140 2 6 33 50 83 -17 1650 0 33 14 3 17 11 42 4 2
![Page 49: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/49.jpg)
Operações aritméticas, tipo double • Adição: x + y • Subtração: x – y • Multiplicação: x * y • Quociente da divisão: x / y
18/12/14 Programação Imperativa 49
Cuidados: • Não deixar o divisor ser zero. • Não contar com precisão ilimitada na representação
do resultado.
Note bem: ambos os operandos, x e y, representam expressões cujo valor é um número double. O resultado, se houver, é de tipo double.
![Page 50: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/50.jpg)
Testando as operações aritméticas, double
18/12/14 Programação Imperativa 50
void test_operations_double(void) { double x; double y; while (scanf("%lf%lf", &x, &y) != EOF) { double z1 = x + y; printf("%f\n", z1); double z2 = x - y; printf("%f\n", z2); double z3 = x * y; printf("%f\n", z3); double z4 = x / y; printf("%f\n", z4); } }
sources pedro$ ./a.out 25.0 4.0 29.000000 21.000000 100.000000 6.250000 14.0 3.0 17.000000 11.000000 42.000000 4.666667 6.125 0.5 6.625000 5.625000 3.062500 12.250000 0.333333 0.5 0.833333 -0.166667 0.166666 0.666666
Note bem: o operador %, resto da divisão inteira, não existe com para números double.
![Page 51: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/51.jpg)
Aritmética mista • Quando numa expressão do tipo x+y, x-y, x*y
ou x/y um dos operandos é double e o outro é int, este é “convertido” automaticamente para double e aplicam-se as regras da aritmética de doubles.
18/12/14 Programação Imperativa 51
A conversão inversa, de double para int, é mais delicada, pois, pode fazer-se de várias maneiras: por truncagem (isto é, eliminando a parte decimal), para o inteiro precedente, para o inteiro seguinte, ou para o inteiro mais próximo. Em cada caso, temos de indicar qual pretendemos.
![Page 52: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/52.jpg)
Funções matemáticas de biblioteca • O C traz um pequeno conjunto de funções
matemáticas, operando sobre doubles:
18/12/14 Programação Imperativa 52
Função Significado sin(x) Seno de x.
cos(x) Cosseno de x.
tan(x) Tangente de x.
atan2(y, x) Arcotangente de y/x, no intervalo [-π, π].
exp(x) Exponencial de x.
log(x) Logaritmo natural de x.
pow(x, y) x elevado a y.
sqrt(x) Raiz quadrada de x.
floor(x) Maior número inteiro menor ou igual a x.
ceil(x) Menor número inteiro maior ou igual a x.
fabs(x) Valor absoluto de x.
Para usar, fazer #include <math.h>.
![Page 53: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/53.jpg)
Arredondamento • No problema da nota, precisamos de
arredondar a nota exata, para o inteiro mais próximo, tendo o cuidado de arredondar para cima as meias unidades.
• Eis uma função para fazer esse cálculo, recorrendo à função floor:
• Note que o resultado é um número inteiro representado por um double.
18/12/14 Programação Imperativa 53
double round(double x) { return floor(x+0.5); }
![Page 54: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/54.jpg)
Nota final • A nota final é o arredondamento da nota
exata e dever ser expressa no tipo int. • Devemos pois explicitar a conversão do resul-
tado do arredondamento, de double para int. • Observe:
18/12/14 Programação Imperativa 54
int final_grade(double lab, double exam) { return (int) round(grade(lab, exam)); }
Em geral, sendo x uma expressão de tipo double, (int) x é uma expressão de tipo int cujo valor é o valor de x sem a parte decimal.
![Page 55: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/55.jpg)
Função de teste para a nota exata • Acrescentamos o novo cálculo à função
test_grade:
18/12/14 Programação Imperativa 55
void test_grade(void) { double lb; double ex; while (scanf("%lf%lf", &lb, &ex) != EOF) { double v = weighted_average(lb, ex); printf("%f\n", v); double z = grade(lb, ex); printf("%f\n", z); int g = final_grade(lb, ex); printf("%d\n", g); } } Em geral, é prudente observar também os
resultados intermédios nas funções de teste.
![Page 56: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/56.jpg)
Nota de exame necessária • Problema: para passar com y como nota final,
quanto precisa conseguir no exame um aluno cuja nota da prática é x?
• Para começar, precisamos de resolver em ordem a z a inequação 0.3 * x + 0.7 * z >= y.
• Mas o resultado exato não basta, pois a nota do exame é expressa com uma casa decimal.
• E, para mais, se, resolvendo a inequação, z vier menor que 8.5, isso não serve: o valor de z tem de ser pelo menos 8.5.
18/12/14 Programação Imperativa 56
![Page 57: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/57.jpg)
Nota necessária exata • Calculemos primeiro a nota necessária com a
precisão possível, sem considerar a questão do 8.5.
• Isso corresponde a resolver a inequação:
18/12/14 Programação Imperativa 57
double exam_exact(double lab, int goal) { return (goal - 0.3 * lab) / 0.7; }
Se o resultado for maior que 20.0, isso significa que é impossível passar com a nota desejada.
![Page 58: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/58.jpg)
Arredondamento para cima às décimas • Para arredondar para cima, às unidades, temos
a função ceil. • Como fazer para arredondar às décimas? • Eis o truque: multiplica-se por 10, arredonda-
se às unidades e divide-se por 10:
18/12/14 Programação Imperativa 58
double ceiling_one_decimal(double x) { return ceil(x * 10.0) / 10.0; }
E se quiséssemos arredondar para cima às milésimas, como faríamos? E arredondar para cima, às dezenas?
![Page 59: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/59.jpg)
Nota necessária com uma casa decimal • Arredonda-se a nota exata, às décimas, para
cima:
18/12/14 Programação Imperativa 59
double exam_one_decimal(double lab, int goal) { return ceiling_one_decimal(exam_exact(lab, goal)); }
Temos ainda de considerar o caso em que esta nota é menor que 8.5, pois um tal valor não daria para passar.
![Page 60: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/60.jpg)
Funções max e min • A função max retorna o valor do maior dos
seus argumentos. • A função min, idem, para o menor. • Como não existem na biblioteca do C,
programamo-las nós:
18/12/14 Programação Imperativa 60
double max(double x, double y) { return x >= y ? x : y; } double min(double x, double y) { return x <= y ? x : y; }
Estas duas funções são muito úteis, muitas vezes.
![Page 61: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/61.jpg)
Nota necessária • Se a nota exata arredondada for menor do
que 8.5 a nota necessária é 8.5; caso contrário, a nota necessária é a nota exata arredondada.
• Por outras palavras: a nota necessária é o máximo entre a nota exata arredondada e 8.5:
18/12/14 Programação Imperativa 61
double exam(double lab, int goal) { return max(exam_one_decimal(lab, goal), 8.5); }
![Page 62: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/62.jpg)
sources pedro$ ./a.out 15.0 10 7.857143 7.900000 8.500000 10 8 12.0 15 16.285714 16.300000 16.300000 15 14 12.0 18 20.571429 20.600000 20.600000 18 17
Função de teste • Na função de teste, observamos os cálculos inter-
médios e recalculamos a nota final, e também a nota final se tivéssemos uma décima a menos no exame:
18/12/14 Programação Imperativa 62
void test_exam(void) { double lb; int gl; while (scanf("%lf%d", &lb, &gl) != EOF) { double z1 = exam_exact(lb, gl); printf("%f\n", z1); double z2 = exam_one_decimal(lb, gl); printf("%f\n", z2); double z = exam(lb, gl); printf("%f\n", z); int x1 = grade(lb, z); printf("%d\n", x1); int x2 = grade(lb, z - 0.1); printf("%d\n", x2); } }
![Page 63: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/63.jpg)
Programação Imperativa
Lição n.º 4 Escolhas
![Page 64: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/64.jpg)
Escolhas • Instrução if-else. • Constantes simbólicas. • Declaração de variáveis. • If-else em cascata.
18/12/14 Programação Imperativa 64
![Page 65: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/65.jpg)
Preço de uma chamada em roaming • Uma chamada feita em roaming num país da
zona 1 para um número em Portugal custa 0,234 euros por minuto.
• A taxação é feita ao segundo após o primeiro impulso de 30 segundos.
• Isto quer dizer que se paga por segundo, mas paga-se sempre pelo menos 30 segundos.
• Queremos uma função para dar o preço a pagar em função da duração da chamada.
• Os arredondamentos são feitos em cada chamada, aos milésimos de euro.
18/12/14 Programação Imperativa 65
![Page 66: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/66.jpg)
Preço exato, preço certo • A expressão que dá o preço nos primeiros 30
segundos é constante: corresponde a metade do preço por minuto.
• A expressão que dá o preço após os primeiros 30 segundos é o produto do número de segundos pelo preço de cada segundo.
• O preço de cada segundo é um sexagésimo do preço do minuto (sem arredondamentos).
• Usaremos uma função para o preço exato, calculado com toda a precisão, e uma para o preço certo, arredondado às milésimas.
18/12/14 Programação Imperativa 66
![Page 67: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/67.jpg)
Constantes simbólicas • O preço por minuto é uma constante
arbitrária, atualmente 0.234. • Normalmente, evitamos usar “números
mágicos” nos programas. • Em vez disso, preferimos constantes
simbólicas:
• Agora, em vez de 0.234, usaremos, com maior clareza, PRICE_PER_MINUTE.
18/12/14 Programação Imperativa 67
#define PRICE_PER_MINUTE 0.234
![Page 68: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/68.jpg)
Preço exato, com expressão condicional • Em casos simples como este, usamos uma
expressão condicional:
• Mas, quando queremos dar mais destaque a cada uma das alternativas, preferimos uma instrução if-else.
18/12/14 Programação Imperativa 68
double roaming_exact(double x) { return x <= 30 ? PRICE_PER_MINUTE / 2 : x * PRICE_PER_MINUTE / 60; }
![Page 69: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/69.jpg)
Preço exato, com instrução if-else • Eis a mesma função, programada à base de
uma instrução if-else:
18/12/14 Programação Imperativa 69
double roaming_exact(double x) { double result; if (x <= 30) result = PRICE_PER_MINUTE / 2; else result = x * PRICE_PER_MINUTE / 60; return result; } Quando as funções de cálculo são mais do que o
return de uma expressão, usaremos uma variável result para representar o resultado. Assim, é habitual as funções terminarem por return result;.
![Page 70: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/70.jpg)
Preço certo, arredondado às milésimas • A técnica de arredondamento já é conhecida:
18/12/14 Programação Imperativa 70
double round(double x) { return floor(x+0.5); } double round_three_decimals(double x) { return round(x * 1000) / 1000; }
double roaming(double x) { return round_three_decimals(roaming_exact(x)); }
• Logo:
![Page 71: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/71.jpg)
Função de teste • Na função de teste, é prudente observar
também o cálculo exato:
18/12/14 Programação Imperativa 71
void test_roaming(void) { double seconds; while (scanf("%lf", &seconds) != EOF) { double z1 = roaming_exact(seconds); printf("%f\n", z1); double z2 = roaming(seconds); printf("%f\n", z2); } }
$ ./a.out 30 0.117000 0.117000 15 0.117000 0.117000 31 0.120900 0.121000 35 0.136500 0.137000 100 0.390000 0.390000 103 0.401700 0.402000
![Page 72: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/72.jpg)
Declaração de variáveis • A declaração da variável indica explicitamente o tipo
de valores que a variável pode representar. • Determina também, implicitamente, a “quantidade”
de memória necessária para guardar o valor da variável.
• Cada variável tem de ser declarada antes de ser usada.
• Normalmente, declara-se a variável mesmo antes da primeira utilização.
• Se possível, declara-se e inicializa-se no mesmo passo. • Quase sempre, as variáveis não variam: recebem um
valor (por leitura, por cálculo) e guardam esse valor até ao fim.
18/12/14 Programação Imperativa 72
![Page 73: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/73.jpg)
Estacionamento no aeroporto de Faro • Nos parques P1 e P2 do aeroporto de Faro, o
preço do estacionamento é dado pela seguinte tabela:
18/12/14 Programação Imperativa 73
Unidades de taxação Preço
Primeira unidade de 15 minutos 0.60
Restantes unidades de 15 minutos 0.60
Máximo para o primeiro dia 9.00
Preço hora após 24 horas 1.50
Máximo segundo dia e seguintes 9.00
![Page 74: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/74.jpg)
Análise
• Na verdade, há dois casos apenas: o primeiro dia e os restantes dias.
• No primeiro dia paga-se por 60 cêntimos por quarto de hora, mas paga-se no máximo 9 euros.
• No outros dias paga-se 1.50 euros por hora, mas paga-se no máximo 9 euros.
• Como as expressões dos cálculos são complicadas, vamos usar if-else.
18/12/14 Programação Imperativa 74
![Page 75: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/75.jpg)
Função parking • Atenção às contas:
18/12/14 Programação Imperativa 75
double parking(double minutes) { double result; double days_complete = floor(minutes / 1440); if (minutes <= 1440) result = min(9.0, ceil(minutes / 15) * 0.6); else result = 9.0 * days_complete + min(9.0, ceil((minutes - days_complete * 1440) / 60) * 1.5); return result; }
Um dia tem 1440 minutos!
Neste caso, por natureza, não é preciso arredondar os resultado, pois nunca terão mais do que duas casas decimais.
Em rigor, a variável days_complete só é necessária no ramo else. Calculamos logo à cabeça, apenas para aligeirar o código.
![Page 76: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/76.jpg)
Reanálise • Podemos admitir que as regras para calcular o
preço do estacionamento não mudam, durante algum tempo, mas que os valores na tabela podem mudar com alguma frequência.
• Por exemplo, o custo dos primeiros 15 minutos pode baixar para 40 cêntimos, para incentivar estadias muito curtas.
• Ou o máximo nos outros dias (depois do primeiro) pode subir para 12 euros, para castigar estadia longas.
• Para incorporar estas mudanças, não bastaria substituir as constantes.
18/12/14 Programação Imperativa 76
![Page 77: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/77.jpg)
Nova estratégia • Programemos “em função” dos valores da
tabela, os quais serão representados por constantes simbólicas:
• Vendo bem, agora há três casos: os primeiros 15 minutos; o resto do primeiro dia; os dias seguintes.
• Usaremos instruções if-else em cascata. 18/12/14 Programação Imperativa 77
#define UNIT_1 0.60 #define UNITS_OTHER 0.60 #define MAX_DAY_1 9.00 #define PRICE_PER_HOUR 1.50 #define MAX_DAY_OTHERS 9.00
Haver aqui repetições de valores deve ser uma coincidência passageira.
![Page 78: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/78.jpg)
Função parking, melhor e mais complicada • Veja com atenção:
18/12/14 Programação Imperativa 78
double parking(double x) { double result; double days_complete = floor(x / 1440); if (x <= 15) result = UNIT_1; else if (x <= 1440) result = min(MAX_DAY_1, UNIT_1 + ceil((x - 15) / 15) * UNITS_OTHER); else result = MAX_DAY_1 + MAX_DAY_OTHERS * (days_complete - 1) + min(MAX_DAY_OTHERS, ceil((x - days_complete * 1440) / 60) * PRICE_PER_HOUR); return result; }
É mais complicada porque a vida é complicada.
![Page 79: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/79.jpg)
Programação Imperativa
Lição n.º 5 Ciclos
![Page 80: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/80.jpg)
Ciclos • Ciclos for. • Representação dos números double em
memória. • Afetação. • Operadores de afetação. • Formatação de números double.
18/12/14 Programação Imperativa 80
![Page 81: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/81.jpg)
Soma geométrica • Queremos calcular a soma S(x, n) = 1 + x + x2
+ ... + xn-1, para um dado x e um dado n. • Existe uma fórmula: S(x, n) = (xn-1)/(x-1). • Para x=2, esta fórmula dá 1+2+4+...+2n-1 =
2n-1. • Para x=1/2, dá 1+1/2+1/4+... = 2. • Estes dois resultados têm muito interesse
computacional. Lembre-se deles! • Por outro lado:
S(x, n) = n == 0 ? 0 : 1 + x * S(x, n-1)
18/12/14 Programação Imperativa 81
![Page 82: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/82.jpg)
• Calculemos à mão S(3, 5): Calculando recursivamente
S(3, 5) = 1 + 3 * S(3, 4) = 1 + 3 * (1 + 3 * S(3, 3)) = 1 + 3 * (1 + 3 * (1 + 3 * S(3, 2))) = 1 + 3 * (1 + 3 * (1 + 3 * (1 + 3 * S(3, 1)))) = 1 + 3 * (1 + 3 * (1 + 3 * (1 + 3 * (1 + 3 * S(3, 0))))) = 1 + 3 * (1 + 3 * (1 + 3 * (1 + 3 * (1 + 3 * 0)))) = 1 + 3 * (1 + 3 * (1 + 3 * (1 + 3 * 1)))) = 1 + 3 * (1 + 3 * (1 + 3 * 4))) = 1 + 3 * (1 + 3 * 13)) = 1 + 3 * 40 = 121 18/12/14 Programação Imperativa 82
Curioso: calculamos uma soma de potências com expoentes sucessivos, mas não usámos a potenciação.
![Page 83: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/83.jpg)
Função para a soma das potências • Exprimimos em C a definição da função S:
18/12/14 Programação Imperativa 83
double sum_geometric(double x, int n) { return n == 0 ? 0.0 : 1.0 + x * sum_geometric(x, n-1); }
![Page 84: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/84.jpg)
Formatos para double no printf • O especificador “%f” indica que o número
aparecerá com parte inteira e parte decimal. Por defeito, a parte decimal vem arredondada com 6 algarismos.
• O especificador “%e” indica que o número aparecerá em notação exponencial, por exemplo, 4.095000e+03. A parte inteira vem está entre1 e 9 e, por defeito, a parte decimal vem arredondada com 6 algarismos.
• O especificador “%g” indica que o número virá na forma mais “apropriada”.
18/12/14 Programação Imperativa 84
![Page 85: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/85.jpg)
Testando a soma das potências • Calculamos e mostramos o resultado usando
os três formatos:
18/12/14 Programação Imperativa 85
void test_sum_geometric(void) { double x; int n; while (scanf("%lf%d", &x, &n) != EOF) { double z = sum_geometric(x, n); printf("%f\n", z); printf("%e\n", z); printf("%g\n", z); } }
$ ./a.out 2 16 65535.000000 6.553500e+04 65535 10 12 111111111111.000000 1.111111e+11 1.11111e+11 0.5 10 1.998047 1.998047e+00 1.99805 0.1 8 1.111111 1.111111e+00 1.11111
![Page 86: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/86.jpg)
Fixando a precisão • Para escolher o número de casas decimais no
caso do “%f” e do “%e” ou o número de algarismos usados no “%g”, fixamos a precisão da escrita.
• Por exemplo, mudemos a precisão de 6 (valor por defeito) para 20, nos 3 printf:
18/12/14 Programação Imperativa 86
printf("%.20f\n", z); printf("%.20e\n", z); printf("%.20g\n", z);
![Page 87: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/87.jpg)
Experimentando com precisão excessiva • Observe, com precisão 20:
18/12/14 Programação Imperativa 87
$ ./a.out 2 15 32767.00000000000000000000 3.27670000000000000000e+04 32767 10 14 11111111111111.00000000000000000000 1.11111111111110000000e+13 11111111111111 2 40 1099511627775.00000000000000000000 1.09951162777500000000e+12 1099511627775 0.1 15 1.11111111111111005023 1.11111111111111005023e+00 1.1111111111111100502 0.1 30 1.11111111111111116045 1.11111111111111116045e+00 1.1111111111111111605
O último teste mostra que o tipo double não consegue representar números com mais que 17 algarismos.
![Page 88: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/88.jpg)
Iteração • Recorde a definição da função S:
S(x, n) = n == 0 ? 0 : 1 + x * S(x, n-1) • Quer dizer: se R for o valor de S(x, n) então 1+x * R
é o valor de S(x, n+1). • Portanto, começando com R = 0 e fazendo
R = 1 + x * R, repetidamente, n vezes, chega-se ao valor de S(x, n):
18/12/14 Programação Imperativa 88
R = 0 R = 1 + x * R // x0
R = 1 + x * R // x0 + x1
R = 1 + x * R // x0 + x1 + x2
... R = 1 + x * R // x0 + x1 + x2 + ... xn-1
![Page 89: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/89.jpg)
Soma das potências, versão iterativa • Para repetir uma instrução um certo número
de vezes, usa-se um ciclo for:
18/12/14 Programação Imperativa 89
double sum_geometric_i(double x, int n) { double result = 0.0; for (int i = 0; i < n; i++) result = 1.0 + x * result; return result; }
No fim do passo i, a variável result contém o valor 1+x+x2+...+xi. Portanto, no fim do ciclo for, conterá 1+x+x2+...xn-1, que constitui a soma pretendida.
Com este ciclo for, repete-se n vezes.
![Page 90: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/90.jpg)
double sum_geometric_t(double x, int n) { double result = 0.0; double term = 1.0; for (int i = 0; i < n; i++) { result += term; term *= x; } return result; }
Cada novo resultado parcial é obtido somando o resultado parcial corrente ao termo corrente; cada novo termo é obtido multiplicando o termo corrente por x.
Versão iterativa, variante • Consegue-se o mesmo efeito, enumerando os
termos da lista das potências, acumulando a soma no resultado:
18/12/14 Programação Imperativa 90
![Page 91: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/91.jpg)
Afetação • Que significa, em programação x = y? • A expressão x = y é uma expressão de afetação. • Em geral, x e y são expressões. • A expressão x, à esquerda, representa uma variável,
isto é, uma posição de memória. • A expressão y, à direita, representa um valor, que
deve pertencer a um tipo que pode ser armazenado na posição de memória representada por x.
• O efeito de x = y é armazenar na posição representada por x o valor representado por y.
• Em geral, o valor de uma variável é o valor que mais recentemente foi armazenado na posição de memória representada por essa variável.
18/12/14 Programação Imperativa 91
![Page 92: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/92.jpg)
Operadores de afetação • O significado dos operadores de afetação +=, −=, *=, /= e %= é “intuitivo”:
18/12/14 Programação Imperativa 92
Utilização Significado x += y x = x + y x −= y x = x − y x *= y x = x * y x /= y x = x / y x %= y x = x % y
![Page 93: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/93.jpg)
Variante overkill • Calcular cada termo da sucessão das
potências, usando pow(x, i) seria overkill:
18/12/14 Programação Imperativa 93
double sum_geometric_bad(double x, int n) { double result = 0.0; for (int i = 0; i < n; i++) result += pow(x, i); return result; } Calcular xi por meio de pow(x, i) é
esbanjamento de recursos, pois o valor de xi-1 terá sido calculado no passo anterior e para calcular xi, basta multiplicar esse valor por x.
![Page 94: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/94.jpg)
void test_sum_geometric_all(void) { double x; int n; while (scanf("%lf%d", &x, &n) != EOF) { double z1 = sum_geometric(x, n); printf("%.16g\n", z1); double z2 = sum_geometric_i(x, n); printf("%.16g\n", z2); double z3 = sum_geometric_t(x, n); printf("%.16g\n", z3); double z4 = sum_geometric_bad(x, n); printf("%.16g\n", z4); } }
$ ./a.out 2 20 1048575 1048575 1048575 1048575 10 12 111111111111 111111111111 111111111111 111111111111 2 60 1.152921504606847e+18 1.152921504606847e+18 1.152921504606847e+18 1.152921504606847e+18 0.5 30 1.999999998137355 1.999999998137355 1.999999998137355 1.999999998137355
Função de teste • Eis uma função de teste para as quatro
variantes da soma de potências:
18/12/14 Programação Imperativa 94
![Page 95: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/95.jpg)
Soma de quadrados • Baseemo-nos na variante overkill da soma
geométrica para programar a soma de quadrados:
18/12/14 Programação Imperativa 95
double sum_squares(int n) { double result = 0.0; for (int i = 0; i < n; i++) result += i * i; return result; }
Há uma expressão polinomial que calcula a soma dos quadrados dos n primeiros números naturais (0+1+4+9+...+(n-1)2), pelo que calcular iterativamente é apenas um exercício de programação. A expressão é (n-1)*n*(2*n-1)/6.
![Page 96: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/96.jpg)
Soma de quadrados, variante elementar • É claro que (n+1)2 – n2 = 2*n+1. • Logo, podemos calcular (n+1)2 a partir de n2,
somando 2*n+1:
18/12/14 Programação Imperativa 96
double sum_squares_e(int n) { double result = 0.0; double term = 0.0; for (int i = 0; i < n; i++) { result += term; term += 2 * i + 1; } return result; }
![Page 97: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/97.jpg)
Soma de quadrados, mais elementar ainda • Usando a mesma técnica, sabemos que
(2(n+1) + 1) – (2*n+1) = 2. • Logo, a sequência das diferenças entre quadrados
cresce de 2 em 2:
18/12/14 Programação Imperativa 97
double sum_squares_f(int n) { double result = 0.0; double term = 0.0; double delta = 1.0; for (int i = 0; i < n; i++) { result += term; term += delta; delta += 2.0; } return result; }
Este é um exercício de programação clássico: calcular a soma dos quadrados nos n primeiros números naturais usando apenas adições.
![Page 98: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/98.jpg)
Soma de quadrados, polinomial • Eis a função que implementa o cálculo direto
da soma dos quadrados dos n primeiros números naturais:
18/12/14 Programação Imperativa 98
double sum_squares_p(int n) { return (n - 1.0) * n * (2.0*n - 1.0)/6; }
Questão técnica: se tivéssemos programado só com inteiros int, assim: return (n-1) * n * (2*n - 1) / 6; o computador usaria aritmética de inteiros (antes de converter o resultado para double, no fim) e daria overflow logo que o produto no numerador ultrapassasse 231-1, o que acontece para n = 1025.
![Page 99: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/99.jpg)
Programação Imperativa
Lição n.º 6 Ciclos for e ciclos while
![Page 100: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/100.jpg)
Ciclos for e ciclos while • Utilização dos ciclos for. • Utilização dos ciclos while. • Exemplos: soma de sequências, fatorial,
função 3x+1, comprimento de números.
18/12/14 Programação Imperativa 100
![Page 101: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/101.jpg)
Utilização dos ciclos for • Usamos um ciclo for para repetir a execução
de uma instrução nas situações em sabemos à partida quantas vezes é preciso repetir.
• Tipicamente, número de repetições ou é fixo (o que é raro) ou vem numa variável.
• No seio da instrução repetida, podemos usar a variável de controlo que enumera as repetições.
• Frequentemente, a enumeração começa em 0 ou em 1 e vai de 1em 1.
• A instrução repetida pode ser uma instrução composta, agrupando várias instruções.
18/12/14 Programação Imperativa 101
![Page 102: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/102.jpg)
Exemplo: progressão aritmética
18/12/14 Programação Imperativa 102
• Eis uma função para calcular iterativamente a soma dos n primeiros termos de uma progressão geométrica cujo primeiro termo é x e cuja razão é r: double sum_progression(double x, double r, int n) { double result = 0.0; for (int i = 0; i < n; i++) { result += x; x += r; } return result; }
Já sabemos que há uma fórmula fechada para isto, pelo que esta função deve ser considerada apenas um exercício de programação.
Note bem: sabemos que queremos somar n termos. Logo, usamos ciclo for.
![Page 103: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/103.jpg)
Exemplo: fatorial
18/12/14 Programação Imperativa 103
• O fatorial de um número inteiro n é o produto de todos os números inteiros entre 1 e n (inclusive):
double factorial(int n) { double result = 1.0; for (int i = 1; i <= n; i++) // <= result *= i; return result; }
Note bem: aqui a variável de controlo varia entre 1 e n (inclusive); no exemplo da página anterior, variava entre 0 e n-1 (inclusive).
Queremos multiplicar n números. Logo, usamos ciclo for.
Note bem: a variável de controlo é usada na instrução repetida.
![Page 104: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/104.jpg)
Testando o fatorial
18/12/14 Programação Imperativa 104
• Eis uma função de teste: void test_factorial(void) { int x; while (scanf("%d", &x) != EOF) { double z = factorial(x); printf("%.15g\n", z); } }
$ ./a.out 1 1 5 120 10 3628800 20 2.43290200817664e+18 30 3.04140932017134e+64 100 9.33262154439441e+157 150 5.71338395644585e+262 170 7.25741561530799e+306 171 inf 300 inf
O maior número double é aproximadamente 1.796931e308. Mais que isso, é infinito, representado em C por inf.
![Page 105: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/105.jpg)
Problema: soma 1 + 1/2+ 1/4 + 1/8 + ...
18/12/14 Programação Imperativa 105
• A soma dos inversos das potências de 2 tende para 2.
• Quantos termos é preciso somar para que o resultado seja maior que 2 – x, para um x dado?
• Neste caso, não sabemos à partida quantas vezes temos de repetir a adição r = 1 + r/2 (cf. função sum_geometric, lição 5.)
• (Se soubéssemos, já tínhamos a solução do problema...)
![Page 106: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/106.jpg)
Ciclos while • O ciclo while é usado para repetir uma
instrução um número indeterminado de vezes. • As repetições terminam logo que uma certa
condição deixa de se verificar. • A condição é representada por uma expressão
lógica. • Isto é, as repetições terminam logo que o
valor da expressão booleana seja 0, representando falso.
• Não haverá repetições se a expressão valer 0 logo de início.
18/12/14 Programação Imperativa 106
![Page 107: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/107.jpg)
Contanto termos da soma aproximada • Repetimos a instrução r = 1 + r/2, enquanto o valor de
r, que representa a soma parcial, for menor que 2 – x, sendo x o tal valor dado.
• Em paralelo, incrementamos um contador de termos adicionados:
18/12/14 Programação Imperativa 107
int count_terms(double x) { int result = 0; double sum = 0.0; while (sum <= 2 - x) { sum = 1 + sum / 2; result++; } return result; }
O contador é o resultado da função.
![Page 108: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/108.jpg)
Testando a contagem de termos • Contamos os termos e depois verificamos que
a contagem confere, usando a função sum_geometric:
18/12/14 Programação Imperativa 108
void test_count_terms(void) { double x; while (scanf("%lf", &x) != EOF) { int z1 = count_terms(x); printf("%d\n", z1); double z2 = sum_geometric(0.5, z1); printf("%.15f\n", z2); } }
$ ./a.out 0.1 5 1.937500000000000 0.01 8 1.992187500000000 0.001 11 1.999023437500000 0.000001 21 1.999999046325684
![Page 109: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/109.jpg)
Problema 3x+1 • Seja a seguinte função f:
• Qualquer que seja x, a sequência x, f(x), f(f(x)), f(f(f(x))), ..., eventualmente alcança o valor 1, após o que oscila 1, 4, 2, 1, 2, 4, 1, ..., indefinidamente.
• Qual é o comprimento da sequência que começa em x e termina no primeiro 1?
18/12/14 Programação Imperativa 109
f (x) =3x +1, se x for ímparx / 2, se não!"#
http://en.wikipedia.org/wiki/Collatz_conjecture http://uva.onlinejudge.org/external/1/100.html
![Page 110: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/110.jpg)
Preliminares • Primeiro, a função f:
• Repare na expressão x % 2 == 1. • Significa “ser o resto da divisão de x por 2
igual a 1”. • Por outras palavras, “ser x um número ímpar”.
18/12/14 Programação Imperativa 110
int f(int x) { return x % 2 == 1 ? 3 * x + 1 : x / 2; }
![Page 111: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/111.jpg)
Observando a sequência • Para perceber melhor de que se trata, programemos
uma função para mostrar a sequência que começa em x e chega até 1, chamada “ciclo” de x:
18/12/14 Programação Imperativa 111
void show_cycle(int x) { while (x != 1) { printf("%d ", x); x = f(x); } printf("%d\n", x); }
void test_cycle(void) { int x; while (scanf("%d", &x) != EOF) show_cycle(x); }
$ ./a.out 3 3 10 5 16 8 4 2 1 9 9 28 14 7 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1 512 512 256 128 64 32 16 8 4 2 1
![Page 112: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/112.jpg)
Comprimento do ciclo • É parecida com show_cycle, substituindo o
printf pelo incremento do contador:
18/12/14 Programação Imperativa 112
int cycle_length(int x) { int result = 0; while (x != 1) { result++; x = f(x); } result++; return result; }
![Page 113: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/113.jpg)
Refinando a função de teste • Acrescentamos à função de teste uma
chamada da nova função:
18/12/14 Programação Imperativa 113
void test_cycle(void) { int x; while (scanf("%d", &x) != EOF) { show_cycle(x); int z = cycle_length(x); printf("%d\n", z); } }
$ ./a.out 11 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1 15 40 40 20 10 5 16 8 4 2 1 9
![Page 114: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/114.jpg)
Comprimento de um número
18/12/14 Programação Imperativa 114
• O comprimento de um número inteiro é o número de algarismos da sua representação decimal.
• Exprime-se recursivamente, em termos do comprimento da décima parte do número.
• Mas quando é menor ou igual a 9, o comprimento é 1:
int decimal_length(int x) { return x <= 9 ? 1 : 1 + decimal_length(x / 10); }
![Page 115: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/115.jpg)
Variante iterativa
18/12/14 Programação Imperativa 115
• Dividir por 10, sucessivamente, enquanto for maior que 9, e contar as divisões: int decimal_length_i(int x) { int result = 1; while (x > 9) { result++; x /= 10; } return result; }
![Page 116: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/116.jpg)
Testando o comprimento do número
18/12/14 Programação Imperativa 116
void test_decimal_length(void) { int x; while (scanf("%d", &x) != EOF) { int z1 = decimal_length(x); printf("%d\n", z1); int z2 = decimal_length_i(x); printf("%d\n", z2); } }
$ ./a.out 6413 4 4 9761826 7 7 5 1 1 0 1 1 3321 4 4
• Testamos as duas variantes:
![Page 117: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/117.jpg)
Programação Imperativa
Lição n.º 7 Arrays
![Page 118: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/118.jpg)
Arrays • Generalidades sobre arrays. • Funções sobre arrays.
18/12/14 Programação Imperativa 118
![Page 119: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/119.jpg)
Arrays para quê? • Até agora, todos os programa que vimos
calculam a partir de uns poucos números, desses cálculos resultando um outro número.
• Frequentemente teremos, não uns poucos números, mas sim milhentos, e queremos a partir de eles calcular outros milhentos.
• Mais adiante teremos não só milhentos números, mas também milhentas palavras e, mais geralmente, milhentos objetos de diversos tipos.
• Usaremos arrays para representar nos programas milhentos objetos do mesmo tipo.
18/12/14 Programação Imperativa 119
![Page 120: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/120.jpg)
Arrays • Os arrays são sequências de objetos, todos do
mesmo tipo, acessíveis para inspeção e para modificação por meio dos respetivos índices.
• Os índices são números inteiros. • O índice do primeiro elemento é zero; o
índice do segundo elemento é 1; o índice do terceiro elemento é 2 e assim por diante.
• Se o array tiver N elementos, o índice do último elemento é N-1.
18/12/14 Programação Imperativa 120
Daqui vem a “tradição” de, em programação, enumerar os objetos a partir de zero.
![Page 121: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/121.jpg)
Cultura geral: a palavra “array” • Em inglês não técnico, o substantivo array é usado
para significar um conjunto considerável de objetos de um certo tipo, dispostos de maneira regular: “an array of troops in battle order”; “there is a vast array of literature on the topic”; “a bewildering array of choices”.
• A palavra “array” vem do latim “ad-redare”, um verbo que significa “arranjar”, “colocar em ordem”.
• De “ad-redare” derivam as palavras portuguesas “arriar”, “arrear”, “arreio” (mas não “arredar”).
18/12/14 Programação Imperativa 121
Fontes: WordWeb Dictionary © WordWebSoftware.com. http://www.etymonline.com/index.php?term=array. Dictionary, v 2.2.1© Apple Inc. http://www.ciberduvidas.com/pergunta.php?id=25368. http://pt.wiktionary.org/wiki/arredar#Etimologia.
Perante isto, e não só, talvez pudéssemos usar “arreios”, em português...
![Page 122: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/122.jpg)
Arrays em memória • Já sabemos que um objeto de tipo int ocupa
em memória uma palavra de 4 bytes. • Também sabemos que um objeto de tipo
double ocupa duas palavras de 4 bytes, isto é, 8 bytes ao todo.
• Um array de int com capacidade para N objetos ocupará 4 * N bytes.
• Esses 4 * N bytes ficam todos de seguida na memória do computador.
• Analogamente para os arrays de double: neste caso serão 8 * N bytes, todos de seguida.
18/12/14 Programação Imperativa 122
![Page 123: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/123.jpg)
Capacidade de um array • A capacidade de um array determina o
número de objetos que o array pode conter, em cada momento.
• A capacidade é fixa: uma vez estabelecida, quando o array é criado, não mais mudará.
• Em geral, se um objeto de tipo T ocupa Z bytes, um array de elementos do tipo T com capacidade C ocupará um bloco de memória com C * Z bytes.
• Esse bloco de memória é inamovível.
18/12/14 Programação Imperativa 123
![Page 124: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/124.jpg)
Capacidade e tamanho • Muitas vezes, os arrays são dimensionados por
excesso, para poder acondicionar todos os conjuntos de valores que possam surgir.
• Por isso, frequentemente, o número de objetos presente no array é menor que a capacidade.
• O número de elementos presentes no array, em cada momento, é o tamanho do array.
• Se a capacidade for C e o tamanho for N, as C – N posições de maior índice ficam desaproveitadas.
• Durante os cálculos, o tamanho pode mudar; a capacidade não!
18/12/14 Programação Imperativa 124
![Page 125: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/125.jpg)
Propriedade fundamental dos arrays
Num array, o custo de aceder ao primeiro elemento é igual a custo de aceder ao último ou a qualquer outro elemento.
18/12/14 Programação Imperativa 125
![Page 126: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/126.jpg)
Limites dos índices • Tentar aceder a um array fora dos limites dos
índices, isto é, usando um índice menor que zero ou maior ou igual ao tamanho, é um grave erro de programação.
• Excetua-se o caso de querermos acrescentar um elemento ao array.
• Para acrescentar, acedemos ao índice dado pelo valor do tamanho, o qual indica a primeira posição livre, e colocamos nesta posição o valor que queremos acrescentar.
• Logo a seguir, incrementamos o valor do tamanho.
18/12/14 Programação Imperativa 126
O qual, não obstante, cometemos muitas vezes.
![Page 127: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/127.jpg)
Problema: array dos dígitos.
18/12/14 Programação Imperativa 127
• Dado um número inteiro não negativo x, queremos programar uma função para construir um array que contenha os dígitos de x, isto é, os números que correspondem aos algarismos usados na representação decimal de x.
• Por exemplo, se o número for 2015, o array ficará com os elementos <5, 1, 0, 2> e o tamanho será 4.
Repare: 5, que é o dígito menos significativo, fica na posição de índice 0; 1 fica na posição de índice 1; 0 fica na posição de índice 2; e 2 fica na posição de índice 3.
![Page 128: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/128.jpg)
Função digits
18/12/14 Programação Imperativa 128
• Os argumentos da função serão o número cujos dígitos queremos e o array onde vamos guardar os dígitos calculados.
• A função retorna o tamanho do array, no final das operações.
• Observe a declaração da função, ainda vazia:
int digits(int x, int *a) { ... }
Repare: int * é o tipo dos arrays de int.
![Page 129: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/129.jpg)
Aquecimento: contando os dígitos
18/12/14 Programação Imperativa 129
• Contar os dígitos de números naturais é mais difícil do que contar os dígitos de números inteiros positivos, porque o zero introduz uma irregularidade.
• De facto, se x estiver no intervalo [10n..10n+1[, x tem n+1 dígitos.
• Ora o zero escapa a estes intervalos e tem de ser tratado à parte.
• Por isso, comecemos pelo caso mais conveniente: contar os dígitos de um número inteiro positivo.
![Page 130: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/130.jpg)
Função count_digits_positive
18/12/14 Programação Imperativa 130
• É uma variante mais simples da função decimal_length_i da lição anterior:
int count_digits_positive(int x) { int result = 0; while (x > 0) { result++; x /= 10; } return result; }
Note bem: o argumento deve ser positivo. Se for zero (ou negativo) o resultado é inválido.
![Page 131: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/131.jpg)
Função count_digits
18/12/14 Programação Imperativa 131
• Consideramos o caso particular de o argumento ser zero, em que o resultado é 1.
• Fora isso, usamos o caso geral, por meio da função anterior.
• Observe: int count_digits(int x) { int result = 1; if (x > 0) result = count_digits_positive(x); return result; }
Aprecie o estilo: evitamos um if-else, inicializando o resultado com o valor por defeito, por assim dizer.
![Page 132: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/132.jpg)
Função digits_positive
18/12/14 Programação Imperativa 132
• Evitemos as chatices do zero, baseando-nos na função count_digits_positive.
• Agora, além de contar, acrescentamos cada dígito ao array: int digits_positive(int x, int *a) { int result = 0; while (x > 0) { a[result++] = x % 10; x /= 10; } return result; }
Repare: o resultado representa o tamanho do array, depois da operação.
![Page 133: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/133.jpg)
Função digits
18/12/14 Programação Imperativa 133
• Esta constitui o caso geral:
int digits(int x, int *a) { int result = 1; a[0] = 0; if (x > 0) result = digits_positive(x, a); return result; }
Deixamos as funções de teste para mais tarde...
![Page 134: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/134.jpg)
Outro exemplo: inverter o array
18/12/14 Programação Imperativa 134
• Queremos construir um array com os elementos de outro array, por ordem inversa.
• Observe:
• O array b é o array de saída; o array a é o array de entrada.
• A função devolve o tamanho do array de saída.
int mirror(const int *a, int n, int *b) { for (int i = 0; i < n; i++) b[n-1-i] = a[i]; return n; }
O qualificador const indica que o array a não será modificado na função.
![Page 135: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/135.jpg)
Programação Imperativa
Lição n.º 8 Arrays e memória
![Page 136: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/136.jpg)
Arrays e memória • Lendo e escrevendo arrays. • Observando os arrays na memória. • Buffer overflow.
18/12/14 Programação Imperativa 136
![Page 137: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/137.jpg)
Escrevendo arrays • Ocasionalmente queremos observar na
consola o conteúdo dos nossos arrays. • Eis uma função simples que escreve os valores
de todos os elementos na mesma linha, cada um antecedido por um espaço, mudando de linha no final:
18/12/14 Programação Imperativa 137
void ints_println_basic(const int *a, int n) { for (int i = 0; i < n; i++) printf(" %d", a[i]); printf("\n"); }
![Page 138: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/138.jpg)
Testando a função digits • Usemos a função ints_println_basic para
testar na consola a função digits:
18/12/14 Programação Imperativa 138
void test_digits(void) { int x; while (scanf("%d", &x) != EOF) { int a[10]; int n = digits(x, a); ints_println_basic(a, n); } }
$ ./a.out 2015 5 1 0 2 300 0 0 3 7 7 2147483647 7 4 6 3 8 4 7 4 1 2 0 0
Parece que o array está ao contrário, mas não está: o primeiro elemento, de índice 0, corresponde ao algarismo das unidades, etc.
![Page 139: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/139.jpg)
Declaração de arrays • Repare bem: ao declarar um array, indicamos a
sua capacidade: • A capacidade determina a quantidade de
memória reservada para o array. • Quando usamos um array como argumento
de uma função, não indicamos a capacidade, mas tipicamente passamos em argumento também o tamanho do array, nos arrays de entrada, ou devolvemos o tamanho calculado, nos arrays de saída.
18/12/14 Programação Imperativa 139
int a[10];
![Page 140: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/140.jpg)
Preciosismo na função ints_println_basic • Aquele espaço no início da linha, antes do
primeiro valor, é deveras irritante.
• De facto, o que queremos é separar cada dois valores consecutivos por um espaço e não colocar um espaço antes de cada valor.
• Ou seja, queremos um espaço antes de cada valor, exceto antes do primeiro.
• Logo, o primeiro elemento do array tem de ser tratado à parte.
18/12/14 Programação Imperativa 140
2015 5 1 0 2
![Page 141: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/141.jpg)
Função de escrita, básica
• Note que se o array estiver vazio, isto é, se o tamanho for zero, a função apenas muda de linha.
18/12/14 Programação Imperativa 141
void ints_println_basic(const int *a, int n) { if (n > 0) { printf("%d", a[0]); for (int i = 1; i < n; i++) // i = 1 printf(" %d", a[i]); } printf("\n"); }
![Page 142: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/142.jpg)
Testando de novo • Fazemos como antes, mas em cada caso
invertemos o array, com a função mirror, para experimentar:
18/12/14 Programação Imperativa 142
void test_mirror(void) { int x; while (scanf("%d", &x) != EOF) { int a[10]; int n = digits(x, a); ints_println_basic(a, n); int b[10]; int m = mirror(a, n, b); ints_println_basic(b, m); } }
$ ./a.out 1945 5 4 9 1 1 9 4 5 20141016 6 1 0 1 4 1 0 2 2 0 1 4 1 0 1 6 5 5 5
![Page 143: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/143.jpg)
Lendo arrays • Por hipótese, queremos ler da consola uma
sequência de números, até ao fim dos dados. • Cada número lido é acrescentado ao array:
18/12/14 Programação Imperativa 143
int ints_get(int *a) { int result = 0; int x; while (scanf("%d", &x) != EOF) a[result++] = x; return result; }
![Page 144: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/144.jpg)
Testando a leitura de arrays • Lemos um array, invertemo-lo para outro, com a
função mirror, e mostramos o array lido e o array invertido:
18/12/14 Programação Imperativa 144
void test_ints_get() { int a[1000]; int n = ints_get(a); int b[1000]; int m = mirror(a, n, b); ints_println_basic(a, n); ints_println_basic(b, m); }
O valor 1000 para a capacidade é um valor arbitrário, apenas para teste.
$ ./a.out 1 2 3 4 5 1 2 3 4 5 5 4 3 2 1 $ ./a.out 12 34 45 67 89 97 86 75 64 53 42 31 54 63 72 81 90 12 34 45 67 89 97 86 75 64 53 42 31 54 63 72 81 90 90 81 72 63 54 31 42 53 64 75 86 97 89 67 45 34 12
Repare que esta função de teste não é iterativa. A iteração existente está na leitura dos dados.
![Page 145: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/145.jpg)
Buffer overflow • Se lermos números demais, ultrapassando a
capacidade do array, causamos buffer overflow. • A memória fica corrompida e, a partir daí, o
programa está comprometido. • Experimentemos com uma função de teste simples:
18/12/14 Programação Imperativa 145
void test_buffer_overflow_1() { int a[4]; int n = ints_get(a); ints_println_basic(a, n); }
$ ./a.out 1 3 5 7 1 3 5 7 OK $ ./a.out 1 3 5 7 9 11 13 15 1 3 5 7 9 11 13 15 Abort trap: 6 $
int main(void) { test_buffer_overflow_1(); printf("OK\n"); return 0; }
No segundo teste, em que há buffer overflow, o programa estoira à saída da função de teste, antes de executar a instrução printf na função main.
![Page 146: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/146.jpg)
Observando a memória do programa • Usando o Visual Studio em Windows ou o Xcode em
MacOS, podemos parar o programa onde quisermos e observar a memória.
• Aqui, parámos antes da leitura:
18/12/14 Programação Imperativa 146
A azul, a memória da variável n; a verde, a memória do array a.
Nesta altura, a memória contém “lixo”.
![Page 147: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/147.jpg)
Preenchimento da memória • Logo a seguir à leitura, os valores de n e as
posições lidas do array a ficam preenchidas:
18/12/14 Programação Imperativa 147
O conteúdo da memória está representado em notação hexadecimal, da direita para a esquerda: 04 00 00 00 é, na verdade, 00 00 00 04, ou seja 4, em notação decimal. Isto é a consola, no Xcode: os
valores lidos foram 1, 3, 5 e 7.
![Page 148: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/148.jpg)
Exemplo com dois arrays • Consideremos a seguinte função de teste, com dois
arrays:
18/12/14 Programação Imperativa 148
void test_buffer_overflow_2() { int a[10]; int n = ints_get(a); int b[4]; int m = mirror(a, n, b); ints_println_basic(a, n); ints_println_basic(b, m); }
$ ./a.out 10 20 30 10 20 30 30 20 10 $ ./a.out 5 10 15 20 5 10 15 20 20 15 10 5 $ ./a.out 7 14 21 28 35 42 49 56 7 14 14 7 35 42 49 56 56 49 42 35 7 14 14 7
No terceiro teste, o array b transbordou e corrompeu o array a. Mas note que o programa terminou normalmente.
![Page 149: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/149.jpg)
Corrupção da memória (1) • Eis o estado da memória, após a leitura de 8
números: 7, 14, 21, 28, 35, 42, 49, 56:
18/12/14 Programação Imperativa 149
A azul, m, ainda com “lixo”; a cor de rosa, n, com valor 8; a verde, b, ainda não inicializado; a amarelo, a, com 8 posições preenchidas.
![Page 150: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/150.jpg)
Corrupção da memória (2) • Eis o estado da memória, após a chamada da função
mirror e das duas escritas:
18/12/14 Programação Imperativa 150
Recapitulemos as operações da função mirror. Primeiro b[7] = a[0].Mas b[7] coincide com a[3]. Logo a[3] fica com 7. Depois b[6] = a[1]. Mas b[6] coincide com a[2]. Logo a[2] fica com 14. Quer dizer, por via destas duas operações, o conteúdo do array a mudou, mas não devia ter mudado. Depois b[5] = a[2]. Mas b[5] coincide com a[1]. Etc.
![Page 151: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/151.jpg)
Erro de execução • Se o buffer overflow ocorre dentro da zona de
memória reservada para o conjunto das variáveis da função, a memória fica corrompida, mas o programa continua, “alegremente”.
• Mas se o buffer overflow sai dessa zona, então ocorre um erro de execução:
• O erro ocorre à saída da função, porque a informação de retorno está estragada.
18/12/14 Programação Imperativa 151
$ ./a.out 10 20 30 40 50 60 70 80 90 100 110 120 10 20 30 40 40 30 20 10 90 100 110 120 120 110 100 90 10 20 30 40 40 30 20 10 Abort trap: 6
Em Windows, apareceu-me uma janela avisando: “a.exe has stopped working, etc.”
![Page 152: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/152.jpg)
O C é assim mesmo • Em linguagens mais modernas, ocorre um erro de
execução “index out of bounds” quando tentamos aceder a um array fora do intervalo dos índices.
• Em C não. • Em C, um array é apenas um pedaço de memória: o
programa em execução sabe onde começa cada array (na posição de índice 0), mas não sabe onde acaba.
• A capacidade de cada array não está registada na memória, em lado nenhum.
• Por isso, podemos ir pela memória fora, ultrapas-sando o fim dos arrays, sem controlo.
• O C é assim mesmo. • Por isso é que nós gostamos dele. 18/12/14 Programação Imperativa 152
![Page 153: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/153.jpg)
Programação Imperativa
Lição n.º 9 Estatísticas
![Page 154: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/154.jpg)
Estatísticas • Soma, média, máximo, mínimo de arrays. • Redirigindo o input. • Argumento do máximo, do mínimo. • Testes unitários.
18/12/14 Programação Imperativa 154
![Page 155: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/155.jpg)
Ler e escrever arrays de doubles • As funções para ler e escrever arrays de double são
parecidas com as usadas com arrays de int:
18/12/14 Programação Imperativa 155
int doubles_get(double *a) { int result = 0; double x; while (scanf("%lf", &x) != EOF) a[result++] = x; return result; }
void doubles_println_basic(const double *a, int n) { if (n > 0) { printf("%g", a[0]); for (int i = 1; i < n; i++) // i = 1 printf(" %g", a[i]); } printf("\n"); }
![Page 156: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/156.jpg)
Testando a leitura e a escrita • Eis uma função de teste:
18/12/14 Programação Imperativa 156
void test_doubles_get() { double a[1000]; int n = doubles_get(a); doubles_println_basic(a, n); }
$ ./a.out 6.3 0 56 -120.5 8 1.333 34.1 1.41 3.1415926 -999 6.3 0 56 -120.5 8 1.333 34.1 1.41 3.14159 -999 $ ./a.out A 1 3 5 8.111 9 10.7 1 3 5 8.111 9 10.7 $
No primeiro teste, os números para o array foram escritos em duas linhas.
![Page 157: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/157.jpg)
Contagem • Problema: quantos elementos do array têm um
valor dado?
18/12/14 Programação Imperativa 157
int doubles_count(const double *a, int n, int x) { int result = 0; for (int i = 0; i < n; i++) if (a[i] == x) result++; return result; }
Repare na conjugação for-if: só alguns elementos do array interessam, por assim dizer.
![Page 158: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/158.jpg)
Testando a contagem • Aceitamos o valor de referência, depois o
array até ao fim dos dados, operamos e mostramos o resultado:
18/12/14 Programação Imperativa 158
void test_doubles_count(void) { double x; scanf("%lf", &x); double a[1000]; int n = doubles_get(a); int z = doubles_count(a, n, x); printf("%d\n", z); }
$ ./a.out 4 7 4 9 4 4 2 0 4 1 4 8 5 4 9 9 6 $ ./a.out 7 9 1 2 71 17 1 5 0
![Page 159: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/159.jpg)
Problema prático • Quantos dias choveu em Faro este ano? • Temos o registo da precipitação em Faro, em
cada dia, desde 1 de Janeiro até 11 de Outubro, deste ano.
• O ficheiro tem um número por linha, representando a precipitação, dia a dia.
• Eis uma função que realiza a tarefa pedida:
18/12/14 Programação Imperativa 159
void task_rainy_days(void) { double p[50000]; // not more than 50000 int n = doubles_get(p); int z = n - doubles_count(p, n, 0.0); printf("%d\n", z); }
0.2 0 0.2 4 0 0 0 0 0 0 0 0 6 0.2 0 0.5 16.7 1 4 0 0 7.8 …
![Page 160: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/160.jpg)
Correndo na consola • Podemos introduzir os dados com copy-paste,
a partir do ficheiro:
18/12/14 Programação Imperativa 160
$ ./a.out 0.2 0 0.2 4 0 2 7.8 0 0 … 0 0 0 2
14.9 0 44
Estes números todos (mais de 250) terão sido metidos na consola com copy-paste, o que não é muito prático.
O programa escreveu 44, que é o número de dia de chuva em Faro, este ano, até agora.
![Page 161: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/161.jpg)
Redirigindo o input • É mais prático redirigir o input, instruindo na
linha de comando o programa para ir buscar os dados ao ficheiro, em vez de os aceitar a partir do teclado.
• Observe:
18/12/14 Programação Imperativa 161
$ ./a.out < chuva_faro.txt 44
Estamos a supor que o ficheiro chuva_faro.txt, que contém os dados, está da diretoria corrente, a mesma que contém o ficheiro executável a.out. Mas isso nem sempre é prático.
![Page 162: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/162.jpg)
Diretorias de dados • Guardaremos os dados de cada problema numa
diretoria própria, dentro da diretoria work, a qual está ao lado da diretoria sources:
18/12/14 Programação Imperativa 162
• Assim, tipicamente, para correr programas, colocamo-nos na diretoria de dados e chamamos o programa que está na diretoria sources:
$ ../../sources/a.out < chuva_faro.txt 44
Quer dizer, a partir de agora trabalharemos com duas janelas de comando: uma para compilar, na diretoria sources; e outra para correr programas, na diretoria de dados.
![Page 163: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/163.jpg)
Soma do array • A soma é um indicador estatístico importante:
18/12/14 Programação Imperativa 163
double doubles_sum(const double *a, int n) { double result = 0; for (int i = 0; i < n; i++) result += a[i]; return result; }
void test_doubles_sum(void) { double a[1000]; int n = doubles_get(a); int z = doubles_sum(a, n); printf("%d\n", z); }
$ ./a.out 5 7 3 1 16 $ ./a.out 1 1 1 1 1 1 1 2 2 2 2 2 2 2
21
Em cada passo, o valor de result é a soma “parcial”, isto é, a soma de todos os valores observado “até agora”.
![Page 164: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/164.jpg)
Testes unitários • Em vez de correr os testes interativamente, na janela
de comando, por vezes é mais prático e mais seguro ter um conjunto de testes fixos, programados em funções de teste unitário:
18/12/14 Programação Imperativa 164
void unit_test_doubles_sum(void) { double a1[8] = {6,7,1,8, 9,3,3,5}; assert(doubles_sum(a1, 8) == 42); assert(doubles_sum(a1, 4) == 22); assert(doubles_sum(a1, 2) == 13); assert(doubles_sum(a1, 1) == 6); assert(doubles_sum(a1, 0) == 0); double a2[10] = {1,5,9,13, 17,21,25,29, 33,37}; assert(doubles_sum(a2, 10) == 190); assert(doubles_sum(a2, 5) == 45); }
Ao primeiro assert que falhe, o programa termina, com uma mensagem de erro que indica a linha culpada.
![Page 165: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/165.jpg)
Teste unitário da função doubles_count • Cada função importante virá acompanhada do seu
teste unitário. • Eis o teste unitário da função doubles_count:
18/12/14 Programação Imperativa 165
void unit_test_doubles_count(void) { double a1[16] = {6,7,1,8, 9,3,3,5, 6,7,3,9, 6,1,1,1}; assert(doubles_count(a1, 16, 1) == 4); assert(doubles_count(a1, 16, 9) == 2); assert(doubles_count(a1, 16, 2) == 0); assert(doubles_count(a1, 8, 1) == 1); assert(doubles_count(a1, 8, 2) == 0); assert(doubles_count(a1, 0, 6) == 0); }
É verdade: os testes ocupam mais código do que as funções de cálculo propriamente ditas.
![Page 166: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/166.jpg)
Correndo os testes unitários • Reunimos todos os testes unitário numa
função unit_tests, que será chamada na função main, logo no início:
18/12/14 Programação Imperativa 166
void unit_tests(void) { unit_test_doubles_count(); unit_test_doubles_sum(); // ... }
int main(void) { unit_tests(); // ... return 0; }
Assim, de cada vez que corremos o programa, corremos os testes todos, automaticamente. Se houver azar, veremos logo.
![Page 167: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/167.jpg)
Média • A partir da soma, calcula-se a média:
18/12/14 Programação Imperativa 167
double doubles_mean(const double *a, int n) { return doubles_sum(a, n) / n; }
![Page 168: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/168.jpg)
Máximo de um array • Calcular o valor do maior elemento presente no
array é um problema clássico. • Se o array não for vazio, podemos programar
assim:
• E se o array puder ser vazio? 18/12/14 Programação Imperativa 168
double doubles_max_non_empty(const double *a, int n) { assert(n > 0); double result = a[0]; for (int i = 1; i < n; i++) // i = 1 if (result < a[i]) result = a[i]; return result; }
Em cada passo, o valor de result é o máximo “parcial”, isto é, o maior valor observado “até agora”.
Se n for zero, a asserção falha e o program estoira.
![Page 169: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/169.jpg)
Máximo de um array, caso geral • Convencionamos que o máximo de um array
vazio é menos infinito. • Observe:
18/12/14 Programação Imperativa 169
double doubles_max(const double *a, int n) { double result = -INFINITY; for (int i = 0; i < n; i++) if (result < a[i]) result = a[i]; return result; }
![Page 170: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/170.jpg)
Testes unitários da função doubles_max
18/12/14 Programação Imperativa 170
void unit_test_doubles_max(void) { double a1[16] = {6,7,3,8, 9,3,3,5, 6,7,3,9, 6,1,8,3}; assert(doubles_max(a1, 16) == 9); assert(doubles_max(a1, 4) == 8); assert(doubles_max(a1, 1) == 6); double a2[10] = {32,67,81,23, 27,12,90,13, 75,13}; assert(doubles_max(a2, 10) == 90); assert(doubles_max(a2, 6) == 81); assert(isinf(doubles_max(a2, 0))); double a3[5] = {7e15,3e18,2e14,4e22,3e13}; assert(doubles_max(a3, 5) == 4e22); double a4[5] = {7e-153,3e-185,2e-140,9e-225,3e-213}; assert(doubles_max(a4, 5) == 2e-140); double a5[5] = {-7e200,-3e185,-2e240,-7e225,-3e280}; assert(doubles_max(a5, 5) == -3e185); }
Os testes unitário são longos e chatos, mas utilíssimos!
![Page 171: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/171.jpg)
Argumento do máximo • Por vezes, não nos interessa o máximo, mas
sim a sua posição no array:
18/12/14 Programação Imperativa 171
int doubles_argmax(const double *a, int n) { assert(n > 0); int result = 0; double m = a[0]; for (int i = 1; i < n; i++) // i = 1 if (m < a[i]) { result = i; m = a[result]; } return result; }
Atenção: esta função só pode ser usada com arrays não vazios.
![Page 172: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/172.jpg)
Habilidades com o C • As duas instruções dentro do if podem juntar-
se numa só:
18/12/14 Programação Imperativa 172
int doubles_argmax(const double *a, int n) { assert(n > 0); int result = 0; double m = a[0]; for (int i = 1; i < n; i++) // i = 1 if (m < a[i]) m = a[result = i]; return result; }
OK, mas não abusemos...
![Page 173: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/173.jpg)
Mínimo, argumento do mínimo • São parecidas com as anteriores:
18/12/14 Programação Imperativa 173
double doubles_min(const double *a, int n) { double result = +INFINITY; for (int i = 0; i < n; i++) if (result > a[i]) result = a[i]; return result; }
int doubles_argmin(const double *a, int n) { assert(n > 0); int result = 0; double m = a[0]; for (int i = 1; i < n; i++) // i = 1 if (m > a[i]) m = a[result = i]; return result; }
![Page 174: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/174.jpg)
Testes unitários para todas • Todas estas funções têm o seu teste unitário:
18/12/14 Programação Imperativa 174
void unit_tests(void) { unit_test_doubles_count(); unit_test_doubles_sum(); unit_test_doubles_max(); unit_test_doubles_min(); unit_test_doubles_argmax(); unit_test_doubles_argmin(); }
int main(void) { unit_tests(); // ... return 0; }
![Page 175: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/175.jpg)
Programação Imperativa
Lição n.º 10 Buscas
![Page 176: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/176.jpg)
Buscas • Buscas lineares em arrays. • Operadores lógicos. • Igualdade de arrays. • Reabrindo a consola.
18/12/14 Programação Imperativa 176
![Page 177: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/177.jpg)
Problema da busca • Existe no array um elemento com um dado
valor? • A resposta é sim ou não, representados em C
por 1 e 0, respetivamente. • Alternativamente: qual a posição no array do
primeiro elemento com um dado valor? • Neste caso, a resposta é um número inteiro. • E que resposta devemos dar quando não
existe nenhum elemento com o valor dado? • Por convenção, quando o valor procurado não
existe, a resposta inequívoca é -1. 18/12/14 Programação Imperativa 177
![Page 178: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/178.jpg)
Teste unitário • Eis o protótipo da função de busca em arrays de int:
• Podemos escrever já o teste unitário:
18/12/14 Programação Imperativa 178
int ints_find(const int *a, int n, int x);
void unit_test_ints_find(void) { int a[8] = {6,2,9,1, 4,2,7,5}; assert(ints_find(a, 8, 9) == 2); assert(ints_find(a, 8, 5) == 7); assert(ints_find(a, 8, 6) == 0); assert(ints_find(a, 8, 3) == -1); assert(ints_find(a, 4, 9) == 2); assert(ints_find(a, 4, 5) == -1); assert(ints_find(a, 4, 6) == 0); assert(ints_find(a, 8, 3) == -1); assert(ints_find(a, 1, 9) == -1); assert(ints_find(a, 1, 6) == 0); assert(ints_find(a, 0, 6) == -1); assert(ints_find(a, 0, 4) == -1); }
Note que o teste unitário serve também para esclarecer o significado da função.
A função devolve o índice da primeira ocorrência do valor x no array a (cujo tamanho é n) ou -1, se não houver.
![Page 179: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/179.jpg)
Função ints_find • Esta função é exemplar:
18/12/14 Programação Imperativa 179
int ints_find(const int *a, int n, int x) { for (int i = 0; i < n; i++) if (a[i] == x) return i; return -1; }
Repare bem: dois returns, o primeiro dentro do ciclo, o seguindo após o ciclo. Esta é a única função em que usamos esta técnica.
![Page 180: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/180.jpg)
Aplicação: validação de números de aluno • Queremos um programa para validar
interativamente números de aluno. • O problema tem acesso a um ficheiro
com os números de todos os alunos inscritos.
• Se o número for válido, o programa escreve 1; se não, escreve 0.
• O ficheiro é lido por redireção do input.
18/12/14 Programação Imperativa 180
... 44928 48075 50816 51732 52395 50076 45934 52263 50110 52875 51493 51990 44949 48272 52722 49728 52260 52319 51749 ...
![Page 181: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/181.jpg)
Questão prévia • Se o input é redirigido para o ficheiro, como
podemos depois usar a janela de comando para interagir com o programa?
• Ora bem: temos de “reabrir a consola”! • Isso faz-se assim em Windows:
• E assim em Unix:
18/12/14 Programação Imperativa 181
freopen("/dev/tty", "r", stdin);
freopen("CON", "r", stdin);
![Page 182: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/182.jpg)
Tarefa de validação • Observe:
18/12/14 Programação Imperativa 182
void task_validate_student(void) { int a[500]; int n = ints_get(a); freopen("/dev/tty", "r", stdin); // Unix // freopen("CON", "r", stdin); // Windows int x; while (scanf("%d", &x) != EOF) { int z = ints_find(a, n, x); printf("%d\n", z != -1); } }
Repare: a expressão z != -1 vale 1 se z for diferente de -1 e vale 0 se z for igual a -1, tal como convém.
![Page 183: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/183.jpg)
Correndo na consola • O comando que invoca o programa realiza a
redireção do input:
18/12/14 Programação Imperativa 183
$ ../../sources/a.out < inscritos.txt 33445 0 45634 0 52092 1 52080 1 50000 0 41895 1 52230 0 40758 1 $
O ficheiro inscritos.txt está arrumado de acordo com as nossas convenções, numa subdiretoria da diretoria work, a qual está a par da diretoria sources, onde reside o executável a.out.
![Page 184: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/184.jpg)
Busca do fim para o princípio • Por vezes, queremos a última ocorrência. • Nesse caso, procuramos do fim para o
princípio:
18/12/14 Programação Imperativa 184
int ints_find_last(const int *a, int n, int x) { int result = n-1; while (result >= 0 && a[result] != x) result--; return result; } Note que neste caso não há interesse
em usar o esquema dos dois returns, pois a variável result tomará o valor -1 “naturalmente”, quando a busca falhar.
![Page 185: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/185.jpg)
Operadores lógicos
18/12/14 Programação Imperativa 185
&& conjunção || disjunção ! negação
![Page 186: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/186.jpg)
Variante: obter todas as ocorrências • Se queremos não a primeira ocorrência mas
sim todas as ocorrências, precisamos de um array:
18/12/14 Programação Imperativa 186
int ints_find_all(const int *a, int n, int x, int *b) { int result = 0; for (int i = 0; i < n; i++) if (a[i] == x) b[result++] = i; return result; } Note que b é um array de índices.
Ficará vazio se x não ocorrer em a.
![Page 187: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/187.jpg)
Array das primeiras ocorrências • Em geral, cada valor pode ocorrer várias vezes. • Queremos agora calcular o array das primeiras
ocorrências de cada valor, dito a “essência” do array:
18/12/14 Programação Imperativa 187
int ints_nub(const int *a, int n, int *b) { int result = 0; for (int i = 0; i < n; i++) if (ints_find(b, result, a[i]) == -1) b[result++] = a[i]; return result; } Palavras para quê?
![Page 188: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/188.jpg)
Testando ints_nub • Eis uma função de teste, como habitualmente:
18/12/14 Programação Imperativa 188
void test_ints_nub(void) { int a[1000]; int n = ints_get(a); int b[1000]; int m = ints_nub(a, n, b); ints_println_basic(b, m); }
$ ./a.out 5 7 5 5 2 7 3 2 2 3 3 1 1 7 5 7 2 3 1 $ ./a.out 3 3 3 3 3 4 4 4 3 3 3 5 5 5 4 4 4 1 1 3 4 5 1 $
![Page 189: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/189.jpg)
Aplicação: quantos alunos na aula prática? • Queremos saber quantos alunos vieram à aula,
com base no registo das submissões ao Mooshak. • De cada submissão, retiramos o número de aluno
e guardamos os números num ficheiro. • Lemos para um array e fazemos ints_nub:
18/12/14 Programação Imperativa 189
void task_students_in_lab(void) { int a[500]; int n = ints_get(a); int b[500]; int m = ints_nub(a, n, b); printf("%d\n", m); }
$ ../../sources/a.out < submissoes.txt 25 $
... 49863 49863 51767 52727 51767 51767 52495 51767 51767 49863 49863 49863 45934 51493 45934 51493 50372 50372 49863 50372 50372 ...
![Page 190: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/190.jpg)
Igualdade de arrays • Para verificar se dois arrays a e b são iguais, isto é, se
têm os mesmos elementos, pela mesma ordem, não basta escrever a == b.
• É preciso programar uma função ad hoc:
18/12/14 Programação Imperativa 190
int ints_equal_arrays( const int *a, const int n, const int *b, int m) { int result = n == m; int i = 0; while (result && i < n) if (a[i] != b[i]) result = 0; else i++; return result; }
Se os tamanhos forem diferentes, os arrays não são iguais. Sendo os tamanhos iguais, procura-se o primeiro par de elementos diferentes. Logo que se encontre, sabe-se que os arrays não são iguais. Não encontrando, os arrays são iguais.
Ao longo do ciclo, o valor da variável result significa “os arrays são iguais até agora”, por assim dizer.
![Page 191: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/191.jpg)
Testando a igualdade de arrays • Se lermos os arrays com ints_get, temos de
reabrir a consola:
18/12/14 Programação Imperativa 191
void test_ints_equal_arrays(void) { int a[1000]; int n = ints_get(a); freopen("/dev/tty", "r", stdin); // Unix // freopen("CON", "r", stdin); // Windows int b[1000]; int m = ints_get(b); int z = ints_equal_arrays(a, n, b, m); printf("%d\n", z); }
![Page 192: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/192.jpg)
Testes unitários com igualdade de arrays • Observe, por exemplo:
18/12/14 Programação Imperativa 192
void unit_test_ints_nub(void) { int a1[12] = {6,2,6,9, 4,2,9,9, 9,2,1,2}; int b1[12]; int m1 = ints_nub(a1, 12, b1); int z1[5] = {6,2,9,4,1}; assert(ints_equal_arrays(b1, m1, z1, 5)); int a2[6] = {1,2,3,3,2,1}; int b2[6]; int m2 = ints_nub(a2, 6, b2); int z2[3] = {1,2,3}; assert(ints_equal_arrays(b2, m2, z2, 3)); int a3[5] = {8,8,8,8,8}; int b3[5]; int m3 = ints_nub(a3, 5, b3); assert(m3 == 1 && b3[0] == 8); }
![Page 193: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/193.jpg)
Programação Imperativa
Lição n.º 11 Subarrays
![Page 194: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/194.jpg)
Subarrays • Subarrays em C. • Grupos. • Remoção de duplicados.
18/12/14 Programação Imperativa 194
![Page 195: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/195.jpg)
Subarrays • Atenção: não há subarrays em C. • O que há é uma maneira de nos referirmos,
nas funções, a uma parte de um array. • Na verdade, já observámos isso, por exemplo
nas funções de teste unitário:
18/12/14 Programação Imperativa 195
void unit_test_doubles_sum(void) { double a1[8] = {6,7,1,8, 9,3,3,5}; assert(doubles_sum(a1, 8) == 42); assert(doubles_sum(a1, 4) == 22); assert(doubles_sum(a1, 2) == 13); assert(doubles_sum(a1, 1) == 6); assert(doubles_sum(a1, 0) == 0); }
O array a tem 8 elementos, mas ao somar os 4 primeiros elementos é como se estivéssemos a somar todos os elementos do subarray inicial de a, com 4 elementos.
![Page 196: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/196.jpg)
Subarrays gerais • Também podemos ter subarrays não iniciais, como
ilustram as seguintes funções de teste unitário:
18/12/14 Programação Imperativa 196
void unit_test_subarrays_sum(void) { double a[8] = {4,9,4,4, 5,2,7,5}; assert(doubles_sum(a, 8) == 40); assert(doubles_sum(a, 5) == 26); assert(doubles_sum(a+3, 5) == 23); assert(doubles_sum(a+2, 4) == 15); }
void unit_test_subarrays_max(void) { double a[8] = {4,9,4,4, 5,2,7,5}; assert(doubles_max(a, 8) == 9); assert(doubles_max(a, 5) == 9); assert(doubles_max(a+3, 5) == 7); assert(doubles_max(a+2, 4) == 5); }
Por exemplo, a expressão doubles_sum(a+3, 5) representa a soma dos valores de a[3], a[4], a[5], a[6] e a[7].
Por exemplo, a expressão doubles_max(a+2, 4) representa o máximo dos valores de a[2], a[3], a[4] e a[5].
![Page 197: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/197.jpg)
a + k • Em geral, sendo a um array e k um número
inteiro, a expressão a + k representa o subarray de a que começa no elemento a[k].
• Tal como com os arrays em geral, ao especificarmos um subarray, normalmente indicamos o seu tamanho, isto é, o número de elementos que queremos processar.
• Frequentemente, interessa-nos o resto do array; por outras palavras, se o array a tiver n elementos, o array a + k terá n – k elementos.
18/12/14 Programação Imperativa 197
![Page 198: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/198.jpg)
Exemplos: soma recursiva, máximo recursivo • Usando subarrays, podemos processar arrays
recursivamente. • Observe, com atenção:
18/12/14 Programação Imperativa 198
double doubles_sum_r(double *a, int n) { return n == 0 ? 0 : a[0] + doubles_sum_r(a+1, n-1); } double doubles_max_r(double *a, int n) { return n == 0 ? -INFINITY : max(a[0], doubles_max_r(a+1, n-1)); }
Em C, normalmente não se programa assim, mas a técnica é interessante e válida, em geral.
![Page 199: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/199.jpg)
Aplicação: problema da via do infante • Dispomos de um ficheiro com o número de
carros que passaram no pórtico de Loulé, minuto a minuto, num certo dia.
• Queremos saber qual foi o quarto de hora com mais trânsito.
• Solução: ler o ficheiro para um array e somar os sucessivos subarrays de tamanho 15, guardando os resultados noutro array.
• Depois, obter o argumento do máximo neste array e identificar o quarto de hora respetivo.
18/12/14 Programação Imperativa 199
... 16 5 4 10 7 3 18 16 7 15 7 18 20 9 3 4 16 3 0 20 9 15 ...
![Page 200: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/200.jpg)
Somar de 15 em 15 • Queremos somar os números de carros que
passaram em cada minuto, para cada quarto de hora:
18/12/14 Programação Imperativa 200
int ints_sums_by_15(const int *a, int n, int *b) { int result = 0; for (int i = 0; i < n; i += 15) b[result++] = ints_sum(a+i, min(15, n - i)); return result; } Repare na utilização da função
min, para o caso geral de n não ser múltiplo de 15.
A variável de controlo cresce de 15 em 15.
![Page 201: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/201.jpg)
Somar grupos de comprimento fixo • Com um pouco mais de esforço,
programamos uma função mais geral, que soma grupos de comprimento dado:
18/12/14 Programação Imperativa 201
int ints_sums_by_tuple (const int *a, int n, int x, int *b) { int result = 0; for (int i = 0; i < n; i += x) b[result++] = ints_sum(a+i, min(x, n - i)); return result; }
![Page 202: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/202.jpg)
Função de teste • Observe:
18/12/14 Programação Imperativa 202
void test_infant(void) { int a[1440]; int n = ints_get(a); int b[1440]; int m = ints_sums_by_tuple(a, n, 15, b); ints_println_basic(b, m); int z = ints_argmax(b, m); printf("%d %d\n", z, b[z]); printf("%d %d\n", z / 4, z % 4); }
$ ../../sources/a.out < infant_data.txt 34 35 31 18 12 15 16 17 12 16 15 13 15 16 14 15 11 12 14 15 17 16 25 29 37 23 42 65 43 94 76 110 106 104 102 144 167 152 164 190 240 230 176 157 177 148 139 155 201 180 146 160 174 168 170 220 146 144 145 135 145 133 187 150 126 122 147 144 152 192 201 182 195 153 238 242 166 205 168 182 147 113 132 121 186 128 138 145 143 103 106 60 65 49 48 24 75 242 18 3 $
Para estes dados, o quarto de hora com mais trânsito foi o que começou às 18:45.
![Page 203: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/203.jpg)
Problema dos grupos • Dado um array de double, construir um outro array
(de int) com os comprimentos dos grupos de elementos consecutivos iguais.
18/12/14 Programação Imperativa 203
void unit_test_doubles_groups(void) { double a1[16] = {4,9,4,4, 4,7,7,7, 7,7,8,6, 6,6,6,4}; int b1[16]; int z1[7] = {1,1,3,5,1,4,1}; int m1 = doubles_groups(a1, 16, b1); assert(ints_equal_arrays(b1, m1, z1, 7)); double a2[8] = {4,4,4,4, 4,4,4,4}; int b2[8]; int z2[1] = {8}; int m2 = doubles_groups(a2, 8, b2); assert(ints_equal_arrays(b2, m2, z2, 1)); }
int doubles_groups(const double *a, int n, int *b);
![Page 204: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/204.jpg)
Contar enquanto... • Vamos basear-nos numa função que conta os
elementos à cabeça do array que têm um dado valor. • A função conta “enquanto” os elementos forem
iguais ao valor dado:
18/12/14 Programação Imperativa 204
void unit_test_doubles_count_while(void) { double a[16] = {4,4,4,3, 5,9,9,5, 5,5,5,5, 5,5,1,1}; assert(doubles_count_while(a, 16, 4) == 3); assert(doubles_count_while(a, 16, 7) == 0); assert(doubles_count_while(a+4, 12, 5) == 1); assert(doubles_count_while(a+4, 12, 2) == 0); assert(doubles_count_while(a+8, 8, 5) == 6); assert(doubles_count_while(a+8, 8, 3) == 0); assert(doubles_count_while(a+14, 2, 1) == 2); assert(doubles_count_while(a+14, 2, 3) == 0); }
int doubles_count_while(const double *a, int n, double x);
![Page 205: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/205.jpg)
Função doubles_count_while • Na verdade, é uma variante da função de
busca:
18/12/14 Programação Imperativa 205
int doubles_count_while (const double *a, int n, double x)
{ int result = 0; while (result < n && a[result] == x) result++; return result; }
![Page 206: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/206.jpg)
Função doubles_groups • Por cada grupo, acrescenta-se o comprimento
do grupo ao array de saída e avança-se no array de entrada:
18/12/14 Programação Imperativa 206
int doubles_groups(const double *a, int n, int *b) { int result = 0; int i = 0; while (i < n) { int z = doubles_count_while(a+i, n-i, a[i]); b[result++] = z; i += z; } return result; }
Em cada passo do ciclo, detetamos um novo grupo, à cabeça do subarray seguinte. Esse grupo é formado pelos elementos que são iguais ao primeiro do grupo.
Aprenda muito bem esta função!
![Page 207: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/207.jpg)
Problema da remoção de duplicados • Dado um array de double, construir um outro array
(de double) com um exemplar de cada grupo de elementos consecutivos iguais.
18/12/14 Programação Imperativa 207
void unit_test_doubles_unique(void) { double a1[16] = {4,9,4,4, 4,7,7,7, 7,7,8,6, 6,6,6,4}; double b1[16]; double z1[7] = {4,9,4,7,8,6,4}; int m1 = doubles_unique(a1, 16, b1); assert(doubles_equal_arrays(b1, m1, z1, 7)); double a2[8] = {4,4,4,4, 4,4,4,4}; double b2[8]; double z2[1] = {4}; int m2 = doubles_unique(a2, 8, b2); assert(doubles_equal_arrays(b2, m2, z2, 1)); }
int doubles_unique(const double *a, int n, double *b);
Não confunda com a função “nub”. Essa guarda a primeira ocorrência no array. Esta guarda um exemplar de cada grupo de elementos consecutivos iguais.
![Page 208: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/208.jpg)
Função doubles_unique • Por cada grupo, acrescenta-se um elemento
do grupo ao array de saída e avança-se no array de entrada:
18/12/14 Programação Imperativa 208
int doubles_unique(const double *a, int n, double *b) { int result = 0; int i = 0; while (i < n) { int z = doubles_count_while(a+i, n-i, a[i]); b[result++] = a[i]; i += z; } return result; }
É praticamente igual à outra, não é?
![Page 209: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/209.jpg)
Versões recursivas • As versões recursivas das funções groups e unique
também são muito interessantes:
18/12/14 Programação Imperativa 209
int doubles_groups_r(const double *a, int n, int *b) { int result = 0; if (n > 0) { int z = doubles_count_while(a, n, a[0]); b[0] = z; result = 1 + doubles_groups_r(a+z, n-z, b+1); } return result; }
int doubles_unique_r(const double *a, int n, double *b) { int result = 0; if (n > 0) { int z = doubles_count_while(a, n, a[0]); b[0] = a[0]; result = 1 + doubles_unique_r(a+z, n-z, b+1); } return result; }
![Page 210: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/210.jpg)
a[0] ≡ *a • Em C, não se escreve a[0]. Em vez disso
escreve-se *a, que significa o mesmo e usa menos carateres...
• Por exemplo:
18/12/14 Programação Imperativa 210
int doubles_unique_r2(const double *a, int n, double *b) { int result = 0; if (n > 0) { int z = doubles_count_while(a, n, *a); *b = *a; result = 1 + doubles_unique_r2(a+z, n-z, b+1); } return result; }
Vá-se habituando...
![Page 211: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/211.jpg)
Programação Imperativa
Lição n.º 12 Arrays ordenados
![Page 212: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/212.jpg)
Arrays ordenados • Renque. • Busca dicotómica. • Método da bisseção.
18/12/14 Programação Imperativa 212
![Page 213: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/213.jpg)
Problema do renque • O renque de um valor num array é o número
de elementos do array cujo valor é menor que esse valor:
18/12/14 Programação Imperativa 213
int ints_rank_general(const int *a, int n, int x);
void unit_test_rank_general(void) { int a[10] = {8,3,9,8,4, 4,2,7,5,7}; assert(ints_rank_general(a, 10, 5) == 4); assert(ints_rank_general(a, 10, 12) == 10); assert(ints_rank_general(a, 10, 1) == 0); assert(ints_rank_general(a, 10, 2) == 0); assert(ints_rank_general(a, 10, 3) == 1); assert(ints_rank_general(a, 10, 7) == 5); assert(ints_rank_general(a, 5, 7) == 2); assert(ints_rank_general(a, 5, 3) == 0); assert(ints_rank_general(a, 5, 9) == 4); assert(ints_rank_general(a, 1, 5) == 0); assert(ints_rank_general(a, 1, 9) == 1); assert(ints_rank_general(a, 0, 5) == 0); }
Chamamos renque geral porque se pode aplicar a um array qualquer. Para arrays ordenados, usaremos funções especializadas.
![Page 214: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/214.jpg)
int ints_rank_general_r(const int *a, int n, int x) { int result = 0; if (n > 0) result = (*a < x) + ints_rank_general_r(a+1, n-1, x); return result; }
Renque geral • Programa-se nas calmas:
• A versão recursiva também é interessante:
18/12/14 Programação Imperativa 214
int ints_rank_general(const int *a, int n, int x) { int result = 0; for (int i = 0; i < n; i++) if (a[i] < x) result++; return result; }
É uma variante da função de contagem.
Note bem: o valor aritmético das expressões lógicas é 0 ou 1.
![Page 215: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/215.jpg)
Arrays ordenados • Um array está ordenado se o valor de cada elemento
é menor ou igual ao valor do elemento seguinte. • Essa propriedade é representada pela seguinte função
lógica:
18/12/14 Programação Imperativa 215
void unit_test_ints_is_sorted(void) { int a[10] = {1,2,5,5,5, 6,8,8,9,9}; assert(ints_is_sorted(a, 10)); assert(ints_is_sorted(a, 1)); assert(ints_is_sorted(a, 0)); int b[10] = {3,5,5,2,4, 4,8,8,2,5}; assert(!ints_is_sorted(b, 10)); assert(ints_is_sorted(b, 3)); assert(!ints_is_sorted(b, 5)); assert(ints_is_sorted(b+3, 5)); assert(!ints_is_sorted(b+5, 5)); }
int ints_is_sorted(int *a, int n);
![Page 216: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/216.jpg)
Função is_sorted • Procura-se o primeiro par de elementos
consecutivos fora de ordem:
• Também a versão recursiva:
18/12/14 Programação Imperativa 216
int ints_is_sorted(int *a, int n) { for (int i = 1; i < n; i++) if (a[i-1] > a[i]) return 0; return 1; }
int ints_is_sorted_r(int *a, int n) { return n <= 1 || (*a <= a[1] && ints_is_sorted_r(a+1, n-1)); }
Um array está ordenado se o seu tamanho for menor ou igual a 1 ou, sendo maior que 1, se o valor do primeiro elemento for menor ou igual ao do segundo e o resto do array (tirando o primeiro elemento) estiver ordenado.
![Page 217: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/217.jpg)
Renque em arrays ordenados • O renque geral inspeciona todos os elementos
do array e conta aqueles que são menores que o valor dado.
• Se o array estiver ordenado, conseguimos calcular o renque sem inspecionar os elementos todos.
• Aliás, conseguimos fazê-lo inspecionando relativamente poucos elementos.
• Vejamos como. • Por hipótese, temos um array a, ordenado, com
tamanho n, e é dado um valor x: queremos calcular o número de elementos de a cujo valor é menor que x.
18/12/14 Programação Imperativa 217
![Page 218: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/218.jpg)
Calculando o renque em arrays ordenados • Tomemos um elemento qualquer de a, a[m]. • Se x<=a[m], então x<=a[m+1], x<=a[m+2], etc., pois
o array está ordenado; logo, todos os elementos de a cujo valor é menor que x estão à esquerda de a[m].
• Inversamente, se x>a[m], então x>a[m-1], x>a[m-2], etc., porque o array está ordenado; logo, o valor de cada um dos elementos à esquerda de a[m] é menor que x e o valor de a[m] também.
• Sendo assim, no primeiro caso, basta contar os elementos de valor menor que x no subarray inicial com m elementos; no segundo, há pelo menos m+1 elementos com valor menor que x, a que se juntam os elementos menores que x no subarray a+(m+1).
18/12/14 Programação Imperativa 218
![Page 219: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/219.jpg)
Função ints_rank, recursiva • Veja com atenção:
18/12/14 Programação Imperativa 219
int ints_rank_r(const int *a, int n, int x) { int result = 0; if (n > 0) { int m = n / 2; if (x <= a[m]) result = ints_rank_r(a, m, x); else result = m+1 + ints_rank_r(a+m+1, n-(m+1), x); } return result; }
Por uma questão de simetria, que convém computacionalmente, escolhemos o elemento a[m] a meio do array.
![Page 220: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/220.jpg)
Função ints_rank, iterativa • Veja com muita atenção:
18/12/14 Programação Imperativa 220
int ints_rank(const int *a, int n, int x) { int result = 0; while (n > 0) { int m = n / 2; if (x <= a[m]) n = m; else { result += m+1; a += m+1; n -= m+1; } } return result; }
Aqui, a contagem parcial mantém-se e o array encolhe, por assim dizer, pois a segunda metade não interessa.
Aqui, entram na contagem os m+1 elementos à esquerda, pois são todos menores que x, e esses m+1 elementos são excluídos do processamento futuro, por assim dizer (pois já foram contabilizados).
![Page 221: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/221.jpg)
Busca dicotómica • Queremos agora calcular o índice da primeira
ocorrência de um valor dado num array ordenado, devolvendo -1, se não houver.
• Baseamo-nos no renque:
18/12/14 Programação Imperativa 221
int ints_bfind(const int *a, int n, int x) { int r = ints_rank(a, n, x); return r < n && a[r] == x ? r : -1; }
Se existirem no array ordenado elementos com valor x, eles virão todos de seguida e o primeiro deles estará na posição correspondente ao renque de x no array; inversamente, se o valor do elemento nessa posição não for igual a x, concluímos que não existe no array nenhum elemento com valor x.
![Page 222: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/222.jpg)
Complexidade da busca linear • A busca linear, implementada pela função ints_find, precisa de
inspecionar todos os elementos do array, no pior caso, isto é, quando o valor procurado não existe. Por isso, o trabalho computacional e, consequentemente, o tempo de execução são proporcionais ao tamanho do array, no pior caso.
• Para a busca linear, o melhor caso é aquele em que o elemento procurado é o que está na primeira posição, mas esse caso é contrabalançado por aquele em que o elemento procurado está na última posição.
• Portanto, se o array tiver 1000 elementos, por exemplo, então, nos casos em que o elemento procurado não existe, a comparação a[i] == x realizar-se-á 1000 vezes; nos casos em que existe, realizar-se-á, em média, 500 vezes.
18/12/14 Programação Imperativa 222
int ints_find(const int *a, int n, int x) { for (int i = 0; i < n; i++) if (a[i] == x) return i;
return -1; }
![Page 223: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/223.jpg)
Complexidade da busca dicotómica • Na busca dicotómica, o tamanho do array em
observação é dividido ao meio, sensivelmente, em cada passo do ciclo.
• Por exemplo, se o array tiver 1000 elementos, após o primeiro passo do ciclo só nos interessa um subarray com 500 elementos; após o segundo passo, só nos interessa o subarray com 250 elementos; depois 125, 62, 31, 15, 8, 4, 2, 1.
• Quer dizer: o ciclo while terá dado 10 voltas e a comparação x <= a[m] terá sido realizada 10 vezes.
• Conclusão: a busca dicotómica é muito melhor que a busca linear.
• No entanto, só dá com array ordenados. 18/12/14 Programação Imperativa 223
![Page 224: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/224.jpg)
Complexidade logarítmica • Portanto, em geral, o tempo usado para encontrar
um elemento no array, ou decidir que ele não existe, é proporcional ao logaritmo do número de elementos: T = K * log2N.
• Por exemplo, se uma busca dicotómica num array com 1000 elementos demorar 200 nanossegundos, num array de 2000 elementos demorará 220 nanossegundos (log21000 ≈ 10, log22000 ≈ 11) e num array de 1000000 elementos demorará 400 nanossegundos (log21000000 ≈ 20).
• Dizemos que a busca dicotómica tem complexidade logarítmica.
• A busca linear tem complexidade linear. 18/12/14 Programação Imperativa 224
![Page 225: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/225.jpg)
Busca dicotómica clássica • Frequentemente, a busca dicotómica é programada
delimitando o intervalo de busca, por meio de dois índices. • Em cada passo, o intervalo de busca encolhe para pouco
menos de metade:
18/12/14 Programação Imperativa 225
int ints_bfind_classic(const int *a, int n, int x) { int i = 0; int j = n-1; while (i <= j) { int m = i + (j-i) / 2; if (x < a[m]) j = m-1; else if (x > a[m]) i = m+1; else return m; } return -1; }
Em cada passo, o intervalo de busca é delimitado pelos valores das variáveis i e j.
Note bem: esta função só é equivalente à outra no caso em que os arrays não tem valores duplicados. Para arrays com valores duplicados, a outra dá a posição da primeira ocorrência enquanto esta dá a posição de uma ocorrência, não necessariamente a primeira.
![Page 226: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/226.jpg)
Método da bisseção • A ideia de dividir ao meio o intervalo de busca em
cada passo ocorre em vários problemas. • Por exemplo: calcular a raiz quadrada de um número
positivo, x, maior que 1. • A raiz existe no intervalo entre 1 e x. • Observamos o ponto médio do intervalo, m. • Se m*m for igual a x, ou estiver muito perto de x,
tomamos m como raiz quadrada de x. • Se não, se m*m for maior que x, a raiz existirá no
intervalo de 1 a m; se m*m for menor que x, a raiz existirá no intervalo de m a x.
• Etc.
18/12/14 Programação Imperativa 226
![Page 227: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/227.jpg)
Método da bisseção, recursivo • Calcular recursivamente a raiz de x no intervalo de a
a b:
18/12/14 Programação Imperativa 227
double square_root_r(double x, double a, double b) { assert(x > 1); assert(a < b); assert(a*a - x < 0), assert(b*b - x > 0); double result = (a + b) / 2; if (fabs(result * result - x) >= 0.000001) { if (result * result < x) result = square_root_r(x, result, b); else result = square_root_r(x, a, result); } return result; }
![Page 228: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/228.jpg)
Método da bisseção, iterativa • A versão iterativa deriva-se diretamente da anterior:
18/12/14 Programação Imperativa 228
double square_root_i(double x, double a, double b) { assert(x > 1); assert(a < b); assert(a*a - x < 0), assert(b*b - x > 0); double result = (a + b) / 2; while (fabs(result * result - x) >= 0.000001) { if (result * result < x) a = result; else b = result; result = (a + b) / 2; } return result; }
![Page 229: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/229.jpg)
Função principal, raiz quadrada • Para demonstração, a função principal invoca
ou a versão iterativa ou a versão recursiva:
18/12/14 Programação Imperativa 229
double square_root(double x) { assert(x > 0); double result; if (x > 1) result = square_root_r(x, 1.0, x); // result = square_root_i(x, 1.0, x); else if (x < 1) result = 1 / square_root(1/x); else result = 1; return result; }
A raiz de números menores que 1 calcula-se invertendo a raiz do inverso (o qual será maior que 1...)
![Page 230: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/230.jpg)
Função de teste, raiz quadrada
18/12/14 Programação Imperativa 230
void test_square_root(void) { double x; while (scanf("%lf", &x) != EOF) { double z = square_root(x); printf("%.6f\n", z); } }
$ ./a.out 6 2.449490 25 5.000000 0.01 0.100000 2 1.414214 0.5 0.707107 64 8.000000 1.44 1.200000 $
Note bem: o método da bisseção é interessante mas há outros métodos melhores para calcular a raiz quadrada e, mais geralmente, para resolver equações não lineares. Esses métodos convergem mais depressa para a solução, pois dividem o intervalo de busca mais “agressivamente”.
![Page 231: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/231.jpg)
Programação Imperativa
Lição n.º 13 Ordenação de arrays
![Page 232: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/232.jpg)
Ordenação de arrays • Troca de dois elementos num array. • Inserção em arrays ordenados. • Insertion sort. • Fusão de arrays ordenados. • Merge sort.
18/12/14 Programação Imperativa 232
![Page 233: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/233.jpg)
Trocando dois elementos num array • Ao ordenar um array, é frequente precisarmos
de trocar dois elementos do array. • Provavelmente, esses dois elementos estariam
fora de ordem; ao trocá-los, ficam na ordem certa.
• Em arrays de int, usaremos a função ints_exchange:
18/12/14 Programação Imperativa 233
void ints_exchange(int *a, int x, int y) { int m = a[x]; a[x] = a[y]; a[y] = m; }
Em arrays de double, será doubles_exchange.
![Page 234: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/234.jpg)
Inserção em array ordenado • Por hipótese, precisamos de acrescentar um
novo elemento a um array que está ordenado. • Ora, depois de acrescentarmos o novo
elemento, no fim do array, talvez o array deixe de estar ordenado.
• Se quisermos que ele fique ordenado de novo, teremos de trocar alguns elementos.
• Vejamos como fazer isso. • O problema é, portanto, ordenar um array
recém-acrescentado, ex-ordenado, por assim dizer...
18/12/14 Programação Imperativa 234
![Page 235: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/235.jpg)
Array recém-acrescentado, ex-ordenado • Num array destes, o único elemento que pode
estar fora de ordem é o último. • Ele estará fora de ordem se for menor que o
elemento à sua esquerda. • Nesse caso, trocamo-lo com esse. • Depois disso, o subarray que começa no nosso
elemento estará ordenado. • Mas o subarray que termina no nosso
elemento talvez não. • Se não estiver, aplica-se a mesma técnica a
este subarray. 18/12/14 Programação Imperativa 235
![Page 236: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/236.jpg)
Função ints_sort_last • Ordena um array em que o único elemento
fora de ordem é último: • Versão recursiva:
• Versão iterativa:
18/12/14 Programação Imperativa 236
void ints_sort_last_r(int *a, int n) { if (n > 1 && a[n-2] > a[n-1]) { ints_exchange(a, n-2, n-1); ints_sort_last_r(a, n-1); } }
void ints_sort_last(int *a, int n) { while (n > 1 && a[n-2] > a[n-1]) { ints_exchange(a, n-2, n-1); n--; } }
![Page 237: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/237.jpg)
Função ints_insert • Após os preparativos anteriores, inserir num
array ordenado é simples: acrescenta-se no fim e depois ordena-se com sort_last:
18/12/14 Programação Imperativa 237
int ints_insert(int *a, int n, int x) { int result = n; a[result++] = x; ints_sort_last(a, result); return result; }
![Page 238: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/238.jpg)
Função de teste • Eis uma função de teste, para experimentar a
inserção em arrays ordenados:
18/12/14 Programação Imperativa 238
void test_ints_insert(void) { int a[1000]; int n = 0; int x; while (scanf("%d", &x) != EOF) { n = ints_insert(a, n, x); ints_println_basic(a, n); } }
$ ./a.out 4 4 9 4 9 2 2 4 9 6 2 4 6 9 2 2 2 4 6 9 99 2 2 4 6 9 99 55 2 2 4 6 9 55 99 3 2 2 3 4 6 9 55 99 $
![Page 239: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/239.jpg)
Algoritmo insertionsort • Temos um array a, com tamanho n, que queremos ordenar. • O subarray de a com 1 elemento está ordenado. • O subarray de a com 2 elementos pode não estar ordenado,
mas, se não estiver, só o último elemento está fora de ordem, no subarray; nesse caso, ordenemo-lo com ints_sort_last.
• O subarray de a com 3 elementos pode não estar ordenado, mas o subarray com 2 já está; só o último elemento do subarray poderá estar fora de ordem; nesse caso, ordenemo-lo com ints_sort_last.
• E assim sucessivamente. • Na verdade, podemos ordenar cada subarray com
inst_sort_last, às cegas, sem verificar se o último elemento está fora de ordem, pois se não estiver nada acontece.
• No fim, o array a, com n elementos, estará ordenado. • Este algoritmo chama-se insertionsort. 18/12/14 Programação Imperativa 239
![Page 240: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/240.jpg)
Função ints_isort • Fica muito simples, pois quem faz o trabalho é
a função inst_sort_last:
18/12/14 Programação Imperativa 240
void ints_isort(int *a, int n) { for (int i = 2; i <= n; i++) ints_sort_last(a, i); }
Mais um, para a nossa coleção de algoritmos!
![Page 241: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/241.jpg)
Testando a função ints_isort • Lemos sucessivamente arrays, com ints_get, e
escrevemo-los, com ints_println_basic, antes e depois de ordenar.
18/12/14 Programação Imperativa 241
void test_ints_isort(void) { int a[1000]; int n; while ((n = ints_get(a)) != 0) { ints_println_basic(a, n); ints_isort(a, n); ints_println_basic(a, n); freopen("/dev/tty", "r", stdin); // Unix // freopen("CON", "r", stdin); // Windows printf("------\n"); } }
$ ./a.out 7 3 9 7 5 3 8 12 1 7 7 3 9 7 5 3 8 12 1 7 1 3 3 5 7 7 7 8 9 12 ------ 76 34 8 38 4 98 46 34 75 12 76 34 8 38 4 98 46 34 75 12 4 8 12 34 34 38 46 75 76 98 ------ $
Observe a técnica de leitura, na condição do while.
![Page 242: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/242.jpg)
Fusão de arrays ordenados • Problema: dados dois arrays ordenados,
construir um terceiro com os elementos de ambos, também ordenado.
• Como NÃO fazer: copiar cada um dos arrays para o array resultado e ordenar no fim, assim:
18/12/14 Programação Imperativa 242
int ints_merge_bad(const int *a, int n, const int *b, int m, int *c) { int result = ints_copy(a, n, c); result += ints_copy(b, m, c + result); ints_isort(c, result); return result; }
Não estaria mal pensado, mas há maneiras de fazer isto muito mais eficientes, ainda que bem menos simplórias.
![Page 243: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/243.jpg)
A propósito: copiar arrays • Copiar arrays, elemento a elemento, é simples:
• Mas é melhor recorrer à função memmove, do C:
18/12/14 Programação Imperativa 243
int ints_copy(const int *a, int n, int *b) { for (int i = 0; i < n; i++) b[i] = a[i]; return n; }
int ints_copy(const int *a, int n, int *b) { if (n < 0) n = 0; memmove(b, a, n * sizeof(int)) return n; }
A função memmove copia o número de bytes indicado no terceiro argumento, a partir da posição de memória onde começa o array indicado no segundo argumento para a posição de memória onde começa o array indicado no primeiro argumento.
![Page 244: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/244.jpg)
Técnica da fusão de arrays • Para fundir dois arrays, comparamos, em cada
passo, o elemento corrente de um array com o elemento corrente do outro.
• O menor dos dois é copiado para o array de saída e o array de onde esse elemento provém avança.
• Quando um dos arrays chegar ao fim, copia-se o resto do outro para o array de saída.
• Assim, o trabalho faz-se com uma passagem em cada array.
18/12/14 Programação Imperativa 244
![Page 245: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/245.jpg)
Função ints_merge • É muito interessante:
18/12/14 Programação Imperativa 245
int ints_merge (const int *a, int n,
const int *b, int m, int *c) { int result = 0; int i = 0; int j = 0; while (i < n && j < m) if (a[i] <= b[j]) c[result++] = a[i++]; else c[result++] = b[j++]; result += ints_copy(a + i, n - i, c+result); result += ints_copy(b + j, m - j, c+result); return result; }
![Page 246: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/246.jpg)
Testando a fusão • Lemos dois arrays, ordenamos com ints_isort,
mostramos os arrays ordenados, fundimos e mostramos o resultado da fusão:
18/12/14 Programação Imperativa 246
void test_ints_merge(void) { int a[1000]; int n = ints_get(a); freopen("/dev/tty", "r", stdin); // Unix // freopen("CON", "r", stdin); // Windows int b[1000]; int m = ints_get(b); ints_println_basic(a, n); ints_println_basic(b, m); ints_isort(a, n); ints_isort(b, m); ints_println_basic(a, n); ints_println_basic(b, m); int c[2000];
int p = ints_merge(a, n, b, m, c); ints_println_basic(c, p); }
$ ./a.out 6 5 8 2 ^D 6 4 8 7 5 3 6 5 8 2 6 4 8 7 5 3 2 5 6 8 3 4 5 6 7 8 2 3 4 5 5 6 6 7 8 8 $
![Page 247: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/247.jpg)
Algoritmo mergesort • Temos um array a, com tamanho n, que
queremos ordenar. • Usando o algoritmo mergesort, ordenamos o
subarray inicial com n/2 elementos. • Usando o algoritmo mergesort de novo,
ordenamos o subarray que começa em a+n/2 e que tem n-n/2 elementos.
• Fundimos os dois subarrays para um terceiro array, com ints_merge.
• Copiamos o terceiro array para o array a. • Já está. 18/12/14 Programação Imperativa 247
![Page 248: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/248.jpg)
Função ints_msort • Palavras para quê?
18/12/14 Programação Imperativa 248
void ints_msort(int *a, int n) { if (n > 1) { int m = n / 2; ints_msort(a, m); ints_msort(a+m, n-m); int b[n]; ints_merge(a, m, a+m, n-m, b); ints_copy(b, n, a); } }
Mais outro para a coleção!
![Page 249: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/249.jpg)
Testando a função ints_msort • Usamos o modelo da função test_ints_isort,
substituindo ints_isort por inst_msort.
18/12/14 Programação Imperativa 249
void test_ints_msort(void) { int a[1000]; int n; while ((n = ints_get(a)) != 0) { ints_println_basic(a, n); ints_msort(a, n); ints_println_basic(a, n); freopen("/dev/tty", "r", stdin); // Unix // freopen("CON", "r", stdin); // Windows printf("------\n"); } }
$ ./a.out 6 3 9 4 7 3 9 12 3 26 2 6 3 9 4 7 3 9 12 3 26 2 2 3 3 3 4 6 7 9 9 12 26 ------ 54 29 3 78 33 73 28 42 90 74 23 42 54 29 3 78 33 73 28 42 90 74 23 42 3 23 28 29 33 42 42 54 73 74 78 90 ------ $
Qual será melhor: o insertionsort ou o mergesort?
![Page 250: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/250.jpg)
Questão técnica sobre a função ints_copy • Se copiarmos “para a direita” subarrays, o ints_copy
“ingénuo” não funciona bem:
• Por exemplo:
18/12/14 Programação Imperativa 250
int ints_copy_naif(const int *a, int n, int *b) { for (int i = 0; i < n; i++) b[i] = a[i]; return n; }
void test_ints_copy_naif(void) { int a[10] = {1,3,5,7,9, 11,13,15,17,19}; ints_copy_naif(a, 7, a+3); ints_println_basic(a, 10); }
Pois, quando a[3] é copiado para a[6], o valor que lá está é 1, que foi copiado anteriormente de a[0].
$ ./a.out 1 3 5 1 3 5 1 3 5 1 $
![Page 251: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/251.jpg)
Função memmove • A função memmove não padece da anomalia
observada na função ints_copy ingénua:
18/12/14 Programação Imperativa 251
void test_memmove(void) { int a[10] = {1,3,5,7,9, 11,13,15,17,19}; ints_copy_naif(a, 7, a+3); ints_println_basic(a, 10); int b[10] = {1,3,5,7,9, 11,13,15,17,19}; ints_copy(b, 7, b+3); ints_println_basic(b, 10); }
int ints_copy(const int *a, int n, int *b) { if (n < 0) n = 0; memmove(b, a, n * sizeof(int)) return n; }
$ ./a.out 1 3 5 1 3 5 1 3 5 1 1 3 5 1 3 5 7 9 11 13 $
![Page 252: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/252.jpg)
Função memmove, copiar para a esquerda • A função memmove copia bem “para a esquerda”,
mas a função ints_copy ingénua também:
18/12/14 Programação Imperativa 252
void test_ints_copy(void) { int a[10] = {1,3,5,7,9, 11,13,15,17,19}; ints_copy_naif(a, 7, a+3); ints_println_basic(a, 10); int b[10] = {1,3,5,7,9, 11,13,15,17,19}; ints_copy_naif(b+3, 7, b); ints_println_basic(b, 10); int c[10] = {1,3,5,7,9, 11,13,15,17,19}; ints_copy(c, 7, c+3); ints_println_basic(c, 10); int d[10] = {1,3,5,7,9, 11,13,15,17,19}; ints_copy(d+3, 7, d); ints_println_basic(d, 10); }
$ ./a.out 1 3 5 1 3 5 1 3 5 1 7 9 11 13 15 17 19 15 17 19 1 3 5 1 3 5 7 9 11 13 7 9 11 13 15 17 19 15 17 19 $
Ao copiar arrays, devemos sempre ter cuidado especial quando estamos a copiar “para cima” deles próprios.
![Page 253: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/253.jpg)
Programação Imperativa
Lição n.º 14 Complexidade dos algoritmos de ordenação
![Page 254: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/254.jpg)
Complexidade dos algoritmos de ordenação • Complexidade do insertionsort. • Complexidade do mergesort. • Alocação dinâmica.
18/12/14 Programação Imperativa 254
![Page 255: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/255.jpg)
Complexidade do insertionsort • Vimos que o insertionsort ordena
instantaneamente arrays pequenos. • E arrays grandes? • Quais serão os limites práticos da utilização
do insertionsort? • Por exemplo, qual o tamanho do maior array
que conseguiremos ordenar em menos de um minuto, no nosso computador?
• E se usarmos um computador duas vezes mais rápido, conseguiremos ordenar um array duas vezes maior no mesmo tempo?
18/12/14 Programação Imperativa 255
![Page 256: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/256.jpg)
Medindo tempo de execução • A função de biblioteca clock() dá o tempo que
passou desde que o nosso programa começou, expresso em “tiques” do relógio.
• A função devolve um valor de tipo clock_t; o
“tipo” deste tipo depende do sistema operativo.
• O número de “tiques” que correspondem a um segundo vem na constante simbólica CLOCKS_PER_SEC.
18/12/14 Programação Imperativa 256
clock_t clock (void);
![Page 257: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/257.jpg)
Exemplo: contar até mil milhões • Quanto tempo demora o nosso computador a
contar até 1000000000?
18/12/14 Programação Imperativa 257
int count_to(int n) { int result = 0; for (int i = 0; i < n; i++) result++; return result; }
void test_timing_count_to(void) { int x; scanf("%d", &x); clock_t t1 = clock(); int z = count_to(x); clock_t t2 = clock(); printf("%d\n", z); printf("%d %d %d\n", (int)t1, (int)t2, (int)(t2-t1)); }
$ ./a.out 1000 1000 1734 1739 5 $ ./a.out 1000000 1000000 1783 3902 2119 $ ./a.out 1000000000 1000000000 1713 1987815 1986102
Resposta: leva cerca de 2 milhões de tiques.
![Page 258: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/258.jpg)
Tempo em milissegundos • É mais prático medir o tempo em
milissegundos:
18/12/14 Programação Imperativa 258
int millisecs(clock_t x) { return (int) round(x * 1000.0 / CLOCKS_PER_SEC); }
void test_timing_count_to(void) { ... int w = millisecs(t2 - t1); printf("%d\n", w); }
$ ./a.out 1000 1000 1744 1748 4 0 $ ./a.out 1000000 1000000 1694 3788 2094 2 $ ./a.out 1000000000 1000000000 1699 1985267 1983568 1984
Quase dois segundos, portanto.
![Page 259: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/259.jpg)
Tempo do insertionsort • A função de cronometragem lê os números
do ficheiro para o array, ordena o array, e escreve o tempo gasto, em milissegundos.
18/12/14 Programação Imperativa 259
void test_timing_isort(void) { int a[MAX_SIZE]; int n = ints_get(a); clock_t t1 = clock(); ints_isort(a, n); clock_t t2 = clock(); assert(ints_is_sorted(a, n)); int w = millisecs(t2 - t1); printf("%d\n", w); }
Nesta fase, o valor de MAX_SIZE é 200000.
![Page 260: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/260.jpg)
Ficheiros de teste • Para medir o tempo do insertionsort,
preparamos uma série de ficheiros, contendo números aleatórios, duplicando sucessivamente o tamanho: 1000, 2000, 4000, 8000, ..., 128000.
18/12/14 Programação Imperativa 260
$ ls -l t*.txt -rw-r--r-- 1 ... 10474 Nov 4 18:15 t1000.txt -rw-r--r-- 1 ... 1341957 Nov 4 18:16 t128000.txt -rw-r--r-- 1 ... 167886 Nov 4 18:15 t16000.txt -rw-r--r-- 1 ... 20964 Nov 4 18:15 t2000.txt -rw-r--r-- 1 ... 335428 Nov 4 18:15 t32000.txt -rw-r--r-- 1 ... 41941 Nov 4 18:15 t4000.txt -rw-r--r-- 1 ... 670832 Nov 4 18:16 t64000.txt -rw-r--r-- 1 ... 83891 Nov 4 18:15 t8000.txt
... 1222651149 1974326747 1739807032 799449272 1686218872 2086375892 1606628628 147973418 203173100 231292970 395545720 1475028575 254039057 436940763 1416814648 1105111000 14514097 1272776168 464447809 2018752665 1081901702 777866365 ...
![Page 261: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/261.jpg)
Experimentando • Experimentamos na consola, a partir da
diretoria onde estão os ficheiros de teste:
18/12/14 Programação Imperativa 261
$ ../../sources/a.out < t1000.txt 2 $ ../../sources/a.out < t2000.txt 8 $ ../../sources/a.out < t4000.txt 30 $ ../../sources/a.out < t8000.txt 118 $ ../../sources/a.out < t16000.txt 479 $ ../../sources/a.out < t32000.txt 1926 $ ../../sources/a.out < t64000.txt 7663 $ ../../sources/a.out < t128000.txt 30549
0
5000
10000
15000
20000
25000
30000
35000
0 50 100 150
tempo
![Page 262: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/262.jpg)
Conclusão • O tempo usado pelo insertionsort para ordenar um
array com N elementos é proporcional a N2: T = K * N2.
• Dizemos que o mergesort é um algoritmo quadrático.
• No nosso caso, calculando para N = 128000, dá K = 30549/1280002 = 1.86*10-6.
• Para calcular o tamanho para 1 minuto, basta fazer as contas: T = sqrt(60000 / (1.86*10-6)) = 180000.
• Confirmemos, com um ficheiro com 180000 números:
18/12/14 Programação Imperativa 262
$ ../../sources/a.out < t180000.txt 60918
![Page 263: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/263.jpg)
Agora o mergesort • É a mesma coisa:
• Mas, como se vê o mergesort é estupida-mente mais rápido!
18/12/14 Programação Imperativa 263
void test_timing_msort(void) { int a[MAX_SIZE]; int n = ints_get(a); clock_t t1 = clock(); ints_msort(a, n); clock_t t2 = clock(); assert(ints_is_sorted(a, n)); int w = millisecs(t2 - t1); printf("%d\n", w); }
$ ../../sources/a.out < t1000.txt 0 $ ../../sources/a.out < t2000.txt 0 $ ../../sources/a.out < t4000.txt 1 $ ../../sources/a.out < t8000.txt 1 $ ../../sources/a.out < t16000.txt 3 $ ../../sources/a.out < t32000.txt
5 $ ../../sources/a.out < t64000.txt 11 $ ../../sources/a.out < t128000.txt 24
![Page 264: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/264.jpg)
Arrays milionários • O tempo do mergesort parece ser pouco mais
que proporcional ao tamanho do array. • Por exemplo, para um array com 1000000
números, o tempo deve ser cerca de 200 ms. • Usemos para o mergesort ficheiros com
1000000, 2000000, etc., até 16000000 de números.
• O valor da constantes MAX_SIZE ficará a 16000000:
18/12/14 Programação Imperativa 264
#define MAX_SIZE 16000000
![Page 265: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/265.jpg)
Segmentation fault • No entanto, com o MAX_SIZE a 16000000, o
programa estoira, com Segmentation fault.
• Mas, baixando para 1000000, já passa:
• O tempo é pouco mais de 200 ms, tal como esperávamos.
18/12/14 Programação Imperativa 265
$ ../../sources/a.out < t1000000.txt Segmentation fault: 11
$ ../../sources/a.out < t1000000.txt 214
![Page 266: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/266.jpg)
Limite da capacidade declarada • Se subirmos MAX_SIZE para 2000000 temos
segmentation fault, de novo. • De facto, há um limite “escondido” para a
capacidade declarada de um array. • Na minha configuração, esse limite está algures
entre 1000000 e 2000000. • Como fazer para usar arrays verdadeiramente
grandes, com capacidade maior que esse limite?
18/12/14 Programação Imperativa 266
![Page 267: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/267.jpg)
Alocação dinâmica • Em vez de declararmos o array “estatica-
mente”, como sempre temos feito: • ... declaramos assim, alocando “dinamicamente”
a memória necessária: • ... e depois, no fim da função onde o array é
declarado, devemos “libertar” a memória:
18/12/14 Programação Imperativa 267
int a[MAX_SIZE];
int *a = (int *) malloc (MAX_SIZE * sizeof(int));
free(a);
![Page 268: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/268.jpg)
Função ints_new • Em vez de chamar o malloc explicitamente, é mais
prático usar a função ints_new:
• Eis a nova função test_timing_msort:
18/12/14 Programação Imperativa 268
int *ints_new (int n) { return (int *) malloc (n * sizeof(int)); }
void test_timing_msort(void) { int *a = ints_new(MAX_SIZE); int n = ints_get(a); clock_t t1 = clock(); ints_msort(a, n); clock_t t2 = clock(); assert(ints_is_sorted(a, n)); int w = millisecs(t2 - t1); printf("%d\n", w); free(a); }
![Page 269: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/269.jpg)
Nova função ints_msort • A função ints_msort declara um array b, que
agora também tem de passar a ser alocado dinamicamente:
18/12/14 Programação Imperativa 269
void ints_msort(int *a, int n) { if (n > 1) { int m = n / 2; ints_msort(a, m); ints_msort(a+m, n-m); // int b[n]; int *b = ints_new(n); ints_merge(a, m, a+m, n-m, b); ints_copy(b, n, a); free(b); } }
![Page 270: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/270.jpg)
Experimentando com a nova função • Agora, o mergesort corre alegremente, mesmo
com arrays muito grandes:
18/12/14 Programação Imperativa 270
$ ../../sources/a.out < t1000000.txt 310 $ ../../sources/a.out < t2000000.txt 640 $ ../../sources/a.out < t4000000.txt 1328 $ ../../sources/a.out < t8000000.txt 2698 $ ../../sources/a.out < t16000000.txt 5650
Mas repare que para 1000000 a versão anterior dava 214 ms, enquanto esta dá 310 ms.
0
1000
2000
3000
4000
5000
6000
0 5 10 15 20
tempo
![Page 271: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/271.jpg)
Complexidade do mergesort • O tempo de cálculo do mergesort é pouco mais do
que diretamente proporcional ao tamanho. • Na verdade, é proporcional ao tamanho vezes o
logaritmo do tamanho: T = K * N * log(N) • No nosso caso, calculando para N = 16000000, dá
K = 5650/(16000000 * 24) = 1.47*10-5. • Nota: estamos a usar logaritmos na base 2. • Logo, por exemplo, para ordenar um array com
100000000 números, o tempo será 1.47*10-5*100000000 * 26.6 = 39100:
18/12/14 Programação Imperativa 271
$ ../../sources/a.out < t100000000.txt 38345
Na gíria, dizemos que o mergesort é um algoritmo N log N (ler “ene logue ene”).
![Page 272: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/272.jpg)
Afinando o mergesort • Observámos que o mergesort “abrandou” de 215 ms
para 310 ms, com 1000000 número, quando introduzimos a alocação dinâmica.
• A alocação dinâmica na função de teste não é problemática, pois é feita só uma vez.
• Mas na função ints_msort sim, porque é feita muitas vezes, uma por cada chamada recursiva: de facto, cada chamada recursiva usa um array b “novo”, alocado dinamicamente.
• Ora isso penaliza o algoritmo deveras. • Evitamos, partilhando o mesmo array b entre todas
as chamadas recursivas, passando-o por argumento.
18/12/14 Programação Imperativa 272
![Page 273: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/273.jpg)
Mergesort “pro” • A função recursiva, usa o array b passado em
argumento:
• A função ints_msort aloca o array b e faz a chamada recursiva inicial:
18/12/14 Programação Imperativa 273
void ints_msort_i(int *a, int n, int *b) { if (n > 1) { int m = n / 2; ints_msort_i(a, m, b); ints_msort_i(a+m, n-m, b); ints_merge(a, m, a+m, n-m, b); ints_copy(b, n, a); } }
void ints_msort(int *a, int n) { int *b = ints_new(n); ints_msort_i(a, n, b); }
Recorde que o array b é usado na fusão, quando as chamadas recursivas já retornaram.
![Page 274: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/274.jpg)
Comprovando • Tiremos tempos com o mergesort “pro”, para
comprovar as melhorias esperadas:
18/12/14 Programação Imperativa 274
$ ../../sources/a.out N < t16000000.txt 4125 $ ../../sources/a.out N < t100000000.txt 27923 Antes os tempos eram 5650 e
38345, respetivamente.
Nota final: todos as experiências foram realizadas no meu computador em casa. Noutro computador, os tempos serão diferentes mas as conclusões gerais continuam válidas.
![Page 275: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/275.jpg)
Programação Imperativa
Lição n.º 15 Memória
![Page 276: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/276.jpg)
Memória • Utilização da memória pelos programas. • Memória estática. • Memória automática. • Memória dinâmica. • Transbordamento de memória.
18/12/14 Programação Imperativa 276
![Page 277: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/277.jpg)
As variáveis residem na memória • Cada variável residirá algures na memória do
computador, quando o programa estiver a ser executado.
• A memória é uma parte do hardware do computador; é formada por uma sequência de células de memória, chamadas bytes.
• Cada byte contém 8 bits. • Um bit é um dispositivo eletrónico que em cada
momento estará num de dois estados. • Cada byte é referenciado pelo seu endereço, que é
um número natural. • O endereço de uma variável é o endereço da
primeiro byte ocupado por essa variável. 18/12/14 Programação Imperativa 277
![Page 278: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/278.jpg)
Semântica abstrata • Sendo x, y e z variáveis de tipo int, qual é o
significado da seguinte instrução? • O significado é: logo após a execução da
instrução, o valor de z é igual à soma do valor de x e do valor de y.
• O valor de x e o valor de y não mudam. • Presume-se que não há overflow de inteiros. • E presume-se que z é uma variável distinta de
x e de y; isto é, z não ocupa a mesma posição de memória que x ou y.
18/12/14 Programação Imperativa 278
z = x + y;
![Page 279: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/279.jpg)
Semântica concreta • Sendo x, y e z variáveis de tipo int, qual é o
significado da seguinte instrução? • O significado é:
• Os bytes que correspondem à variável x são copiados para um registo.
• Os bytes que correspondem à variável y são copiados para outro registo.
• A unidade aritmética realiza calcula a soma do valor numérico presente no primeiro registo com o valor numérico presente no segundo registo, deixando o resultado no primeiro registo.
• Os bytes do primeiro registo são copiados para os bytes que correspondem à variável z.
18/12/14 Programação Imperativa 279
z = x + y;
![Page 280: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/280.jpg)
Gestão da memória • Quando o sistema operativo lança a execução
de um programa, atribui ao programa um espaço de memória, para uso exclusivo do programa.
• Todas as variáveis do programa residirão nesse espaço de memória.
• Nem todas as variáveis usadas pelo programa estarão residentes na memória durante toda a execução do programa.
• A gestão do espaço da memória é realizada pelo programa em colaboração com o sistema operativo
18/12/14 Programação Imperativa 280
![Page 281: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/281.jpg)
Memória estática • As variáveis externas, isto é, as variáveis
declaradas fora das funções, ficam na memória estática e existem desde que o programa arranca até que o programa termina.
• Note bem: a memória estática não é um conceito de hardware; é apenas a zona da memória atribuída ao programa onde ficam as variáveis externas.
• As variáveis externas são globais, isto é, podem ser acedidas a partir de qualquer função.
18/12/14 Programação Imperativa 281
![Page 282: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/282.jpg)
Variáveis externas • Tipicamente, as variáveis externas são arrays
constantes, com valores de referência, que não mudam e que são usados em todo o programa.
18/12/14 Programação Imperativa 282
const int day_in_month[12] = { 31,28,31,30, 31,30,31,31, 30,31,30,31 };
const int primes[100] = { 2,3,5,7,11,13,17,19,23,29, 31,37,41,43,47,53,59,61,67,71, 73,79,83,89,97,101,103,107,109,113, 127,131,137,139,149,151,157,163,167,173, 179,181,191,193,197,199,211,223,227,229, 233,239,241,251,257,263,269,271,277,281, 283,293,307,311,313,317,331,337,347,349, 353,359,367,373,379,383,389,397,401,409, 419,421,431,433,439,443,449,457,461,463, 467,479,487,491,499,503,509,521,523,541, };
![Page 283: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/283.jpg)
Memória automática • As variáveis locais, isto é, as variáveis declaradas
dentro das funções e os argumentos da função, só existem na memória quando as funções estão ativas.
• Quando uma função g é chamada por uma função f, o espaço de memória necessário para todas as variáveis da função g, incluindo os argumentos, é alocado automaticamente na pilha de execução, a seguir ao espaço de memória que terá sido alocado anteriormente para a função f.
• Quando a função g, chamada pela função f, retorna, o espaço de memória reservado para as variáveis de g na pilha de execução é libertado e todas as variáveis de g, na presente ativação da função, deixam de existir em memória.
18/12/14 Programação Imperativa 283
![Page 284: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/284.jpg)
Memória dinâmica • Dizemos que uma variável é dinâmica, ou que
está na memória dinâmica, quando os bytes necessários para guardar os valores dessa variável são obtidos por meio da função malloc.
• Todas as variáveis dinâmicas residem no heap. • As variáveis dinâmicas são colocadas no heap,
explicitamente, por meio da função malloc (ou outra função do mesmo género), e retiradas do heap, também explicitamente, por meio da função free.
18/12/14 Programação Imperativa 284
Também usaremos a função calloc, semelhante a malloc.
![Page 285: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/285.jpg)
Zonas de memória em C • Segmento de dados (em inglês data segment):
zona onde são guardadas as variáveis externas; a dimensão desta zona é determinada pelo compilador.
• Pilha (em inglês stack): zona onde são guardadas as variáveis locais das funções.
• Heap: zona onde são guardadas as variáveis criadas com malloc.
18/12/14 Programação Imperativa 285
O tamanho da pilha e do heap são fixados pelo compilador e pelo sistema operativo. Quando se esgotam, há overflow.
![Page 286: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/286.jpg)
Transbordamento de memória • O erro de stack overflow ocorre tipicamente
quando por lapso programamos uma recursividade sem fim:
• A situação de heap overflow pode ser
controlada programaticamente, observando o valor retornado pelo malloc.
18/12/14 Programação Imperativa 286
int crazy_fact(int x) { return x == 1 ? 1 : crazy_fact(x+1) / (x+1); }
void test_crazy_fact(void) { int z = crazy_fact(2); printf("%d\n", z); }
$ ./a.out Segmentation fault: 11 $
![Page 287: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/287.jpg)
Programação Imperativa
Lição n.º 16 A pilha de execução
![Page 288: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/288.jpg)
A pilha de execução • Registos de execução. • Arrays locais. • Caso da alocação dinâmica. • Segmento de dados.
18/12/14 Programação Imperativa 288
![Page 289: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/289.jpg)
A pilha de execução • Quando uma função é chamada, o sistema de
execução aloca automaticamente espaço para as variáveis locais da função (incluindo argumentos) na pilha de execução (em inglês runtime stack).
• A pilha de execução chama-se “pilha”, porque é gerida em “pilha”.
• Isto é, quando uma função g é chamada por uma função f, as variáveis de g são guardadas na memória a seguir às variáveis de f.
• “A seguir” significa “em endereços mais altos”. • Portanto, as variáveis de g ficam “por cima” das de f. • Quando a função g retorna, é como se as suas
variáveis saíssem “de cima”. 18/12/14 Programação Imperativa 289
![Page 290: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/290.jpg)
Registos de ativação • A pilha de execução é formada por uma
sequência de registos de ativação, cada um deles correspondendo a uma ativação de uma função, gerida em modo pilha.
• Em inglês, registo de ativação é stack frame. • Na presença de recursividade, pode haver na
pilha várias frames relativas a uma mesma função. • Na frame há espaço para os argumentos da
função, para as variáveis locais e para informação de controlo.
• As frames relativas a uma função têm todas o mesmo tamanho, que é determinado pelo compilador. 18/12/14 Programação Imperativa 290
![Page 291: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/291.jpg)
int f1 (int x, int y) { int a = 204; // 0x000000CC int b = x + 1; int c = y + 5; int d = a + b + c; return d; } int h1(void) { int p = 68; // 0x00000044 int q = 34; // 0x00000022 int r = f1(p, q); int s = r + 1; return s; } void t1(void) { int x = 85; // 0x00000055 int y = h1(); int z = x + y; printf("Valor de z = %d\n", z); } int main(void) { t1(); return 0; }
Observando a pilha • Usaremos este programa. • A função main chama a
função de teste t1, que chama a função h1, que chama a função f1.
• As constantes usadas têm valores cuja representação hexadecimal é mais fácil de localizar na memória.
• Observaremos a memória à entrada de f1 à saída de f1, à saída de h1 e à saída de t1.
18/12/14 Programação Imperativa 291
![Page 292: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/292.jpg)
Primeira observação: à entrada de f1
18/12/14 Programação Imperativa 292
![Page 293: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/293.jpg)
• Note, a cinzento, o frame pointer, isto é, o endereço do início da frame da função que chamou “esta” (o qual corresponde à variável marcada a cor de laranja).
• Por exemplo 7FFF5FBFF810 é o endereço da variável p, a cor de laranja, com valor 0x00000044 na quarta linha; aqui começa a frame da função h1, a qual chamou f1.
Frame pointer
18/12/14 Programação Imperativa 293
E note que, por acaso, as variáveis a (a azul) e b (a verde) na primeira linha contêm lixo que parece corresponder a algum anterior frame pointer.
![Page 294: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/294.jpg)
Segunda observação: à saída de f1
18/12/14 Programação Imperativa 294
Nota: 0x00000138 é 256 + 3 * 16 + 8 = 312; 0x00000027 é 2 * 16 + 7 = 39; 0x00000045 é 16 * 4 + 5 = 69; 0x000000CC é 12 * 16 + 12 = 204;
![Page 295: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/295.jpg)
Terceira observação: à saída de h1
18/12/14 Programação Imperativa 295
Nota: 0x00000138 é 256 + 3 * 16 + 8 = 312; 0x00000139 é 256 + 3 * 16 + 9 = 313.
![Page 296: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/296.jpg)
Quarta observação: à saída de t1
18/12/14 Programação Imperativa 296
Nota: 0x0000018E é 256 + 8 * 16 + 14 = 398;
![Page 297: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/297.jpg)
Segunda experiência • Neste caso, temos uma
função que chama duas outras funções.
• Vamos observar a memória à saída de f2, depois em h2 entre as duas chamadas, depois à saída de g2 e por fim à saída de h2.
18/12/14 Programação Imperativa 297
int f2 (int x, int y) { int a = 204; // 0x000000CC int b = x + 1; int c = y + 5; int d = a + b + c; return d; } int g2 (int z) { int i = 221; // 0x000000DD int j = z + i; return j; } int h2(void) { int p = 68; // 0x00000044 int q = 34; // 0x00000022 int r = f2(p, q); int s = r + 1; int t = g2(p+1); int u = t + s; return u; } void t2(void) { ... }
![Page 298: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/298.jpg)
Observações
18/12/14 Programação Imperativa 298
![Page 299: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/299.jpg)
Funcionamento em pilha • Este exemplo ilustra o funcionamento “em
pilha” da pilha de execução. • As variáveis de g2 foram para a memória
“acima” das variáveis de h2, precisamente na zona onde antes tinham estado as variáveis de f2, anteriormente chamada por h2.
• Quando a pilha cresce, apanha a memória tal como ela estava: se nos tivéssemos esquecido de inicializar alguma variável local, o “lixo” que memória continha serviria de valor inicial...
18/12/14 Programação Imperativa 299
![Page 300: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/300.jpg)
Recursividade • É este funcionamento em pilha que permite a
existência de funções recursivas. • Quando uma função se chama a si própria,
diretamente ou indiretamente, haverá várias frames dessa função na pilha.
18/12/14 Programação Imperativa 300
int f4(int x) { int result = 119; if (x == 1) result = 0; else result = 1 + f4(x/2); return result; } void t4(void) { int x = 85; // 0x00000055 int y = f4(x); printf("Valor de y = %d\n", y); printf("Valor de x = %d\n", x); }
![Page 301: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/301.jpg)
Observando a recursividade
18/12/14 Programação Imperativa 301
![Page 302: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/302.jpg)
Arrays na memória • Agora, um exemplo com
arrays. • Neste caso, vamos observar
a memória antes da declaração do array, à saída da função f3 e à saída de t3.
18/12/14 Programação Imperativa 302
int f3(int x) { int b = 204; // 0x000000CC int m = 8; int a[8]; a[0] = x; a[1] = x+1; a[2] = x+2; a[3] = x+3; a[4] = x+4; a[5] = x+5; a[6] = x+6; a[m] = x+7; int c = a[0] + a[7]; int d = a[1] + a[2]; int e = b + c + d; return e; } void t3(void) { int x = 85; // 0x00000055 int y = f3(x); printf("Valor de y = %d\n", y); printf("Valor de x = %d\n", x); }
![Page 303: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/303.jpg)
Observações com arrays
18/12/14 Programação Imperativa 303
Que acontecerá se na função f3 acrescentarmos a[m+3] = 119? E a[m+7] = 119? E a[m+11] = 119. (119 é 0x00000077).
![Page 304: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/304.jpg)
Memória dinâmica • A memória dinâmica está
no heap. • Mas onde é que está o
heap? • Procuremos, substituindo o
array automático na função anterior por um array dinâmico.
• Observemos a memória, antes do malloc, depois do malloc e à saída da função f3.
18/12/14 Programação Imperativa 304
int f3(int x) { int b = 204; // 0x000000CC int m = 8; // int a[8]; int *a = malloc(m * sizeof(int)); a[0] = x; a[1] = x+1; a[2] = x+2; a[3] = x+3; a[4] = x+4; a[5] = x+5; a[6] = x+6; a[m] = x+7; int c = a[0] + a[7]; int d = a[1] + a[2]; int e = b + c + d; return e; } void t3(void) { int x = 85; // 0x00000055 int y = f3(x); printf("Valor de y = %d\n", y); printf("Valor de x = %d\n", x); }
![Page 305: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/305.jpg)
Observações com arrays dinâmicos
18/12/14 Programação Imperativa 305
Tecnicamente, a é um “apontador”, e ocupa 8 bytes:
Um apontador é uma variável cujo valor é um endereço.
Aqui, o apontador já está inicializado:
A memória alocada para o array a ainda não está inicializada. Repare que os endereços no heap são de uma gama completamente diferente dos endereços na pilha.
![Page 306: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/306.jpg)
Inicialização dos arrays dinâmicos
18/12/14 Programação Imperativa 306
![Page 307: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/307.jpg)
Memória estática
18/12/14 Programação Imperativa 307
• A memória estática está no segmento de dados: #include <stdio.h> #include <stdlib.h> const int day_in_month[12] = { 31,28,31,30, 31,30,31,31, 30,31,30,31 }; const int primes[100] = { 2,3,5,7,11,13,17,19,23,29, ... }; int f3(int x) { ... } ...
A memória estática é inicializada antes mesmo de a função main arrancar. Note que a gama de endereços é diferente da pilha e do heap.
![Page 308: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/308.jpg)
Cadeias literais na memória estática
18/12/14 Programação Imperativa 308
• As cadeias de carateres que usamos nos printf, por exemplo, estão na memória estática também
... void t3(void) { int x = 85; // 0x00000055 int y = f3(x); printf("Valor de y = %d\n", y); printf("Valor de x = %d\n", x); } ...
Observamos que as cadeias vêm a seguir ao array dos primos.
![Page 309: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/309.jpg)
Programação Imperativa
Lição n.º 17 Cadeias de carateres
![Page 310: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/310.jpg)
Cadeias de carateres • Conceitos básicos. • Arrays de cadeias de carateres.
18/12/14 Programação Imperativa 310
![Page 311: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/311.jpg)
Cadeias de carateres • As cadeias de carateres são arrays de char. • Cada valor de tipo char ocupa um byte. • As cadeias de carateres são representadas em
memória por sequências de bytes. • Os valores numéricos dos bytes vão de 0 a 255 ou
de -128 a 127. • Nas cadeias de carateres, o byte de valor numérico
zero é o terminador: assinala o fim da cadeia. • As funções que processam cadeias de carateres
ignoram o que está “para além” do terminador. • As funções que criam cadeias de carateres inserem o
terminador após os carateres “visíveis”.
18/12/14 Programação Imperativa 311
![Page 312: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/312.jpg)
Exemplo: Hello • Um programa que ciclicamente aceita um nome e
responde com “Hello” seguido do nome lido:
18/12/14 Programação Imperativa 312
void hello(const char *s) { printf("Hello %s\n", s); } void test_hello(void) { char name[16]; while (scanf("%s", name) != EOF) hello(name); }
$ ./a.out Pedro Hello Pedro Nobody Hello Nobody Cristiano Ronaldo Hello Cristiano Hello Ronaldo Monster Hello Monster $
Se introduzirmos um nome com mais de 15 carateres, causaremos buffer overflow...
![Page 313: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/313.jpg)
Cadeias na memória • Observemos a pilha, à entrada da função hello:
• A variável s (a amarelo) na frame da função hello,
contém o endereço da variável name (a verde) na frame da função test_hello.
• A cadeia “Cristiano” está na variável name, a qual ocupa 16 bytes.
• O byte 00 a seguir ao byte 6F é o terminador. 18/12/14 Programação Imperativa 313
![Page 314: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/314.jpg)
Arrays de cadeias de carateres • Usaremos apenas arrays de cadeias dinâmicas. • As cadeias dinâmicas são criadas com malloc. • Cada valor no array de cadeias dinâmicas contém o
endereço de uma cadeia, a qual reside na memória dinâmica.
• O valor da cadeia na memória dinâmica terá sido copiado a partir de uma cadeia na pilha.
• Tecnicamente, um array de cadeias dinâmicas é um array de “apontadores”, isto é, um array cujos valores são endereços.
• Nos usos mais comuns, as cadeias dinâmicas são referenciadas por variáveis que estão na pilha.
18/12/14 Programação Imperativa 314
Note bem: dizemos “cadeias dinâmicas” porque elas residem na memória dinâmica (ou seja, no heap), não porque elas tenham algum tipo de “dinamismo”.
![Page 315: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/315.jpg)
Exemplo: Hello para muitos • Um programa que aceita uma sequência de
nomes, até ao fim dos dados, e depois diz “Hello” com cada um dos nomes lidos.
18/12/14 Programação Imperativa 315
void test_hello_many(void) { char *names[10]; int n = 0; char word[16]; while (scanf("%s", word) != EOF) { names[n] = (char *) malloc(strlen(word) + 1); strcpy(names[n++], word); } for (int i = 0; i < n; i++) hello(names[i]); }
$ ./a.out Cristiano Rui Ricardo William Rafael Hello Cristiano Hello Rui Hello Ricardo Hello William Hello Rafael $
![Page 316: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/316.jpg)
strlen • A função strlen dá o comprimento da cadeia passada
em argumento:
• Podia programar-se assim:
18/12/14 Programação Imperativa 316
void unit_test_strlen(void) { char *s1 = "guatemala"; assert(strlen(s1) == 9); assert(strlen(s1+4) == 5); assert(strlen(s1+9) == 0); char *s2 = "brasil"; assert(strlen(s2) == 6); assert(strlen(s2+1) == 5); }
Note que s1+4, por exemplo, é a subcadeia de s1 que começa em s[4].
int str_len(const char *s) { int result = 0; while (s[result] != '\0') result++; return result; }
Na verdade, o tipo do resultado da função de biblioteca strlen não é int, mas sim size_t, o que por vezes causa algumas surpresas.
Note bem: representamos por '\0' o caráter cujo valor numérico é 0 (o tal que é usado como terminador nas cadeias).
![Page 317: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/317.jpg)
strcpy • Para copiar os bytes que constituem uma cadeia para
outra posição de memória, usamos a função strcpy. • Podia programar-se assim:
18/12/14 Programação Imperativa 317
void str_cpy(char *s, const char *t) { int i = 0; while (t[i] != 0) { s[i] = t[i]; i++; } s[i] = '\0'; }
Na verdade, a função de biblioteca strcpy retorna o valor do primeiro argumento, que representa o endereço para onde o segundo argumento terá sido copiado. Quase sempre ignoramos o valor de retorno.
Note que todos os carateres de t são copiados para posições sucessivas, a partir da primeira posição s, e depois, no fim, acrescenta-se o terminador.
![Page 318: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/318.jpg)
Hello para muitos: pilha • Nesta experiência entrámos os nomes
de cinco países: dinamarca, mali, uruguai, angola e china.
• Na pilha, a amarelo, a variável s; a vermelho, a variável n, com valor 5; a verde, a variável word, que contém “china”; a azul, o array names.
• No array names, cada valor ocupa 8 bytes; os 5 primeiros representam endereços no heap; os outros não estão inicializados.
• O valor em s é igual ao primeiro valor em names.
18/12/14 Programação Imperativa 318
dinamarca mali uruguai angola china
![Page 319: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/319.jpg)
Hello para muitos: heap • Os nomes dos países estarão no heap,
referenciados pelos endereços no array names:
• Curiosamente, nesta experiência “angola” foi parar a outra zona do heap:
18/12/14 Programação Imperativa 319
dinamarca mali uruguai angola china
![Page 320: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/320.jpg)
Argumentos de tipo array • Observámos que o valor do argumento s, à entrada da função
hello contém o endereço de cadeia que foi “passada”. • Note muito bem: é o endereço da cadeia, não os carateres
que compõem a cadeia; estes ficam onde estavam. • No exemplo test_hello, esses carateres estavam na pilha; no
exemplo test_hello_many, estavam no heap. • Recorde que quando o argumento é um número, a variável
que representa o argumento contém uma cópia do valor argumento.
• Isto é uma regra geral: se o argumento é um número, o seu valor residirá numa posição de memória na frame da função chamada; se o argumento é um array, a frame da função chamada registará o endereço do array.
18/12/14 Programação Imperativa 320
Na situação registada na página anterior, tratando-se da primeira chamada da função hello, o argumento tem o valor do endereço da primeira cadeira, no heap: 1001055B0. A seguir terá os valores dos endereças outras cadeias no array: 1001055C0, 1001055D0, 100200000, 1001055E0.
![Page 321: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/321.jpg)
Programação Imperativa
Lição n.º 18 Técnicas com arrays de cadeias de carateres
![Page 322: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/322.jpg)
Técnicas com arrays de cadeias de carateres • Leitura linha a linha. • Leitura de arrays de cadeias. • Escrita de arrays de cadeias. • Busca em arrays de cadeias. • Ordenação de arrays de cadeias.
18/12/14 Programação Imperativa 322
![Page 323: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/323.jpg)
Ler linha a linha vs. ler palavra a palavra • Ao ler cadeias de carateres com scanf (“%s”, ...),
primeiro a função salta os carateres brancos que existam no input e depois recolhe para a cadeia passada em argumento os carateres não brancos, até surgir um caráter branco (que não é recolhido) ou até ao fim dos dados.
• Os carateres brancos são o espaço, o tab ('\t') e o caráter de mudança de linha ('\n').
• No final, o scanf acrescenta o terminador. • Sendo assim, o scanf(“%s”, ...) é prático para ler o
input palavra a palavra, mas não para ler linhas inteiras que tenham mais que uma palavra.
18/12/14 Programação Imperativa 323
Note bem: o scanf não controla o buffer overflow.
![Page 324: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/324.jpg)
Ler uma linha inteira • Por razões técnicas deveras subtis, o C não tem uma
função de biblioteca para ler uma linha inteira (ou o resto da linha corrente).
• À falta de uma função de biblioteca, eu uso as seguintes:
18/12/14 Programação Imperativa 324
int str_readline(FILE *f, char *s) { int result = EOF; char *p = fgets(s, INT_MAX, f); if (p != NULL) { result = (int) strlen(s); if (result > 0 && s[result-1] == '\n') s[--result] = '\0'; } return result; } int str_getline(char *s) { return str_readline(stdin, s); }
Explicação: str_readline lê uma linha com fgets, para a cadeia s, a partir do ficheiro f, sem controlar buffer overflow. Se a linha lida terminar com mudança de linha (o que acontece sempre exceto porventura na última linha do ficheiro), o último caráter de s será ‘\n’. Então, a função elimina o ‘\n’, substituindo-o pelo terminador ‘\0’. Em caso de fim de ficheiro, o fgets devolve NULL e a função str_readline devolve EOF (por analogia com scanf).
A função str_getline faz a leitura a partir da consola, representada por stdin.
![Page 325: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/325.jpg)
Digressão: ++ e --
• Quanto vale x++? Vale x. • Quanto vale ++x? Vale x+1. • Em ambos os casos, depois da avaliação da
expressão, a variável x fica a valer x+1.
• Quanto vale x--? Vale x. • Quanto vale --x? Vale x-1. • Em ambos os casos x, depois da avaliação da
expressão, a variável x fica a valer x-1.
18/12/14 Programação Imperativa 325
Não confunda: uma coisa é o valor da expressão x++; outra coisa é o valor da variável x.
![Page 326: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/326.jpg)
Evidência • A seguinte função de teste unitário ilustra o
significado dos operadores ++ e --:
18/12/14 Programação Imperativa 326
void unit_test_plus_plus_minus_minus(void) { int x = 5; assert(x++ == 5); assert(x == 6); int y = 9; assert(++y == 10); assert(y == 10); int z = 3; assert(z-- == 3); assert(z == 2); int w = 8; assert(--w == 7); assert(w == 7); }
![Page 327: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/327.jpg)
Ler linhas para a memória dinâmica • Eis uma função de teste que lê linhas, da consola para
a memória dinâmica, até ao fim dos dados e que depois despeja a memória para a consola, cada linha entre parêntesis retos.
18/12/14 Programação Imperativa 327
#define MAX_LINES 10000 #define MAX_LINE_LENGTH 10000 void test_get_many_lines_basic(void) { char *s[MAX_LINES]; int n = 0; char line[MAX_LINE_LENGTH]; while (str_getline(line) != EOF) { s[n] = (char *) malloc(strlen(line) + 1); strcpy(s[n++], line); } for (int i = 0; i < n; i++) printf("[%s]\n", s[i]); }
$ ./a.out viana do castelo ponte de lima porto vila franca de xira setubal sao bras de alportel [viana do castelo] [ponte de lima] [porto] [vila franca de xira] [setubal] [sao bras de alportel] $
Cada uma das operações assinadas com uma caixa merece ser autonomizada numa função.
![Page 328: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/328.jpg)
Duplicar uma cadeia, str_dup • Duplicar uma cadeia significa alocar espaço para
uma cópia dessa cadeia na memória dinâmica e copiar para lá os carateres da cadeia original, devolvendo o endereço da cópia recém-criada:
18/12/14 Programação Imperativa 328
char *str_dup(const char *s) { char *result = (char *) malloc(strlen(s) + 1); strcpy(result, s); return result; }
Atenção: esta é uma operação fundamental. Usamo-la a toda a hora!
Nota: esta função não existe na biblioteca standard do C, mas existe em certas extensões, com o nome strdup.
![Page 329: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/329.jpg)
Ler cadeias, linha a linha • Normalmente, queremos ler de um ficheiro, linha a
linha, para um array de cadeiras dinâmicas:
• Para ler da consola, usamos a seguinte variante:
18/12/14 Programação Imperativa 329
int strings_read(FILE *f, char **a) { int result = 0; char line[MAX_LINE_LENGTH]; while (str_readline(f, line) != EOF) a[result++] = str_dup(line); return result; }
int strings_get(char **a) { return strings_read(stdin, a); }
![Page 330: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/330.jpg)
Ler cadeias, palavra a palavra • Para ler palavra a palavra, confiamos no scanf:
• Para ler da consola, usamos a seguinte variante:
18/12/14 Programação Imperativa 330
int strings_readwords(FILE *f, char **a) { int result = 0; char word[MAX_LINE_LENGTH]; while (fscanf(f, "%s", word) != EOF) a[result++] = str_dup(word); return result; }
int strings_getwords(char **a) { return strings_readwords(stdin, a); }
![Page 331: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/331.jpg)
Escrever cadeias • Em geral, ao escrever um array de cadeias num
ficheiro, queremos ser capazes de especificar o formato de escrita:
• Para escrever na consola, usamos a seguinte
variante:
18/12/14 Programação Imperativa 331
void strings_fprintf(FILE *f, char **s, int n, const char *fmt) { for (int i = 0; i < n; i++) fprintf(f, fmt, s[i]); }
void strings_printf(char **s, int n, const char *fmt) { strings_fprintf(stdout, s, n, fmt); }
![Page 332: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/332.jpg)
Escrever cadeias, com separador • Por vezes, queremos apenas indicar o separador:
• Para escrever na consola, com separador, usamos a
seguinte variante:
18/12/14 Programação Imperativa 332
void strings_write(FILE *f, char **s, int n, const char *separator) { if (n > 0) { fprintf(f, "%s", s[0]); for (int i = 1; i < n; i++) // i = 1 fprintf(f, "%s%s", separator, s[i]); } }
void strings_print(char **s, int n, const char *separator) { strings_write(stdout, s, n, separator); }
![Page 333: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/333.jpg)
Escrever cadeias e mudar de linha • Frequentemente, depois de escrever queremos mudar
de linha automaticamente:
18/12/14 Programação Imperativa 333
void strings_writeln(FILE *f, char **s, int n, const char *separator) { strings_write(f, s, n, separator); fprintf(f, "\n"); } void strings_println(char **s, int n, const char *separator) { strings_writeln(stdout, s, n, separator); } void strings_fprintfln(FILE *f, char **s, int n, const char *fmt) { strings_fprintf(f, s, n, fmt); fprintf(f, "\n"); } void strings_printfln(char **s, int n, const char *fmt) { strings_fprintfln(stdout, s, n, fmt); }
![Page 334: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/334.jpg)
Função test_strings_get • Eis uma função que testa simultaneamente algumas das
funções que descrevemos, lendo de uma vez um ficheiro para uma array de cadeias, linha a linha, e despejando as linhas para a consola, entre chavetas:
18/12/14 Programação Imperativa 334
void test_strings_get(void) { char *s[MAX_WORDS]; int n = strings_get(s); strings_printf(s, n, "{%s}\n"); }
$ ./a.out a cidade e as serras memorial do convento o que diz molero {a cidade e as serras} {memorial do convento} {o que diz molero} $
![Page 335: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/335.jpg)
Busca linear • A busca linear num array de cadeias é análoga
à busca linear num array de int:
• Mas atenção: a comparação faz-se com a função de biblioteca strcmp.
18/12/14 Programação Imperativa 335
int strings_find(char **s, int n, const char *x) { for (int i = 0; i < n; i++) if (strcmp(s[i], x) == 0) return i; return -1; }
A função str_getline faz a leitura a partir da consola, representada por stdin.
![Page 336: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/336.jpg)
strcmp • A função strcmp compara duas cadeias de carateres
usando a ordem lexicográfica dos valores numéricos dos carateres.
• Dá zero, se as duas cadeiras forem iguais, caráter a caráter.
• Dá um número negativo indeterminado se a cadeia no primeiro argumento for lexicograficamente menor que a cadeia no segundo argumento.
• Dá um número positivo indeterminado se a cadeia no primeiro argumento for lexicograficamente maior que a cadeia no segundo argumento.
18/12/14 Programação Imperativa 336
Se as cadeias em análise só tiverem letras minúsculas ou só letras maiúsculas, sem acentos ou outros sinais diacríticos, a ordem lexicográfica coincide com a ordem alfabética habitual.
![Page 337: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/337.jpg)
Comparação de cadeias • Para comparar cadeias, não se usa <, <=, ==,
etc. • Usa-se strcmp. • Por exemplo: • strcmp(s, t) < 0 significa “s menor que t”. • strcmp(s, t) == 0 significa “s igual a t”. • strcmp(s, t) != 0 significa “s diferente de t”. • strcmp(s, t) <= 0 significa “s menor ou igual a t”. • strcmp(s, t) > 0 significa “s maior que t”. • strcmp(s, t) >= 0 significa “s maior ou igual a t”.
18/12/14 Programação Imperativa 337
![Page 338: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/338.jpg)
Teste unitário para strcmp • Observe:
18/12/14 Programação Imperativa 338
void unit_test_strcmp(void) { assert(strcmp("lisboa", "faro") > 0); assert(strcmp("quarteira", "queluz") < 0); assert(strcmp("tavira", "Tavira") != 0); assert(strcmp("lagos", "LAGOS") != 0); assert(strcmp("silves", "silves") == 0); assert(strcmp("braga", "braganca") < 0); assert(strcmp("vila real de santo antonio", "vila real") > 0); assert(strcmp("", "sagres") < 0); assert(strcmp("alcoutim", "") > 0); assert(strcmp("", "") == 0); }
![Page 339: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/339.jpg)
Exemplo: consultando as cidades • Eis uma função que lê para um array um ficheiro com
a lista das cidades portuguesas e depois interroga esse array para cada nome lido da consola:
18/12/14 Programação Imperativa 339
void test_portuguese_cities(void) { FILE *f = fopen("cidades_2011.txt", "r"); char *cities[MAX_CITIES]; int n = strings_read(f, cities); char line [MAX_LINE_LENGTH]; while (str_getline(line) != EOF) { int k = strings_find(cities, n, line); printf("%d\n", k); } }
$ ./a.out faro 43 gambelas -1 aveiro 16 estoi -1 tavira 134
![Page 340: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/340.jpg)
Busca dicotómica • Adaptamos as funções usadas com arrays de int:
18/12/14 Programação Imperativa 340
int strings_rank(char **s, int n, const char *x) { int result = 0; while (n > 0) { int m = n / 2; if (strcmp(x, s[m]) <= 0) n = m; else { result += m+1; s += m+1; n -= m+1; } } return result; } int strings_bfind(char **s, int n, const char *x) { int r = strings_rank(s, n, x); return r < n && strcmp(s[r], x) == 0 ? r : -1; }
![Page 341: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/341.jpg)
Exemplo: consultando palavras inglesas • Eis uma função que consulta dicotomicamente uma
lista ordenada de 10000 palavras inglesas, carregadas num array de cadeias:
18/12/14 Programação Imperativa 341
void test_english_words(void) { FILE *f = fopen("wordlist.10000.txt", "r"); char *words[MAX_WORDS]; int n = strings_read(f, words); char line [MAX_LINE_LENGTH]; while (str_getline(line) != EOF) { int k = strings_bfind(words, n, line); printf("%d\n", k); } }
$ ./a.out house 4291 batata -1 potato 6829 gato -1 cat 1397 Program 6998
![Page 342: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/342.jpg)
Ordenação de arrays de cadeias • Atenção: ao ordenar arrays de cadeias dinâmicas, as
cadeiras não mexem, no heap. • Quem mexe, por troca, são os apontadores no array
de apontadores. • Para trocar dois apontadores num array de
apontadores para char, usamos a seguinte função:
18/12/14 Programação Imperativa 342
void strings_exchange(char **a, int x, int y) { char *m = a[x]; a[x] = a[y]; a[y] = m; } Note bem: ao fazer a[x] = a[y] NÃO estamos a copiar a
cadeia a[y] para a cadeia cadeia a[x]. Estamos sim a copiar o endereço existente na posição y do array a para a posição x do mesmo array.
![Page 343: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/343.jpg)
Insertionsort para array de cadeias • Adaptamos a função ints_isort:
18/12/14 Programação Imperativa 343
void strings_sort_last(char **a, int n) { int i = n-1; while (i > 0 && strcmp(a[i-1], a[i]) > 0) { strings_exchange(a, i-1, i); i--; } }
void strings_isort(char **a, int n) { for (int i = 2; i <= n; i++) strings_sort_last(a, i); }
![Page 344: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/344.jpg)
Testando o insersionsort com cadeias • Ler algumas palavras e ordenar, repetidamente:
18/12/14 Programação Imperativa 344
void test_strings_isort_demo(void) { char *a[1000]; int n; while ((n = strings_getwords(a)) != 0) { strings_isort(a, n); strings_println(a, n, " "); freopen("/dev/tty", "r", stdin); // Unix // freopen("CON", "r", stdin); // Windows printf("------\n"); } } $ ./a.out
sardinha carapau bacalhau pescada dourada cherne faneca bacalhau carapau cherne dourada faneca pescada sardinha ------ morango uva laranja banana quivi clementina manga ameixa framboesa ameixa banana clementina framboesa laranja manga morango quivi uva ------
![Page 345: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/345.jpg)
Programação Imperativa
Lição n.º 19 Estruturas e arrays de estruturas
![Page 346: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/346.jpg)
Estruturas e arrays de estruturas • Estruturas. • Typedefs. • Arrays de estruturas. • Buscas em arrays de estruturas. • Argumentos na linha de comando.
18/12/14 Programação Imperativa 346
![Page 347: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/347.jpg)
Estruturas • “A structure is a collection of one or more
variables, possibly of different types, grouped together under a single name for convenient handling” (K&R, p. 127).
• Ao usar estruturas, em cada caso definiremos um tipo, por meio de um typedef.
18/12/14 Programação Imperativa 347
![Page 348: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/348.jpg)
Exemplo: Pontos • Para representar pontos de coordenadas
inteiras, usamos o seguinte tipo Point:
• Dizemos que o tipo Point é um tipo struct e que cada ponto é uma “estrutura”.
• Cada variável na estrutura é um “membro”; logo, cada ponto tem dois membros, x e y, ambos de tipo int.
18/12/14 Programação Imperativa 348
typedef struct { int x; int y; } Point;
Não confunda com pontos no plano, em que as coordenadas seriam double. Este tipo Point serve, por exemplo, para identificar pixéis numa janela ou quadrículas numa grelha.
![Page 349: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/349.jpg)
Operações com pontos • Distância
• Colinearidade
18/12/14 Programação Imperativa 349
//Euclidean distance double distance(Point p, Point q) { return sqrt((p.x-q.x)*(p.x-q.x) + (p.y-q.y)*(p.y-q.y)); }
//q.y - p.y r.y - q.y //--------- == --------- //q.x - p.x r.x - q.x int collinear(Point p, Point q, Point r) { return (q.x-p.x)*(r.y-p.y) == (r.x-p.x)*(q.y-p.y); }
![Page 350: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/350.jpg)
Construtor • Um construtor é uma função que devolve uma
estrutura, frequentemente a partir dos valores de cada um dos membros:
18/12/14 Programação Imperativa 350
Point point(int x, int y) { Point result; result.x = x; result.y = y; return result; }
Estilo: os nomes dos tipos struct escrevem-se com maiúscula inicial (Point) e o construtor é o mesmo nome, mas escrito todo em minúsculas (point).
![Page 351: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/351.jpg)
Testes unitários • Eis uma função de teste unitário para cada
uma das funções distance e collinear:
18/12/14 Programação Imperativa 351
void unit_test_distance(void) { Point p1 = point(2,4); Point p2 = point(5,8); assert(distance(p1, p2) == 5); Point p3 = point(0,0); Point p4 = point(1,1); assert(distance(p3, p4) == sqrt(2)); assert(distance(p1, p3) == sqrt(20)); assert(distance(p1, p1) == 0); assert(distance(p2, p4) == sqrt(65)); }
void unit_test_collinear(void) { Point p1 = point(2,4); Point p2 = point(5,8); Point p3 = point(8,12); assert(collinear(p1, p2, p3)); Point p4 = point(0,0); Point p5 = point(1,1); Point p6 = point(4,4); assert(collinear(p4, p5, p6)); Point p7 = point(1000, 4); assert(collinear(p1, p6, p7)); assert(!collinear(p4, p1, p2)); }
![Page 352: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/352.jpg)
Teste na consola
18/12/14 Programação Imperativa 352
void test_points(void) { int x1, y1, x2, y2, x3, y3; while (scanf("%d%d%d%d%d%d", &x1, &y1, &x2, &y2, &x3, &y3) != EOF) { Point p1 = point(x1, y1); Point p2 = point(x2, y2); Point p3 = point(x3, y3); double d12 = distance(p1, p2); double d23 = distance(p2, p3); double d31 = distance(p3, p1); int c = collinear(p1, p2, p3); printf("%.4f %.4f %.4f\n", d12, d23, d31); printf("%d\n", c); } }
$ ./a.out 1 3 2 4 3 5 1.4142 1.4142 2.8284 1 0 0 4 0 0 3 4.0000 5.0000 3.0000 0 2 1 6 1 -5 1 4.0000 11.0000 7.0000 1 $
![Page 353: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/353.jpg)
Segundo exemplo: Voos • Temos ficheiros com a lista de partidas nos
aeroportos de Faro, Lisboa, Porto, com o formato que o exemplo ilustra.
18/12/14 Programação Imperativa 353
0605 TP1900 Lisboa 0910 FR6313 Brussels,_Charleroi 0950 EZY8918 London,_Gatwick 1045 EI491 Dublin 1120 TP1908 Lisboa 1130 EZY6446 Newcastle 1130 EZY7362 London,_Southend 1205 EZY6794 Belfast 1250 EZY2016 London,_Luton 1255 FR6827 Edinburgh 1300 FR3712 Birmingham 1415 FR4051 Manchester 1510 EZY6844 Glasgow 1650 TP1902 Lisboa 1900 EZY7196 Liverpool 2025 FR5487 Porto
• Queremos realizar diversas operações sobre estes dados: • Que voos têm um dado destino? • Que voos partem entre as tantas e
as tantas? • Listar os voos por ordem alfabética
de destino. • Quais são os voos de uma dada
companhia? • Etc. Estes são os valores reais das
partidas do aeroporto de Faro no dia 25 de Novembro de 2014.
![Page 354: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/354.jpg)
Tipo Flight • Caraterizamos um voo pela hora de partida, número
de voo e destino:
• O número de voo e o destino serão cadeias de
carateres no heap, representadas pelo seu endereço. • O construtor é simples:
18/12/14 Programação Imperativa 354
typedef struct { const char *code; const char *destination; int departure; } Flight;
Flight flight(const char *code, const char *destination, int departure) { Flight result; result.code = code; result.destination = destination; result.departure = departure; return result; }
A utilização de const char * indica que através do endereço registado na variável podemos consultar a cadeira referenciada, mas não podemos modificá-la.
![Page 355: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/355.jpg)
Lendo para array • O processamento será feito sobre os dados
em memória, após terem sido lidos do ficheiro para um array.
• Para ler, usamos a seguinte função:
18/12/14 Programação Imperativa 355
int flights_read(FILE *f, Flight *a) { int result = 0; char code[16]; char destination[64]; int departure; while (fscanf(f, "%d%s%s", &departure, code, destination) != EOF) a[result++] = flight(str_dup(code), str_dup(destination), departure); return result; }
As dimensões das variáveis locais code e destination são arbitrárias, mas suficientes para os valores em jogo.
Note bem: as cadeias lidas são duplicadas para o heap e os endereços são guardados na estrutura.
![Page 356: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/356.jpg)
Escrevendo o array • Para controlar que a leitura ficou bem feita,
escrevemos o array, com um formato apropriado:
18/12/14 Programação Imperativa 356
void flights_write(FILE *f, Flight *a, int n) { for (int i = 0; i < n; i++) fprintf(f, "[%d][%s][%s]\n", a[i].departure, a[i].code, a[i].destination); }
Escrevemos os valores dos membros entre parêntesis retos, para melhor controlo.
![Page 357: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/357.jpg)
Função de teste, leitura-escrita • Eis uma função de teste, que lê o ficheiro com as
partidas de Faro e que depois escreve o array na consola:
18/12/14 Programação Imperativa 357
#define MAX_FLIGHTS 10000 void test_flights_read_write(void) { FILE *f = fopen("partidas_faro.txt", "r"); assert(f != NULL); Flight flights[MAX_FLIGHTS]; int n_flights = flights_read(f, flights); flights_write(stdout, flights, n_flights); }
$ ./a.out [605][TP1900][Lisboa] [910][FR6313][Brussels,_Charleroi] [950][EZY8918][London,_Gatwick] [1045][EI491][Dublin] [1120][TP1908][Lisboa] [1130][EZY6446][Newcastle] [1130][EZY7362][London,_Southend] [1205][EZY6794][Belfast] [1250][EZY2016][London,_Luton] [1255][FR6827][Edinburgh] [1300][FR3712][Birmingham] [1415][FR4051][Manchester] [1510][EZY6844][Glasgow] [1650][TP1902][Lisboa] [1900][EZY7196][Liverpool] [2025][FR5487][Porto] $
Note bem: estamos a supor, simplificando, que o executável a.out está colocado na diretoria de trabalho onde residem os ficheiros de dados. Nem sempre será assim.
![Page 358: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/358.jpg)
Voos para um dado destino • Queremos uma função para encontrar todos os voos
que têm um dado destino. • O resultado será o array dos índices dos voos cujo
membro destination é igual ao destino dado:
18/12/14 Programação Imperativa 358
int flights_find_to_destination(Flight *a, int n, char *destination, int *b) { int result = 0; for (int i = 0; i < n; i++) if (strcmp(a[i].destination, destination) == 0) b[result++] = i; return result; }
Note bem: o array b é um array de int. Cada valor representa uma posição no array a onde o membro destination tem valor igual ao valor do argumento destination.
![Page 359: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/359.jpg)
Função de teste, voos para destino dado • Primeiro, uma função para escrever os voos de um
array cujos índices vêm noutro array:
• Agora a função de teste:
18/12/14 Programação Imperativa 359
void flights_write_some(FILE *f, Flight *a, int *b, int n) { for (int i = 0; i < n; i++) fprintf(f, "[%d][%s][%s]\n", a[b[i]].departure, a[b[i]].code, a[b[i]].destination); }
void test_flights_find_to_destination(void) { FILE *f = fopen("partidas_lisboa.txt", "r"); assert(f != NULL); Flight flights[MAX_FLIGHTS]; int n_flights = flights_read(f, flights); char line[MAX_LINE_LENGTH]; while (scanf("%s", line) != EOF) { int b[n_flights]; int n = flights_find_to_destination(flights, n_flights, line, b); flights_write_some(stdout, flights, b, n); } }
$ ./a.out Porto [705][TP1928][Porto] [800][FR2094][Porto] [935][TP1926][Porto] [1335][TP1930][Porto] [1600][TP1936][Porto] [1835][TP1934][Porto] [2010][TP1940][Porto] [2025][FR2096][Porto] [2210][TP1922][Porto] Faro [950][TP1907][Faro] [1525][TP1909][Faro] [2210][TP1901][Faro] Luanda
[1000][DT651][Luanda] [2325][TP289][Luanda] Dubai [1335][EK192][Dubai] $
![Page 360: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/360.jpg)
Argumentos na linha de comando • Para selecionar uma ou outra função de teste,
podemos usar “argumentos na linha de comando”. • De facto, temos acesso, no nosso programa, a cada
uma das cadeias de carateres presentes na linha de comando usada para invocar o nosso programa.
• Observe:
18/12/14 Programação Imperativa 360
int main(int argc, char **argv) { strings_printfln(argv, argc, "{%s}"); return 0; } $ ./a.out
{./a.out} $ ./a.out um dois tres {./a.out}{um}{dois}{tres} $ ./a.out 89 23 abcd 3.141592 {./a.out}{89}{23}{abcd}{3.141592} $
Quer dizer: programando a função main desta maneira, temos no array argv cada uma das “palavras” da linha de comando. A primeira dessas palavras é a cadeia que invoca o programa; as outras são o que nós quisermos.
![Page 361: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/361.jpg)
Selecionando, na função main • Tipicamente, para nós o primeiro
argumento será uma letra maiús-cula, com a qual selecionamos a função de teste:
18/12/14 Programação Imperativa 361
int main(int argc, char **argv) { char x = 'A'; if (argc > 1) x = *argv[1]; if (x == 'A') test_flights_read_write(); else if (x == 'B') test_flights_find_to_destination(); else printf("%c Invalid option.\n", x); return 0; }
$ a.out A [605][TP1900][Lisboa] [910][FR6313][Brussels,_Charleroi] [950][EZY8918][London,_Gatwick] [1045][EI491][Dublin] [1120][TP1908][Lisboa] [1130][EZY6446][Newcastle] [1130][EZY7362][London,_Southend] [1205][EZY6794][Belfast] [1250][EZY2016][London,_Luton] [1255][FR6827][Edinburgh] [1300][FR3712][Birmingham] [1415][FR4051][Manchester] [1510][EZY6844][Glasgow] [1650][TP1902][Lisboa] [1900][EZY7196][Liverpool] [2025][FR5487][Porto] $ a.out B Belgrade [840][TP1323][Belgrade] Amsterdam [500][KL1692][Amsterdam] [705][TP664][Amsterdam] [930][HV5952][Amsterdam] [1305][TP662][Amsterdam] [1520][KL1694][Amsterdam] $ a.out C C Invalid option. $
![Page 362: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/362.jpg)
Nomes de ficheiro na linha de comando • Para escolher o ficheiro de dados, entre os vários
disponíveis, podemos também usar argumentos na linha de comando.
• Mas antes temos de retocar as nossas funções de teste, de modo a aceitarem como argumento o nome do ficheiro que devem usar (o qual deixa de ser fixo):
18/12/14 Programação Imperativa 362
void test_flights_read_write_better(char *filename) { FILE *f = fopen(filename, "r"); assert(f != NULL); ... } void test_flights_find_to_destination_better(char *filename) { FILE *f = fopen(filename, "r"); assert(f != NULL); ... }
![Page 363: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/363.jpg)
Função main, nova versão • Temos agora quatro funções de teste:
18/12/14 Programação Imperativa 363
int main(int argc, char **argv) { char x = 'A'; char *filename = "partidas_faro.txt"; if (argc > 1) x = *argv[1]; if (x == 'A') test_flights_read_write(); else if (x == 'B') test_flight_flights_find_to_destination(); else if (x == 'C') test_flights_read_write_better(argc > 2 ? argv[2] : filename); else if (x == 'D') test_flights_find_to_destination_better (argc > 2 ? argv[2] : filename); else printf("%c Invalid option.\n", x); return 0; }
Não havendo argumentos na linha de comando (para além da invocação do programa) usa-se a opção ‘A’. No caso das opções ‘C’ e ‘D’, se faltar o nome do ficheiro, usa-se “partidas_faro.txt”.
![Page 364: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/364.jpg)
Exemplos
18/12/14 Programação Imperativa 364
$ ../../sources/a.out A [605][TP1900][Lisboa] [910][FR6313][Brussels,_Charleroi] [950][EZY8918][London,_Gatwick] [1045][EI491][Dublin] [1120][TP1908][Lisboa] [1130][EZY6446][Newcastle] [1130][EZY7362][London,_Southend] [1205][EZY6794][Belfast] [1250][EZY2016][London,_Luton] [1255][FR6827][Edinburgh] [1300][FR3712][Birmingham] [1415][FR4051][Manchester] [1510][EZY6844][Glasgow] [1650][TP1902][Lisboa] [1900][EZY7196][Liverpool] [2025][FR5487][Porto] $ ../../sources/a.out D partidas_porto.txt Luxembourg [810][LG3770][Luxembourg] Madeira [705][TP1711][Madeira] Madrid [630][FR5483][Madrid] [700][TP1002][Madrid] [730][AEA1148][Madrid] [835][IB8721][Madrid] [1110][AEA1146][Madrid]
[1530][AEA1144][Madrid] [1630][TP1004][Madrid] [1710][IB8723][Madrid] [1910][AEA1142][Madrid] [2020][FR5485][Madrid] $ ../../sources/a.out D partidas_lisboa.txt Porto [705][TP1928][Porto] [800][FR2094][Porto] [935][TP1926][Porto] [1335][TP1930][Porto] [1600][TP1936][Porto] [1835][TP1934][Porto] [2010][TP1940][Porto] [2025][FR2096][Porto] [2210][TP1922][Porto] Terceira [800][TP1821][Terceira] $ ../../sources/a.out D partidas_lisboa.txt > out.txt Faro Madrid Newark $ ../../sources/a.out D Lisboa [605][TP1900][Lisboa] [1120][TP1908][Lisboa] [1650][TP1902][Lisboa] Porto [2025][FR5487][Porto] $
Neste exemplo, o executável a.out está na diretoria sources, onde residem os ficheiros de dados, a qual se encontra dois níveis acima da diretoria de trabalho.
Neste caso, o output está a ser redirigido para o ficheiro out.txt.
Neste caso, faltando o nome do ficheiro, usa-se o valor por defeito.
Continua na coluna da direita.
![Page 365: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/365.jpg)
Programação Imperativa
Lição n.º 20 Ordenação de arrays de estruturas
![Page 366: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/366.jpg)
Ordenação de arrays de estruturas • Ordenação simples. • Funções de comparação. • Tipos funcionais. • Ordenação geral.
18/12/14 Programação Imperativa 366
![Page 367: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/367.jpg)
Primeiro caso: ordenar arrays de pontos • Por hipótese, queremos ordenar um array de
pontos pela coordenada x, usando o insertionsort.
• Para começar, precisamos de uma função para trocar dois pontos num array:
18/12/14 Programação Imperativa 367
void points_exchange(Point *a, int x, int y) { Point m = a[x]; a[x] = a[y]; a[y] = m; }
![Page 368: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/368.jpg)
Insertionsort de um arrays de pontos • Temos programado o insertionsort com base numa
função que ordena um array onde o único elemento fora de ordem é o último:
18/12/14 Programação Imperativa 368
void points_sort_last_by_x(Point *a, int n) { int i = n-1; while (i > 0 && a[i-1].x > a[i].x) { points_exchange(a, i-1, i); i--; } }
void points_isort_by_x(Point *a, int n) { for (int i = 2; i <= n; i++) points_sort_last_by_x(a, i); }
![Page 369: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/369.jpg)
Insertionsort, novo estilo • Frequentemente, a função sort_last é inserida “em
linha” no local da sua chamada na função isort. • Observe:
18/12/14 Programação Imperativa 369
void points_isort_by_x(Point *a, int n) { for (int i = 1; i < n; i++) { int j = i; while (j > 0 && a[j-1].x > a[j].x) { points_exchange(a, j-1, j); j--; } } }
Repare: primeiro mudámos i para j na função sort_last, pois a função isort também usa uma variável i. Depois trocámos o ciclo for de for (int i = 2; i <= n; i++) para for (int i = 1; i < n; i++) para simplificar, pois assim em vez de inicializar j com j = i-1, fica, mais simplesmente, j = i.
![Page 370: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/370.jpg)
Testando a ordenação de pontos • Como de costume, primeiro funções para ler e para
escrever arrays de pontos:
18/12/14 Programação Imperativa 370
int points_read(FILE *f, Point *a) { int result = 0; int x, y; while (scanf("%d%d", &x, &y) != EOF) a[result++] = point(x, y); return result; }
void points_fprintf(FILE *f, Point *a, int n, const char *fmt) { for (int i = 0; i < n; i++) fprintf(f, fmt, a[i].x, a[i].y); }
void points_fprintfln(FILE *f, Point *a, int n, const char *fmt) { points_fprintf(f, a, n, fmt); fprintf(f, "\n"); }
![Page 371: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/371.jpg)
Função de teste para a ordenação • Eis a função de teste: lê, escreve, ordena e escreve de
novo:
18/12/14 Programação Imperativa 371
void test_points_isort_by_x(void) { Point a[1000]; int n = points_read(stdin, a); points_fprintfln(stdout, a, n, "<%d,%d>"); points_isort_by_x(a, n); points_fprintfln(stdout, a, n, "[%d,%d]"); }
$ ./a.out 6 2 4 9 -1 4 4 6 5 2 4 -5 -3 7 4 -2 5 0 6 8 5 1 <6,2><4,9><-1,4><4,6><5,2><4,-5><-3,7><4,-2><5,0><6,8><5,1> [-3,7][-1,4][4,9][4,6][4,-5][4,-2][5,2][5,0][5,1][6,2][6,8] $
Os resultados do teste estão OK: os pontos aparecem ordenados pela coordenada x. No entanto, observamos que sequências de pontos com a mesma coordenada x não estão ordenados pela coordenada y. Claro que não: não programámos isso. A comparação na ordenação apenas liga à coordenada x.
![Page 372: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/372.jpg)
Ordenação multicritério • Muitas vezes, quando há empate na condição de
ordenação, queremos usar um critério adicional de desempate.
• Por exemplo, ao ordenar por pontos, se houver empate entre dois pontos com a mesma coordenada x, deve vir primeiro o ponto cuja coordenada y é menor.
• Dizemos que ordenamos por x e depois por y ou, mais simplesmente, que ordenamos por x e y (ficando subentendido que x é o critério principal e y é o critério secundário).
18/12/14 Programação Imperativa 372
Em geral, pode haver um número arbitrário de critérios de comparação. Na prática, não ultrapassa dois ou três, normalmente.
![Page 373: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/373.jpg)
Função de comparação • Quando temos uma ordenação multicritério, usamos uma
função de comparação que aplica os critérios pela ordem pretendida.
• A função devolve um valor negativo se o primeiro argumento for “menor” que o segundo (de acordo com os critérios de ordenação), devolve zero se os dois argumentos forem “iguais” e devolve um valor positivo, se o primeiro argumento for “maior” que o segundo.
• Aqui, ser “menor” significa que deve vir antes, no array ordenado, e analogamente para “maior”.
• Ser “iguais” significa que os critérios de comparação não distinguem os dois argumentos, e não que os dois argumentos são “iguaizinhos” (isto é, que têm exatamente o mesmo valor).
18/12/14 Programação Imperativa 373
![Page 374: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/374.jpg)
Comparação por x e depois por y • Observe a técnica:
18/12/14 Programação Imperativa 374
int point_cmp_x_y(Point p, Point q) { int result = p.x - q.x; if (result == 0) result = p.y - q.y; return result; }
Compara-se pelo critério principal. Se não der zero, isto é, se não houver empate no critério principal, estamos resolvidos; se houver empate, compara-se pelo critério secundário.
![Page 375: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/375.jpg)
Insertionsort por x e y • Para ordenar um array de pontos por x e y, usando
de novo o insertionsort, basta retocar a função points_isort_by_x:
18/12/14 Programação Imperativa 375
void points_isort_by_x_y(Point *a, int n) { for (int i = 1; i < n; i++) { int j = i; while (j > 0 && point_cmp_x_y(a[j-1], a[j]) > 0) { points_exchange(a, j-1, j); j--; } } }
![Page 376: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/376.jpg)
Testando a ordenação multicritério • A função de teste é análoga à anterior:
• Os resultados são os esperados:
18/12/14 Programação Imperativa 376
void test_points_isort_by_x_y(void) { Point a[1000]; int n = points_read(stdin, a); points_fprintfln(stdout, a, n, "<%d,%d>"); points_isort_by_x_y(a, n); points_fprintfln(stdout, a, n, "[%d,%d]"); }
$ ./a.out 6 2 4 9 -1 4 4 6 5 2 4 -5 -3 7 4 -2 5 0 6 8 5 1 <6,2><4,9><-1,4><4,6><5,2><4,-5><-3,7><4,-2><5,0><6,8><5,1> [-3,7][-1,4][4,-5][4,-2][4,6][4,9][5,0][5,1][5,2][6,2][6,8] $ ./a.out 5 3 5 9 5 1 5 14 5 -4 5 -12 5 0 5 3 5 1 5 3 <5,3><5,9><5,1><5,14><5,-4><5,-12><5,0><5,3><5,1><5,3> [5,-12][5,-4][5,0][5,1][5,1][5,3][5,3][5,3][5,9][5,14] $
![Page 377: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/377.jpg)
Outras ordenações multicritério • Em geral, queremos ordenar pontos por x e y, mas
também por y e x. • E, por que não, ordenar por x e y ou por y e x mas
decrescentemente? • Ou por x crescentemente e y decrescentemente? • Ou o contrário? • Ou ainda, que tal ordenar pontos pelo ângulo do
semieixo positivo dos x com o segmento que os liga à origem, desempatando pelo comprimento desse segmento?
• Será que precisamos de reprogramar o insertionsort para cada um destes casos?
• NÃO! 18/12/14 Programação Imperativa 377
![Page 378: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/378.jpg)
Parametrizando a função de comparação • As funções de comparação de pontos têm dois
argumentos de tipo Point e resultado de tipo int. • Podemos generalizar a função do insertionsort, com
um terceiro argumento que representa parametrica-mente a função de comparação:
18/12/14 Programação Imperativa 378
void points_isort_gen(Point *a, int n, int(*cmp)(Point, Point)) { for (int i = 1; i < n; i++) { int j = i; while (j > 0 && cmp(a[j-1], a[j]) > 0) { points_exchange(a, j-1, j); j--; } } }
Repare no terceiro argumento: o identificador do argumento é cmp e int(*cmp)(Point, Point) significa que cmp representa uma função com dois argumentos de tipo Point e resultado de tipo int, tal como nós pretendemos.
![Page 379: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/379.jpg)
Tipos funcionais • Também podemos usar um typedef para definir,
dando-lhe um nome, o tipo das funções de comparação de pontos:
• Depois disto, podemos agora o tipo Point_cmp no cabeçalho da função de ordenação:
18/12/14 Programação Imperativa 379
void points_isort_gen(Point *a, int n, Point_cmp cmp) { ... }
typedef int (*Point_cmp)(Point, Point);
![Page 380: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/380.jpg)
Ordenação por y e depois por x • Primeiro definimos a função de comparação:
• Depois usamos a função de ordenação geral, usando como argumento a função de comparação:
18/12/14 Programação Imperativa 380
int point_cmp_y_x(Point p, Point q) { int result = p.y - q.y; if (result == 0) result = p.x - q.x; return result; }
void test_points_isort_by_y_x(void) { Point a[1000]; int n = points_read(stdin, a); points_fprintfln(stdout, a, n, "<%d,%d>"); points_isort_gen(a, n, point_cmp_y_x); points_fprintfln(stdout, a, n, "[%d,%d]"); }
$ ./a.out 7 3 8 0 2 6 3 0 2 -3 -4 -1 8 -1 5 3 7 3 1 6 <7,3><8,0><2,6><3,0><2,-3><-4,-1><8,-1><5,3><7,3><1,6> [2,-3][-4,-1][8,-1][3,0][8,0][5,3][7,3][7,3][1,6][2,6] $
![Page 381: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/381.jpg)
Ângulo e magnitude • Calculamos o ângulo feito do semieixo positivo dos x
com o segmento que une o ponto à origem por meio da função atan2.
• Só que esta função dá valores entre –π e π; ora, a nós convém-nos valores entre 0 e 2π:
• A magnitude é mais simples:
18/12/14 Programação Imperativa 381
double angle(Point p) { double result = atan2(p.y, p.x); if (result < 0) result += 2*M_PI; return result; }
≤
double magnitude(Point p) { return sqrt(p.x*p.x + p.x*p.y); }
![Page 382: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/382.jpg)
Testes unitários • Os testes unitários ajudam a perceber o significado
das funções angle e magnitude:
18/12/14 Programação Imperativa 382
≤
void unit_test_angle(void) { assert(angle(point(1, 0)) == 0); assert(angle(point(1, 1)) == M_PI_4); assert(angle(point(0, 1)) == M_PI_2); assert(angle(point(-1, 1)) == M_PI_2 + M_PI_4); assert(angle(point(-1, 0)) == M_PI); assert(angle(point(-1, -1)) == M_PI + M_PI_4); assert(angle(point(0, -1)) == M_PI + M_PI_2); assert(angle(point(1, -1)) == M_PI + M_PI_2 + M_PI_4); }
Note bem: M_PI é π; M_PI_2 é π/2; M_PI_4 é π/4.
void unit_test_magnitude(void) { assert(magnitude(point(0, 0)) == 0.0); assert(magnitude(point(0, 2)) == 2.0); assert(magnitude(point(-7, 0)) == 7.0); assert(magnitude(point(1, 1)) == sqrt(2.0)); assert(magnitude(point(3, 4)) == 5); assert(magnitude(point(12, 5)) == 13); assert(magnitude(point(12, -5)) == 13); assert(magnitude(point(-12, 5)) == 13); assert(magnitude(point(-12, -5)) == 13); }
![Page 383: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/383.jpg)
Comparação por ângulo e magnitude • Tem a pequena complicação de os valores serem double,
pelo que não podemos usá-los diretamente para calcular o resultado, que deve ser int.
• Recorremos à função sign, que dá o sinal de um número double: 1 se for positivo, 0 se for 0, -1 se for negativo:
18/12/14 Programação Imperativa 383
≤
int sign(double x) { return (x > 0) - (x < 0); }
int point_cmp_by_angle(Point p, Point q) { int result = sign(angle(p) - angle(q)); if (result == 0) result = sign(magnitude(p) - magnitude(q)); return result; }
• Com isto a função de comparação fica assim:
void unit_test_sign(void) { assert(sign(3.14) == 1); assert(sign(-2.0) == -1); assert(sign(0.0) == 0); assert(sign(-0.0) == 0); }
![Page 384: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/384.jpg)
Ordenação por ângulo e magnitude • Programamos diretamente, recorrendo à função de
ordenação geral, mas usamos uma função de escrita especial, para observar o ângulo:
18/12/14 Programação Imperativa 384
≤ void points_println_with_angle(Point *a, int n, const char *fmt) { for (int i = 0; i < n; i++) printf("{%d,%d,%.2f}", a[i].x, a[i].y, angle(a[i])); printf("\n"); }
void test_points_isort_by_angle(void) { Point a[1000]; int n = points_read(stdin, a); points_fprintfln(stdout, a, n, "<%d,%d>"); points_isort_gen(a, n, point_cmp_by_angle); points_println_with_angle(a, n, "[%d,%d]"); }
$ ./a.out 1 2 -3 3 4 -1 5 0 2 0 0 -1 0 -6 3 6 -1 1 -4 -4 -2 -1 -1 -1 <1,2><-3,3><4,-1><5,0><2,0><0,-1><0,-6><3,6><-1,1><-4,-4><-2,-1><-1,-1> {2,0,0.00}{5,0,0.00}{1,2,1.11}{3,6,1.11}{-1,1,2.36}{-3,3,2.36}{-2,-1,3.61}{-1,-1,3.93}{-4,-4,3.93}{0,-1,4.71}{0,-6,4.71}{4,-1,6.04} $
![Page 385: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/385.jpg)
Ordenação de arrays de voos • Queremos, primeiro, ordenar o array de voos por
destino, desempatando por número de voo, e, depois, ordenar por número de voo.
• Primeiro, a função de ordenação geral, com a respetiva função de troca:
18/12/14 Programação Imperativa 385
void flights_isort_gen(Flight *a, int n, Flight_cmp cmp) { for (int i = 1; i < n; i++) { int j = i; while (j > 0 && cmp(a[j-1], a[j]) > 0) { flights_exchange(a, j-1, j); j--; } } }
void flights_exchange(Flight *a, int x, int y) { Flight m = a[x]; a[x] = a[y]; a[y] = m; }
Continuamos a usar o insertionsort, mas a técnica aplica-se com qualquer algoritmo de ordenação, bem entendido.
![Page 386: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/386.jpg)
Funções de comparação • O tipo das funções de comparação de voos: • A função de comparação por destino, desempatando
por número de voo:
• A função de comparação por número de voo:
18/12/14 Programação Imperativa 386
typedef int(*Flight_cmp)(Flight, Flight);
int flight_cmp_by_destination(Flight x, Flight y) { int result = strcmp(x.destination, y.destination); if (result == 0) result = strcmp(x.code, y.code); return result; }
int flight_cmp_by_code(Flight x, Flight y) { return strcmp(x.code, y.code); }
Neste caso não é preciso desempatar, porque os números de voo são “únicos”, isto é, nenhum número de voo surge mais que uma vez em cada caso.
![Page 387: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/387.jpg)
Função de teste geral • Aceita parametricamente o nome do ficheiro e a
função de comparação:
18/12/14 Programação Imperativa 387
void test_flights_isort_gen(char *filename, Flight_cmp cmp) { FILE *f = fopen(filename, "r"); assert(f != NULL); Flight flights[MAX_FLIGHTS]; int n_flights = flights_read(f, flights); flights_isort_gen(flights, n_flights, cmp); flights_write(stdout, flights, n_flights); }
![Page 388: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/388.jpg)
Exemplos • Chamando assim:
18/12/14 Programação Imperativa 388
test_flights_isort_gen ("partidas_porto.txt", flight_cmp_by_destination);
$ ./a.out [635][FR4584][Barcelona] [1510][TP1032][Barcelona] [2030][TP1034][Barcelona] [845][TP1036][Barcelona] [1510][BE1853][Birmingham] [1215][FR4506][Bordeaux] [915][FR2929][Brussels] [955][TP602][Brussels] [1640][FR5488][DusseldorfWeeze] [2200][FR5486][Faro] [1235][LH1177][Frankfurt] [600][LH1181][Frankfurt] [855][FR4172][Frankfurt,_Hahn] [1250][EZS1454][Geneva] [1930][EZS1458][Geneva] ... [1005][FR6128][Strasbourg]
[1015][TP081][SãoPaulo,_Guarulhos] [1125][FR7464][Tours,_ValdeLoire] [1210][TP916][Zurich]
$
• Chamando assim: test_flights_isort_gen ("partidas_faro.txt", flight_cmp_by_code);
$ ./a.out [1045][EI491][Dublin] [1250][EZY2016][London,_Luton] [1130][EZY6446][Newcastle] [1205][EZY6794][Belfast] [1510][EZY6844][Glasgow] [1900][EZY7196][Liverpool] [1130][EZY7362][London,_Southend] [950][EZY8918][London,_Gatwick] [1300][FR3712][Birmingham] [1415][FR4051][Manchester] [2025][FR5487][Porto] [910][FR6313][Brussels,_Charleroi] [1255][FR6827][Edinburgh] [605][TP1900][Lisboa] [1650][TP1902][Lisboa] [1120][TP1908][Lisboa] $
![Page 389: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/389.jpg)
Programação Imperativa
Lição n.º 21 Passagem de argumentos
![Page 390: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/390.jpg)
Passagem de argumentos • Passagem por valor. • Caso das estruturas pesadas. • Passagem de endereços.
18/12/14 Programação Imperativa 390
![Page 391: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/391.jpg)
Revisão: passagem de argumentos numéricos • Já sabemos que quando o argumento de uma função
é um número (int ou double) o valor do argumento é guardado na variável respetiva (o “parâmetro”) na frame da função, na pilha de execução.
• Estudemos o assunto, de novo, observando a memória, antes do return em f1 e antes do printf em test_f1.
18/12/14 Programação Imperativa 391
void test_f1(void) { int a = 3; int b = 4; int c1 = f1(a, b); int c2 = f1(a+b+1, 7); printf("%d %d\n", c1, c2); }
int f1(int x, int y) { int result = 0; result = x * y; return result; }
![Page 392: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/392.jpg)
Memória de f1 e test_f1
18/12/14 Programação Imperativa 392
Na primeira chamada, os argumentos da função f1 têm o mesmo valor que as variáveis da função test_f1.
Na segunda chamada, o primeiro argumento é o resultado da avaliação de uma expressão (a qual envolve as as variáveis da função test_f1) e o segundo é uma constante.
Neste momento, as variáveis c1 e c2 já estão calculadas; a frame da função f1 já foi desempilhada (mas a memória ainda está intacta...)
• Conclusão: os argumentos são calculados pela função que chama e guardados na memória da função chamada.
![Page 393: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/393.jpg)
Exemplo com números double • A situação é semelhante, mas sabemos que as
variáveis double ocupam 8 bytes.
18/12/14 Programação Imperativa 393
void test_f2(void) { double a = 3; double b = 4; double c1 = f2(a, b); double c2 = f2(a*b/2, 0.5); printf("%f %f\n", c1, c2); }
double f2(double x, double y) { double result = 1.0; result = pow(x, y); return result; }
Tal como no outro exemplo, na primeira chamada os argumentos são variáveis da função que chama e na segunda chamada, o primeiro é uma expressão e o segundo é uma constante.
![Page 394: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/394.jpg)
Memória de f2 e test_f2
18/12/14 Programação Imperativa 394
Com números double, fica mais difícil localizá-los na memória.
Aqui, tal como no exemplo com números int, a variável c1 tem o valor da variável result da chamada anterior. A variável c2 ainda não foi inicializada.
Neste momento, as variáveis c1 e c2 já estão calculadas; a frame da função f2 já foi desempilhada (mas a memória continua na mesma, tal como no exemplo anterior)
• Conclusão: o mecanismo é o mesmo. • E, como já sabíamos, cada double ocupa 8 bytes
(enquanto cada int ocupa 4 bytes.)
![Page 395: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/395.jpg)
Caso dos argumentos struct • Questão: como passam os argumentos que são
estruturas? • Vejamos, primeiro com pontos:
• Usaremos também uma nova função, que cria um novo ponto, a partir de outro, deslocado dx para a direita e dy para cima:
18/12/14 Programação Imperativa 395
Point shifted(Point p, int dx, int dy) { Point result; result.x = p.x + dx; result.y = p.y + dy;
return result; }
typedef struct { ... } Point;
Point point(int x, int y) { ... }
![Page 396: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/396.jpg)
Distância de Manhattan • Testaremos usando a distância de Manhattan, que
soma os valores absolutos das diferenças das coordenadas:
18/12/14 Programação Imperativa 396
int manhattan(Point p, Point q) { int result = 0; result = abs(p.x - q.x) + abs(p.y - q.y); return result; }
void test_manhattan(void) { Point a = point(2, 8); Point b = point(4, 1); int z1 = manhattan(a, b); int z2 = manhattan(shifted(a, 1, 2), point(12, 15)); printf("%d %d\n", z1, z2); }
• A função de teste é análoga às anteriores: De novo, na primeira chamada os argumentos são variáveis da função que chama e na segunda chamada, o primeiro é uma expressão cujos valor é um ponto e o segundo é um ponto constante, por assim dizer..
![Page 397: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/397.jpg)
Memória de manhattan e test_manhattan
18/12/14 Programação Imperativa 397
Cada pontos ocupa o mesmo que dois ints, ou seja 8 bytes. Aqui, os argumentos são copiados das variáveis da função que chama para as variáveis da função chamada.
Aqui, a variável z1 tem o valor da variável result da chamada anterior. A variável z2 ainda não foi inicializada. Os argumentos foram calculados pela função que chama e guardados nas variáveis da função chamada.
Neste momento, as variáveis z1 e z2 já estão calculadas; a frame da função manhattan já foi desempilhada (mas a memória ainda está igual).
• Conclusão: o mecanismo é o mesmo. Isto é, os argumentos estão em variáveis da função chamada.
![Page 398: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/398.jpg)
Exemplo com voos • A estrutura Flight é ligeiramente mais complicada,
porque dois dos membros são endereços:
• Cada endereço ocupa 8 bytes; logo, um voo ocupa 20
bytes, mais os bytes das cadeias de carateres no heap.
18/12/14 Programação Imperativa 398
typedef struct { const char *code; const char *destination; int departure; } Flight;
Flight flight(const char *code, const char *destination, int departure) { Flight result; result.code = code; result.destination = destination; result.departure = departure; return result; }
![Page 399: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/399.jpg)
O voo é da TAP? • Usaremos para teste uma função que verifica se o
voo passado em argumento é da TAP:
18/12/14 Programação Imperativa 399
int is_tap(Flight x) { int result = 0; result = strstr(x.code, "TP") == x.code; return result; }
Note bem: strstr(s, t) calcula o endereço da primeira ocorrência da cadeia t na cadeira s. Ambas as cadeias são representadas pelo endereço da posição de memória onde começam. Logo, se o resultado de strstr(s, t) for igual a s, isso significa que os primeiros carateres de s são iguais aos carateres todos de t.
void test_is_tap(void) { Flight a1 = flight(str_dup("BA234"), str_dup("London"), 1735); Flight a2 = flight(str_dup("TP501"), str_dup("Lisbon"), 1100); int z1 = is_tap(a1); int z2 = is_tap(a2); int z3 = is_tap(flight(str_dup("FR7172"), str_dup("Dublin"), 820)); printf("%d %d %d\n", z1, z2, z3); }
Nesta experiência, estamos a usar uma função de teste com apenas um argumento.
A função de teste tem duas variáveis Flight. As duas primeiras chamadas usam essas variáveis como argumento. A terceira usa diretamente uma expressão cujo valor é um voo.
![Page 400: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/400.jpg)
Memória de is_tap e test_is_tap (1)
18/12/14 Programação Imperativa 400
O argumento da função chamada (a verde) é uma cópia da variável da função que chama (a azul).
O argumento da função chamada (a verde) é uma cópia da variável da função que chama (a cor de salmão). À esquerda deste está a variável z1, que agora vale zero.
Note bem: 1735 é 6C7 em hexadecimal; 1100 é 44C. Logo, a azul o voo para Londres; a cor de salmão, o voo para Lisboa.
![Page 401: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/401.jpg)
Memória de is_tap e test_is_tap (2)
18/12/14 Programação Imperativa 401
Aqui o argumento é o resultado da avaliação direta do construtor. A variável z2 já está inicializada, com 1, resultado da chamada anterior.
Neste momento, as variáveis z1, z2 e z3 já estão calculadas; a frame da função is_tap já foi desempilhada (mas a memória ainda está igual).
• Conclusão: a função chamada detém o valor do argumento, neste caso um objeto de tipo Flight.
Note bem: 820 é 334 em hexadecimal.
![Page 402: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/402.jpg)
Estruturas pesadas • Por vezes, precisamos de estrutura com muitos
membros. • Por exemplo, uma estrutura para representar carros,
com marca, modelo, matrícula, tipo de combustível, cor, velocidade máxima, cilindrada, ano de fabrico e preço:
18/12/14 Programação Imperativa 402
typedef struct { char brand[16]; char model[16]; char plate[12]; Fuel fuel; Color color; int maxspeed; int cc; int year; int price; } Car;
Repare que, por decisão de “desenho”, as cadeias de carateres para a marca, modelo e matrícula não são dinâmicas. Em vez disso (isto é, em vez de estarem no heap) estão “dentro” da estrutura.
Os tipos Fuel e Color são tipos enumerados, que estariam previamente definidos.
Cada objeto de tipo Car ocupa 16 + 16 + 12 + 4 + 4 + 4 + 4 + 4 + 4 = 68 bytes.
![Page 403: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/403.jpg)
Tipos enumerados • Os tipos enumerados são isso mesmo: tipos cujos
valores são enumerados à mão, na definição:
• Eis uma pequena função de teste que ilustra a utilização dos tipos enumerados:
18/12/14 Programação Imperativa 403
typedef enum {gasoline, diesel, lpg, electric} Fuel; typedef enum {white, red, green, blue, yellow, orange, brown, grey, black, other} Color;
void test_enum(void) { Fuel f = gasoline; Color c = yellow; printf("%d %d\n", f, c); f = 3; if (f == electric) c = green - 1; printf("%d %d\n", f, c); } Não abuse!
$ ./a.out 0 4 3 1 $
![Page 404: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/404.jpg)
Construtor de carros • É uma função longa e maçadora:
18/12/14 Programação Imperativa 404
Car car(char* brand, char *model, char *plate, Fuel fuel, Color color, int maxspeed, int cc, int year, int price) { Car result; strcpy(result.brand, brand); strcpy(result.model, model); strcpy(result.plate, plate); result.fuel = fuel; result.color = color; result.maxspeed = maxspeed; result.cc = cc; result.year = year; result.price = price; return result; }
Repare que as cadeias são inicializadas com strcpy e não com str_dup.
Um objeto criado desta maneira será copiado para alguma outra posição de memória, quando o construtor termina, para processamento posterior.
![Page 405: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/405.jpg)
Testando os carros • Usaremos as seguintes funções para observar a
memória:
18/12/14 Programação Imperativa 405
int is_luxury(Car x) { int result = 0; result = x.maxspeed > 250 && x.price >= 60000; return result; }
void test_is_luxury(void) { Car a1 = car("Porsche", "Cayenne", "12-IF-78", diesel, black, 275, 2600, 2012, 80000); Car a2 = car("Opel", "Adam Rocks", "31-OS-40", gasoline, yellow, 170, 1200, 2014, 12000); Car a3 = car("Volvo", "T4", "12-09-NS", gasoline, grey, 230, 1800, 1999, 4000); int z1 = is_luxury(a1); int z2 = is_luxury(a2); int z3 = is_luxury(a3); int z4 = is_luxury(car("Renault", "Twingo", "61-PF-88", gasoline, red, 180, 900, 2014, 11000)); printf("%d %d %d %d\n", z1, z2, z3, z4); }
![Page 406: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/406.jpg)
Objetos de tipo Car em memória
18/12/14 Programação Imperativa 406
Esta é a configuração no final da primeira chamada da função is_luxury. Reparamos que a variável a cor-de-rosa (o Porsche) foi copiada para a variável do argumento na função is_luxury. Nas chamadas seguintes, acontecerá o mesmo com as outras variáveis. Na última chamada, o argumento virá copiado diretamente do resultado do construtor, sem ter ficado guardado em alguma variável da função de teste.
![Page 407: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/407.jpg)
Avaliação • Parece excessivo movimentar 68 bytes, da frame da
função que chama para a frame da função chamada, e depois só usar 8 desses bytes (os dos membros maxspeed e price), na função is_luxury.
• Devia haver uma maneira mais económica de conseguir o mesmo efeito com menos trabalho, fazendo com que a função chamada fosse buscar os bytes de que precisa diretamente à frame da função que chama, onde esses bytes já residem.
• Na verdade é simples: em vez de passar como argumento o “valor” do objeto (neste caso um objeto de tipo Car), passar explicitamente o endereço da variável onde reside esse objeto.
18/12/14 Programação Imperativa 407
![Page 408: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/408.jpg)
Função is_luxury, com endereços • O argumento da função is_luxury deve agora ser do
tipo dos endereços de variáveis de tipo Car. • Observe com atenção e veja as diferenças:
• O tipo dos endereços das variáveis de tipo Car é o tipo Car *, chamado tipo dos apontadores para Car.
18/12/14 Programação Imperativa 408
int is_luxury_better(Car *x) { int result = 0; result = x->maxspeed > 250 && x->price >= 60000; return result; }
Note bem: aquela programação detalhada da função is_luxury_better é para efeitos da observação da memória. Normalmente ela teria apenas uma instrução: return x->maxspeed > 250 && x->price >= 60000;
![Page 409: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/409.jpg)
Operador de endereçamento • Em vez de passar o valor, passa-se o endereço,
usando o operador de endereçamento &:
18/12/14 Programação Imperativa 409
void test_luxury_better(void) { Car a1 = car("Porsche", "Cayenne", "12-IF-78", diesel, black, 275, 2600, 2012, 80000); Car a2 = car("Opel", "Adam Rocks", "31-OS-40", gasoline, yellow, 170, 1200, 2014, 12000); Car a3 = car("Volvo", "T4", "12-09-NS", gasoline, grey, 230, 1800, 1999, 4000); int z1 = is_luxury_better(&a1); int z2 = is_luxury_better(&a2); int z3 = is_luxury_better(&a3); printf("%d %d %d\n", z1, z2, z3); }
Note bem: não poderíamos escrever is_luxury_better(a1), pois o argumento da função is_luxury_better é de tipo Car * (isto é, de tipo apontador para Car), e não de tipo Car.
![Page 410: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/410.jpg)
Passagem de endereço
18/12/14 Programação Imperativa 410
Observamos que agora os bytes da variável do Porsche não são duplicados para a frame da função chamada. Em vez disso, a função chamada tem o endereço da variável do Porsche. Através deste endereço, a função chamada pode ir buscar os membros de que necessita à frame da função que chama.
• Regra prática: as estruturas pesadas passam por endereço.
Por outras palavras: quando uma função precisa de um argumento que é de um tipo struct com muitos bytes, não se usa o tipo do argumento mas sim o tipo dos apontadores para esse tipo; depois, em vez de passar uma variável como argumento, passa-se o endereço dessa variável.
![Page 411: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/411.jpg)
Programação Imperativa
Lição n.º 22 Leitura avançada de ficheiros
![Page 412: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/412.jpg)
Leitura avançada de ficheiros • Leitura de ficheiros csv. • Processamento de tabelas.
18/12/14 Programação Imperativa 412
![Page 413: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/413.jpg)
Problema: calculadora de câmbios • Queremos um programa para converter uma quantia
em euros para o equivalente noutra moeda, ou vice-versa, usando a tabela de câmbios do Banco de Portugal.
18/12/14 Programação Imperativa 413
Retirado de https://www.bportugal.pt/en-US/Estatisticas/Dominios%20Estatisticos/EstatisticasCambiais/Pages/Taxasdereferenciadiarias.aspx, no dia 9 de Dezembro de 2014.
![Page 414: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/414.jpg)
Estratégia • Primeiro, transformamos à mão a tabela
presente na página Web, de forma a obter um ficheiro de texto equivalente.
• Esse ficheiro de texto será lido pelo programa, logo de início, vindo o seu nome na linha de comando.
• Segue-se a fase interativa. • Em cada passo, indica-se a conversão a realizar
(de euros para a moeda indicada ou da moeda indicada para euros), a moeda em causa e o montante; o programa faz as contas e mostra o contravalor.
18/12/14 Programação Imperativa 414
![Page 415: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/415.jpg)
Transformar a tabela em texto • Selecionar a tabela na página. • Copiar. • Fazer “paste special” para o Excel. • Editar a tabela para retirar as vírgulas como separadores dos milhares. • Eliminar as notas (a), (b), (c), na Islândia, Israel e Roménia. • Guardar em formato xlsx, para arquivo. • Guardar em formato csv (comma-separated values), para processamento:
18/12/14 Programação Imperativa 415
AUSTRALIA,AUSTRALIAN DOLLAR,AUD,1.4903,1.4789,1.4765,1.4692,1.462 BRAZIL,BRAZILIAN REAL,BRL,3.2238,3.1777,3.1832,3.1728,3.1534 BULGARIA,BULGARIAN LEV,BGN,1.9558,1.9558,1.9558,1.9558,1.9558 CANADA,CANADIAN DOLLAR,CAD,1.4166,1.4027,1.4085,1.3998,1.4034 CAPE VERDE,CAPE VERDE ESCUDO,CVE,110.265,,110.265,110.265,110.265 CHINA,CHINESE YUAN RENMINBI,CNY,7.6549,7.5666,7.6055,7.5777,7.5752 CROATIA,CROATIAN KUNA,HRK,7.6655,7.67,7.674,7.6753,7.6755 CZECH REPUBLICA,CZECH KORUNA,CZK,27.618,27.611,27.635,27.616,27.623 DENMARK,DANISH KRONE,DKK,7.4403,7.4401,7.4399,7.44,7.4411 HONG-KONG,HONG KONG DOLLAR,HKD,9.5889,9.5013,9.5823,9.544,9.5597 HUNGARY,HUNGARIAN FORINT,HUF,305.75,306.55,307.25,306.9,306.72 ICELAND,ICELANDIC KRONA,ISK,,,,, INDIA,INDIAN RUPEE,INR,76.6415,75.9015,76.3786,76.2063,76.2179 ...
Ao todo são 35 linhas.
![Page 416: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/416.jpg)
Subproblema: ler o ficheiro para um array • Vamos ler para um array de estruturas, claro:
18/12/14 Programação Imperativa 416
#define MAX_DAYS 5 typedef struct { const char *country; const char *currency; const char *code; double rates[MAX_DAYS]; } Exchange;
Exchange exchange_from_line(char *s) { Exchange result; ... return result; }
• Omitimos o construtor, porque não será usado. • Em vez disso, precisamos de uma função que
“converta” uma linha do ficheiro para uma estrutura Exchange:
![Page 417: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/417.jpg)
Subsubproblema: obter os valores na linha • A linha do ficheiro csv é uma cadeia, formada por
subcadeias separadas por vírgulas. • Assim, cada valor é, numa primeira fase, uma dessas
subcadeias. • Portanto, temos o problema de, dada uma linha csv,
obter o array dos valores. • Note-se que os valores podem estar omissos, caso
em que surgem duas vírgulas de seguida; nesse caso, o valor é uma cadeia vazia.
• As vírgulas são separadores: aparecem entre cada dois valores: logo, se houver n valores, há n-1 vírgulas.
• (Se houvesse só um valor, não haveria vírgula nenhuma.)
18/12/14 Programação Imperativa 417
![Page 418: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/418.jpg)
Function count_while_not • Usaremos uma função count_while_not para
contar os carateres de uma string até à primeira vírgula ou todos, se não houver nenhuma vírgula.
• Parametrizamos o separador, pois, em geral, poderia ser um caráter diferente de vírgula:
18/12/14 Programação Imperativa 418
int count_while_not(const char *s, char x) { int result = 0; while (s[result] != '\0' && s[result] != x) result++; return result; }
![Page 419: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/419.jpg)
Function str_ndup • Antecipando um pouco, vamos precisar de
uma função para criar uma cadeia dinâmica com os n primeiros carateres de outra cadeia (e não com os carateres todos, como faz a função str_dup):
18/12/14 Programação Imperativa 419
char *str_ndup(const char *s, int n) { n = min(n, (int) strlen(s)); char *result = (char *) malloc(n + 1); strncpy(result, s, n); result[n] = '\0'; return result; }
Note bem: se n for maior ou igual ao comprimento da cadeia, copia-se a cadeia toda.
Repare que a função precisa de acrescentar “à mão” o terminador.
![Page 420: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/420.jpg)
Análise da linha csv • Note bem: numa linha csv, cada vírgula marca
o início de um novo valor. • Todos os valores vêm a seguir a uma vírgula
(incluindo os valores vazios), exceto o primeiro.
• O primeiro valor existe sempre (por hipótese) e vai até à primeira vírgula (se houver) ou até ao fim da linha (se só houver um valor na linha).
• Por isso, o primeiro valor, que é estrutural-mente diferente dos outros, será obtido antes do ciclo.
18/12/14 Programação Imperativa 420
![Page 421: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/421.jpg)
Função strings_from_csv • A função implementa a estratégia resultante da
análise anterior:
18/12/14 Programação Imperativa 421
#define SEPARATOR ','
int strings_from_csv(char *s, const char **a) { int result = 0; int x = count_while_not(s, SEPARATOR); a[result++] = str_ndup(s, x); int i = x; while (s[i++] == SEPARATOR) { int x = count_while_not(s+i, SEPARATOR); a[result++] = str_ndup(s+i, x); i += x; } return result; }
Note bem: o primeiro valor é obtido antes do ciclo, tal como planeado...
Representamos o separador por meio de uma constante simbólica.
... e os restantes são obtidos ao longo do ciclo.
![Page 422: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/422.jpg)
Testando a função strings_from_csv • Eis uma função de teste:
18/12/14 Programação Imperativa 422
void test_strings_from_csv(void) { char line [MAX_LINE_LENGTH]; while (str_getline(line) != EOF) { const char *a[1000]; int n = strings_from_csv(line, a); strings_fprintfln(stdout, a, n, "<%s>"); } }
$ ./a.out uuu,iii,e,ssssss,aaaa <uuu><iii><e><ssssss><aaaa> rrr,,uuu,iii,,, <rrr><><uuu><iii><><><> ,,,,, <><><><><><> <> , <><> ,iii,ooo <><iii><ooo> zzz, <zzz><> ooo,,,,ttt <ooo><><><><ttt> $
![Page 423: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/423.jpg)
Conversão de cadeia para número • Para converter de cadeia para número double,
usa-se a função atof. • Para converter um array de cadeias, podemos
usar a seguinte função:
• A propósito (ainda que não faça falta aqui): para converter de cadeia para int, usa-se a função atoi.
18/12/14 Programação Imperativa 423
int doubles_from_strings(const char **a, int n, double *b) { for (int i = 0; i < n; i++) b[i] = atof(a[i]); return n; }
Note bem: se a conversão for impossível, o resultado é 0.0.
![Page 424: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/424.jpg)
Experimentando atoi e atof • Eis uma pequena função de teste para
experimentar as duas funções, atoi e atof:
18/12/14 Programação Imperativa 424
void test_atoi_atof(void) { char s[MAX_LINE_LENGTH]; while (scanf("%s", s) != EOF) { int x = atoi(s); double y = atof(s); printf("[%d][%f]\n", x, y); } }
Constatamos que ambas as funções convertem até onde conseguem (por assim dizer). Quando não conseguem converter nada, dão zero.
$ ./a.out 56 [56][56.000000] 89.672 [89][89.672000] -3.14 [-3][-3.140000] ----4.5 [0][0.000000] 123aaa [123][123.000000] aaa123 [0][0.000000] 34.672aaa [34][34.672000] +56.8 [56][56.800000] ++89.786 [0][0.000000] $
![Page 425: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/425.jpg)
void test_doubles_from_strings(void) { char line [MAX_LINE_LENGTH]; while (str_getline(line) != EOF) { const char *a[1000]; int n = strings_from_csv(line, a); strings_fprintfln(stdout, a, n, "<%s>"); double b[1000]; int m = doubles_from_strings(a, n, b); doubles_printfln(b, m, "[%.4f]"); } }
$ ./a.out 7,55,-1000,89 <7><55><-1000><89> [7.0000][55.0000][-1000.0000][89.0000] 3.14,8.125,1.111 <3.14><8.125><1.111> [3.1400][8.1250][1.1110] aaa,12,bbb <aaa><12><bbb> [0.0000][12.0000][0.0000] 45.5,,,12.15 <45.5><><><12.15> [45.5000][0.0000][0.0000][12.1500] $
Testando a conversão cadeia para double • Eis uma função de teste, que converte a partir de
números obtidos de uma linha csv.
18/12/14 Programação Imperativa 425
Constatamos que, tal como esperado, uma cadeia não numérica como “aaa” converte para 0.0 e que o mesmos acontece com as cadeias vazias.
![Page 426: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/426.jpg)
Exchange exchange_from_line(char *s) { Exchange result; const char *a[MAX_DAYS+3]; int n = strings_from_csv(s, a); result.country = a[0]; result.currency = a[1]; result.code = a[2]; doubles_from_strings(a+3, n-3, result.rates); return result; }
Função exchange_from_line, finalmente • Podemos finalmente, programar a função que
transforma uma linha csv do ficheiro dos câmbios numa estrutura de tipo Exchange:
18/12/14 Programação Imperativa 426
Note bem: as cadeias “retiradas” da cadeia csv passada em argumento ficam na memória dinâmica.
![Page 427: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/427.jpg)
void exchanges_write(FILE *f, Exchange *a, int n) { for (int i = 0; i < n; i++) { fprintf(stdout, "[%s][%s][%s]", a[i].country, a[i].currency, a[i].code); doubles_fprintf(stdout, a[i].rates, MAX_DAYS, "[%.4f]"); fprintf(stdout, "\n"); } }
int exchanges_read(FILE *f, Exchange *a) { int result = 0; char line[MAX_LINE_LENGTH]; while (str_readline(f, line) != EOF) a[result++] = exchange_from_line(line); return result; }
Leitura para array • Agora, a função que lê o ficheiro dos câmbios para
um array de estruturas:
• Reciprocamente, uma função para escrever o array:
18/12/14 Programação Imperativa 427
![Page 428: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/428.jpg)
void test_exchange_read_write(const char *filename) { FILE *f = fopen(filename, "r"); assert(f != NULL); Exchange a[MAX_EXCHANGES]; int n = exchanges_read(f, a); exchanges_write(stdout, a, n); }
Testando a leitura e a escrita • Eis uma função de teste, que lê de um ficheiro
cujo pathname é dado em argumento para uma array de estruturas e depois escreve na consola o conteúdo do array:
18/12/14 Programação Imperativa 428
![Page 429: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/429.jpg)
int main(int argc, const char **argv) { // test_strings_from_csv(); // test_doubles_from_strings(); test_exchange_read_write(argv[1]); return 0; }
Função main com argumentos • Para ir buscar o nome do ficheiro de dados à linha de
comando, precisamos de uma função main com argumentos:
18/12/14 Programação Imperativa 429
$ ../../sources/a.out cambios_20141209.csv [AUSTRALIA][AUSTRALIAN DOLLAR][AUD][1.4903][1.4789][1.4765][1.4692][1.4620] [BRAZIL][BRAZILIAN REAL][BRL][3.2238][3.1777][3.1832][3.1728][3.1534] [BULGARIA][BULGARIAN LEV][BGN][1.9558][1.9558][1.9558][1.9558][1.9558] [CANADA][CANADIAN DOLLAR][CAD][1.4166][1.4027][1.4085][1.3998][1.4034] [CAPE VERDE][CAPE VERDE ESCUDO][CVE][110.2650][0.0000][110.2650][110.2650][110.2650] [CHINA][CHINESE YUAN RENMINBI][CNY][7.6549][7.5666][7.6055][7.5777][7.5752] [CROATIA][CROATIAN KUNA][HRK][7.6655][7.6700][7.6740][7.6753][7.6755]
... $
Neste exemplo, o ficheiro executável não está na diretoria de trabalho, onde residem os ficheiros de dados.
![Page 430: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/430.jpg)
Exchange *exchanges_find(Exchange *a, int n, const char *key) { for (int i = 0; i < n; i++) if (strcmp(a[i].code, key) == 0) return a+i; // same as &a[i] return NULL; }
Busca por chave • Tipicamente, acedemos ao array de câmbios pelo
código. • Dizemos que o array é uma tabela e que o código é a
chave da tabela. • Em vez de devolver o índice do primeiro elemento
que tem a chave dada ou -1 se não houver, é mais prático devolver o endereço desse elemento ou NULL se não houver:
18/12/14 Programação Imperativa 430
Mais uma vez o esquema “for if return return”, mas agora com endereços.
![Page 431: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/431.jpg)
// amount in euros; result in foreign currency double change_to_currency(Exchange *p, int amount) { return amount * p->rates[0]; }
Conversão cambial • Ao converter de euros para moeda estrangeira e de
moeda estrangeira para euros, usamos a estrutura que tem a informação relativa à moeda estrangeira, passada por apontador:
18/12/14 Programação Imperativa 431
// amount in foreign currency; result in euros double change_from_currency(Exchange *p, int amount) { return amount / p->rates[0]; }
Usamos sempre a cotação mais recente, que está na posição 0 do array rates.
![Page 432: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/432.jpg)
Função de teste interativa • Em cada passo, o utilizador entra três elementos de
informação: uma letra, indicando o sentido da conversão, uma cadeia representando o código da moeda e um número inteiro representando uma quantia.
• A letra é T (do inglês “to”), significando que é uma conversão de euros para moeda estrangeira, ou F (do inglês (“from”), significando que é uma conversão de moeda estrangeira para euros.
• O programa verifica se a letra é válida e se o código existe, mas NÃO verifica se o número é mesmo um número.
• A operação não se poderá realizar se a taxa de câmbio for 0.0.
18/12/14 Programação Imperativa 432
![Page 433: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/433.jpg)
Função de teste interativa, programa
18/12/14 Programação Imperativa 433
void test_conversion(const char *filename) { FILE *f = fopen(filename, "r"); assert(f != NULL); Exchange a[MAX_EXCHANGES]; int n = exchanges_read(f, a); char operation[MAX_LINE_LENGTH]; char code[MAX_LINE_LENGTH]; int amount; while (scanf("%s%s%d", operation, code, &amount) != EOF)
{ Exchange *p = exchanges_find(a, n, code); if (p == NULL) printf("Invalid code: %s\n", code); else if (p->rates[0] == 0.0) printf("Not available: %s\n", p->currency);
else if (*operation == 'T') { double z = change_to_currency(p, amount); printf("%s: %.4f\n", p->currency, z); } else if (*operation == 'F') { double z = change_from_currency(p, amount); printf("%s: %.4f\n", "EURO", z); } else printf("Invalid operation: <%s>\n", operation);
} }
Note bem: lemos a letra para uma cadeia e depois aproveitamos apenas o primeiro caráter dessa cadeia.
Se usássemos uma interface mais moderna, com janelas, botões, menus e caixas de texto, poderíamos arranjá-la de maneira a que fosse impossível escolher códigos inválidos, operações inválidas ou mesmo quantias inválidas.
![Page 434: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/434.jpg)
Testando na consola • Eis a função main, que, como a anterior, obtém o
nome do ficheiro na linha de comando:
18/12/14 Programação Imperativa 434
int main(int argc, const char **argv) { // test_strings_from_csv(); // test_doubles_from_strings(); // test_exchange_read_write(argv[1]); test_conversion(argv[1]); return 0; }
$ ../../sources/a.out cambios_20141209.csv T CHF 1000 SWISS FRANC: 1202.1000 T ISK 1000 Not available: ICELANDIC KRONA T III 200 Invalid code: III U CHF 1000 Invalid operation: <U> F PLN 20000 EURO: 4807.4612 T PLN 4808 POLISH ZLOTY: 20002.2416 $
![Page 435: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/435.jpg)
Programação Imperativa
Lição n.º 23 Apontadores
![Page 436: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/436.jpg)
Apontadores • Apontadores e endereços. • Tipos apontador. • Apontadores e arrays. • Apontadores em argumento. • Desreferenciação de apontadores. • Apontadores no scanf.
18/12/14 Programação Imperativa 436
![Page 437: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/437.jpg)
Apontadores • “A pointer is a variable that contains the
address of a variable” (K&R, p. 93). • Cada apontador contém endereços de
variáveis de um certo tipo, especificado na declaração do apontador.
• Com mais generalidade, um apontador de tipo T* é uma expressão cujo valor é o endereço de uma variável de tipo T.
18/12/14 Programação Imperativa 437
Na gíria, em vez de dizer “a variável p contém o endereço da variável x”, ou o “valor da expressão p é o endereço da variável x”, diz-se “o apontador p aponta para a variável x”, ou, mais brevemente, “p aponta para x”, admitindo que o contexto deixa claro quem são p e x.
![Page 438: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/438.jpg)
Tipos apontador • T* é o tipo dos apontadores que representam
endereços de variáveis de tipo T, para um tipo T pré-existente.
• Se p for um apontador de tipo T*, e se um valor de tipo T ocupar n bytes, o programa tratará os n bytes a partir da posição de endereço p como sendo um valor de tipo T.
• Todos os tipos apontador possuem um valor especial, NULL, usado para representar o endereço fictício de uma posição de memória que é inacessível.
18/12/14 Programação Imperativa 438
![Page 439: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/439.jpg)
Apontadores e endereços: não confunda • De novo: um apontador é uma expressão cujo valor
é um endereço. • Um endereço é apenas um número inteiro não
negativo que identifica um byte na memória. • O apontadores têm tipo: se o apontador é de tipo
T*, o programa considera que valor resultante da avaliação do apontador é o endereço de uma variável de tipo T.
• O valor de um apontador de tipo T* só pode ser guardado em variáveis de tipo T*; não pode ser guardado em variáveis de tipo U*, se U for diferente de T.
18/12/14 Programação Imperativa 439
De certa forma, podemos dizer que um apontador é um endereço ao qual está associado um tipo.
![Page 440: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/440.jpg)
Quando usamos variáveis apontador? • Quando queremos referenciar um array em memória
dinâmica; neste caso, o apontador aponta para a variável que constitui o primeiro elemento do array.
• Quanto programamos uma função em que um dos argumentos é um array; neste caso, o argumento é um apontador que, com no caso anterior, aponta para a variável que constitui o primeiro elemento do array.
• Quanto programamos uma função em que um dos argumentos é um endereço; neste caso, o argumento é um apontador que apontará para a variável cujo endereço for usado na chamada da função.
18/12/14 Programação Imperativa 440
Observámos esta última situação numa lição anterior, a propósito das estruturas, quando decidimos usar endereços para evitar copiar uma estrutura pesada passada como argumento a uma função.
![Page 441: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/441.jpg)
Endereços de arrays • Observe a seguinte função de teste, que usa dois
arrays, um na pilha e outro no heap. • O primeiro printf serve para observarmos os
endereços de base de cada um dos arrays:
18/12/14 Programação Imperativa 441
void test_pointers_1(void) { int a[50] = {0,1,4,9,16, 25,36,49,64,81, 100,121,144,169,196, 225}; int n = 16; int *b = ints_new(100); int m = ints_copy(a, n, b); assert(m == 16); printf("%p %p\n", a, b); ints_println(a, 6, " "); ints_println(a+2, 8, " "); ints_println(b, 5, " "); ints_println(b+10, 5, " "); }
$ ./a.out 0x7fff5775db10 0x7fed13404b70 0 1 4 9 16 25 4 9 16 25 36 49 64 81 0 1 4 9 16 100 121 144 169 196 $
Constatamos que, tal com esperávamos, os dois endereços estão muito afastados um do outro: o primeiro na pilha; o segundo no heap.
![Page 442: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/442.jpg)
Endereços de subarrays • Qual será a relação entre o endereço de um
subarray e o endereço do array de base?
18/12/14 Programação Imperativa 442
void test_pointers_2(void) { int a[50] = {0,1,4,9,16, 25,36,49,64,81, 100,121,144,169,196, 225}; int n = 16; int *b = ints_new(100); int m = ints_copy(a, n, b); assert(m == 16); printf("%p %p\n", a, b); printf("%p %p\n", a+1, b+1); printf("%p %p\n", a+2, b+2); printf("%p %p\n", a+8, b+8); }
$ ./a.out 0x7fff509b2b10 0x7f96a8404b70 0x7fff509b2b14 0x7f96a8404b74 0x7fff509b2b18 0x7f96a8404b78 0x7fff509b2b30 0x7f96a8404b90 $
Constatamos que, no caso geral, o endereço numérico do subarray a+k é o endereço numérico do array a mais 4*k. Este 4 aparece porque é o tamanho de um int, em bytes. Com o array b passa-se a mesma coisa. No caso geral, se cada elemento do array ocupar B bytes, o endereço numérico do subarray a+k é o endereço numérico do array a mais B*k.
![Page 443: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/443.jpg)
Note bem! • Nas funções de teste anteriores, a denota um array e
b denota um apontador. • Isto é, a representa um troço de memória fixo, que
começa no endereço 0x7fff509b2b10 e b representa uma variável que foi inicializada com o valor 0x7f96a8404b70; neste endereço começa um array que foi alocado dinamicamente (no heap).
• Note muito bem: b é uma variável; a NÃO é uma variável.
18/12/14 Programação Imperativa 443
void test_pointers_2(void) { int a[50] = {0,1,4,9,16, 25,36,49,64,81, 100,121,144,169,196, 225}; int n = 16; int *b = ints_new(100); int m = ints_copy(a, n, b); assert(m == 16); ... }
Claro que, apesar destas distinções técnicas, tanto a como b “são” arrays.
![Page 444: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/444.jpg)
Uma variável apontador é uma variável • O valor de b pode mudar; observe:
18/12/14 Programação Imperativa 444
void test_pointers_3(void) { int a[50] = {0,1,4,9,16, 25,36,49,64,81, 100,121,144,169,196, 225}; int n = 16; int *b = ints_new(100); printf("%p %p\n", a, b); int m = ints_copy(a, n, b); assert(m == 16); ints_println(b, 10, " "); b = ints_new(20); printf("%p\n", b); m = ints_id(b, 20); assert(m == 20); ints_println(b, 10, " "); b += 8; printf("%p\n", b); ints_println(b, 10, " "); b = a; printf("%p\n", b); ints_println(b, 10, " "); b = a+4; printf("%p\n", b); ints_println(b, 10, " "); }
$ ./a.out 0x7fff595f1b10 0x7f8690c04b70 0 1 4 9 16 25 36 49 64 81 0x7f8690c04a70 0 1 2 3 4 5 6 7 8 9 0x7f8690c04a90 8 9 10 11 12 13 14 15 16 17 0x7fff595f1b10 0 1 4 9 16 25 36 49 64 81 0x7fff595f1b20 16 25 36 49 64 81 100 121 144 169 $
int ints_id(int *a, int n) { for (int i = 0; i < n; i++) a[i] = i; return n; }
![Page 445: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/445.jpg)
void test_pointers_3_wrong(void) { int a[50] = {0,1,4,9,16, 25,36,49,64,81, 100,121,144,169,196, 225}; int n = 16; int *b = ints_new(100); printf("%p %p\n", a, b); int m = ints_copy(a, n, b); assert(m == 16); ints_println(b, 10, " "); a = ints_new(20); printf("%p\n", a); n = ints_id(a, 20); assert(n == 20); ints_println(a, 10, " "); a += 8; printf("%p\n", a); ints_println(a, 10, " "); a = b; printf("%p\n", a); ints_println(a, 10, " "); a = b+4; printf("%p\n", a); ints_println(a, 10, " "); }
$ gcc -Wall pointers.c pointers.c:146:5: error: array type 'int [50]' is not assignable a = ints_new(20); ~ ^ pointers.c:151:5: error: invalid operands to binary expression ('int [50]' and 'int') a += 8; ~ ^ ~ pointers.c:154:5: error: array type 'int [50]' is not assignable a = b; ~ ^ pointers.c:157:5: error: array type 'int [50]' is not assignable a = b+4; ~ ^ 4 errors generated. $
Um array não é uma variável • Se tratarmos o array a como se fosse uma variável, o
compilador reclama:
18/12/14 Programação Imperativa 445
Um array não é uma variável: um array é uma sequência de variáveis do mesmo tipo, contíguas na memória, representadas coletivamente pelo endereço da primeira dessas variáveis.
![Page 446: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/446.jpg)
Arrays em argumentos • Quando precisamos de uma função sobre arrays,
programamo-la em termos do endereço de base do array que há de vir no argumento.
• Recorde a função ints_sum:
• O argumento a é um apontador int *: o seu valor será o endereço de base do array que queremos somar.
• A expressão a[i], que designa o i-ésimo elemento do array, representa o conteúdo da memória i posições de int à frente da posição apontada por a.
18/12/14 Programação Imperativa 446
int ints_sum(const int *a, int n) { int result = 0; for (int i = 0; i < n; i++) result += a[i]; return result; }
Tecnicamente, o tipo do argumento a é const int *. O qualificador const significa que através do apontador a é proibido modificar o valor da variável apontada.
![Page 447: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/447.jpg)
Observando os argumentos array • Instrumentamos a função ints_sum para escrever na
consola os valores dos argumentos:
18/12/14 Programação Imperativa 447
int ints_sum(const int *a, int n) { printf("%p %d\n", a , n); int result = 0; for (int i = 0; i < n; i++) result += a[i]; return result; }
void test_pointers_4(void) { int a[50] = {7,2,9,5,4}; int n = 5; int *b = ints_new(100); printf("%p %p\n", a, b); int m = ints_id(b, 20); int z1 = ints_sum(a, n); int z2 = ints_sum(b, m); printf("%d %d\n", z1, z2); int z3 = ints_sum(a+1, n-1); int z4 = ints_sum(b+1, m-1); printf("%d %d\n", z3, z4); int z5 = ints_sum(a+2, 3); int z6 = ints_sum(b+10, 5); printf("%d %d\n", z5, z6); }
$ ./a.out 0x7fff5d226b10 0x7f8111c04b70 0x7fff5d226b10 5 0x7f8111c04b70 20 27 190 0x7fff5d226b14 4 0x7f8111c04b74 19 20 190 0x7fff5d226b18 3 0x7f8111c04b98 5 18 60 $
![Page 448: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/448.jpg)
Enganando a função que espera um array • Programámos a função ints_sum para que ela calcule
a soma de um array de int. • Normalmente, o argumento será o endereço de base
de algum array ou subarray existente em memória. • Mas a função credulamente aceita como argumento
qualquer endereço de tipo int *, e trata esse endereço como se aí começasse um array de int.
• Ora o operador de endereçamento & obtém o endereço de uma variável de tipo int.
• Logo, nada nos impede de enganar a função ints_sum, passando-lhe o endereço de uma variável int, em vez de um array verdadeiro.
18/12/14 Programação Imperativa 448
![Page 449: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/449.jpg)
A função do engano • Observe a seguinte função de teste, que trata os
endereço das variáveis x, y e z como se fossem arrays:
18/12/14 Programação Imperativa 449
void test_pointers_5(void) { int x = 8; int y = 15; int z = 40; printf("%p %p %p\n", &x, &y, &z); int r1 = ints_sum(&z, 3); int r2 = ints_sum(&y, 2); int r3 = ints_sum(&x, 1); printf("%d %d %d\n", r1, r2, r3); }
$ ./a.out 0x7fff5efb8bdc 0x7fff5efb8bd8 0x7fff5efb8bd4 0x7fff5efb8bd4 3 0x7fff5efb8bd8 2 0x7fff5efb8bdc 1 63 23 8 $
Como as três variáveis, x, y e z estão na memória de seguida, na ordem z, y, x, o valor de r1 contém a soma das três, obtida como fizessem parte de um array (que começa em z).
![Page 450: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/450.jpg)
Desreferenciação • Já aprendemos que, sendo a um array, a expressão *a
é equivalente a a[0]. • Tecnicamente, * é o operador de desreferenciação. • Com mais generalidade, sendo x uma expressão de
tipo T *, cujo valor é p, a expressão *x representa o valor (de tipo T) localizado na posição de memória cujo endereço é p.
• Ora, se a representar um array, já aprendemos que a expressão a+i representa o subarray de a que começa na posição correspondente a a[i].
• Logo, a expressão *(a+i) representa o valor de a[i]. • Por outras palavras, o endereço de a[i] vale a+i. • Ou seja, a expressão &a[i] é equivalente a a+i. 18/12/14 Programação Imperativa 450
![Page 451: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/451.jpg)
Propriedade fundamental dos arrays
a[i] ≡ *(a+i)
18/12/14 Programação Imperativa 451
![Page 452: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/452.jpg)
Significado • Para obter o valor do a[i], o programa soma o
valor numérico do endereço de base do array a e o número de bytes que cada elemento do array ocupa na memória multiplicado pelo valor do índice i.
• Ou seja, o acesso a um elemento de um array, qualquer que ele seja, envolve apenas uma multiplicação e uma adição.
• Os nossos olhos veem a[i] nos programas, mas o compilador apenas “vê” *(a+i).
18/12/14 Programação Imperativa 452
Quer dizer, as operações vetoriais da nossa imaginação são implementas por meio de operações sobre endereços no computador.
![Page 453: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/453.jpg)
Argumentos que representam endereços • Já vimos que quando queremos parametrizar
uma função com um array, usamos um apontador, o qual representará o endereço de base do array.
• Por vezes, queremos mesmo parametrizar a função com um endereço (que não tem nada a ver com arrays.)
• Vimos essa técnica a propósito das estruturas, quando observámos que “as estruturas pesadas passam por apontador”, para evitar a sobrecarga computacional necessária para copiar a estrutura, se passasse por valor.
18/12/14 Programação Imperativa 453
![Page 454: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/454.jpg)
Charada • Que faz a seguinte função?
• Troca os valores dos argumentos, claro!
18/12/14 Programação Imperativa 454
void swap(int x, int y) { int m = x; x = y; y = m; }
![Page 455: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/455.jpg)
Errado! Fica tudo na mesma • Função de teste:
• Ah, pois: o que troca são as variáveis locais da função swap, que foram inicializadas no momento da chamada com cópias do valores das variáveis da função de teste.
• Para que a função chamada possa aceder às variáveis da função que chama, esta deve enviar-lhe o endereço dessas variáveis.
18/12/14 Programação Imperativa 455
void test_swap(void) { int x, y; scanf("%d%d", &x, &y); printf("%d %d\n", x, y); swap(x, y); printf("%d %d\n", x, y); }
$ ./a.out 49 81 49 81 49 81 $
![Page 456: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/456.jpg)
Função swap, com endereços • Observe:
• É como se os dois argumentos fossem arrays e a função swap trocasse o valor do primeiro elemento de um dos arrays com o valor do primeiro elemento do outro.
18/12/14 Programação Imperativa 456
$ ./a.out 49 81 49 81 49 81 $
void swap(int *x, int *y) { int m = *x; *x = *y; *y = m; }
void test_swap(void) { int x, y; scanf("%d%d", &x, &y); printf("%d %d\n", x, y); swap(&x, &y); printf("%d %d\n", x, y); }
$ ./a.out 32 75 32 75 75 32 $
![Page 457: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/457.jpg)
Endereços no scanf • Desde cedo aprendemos que para ler o valor de uma
variável x de tipo int com o scanf era preciso “meter um & antes do x”:
• De facto, o que estamos a fazer é passar ao scanf o
endereço da variável x, usando o operador de endereçamento &, para que o scanf possa modificar o conteúdo da variável x, em resultado da leitura realizada.
18/12/14 Programação Imperativa 457
void test_scanf_int(void) { int x; scanf("%d", &x); printf("%d\n", x); }
$ ./a.out 512 512 $
![Page 458: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/458.jpg)
Caso das cadeias de carateres • Também aprendemos que o scanf de cadeias de
carateres não leva o &:
• Não leva o & porque, sendo s uma cadeia de
carateres, a expressão s no argumento do scanf representa o endereço dessa cadeia.
• Através desse endereço, o scanf tem acesso à cadeia declarada na função de teste, como convém.
18/12/14 Programação Imperativa 458
void test_scanf_string(void) { char s[MAX_LINE_LENGTH]; scanf("%s", s); printf("%s\n", s); }
$ ./a.out 512 512 $
![Page 459: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/459.jpg)
Programação Imperativa
Lição n.º 24 Apontadores, parte 2
![Page 460: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/460.jpg)
Apontadores, parte 2 • Aritmética de apontadores. • Apontadores para funções. • Arrays de funções. • Epílogo.
18/12/14 Programação Imperativa 460
![Page 461: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/461.jpg)
Os arrays não existem • Os arrays não existem, a não ser na nossa
imaginação. • Nos computadores só há uma sequência de
células de memória. • Podíamos programar todos os arrays só com
endereços e desreferenciação. • Para isso, basta substituir a[i] por *(a+i) e usar
malloc para alocar memória dinamicamente. • Fica esquisito, mas só porque não estamos
habituados.
18/12/14 Programação Imperativa 461
![Page 462: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/462.jpg)
Soma de um array, sem array • Comecemos com a conhecida função para somar um
array de int:
18/12/14 Programação Imperativa 462
int ints_sum(const int *a, int n) { int result = 0; for (int i = 0; i < n; i++) result += a[i]; return result; }
• Retiremos a indexação: int ints_sum_1(const int *a, int n) { int result = 0; for (int i = 0; i < n; i++) result += *(a+i); return result; }
![Page 463: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/463.jpg)
Soma de um array, evitando multiplicações • Sabemos que calcular *(a+i) envolve uma
multiplicação e uma adição. • Ora isso é feito em cada passo do ciclo. • Na verdade, seria mais simples ir somando 1 a a, em
cada passo do ciclo:
18/12/14 Programação Imperativa 463
int ints_sum_2(const int *a, int n) { int result = 0; for (int i = 0; i < n; i++) { result += *a; a++; } return result; }
Assim, percorremos o array todo, poupando nas multiplicações. Antigamente, esta era uma técnica básica de programação com C. Agora, confiamos que o compilador otimizará o nosso código, transformando o nosso programa original num equivalente a este, automaticamente.
![Page 464: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/464.jpg)
*a++ • Na verdade, podemos reunir as duas instruções do
ciclo numa só:
• Note que *a++ significa *(a++). • Ora o valor de a++ é o valor de a (tal com o valor
de x++ é o valor de x, quando x é um número.) • Logo o valor de *a++ é o valor de *a, tal como
convém. 18/12/14 Programação Imperativa 464
int ints_sum_3(const int *a, int n) { int result = 0; for (int i = 0; i < n; i++) result += *a++; return result; }
![Page 465: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/465.jpg)
Estilo • Quase sempre, preferimos não mudar o valor dos
argumentos das funções. • Nessa onda, reprogramaríamos a função anterior
assim:
18/12/14 Programação Imperativa 465
int ints_sum_4(const int *a, int n) { int result = 0; const int *p = a; for (int i = 0; i < n; i++) result += *p++; return result; } Uma vez percebido isto,
continuamos a preferir a primeira versão!
![Page 466: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/466.jpg)
Aritmética de apontadores • Da regra fundamental a[i] ≡ *(a+i) derivamos
as seguintes:
18/12/14 Programação Imperativa 466
A última regra significa que a diferença entre os apontadores para dois elementos de um array é igual à diferença dos índices.
• a[0] ≡ *a • &a[i] = a+i • &a[0] = a • &a[i] – a = i • &a[i] – &a[k] = i – k
Preciosismo: usamos o sinal ≡ para significar a identidade entre a variável da esquerda e a variável da direita; usamos o sinal = para significar a igualdade entre o valor da esquerda e o valor da direita.
![Page 467: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/467.jpg)
Exemplo: atualização da função ints_get • A função ints_get é assim:
18/12/14 Programação Imperativa 467
int ints_get(int *a) { int result = 0; int x; while (scanf("%d", &x) != EOF) a[result++] = x; return result; }
int ints_get(int *a) { int *p = a; while (scanf("%d", p) != EOF) p++; return (int)(p-a); }
• Sem arrays, pode ficar assim: Podíamos pensar que a função de baixo é mais eficiente do que a de cima, porque coloca o valor lido logo na posição certa, em vez de passar por uma variável intermediária. No entanto, na prática isso é irrelevante.
![Page 468: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/468.jpg)
Outro exemplo: str_len • Programámos a função str_len assim:
• Se fosse hoje, teria ficado assim:
18/12/14 Programação Imperativa 468
int str_len(const char *s) { int result = 0; while (s[result] != '\0') result++; return result; }
int str_len(const char *s) { const char *p = s; while (*p) p++; return (int)(p-s); }
Note bem: numa instrução while ou numa instrução if, escrever while(x) ou if(x), sendo x uma expressão qualquer (de tipo int), é o mesmo que escrever while(x!=0) ou if(x!=0), respetivamente.
![Page 469: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/469.jpg)
Otimização prematura • Em teoria, percorrer arrays com apontadores é mais
eficiente do que fazê-lo com índices. Mas:
18/12/14 Programação Imperativa 469
Premature optimization is the root of all evil.
Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. -- Donald Knuth
![Page 470: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/470.jpg)
Apontadores para funções • As funções “são” apontadores. • Não acredita? Então observe:
• Cada função existe na memória, na sua forma executável, quando o programa corre.
• Assim, sendo f o identificador de uma função, a expressão f representa o endereço da posição de memória onde está a função.
18/12/14 Programação Imperativa 470
void test_function_pointers(void) { printf("%p\n", str_len); printf("%p\n", ints_get); printf("%p\n", ints_sum); printf("%p\n", test_function_pointers); }
$ ./a.out 0x100ecec10 0x100ece650 0x100ece870 0x100eced50 $
OK, mas para que é que isto serve?
![Page 471: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/471.jpg)
Funções que são argumento de outras • Quando uma função é usada como argumento de
outra, o que passa é o endereço. • Foi o que usámos nas funções de ordenação geral,
onde parametrizámos a função de comparação:
18/12/14 Programação Imperativa 471
int point_cmp_y_x(Point p, Point q) { int result = p.y - q.y; if (result == 0) result = p.x - q.x; return result; } void points_isort_gen(Point *a, int n, Point_cmp cmp)
{ ... }
Portanto, o terceiro argumento da função points_isort_gen é um apontador para função.
void test_points_isort_by_y_x(void) { Point a[1000]; int n = points_read(stdin, a); points_fprintfln(stdout, a, n, "<%d,%d>"); points_isort_gen(a, n, point_cmp_y_x); points_fprintfln(stdout, a, n, "[%d,%d]"); }
![Page 472: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/472.jpg)
Arrays de funções • Primeiro definimos o tipo das funções que queremos
meter no array: • Este typedef define Function como sendo o tipo das
funções em que o primeiro argumento é de tipo const int *, o segundo argumento é de tipo int e o resultado é de tipo int.
• As funções ints_sum, int_min e ints_max são desse tipo:
18/12/14 Programação Imperativa 472
typedef int (*Function)(const int *, int);
int ints_sum(const int *a, int n) { ... }
int ints_min(const int *a, int n) { ... }
int ints_max(const int *a, int n) { ... }
![Page 473: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/473.jpg)
Exemplo com array de funções • Observe a função de teste:
18/12/14 Programação Imperativa 473
void test_function_array(void) { Function f[3] = {ints_min, ints_max, ints_sum}; int a[1000]; int n = ints_get(a); for (int i = 0; i < 3; i++) { int z = (*f[i])(a, n); printf("%d\n", z); } }
$ ./a.out 7 10 3 12 6 10 3 12 48 $
Repare na estranha sintaxe usada na chamada da função: (*f[i])(a, n).
![Page 474: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/474.jpg)
Variáveis função • Nesta altura, não espantará que possamos ter uma
variável que representa uma função. • Veja, como exemplo, a seguinte nova versão da
função de teste anterior:
18/12/14 Programação Imperativa 474
void test_function_variable(void) { Function f[3] = {ints_min, ints_max, ints_sum}; int a[1000]; int n = ints_get(a); for (int i = 0; i < 3; i++) { Function func = f[i]; int z = func(a, n); printf("%d\n", z); } }
A variável func, de tipo Function, recebe o valor f[i]. Na verdade, trata-se de uma afetação de apontadores.
Repare que a chamada da função através da variável se faz normalmente: func(a, n). No entanto, também seria possível escrever (*func)(a, n). Esta é a forma clássica.
![Page 475: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/475.jpg)
Aplicação à função main • Na sua forma básica, a função main contém apenas
uma série de chamadas de funções de teste, todas comentadas menos uma:
18/12/14 Programação Imperativa 475
int main(void) { // test_pointers_1(); // test_pointers_2(); // test_pointers_3(); // test_pointers_4(); // test_pointers_5(); // test_swap(); // test_scanf_int(); // test_scanf_string(); // test_ints_sum(); // test_str_len(); // test_function_pointers(); // test_function_array(); test_function_variable(); return 0; }
Isto é pouco prático quando temos de alternar frequentemente entre as funções de teste, pois é preciso recompilar de cada vez.
![Page 476: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/476.jpg)
int main(int argc, const char **argv) { char x = 'A'; if (argc > 1) x = *argv[1]; if (x == 'A') test_pointers_1(); else if (x == 'B') test_pointers_2(); else if (x == 'C') test_pointers_3(); else if (x == 'D') test_pointers_4(); else if (x == 'E') test_pointers_4(); else if (x == 'F') test_swap(); else if (x == 'G') test_scanf_int(); else if (x == 'H') test_scanf_string(); else if (x == 'I') test_ints_sum(); else if (x == 'J') test_str_len(); else if (x == 'K') test_function_pointers(); else if (x == 'L')
test_function_array(); else if (x == 'M') test_function_variable(); else printf("Invalid choice: [%s]\n", argv[1]); return 0; }
Linha de comando • Já sabemos que
podemos manter todas as funções de teste ativas, selecionando a que queremos usar na linha de comando:
18/12/14 Programação Imperativa 476
![Page 477: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/477.jpg)
Array das funções de teste • Arrumamos todas as funções de teste num array:
18/12/14 Programação Imperativa 477
typedef void (*TEST)(void); #define MAX_TESTS 13 TEST tests[MAX_TESTS] = { test_pointers_1, // A test_pointers_2, // B test_pointers_3, // C test_pointers_4, // D test_pointers_5, // E test_swap, // F test_scanf_int, // G test_scanf_string, // H test_ints_sum, // I test_str_len, // J test_function_pointers, // K test_function_array, // L test_function_variable, // M };
![Page 478: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/478.jpg)
Função main moderna • A função main seleciona a função de teste por
indexação, usando o argumento na linha de comando:
18/12/14 Programação Imperativa 478
int main(int argc, const char **argv) { int x = 0; if (argc > 1) x = *argv[1] - 'A'; if (0 <= x && x < MAX_TESTS) (*tests[x])(); else printf("Invalid choice: [%s]\n", argv[1]); return 0; }
Neste exemplo, todas as funções de teste são rigorosamente do mesmo tipo. Se não fossem, também se fazia, mas era ligeiramente mais complicado.
![Page 479: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/479.jpg)
Epílogo • Não vimos matrizes. • Matrizes são arrays de arrays, todos do mesmo
tamanho e todos justapostos em memória. • Note que um array de arrays dinâmicos é apenas
um array de apontadores obtidos, cada um deles por meio de um malloc.
• Um array de cadeias de carateres é uma coisa dessas.
• Uma matriz é outra coisa. • Será o primeiro assunto em Laboratório de
Programação.
18/12/14 Programação Imperativa 479
![Page 480: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/480.jpg)
Linguagem C • Não percorremos a linguagem C toda. • Dos aspetos importantes, não demasiado
especializados, falta ver e praticar: • Os operadores de bits. • Os apontadores void *. • As estruturas auto-referenciadas, que permitem criar listas
ligadas, por exemplo. • Algumas funções de biblioteca.
18/12/14 Programação Imperativa 480
![Page 481: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/481.jpg)
Linguagem C, omissões • Omitimos deliberadamente algumas da
instruções do C: • switch • break • continue • goto • do-while
• Destas, apenas a última tem algum mérito; mesmo assim, nunca nos fez falta.
18/12/14 Programação Imperativa 481
![Page 482: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/482.jpg)
Modularidade • Todos os nossos programas residem num
único ficheiro fonte. • No futuro (começando em Laboratório de
Programação) não será assim: as funções que estamos sempre a usar (por exemplo, as funções sobre arrays de int ou arrays de cadeias) estarão em ficheiros separados que serão compilados juntamente com o nosso programa.
• Isto substituirá a técnica do copy-paste a partir de programas anteriores.
18/12/14 Programação Imperativa 482
![Page 483: Pi1415 tudo](https://reader030.vdocuments.site/reader030/viewer/2022032618/55b880debb61eb177f8b477b/html5/thumbnails/483.jpg)
18/12/14 Programação Imperativa 483
return 0;