Este documento apresenta um resumo de três frases ou menos sobre o conteúdo do documento fornecido:
O documento introduz o curso de Programação Imperativa, descrevendo o conteúdo programado, como as aulas serão ministradas e avaliadas. É apresentado o que é um computador e programa, e discutidas linguagens de programação e a linguagem C que será usada no curso.
2. 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
3. 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 CristinaVieira.
• Avaliação ao longo do funcionamento e exame
final.
• Página na tutoria: http://goo.gl/1B56WO.
18/12/14 Programação Imperativa 3
4. 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
5. 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
6. O que é um computador?
18/12/14 Programação Imperativa 6
ENIAC (1946)
UNIVAC I (1951)
7. O que é um computador? (2)
18/12/14 Programação Imperativa 7
IBM 360 (1965)
DG Eclipse MV/8000 (1980)DECVAX-11/780 (1978)
PDP 11/70 (1975)
8. 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)
9. O que é um computador? (4)
18/12/14 Programação Imperativa 9
Computador “torre” Computador “laptop”
10. O que é um computador? (5)
18/12/14 Programação Imperativa 10
Computador “torre” Computador “laptop”
Surface Pro 3 e Macbook Air
11. 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
12. “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).
13. 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
14. 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
15. 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
16. 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
18. 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
19. 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
22. 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
23. 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
24. 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
25. 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.
26. 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.
27. 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
28. 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.
29. 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("%fn", z);
}
O compilador continuaria a
dar erro, porque continua a
faltar a função main.
30. 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>.
31. 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("%fn", 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>.
32. 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.
33. void test_weighted_average(void)
{
double lb;
double ex;
scanf("%lf%lf", &lb, &ex);
double z = weighted_average(lb, ex);
printf("%fn", 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.
34. 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
35. 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("%fn", 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.
36. 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!
37. 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("%fn", v);
double z = grade(lb, ex);
printf("%fn", z);
}
}
38. 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.
39. 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.
40. 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
42. 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
43. 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
44. 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("%dn", 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.
45. [-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
46. 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.
47. 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.
48. 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("%dn", z1);
int z2 = x - y;
printf("%dn", z2);
int z3 = x * y;
printf("%dn", z3);
int z4 = x / y;
printf("%dn", z4);
int z5 = x % y;
printf("%dn", 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
49. 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.
50. 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("%fn", z1);
double z2 = x - y;
printf("%fn", z2);
double z3 = x * y;
printf("%fn", z3);
double z4 = x / y;
printf("%fn", 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.
51. 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.
52. 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>.
53. 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);
}
54. 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.
55. 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("%fn", v);
double z = grade(lb, ex);
printf("%fn", z);
int g = final_grade(lb, ex);
printf("%dn", g);
}
} Em geral, é prudente observar também os
resultados intermédios nas funções de teste.
56. 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
57. 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.
58. 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?
59. 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.
60. 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.
61. 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);
}
62. 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("%fn", z1);
double z2 = exam_one_decimal(lb, gl);
printf("%fn", z2);
double z = exam(lb, gl);
printf("%fn", z);
int x1 = grade(lb, z);
printf("%dn", x1);
int x2 = grade(lb, z - 0.1);
printf("%dn", x2);
}
}
64. Escolhas
• Instrução if-else.
• Constantes simbólicas.
• Declaração de variáveis.
• If-else em cascata.
18/12/14 Programação Imperativa 64
65. 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
66. 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
67. 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
68. 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;
}
69. 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;.
70. 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:
71. 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("%fn", z1);
double z2 = roaming(seconds);
printf("%fn", 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
72. 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
73. 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
74. 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
75. 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.
76. 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
77. 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.
78. 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.
80. 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
81. 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
83. 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);
}
84. 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
85. 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("%fn", z);
printf("%en", z);
printf("%gn", 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
86. 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("%.20fn", z);
printf("%.20en", z);
printf("%.20gn", z);
87. 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.
88. 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
89. 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.
90. 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
91. 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
92. 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
93. 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.
94. void test_sum_geometric_all(void)
{
double x;
int n;
while (scanf("%lf%d", &x, &n) != EOF)
{
double z1 = sum_geometric(x, n);
printf("%.16gn", z1);
double z2 = sum_geometric_i(x, n);
printf("%.16gn", z2);
double z3 = sum_geometric_t(x, n);
printf("%.16gn", z3);
double z4 = sum_geometric_bad(x, n);
printf("%.16gn", 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
95. 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.
96. 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;
}
97. 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.
98. 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.
100. 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
101. 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
102. 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.
103. 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.
104. 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("%.15gn", 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.
105. 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...)
106. 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
107. 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.
108. 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("%dn", z1);
double z2 = sum_geometric(0.5, z1);
printf("%.15fn", z2);
}
}
$ ./a.out
0.1
5
1.937500000000000
0.01
8
1.992187500000000
0.001
11
1.999023437500000
0.000001
21
1.999999046325684
109. 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 ímpar
x / 2, se não
!
"
#
http://en.wikipedia.org/wiki/Collatz_conjecture
http://uva.onlinejudge.org/external/1/100.html
110. 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;
}
111. 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("%dn", 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
112. 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;
}
113. 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("%dn", 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
114. 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);
}
115. 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;
}
116. 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("%dn", z1);
int z2 = decimal_length_i(x);
printf("%dn", z2);
}
}
$ ./a.out
6413
4
4
9761826
7
7
5
1
1
0
1
1
3321
4
4
• Testamos as duas variantes:
119. 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
120. 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.
122. 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
123. 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
124. 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
125. 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
126. 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.
127. 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.
128. 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.
129. 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.
130. 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.
131. 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.
132. 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.
133. 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...
134. 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.
136. Arrays e memória
• Lendo e escrevendo arrays.
• Observando os arrays na memória.
• Buffer overflow.
18/12/14 Programação Imperativa 136
137. 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");
}
138. 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.
139. 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];
140. 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
141. 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");
}
142. 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
143. 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;
}
144. 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.
145. 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("OKn");
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.
146. Observando a memória do programa
• Usando oVisual 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”.
147. 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.
148. 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.
149. 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.
150. 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.
151. 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.”
152. 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
154. 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
155. 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");
}
156. 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.
157. 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.
158. 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("%dn", 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
159. 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("%dn", 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
…
160. 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.
161. 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.
162. 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.
163. 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("%dn", 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”.
164. 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.
165. 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.
166. 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.
167. 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;
}
168. 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.
169. 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;
}
171. 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.
172. 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...
173. 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;
}
174. 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;
}
176. Buscas
• Buscas lineares em arrays.
• Operadores lógicos.
• Igualdade de arrays.
• Reabrindo a consola.
18/12/14 Programação Imperativa 176
177. 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
178. 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.
179. 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.
180. 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
...
181. 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);
182. 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("%dn", 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.
183. 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.
184. 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.
186. 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.
187. 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ê?
188. 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
$
189. 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("%dn", 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
...
190. 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.
191. 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("%dn", z);
}
192. 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);
}
194. Subarrays
• Subarrays em C.
• Grupos.
• Remoção de duplicados.
18/12/14 Programação Imperativa 194
195. 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.
196. 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].
197. 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
198. 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.
199. 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
...
200. 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.
201. 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;
}
202. 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 %dn", z, b[z]);
printf("%d %dn", 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.
203. 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);
204. 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);
205. 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;
}
206. 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!
207. 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.
208. 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 é?
209. 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;
}
210. 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...
213. 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.
214. 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.
215. 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);
216. 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.
217. 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
218. 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