Programação Imperativa
Lição n.º 1
Preliminares
Preliminares
•  Apresentação.
•  A programação na LEI.
•  O que é um computador?
•  O que é um programa?
•  Linguagens de ...
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 ...
A programação na LEI
•  Programação Imperativa.
•  Laboratório de Programação.
•  Programação Orientada por Objetos.
•  Al...
A programação na LEI
•  Programação Imperativa.
•  Laboratório de Programação.
•  Programação Orientada por Objetos.
•  Al...
O que é um computador?
18/12/14 Programação Imperativa 6
ENIAC (1946)
UNIVAC I (1951)
O que é um computador? (2)
18/12/14 Programação Imperativa 7
IBM 360 (1965)
DG Eclipse MV/8000 (1980)DECVAX-11/780 (1978)
...
O que é um computador? (3)
18/12/14 Programação Imperativa 8
IBM PC 5150
(12 de Agosto de 1981)
Apple Macintosh
(24 de Jan...
O que é um computador? (4)
18/12/14 Programação Imperativa 9
Computador “torre” Computador “laptop”
O que é um computador? (5)
18/12/14 Programação Imperativa 10
Computador “torre” Computador “laptop”
Surface Pro 3 e Macbo...
E ainda...
18/12/14 Programação Imperativa 11
Fonte: IEEE Standard Glossary of Computer
Hardware Terminology (1994).
I'm s...
“Definição” de computador
A device that consists of one or more associated
processing units and peripheral units, that is
...
O que é um programa?
•  Um programa é uma sequência de instruções
que um computador executará automatica-
mente, para leva...
Como são os programas?
•  Os programas são texto, isto é, sequências de
frases, formadas por palavras, formadas por
carate...
Programação imperativa
•  A programação imperativa é um estilo de
programação que reflete a ideia fundamental
de que as in...
Linguagens de programação
•  Os programas são escritos usando linguagens
de programação.
•  Cada linguagem de programação ...
Principais linguagens
18/12/14 Programação Imperativa 17
A linguagem de programação C
•  Em Programação Imperativa programaremos
em C.
•  A linguagem C foi inventada por
Dennis Ri...
Evolução do C
•  1972: invenção do C.
•  1989: normalização ANSI C, ou C89.
•  1990: normalização ISO C, ou C90, igual à
a...
Bibliografia
18/12/14 Programação Imperativa 20
Programação Imperativa
Lição n.º 2
Programação com C
Programação com C
•  Problemas de programação.
•  Decomposição funcional.
•  Funções em C.
•  Funções de teste.
18/12/14 P...
Problemas de programação
•  Tipicamente, a tarefa de um programador é
escrever programas para realizar determinadas
tarefa...
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...
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 fu...
Ambiente de programação
•  Programaremos escrevendo os nossos
programas num editor de texto e compilando
numa janela de co...
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 o...
Função weighted_average
•  Observe:
•  Usamos aqueles nomes lab e exam para deixar
claro o significado dos argumentos.
18/...
Função de teste
•  Escrevamos uma função de teste para
exercitar a função weighted_average:
18/12/14 Programação Imperativ...
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_av...
Programa completo
18/12/14 Programação Imperativa 31
#include <stdio.h>
double weighted_average(double lab, double exam)
{...
Experimentando
•  Compilamos e corremos na janela de comando:
18/12/14 Programação Imperativa 32
sources pedro$ gcc -Wall ...
void test_weighted_average(void)
{
double lb;
double ex;
scanf("%lf%lf", &lb, &ex);
double z = weighted_average(lb, ex);
p...
Experimentando repetidamente, melhor
•  Em vez de interromper o programa à bruta,
com ctrl-C, é melhor deixar o programa s...
Ciclo de teste
•  Observe com muita atenção:
18/12/14 Programação Imperativa 35
void test_weighted_average(void)
{
double ...
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
•  Cas...
Função de teste para a nota exata
•  Para controlo, incluímos também uma
chamada à função weighted_average:
18/12/14 Progr...
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
com...
Experimentando a nota exata
•  Eis uma sessão de
experimentação,
usando a nova
função main:
18/12/14 Programação Imperativ...
Conclusão
•  Já conseguimos calcular a nota exata, isto é, a
nota calculada com toda a precisão.
•  Falta calcular a nota ...
Programação Imperativa
Lição n.º 3
Operações aritméticas
Programação com C
•  Aritmética em C.
•  Aritmética int.
•  Aritmética double.
•  Aritmética mista.
•  Funções matemáticas...
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 p...
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:
1...
[-2147483648..2147483647] ou [-231..231-1]
•  Em C, cada número int ocupa uma palavra de
32 bits.
•  A sequência dos valor...
Overflow
•  Há overflow de inteiros quando o resultado de
um cálculo com números inteiros cai fora do
intervalos dos númer...
Operações aritméticas, tipo int
•  Adição: x + y
•  Subtração: x – y
•  Multiplicação: x * y
•  Quociente da divisão intei...
Testando as operações aritméticas, int
18/12/14 Programação Imperativa 48
void test_operations_int(void)
{
int x;
int y;
w...
Operações aritméticas, tipo double
•  Adição: x + y
•  Subtração: x – y
•  Multiplicação: x * y
•  Quociente da divisão: x...
Testando as operações aritméticas, double
18/12/14 Programação Imperativa 50
void test_operations_double(void)
{
double x;...
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 é “...
Funções matemáticas de biblioteca
•  O C traz um pequeno conjunto de funções
matemáticas, operando sobre doubles:
18/12/14...
Arredondamento
•  No problema da nota, precisamos de
arredondar a nota exata, para o inteiro mais
próximo, tendo o cuidado...
Nota final
•  A nota final é o arredondamento da nota
exata e dever ser expressa no tipo int.
•  Devemos pois explicitar a...
Função de teste para a nota exata
•  Acrescentamos o novo cálculo à função
test_grade:
18/12/14 Programação Imperativa 55
...
Nota de exame necessária
•  Problema: para passar com y como nota final,
quanto precisa conseguir no exame um aluno
cuja n...
Nota necessária exata
•  Calculemos primeiro a nota necessária com a
precisão possível, sem considerar a questão do
8.5.
•...
Arredondamento para cima às décimas
•  Para arredondar para cima, às unidades, temos
a função ceil.
•  Como fazer para arr...
Nota necessária com uma casa decimal
•  Arredonda-se a nota exata, às décimas, para
cima:
18/12/14 Programação Imperativa ...
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.
•  Co...
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...
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.5714...
Programação Imperativa
Lição n.º 4
Escolhas
Escolhas
•  Instrução if-else.
•  Constantes simbólicas.
•  Declaração de variáveis.
•  If-else em cascata.
18/12/14 Progr...
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 ...
Preço exato, preço certo
•  A expressão que dá o preço nos primeiros 30
segundos é constante: corresponde a metade
do preç...
Constantes simbólicas
•  O preço por minuto é uma constante
arbitrária, atualmente 0.234.
•  Normalmente, evitamos usar “n...
Preço exato, com expressão condicional
•  Em casos simples como este, usamos uma
expressão condicional:
•  Mas, quando que...
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...
Preço certo, arredondado às milésimas
•  A técnica de arredondamento já é conhecida:
18/12/14 Programação Imperativa 70
do...
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...
Declaração de variáveis
•  A declaração da variável indica explicitamente o tipo
de valores que a variável pode representa...
Estacionamento no aeroporto de Faro
•  Nos parques P1 e P2 do aeroporto de Faro, o
preço do estacionamento é dado pela seg...
Análise
•  Na verdade, há dois casos apenas: o primeiro
dia e os restantes dias.
•  No primeiro dia paga-se por 60 cêntimo...
Função parking
•  Atenção às contas:
18/12/14 Programação Imperativa 75
double parking(double minutes)
{
double result;
do...
Reanálise
•  Podemos admitir que as regras para calcular o
preço do estacionamento não mudam, durante
algum tempo, mas que...
Nova estratégia
•  Programemos “em função” dos valores da
tabela, os quais serão representados por
constantes simbólicas:
...
Função parking, melhor e mais complicada
•  Veja com atenção:
18/12/14 Programação Imperativa 78
double parking(double x)
...
Programação Imperativa
Lição n.º 5
Ciclos
Ciclos
•  Ciclos for.
•  Representação dos números double em
memória.
•  Afetação.
•  Operadores de afetação.
•  Formataçã...
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...
•  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 ...
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...
Formatos para double no printf
•  O especificador “%f” indica que o número
aparecerá com parte inteira e parte decimal.
Po...
Testando a soma das potências
•  Calculamos e mostramos o resultado usando
os três formatos:
18/12/14 Programação Imperati...
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 ...
Experimentando com precisão excessiva
•  Observe, com precisão 20:
18/12/14 Programação Imperativa 87
$ ./a.out
2 15
32767...
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...
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...
double sum_geometric_t(double x, int n)
{
double result = 0.0;
double term = 1.0;
for (int i = 0; i < n; i++)
{
result += ...
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 ...
Operadores de afetação
•  O significado dos operadores de afetação +=,
−=, *=, /= e %= é “intuitivo”:
18/12/14 Programação...
Variante overkill
•  Calcular cada termo da sucessão das
potências, usando pow(x, i) seria overkill:
18/12/14 Programação ...
void test_sum_geometric_all(void)
{
double x;
int n;
while (scanf("%lf%d", &x, &n) != EOF)
{
double z1 = sum_geometric(x, ...
Soma de quadrados
•  Baseemo-nos na variante overkill da soma
geométrica para programar a soma de
quadrados:
18/12/14 Prog...
Soma de quadrados, variante elementar
•  É claro que (n+1)2 – n2 = 2*n+1.
•  Logo, podemos calcular (n+1)2 a partir de n2,...
Soma de quadrados, mais elementar ainda
•  Usando a mesma técnica, sabemos que
(2(n+1) + 1) – (2*n+1) = 2.
•  Logo, a sequ...
Soma de quadrados, polinomial
•  Eis a função que implementa o cálculo direto
da soma dos quadrados dos n primeiros
número...
Programação Imperativa
Lição n.º 6
Ciclos for e ciclos while
Ciclos for e ciclos while
•  Utilização dos ciclos for.
•  Utilização dos ciclos while.
•  Exemplos: soma de sequências, f...
Utilização dos ciclos for
•  Usamos um ciclo for para repetir a execução
de uma instrução nas situações em sabemos à
parti...
Exemplo: progressão aritmética
18/12/14 Programação Imperativa 102
•  Eis uma função para calcular iterativamente a
soma d...
Exemplo: fatorial
18/12/14 Programação Imperativa 103
•  O fatorial de um número inteiro n é o
produto de todos os números...
Testando o fatorial
18/12/14 Programação Imperativa 104
•  Eis uma função de teste:
void test_factorial(void)
{
int x;
whi...
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 tend...
Ciclos while
•  O ciclo while é usado para repetir uma
instrução um número indeterminado de vezes.
•  As repetições termin...
Contanto termos da soma aproximada
•  Repetimos a instrução r = 1 + r/2, enquanto o valor de
r, que representa a soma parc...
Testando a contagem de termos
•  Contamos os termos e depois verificamos que
a contagem confere, usando a função
sum_geome...
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))), ..., eventual...
Preliminares
•  Primeiro, a função f:
•  Repare na expressão x % 2 == 1.
•  Significa “ser o resto da divisão de x por 2
i...
Observando a sequência
•  Para perceber melhor de que se trata, programemos
uma função para mostrar a sequência que começa...
Comprimento do ciclo
•  É parecida com show_cycle, substituindo o
printf pelo incremento do contador:
18/12/14 Programação...
Refinando a função de teste
•  Acrescentamos à função de teste uma
chamada da nova função:
18/12/14 Programação Imperativa...
Comprimento de um número
18/12/14 Programação Imperativa 114
•  O comprimento de um número inteiro é o
número de algarismo...
Variante iterativa
18/12/14 Programação Imperativa 115
•  Dividir por 10, sucessivamente, enquanto for
maior que 9, e cont...
Testando o comprimento do número
18/12/14 Programação Imperativa 116
void test_decimal_length(void)
{
int x;
while (scanf(...
Programação Imperativa
Lição n.º 7
Arrays
Arrays
•  Generalidades sobre arrays.
•  Funções sobre arrays.
18/12/14 Programação Imperativa 118
Arrays para quê?
•  Até agora, todos os programa que vimos
calculam a partir de uns poucos números,
desses cálculos result...
Arrays
•  Os arrays são sequências de objetos, todos do
mesmo tipo, acessíveis para inspeção e para
modificação por meio d...
Cultura geral: a palavra “array”
•  Em inglês não técnico, o substantivo array é usado
para significar um conjunto conside...
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 u...
Capacidade de um array
•  A capacidade de um array determina o
número de objetos que o array pode conter,
em cada momento....
Capacidade e tamanho
•  Muitas vezes, os arrays são dimensionados por
excesso, para poder acondicionar todos os
conjuntos ...
Propriedade fundamental dos arrays
Num array, o custo de
aceder ao primeiro
elemento é igual a custo
de aceder ao último o...
Limites dos índices
•  Tentar aceder a um array fora dos limites dos
índices, isto é, usando um índice menor que
zero ou m...
Problema: array dos dígitos.
18/12/14 Programação Imperativa 127
•  Dado um número inteiro não negativo x,
queremos progra...
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 arr...
Aquecimento: contando os dígitos
18/12/14 Programação Imperativa 129
•  Contar os dígitos de números naturais é mais
difíc...
Função count_digits_positive
18/12/14 Programação Imperativa 130
•  É uma variante mais simples da função
decimal_length_i...
Função count_digits
18/12/14 Programação Imperativa 131
•  Consideramos o caso particular de o
argumento ser zero, em que ...
Função digits_positive
18/12/14 Programação Imperativa 132
•  Evitemos as chatices do zero, baseando-nos na
função count_d...
Função digits
18/12/14 Programação Imperativa 133
•  Esta constitui o caso geral:
int digits(int x, int *a)
{
int result =...
Outro exemplo: inverter o array
18/12/14 Programação Imperativa 134
•  Queremos construir um array com os
elementos de out...
Programação Imperativa
Lição n.º 8
Arrays e memória
Arrays e memória
•  Lendo e escrevendo arrays.
•  Observando os arrays na memória.
•  Buffer overflow.
18/12/14 Programaçã...
Escrevendo arrays
•  Ocasionalmente queremos observar na
consola o conteúdo dos nossos arrays.
•  Eis uma função simples q...
Testando a função digits
•  Usemos a função ints_println_basic para
testar na consola a função digits:
18/12/14 Programaçã...
Declaração de arrays
•  Repare bem: ao declarar um array, indicamos a
sua capacidade:
•  A capacidade determina a quantida...
Preciosismo na função ints_println_basic
•  Aquele espaço no início da linha, antes do
primeiro valor, é deveras irritante...
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
lin...
Testando de novo
•  Fazemos como antes, mas em cada caso
invertemos o array, com a função mirror, para
experimentar:
18/12...
Lendo arrays
•  Por hipótese, queremos ler da consola uma
sequência de números, até ao fim dos dados.
•  Cada número lido ...
Testando a leitura de arrays
•  Lemos um array, invertemo-lo para outro, com a
função mirror, e mostramos o array lido e o...
Buffer overflow
•  Se lermos números demais, ultrapassando a
capacidade do array, causamos buffer overflow.
•  A memória f...
Observando a memória do programa
•  Usando oVisual Studio em Windows ou o Xcode em
MacOS, podemos parar o programa onde qu...
Preenchimento da memória
•  Logo a seguir à leitura, os valores de n e as
posições lidas do array a ficam preenchidas:
18/...
Exemplo com dois arrays
•  Consideremos a seguinte função de teste, com dois
arrays:
18/12/14 Programação Imperativa 148
v...
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 ...
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 Program...
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 C é assim mesmo
•  Em linguagens mais modernas, ocorre um erro de
execução “index out of bounds” quando tentamos
aceder ...
Programação Imperativa
Lição n.º 9
Estatísticas
Estatísticas
•  Soma, média, máximo, mínimo de arrays.
•  Redirigindo o input.
•  Argumento do máximo, do mínimo.
•  Teste...
Ler e escrever arrays de doubles
•  As funções para ler e escrever arrays de double são
parecidas com as usadas com arrays...
Testando a leitura e a escrita
•  Eis uma função de teste:
18/12/14 Programação Imperativa 156
void test_doubles_get()
{
d...
Contagem
•  Problema: quantos elementos do array têm um
valor dado?
18/12/14 Programação Imperativa 157
int doubles_count(...
Testando a contagem
•  Aceitamos o valor de referência, depois o
array até ao fim dos dados, operamos e
mostramos o result...
Problema prático
•  Quantos dias choveu em Faro este ano?
•  Temos o registo da precipitação em Faro, em
cada dia, desde 1...
Correndo na consola
•  Podemos introduzir os dados com copy-paste,
a partir do ficheiro:
18/12/14 Programação Imperativa 1...
Redirigindo o input
•  É mais prático redirigir o input, instruindo na
linha de comando o programa para ir buscar
os dados...
Diretorias de dados
•  Guardaremos os dados de cada problema numa
diretoria própria, dentro da diretoria work, a qual
está...
Soma do array
•  A soma é um indicador estatístico importante:
18/12/14 Programação Imperativa 163
double doubles_sum(cons...
Testes unitários
•  Em vez de correr os testes interativamente, na janela
de comando, por vezes é mais prático e mais segu...
Teste unitário da função doubles_count
•  Cada função importante virá acompanhada do seu
teste unitário.
•  Eis o teste un...
Correndo os testes unitários
•  Reunimos todos os testes unitário numa
função unit_tests, que será chamada na função
main,...
Média
•  A partir da soma, calcula-se a média:
18/12/14 Programação Imperativa 167
double doubles_mean(const double *a, in...
Máximo de um array
•  Calcular o valor do maior elemento presente no
array é um problema clássico.
•  Se o array não for v...
Máximo de um array, caso geral
•  Convencionamos que o máximo de um array
vazio é menos infinito.
•  Observe:
18/12/14 Pro...
Testes unitários da função doubles_max
18/12/14 Programação Imperativa 170
void unit_test_doubles_max(void)
{
double a1[16...
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 Imperat...
Habilidades com o C
•  As duas instruções dentro do if podem juntar-
se numa só:
18/12/14 Programação Imperativa 172
int d...
Mínimo, argumento do mínimo
•  São parecidas com as anteriores:
18/12/14 Programação Imperativa 173
double doubles_min(con...
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...
Programação Imperativa
Lição n.º 10
Buscas
Buscas
•  Buscas lineares em arrays.
•  Operadores lógicos.
•  Igualdade de arrays.
•  Reabrindo a consola.
18/12/14 Progr...
Problema da busca
•  Existe no array um elemento com um dado
valor?
•  A resposta é sim ou não, representados em C
por 1 e...
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 P...
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)
...
Aplicação: validação de números de aluno
•  Queremos um programa para validar
interativamente números de aluno.
•  O probl...
Questão prévia
•  Se o input é redirigido para o ficheiro, como
podemos depois usar a janela de comando
para interagir com...
Tarefa de validação
•  Observe:
18/12/14 Programação Imperativa 182
void task_validate_student(void)
{
int a[500];
int n =...
Correndo na consola
•  O comando que invoca o programa realiza a
redireção do input:
18/12/14 Programação Imperativa 183
$...
Busca do fim para o princípio
•  Por vezes, queremos a última ocorrência.
•  Nesse caso, procuramos do fim para o
princípi...
Operadores lógicos
18/12/14 Programação Imperativa 185
&& conjunção
|| disjunção
! negação
Variante: obter todas as ocorrências
•  Se queremos não a primeira ocorrência mas
sim todas as ocorrências, precisamos de ...
Array das primeiras ocorrências
•  Em geral, cada valor pode ocorrer várias vezes.
•  Queremos agora calcular o array das ...
Testando ints_nub
•  Eis uma função de teste, como habitualmente:
18/12/14 Programação Imperativa 188
void test_ints_nub(v...
Aplicação: quantos alunos na aula prática?
•  Queremos saber quantos alunos vieram à aula,
com base no registo das submiss...
Igualdade de arrays
•  Para verificar se dois arrays a e b são iguais, isto é, se
têm os mesmos elementos, pela mesma orde...
Testando a igualdade de arrays
•  Se lermos os arrays com ints_get, temos de
reabrir a consola:
18/12/14 Programação Imper...
Testes unitários com igualdade de arrays
•  Observe, por exemplo:
18/12/14 Programação Imperativa 192
void unit_test_ints_...
Programação Imperativa
Lição n.º 11
Subarrays
Subarrays
•  Subarrays em C.
•  Grupos.
•  Remoção de duplicados.
18/12/14 Programação Imperativa 194
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 a...
Subarrays gerais
•  Também podemos ter subarrays não iniciais, como
ilustram as seguintes funções de teste unitário:
18/12...
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 elem...
Exemplos: soma recursiva, máximo recursivo
•  Usando subarrays, podemos processar arrays
recursivamente.
•  Observe, com a...
Aplicação: problema da via do infante
•  Dispomos de um ficheiro com o número de
carros que passaram no pórtico de Loulé,
...
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 P...
Somar grupos de comprimento fixo
•  Com um pouco mais de esforço,
programamos uma função mais geral, que
soma grupos de co...
Função de teste
•  Observe:
18/12/14 Programação Imperativa 202
void test_infant(void)
{
int a[1440];
int n = ints_get(a);...
Problema dos grupos
•  Dado um array de double, construir um outro array
(de int) com os comprimentos dos grupos de
elemen...
Contar enquanto...
•  Vamos basear-nos numa função que conta os
elementos à cabeça do array que têm um dado valor.
•  A fu...
Função doubles_count_while
•  Na verdade, é uma variante da função de
busca:
18/12/14 Programação Imperativa 205
int doubl...
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 en...
Problema da remoção de duplicados
•  Dado um array de double, construir um outro array
(de double) com um exemplar de cada...
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 entr...
Versões recursivas
•  As versões recursivas das funções groups e unique
também são muito interessantes:
18/12/14 Programaç...
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 ...
Programação Imperativa
Lição n.º 12
Arrays ordenados
Arrays ordenados
•  Renque.
•  Busca dicotómica.
•  Método da bisseção.
18/12/14 Programação Imperativa 212
Problema do renque
•  O renque de um valor num array é o número
de elementos do array cujo valor é menor que
esse valor:
1...
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...
Arrays ordenados
•  Um array está ordenado se o valor de cada elemento
é menor ou igual ao valor do elemento seguinte.
•  ...
Função is_sorted
•  Procura-se o primeiro par de elementos
consecutivos fora de ordem:
•  Também a versão recursiva:
18/12...
Renque em arrays ordenados
•  O renque geral inspeciona todos os elementos
do array e conta aqueles que são menores que o
...
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...
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Pi1415 tudo
Próximos SlideShares
Carregando em…5
×

Pi1415 tudo

744 visualizações

Publicada em

Coleção completa dos slides de Programação Imperativa, edição de 2014-2015, Universidade do Algarve

Publicada em: Educação
0 comentários
0 gostaram
Estatísticas
Notas
  • Seja o primeiro a comentar

  • Seja a primeira pessoa a gostar disto

Sem downloads
Visualizações
Visualizações totais
744
No SlideShare
0
A partir de incorporações
0
Número de incorporações
36
Ações
Compartilhamentos
0
Downloads
10
Comentários
0
Gostaram
0
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

Pi1415 tudo

  1. 1. Programação Imperativa Lição n.º 1 Preliminares
  2. 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. 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. 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. 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. 6. O que é um computador? 18/12/14 Programação Imperativa 6 ENIAC (1946) UNIVAC I (1951)
  7. 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. 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. 9. O que é um computador? (4) 18/12/14 Programação Imperativa 9 Computador “torre” Computador “laptop”
  10. 10. O que é um computador? (5) 18/12/14 Programação Imperativa 10 Computador “torre” Computador “laptop” Surface Pro 3 e Macbook Air
  11. 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. 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. 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. 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. 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. 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
  17. 17. Principais linguagens 18/12/14 Programação Imperativa 17
  18. 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. 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
  20. 20. Bibliografia 18/12/14 Programação Imperativa 20
  21. 21. Programação Imperativa Lição n.º 2 Programação com C
  22. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  41. 41. Programação Imperativa Lição n.º 3 Operações aritméticas
  42. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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); } }
  63. 63. Programação Imperativa Lição n.º 4 Escolhas
  64. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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.
  79. 79. Programação Imperativa Lição n.º 5 Ciclos
  80. 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. 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
  82. 82. •  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.
  83. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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.
  99. 99. Programação Imperativa Lição n.º 6 Ciclos for e ciclos while
  100. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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:
  117. 117. Programação Imperativa Lição n.º 7 Arrays
  118. 118. Arrays •  Generalidades sobre arrays. •  Funções sobre arrays. 18/12/14 Programação Imperativa 118
  119. 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. 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.
  121. 121. 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...
  122. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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.
  135. 135. Programação Imperativa Lição n.º 8 Arrays e memória
  136. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  153. 153. Programação Imperativa Lição n.º 9 Estatísticas
  154. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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; }
  170. 170. 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!
  171. 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. 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. 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. 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; }
  175. 175. Programação Imperativa Lição n.º 10 Buscas
  176. 176. Buscas •  Buscas lineares em arrays. •  Operadores lógicos. •  Igualdade de arrays. •  Reabrindo a consola. 18/12/14 Programação Imperativa 176
  177. 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. 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. 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. 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. 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. 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. 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. 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.
  185. 185. Operadores lógicos 18/12/14 Programação Imperativa 185 && conjunção || disjunção ! negação
  186. 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. 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. 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. 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. 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. 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. 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); }
  193. 193. Programação Imperativa Lição n.º 11 Subarrays
  194. 194. Subarrays •  Subarrays em C. •  Grupos. •  Remoção de duplicados. 18/12/14 Programação Imperativa 194
  195. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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...
  211. 211. Programação Imperativa Lição n.º 12 Arrays ordenados
  212. 212. Arrays ordenados •  Renque. •  Busca dicotómica. •  Método da bisseção. 18/12/14 Programação Imperativa 212
  213. 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. 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. 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. 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. 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. 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

×