Programação Imperativa -...

483
Programação Imperativa Lição n.º 1 Preliminares

Transcript of Programação Imperativa -...

Page 1: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 1 Preliminares

Page 2: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

O que é um computador?

18/12/14 Programação Imperativa 6

ENIAC (1946)

UNIVAC I (1951)

Page 7: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

O que é um computador? (4)

18/12/14 Programação Imperativa 9

Computador “torre” Computador “laptop”

Page 10: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

O que é um computador? (5)

18/12/14 Programação Imperativa 10

Computador “torre” Computador “laptop”

Surface Pro 3 e Macbook Air

Page 11: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

“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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Principais linguagens

18/12/14 Programação Imperativa 17

Page 18: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Bibliografia

18/12/14 Programação Imperativa 20

Page 21: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 2 Programação com C

Page 22: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 3 Operações aritméticas

Page 42: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

[-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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 4 Escolhas

Page 64: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 5 Ciclos

Page 80: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

•  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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 6 Ciclos for e ciclos while

Page 100: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 7 Arrays

Page 118: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Arrays •  Generalidades sobre arrays. •  Funções sobre arrays.

18/12/14 Programação Imperativa 118

Page 119: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 8 Arrays e memória

Page 136: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 9 Estatísticas

Page 154: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 10 Buscas

Page 176: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Buscas •  Buscas lineares em arrays. •  Operadores lógicos. •  Igualdade de arrays. •  Reabrindo a consola.

18/12/14 Programação Imperativa 176

Page 177: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Operadores lógicos

18/12/14 Programação Imperativa 185

&& conjunção || disjunção ! negação

Page 186: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 11 Subarrays

Page 194: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Subarrays •  Subarrays em C. •  Grupos. •  Remoção de duplicados.

18/12/14 Programação Imperativa 194

Page 195: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 12 Arrays ordenados

Page 212: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Arrays ordenados •  Renque. •  Busca dicotómica. •  Método da bisseção.

18/12/14 Programação Imperativa 212

Page 213: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 13 Ordenação de arrays

Page 232: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 14 Complexidade dos algoritmos de ordenação

Page 254: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 15 Memória

Page 276: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 16 A pilha de execução

Page 288: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Primeira observação: à entrada de f1

18/12/14 Programação Imperativa 292

Page 293: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

•  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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Quarta observação: à saída de t1

18/12/14 Programação Imperativa 296

Nota: 0x0000018E é 256 + 8 * 16 + 14 = 398;

Page 297: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Observações

18/12/14 Programação Imperativa 298

Page 299: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Observando a recursividade

18/12/14 Programação Imperativa 301

Page 302: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Inicialização dos arrays dinâmicos

18/12/14 Programação Imperativa 306

Page 307: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 17 Cadeias de carateres

Page 310: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Cadeias de carateres •  Conceitos básicos. •  Arrays de cadeias de carateres.

18/12/14 Programação Imperativa 310

Page 311: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 18 Técnicas com arrays de cadeias de carateres

Page 322: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 19 Estruturas e arrays de estruturas

Page 346: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 20 Ordenação de arrays de estruturas

Page 366: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Â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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 21 Passagem de argumentos

Page 390: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Passagem de argumentos •  Passagem por valor. •  Caso das estruturas pesadas. •  Passagem de endereços.

18/12/14 Programação Imperativa 390

Page 391: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 22 Leitura avançada de ficheiros

Page 412: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Leitura avançada de ficheiros •  Leitura de ficheiros csv. •  Processamento de tabelas.

18/12/14 Programação Imperativa 412

Page 413: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

// 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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 23 Apontadores

Page 436: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Propriedade fundamental dos arrays

a[i] ≡ *(a+i)

18/12/14 Programação Imperativa 451

Page 452: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

Programação Imperativa

Lição n.º 24 Apontadores, parte 2

Page 460: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

*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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

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: Programação Imperativa - w3.ualg.ptw3.ualg.pt/~pjguerreiro/sites/21_pi_1415/lessons/pi1415_tudo.pdf · Fonte: IEEE Standard Glossary of Computer Hardware Terminology (1994). I'm

18/12/14 Programação Imperativa 483

return 0;