a verdadeira programação um - moodle @ fctunl · escrevemos isto a seguir à classe, no ficheiro...

274
20-02-2005 Programação I © Pedro Guerreiro 2003 1 A Verdadeira Programação Um http://ctp.di.fct.unl.pt/lei/p1/ Setembro-Dezembro de 2003 por Pedro Guerreiro [email protected] , http://ctp.di.fct.unl.pt/~pg Departamento de Informática Faculdade de Ciências e Tecnologia Universidade Nova de Lisboa 2829-516 Caparica, Portugal

Upload: others

Post on 20-Jul-2020

1 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 1

A Verdadeira Programação Um

http://ctp.di.fct.unl.pt/lei/p1/

Setembro-Dezembro de 2003

porPedro Guerreiro

[email protected], http://ctp.di.fct.unl.pt/~pgDepartamento de Informática

Faculdade de Ciências e TecnologiaUniversidade Nova de Lisboa2829-516 Caparica, Portugal

Page 2: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 2

Programação• Programar é escrever programas para computador.• Computador = Máquina programável, formada por uma ou

várias unidades de processamento, controlada por programas registados internamente, capaz de executar cálculos complexos, que incluem muitas operações aritméticas e muitas operações lógicas, sem intervenção humana.

• Os programas controlam, ou conduzem, os cálculos que os computadores vão executando.

• Os programas são descrições textuais, feitas usando uma linguagem de programação.

• Cada linguagem de programação é um conjunto de regras sintácticas e semânticas, com um documento de referência.

Page 3: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 3

Programando• Para escrever um programa, usamos um editor de texto

num computador, claro.• Antes de ser executado, o nosso programa tem de ser

compilado, com um compilador da linguagem que usámos.• O compilador detectará eventuais erros de escrita (violação

das regras), mas não erros de lógica.• Os erros de escrita não são graves: o compilador assinala-

os claramente.• Os erros de lógica podem ser muito graves, pois os

resultados podem vir errados sem ninguém dar por isso.• Os erros de lógica são causados por desatenção, por

distracção, por incompreensão, por ignorância, por pressa, por descuido, por excesso de confiança.

Page 4: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 4

Exemplo: um programa para calcular a nota em Programação I

Qual é a regra?A nota é a média ponderada da nota do exame, com peso 70%, e da nota da parte prática, com peso 30%, arredondada às unidades. A nota da parte prática é o quociente do total de pontos obtidos pela pontuação total máxima, multiplicado por 20 e arredondado às décimas. Isto aplica-se se a nota do exame for maior ou igual a 8,5. Se não, a nota é a nota do exame arredondada às unidades.Quais são os dados?A nota do exame, um número real entre 0 e 20, e a pontuação da práticas, um número inteiro entre 0 e a pontuação total máxima, um número inteiro.Qual é o resultado?A nota final, um número inteiro entre 0 e 20.

Este ano a pontuação total máxima planeada é 28.

Page 5: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 5

Cálculos de exemplo

Se a nota do exame for 12,7 e o número de pontos for 20, qual é a nota?Calculemos a nota da parte prática:20 / 28 * 20 = 14,285714..., o que arredondado às décimas dá14,3.Calculemos agora a nota final:12,7 * 0,7 + 14.3 * 0,3 = 13,18, o que arredondado às unidades dá 13.Programar isto é escrever um programa para descrever a realização destes cálculos, independentemente dos valores dos dados.

Page 6: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 6

Que linguagem vamos usar?

http://www.research.att.com/~bs/3rd.html http://pwp.netcabo.pt/pg/pcccpp_2.html

Page 7: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 7

Analisando o problemaJá vimos os dados e os resultados.Quais são as operações?A operação principal é o cálculo da nota final. Outras, encontramo-las relendo as regras de cálculo:A nota é a média ponderada da nota do exame, com peso 70%, e da nota da parte prática, com peso 30%, arredondada às unidades. A nota da parte prática é o quociente dos total de pontos obtidos pela pontuação total máxima, multiplicado por 20 e arredondado às décimas. Isto aplica-se se a nota do exame for maior ou igual a 8.5. Se não, a nota é a nota do exame arredondada às unidades.

Como isto parece um bocado complicado, comecemos por resolver uma simplificação do problema.

Page 8: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 8

Simplificando o problema

Ignoremos, temporariamente, os arredondamentos e o caso do exames com menos de 8,5. A regra fica assim:A nota é a média ponderada da nota do exame, com peso 70%, e da nota da parte prática, com peso 30%. A nota da parte prática é o quociente dos total de pontos obtidos pela pontuação total máxima, multiplicado por 20.Não é preciso programar o quociente e a multiplicação, pois essas operações já existem na linguagem. A nota da parte prática calcula-se com uma multiplicação e uma divisão. A média ponderada faz-se com duas multiplicações e adições.O cálculo (simplificado) da nota final será representado por uma função.

Page 9: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 9

Números inteiros e números reaisO nosso problema envolve números inteiros (os pontos, a nota final, na versão original) e números reais (a nota de exame, a nota da parte prática, a nota final na versão simplificada.)Em C++, os números inteiros são representados pelo tipo int, e os números reais pelo tipo double.Os números do tipo int vão de –2147483648 até 2147483647. –2147483648 é –231 e 2147483647 é 231-1.Os números do tipo double positivos vão desde cerca de 10-308

até 10308, e têm precisão de cerca de 15 casas decimais. Hátambém o número zero e os números negativos, simétricos dos positivos.Num programa, se um número aparecer com ponto decimal, édouble, senão é int. Não é preciso mostrar exemplos, pois não?

Page 10: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 10

Operações aritméticas

As expressões aritméticas escrevem-se como habitualmente, com a ressalva de que o operador de multiplicação é o asterisco, que não pode ficar implícito. Exemplos:

++

As quatro operações aritméticas existem em C++, representadas pelos símbolos habituais:

Adição: Subtracção: ––

**Multiplicação: Divisão: //

x + 1x + 1

x * 0.7 + y * 0.3x * 0.7 + y * 0.3

(a – 2) * (b – 3)(a – 2) * (b – 3)

x / 100.0x / 100.0

(x+2) / (x * x)(x+2) / (x * x)

0.5 * a * t * t0.5 * a * t * t

Usam-se os parêntesis tal como na matemática

Page 11: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 11

Tipo das expressões aritméticasSe ambos os operandos de uma operação aritmética forem ou int ou double, o tipo do resultado da expressão é int ou double, respectivamente.Se um deles for int e o outro for double, o resultado édouble.Esta regra é traiçoeira:Qual é o valor de 7/2? É 3. (Tudo números int.)Qual é o valor de 7.0/2? É 3.5.O valor de 7/2.0 e de 7.0/2.0 também é 3.5.

Isto é muito simples, é verdade, mas se nos distrairmos, erramos as contas.

Page 12: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 12

Expressão da notaSe exam representar a nota do exame (de tipo double) e labo número de pontos obtidos na parte prática (de tipo int), a expressão que dá a nota final, na forma simplificada é:

Atenção: se tivéssemos escrito 20 em vez de 20.0, estaria errado, pois teríamos a divisão inteira e perdia-se a parte decimal.

exam * 0.7 + lab * 20.0 / 28 * 0.3exam * 0.7 + lab * 20.0 / 28 * 0.3

Dijkstra: “Em programação, qualquer pequena confusão propaga-se à velocidade da luz.”

Page 13: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 13

Função da notaUm programa C++ é composto de funções. O cálculo da nota aparece numa função. Observe:

Os programas C++ que escreveremos neste curso terão dezenas ou centenas de funções como esta.

No cabeçalho da função indicamos o nome da função, declaramos os argumentos e o tipo do resultado; no corpo (o que está dentro das chavetas) da função, indicamos os cálculos.

double FinalScore0(double exam, int lab){

return exam * 0.7 + lab * 20.0 / 28 * 0.3;}

double FinalScore0(double exam, int lab){

return exam * 0.7 + lab * 20.0 / 28 * 0.3;} Observe bem a sintaxe!

Page 14: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 14

Exercícios (1)Escreva funções para:

1. Calcular a área de uma esfera, dado o raio, representado pelo argumento r, e usando 3.1415926 para o valor de π.

2. Calcular o espaço percorrido por um móvel em movimento uniformemente acelerado, partindo parado, dada a aceleração a e o tempo t.

3. Calcular o juro de um empréstimo à taxa anual de t, ao fim de d dias. Considere que um ano bancário tem 360 dias.

4. A velocidade expressa em metros por segundo dada a velocidade expressa em quilómetros por hora, e vice-versa.

5. O número de segundos desde a meia-noite, dada a hora expressa em horas, minutos e segundos (três argumentos: h, m, s).

Page 15: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 15

ClassesEm C++, arrumamos funções em classes.

class Score {public:double FinalScore0(double exam, int lab){return exam * 0.7 + lab * 20.0 / 28 * 0.3;

}};

Dizemos que a função FinalScore0 é um membro da classe Score.Por hipótese, estamos a escrever esta classe num ficheiro chamado M_Score.cpp

class Score {public:double FinalScore0(double exam, int lab){return exam * 0.7 + lab * 20.0 / 28 * 0.3;

}};

Aquela indicação public serve apenas para significar que a função pode ser chamada a partir de fora da classe.

Observe bem a sintaxe!

Page 16: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 16

VariáveisO número máximo de pontos, 28, sendo fixo, pode mudar para o ano, por exemplo. Era melhor ser representado por uma variável. Idem para o peso do exame e para o peso da parte prática. Faz-se assim, na classe: class Score {public:

double examWeight;double labWeight;int maxPoints;

Score(){examWeight = 0.70;labWeight = 0.30;maxPoints = 28;

}

// ...

double FinalScore1(double exam, int lab){

return exam * examWeight + lab * 20.0 / maxPoints * labWeight;}

};

class Score {public:

double examWeight;double labWeight;int maxPoints;

Score(){examWeight = 0.70;labWeight = 0.30;maxPoints = 28;

}

// ...

double FinalScore1(double exam, int lab){

return exam * examWeight + lab * 20.0 / maxPoints * labWeight;}

};

Por um lado declaramos, por outro inicializamos.

Tecnicamente, estas três variáveis são membros de dados da classe.

Agora a classe já tem duas funções. A função especial onde se faz a inicialização dos membros de dados, chamada construtor da classe tem o mesmo nome que a classe.

Esta é uma nova função. A anterior, FinalScore0, não éapagada. Fica para documentação.

Page 17: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 17

A função mainUm programa C++ é um conjunto de funções que se chamam umas às outras para efectuar os cálculos pretendidos. Mas tem de haver uma função para desencadear as operações. É a função main.Eis uma função main para calcular a nota de um alunos com 12,5 no exame e 19 pontos na parte prática:

OK, mas para isto ter alguma utilidade, temos de poder calcular para quaisquer valores dados e, depois, temos de conseguir ver o resultado.

int main(){Score s;s.FinalScore0(12.5, 19);

}

int main(){Score s;s.FinalScore0(12.5, 19);

}

Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp.

Observe a técnica: declaramos s como sendo um objecto de tipo Score. Isso provoca a inicialização dos membros de dados. A seguir, chamamos a função FinalScore0 desse objecto s, isto é, usando os valores de examWeight, labWeight e maxPoint que foram definidos.

Page 18: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 18

Lendo e escrevendoObserve:

Eis o resultado na consola após uma corrida do programa.

int main(){Score s;double x; // examint b; // labstd::cout << "Nota do exame e pontos nos trabalhos, sff: ";std::cin >> x >> b;std::cout << x << " " << b << std::endl;double fs; // final gradefs = s.FinalScore1(x, b);std::cout << "Nota final simplificada: " << fs << std::endl;

}

int main(){Score s;double x; // examint b; // labstd::cout << "Nota do exame e pontos nos trabalhos, sff: ";std::cin >> x >> b;std::cout << x << " " << b << std::endl;double fs; // final gradefs = s.FinalScore1(x, b);std::cout << "Nota final simplificada: " << fs << std::endl;

}

Esta notação // indica um comentário. A partir do // até ao fim da linha, o texto apenas tem significado para as pessoas que lêem o programa.

Repare: duas variáveis para os dados...

... e uma para o resultado.

Note bem: para segurança, escrevemos logo os valores que acabámos de ler.

Page 19: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 19

O programa completo#include <iostream>

class Score {public:double examWeight;double labWeight;int maxPoints;

Score(){examWeight = 0.70;labWeight = 0.30;maxPoints = 28;

}

double FinalScore1(double exam, int lab){return exam * examWeight + lab * 20.0 / maxPoints * labWeight;

}

};

int main(){Score s;double x; // examint b; // labstd::cout << "Nota do exame e pontos nos trabalhos, sff: ";std::cin >> x >> b;std::cout << x << " " << b << std::endl;double fs; // final gradefs = s.FinalScore0(x, b);std::cout << "Nota final simplificada: " << fs << std::endl;

}

#include <iostream>

class Score {public:double examWeight;double labWeight;int maxPoints;

Score(){examWeight = 0.70;labWeight = 0.30;maxPoints = 28;

}

double FinalScore1(double exam, int lab){return exam * examWeight + lab * 20.0 / maxPoints * labWeight;

}

};

int main(){Score s;double x; // examint b; // labstd::cout << "Nota do exame e pontos nos trabalhos, sff: ";std::cin >> x >> b;std::cout << x << " " << b << std::endl;double fs; // final gradefs = s.FinalScore0(x, b);std::cout << "Nota final simplificada: " << fs << std::endl;

}

Temos de “incluir” o iostream, assim. Sem isto, não poderíamos ler e escrever na consola usando std::cin, std::cout e os respectivos operadores >> e <<.

Note bem: isto é um ficheiro de texto normalíssimo, que poderia ter sido criado com qualquer editor de texto.

Recorde que este programa resolve uma simplificação do problema. Serve como ponto de partida, mas agora temos de começar a remover as simplificações.

Page 20: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 20

Usando o Visual C++Para correr o programa no ambiente Visual C++, criamos um projecto de tipo “Win32 Project”, ajustamos os settings (para ficarmos com uma aplicação de consola “vazia”), juntamos o ficheiro ao projecto, compilamos, construímos (usando menus ou shortcuts) e pronto: não havendo erros, já podemos correr de dentro do ambiente.Convém ajustar as propriedades do projecto, nomeadamente subir o nível dos avisos para 4 (warning level), não autorizar extensões àlinguagem e forçar a conformidade dos ciclos for.

Page 21: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 21

Usando o gppSe não tivermos o Visual C++ (ou mesmo tendo...) podemos usar um editor qualquer para escrever o programa e depois, numa janela de comando mandamos compilar com o gpp, usando de preferência as opções –Wall, que fazem o compilador ser mais rigoroso. Se houver erros voltamos ao editor e repetimos. Se não houver, podemos correr o programa executável (que terá sido criado) a partir da linha comando.

Page 22: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 22

Removendo as simplificações1. Na fórmula da nota não podemos usar a subexpressão

lab * 20.0 / maxPoints * labWeight directamente, pois esse valor não estará arredondado às décimas.

2. A nota final é arredondada às unidades.

Logo, temos de aprender a arredondar.Como o C++ não dispõe de funções de arredondamento, temos de ser nós a programá-las. Arrumamo-las na classe Score:

class Score {public:

// ...

int Round(double x){

...}

double RoundToDecimal(double x){

...}

};

class Score {public:

// ...

int Round(double x){

...}

double RoundToDecimal(double x){

...}

};

Page 23: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 23

ArredondandoAo fazer uma conversão estática de double para int, o C++ trunca, isto é, elimina a parte decimal. Logo:class Score {public:

// ...

int Round(double x){

return static_cast<int>(x + 0.5);}

double RoundToDecimal(double x){

return Round(x * 10) / 10.0;}

};

class Score {public:

// ...

int Round(double x){

return static_cast<int>(x + 0.5);}

double RoundToDecimal(double x){

return Round(x * 10) / 10.0;}

};

Sim, a função RoundToDecimal éprogramada em termos da função Round. Fica mais simples assim.

Tecnicamente o static_cast<int> éum operador, que tem como resultado o maior número inteiro menor ou igual ao argumento (que é de tipo double) se o argumento for positivo e o menor número inteiro maior ou igual ao argumento, se o argumento for negativo.

Também há o static_cast<double>que serve para converter de int para double.

Page 24: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 24

A nota arredondadaComo a nota da parte prática tem de ser calculada, o melhor écalculá-la explicitamente numa função: class Score {public:

// ...

double LabScore(int lab){

return RoundToDecimal(lab * 20.0 / maxPoints);}

int FinalScore2(double exam, int lab){

return Round (exam * examWeight + LabScore(lab) * labWeight);}

};

class Score {public:

// ...

double LabScore(int lab){

return RoundToDecimal(lab * 20.0 / maxPoints);}

int FinalScore2(double exam, int lab){

return Round (exam * examWeight + LabScore(lab) * labWeight);}

};Esta função FinalScore2 é percebe-se melhor do que as versões anteriores, por causa da introdução da função LabScore, não é?

Page 25: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 25

FinalizandoSó falta distinguir os casos de exames abaixo de 8.5. Usamos uma expressão condicional. Observe:class Score {public:

// ...

int FinalScore(double exam, int lab){

return exam < 8.5 ? Round(exam) : FinalScore2(exam, lab);}

};

class Score {public:

// ...

int FinalScore(double exam, int lab){

return exam < 8.5 ? Round(exam) : FinalScore2(exam, lab);}

};

A expressão condicional é uma expressão ternária, da forma x?y:z. A primeira expressão, x, é uma expressão lógica: se valer true, o valor da expressão condicional é o valor de y, se não é o valor de z.

Page 26: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 26

ExperimentandoA função main agora está assim:int main(){

Score s;double x; // examint b; // labstd::cout << "Nota do exame e pontos nos trabalhos, sff: ";std::cin >> x >> b;std::cout << x << " " << b << std::endl;int f; // final gradef = s.FinalScore(x, b);std::cout << "Nota final: " << f << std::endl;

}

int main(){

Score s;double x; // examint b; // labstd::cout << "Nota do exame e pontos nos trabalhos, sff: ";std::cin >> x >> b;std::cout << x << " " << b << std::endl;int f; // final gradef = s.FinalScore(x, b);std::cout << "Nota final: " << f << std::endl;

}

Page 27: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 27

ComplicandoLogo que um programa fica pronto, sempre aparece alguém a pedir mais qualquer coisa. Neste caso, queremos subir os noves para dez. É a nota bonificada.

class Score {// ...

int NineToTen(int x){

return x == 9 ? 10 : x;}

};

class Score {// ...

int NineToTen(int x){

return x == 9 ? 10 : x;}

};

Começamos com uma função inteira que “transforma” 9 em 10:

Depois acrescentamos a nova função para a nota bonificada: class Score {

// ...

int FinalScoreWithBonus(double exam, int lab){

return NineToTen(FinalScore(exam, lab));}

};

class Score {// ...

int FinalScoreWithBonus(double exam, int lab){

return NineToTen(FinalScore(exam, lab));}

};

Quantas funções já tem a nossa classe?Cada uma delas é muito simples. Devemos ser capazes de as entender uma a uma. É desnecessário imaginar tudo a funcionar ao mesmo tempo.

Sim, em C++ a igualdade é representada pelo operador ==, já que o operador = representa a afectação de um valor a uma variável

Page 28: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 28

Experimentando, de novoEis uma função main para experimentar as duas funções:int main(){

Score s;double x; // examint b; // labstd::cout << "Nota do exame e pontos nos trabalhos, sff: ";std::cin >> x >> b;std::cout << x << " " << b << std::endl;int f; // final gradef = s.FinalScore(x, b);std::cout << "Nota final: " << f << std::endl;f = s.FinalScoreWithBonus(x, b);std::cout << "Nota final com bonus: " << f << std::endl;

}

int main(){

Score s;double x; // examint b; // labstd::cout << "Nota do exame e pontos nos trabalhos, sff: ";std::cin >> x >> b;std::cout << x << " " << b << std::endl;int f; // final gradef = s.FinalScore(x, b);std::cout << "Nota final: " << f << std::endl;f = s.FinalScoreWithBonus(x, b);std::cout << "Nota final com bonus: " << f << std::endl;

}

Page 29: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 29

ModularizaçãoFrequentemente precisamos de programar várias classes para resolver um problema. Nesse caso, é melhor colocar cada classe num ficheiro diferente, e a função main noutro ainda. Façamos isso no nosso programa.

class Score {public:

double examWeight;double labWeight;int maxPoints;

//...

};

class Score {public:

double examWeight;double labWeight;int maxPoints;

//...

};

#include <iostream>#include "Score.h"

int main(){

Score s;double x; // examint b; // lab// ...

}

#include <iostream>#include "Score.h"

int main(){

Score s;double x; // examint b; // lab// ...

}

Um ficheiro tem só a classe:

Outro tem só a função main e as directivas #include:

Regra prática: o ficheiro que contém a declaração da classe X chamar-se-áX.h

Regra prática: um ficheiro que contenha uma função main teráum nome começado por M_ e extensão .cpp.

O texto da classe é substituído por uma directiva #include.

Page 30: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 30

Programação visualEm vez de experimentarmos o programa na janela de comando, era mais interessante ter uma caixa de diálogo, assim:

Dá um pouco mais trabalho, mas é muito mais gratificante.

Page 31: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 31

Projectos com interface visual

visualmente a caixa de diálogo, usando controlos de edição, botões e texto estático.

No Visual C++, criamos um projecto “MFC Application”, de tipo Dialog based, com caixas de minimização e maximização, sem advanced features. Depois, no editor de recursos, desenhamos

Se os vários controlos não saírem bem alinhados à primeira, não se preocupe. Mais tarde, no fim, afinamos isso.

Page 32: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 32

Ficheiros nos programas visuaisPara projectos destes, o Visual C++ cria dois pares de ficheiros. Se o projecto se chamar X, serão os ficheiros, X.cpp, X.h, XDlg.cpp e XDlg.h. Só mexeremos, cuidadosamente e localizadamente, no XDlg.h. Neste, existe a classe CXDlg.O nosso projecto chama-se ScoreVisual. Devemos acrescentar-lhe o ficheiro da classe Score e ainda o ficheiro fornecido UtilitiesVisual.h. (Não usamos o ficheiro da função main.)

Page 33: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 33

Manipulando os controlosNo programa, os controlos de edição são representados por variáveis na classe CScoreVisualDlg associadas a esses controlos por meio de comandos do Visual C++.

class CScoreVisualDlg : public CDialog{// ...public:

CEdit m_final;CEdit m_exam;CEdit m_lab;

};

class CScoreVisualDlg : public CDialog{// ...public:

CEdit m_final;CEdit m_exam;CEdit m_lab;

};

Aqui usaremos m_exam, m_lab e m_final. Observe o final da declaração da classe, no ficheiro ScoreVisualDlg.h.Biclicando no botão, no editor de recursos, abre--se o ficheiro ScoreVisualDlg.cpp numa nova função OnBnClickedButton1.

Page 34: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 34

OnBnClickedButton1Na função OnBnClickedButton1 programamos as operações a realizar quando o utilizador carrega no botão. São as seguintes: obter a nota do exame da caixa de cima à esquerda; obter a número de ponto na caixa de cima à direita; calcular a nota; colocar o resultado na caixa de baixo. Observe:

void CScoreVisualDlg::OnBnClickedButton1(){Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);int z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}

void CScoreVisualDlg::OnBnClickedButton1(){Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);int z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}

GetDouble, para obter um double, da caixa indicada em argumento.

GetInt, para obter um int., idem.

SetInt, para colocar o int, indicado no segundo argumento na caixa indicada no primeiro.

Page 35: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 35

Espaços de nomesRecapitulando:void CScoreVisualDlg::OnBnClickedButton1(){

Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);int z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}

void CScoreVisualDlg::OnBnClickedButton1(){

Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);int z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}

Observe o cabeçalho desta função. O tipo de retorno voidsignifica que a função apenas vale pelo que faz, e que, ao contrário das funções da matemática, não produz um valor.

A função FinalScore é uma função da classe Score. Para a chamar, precisamos de um objecto de tipo Score. As funções GetDouble, GetInt e SetInt não são de nenhuma classe. Aquela qualificação mas:: é a indicação do espaço de nomes a que as funções pertencem.O espaço de nomes mas é o das funções “de biblioteca” deste curso. O espaço de nomes std (que já apareceu em std::cin, std::cout e std::endl) é o espaço de nomes da biblioteca do C++.

Page 36: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 36

Ficheiros de inclusãoPara podermos usar a classe Score no ficheiro ScoreVisualDlg.cpp, precisamos incluir Score.h. Para poder usar as funções GetInt, etc., precisamos incluir UtilitiesVisual.h. Observe:

// ScoreVisualDlg.cpp : implementation file//

#include "stdafx.h"

#include "UtilitiesVisual.h"#include "Score.h"

#include "ScoreVisual.h"#include "ScoreVisualDlg.h“

// ...

// ScoreVisualDlg.cpp : implementation file//

#include "stdafx.h"

#include "UtilitiesVisual.h"#include "Score.h"

#include "ScoreVisual.h"#include "ScoreVisualDlg.h“

// ...

Aqui estão eles!

Mas note bem: neste caso, os ficheiros incluídos não estão na mesma directoria dos que os incluem. Ou marcamos o caminho completo nas directiva #include (não foi o que fizemos aqui), ou definimos “additional include directories” no Visual C++

Page 37: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 37

ExperimentandoEis o resultado de algumas experiências com o programa:

Este disparate passa despercebido .

Este não passa, mas o programa estoira .

Page 38: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 38

Apanhando as excepçõesPara evitar que o programa estoire, devemos apanhar a excepção que provoca o estoiro. A excepção é apenas uma cadeia de caracteres com uma descrição do que correu mal e é

void CScoreVisualDlg::OnBnClickedButton1(){

try {Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);int z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}catch (const std::string& e) {

mas::ErrorMessage(e);}

}

void CScoreVisualDlg::OnBnClickedButton1(){

try {Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);int z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}catch (const std::string& e) {

mas::ErrorMessage(e);}

}

lançada pela função GetIntquando não consegue converter a cadeia “xxx” num número inteiro. (Na verdade, não é a função GetInt que lança a excepção, mas sim uma função chamada por ela.) Ao apanhar a excepção, mostramos uma mensagem de erro e pronto.

Repare no bloco try com o tratador catch.

A função GetDouble também pode levantar excepções, se escrevermos uma cadeia que não pode ser interpretada como número real.

Eis o espaço de nomes std.

Page 39: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 39

Mensagens de erroObserve as mensagens de erro:

Também há mensagens de aviso e de informação, com as funções WarningMessage e InformationMessage.

Esta mensagem é o texto da excepção.

Em C++ usa-se o ponto decimal e não a vírgula.

Page 40: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 40

Funções booleanasTambém devemos emitir mensagens de erro se os dados forem inválidos, ainda que bem escritos.Começamos por definir duas novas funções na classe Score, uma para validar a nota do exame (que deve estar entre 0 e 20), outra para validar os pontos (que devem estar entremaxPoints / 3 e maxPoints. São funções booleanas: o seuclass Score {public:// ...

bool ValidExam(double exam){

return 0.0 <= exam && exam <= 20.0;}

bool ValidLab(int lab){

return maxPoints <= 3 * lab && lab <= maxPoints;}

};

class Score {public:// ...

bool ValidExam(double exam){

return 0.0 <= exam && exam <= 20.0;}

bool ValidLab(int lab){

return maxPoints <= 3 * lab && lab <= maxPoints;}

};

De acordo com as regras de frequência, um aluno com menos do que um terço do máximo total de pontos não é admitido a exame. Logo a fórmula da nota final não tem sentido nesse caso.

resultado é um valor lógico, true ou false. Estes dois valores formam o tipo bool.

Repare na maneira indirecta de fazer a comparação: maxPoints <= 3 * lab, para evitar confusões com a divisão.

Page 41: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 41

E, OU, NÃOEm C++, o operador lógico de conjunção, “e”, escreve-se &&.

O operador lógico de disjunção, “ou”, escreve-se ||.O operador lógico de negação, “não”, escreve-se !.O “não” tem mais prioridade, a seguir vem o “e” e o “ou” é o de menor prioridade.Eis alguns exemplos de expressões booleanas, avulso:

0 <= x && x <= 1000 <= x && x <= 100

x != 0 || y != 0x != 0 || y != 0

! (x == 0 && y == 0)! (x == 0 && y == 0)

Page 42: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 42

Validando os dados (2)Só fazemos as contas se os dados forem válidos. Se não forem, mostramos mensagens de erro. Recorremos à instrução if-else:void CScoreVisualDlg::OnBnClickedButton1(){

try {Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);if (!s.ValidExam(x))

mas::ErrorMessage("Nota de exame inválida.");else if (!s.ValidLab(y))

mas::ErrorMessage("Número de pontos inválido.");else{int z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}}catch (const std::string& e) {

mas::ErrorMessage(e);}

}

void CScoreVisualDlg::OnBnClickedButton1(){

try {Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);if (!s.ValidExam(x))

mas::ErrorMessage("Nota de exame inválida.");else if (!s.ValidLab(y))

mas::ErrorMessage("Número de pontos inválido.");else{int z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}}catch (const std::string& e) {

mas::ErrorMessage(e);}

}

Se algum número estiver mal escrito, é o tratador de excepções que trabalha.

Este ramo else só é executado se o exame e os pontos forem válidos.

Temos aqui duas instruções if-else, em cascata:if (...)

...else if (...)

...else

...

Page 43: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 43

A instrução if-else

if (expressão)

instrução1

else

instrução2

Sintaxe:

Semântica:Primeiro avalia-se a expressão. Se der true, executa-se a instrução1; se não (isto é, se a expressão der false) executa-se a instrução2.

Repare que a expressão tem de estar entre parêntesis.

A parte else pode faltar, caso em que a instrução if não tem efeito se a expressão der false.

A instrução if é uma instrução: executa-se.

A expressão condicional é uma expressão: avalia-se.

Page 44: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 44

Com ou sem bónus?Se quisermos que o nosso programa também calcule a nota final com bónus (os noves passam a dez) ou acrescentamos um botão “Calcular Bonificado” (faça isto como exercício) ou usamos uma check box. Estudemos esta técnica. Para começar puxamos uma check box da caixa de ferramentas para a nossa janela, no editor de recursos:

Haverá uma variável associada à check box, m_bonus(análoga a m_final, m_exam e m_lab), através da qual o programa verifica se a caixa está marcada ou não.

Page 45: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 45

OnBnClickedButton1, com bónusEis a versão final da função OnBnClicked-Button1:

void CScoreVisualDlg::OnBnClickedButton1(){

try {Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);if (!s.ValidExam(x))

mas::ErrorMessage("Nota de exame inválida.");else if (!s.ValidLab(y))

mas::ErrorMessage("Número de pontos inválido.");else{int z;if (m_bonus.GetCheck())

z = s.FinalScoreWithBonus(x, y);else

z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}}catch (const std::string& e) {

mas::ErrorMessage(e);}

}

void CScoreVisualDlg::OnBnClickedButton1(){

try {Score s;double x = mas::GetDouble(m_exam);int y = mas::GetInt(m_lab);if (!s.ValidExam(x))

mas::ErrorMessage("Nota de exame inválida.");else if (!s.ValidLab(y))

mas::ErrorMessage("Número de pontos inválido.");else{int z;if (m_bonus.GetCheck())

z = s.FinalScoreWithBonus(x, y);else

z = s.FinalScore(x, y);mas::SetInt(m_final, z);

}}catch (const std::string& e) {

mas::ErrorMessage(e);}

}

Neste caso, a variável z édeclarada mas não inicializada logo.

Quando a caixa estámarcada, GetCheck() dátrue.

Depois do if, o valor de z émostrado no controlo de edição.

Page 46: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 46

Exercícios (2)1. Modifique o programa da nota admitindo que uma nova regra

determina que a nota final não pode subir mais do que dois valores em relação à nota do exame.

2. Idem, admitindo que uma nova regra (independente da anterior) determina que os alunos com mais do que 80% do máximo de pontos têm um bónus de 1 valor, desde que com isso não ultrapassem 20 valores.

3. Idem, juntando as duas regras anteriores.4. Treine-se no Visual C++, reprogramando os seus exercícios

da página 14 mas agora com uma bela interface visual.

Page 47: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 47

Exercícios (3) – Altura da TorreEste exercício é para você aprender a usar as funções trigonométricas em C++. Faz-se #include <cmath> e usa-se ::sin para o seno, ::cos para o coseno, ::tan para a tangente, e ::atan para o arco-tangente. Os ângulos são sempre números reais (radianos).Problema: escrever um programa para calcular a altura de uma torre, dada a distância do ponto de observação à base da torre e o ângulo, expresso em graus e minutos, feito pela horizontal com a linha que vai do ponto de observação ao topo da torre, tal com esquematizado na figura:

αh

Page 48: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 48

Exercícios (4) – Índice de FrioUma coisa é a temperatura do ar ser 5º Celsius e não haver vento, outra é ser 5º e o vento soprar a, por exemplo, 10 km/h. Neste caso, até parece que estão –10º! O índice de frio (windchill temperature index) é uma medida do efeito combinado da temperatura do ar e da velocidade do vento na sensação de “frio” para os seres humanos. Estudos científicos conduziram à seguinte fórmula:

Twc = 13.112 + 0.6215 Ta -11.37 V0.16 + 0.3965 Ta V0.16

onde Twc é o índice de frio, Ta é a temperatura do ar em graus Celsius e V é a velocidade do vento em km/h. (Mais informação em http://www.islandnet.com/~see/weather/life/windchill.htm.)Escreva programas para calcular o índice de frio.(Sim, o C++ tem uma função para a potência, com base real e expoente real, ::pow(x, y) significa x elevado a y. Também há funções para a exponencial, ::exp, e para o logaritmo natural, ::log.)

Em memória das vagas de calor do ano 2003.

Page 49: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 49

VectoresVectores são sequências de objectos do mesmo tipo, acessíveis para consulta e modificação através do seu índice.O índice é um número inteiro. O índice do primeiro elemento do vector é zero, o do segundo elemento é 1, o do terceiro é 2, etc. Se o tamanho do vector for n, o índice do último elemento é n-1.Em C++, declaramos uma variável vectorial usando a classe genérica std::vector<T>:

Um ficheiro .cpp que use vectores (directamente ou via #include) deve fazer #include <vector>.

#include <vector>

int main(){

std::vector<int> squares;// ...

}

#include <vector>

int main(){

std::vector<int> squares;// ...

}

Page 50: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 50

Ilustração com vector de inteirosEis uma função main, para ilustração, que preenche um vector com os quadrados dos 100 primeiros números inteiros e depois os mostra na consola, todos de seguida, cada um precedido por um espaço:#include <iostream>#include <vector>

int main(){

std::vector<int> squares;for (int i = 0; i < 100; i++)

squares.push_back(i*i);for (int i = 0; i < 100; i++)

std::cout << " " << squares[i];std::cout << std::endl;

}

#include <iostream>#include <vector>

int main(){

std::vector<int> squares;for (int i = 0; i < 100; i++)

squares.push_back(i*i);for (int i = 0; i < 100; i++)

std::cout << " " << squares[i];std::cout << std::endl;

}

Observe as instruções for.

Observe o preenchimento do vector: squares.push_back(i*i);.

Observe a indexação: squares[i];.

Observe o efeito obtido na consola.

Page 51: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 51

A instrução for

for (declaração inicializada; expressão2; expressão3)instrução

Sintaxe (com inicialização):

Semântica:1. Elabora a inicialização ou avalia a expressão1.2. Avalia a expressão2; se der false, termina;3. Executa a instrução;4. Avalia a expressão3;5. Volta ao passo 2.

Qualquer destas expressões pode faltar.

As expressão1 e a expressão3 têm efeitos laterais que mudam o estado do programa. A expressão2 normalmente é uma expressão booleana que apenas calcula, sem efeitos laterais.

for (expressão; expressão2; expressão3)instrução

Sintaxe (tradicional):

Se a expressão2 faltar, é como se fosse true. Ficamos com um ciclo “infinito” que terá de ser parado por outros meios.

A instrução for é muito usada com vectores.

Page 52: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 52

O operador ++O operador ++ serve para incrementar de uma unidade o valor de uma variável numérica.Assim, em vez de

x = x + 1;x = x + 1;

preferimos:

x++;x++;ou x += 1;x += 1;

Também há um operador ++ prefixo, que se usa ++x. Também incrementa o valor da variável x, mas o valor da expressão ++x é x+1 e não x. (Veja os exemplos na página seguinte.)

O ++ no nome da linguagem C++ vem deste operador...

O operador ++ é muito usado nos ciclos for.

Page 53: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 53

x++ e ++xAs duas funções de teste ilustram a diferença entre o operador sufixo (que é o que usamos quase sempre) e o operador prefixo:

void Test_plus_plus_suffix(){int x;std::cout << "Um numero: ";std::cin >> x;std::cout << x << std::endl;std::cout << x++ << std::endl;std::cout << x << std::endl;

}

void Test_plus_plus_suffix(){int x;std::cout << "Um numero: ";std::cin >> x;std::cout << x << std::endl;std::cout << x++ << std::endl;std::cout << x << std::endl;

}

void Test_plus_plus_prefix(){int x;std::cout << "Um numero: ";std::cin >> x;std::cout << x << std::endl;std::cout << ++x << std::endl;std::cout << x << std::endl;

}

void Test_plus_plus_prefix(){int x;std::cout << "Um numero: ";std::cin >> x;std::cout << x << std::endl;std::cout << ++x << std::endl;std::cout << x << std::endl;

}

Claro que há também dois operadores -- análogos a estes.

Page 54: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 54

Declarando vectoresEis várias declarações de vectores, como exemplo:

std::vector<int> squares;std::vector<int> squares;

Um vector de números inteiros:

Um vector de números reais:

std::vector<double> roots;

Um vector de valores lógicos:

std::vector<double> roots;

std::vector<bool> answers;std::vector<bool> answers;

std::vector<std::string> names;std::vector<std::string> names;

Um vector de cadeias de caracteres: O tipo std::string é o tipo que representa as cadeias de caracteres em C++.Um ficheiro .cpp que use cadeias de caracteres (directamente ou via #include) deve fazer #include <string>.Exemplo um pouco mais à frente.

Exemplo na página seguinte.

Page 55: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 55

Exemplo com vectores de doubleEste programa tabela as raízes quadradas dos números entre zero e 9999 e depois consulta a tabela interactivamente:int main(){std::vector<double> roots;for (int i = 0; i < 10000; i++)roots.push_back(::sqrt(static_cast<double>(i)));

for (;;){int x;std::cout << "Um numero entre 0 e 9999: ";std::cin >> x;if (!std::cin)break;

std::cout << "Raiz quadrada: " << roots[x] << std::endl;}

}

int main(){std::vector<double> roots;for (int i = 0; i < 10000; i++)roots.push_back(::sqrt(static_cast<double>(i)));

for (;;){int x;std::cout << "Um numero entre 0 e 9999: ";std::cin >> x;if (!std::cin)break;

std::cout << "Raiz quadrada: " << roots[x] << std::endl;}

}

Recorde: para usar a função ::sqrt épreciso fazer #include <cmath>.

OK!

Disparate: usando um índice fora do intervalo dos índices válidos (neste caso o intervalo de zero a 9999) só pode dar asneira.

Page 56: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 56

Fixando o número de casas decimaisAproveitemos para aprender a escrever números reais com um número fixo de casa decimais. Observe a nova instrução antes da instrução de escrita:int main(){std::vector<double> roots;for (int i = 0; i < 10000; i++)roots.push_back(::sqrt(static_cast<double>(i)));

for (;;){int x;std::cout << "Um numero entre 0 e 9999: ";std::cin >> x;if (!std::cin)break;

std::cout << std::fixed << std::setprecision(10);std::cout << "Raiz quadrada: " << roots[x] << std::endl;

}}

int main(){std::vector<double> roots;for (int i = 0; i < 10000; i++)roots.push_back(::sqrt(static_cast<double>(i)));

for (;;){int x;std::cout << "Um numero entre 0 e 9999: ";std::cin >> x;if (!std::cin)break;

std::cout << std::fixed << std::setprecision(10);std::cout << "Raiz quadrada: " << roots[x] << std::endl;

}}

Tecnicamente, aqueles std::fixed e std::setprecision são chamados manipuladores de entrada-saída. Para usar manipuladores com argumentos (caso do std::setprecision) é preciso fazer #include <iomanip>. Não se esqueça!

Formato ponto fixo com 10 casas decimais.

Page 57: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 57

Exemplo com vectores de cadeiasEste programa lê nomes da consola, um por linha até ao fim dos dados e depois escreve na consola primeiro os nomes que têm menos do que 8 caracteres e depois os outros:int main(){

std::vector<std::string> names;for (;;){

std::string line;std::getline(std::cin, line);if (!std::cin)

break;names.push_back(line);

}for (int i = 0; i < static_cast<int>(names.size()); i++)

if (names[i].size() < 8)std::cout << names[i] << std::endl;

for (int i = 0; i < static_cast<int>(names.size()); i++)if (names[i].size() >= 8)

std::cout << names[i] << std::endl;}

int main(){

std::vector<std::string> names;for (;;){

std::string line;std::getline(std::cin, line);if (!std::cin)

break;names.push_back(line);

}for (int i = 0; i < static_cast<int>(names.size()); i++)

if (names[i].size() < 8)std::cout << names[i] << std::endl;

for (int i = 0; i < static_cast<int>(names.size()); i++)if (names[i].size() >= 8)

std::cout << names[i] << std::endl;}

Lemos uma linha inteira para uma cadeia usando a função std::getline.

A função size para vectores dá o tamanho do vector, isto é, o número de elementos presentes no vector.

A função size para cadeias dá o comprimento da cadeia do vector, isto é, o número de caracteres que a compõem.

Page 58: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 58

Pormenor técnico: tipo da função sizeNas instruções for do exemplo da página anterior, escrevemos:int main(){

//...for (int i = 0; i < static_cast<int>(names.size()); i++)

// ...for (int i = 0; i < static_cast<int>(names.size()); i++)

// ...}

int main(){

//...for (int i = 0; i < static_cast<int>(names.size()); i++)

// ...for (int i = 0; i < static_cast<int>(names.size()); i++)

// ...}

Por que razão não comparamos o valor da variável idirectamente com o valor de names.size()?Porque, tecnicamente, o tipo do resultado da função size não éint, mas sim um outro tipo, também de números inteiros mas diferente do tipo int. Sem a conversão, o compilador avisaria de “possible loss of data”, porque este outro tipo é mais vasto do que o tipo int.

Page 59: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 59

Redirigindo o std::cinComo fazer se os nomes estiverem num ficheiro de texto?O melhor seria correr o programa na linha de comando e redirigir o standard input para esse ficheiro. Assim, quando o problema ler do std::cin, estará de facto a ler do ficheiro. Observe, supondo que os nomes vêm no ficheiro nomes1.txt:

Redigirindo o input.

O output continua a ser a consola.

Neste exemplo, o ficheiro tem de estar na mesma directoria que o programa executável.

Page 60: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 60

Redirigindo o std::coutPodemos também redirigir o std::cout, obtendo os resultados num ficheiro, e não escritos na consola. A técnica é semelhante:

Os resultados do programa ficam guardados no ficheiro out.txt.

Page 61: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 61

Ordenando o vector de nomesPara ordenar um vector usa-se a função std::sort. Veja como se faz:int main(){

std::vector<std::string> names;for (;;){

std::string line;std::getline(std::cin, line);if (!std::cin)

break;names.push_back(line);

}std::sort(names.begin(), names.end());for (int i = 0; i < static_cast<int>(names.size()); i++)

std::cout << names[i] << std::endl;}

int main(){

std::vector<std::string> names;for (;;){

std::string line;std::getline(std::cin, line);if (!std::cin)

break;names.push_back(line);

}std::sort(names.begin(), names.end());for (int i = 0; i < static_cast<int>(names.size()); i++)

std::cout << names[i] << std::endl;}

Esta técnica aplica-se a vectores de quaisquer tipos de objectos, desde que esse tipo reconheça o operador de comparação <.

Mas atenção: para usar a função std::sort, é preciso fazer #include <algorithm>.

Page 62: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 62

Números aleatóriosPara fazer experiências com vectores de números, por vezes é prático usar números aleatórios.Este programa preenche um vector com dez números aleatórios e depois calcula a média e o desvio padrão.

int main(){::srand(static_cast<unsigned>(::time(0)));std::vector<int> numbers;for (int i = 0; i < 10; i++)numbers.push_back(::rand() % 1000);

for (int i = 0; i < static_cast<int>(numbers.size()); i++)std::cout << " " << numbers[i];

std::cout << std::endl;int sum = 0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)sum += numbers[i];

double average = static_cast<double>(sum) / numbers.size();double ssum = 0.0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)ssum += ::pow(numbers[i] - average, 2);

double standardDeviation = ::sqrt(ssum / numbers.size());std::cout << average << " " << standardDeviation << std::endl;

}

int main(){::srand(static_cast<unsigned>(::time(0)));std::vector<int> numbers;for (int i = 0; i < 10; i++)numbers.push_back(::rand() % 1000);

for (int i = 0; i < static_cast<int>(numbers.size()); i++)std::cout << " " << numbers[i];

std::cout << std::endl;int sum = 0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)sum += numbers[i];

double average = static_cast<double>(sum) / numbers.size();double ssum = 0.0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)ssum += ::pow(numbers[i] - average, 2);

double standardDeviation = ::sqrt(ssum / numbers.size());std::cout << average << " " << standardDeviation << std::endl;

}

Novidades neste programa: funções ::srand, ::rand, ::time; operador % (resto da divisão inteira), operador +=.

Preenchendo.

Escrevendo (todos na mesma linha).

Somando.

Somando o quê?

static_cast<double>. Porquê?.

Page 63: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 63

Funções ::rand e ::srandDe cada vez que é chamada, a função ::rand retorna um número pseudo-aleatório entre 0 e 32767. (Este valor é 215-1. No âmbito da programação com números aleatórios referimo--nos a ele por meio da constante RAND_MAX.) Antes de usarmos a função ::rand, devemos chamar a função ::srand, para estabelecer a semente da sequência de números pseudo-aleatórios. Se repetirmos a semente, a sequência virá igual.Quando queremos variar as sequências fora do nosso controlo, usamos para semente o valor de ::time(0), que dáo número de segundos que passaram desde a meia-noite de 1 de Janeiro de 1970.Para usar a função ::time, é preciso fazer #include <ctime>.

Page 64: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 64

Resto da divisão inteiraA expressão X % Y, onde X e Y são expressões inteiras, representa o resto da divisão inteira do valor de X pelo valor de Y. O valor de Y não pode ser zero.

int main(){// ...for (int i = 0; i < 10; i++)numbers.push_back(::rand() % 1000);

// ...}

int main(){// ...for (int i = 0; i < 10; i++)numbers.push_back(::rand() % 1000);

// ...}

Ao tomar o resto da divisão por 1000 do resultado da função ::rand, que é um número entre 0 e 32767, obtemos um número entre 0 e 999.

No entanto, a sequência de números aleatórios fica ligeiramente enviesada, pois a probabilidade de o resto estar entre 0 e 767 émaior do que a de estar entre 768 e 999.

Conselho: não use o operador % com operandos negativos.

Page 65: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 65

Afectações não simplesO operador += é um operador de afectação não simples. A expressão x += y é equivalente a x = x + y (com a vantagem de que a expressão x só é avaliada uma vez).

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

Eis a tabela dos operadores de afectação não simples mais habituais:Use Em vez de

int main(){

int sum = 0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)

sum += numbers[i];// ...double ssum = 0.0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)

ssum += ::pow(numbers[i] - average, 2);// ...

}

int main(){

int sum = 0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)

sum += numbers[i];// ...double ssum = 0.0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)

ssum += ::pow(numbers[i] - average, 2);// ...

}

Escrever, por exemplo, x = x+3 ou y = 2*y ou z = z/10, não está errado, mas é mau estilo. Em C++ preferimos x += 3, y *= 2 e z /= 10.

Page 66: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 66

Máximo do vectorFrequentemente, dado um vector, queremos saber qual é o seu maior elemento. Eis um exemplo, com um vector de números aleatórios:

int main(){

::srand(static_cast<unsigned>(::time(0)));std::vector<int> numbers;for (int i = 0; i < 10; i++)

numbers.push_back(::rand() % 1000);for (int i = 0; i < static_cast<int>(numbers.size()); i++)

std::cout << " " << numbers[i];std::cout << std::endl;int max = 0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)

if (max < numbers[i])max = numbers[i];

std::cout << max << std::endl;}

int main(){

::srand(static_cast<unsigned>(::time(0)));std::vector<int> numbers;for (int i = 0; i < 10; i++)

numbers.push_back(::rand() % 1000);for (int i = 0; i < static_cast<int>(numbers.size()); i++)

std::cout << " " << numbers[i];std::cout << std::endl;int max = 0;for (int i = 0; i < static_cast<int>(numbers.size()); i++)

if (max < numbers[i])max = numbers[i];

std::cout << max << std::endl;}

Observe o algoritmo: inicializamos uma variável, max, com um número menor ou igual a todos os elementos do vector. Depois percorremos o vector comparando cada elemento com o valor corrente de max. Se for maior, actualizamos max com o valor desse elemento. O valor final de max é o valor do maior elemento do vector.

Achar o mínimo seria muito parecido. Fica como exercício. E achar o segundo maior ou o segundo menor?

Page 67: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 67

Procurando no vector

int main(){

::srand(static_cast<unsigned>(::time(0)));std::vector<int> numbers;for (int i = 0; i < 10; i++)

numbers.push_back(::rand() % 1000);for (int i = 0; i < static_cast<int>(numbers.size()); i++)

std::cout << " " << numbers[i];std::cout << std::endl;bool found = false;for (int i = 0; !found && i < static_cast<int>(numbers.size()); i++)

if (numbers[i] < 10)found = true;

std::cout << (found ? "sim" : "nao") << std::endl;}

int main(){

::srand(static_cast<unsigned>(::time(0)));std::vector<int> numbers;for (int i = 0; i < 10; i++)

numbers.push_back(::rand() % 1000);for (int i = 0; i < static_cast<int>(numbers.size()); i++)

std::cout << " " << numbers[i];std::cout << std::endl;bool found = false;for (int i = 0; !found && i < static_cast<int>(numbers.size()); i++)

if (numbers[i] < 10)found = true;

std::cout << (found ? "sim" : "nao") << std::endl;}

Frequentemente, dado um vector, queremos saber se nele existe algum elemento com uma certa propriedade. Eis um exemplo, com um vector de números aleatórios: existe algum número menor do que 10?

Repare no funcionamento da variável found. Logo que found fica a valer true (se ficar) o ciclo de busca termina.

Page 68: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 68

Procurando num vector de nomes

int main(){std::vector<std::string> names;std::ifstream input ("C:/Documents and Settings/.../NomesPróprios.txt");for (;;){std::string line;std::getline(input, line);if (!input)break;

names.push_back(line);}std::cout << static_cast<int>(names.size()) << std::endl;for (;;){std::string name;std::getline(std::cin, name);if (!std::cin)break;

bool found = false;for (int i = 0; !found && i < static_cast<int>(names.size()); i++)if (names[i] == name)found = true;

std::cout << (found ? "sim" : "nao") << std::endl;}

}

int main(){std::vector<std::string> names;std::ifstream input ("C:/Documents and Settings/.../NomesPróprios.txt");for (;;){std::string line;std::getline(input, line);if (!input)break;

names.push_back(line);}std::cout << static_cast<int>(names.size()) << std::endl;for (;;){std::string name;std::getline(std::cin, name);if (!std::cin)break;

bool found = false;for (int i = 0; !found && i < static_cast<int>(names.size()); i++)if (names[i] == name)found = true;

std::cout << (found ? "sim" : "nao") << std::endl;}

}

Queremos um programa para consultar uma lista de palavras, interactivamente. A lista de palavras reside num ficheiro.

Assim declaramos e inicializamos o ficheiro de leitura. Para usar o tipo std::ifstream, que é o tipo dos ficheiros de leitura, é preciso fazer #include <fstream>.

Aqui é preciso colocar o pathname completo.

Mostramos o número de elementos lidos, para controlo.

A busca é como a outra.

Trata-se de uma lista de nomes próprios, em maiúsculas.

Se soubéssemos que os nomes estavam por ordem alfabética poderíamos fazer uma busca dicotómica, mais eficiente.

Page 69: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 69

Vectores de vectoresOs vectores têm um só índice. Quando precisamos de quadros a duas dimensões (matrizes) ou mais, usamos vectores de vectores:Uma matriz de números inteiros:

std::vector<std::vector<int> > matrix;std::vector<std::vector<int> > matrix;

std::vector<std::vector<double> > realMatrix;std::vector<std::vector<double> > realMatrix;

std::vector<std::vector<bool> > connected;std::vector<std::vector<bool> > connected;

Uma matriz de números reais, talvez representando um sistema de equações:

Uma matriz de valores lógicos:

O nome sugere que connected[x][y] vale true quando o “ponto” x está ligado ao “ponto” y, e false quando não está:

Tem de haver um espaço entre os parêntesis angulosos >. Se não, o C++ confundiria isto com o operador >>.

Page 70: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 70

Preenchendo um vector de vectoresPor hipótese, queremos uma matriz de 5x5 preenchida com números aleatórios entre 0 e 127. Eis um programa que faz isso, mostrando depois a matriz no standard output:int main(){

::srand(static_cast<unsigned>(::time(0)));std::vector<std::vector<int> > matrix;for (int i = 0; i < 5; i++){

matrix.push_back(std::vector<int>());for (int j = 0; j < 5; j++)

matrix[i].push_back(::rand() % 128);}for (int i = 0; i < 5; i++){

for (int j = 0; j < 5; j++)std::cout << std::setw(4) << matrix[i][j];

std::cout << std::endl;}

}

int main(){

::srand(static_cast<unsigned>(::time(0)));std::vector<std::vector<int> > matrix;for (int i = 0; i < 5; i++){

matrix.push_back(std::vector<int>());for (int j = 0; j < 5; j++)

matrix[i].push_back(::rand() % 128);}for (int i = 0; i < 5; i++){

for (int j = 0; j < 5; j++)std::cout << std::setw(4) << matrix[i][j];

std::cout << std::endl;}

}

Preenchimento.

Escrita.

Repare bem: cada linha da matriz é um vector. Aqui estamos a acrescentar um vector vazio...

... que depois preenchemos como habitualmente.

Este manipulador std::setw faz com que o número seguinte seja escrito num campo com a largura indicada. Assim fica tudo bem alinhado.

Page 71: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 71

Lendo um vector de vectoresAo ler um vector de vectores de um ficheiro, normalmente indica-se o número de linhas e o número de colunas na primeira linha do ficheiro:int main(){

std::vector<std::vector<double> > realMatrix;int rows;int columns;std::cin >> rows >> columns;for (int i = 0; i < rows; i++){

realMatrix.push_back(std::vector<double>());for (int j = 0; j < columns; j++){

double x;std::cin >> x;realMatrix[i].push_back(x);

}}// ... (continua na página seguinte.

}

int main(){

std::vector<std::vector<double> > realMatrix;int rows;int columns;std::cin >> rows >> columns;for (int i = 0; i < rows; i++){

realMatrix.push_back(std::vector<double>());for (int j = 0; j < columns; j++){

double x;std::cin >> x;realMatrix[i].push_back(x);

}}// ... (continua na página seguinte.

}

Repare bem: cada valor é lido para uma variável local e só depois acrescentado àlinha corrente da matriz.

Lendo o número de linhas e o número de colunas.

Page 72: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 72

Lendo um vector de vectores (final)Eis a parte da função que se ocupa de escrever a matriz no standard output:

int main(){

// ...std::cout << std::fixed << std::setprecision(2);for (int i = 0; i < rows; i++){

for (int j = 0; j < columns; j++)std::cout << std::setw(8) << realMatrix[i][j];

std::cout << std::endl;}

}

int main(){

// ...std::cout << std::fixed << std::setprecision(2);for (int i = 0; i < rows; i++){

for (int j = 0; j < columns; j++)std::cout << std::setw(8) << realMatrix[i][j];

std::cout << std::endl;}

}

Escrevemos em formato de ponto fixo, com duas casas decimais, em campos de largura 8.

Mais um exemplo de inputredirigido.

Page 73: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 73

Lendo e incrementandoSuponhamos que temos um ficheiro com os resultados de um exame, mas apenas as notas, na escala de zero a vinte. Queremos saber quantos alunos tiveram 20, 19, etc.

int main(){

std::vector<int> histogram(21);for (;;){

int x;std::cin >> x;if (!std::cin)

break;histogram[x]++;

}for (int i = 0; i <= 20; i++)

std::cout << " " << histogram[i];std::cout << std::endl;

}

int main(){

std::vector<int> histogram(21);for (;;){

int x;std::cin >> x;if (!std::cin)

break;histogram[x]++;

}for (int i = 0; i <= 20; i++)

std::cout << " " << histogram[i];std::cout << std::endl;

}

Esta declaração cria um vector com tamanho 21, índices de zero a 20, todos os elementos inicializados com zero.

Aqui incrementamos directamente o elemento do vector.

Page 74: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 74

Lendo matrizes rarefeitasAo registar num ficheiro os elementos de uma matriz rarefeita só se indicam os valores diferentes de zero, por meio de um triplo de números: linha, coluna e valor. Inicialmente indica-se o número de linhas e o número de colunas e ainda o número de valores presentes no ficheiro.

int main(){

std::vector<std::vector<int> > matrix;int rows;int columns;std::cin >> rows >> columns;for (int i = 0; i < rows; i++)

matrix.push_back(std::vector<int>(columns));int count;std::cin >> count;for (int i = 0; i < count; i++){

int x;int y;int a;std::cin >> x >> y >> a;matrix[x][y] = a;

}// ...

}

int main(){

std::vector<std::vector<int> > matrix;int rows;int columns;std::cin >> rows >> columns;for (int i = 0; i < rows; i++)

matrix.push_back(std::vector<int>(columns));int count;std::cin >> count;for (int i = 0; i < count; i++){

int x;int y;int a;std::cin >> x >> y >> a;matrix[x][y] = a;

}// ...

}

Acrescentando uma linha cheia de zeros.

Page 75: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 75

Lendo matrizes rarefeitas (final)A parte de escrita é como de costume. Aqui serve apenas para conferir.

int main(){// ...for (int i = 0; i < rows; i++){for (int j = 0; j < columns; j++)std::cout << std::setw(4) << matrix[i][j];

std::cout << std::endl;}

}

int main(){// ...for (int i = 0; i < rows; i++){for (int j = 0; j < columns; j++)std::cout << std::setw(4) << matrix[i][j];

std::cout << std::endl;}

}

Tipicamente usa-se na linha de comando:

Este é o ficheiro out.txt.

Page 76: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 76

Lendo matrizes de booleanosTipicamente são registadas como matrizes de zeros e uns:int main(){std::vector<std::vector<bool> > connected;int rows;int columns;std::cin >> rows >> columns;for (int i = 0; i < rows; i++){connected.push_back(std::vector<bool>());for (int j = 0; j < columns; j++){int x;std::cin >> x;connected[i].push_back(x == 1);

}}for (int i = 0; i < rows; i++){for (int j = 0; j < columns; j++)std::cout << connected[i][j];

std::cout << std::endl;}

}

int main(){std::vector<std::vector<bool> > connected;int rows;int columns;std::cin >> rows >> columns;for (int i = 0; i < rows; i++){connected.push_back(std::vector<bool>());for (int j = 0; j < columns; j++){int x;std::cin >> x;connected[i].push_back(x == 1);

}}for (int i = 0; i < rows; i++){for (int j = 0; j < columns; j++)std::cout << connected[i][j];

std::cout << std::endl;}

}

Neste caso, os dados foram introduzidos na consola por cut-and-paste, a partir do ficheiro acima.

Repare na maneira de transformar 1 em true e zero em false.

Acrescentando um vector vazio.

Page 77: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 77

Exercício: almoço em Nova IorqueN amigos vivendo numa cidade reticular (com ruas e avenidas perpendiculares, como Nova Iorque) pretendem marcar um almoço. Felizmente há restaurantes em todos os cruzamentos e eles decidiram que o almoço deve ser no restaurante que minimize o total de deslocações. Os amigos vão a pé de casa para o restaurante, seguindo pelas ruas e pelas avenidas. Onde deve ser o almoço?

5

20 14

10 4

2 5

2 7

7 3

10 1

5

20 14

10 4

2 5

2 7

7 3

10 1

Dados: um ficheiro onde na primeira linha vem N, seguida de uma linha com o número de ruas e o número de avenidas da cidade, seguida de N linhas com dois números: o primeiro é a rua e o segundo a avenida onde cada um dos amigos mora (sim, todos eles moram em cruzamentos também .) Trata-se de uma cidade matemática em que a primeira rua é a rua zero e o mesmo para as avenidas.

Exemplo

Veja o problema original em http://acm.uva.es/p/v8/855.html.

Page 78: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 78

Vectores de paresEis várias declarações de vectores de pares, como exemplo:

std::vector<std::pair<double, double> > points;std::vector<std::pair<double, double> > points;Um vector de pontos no plano.

std::vector<std::pair<std::string, int> > final;std::vector<std::pair<std::string, int> > final;

std::vector<std::pair<std::string, std::string> > dictionary;std::vector<std::pair<std::string, std::string> > dictionary;

std::vector<std::pair<std::pair<std::string, std::string>,std::pair<int, int> > > matchesPlayed;

std::vector<std::pair<std::pair<std::string, std::string>,std::pair<int, int> > > matchesPlayed;

std::vector<std::pair<double, int> > grades;std::vector<std::pair<double, int> > grades;

Um vector de pares de notas (a primeira seria a nota do exame e a segunda a da parte prática).

Uma pauta, por exemplo, com nome e nota final

Um dicionário bilingue.

Os resultados dos jogos de um campeo-nato, de futebol, de basquetebol, etc.

Page 79: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 79

ParesUm par é um par. Há o primeiro elemento do par (first) e há o segundo elemento do par (second). Para criar um par, usa-se std::make_pair. Observe:

int main(){

std::pair<std::string, int> p1;p1.first = "carolina";p1.second = 218909210;std::cout << p1.first << " " << p1.second << std::endl;p1.second = 919100321;std::cout << p1.first << " " << p1.second << std::endl;std::pair<std::string, int> p2 = std::make_pair("carlos", 968027761);std::cout << p2.first << " " << p2.second << std::endl;

std::pair<double, double> x = std::make_pair(1.0, -1.0);std::cout << x.first << " " << x.second << std::endl;

}

int main(){

std::pair<std::string, int> p1;p1.first = "carolina";p1.second = 218909210;std::cout << p1.first << " " << p1.second << std::endl;p1.second = 919100321;std::cout << p1.first << " " << p1.second << std::endl;std::pair<std::string, int> p2 = std::make_pair("carlos", 968027761);std::cout << p2.first << " " << p2.second << std::endl;

std::pair<double, double> x = std::make_pair(1.0, -1.0);std::cout << x.first << " " << x.second << std::endl;

}

Para escrever um par, escreve-se um elemento de cada vez.

Outros exemplos já a seguir.

Page 80: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 80

MeteorologiaQueremos um programa para processar as temperaturas registadas num certo número de localidades. Os dados vêm num ficheiro, com um nome de localidade e respectiva temperatura em cada linha. O nome da localidade está em minúsculas e não tem espaços. Castelo Branco seria representado castelo_branco.Queremos:• As temperaturas máxima e mínima e as localidades onde

ocorreram.• A temperatura média.• A temperatura numa dada localidade.• Quantas localidades têm temperatura acima da média.• Ordenar as localidades por ordem alfabética e por ordem

decrescente de temperatura.

Page 81: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 81

Classe Meteo

A classe Meteo tem um vector para registar as localidades e as respectivas temperaturas. É um vector de pares:

O nome da localidade a que corresponde a entrada x do vector é obtido pela expressão temperatures[x].first e a respectiva temperatura por temperatures[x].second.

class Meteo {

public:

std::vector<std::pair<std::string, double> > temperatures;

};

class Meteo {

public:

std::vector<std::pair<std::string, double> > temperatures;

};

Page 82: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 82

Lendo um vector de paresA função de leitura tem um argumento que representa a stream de onde se lê. O algoritmo é o do costume:class Meteo {// ...

void Read(std::istream& input){

for (;;){

std::string s;double x;input >> s >> x;if (!input)

break;temperatures.push_back(std::make_pair(s, x));

}}

};

class Meteo {// ...

void Read(std::istream& input){

for (;;){

std::string s;double x;input >> s >> x;if (!input)

break;temperatures.push_back(std::make_pair(s, x));

}}

};

Note bem: a leitura pode ser feita assim, tão simplesmente, porque o nome da localidade não tem espaços. Na verdade, a expressãoinput >> s, primeiro salta os espaços que houver, depois lê os caracteres da streaminput para a cadeia s e pára quando encontra um espaço ou o fim da linha.

Page 83: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 83

Escrevendo um vector de paresPara escrever, usa-se um ciclo for que percorre todos os elementos, do primeiro até ao último:

class Meteo {// ...

int Count(){

return static_cast<int>(temperatures.size());}

void Write(std::ostream& output){

for (int i = 0; i < Count(); i++)output << temperatures[i].first << " "

<< temperatures[i].second << std::endl;}

};

class Meteo {// ...

int Count(){

return static_cast<int>(temperatures.size());}

void Write(std::ostream& output){

for (int i = 0; i < Count(); i++)output << temperatures[i].first << " "

<< temperatures[i].second << std::endl;}

};

A função Count retorna o número de elementos do vector temperatures.

Note bem: não poderíamos escrever apenas output << temperatures[i] << std::endl;pois o C++ “não sabe” escrever pares directamente.

Page 84: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 84

A temperatura médiaA média é o quociente da soma pelo número de elementos:

Exercício: programe uma função para calcular o desvio padrão das temperaturas. O desvio padrão é a raiz quadrada da variância. A variância é o quociente do somatório dos quadrados das diferenças dos elementos em relação à média pelo número de elementos.

class Meteo {// ...

double AverageTemperature(){

double sum = 0.0;for (int i = 0; i < Count(); i++)

sum += temperatures[i].second;return sum / Count();

}};

class Meteo {// ...

double AverageTemperature(){

double sum = 0.0;for (int i = 0; i < Count(); i++)

sum += temperatures[i].second;return sum / Count();

}};

Page 85: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 85

A temperatura máximaUsamos o algoritmo para achar o máximo num vector, adaptando-o. (Ver página 66):

class Meteo {// ...

double Max(){

double result = -std::numeric_limits<double>::infinity();for (int i = 0; i < Count(); i++)

if (result < temperatures[i].second)result = temperatures[i].second;

return result;}

};

class Meteo {// ...

double Max(){

double result = -std::numeric_limits<double>::infinity();for (int i = 0; i < Count(); i++)

if (result < temperatures[i].second)result = temperatures[i].second;

return result;}

};

Repare na utilização do valor “menos infinito”, no tipo double. Menos infinito éo elemento neutro da operação binária que calcula o máximo de dois números.

Para usar a classe genérica std::numeric_limits<T>, é preciso fazer #include <limits>.

Note bem: algumas implementações do C++ não têm o “infinito”.

Se o vector estiver vazio, o máximo é menos infinito.

Page 86: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 86

A cidade mais quenteEis uma função para calcular o índice, no vector, do par cuja temperatura é máxima e outra, que usa a primeira, para calcular o nome da respectiva localidade.class Meteo {// ...

int IndexOfMax() // pre Count() > 0;{

int result = 0;for (int i = 1; i < Count(); i++)if (temperatures[result].second < temperatures[i].second)

result = i;return result;

}

std::string Hottest() // pre: Count() > 0;{

return temperatures[IndexOfMax()].first;}

};

class Meteo {// ...

int IndexOfMax() // pre Count() > 0;{

int result = 0;for (int i = 1; i < Count(); i++)if (temperatures[result].second < temperatures[i].second)

result = i;return result;

}

std::string Hottest() // pre: Count() > 0;{

return temperatures[IndexOfMax()].first;}

};

Repare nas precondições em comentário, exprimindo que estas funções só podem ser chamadas se houver pelo menos um elemento no vector.

Page 87: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 87

MínimosPara referência, eis as funções para calcular a temperatura mínima, o índice do mínimo e o nome da localidade mais fria:

class Meteo {// ...

double Min(){

double result = std::numeric_limits<double>::infinity();for (int i = 0; i < Count(); i++)if (result > temperatures[i].second)

result = temperatures[i].second;return result;

}

int IndexOfMin() // pre: Count() > 0;{

int result = 0;for (int i = 1; i < Count(); i++)if (temperatures[result].second > temperatures[i].second)

result = i;return result;

}

std::string Coldest() // pre: Count() > 0;{

return temperatures[IndexOfMin()].first;}

};

class Meteo {// ...

double Min(){

double result = std::numeric_limits<double>::infinity();for (int i = 0; i < Count(); i++)if (result > temperatures[i].second)

result = temperatures[i].second;return result;

}

int IndexOfMin() // pre: Count() > 0;{

int result = 0;for (int i = 1; i < Count(); i++)if (temperatures[result].second > temperatures[i].second)

result = i;return result;

}

std::string Coldest() // pre: Count() > 0;{

return temperatures[IndexOfMin()].first;}

};

Agora inicializamos com “mais infinito”. Mais infinito é o elemento neutro da operação binária que calcula o mínimo de dois números.

Page 88: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 88

RenqueQueremos calcular o rank (em português: “renque”) do par que ocupa a posição x, na ordenação decrescente das temperaturas. O par cuja temperatura é máxima tem renque 0, e aquele cuja temperatura é mínima tem renque Count() – 1. Primeiro contamos quantos pares têm temperatura maior (e depois menor) do que uma temperatura dada. Depois, idem para temperatura menor. Observe a função Rank:

class Meteo {// ...

int Rank(int x){

return Count() - CountLessThan(temperatures[x].second);}

};

class Meteo {// ...

int Rank(int x){

return Count() - CountLessThan(temperatures[x].second);}

};

class Meteo {// ...

int CountGreaterThan(double x){

int result = 0;for (int i = 0; i < Count(); i++)if (temperatures[i].second >= x)

result++;return result;

}

int CountLessThan(double x){

int result = 0;for (int i = 0; i < Count(); i++)if (temperatures[i].second <= x)

result++;return result;

}

};

class Meteo {// ...

int CountGreaterThan(double x){

int result = 0;for (int i = 0; i < Count(); i++)if (temperatures[i].second >= x)

result++;return result;

}

int CountLessThan(double x){

int result = 0;for (int i = 0; i < Count(); i++)if (temperatures[i].second <= x)

result++;return result;

}

};Neste contexto, “maior”significa “maior ou igual”e “menor” significa “menor ou igual”.

Sim, pode haver empates.

Page 89: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 89

Busca linearA função IndexOf procura no vector a primeira ocorrência de um par cujo primeiro elemento tenha o valor dado e devolve o respectivo índice. Se não encontrar, devolve –1, convencionalmente:

class Meteo {// ...

int IndexOf(std::string x){for (int i = 0; i < Count(); i++)if (temperatures[i].first == x)return i;

return -1;}

};

class Meteo {// ...

int IndexOf(std::string x){for (int i = 0; i < Count(); i++)if (temperatures[i].first == x)return i;

return -1;}

};

Este é o modelo do algoritmo de busca linear em C++. Aprenda-o bem.

Page 90: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 90

A temperatura, dada a localidadeQueremos saber a temperatura de uma localidade.Procuramos, com IndexOf, o índice da localidade no vector e retornamos o segundo elemento do par respectivo.Se a localidade indicada não existir no vector, levantamos uma excepção:

class Meteo {// ...

double TemperatureOf(std::string s){

int x = IndexOf(s);if (x == -1)

throw std::string ("\"" + s + "\": Localidade inexistente");return temperatures[x].second;

}};

class Meteo {// ...

double TemperatureOf(std::string s){

int x = IndexOf(s);if (x == -1)

throw std::string ("\"" + s + "\": Localidade inexistente");return temperatures[x].second;

}};

Note muito bem: quando a excepção élançada, a função não retorna nenhum valor, porque nesse caso a execução da função termina com o lançamento da excepção.

Page 91: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 91

Experimentando as temperaturasvoid TestTemperatures1(){Meteo m;std::ifstream input("temperatures_01.txt");m.Read(input);m.Write(std::cout);std::cout << m.Count() << std::endl;std::cout << m.AverageTemperature() << std::endl;std::cout << m.CountGreaterThan(m.AverageTemperature()) << std::endl;std::cout << m.Max() << " " << m.IndexOfMax() << " " << m.Hottest() << std::endl;std::cout << m.Min() << " " << m.IndexOfMin() << " " << m.Coldest() << std::endl;for (;;){std::cout << "cidade: ";std::string city;std::cin >> city;if (!std::cin)break;

try {double x = m.TemperatureOf(city);std::cout << x << std::endl;int k = m.IndexOf(city);std::cout << m.Rank(k) << std::endl;

} catch (std::string e){std::cout << "Erro: " << e << std::endl;

}}

}

void TestTemperatures1(){Meteo m;std::ifstream input("temperatures_01.txt");m.Read(input);m.Write(std::cout);std::cout << m.Count() << std::endl;std::cout << m.AverageTemperature() << std::endl;std::cout << m.CountGreaterThan(m.AverageTemperature()) << std::endl;std::cout << m.Max() << " " << m.IndexOfMax() << " " << m.Hottest() << std::endl;std::cout << m.Min() << " " << m.IndexOfMin() << " " << m.Coldest() << std::endl;for (;;){std::cout << "cidade: ";std::string city;std::cin >> city;if (!std::cin)break;

try {double x = m.TemperatureOf(city);std::cout << x << std::endl;int k = m.IndexOf(city);std::cout << m.Rank(k) << std::endl;

} catch (std::string e){std::cout << "Erro: " << e << std::endl;

}}

}

Eis uma função de teste para experimentar a classe Meteo.

Page 92: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 92

Ordenando vectores de paresA ordem por que os pares vêm no vector não é significativa. Podemos ordená-los por ordem alfabética do nome das localidades, por exemplo, usando a função genérica std::sort:

class Meteo {// ...

void Sort(){

std::sort(temperatures.begin(), temperatures.end());}

};

class Meteo {// ...

void Sort(){

std::sort(temperatures.begin(), temperatures.end());}

};

Ao ordenar um vector de pares, a função std::sort ordena pelo primeiro elemento e, em caso de empate, desempata pelo segundo.

void TestSort1(){Meteo m;std::ifstream input("temperatures_01.txt");m.Read(input);m.Write(std::cout);std::cout << "-----" << std::endl;m.Sort();m.Write(std::cout);

}

void TestSort1(){Meteo m;std::ifstream input("temperatures_01.txt");m.Read(input);m.Write(std::cout);std::cout << "-----" << std::endl;m.Sort();m.Write(std::cout);

}

Eis uma função de teste:

Page 93: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 93

Busca dicotómicaEstando o vector ordenado pelo nome da localidade, podemos procurar uma dada localidade dicotomicamente:class Meteo {// ...

int BinaryIndexOf(std::string s){

int i = 0;int j = Count() - 1;while (i <= j){

int m = (i+j) / 2;if (temperatures[m].first == s)return m;

else if (temperatures[m].first < s)i = m+1;

else j = m-1;

}return -1;

}};

class Meteo {// ...

int BinaryIndexOf(std::string s){

int i = 0;int j = Count() - 1;while (i <= j){

int m = (i+j) / 2;if (temperatures[m].first == s)return m;

else if (temperatures[m].first < s)i = m+1;

else j = m-1;

}return -1;

}};

No início de cada passo do ciclo, i e j delimitam o intervalo de busca, e m é o ponto médio desse intervalo.

Se o elemento no ponto médio do intervalo éo que procuramos, retornamos o seu índice...

... se não, se o nome da localidade no ponto médio for alfabeticamente menor do que o que procuramos, então a existir, só pode existir na segunda metade do intervalo de busca. A instrução i = m+1 reduz a metade o diâmetro do intervalo de busca. O caso “else” é análogo.

Page 94: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 94

A instrução while

while (expressão)

instrução

Sintaxe:

Semântica:1. Avalia a expressão. Se der false, termina; se não:2. Executa a instrução;3. Volta ao passo 1. O que se faz com while pode fazer-se com for e vice-

versa. A instrução while é mais expressiva quando queremos colocar a ênfase na expressão de continuação. A instrução for é mais expressiva quando queremos realçar o papel da variável de controlo.

Page 95: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 95

Busca dicotómica e busca linearConsideremos um vector com N elementos. Para encontrar um elemento no vector sabendo que ele existe, usando a busca linear, são precisos N/2 acessos ao vector. Para determinar que um elemento não existe são precisos N acessos.Se o vector estiver ordenado e usarmos a busca dicotómica, em cada passo o intervalo de busca divide-se sensivelmente ao meio. Assim, em cerca de log2N passos (no ciclo while) concluiremos que um dado elemento existe ou que não existe.Logo, a busca dicotómica é muito mais eficiente do que a busca linear. Em contrapartida, a busca linear é mais geral, pois a busca dicotómica só se pode usar quando o vector estáordenado pelo campo cujo valor estamos a procurar.

Page 96: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 96

Ordenando algoritmicamenteSe quisermos ordenar o vector por temperaturas crescentes, a função std::sort não serve. Temos nós de programar a função de ordenação. Aqui usamos o bubblesort.

class Meteo {// ...

void SortByTemperature() // bubblesort{

for (int i = 1; i < Count(); i++)for (int j = Count() - 1; j >= i; j--)

if (temperatures[j-1].second > temperatures[j].second)std::swap(temperatures[j-1], temperatures[j]);

}};

class Meteo {// ...

void SortByTemperature() // bubblesort{

for (int i = 1; i < Count(); i++)for (int j = Count() - 1; j >= i; j--)

if (temperatures[j-1].second > temperatures[j].second)std::swap(temperatures[j-1], temperatures[j]);

}}; A função genérica std::swap troca

os valores dos seus dois argumentos.

Explicação pormenorizada do bubblesort na página seguinte.

Note bem: não é bem verdade que a função std::sort não pudesse servir aqui. Só que teria de ser parametrizada usando técnicas mais avançadas.

Page 97: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 97

BubblesortO bubblesort é o pior* algoritmo de ordenação, mas o mais simples de programar. Estudemo-lo para o caso de um vector de inteiros:

class Bubblesort {public:

void Sort(std::vector<int>& a){

for (int i = 1; i < static_cast<int>(a.size()); i++)for (int j = static_cast<int>(a.size()) - 1; j >= i; j--)

if (a[j-1] > a[j])std::swap(a[j-1], a[j]);

}

};

class Bubblesort {public:

void Sort(std::vector<int>& a){

for (int i = 1; i < static_cast<int>(a.size()); i++)for (int j = static_cast<int>(a.size()) - 1; j >= i; j--)

if (a[j-1] > a[j])std::swap(a[j-1], a[j]);

}

};

*É o pior em termos de desempenho: há outros que ordenam mais depressa. Mas é o melhor em simplicidade: percebe-se logo, é pequeno, pode memorizar-se com segurança. Adiante veremos o algoritmo quicksort que é muito melhor (mais rápido), mas mais complexo.

Percorre-se o vector do fim para o princípio, comparando elementos contíguos e trocando-os se estiverem fora de ordem. Ao fim da primeira passagem o menor elemento estarána primeira posição, que é a sua posição definitiva. Recomeça-se, com novo percurso, mas comparando apenas até ao segundo elemento. No fim do segundo percurso, o menor elemento está na primeira posição e o segundo menor na segunda. E assim sucessivamente, até todos os elementos terem alcançado a sua posição definitiva de acordo com a relação de ordem usada.

Repare no &: assinala que a função vai mudar o valor do argumento. É sempre assim: o tipo dos argumentos que mudam de valor na função termina por &.

Page 98: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 98

Comparando bubblesort e std::sortEis uma função de teste:void TestBubblesort(){

Bubblesort b;std::vector<int> numbers;std::cout << "Tamanho? ";int n;std::cin >> n;for (int i = 0; i < n; i++)

numbers.push_back(::rand() % 1000);

std::vector<int> copy = numbers;

if (n <= 100)mas::WriteLine(numbers);

std::cout << "------" << std::endl;b.Sort(numbers);std::cout << "------" << std::endl;

if (n <= 100)mas::WriteLine(numbers);

std::cout << "++++++" << std::endl;std::sort(copy.begin(), copy.end());std::cout << "++++++" << std::endl;

if (n <= 100)mas::WriteLine(numbers);

}

void TestBubblesort(){

Bubblesort b;std::vector<int> numbers;std::cout << "Tamanho? ";int n;std::cin >> n;for (int i = 0; i < n; i++)

numbers.push_back(::rand() % 1000);

std::vector<int> copy = numbers;

if (n <= 100)mas::WriteLine(numbers);

std::cout << "------" << std::endl;b.Sort(numbers);std::cout << "------" << std::endl;

if (n <= 100)mas::WriteLine(numbers);

std::cout << "++++++" << std::endl;std::sort(copy.begin(), copy.end());std::cout << "++++++" << std::endl;

if (n <= 100)mas::WriteLine(numbers);

}

Primeiro preenchemos o vector com números aleatórios e fazemos uma cópia. Depois ordenamos o vector com o bubblesort e a cópia com o std::sort. Se houver menos de 100 elementos nos vectores, vamos escrevendo, com a função utilitária mas::WriteLine.

Qual é mais rápido: o nosso bubblesort ou o std::sort da biblioteca do C++?

Page 99: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 99

Cronometrando o bubblesortNo meu computador, para vectores pequenos, até cerca de 100 elementos, ambas as funções são instantâneas, à escala dos reflexos humanos. A partir de cento e tal, o bubblesortdeixa de ser instantâneo. Isso só acontece para o std::sort a partir de três mil e tal.

Eis o resultado de cronometra-gens do bubblesort: Os tempos absolutos não interessam muito, pois dependem do compu-tador, das optimizações do compilador, do estado do sistema. Os valores relativos é que são interessantes.

N seg1000 2,22000 8,93000 204000 355000 566000 817000 1098000 1439000 181

10000 223

seg

0

50

100

150

200

250

0 2000 4000 6000 8000 10000 12000

A curva parece uma parábola, o que mostra que o tempo do bubblesort éproporcional ao quadrado do número de elementos no vector.

Page 100: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 100

Funções utilitárias de vectoresO ficheiro Utilities.h contém uma função Write que serve para escrever os elementos de um vector num ficheiro, parametri-zando o separador. Se só indicarmos o vector, escreve na consola com um espaço entre dois elementos consecutivos. A função WriteLine faz o mesmo e no fim muda de linha. Observe:namespace mas {

template <class T>void Write(const std::vector<T>& v, const std::string& separator = " ",

std::ostream& output = std::cout){

for (typename std::vector<T>::const_iterator i = v.begin(); i != v.end(); i++)output << (i != v.begin() ? separator : "") << *i;

}

template <class T>void WriteLine(const std::vector<T>& v, const std::string& separator = " ",

std::ostream& output = std::cout){

Write(v, separator, output);output << std::endl;

}

}

namespace mas {

template <class T>void Write(const std::vector<T>& v, const std::string& separator = " ",

std::ostream& output = std::cout){

for (typename std::vector<T>::const_iterator i = v.begin(); i != v.end(); i++)output << (i != v.begin() ? separator : "") << *i;

}

template <class T>void WriteLine(const std::vector<T>& v, const std::string& separator = " ",

std::ostream& output = std::cout){

Write(v, separator, output);output << std::endl;

}

}

Sim, estas funções usam muitas coisas ainda desconhecidas. Mas dão para ficar com uma ideia do que se passa. Lembre-se que neste momento apenas queremos poder usá-las. Mais tarde aprenderemos a programá-las.

Page 101: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 101

Quicksortclass Quicksort {public:

void Sort(std::vector<int>& a, int x, int y){

int i = x;int j = y;int p = a[(i+j) / 2];do{while (a[i] < p)

i++;while (p < a[j])

j--;if (i <= j)

std::swap(a[i++], a[j--]);} while (i <= j);if (x < j)

Sort(a, x, j);if (i < y)

Sort(a, i, y);}

void Sort(std::vector<int>& a){

Sort(a, 0, static_cast<int>(a.size() - 1));}

};

class Quicksort {public:

void Sort(std::vector<int>& a, int x, int y){

int i = x;int j = y;int p = a[(i+j) / 2];do{while (a[i] < p)

i++;while (p < a[j])

j--;if (i <= j)

std::swap(a[i++], a[j--]);} while (i <= j);if (x < j)

Sort(a, x, j);if (i < y)

Sort(a, i, y);}

void Sort(std::vector<int>& a){

Sort(a, 0, static_cast<int>(a.size() - 1));}

};

O quicksort é o melhor algoritmo de ordenação de vectores, de uso geral.

É um algoritmo recursivo: a função Sort chama-se a si própria duas vezes, para ordenar as duas partes do vector, numa altura em que todos os elementos da parte da esquerda são menores que todos os da parte da direita.

O quicksort foi inventado por Hoare, em 1962. Esta é a versão do livro Algorithms + Data Structures = Programs, de Niklaus Wirth (1976), adaptada para C++.

Instrução do-while.

Page 102: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 102

Como funciona o quicksort?A ideia é colocar todos os números pequenos antes de todos os grandes; depois fazer o mesmo no subvector dos números pequenos e no dos grandes. Ser pequeno é ser menor ou igual ao pivô; ser grande é ser maior ou igual ao pivô. Eis o filme do funcionamento do quicksort: :

* 1 12 11 71 5 67 36 10 11 18 94 75 73 37 23+ 4 3 11 5 10 36 67 71 18 94 75 73 37 23* 1 3 5 11 5 10 36 67 71 18 94 75 73 37 23+ 2 1 5 11 10 36 67 71 18 94 75 73 37 23* 2 3 11 5 11 10 36 67 71 18 94 75 73 37 23+ 3 2 5 10 11 36 67 71 18 94 75 73 37 23* 4 12 94 5 10 11 36 67 71 18 94 75 73 37 23+12 11 5 10 11 36 67 71 18 23 75 73 37 94* 4 11 18 5 10 11 36 67 71 18 23 75 73 37 94+ 5 4 5 10 11 18 67 71 36 23 75 73 37 94* 5 11 23 5 10 11 18 67 71 36 23 75 73 37 94+ 6 5 5 10 11 18 23 71 36 67 75 73 37 94* 6 11 67 5 10 11 18 23 71 36 67 75 73 37 94+ 9 7 5 10 11 18 23 37 36 67 75 73 71 94* 6 7 37 5 10 11 18 23 37 36 67 75 73 71 94+ 7 6 5 10 11 18 23 36 37 67 75 73 71 94* 9 11 73 5 10 11 18 23 36 37 67 75 73 71 94+11 9 5 10 11 18 23 36 37 67 71 73 75 94

71 5 67 36 10 11 18 94 75 73 37 23i j pivô

5 10 11 18 23 36 37 67 71 73 75 94

O pivô é o elemento na posição média, em cada subvector.

Vector no início

Vector no fim, ordenado.

“Fotos” tiradas antes (*) e depois (+) da instrução do-while.

Page 103: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 103

Cronometrando o quicksortNo meu computador, o quicksort é instantâneo para vectores com até cerca de 3000 elementos.Eis os tempos para o quicksort:

N seg10000 0,3620000 0,7830000 1,2240000 1,7250000 2,1660000 2,6170000 3,180000 3,5590000 4,09

100000 4,59

seg

00,5

11,5

22,5

33,5

44,5

5

0 20000 40000 60000 80000 100000 120000

Parece uma recta, mas não é: tem uma ligeira curvatura (com a concavidade para cima) mas também não é uma parábola. Na verdade para um vector com Nelementos, o tempo é de cálculo é proporcional a N*log(N).

Quanto tempo demoraria o bubblesort a ordenar um vector com 100000 números?

Page 104: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 104

A instrução do

do

instrução

while (condição);

Sintaxe:

Semântica:1. Executa a instrução;2. Avalia a expressão. Se der false, termina; se não:3. Volta ao passo 1.A instrução do é muito menos frequente do que as instruções while e for.

A instrução do é semelhante à instrução while, mas o teste da expressão de continuação é feito depois da instrução:

Usa-se quando, por natureza, a instruçãotem de ser executa pelo menos uma vez. (Isso acontece necessariamente quando algumas das variáveis que intervêm na expressão de continuação são inicializadas no seio da instrução.)

Page 105: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 105

Máximo Divisor ComumEm muitos problemas aritméticos, temos de calcular o máximo divisor comum de dois números. Podemos fazê-lo “à letra”, procurando o maior número que divide simultaneamente os dois números dados.

int gcd(int x, int y){

for (int i = mas::Min(x, y); i > 0; i--)if (x % i == 0 && y % i == 0)

return i;return -1; // cannot happen

}

int gcd(int x, int y){

for (int i = mas::Min(x, y); i > 0; i--)if (x % i == 0 && y % i == 0)

return i;return -1; // cannot happen

}

Eis uma função que usa essa técnica. O primeiro número que vale a pena testar é o menor dos dois argumentos:

É uma função de busca. Neste caso, pela natureza do problema, sabemos que a busca terá êxito, pelo que o return –1; no fim, seria desnecessário. No entanto, sem esse return, o compilador queixar-se-ia de que nem todos os caminhos de execução da função terminariam num return. Nós sabemos que sim, mas o compilador não se dá conta disso.

A função mas::Min(x, y) retorna o menor dos seus dois argumentos. Vem no ficheiro Utilities.h.

Page 106: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 106

Busca garantidaQuando a busca é garantida (isto é, quando temos a certeza de encontrar o que procuramos), geralmente simplifica-se usando uma instrução while:

int gcd(int x, int y){

int result = mas::Min(x, y);while (!(x % result == 0 && y % result == 0))

result--;return result;

}

int gcd(int x, int y){

int result = mas::Min(x, y);while (!(x % result == 0 && y % result == 0))

result--;return result;

}

Page 107: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 107

Algoritmo de Euclides (1)Há uma maneira mais expedita de calcular o máximo divisor comum, e foi Euclides que a inventou, há mais de 2000 anos. Baseia-se na seguinte formulação da função que calcula o máximo divisor comum de dois números inteiros positivos:

int gcd(int x, int y) // pre x >= 1 && y >= 1;{return (x == y) ? x : x < y ? gcd(x, y-x) : gcd(x-y, y);

}

int gcd(int x, int y) // pre x >= 1 && y >= 1;{return (x == y) ? x : x < y ? gcd(x, y-x) : gcd(x-y, y);

}

gcdx,y x, se x y

gcdx,y − x, se x ygcdx − y,y, se x y

A transcrição directa desta fórmula para C++ dá:

Exercício de matemática: demonstre que se x < yentão gcd(x, y) é igual a gcd(x, y-x), tal como a fórmula afirma.

Repare na precondição, em comentário. Serve para não nos esquecermos de que a função só pode ser usada quando x >= 1 e y >= 1. Para outros pares de valores, dá disparate.

Page 108: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 108

Algoritmo de Euclides (2)

int GreatestCommonDivisor(int x, int y) // pre x >= 1 && y >= 1;{while (x != y)if (x < y)y -= x;

elsex -= y;

return x;}

int GreatestCommonDivisor(int x, int y) // pre x >= 1 && y >= 1;{while (x != y)if (x < y)y -= x;

elsex -= y;

return x;}

Observando o funcionamento da função recursiva, concluímos que em cada chamada o menor dos argumentos é subtraído do maior. Isto conduz-nos à formulação iterativa:

class Euclid {public:int gcd(int x, int y) // pre x >= 1 && y >= 1;{return (x == y) ? x : x < y ? gcd(x, y-x) : gcd(x-y, y);

}

int GreatestCommonDivisor(int x, int y) // pre x >= 1 && y >= 1;{while (x != y)if (x < y)y -= x;

elsex -= y;

return x;}

};

class Euclid {public:int gcd(int x, int y) // pre x >= 1 && y >= 1;{return (x == y) ? x : x < y ? gcd(x, y-x) : gcd(x-y, y);

}

int GreatestCommonDivisor(int x, int y) // pre x >= 1 && y >= 1;{while (x != y)if (x < y)y -= x;

elsex -= y;

return x;}

};

Reunimos estas funções (e as outras que vêm a seguir) na classe Euclid:

Repare que os argumentos da função estão a ser usados como variáveis.

Esta é a versão que corresponde ao que Euclides realmente imaginou e descreveu.

Page 109: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 109

Algoritmo de Euclides (3)

int GreatestCommonDivisorFaster(int x, int y){

while (y > 0)std::swap(x = x % y, y);

return x;}

int GreatestCommonDivisorFaster(int x, int y){

while (y > 0)std::swap(x = x % y, y);

return x;}

Na prática, usamos uma versão mais eficiente, que tira partido do operador % (resto da divisão inteira), para fazer de uma vez uma sequência de subtracções sucessivas:

O algoritmo de Euclides é considerado o primeiro algoritmo não-trivial inventado pelo espírito humano.

Esta função está escrita de maneira compacta, de propósito, para ilustração. Mais convencionalmente, programaríamos assim:

int GreatestCommonDivisorFaster1(int x, int y){while (y > 0){x = x % y;std::swap(x, y);

}return x;

}

int GreatestCommonDivisorFaster1(int x, int y){while (y > 0){x = x % y;std::swap(x, y);

}return x;

}

Page 110: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 110

Testando o algoritmo de Euclides

int GreatestCommonDivisor(int x, int y) // ...{while (x != y){std::cout << x << " " << y << std::endl;// ...

int GreatestCommonDivisor(int x, int y) // ...{while (x != y){std::cout << x << " " << y << std::endl;// ...

Observemos o valor das variáveis ao longo dos ciclos, colocando uma instrução de escrita no início de cada passo:

int GreatestCommonDivisorFaster(int x, int y){while (y > 0){std::cout << x << " " << y << std::endl;// ...

int GreatestCommonDivisorFaster(int x, int y){while (y > 0){std::cout << x << " " << y << std::endl;// ...

Esta chega ao resultado em menos passos.

Page 111: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 111

Testando com um programa visualAproveitemos para praticar os programas visuais, experimentando o algoritmo de Euclides usando uma caixa de diálogo, assim:

Chamando-se o projecto EuclidVisual, as classes geradas são CEuclidVisual e CEuclidVisualDlg.As caixas de cima terão variáveis associadas m_number1 e m_number2 e a de baixo m_gcd. A função OnBnClicked...vem na página seguinte.

Page 112: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 112

Função OnBnClickedButton1A função valida os dados:void CEuclidVisualDlg::OnBnClickedButton1(){try{Euclid e;mas::Clear(m_gcd);int x = mas::GetInt(m_number1);int y = mas::GetInt(m_number2);if (!(x >= 1 && y >= 1))mas::ErrorMessage("Erro: ambos os números devem ser positivos.");

else{int z = e.GreatestCommonDivisor(x, y);mas::SetInt(m_gcd, z);

}} catch (std::string e) {mas::ErrorMessage(e);

}}

void CEuclidVisualDlg::OnBnClickedButton1(){try{Euclid e;mas::Clear(m_gcd);int x = mas::GetInt(m_number1);int y = mas::GetInt(m_number2);if (!(x >= 1 && y >= 1))mas::ErrorMessage("Erro: ambos os números devem ser positivos.");

else{int z = e.GreatestCommonDivisor(x, y);mas::SetInt(m_gcd, z);

}} catch (std::string e) {mas::ErrorMessage(e);

}}

O valor afixado na caixa do resultado, se houver, é apagado logo no início.

Page 113: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 113

Função OnOKSe carregarmos na tecla Enter depois de dar um número num dos controlos de cima o programa sai. Como se trata de uma caixa de diálogo, o programa “pensa” que clicámos no botão OK (que já não existe na nossa caixa) e clicar no OK numa caixa de diálogo faz a caixa terminar.Para evitar esse comportamento, basta programar a função OnOK. Esta é a função que é chamada quando clicamos no botão “OK” de uma caixa de diálogo, ou, como vimos, quando carregamos na tecla Enter num controlo de edição.void CEuclidVisualDlg::OnOK(){

OnBnClickedButton1();}

void CEuclidVisualDlg::OnOK(){

OnBnClickedButton1();}

class CEuclidVisualDlg : public CDialog{// ...

public:CEdit m_number1;CEdit m_number2;CEdit m_gcd;afx_msg void OnBnClickedButton1();afx_msg void OnOK();

};

class CEuclidVisualDlg : public CDialog{// ...

public:CEdit m_number1;CEdit m_number2;CEdit m_gcd;afx_msg void OnBnClickedButton1();afx_msg void OnOK();

};

Tal como está programada a função OnOK, quando carregarmos em Enter écomo se tivéssemos clicado no botão Calcular.

Temos de declarar a função, àmão, junto da outra, no ficheiro .h.

Page 114: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 114

Números primos entre siDois números são primos entre si, se o seu máximo divisor comum for a unidade:

bool RelativePrimes(int x, int y){

return GreatestCommonDivisorFaster(x, y) == 1;}

bool RelativePrimes(int x, int y){

return GreatestCommonDivisorFaster(x, y) == 1;}

Page 115: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 115

Menor Múltiplo ComumO menor múltiplo comum de dois números inteiros positivos está relacionado com o máximo divisor comum. Um momento de reflexão mostra que:

int LeastCommonMultiple(int x, int y){

return x / GreatestCommonDivisorFaster(x, y) * y;}

int LeastCommonMultiple(int x, int y){

return x / GreatestCommonDivisorFaster(x, y) * y;}

std::pair<int, int> Both(int x, int y){

int d = GreatestCommonDivisorFaster(x, y);int m = x / d * y;return std::make_pair(d, m);

}

std::pair<int, int> Both(int x, int y){

int d = GreatestCommonDivisorFaster(x, y);int m = x / d * y;return std::make_pair(d, m);

}

Se precisarmos do mdc e do mmc ao mesmo tempo, o melhor é calculá-los em conjunto:

Todas estas funções vêm na classe Euclid.

Recorde que as operações aritméticas com a mesma precedência se fazem da esquerda para a direita.

Page 116: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 116

Números primosOutra questão numérica interessante em programação é a dos números primos. Por vezes, queremos saber se um número inteiro maior do que 1 é primo ou não. Outras vezes, queremos dispor de uma tabela de números primos, para podermos seleccionar directamente.

class Primes {public:

std::vector<int> primes;

bool IsPrime(int x) // pre x >= 2{

//...}

void ComputePrimes(int x, int y){

//...}

};

class Primes {public:

std::vector<int> primes;

bool IsPrime(int x) // pre x >= 2{

//...}

void ComputePrimes(int x, int y){

//...}

};

A função IsPrime dá true se o número passado em argumento for um número primo. A função ComputePrimes preenche a tabela com todos os números primos entre os dois limites indicados.

Eis uma classe, Primes, para números primos:

Page 117: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 117

És Primo?Apliquemos a definição: x é primo se, sendo maior que 1, for divisível apenas por x e por 1. Logo, temos um problema de busca: procurar um divisor de x entre 2 e x-1. Se houver, xnão é primo, se não houver, é.

bool IsPrime0(int x){

for (int i = 2; i * i <= x; i++)if (x % i == 0)

return false;return true;

}

bool IsPrime0(int x){

for (int i = 2; i * i <= x; i++)if (x % i == 0)

return false;return true;

}

Aliás, se x não for primo, terá um divisor menor ou igual à raiz quadrada de x. Logo, basta procurar entre 2 e raiz de x:

Podemos reduzir o tempo de cálculo a metade, em média, tratando logo o caso dos números pares, pois nenhum deles é primo, excepto o 2:

bool IsPrime(int x) // pre x >= 2{

if (x != 2 && x % 2 == 0)return false;

for (int i = 3; i * i <= x; i+=2)if (x % i == 0)

return false;return true;

}

bool IsPrime(int x) // pre x >= 2{

if (x != 2 && x % 2 == 0)return false;

for (int i = 3; i * i <= x; i+=2)if (x % i == 0)

return false;return true;

}

Repare nestas instruções for.

Page 118: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 118

Tabela de números primosPara preencher a tabela, recorremos à função IsPrime:

void ComputePrimes(int x, int y){

primes.clear();x = x < 2 ? 2 : x;if (x == 2)

primes.push_back(2);if (x % 2 == 0)

x++;for (int i = x; i <= y; i += 2)if (IsPrime(i))

primes.push_back(i);}

void ComputePrimes(int x, int y){

primes.clear();x = x < 2 ? 2 : x;if (x == 2)

primes.push_back(2);if (x % 2 == 0)

x++;for (int i = x; i <= y; i += 2)if (IsPrime(i))

primes.push_back(i);}

Isto é simples, mas não é eficiente, porque para testar se um número não éprimo bastaria ver se ele é divisível por um dos números primos já presentes na tabela, admitindo que a tabela está a ser construída a partir de 2. Ora a função IsPrime não tira partido disso.O método clássico para construir uma tabela de números primos é o crivo de Eratóstenes.

Se o argumento for menor do que 2, é como se fosse 2.

Page 119: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 119

Mostrando os primos na consolaEis uma função de teste que escreve a tabela de primos na consola:

void TestPrimesVector(){

Primes p;int x;int y;std::cout << "Entre que limites? ";std::cin >> x >> y;p.ComputePrimes(x, y);mas::WriteLine(p.primes);

}

void TestPrimesVector(){

Primes p;int x;int y;std::cout << "Entre que limites? ";std::cin >> x >> y;p.ComputePrimes(x, y);mas::WriteLine(p.primes);

}Eis o resultado de uma experiência:

Para tabelas maiores, mostrar na consola fica pouco prático.

Page 120: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 120

Mostrando os primos visualmenteEscrevamos um programa visual para mostrar a tabela dos números primos. Queremos uma caixa de diálogo com dois controlos de edição, para os limites, um controlo de edição multilinha, para a tabela, e um botão, para desencadear os cálculos:

Neste controlo de edição, as propriedades Multiline e Vertical Scroll estão a true, e as propriedade Auto HScroll e Auto VScrollestão a false.

Associamos as variáveis m_from, m_to e m_primesaos controlos.

Para que carregar na tecla Enter seja equivalente a clicar no botão, podemos usar a técnica do OnOK (ver página 113) ou então, alternativamente, mudar a propriedade Default Button do botão de false para true.

Page 121: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 121

Escrevendo na caixa multilinhaComo fazemos para escrever a tabela de números primos na caixa multilinha?

void CPrimesVisualDlg::OnBnClickedButton1(){

try {Primes p;int x = mas::GetInt(m_from);int y = mas::GetInt(m_to);p.ComputePrimes(x, y);std::stringstream ss;mas::Write(p.primes, " ", ss);mas::SetString(m_primes, ss.str());

} catch (std::string e) {mas::ErrorMessage(e);

}}

void CPrimesVisualDlg::OnBnClickedButton1(){

try {Primes p;int x = mas::GetInt(m_from);int y = mas::GetInt(m_to);p.ComputePrimes(x, y);std::stringstream ss;mas::Write(p.primes, " ", ss);mas::SetString(m_primes, ss.str());

} catch (std::string e) {mas::ErrorMessage(e);

}}

Usamos a função mas::Write para escrever o vector numa stringstream. Depois, usamos a função mas::SetString para mostrar na caixa o valor da string contida na stringstream. Observe:

Para usar o tipo std::stringstream, é preciso fazer #include <sstream>.

Page 122: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 122

CapicuasUma capicua é um número inteiro cuja representação decimal é simétrica: lê-se igualmente do princípio para o fim e do fim para o princípio. Por exemplo: 4, 22, 313, 5445, 76167:class Numerals {public:

bool IsSymmetric(std::string s){

for (int i = 0, j = static_cast<int>(s.size())-1; i < j; i++, j--)if (s[i] != s[j])

return false;return true;

}

std::string Decimal(int x){

std::stringstream ss;ss << x;return ss.str();

}

bool IsPalindrome(int x){

return IsSymmetric(Decimal(x));}

};

class Numerals {public:

bool IsSymmetric(std::string s){

for (int i = 0, j = static_cast<int>(s.size())-1; i < j; i++, j--)if (s[i] != s[j])

return false;return true;

}

std::string Decimal(int x){

std::stringstream ss;ss << x;return ss.str();

}

bool IsPalindrome(int x){

return IsSymmetric(Decimal(x));}

};

A função IsSymmetric dá true se o seu argumento for uma string simétrica. Repare no ciclo for com duas variáveis de controlo.

A função Decimal calcula a representação decimal de um número. A representação decimal é uma string. Repare na utilização da stringstream.

Um número é capicua, IsPalindrome, se a sua representação decimal for simétrica:

Page 123: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 123

Representação decimal dos númerosPodemos obter a representação decimal dos números inteiros directamente, calculando recursivamente os sucessivos algarismos, começando pelos menos significativos:class Numerals {public:

// ...

std::string Decimal(int x){//return x < 10 ? std::string(1, x + '0') : Decimal(x / 10) + static_cast<char>(x % 10 + '0');return (x < 10 ? "" : Decimal(x / 10)) + static_cast<char>(x % 10 + '0');

}};

class Numerals {public:

// ...

std::string Decimal(int x){//return x < 10 ? std::string(1, x + '0') : Decimal(x / 10) + static_cast<char>(x % 10 + '0');return (x < 10 ? "" : Decimal(x / 10)) + static_cast<char>(x % 10 + '0');

}};

A expressão em comentário ilustra uma maneira alternativa de programar, menos compacta.

Note bem: a expressão x % 10 + '0'representa o valor numérico do carácter que constitui o último algarismo da representação decimal do número x.Note bem: este operador + faz a concatenação do

operando da esquerda (uma cadeia) com o da direita (um carácter). O static_cast<char> é necessário para que a expressão numérica x % 10 + '0’ seja interpretada como sendo do tipo char.

Page 124: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 124

Representação binária dos númerosUsemos o mesmo esquema para calcular a representação dos números na base 2 e na base 16:class Numerals {public:

// ...

std::string Binary(int x){//return x < 2 ? std::string(1, x + '0') : Binary(x/2) + static_cast<char>(x % 2 + '0');return (x < 2 ? "" : Binary(x / 2)) + static_cast<char>(x % 2 + '0');

}

std::string Hexadecimal(int x){//return (x < 16 ? "" : Hexadecimal(x / 16))

+ static_cast<char>(x % 16 < 10 ? x % 16 + '0' : x % 16 + 'A' - 10);return (x < 16 ? "" : Hexadecimal(x / 16))

+ static_cast<char>(x % 16 + (x % 16 < 10 ? '0' : 'A' - 10));}

};

class Numerals {public:

// ...

std::string Binary(int x){//return x < 2 ? std::string(1, x + '0') : Binary(x/2) + static_cast<char>(x % 2 + '0');return (x < 2 ? "" : Binary(x / 2)) + static_cast<char>(x % 2 + '0');

}

std::string Hexadecimal(int x){//return (x < 16 ? "" : Hexadecimal(x / 16))

+ static_cast<char>(x % 16 < 10 ? x % 16 + '0' : x % 16 + 'A' - 10);return (x < 16 ? "" : Hexadecimal(x / 16))

+ static_cast<char>(x % 16 + (x % 16 < 10 ? '0' : 'A' - 10));}

}; Na base 16, 10 é representado por 'A', 11 por 'B', etc., e 15 por 'F'.

Page 125: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 125

Experimentando os numeraisEis uma função de teste para experimentar os números decimais, binários e hexadecimais:void TestNumerals(){

Numerals n;for (;;){

int x;std::cout << "> ";std::cin >> x;if (!std::cin)

break;std::cout << x << std::endl;std::cout << n.Decimal(x) << std::endl;std::cout << n.Binary(x) << std::endl;std::cout << n.Hexadecimal(x) << std::endl;std::cout << std::endl;

}}

void TestNumerals(){

Numerals n;for (;;){

int x;std::cout << "> ";std::cin >> x;if (!std::cin)

break;std::cout << x << std::endl;std::cout << n.Decimal(x) << std::endl;std::cout << n.Binary(x) << std::endl;std::cout << n.Hexadecimal(x) << std::endl;std::cout << std::endl;

}}

Page 126: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 126

Convertendo de std::string para intFrequentemente, quando temos um numeral (isto é, uma cadeia de caracteres que representa um número) queremos calcular o número correspondente. Trata-se da função inversa da função Decimal, FromDecimal. A função FromBinary éanáloga, mas usa a base 2.

class Numerals {public:

// ...

int FromDecimal(std::string s) // all chars is s are decimal digits{int result = 0;for (int i = 0; i < static_cast<int>(s.size()); i++)result = 10 * result + s[i] - '0';

return result;}

int FromBinary(std::string s) // all chars in s are either '0' or '1'{int result = 0;for (int i = 0; i < static_cast<int>(s.size()); i++)//result = 2 * result + s[i] - '0';result = 2 * result + (s[i] == '1');

return result;}

};

class Numerals {public:

// ...

int FromDecimal(std::string s) // all chars is s are decimal digits{int result = 0;for (int i = 0; i < static_cast<int>(s.size()); i++)result = 10 * result + s[i] - '0';

return result;}

int FromBinary(std::string s) // all chars in s are either '0' or '1'{int result = 0;for (int i = 0; i < static_cast<int>(s.size()); i++)//result = 2 * result + s[i] - '0';result = 2 * result + (s[i] == '1');

return result;}

};

Na prática, usamos a função mas::AsInt (do ficheiro UtilitiesNumerical.h) que levanta uma excepção se a conversão não puder realizar-se.

Note que o valores lógicos são transformados automaticamente em valores inteiros (false dá 0, true dá 1) nas expressões aritméticas.

Page 127: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 127

Quantos algarismos tem um número?Se for menor do que 10, tem 1. Se não, tem mais 1 do que o quociente da sua divisão por 10:

class Numerals {public:// ...

int CountDigits(int x) // pre x >= 0;{return x < 10 ? 1 : CountDigits(x / 10) + 1;

}

};

class Numerals {public:// ...

int CountDigits(int x) // pre x >= 0;{return x < 10 ? 1 : CountDigits(x / 10) + 1;

}

};

A formulação recursiva éengraçada e muito compacta, mas a iterativa também é simples:

class Numerals {public:// ...

int CountDigits(int x) // pre x >= 0;{int result = 1;while (x >= 10){result++;x /= 10;

}return result;

}

};

class Numerals {public:// ...

int CountDigits(int x) // pre x >= 0;{int result = 1;while (x >= 10){result++;x /= 10;

}return result;

}

};

Page 128: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 128

O reversoO “reverso” de um número inteiro xé o número y cuja representação decimal é simétrica da de x, descontando os zeros à esquerda. Podemos calcular aritmeticamente:

class Numerals {public:// ...

int Reverse(int x){int result = 0;while (x > 0){result = 10 * result + x % 10;x /= 10;

}return result;

}

};

class Numerals {public:// ...

int Reverse(int x){int result = 0;while (x > 0){result = 10 * result + x % 10;x /= 10;

}return result;

}

};

Usando a função Reverse, a função IsPalindrome (que vê se um número é capicua) fica mais simples (e mais eficiente):

class Numerals {public:// ...

bool IsPalindrome(int x){return x == Reverse(x);

}

};

class Numerals {public:// ...

bool IsPalindrome(int x){return x == Reverse(x);

}

};

Portanto, descobrir se um número é uma capicua fica um problema puramente numérico.

Page 129: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 129

Problema do Metro de LisboaTodos os anos, o plano de expansão da rede do metro de Lisboa muda. Actualmente (final de 2003), é o seguinte:

Na verdade, este mapa é o do plano de expansão da linha vermelha. No entanto, dá para ver os planos das outras linhas também.Para mais informações, consulte www.metrolisboa.pt/futuro.htm.

Page 130: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 130

Como ir de uma estação a outra?Nas redes de metro complicadas, como a de Lisboa futuramente, coloca-se a questão de descobrir o melhor percurso para ir de uma estação a outra.Em geral, temos a seguinte sequência de problemas:• Calcular a distância de uma estação até outra.• Calcular o caminho mais curto de uma estação até outra.• Enumerar todos os caminhos de uma estação até outra.

A distância mede-se em número de ligações entre as duas estações e não em metros. Por “todos os caminhos” entende-se caminhos que não passam duas vezes pela mesma estação.Depois de enumerar os caminhos, podemos calcular o caminho mais longo, o mais curto, o segundo mais curto, etc.

Page 131: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 131

Como representar a rede do metro?Primeiro, representamos cada estação por um número. Fazemos isso com um vector de strings, stations, tal que se stations[k] == s, então k é o número da estação s.Depois, representamos a rede por um grafo: um vector de vectores de números inteiros, graph, tal que graph[x] é o vector com os números das estações às quais a estação xestá ligada directamente.Eis as declarações, na nova classe Metro:

class Metro {public:

std::vector<std::string> stations;std::vector<std::vector<int> > graph;//...

};

class Metro {public:

std::vector<std::string> stations;std::vector<std::vector<int> > graph;//...

};

Esta é a representação interna, isto é no programa. A representação externa, no ficheiro de dados serádiferente.

Page 132: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 132

O ficheiro com a rede do metroO programa começará por ler a descrição da rede, a partir de um ficheiro de texto.Por hipótese, na primeira linha há dois números: o número de estações, X, e o número de ligações, Y. Seguem-se Xlinhas com os nomes das estações e depois Y linhas com as ligações entre estações, representadas pelos nomes das estações separados por um sinal ‘#’.Eis o ficheiro da rede do metro de Lisboa, depois das extensões planeadas, em esquema:

58 61cais do sodrebaixa-chiadorossiomartim monizintendenteanjosarroiosalameda...aeroportoportelasacavemcais do sodre#baixa-chiadobaixa-chiado#rossiorossio#martim monizmartim moniz#intendenteintendente#anjos...moscavide#encarnacaoencarnacao#aeroportomoscavide#portelaportela#sacavem

58 61cais do sodrebaixa-chiadorossiomartim monizintendenteanjosarroiosalameda...aeroportoportelasacavemcais do sodre#baixa-chiadobaixa-chiado#rossiorossio#martim monizmartim moniz#intendenteintendente#anjos...moscavide#encarnacaoencarnacao#aeroportomoscavide#portelaportela#sacavem

58 estações

61 ligações

Page 133: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 133

Lendo o ficheiro com a rede (1)Separemos a leitura da construção do grafo, lendo as estações para o vector stations e as ligações para um novo vector links:class Metro {public:

std::vector<std::string> stations;std::vector<std::pair<std::string, std::string> > links;std::vector<std::vector<int> > graph;//...

};

class Metro {public:

std::vector<std::string> stations;std::vector<std::pair<std::string, std::string> > links;std::vector<std::vector<int> > graph;//...

};

Lemos com a função Read (página seguinte) e depois construímos o grafo a partir dos dados nos vectores stationse links.

Page 134: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 134

Lendo o ficheiro com a rede (2)A leitura é simples:

void Read(std::istream& input){

int x; // number of stations in inputint y; // number of links in inputinput >> x >> y;std::string line;std::getline(input, line); // ignore rest of linefor (int i = 0; i < x; i++){

std::getline(input, line);stations.push_back(line);

}for (int i = 0; i < y; i++){

std::getline(input, line);int k = static_cast<int>(line.find('#'));std::string s1 = line.substr(0, k);std::string s2 = line.substr(k + 1); links.push_back(std::make_pair(s1, s2));

}}

void Read(std::istream& input){

int x; // number of stations in inputint y; // number of links in inputinput >> x >> y;std::string line;std::getline(input, line); // ignore rest of linefor (int i = 0; i < x; i++){

std::getline(input, line);stations.push_back(line);

}for (int i = 0; i < y; i++){

std::getline(input, line);int k = static_cast<int>(line.find('#'));std::string s1 = line.substr(0, k);std::string s2 = line.substr(k + 1); links.push_back(std::make_pair(s1, s2));

}}

Repare bem: depois de termos lido os dois números inteiros, x e y, a leitura fica suspensa logo a seguir ao último algarismo de y, ainda antes do fim da primeira linha do ficheiro. A leitura “ignore rest of line” é indispensável: sem ela, a primeira estação seria lida ainda na primeira linha, onde não hámais nada, ficando portanto com uma cadeia vazia. A partir daí as coisas iriam correr mal, pois estaríamos a ler fora do sítio.

Repare na técnica para separar os dois nomes de estação, com auxílio das funções find e substr, ambas da classe std::string. A função find dá o índice da primeira ocorrência do argumento ou –1, se não ocorrer. A função substr, quando chamada com dois argumentos, s.substr(x, n), dá a subcadeia de s formada pelos ncaracteres a partir do que está na posição x. Chamada s.substr(x) dá a subcadeia formada por todos os caracteres de s a partir do que está na posição x.

Page 135: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 135

Construindo o grafoO grafo constrói-se a partir do vector links, substituindo os nomes das estações pelo respectivo número, o qual se calcula com a função StationOf:

int StationNumber(std::string s){

for (int i = 0; i < static_cast<int>(stations.size()); i++)if (stations[i] == s)

return i;return -1;

}

int StationNumber(std::string s){

for (int i = 0; i < static_cast<int>(stations.size()); i++)if (stations[i] == s)

return i;return -1;

}void MakeGraph(){

graph.clear();graph.resize(stations.size());for (int i = 0; i < static_cast<int>(links.size()); i++){int x = StationNumber(links[i].first);int y = StationNumber(links[i].second);graph[x].push_back(y);graph[y].push_back(x);

}}

void MakeGraph(){

graph.clear();graph.resize(stations.size());for (int i = 0; i < static_cast<int>(links.size()); i++){int x = StationNumber(links[i].first);int y = StationNumber(links[i].second);graph[x].push_back(y);graph[y].push_back(x);

}}

Isto é uma busca linear. Se tivéssemos ordenado o vector stations, poderíamos ter usado a busca dicotómica, que é mais eficiente.

Neste problema do metro, o grafo ébidireccional: quando há uma aresta do vértice x para o vérticey, também há uma aresta de vértice ypara o vértice x.

Page 136: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 136

Distância no grafoA distância de um vértice a outro é o comprimento do caminho mais curto que vai do primeiro ao segundo. No entanto, para calcular a distância não é preciso determinar esse caminho. Usamos, em vez disso, a metáfora da bomba atómica:Consideremos, para concretizar, que queremos saber qual a distância entre o Areeiro e a Cidade Universitária. (Tenha presente o mapa da rede.) Então, admitamos, metaforicamente, que colocamos uma bomba atómica na estação do Areeiro. Por hipótese, quando a bomba explodir, a onda de choque propaga-se nos túneis em todas as direcções, e leva um segundo a chegar à próxima estação. Teremos então, que ao segundo zero da explosão, morre quem estiver no Areeiro. Ao segundo 1, morre quem estiver numa estação adjacente ao Areeiro, ou seja Alameda e Roma. Ao segundo 2 morre que estiver numa estação adjacente a Alameda ou Roma, ou seja, Alvalade, Olaias, Arroios e Saldanha. E assim por diante.Para calcular a distância, “basta” deslocarmo-nos para a Cidade Univer-sitária com um cronómetro, fazer deflagrar por controlo remoto a bomba no Areeiro, e medir o tempo que onda de choque demora a chegar à Cidade Universitária. Neste caso seria 4 segundos. Ou seja, a distância é 4.

Page 137: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 137

A distância, à bombaclass Metro {public://...int Distance(int x, int y)

// Distance from vertex x to vertex y, provided there is a path form x to y{std::vector<std::vector<int> > dead;std::vector<int> died(graph.size(), -1);dead.push_back(std::vector<int>());dead.back().push_back(x);died[x] = 0;int result = 0;while (died[y] == -1){result++;dead.push_back(std::vector<int>());for (int i = 0; i < static_cast<int>(dead[result-1].size()); i++)for (int j = 0; j < static_cast<int>(graph[dead[result-1][i]].size()); j++){int z = graph[dead[result-1][i]][j];if (died[z] == -1){dead.back().push_back(z);died[z] = result;

}}

}return result;

}};

class Metro {public://...int Distance(int x, int y)

// Distance from vertex x to vertex y, provided there is a path form x to y{std::vector<std::vector<int> > dead;std::vector<int> died(graph.size(), -1);dead.push_back(std::vector<int>());dead.back().push_back(x);died[x] = 0;int result = 0;while (died[y] == -1){result++;dead.push_back(std::vector<int>());for (int i = 0; i < static_cast<int>(dead[result-1].size()); i++)for (int j = 0; j < static_cast<int>(graph[dead[result-1][i]].size()); j++){int z = graph[dead[result-1][i]][j];if (died[z] == -1){dead.back().push_back(z);died[z] = result;

}}

}return result;

}};

Explicação nas páginas seguintes.

Page 138: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 138

Explicação, primeira parteint Distance(int x, int y) // Distance ...{

std::vector<std::vector<int> > dead;std::vector<int> died(graph.size(), -1);dead.push_back(std::vector<int>());dead.back().push_back(x);died[x] = 0;int result = 0;while (died[y] == -1){

// ...}return result;

}};

int Distance(int x, int y) // Distance ...{

std::vector<std::vector<int> > dead;std::vector<int> died(graph.size(), -1);dead.push_back(std::vector<int>());dead.back().push_back(x);died[x] = 0;int result = 0;while (died[y] == -1){

// ...}return result;

}};

O vector dead é um vector de vectores, tal que durante o processamento, dead[t] representa o conjunto dos vértices que “morreram” no instante t. Complementarmente, o vector died éum vector de números inteiros tal que se died[x] vale t, isso significa que o vértice k “morreu” no instante t.

No instante zero, “morre” o vértice x, que é o vértice de partida.

O processamento prossegue até o vértice y “morrer”. Inicialmente o vector died é preenchido com –1.

Note que, em rigor, um dos vectores, dead ou died, bastava. No entanto, assim a programação fica mais simples e mais eficiente.

Page 139: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 139

Explicação, segunda parteint Distance(int x, int y) // Distance ... provided there is a path form x to y{

// ...int result = 0;while (died[y] == -1){

result++;dead.push_back(std::vector<int>());for (int i = 0; i < static_cast<int>(dead[result-1].size()); i++)

for (int j = 0; j < static_cast<int>(graph[dead[result-1][i]].size()); j++){int z = graph[dead[result-1][i]][j];if (died[z] == -1){

dead.back().push_back(z);died[z] = result;

}}

}return result;

}};

int Distance(int x, int y) // Distance ... provided there is a path form x to y{

// ...int result = 0;while (died[y] == -1){

result++;dead.push_back(std::vector<int>());for (int i = 0; i < static_cast<int>(dead[result-1].size()); i++)

for (int j = 0; j < static_cast<int>(graph[dead[result-1][i]].size()); j++){int z = graph[dead[result-1][i]][j];if (died[z] == -1){

dead.back().push_back(z);died[z] = result;

}}

}return result;

}};

Percorremos o vector dead[result-1], dos vértices que morreram no instante anterior...

... e para cada um deles consideramos todos os vértices que lhes estão ligados.

A variável z é uma variável auxiliar que representa sucessivamente os vértices ligados a cada um dos vértices que morreram no instante anterior.

Se z ainda não morreu, morre agora!

Note que esta função só pode ser chamada se houver um caminho de x para y (tal como está expresso no comentário). Se não houvesse, cairíamos um ciclo infinito.

Page 140: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 140

A tabela dos predecessoresCom uma pequena adição à função Distance, podemos registar, para cada vértice, qual foi o vértice que o “matou”. O vértice que matou é o predecessor do vértice morto, no caminho mais curto desde o vértice original até ao vértice em causa.Depois, percorrendo a tabela dos predecessores a partir do vértice de chegada, reconstruímos, do fim para o princípio, o caminho mais curto.A tabela dos predecessores é uma nova variável na classe Metro:

class Metro {public:

//...std::vector<int> predecessors; //...

};

class Metro {public:

//...std::vector<int> predecessors; //...

};

Dado um vértice de partida, normalmente calculamos a tabela completa para todos os nós alcançáveis e não apenas até atingir o nó de chegada.

Page 141: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 141

Calculando os predecessoresvoid ComputePredecessors(int x){predecessors.clear();predecessors.resize(graph.size(), -1);std::vector<std::vector<int> > dead;std::vector<int> died(graph.size(), -1);dead.push_back(std::vector<int>());dead.back().push_back(x);died[x] = 0;int distance = 0;while (!dead.back().empty()){distance++;dead.push_back(std::vector<int>());for (int i = 0; i < static_cast<int>(dead[distance-1].size()); i++)for (int j = 0; j < static_cast<int>(graph[dead[distance-1][i]].size()); j++){int z = graph[dead[distance-1][i]][j];if (died[z] == -1){dead.back().push_back(z);died[z] = distance;predecessors[z] = dead[distance-1][i];

}}

}}

void ComputePredecessors(int x){predecessors.clear();predecessors.resize(graph.size(), -1);std::vector<std::vector<int> > dead;std::vector<int> died(graph.size(), -1);dead.push_back(std::vector<int>());dead.back().push_back(x);died[x] = 0;int distance = 0;while (!dead.back().empty()){distance++;dead.push_back(std::vector<int>());for (int i = 0; i < static_cast<int>(dead[distance-1].size()); i++)for (int j = 0; j < static_cast<int>(graph[dead[distance-1][i]].size()); j++){int z = graph[dead[distance-1][i]][j];if (died[z] == -1){dead.back().push_back(z);died[z] = distance;predecessors[z] = dead[distance-1][i];

}}

}}

Limpamos o vector predecessors e preenchemo-lo com –1. No final, predecessors[x] vale –1. Os outros vértices que valerem –1 no final são os que não são alcançáveis a partir de x.

Paramos quando já não “morrer” ninguém.

Quem matou o vértice z foi o vértice dead[distance-1][i].

A variável local distance faz as vezes de result no cálculo da distância.

Page 142: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 142

O caminho mais curto, com listasPara calcular o caminho mais curto do vértice x para o vértice y, uma vez disponível a tabela dos predecessores a partir de x, começa-se pelo fim, isto é, pelo vértice y. Depois adiciona--se à cabeça o predecessor de y, depois o predecessor do predecessor de y e assim sucessivamente, enquanto esse predecessor não for –1.

std::list<int> ShortestPathTo(int y){

std::list<int> result;result.push_front(y);while (predecessors[y] != -1){

y = predecessors[y];result.push_front(y);

}return result;

}

std::list<int> ShortestPathTo(int y){

std::list<int> result;result.push_front(y);while (predecessors[y] != -1){

y = predecessors[y];result.push_front(y);

}return result;

}

Temos de adicionar à cabeça, com push_front, e não no fim, como temos feito nos vectores, com push_back.Ora nos vectores não hápush_front. Mas nas listashá! Por isso o caminho mais curto será uma lista.

Page 143: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 143

Testando o grafo (1)Eis uma função de teste. Para cada estação de partida, calcula os predecessores. Depois, para cada estação de chegada, calcula a distância e o caminho mais curto:void TestMetro(){Metro m;std::ifstream input("metro_2006.txt");m.Read(input);m.MakeGraph();for (;;){std::string s1;std::cout << "Partida: ";std::getline(std::cin, s1);if (!std::cin)break;

int x1 = m.StationNumber(s1);if (x1 != -1){// ...

}}

}

void TestMetro(){Metro m;std::ifstream input("metro_2006.txt");m.Read(input);m.MakeGraph();for (;;){std::string s1;std::cout << "Partida: ";std::getline(std::cin, s1);if (!std::cin)break;

int x1 = m.StationNumber(s1);if (x1 != -1){// ...

}}

}

Ver na página seguinte.

Page 144: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 144

Testando o grafo (2)void TestMetro(){// ...for (;;){// ...if (x1 != -1){for (;;){std::string s2;std::cout << "Chegada: ";std::getline(std::cin, s2);if (!std::cin)break;

int x2 = m.StationNumber(s2);if (x2 != -1){std::cout << "Distancia: " << m.Distance(x1, x2) << std::endl;std::list<int> w = m.ShortestPathTo(x2);std::cout << "Caminho mais curto: ";mas::WriteLine(w);

}}std::cin.clear();

}}

}

void TestMetro(){// ...for (;;){// ...if (x1 != -1){for (;;){std::string s2;std::cout << "Chegada: ";std::getline(std::cin, s2);if (!std::cin)break;

int x2 = m.StationNumber(s2);if (x2 != -1){std::cout << "Distancia: " << m.Distance(x1, x2) << std::endl;std::list<int> w = m.ShortestPathTo(x2);std::cout << "Caminho mais curto: ";mas::WriteLine(w);

}}std::cin.clear();

}}

}

Temos dois ciclos for infinitos, um dentro do outro. Ambos terminam quando o utilizador dá Control-Z.

Repare nesta instrução std::cin.clear(). Serve para “limpar” o “erro” provocado pelo Control-Z que fez terminar o ciclo interior.

Sim, também há funções mas::Write e mas::WriteLine para escrever listas.

Page 145: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 145

Listas de cadeiasPara interpretar o caminho, devemos mostrar os nomes das estações e não os números.Usamos uma função PathIdentified que retorna a lista de strings que corresponde à lista de inteiros no argumento.Para visitar os sucessivos elementos da lista usamos um iterador. Observe:

std::list<std::string> PathIdentified(std::list<int> w){

std::list<std::string> result;for (std::list<int>::iterator i = w.begin(); i != w.end(); i++)

result.push_back(stations[*i]);return result;

}

std::list<std::string> PathIdentified(std::list<int> w){

std::list<std::string> result;for (std::list<int>::iterator i = w.begin(); i != w.end(); i++)

result.push_back(stations[*i]);return result;

}w.begin(): o início da lista.

w.end(): o fim da lista.

i++: passar ao elemento seguinte na lista.

*i: o elemento corrente (o que está a ser visitado).

Page 146: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 146

Mostrando o caminhoPara interpretar o caminho, devemos mostrar os nomes das estações e não os números:void TestMetro(){// ...for (;;){// ...if (x1 != -1){for (;;){// ... if (x2 != -1){std::cout << "Distancia: " << m.Distance(x1, x2) << std::endl;std::list<int> w = m.ShortestPathTo(x2);std::cout << "Caminho mais curto: ";mas::WriteLine(m.PathIdentified(w), "->");

}}std::cin.clear();

}}

}

void TestMetro(){// ...for (;;){// ...if (x1 != -1){for (;;){// ... if (x2 != -1){std::cout << "Distancia: " << m.Distance(x1, x2) << std::endl;std::list<int> w = m.ShortestPathTo(x2);std::cout << "Caminho mais curto: ";mas::WriteLine(m.PathIdentified(w), "->");

}}std::cin.clear();

}}

}

Page 147: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 147

Vectores e ListasNos vectores guardamos objectos de um certo tipo, nas listas também.A principal diferença é que nos vectores os elementos podem ser acedidos por indexação, nas listas não. Por outro lado, as listas podem crescer pelo princípio, com push_front, os vectores não.

• resize• size• push_back• indexação• back• front

• resize• size• push_back• push_front• back• front

Operações sobre listas

Operações sobre vectores

Page 148: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 148

Vectores na memória

v

v.push_back(3)

3

3 8v.push_back(8)

3 8

...v.push_back(2)

7 1 4 3 5 4 4 2

std::vector<int> v;

............

...

...

...

.........

.........

.........

Os vectores ocupam posições contíguas na memória do programa em execução.

Aqui vemos o vector v, ainda sem elementos.

Depois do primeiro push_back o vector já tem um elemento.

E agora já tem dois elementos.

E agora já tem muitos, e estão todos de seguida na memória.

Page 149: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 149

Listas na memória (1)Os elementos da lista são alocados um a um, algures na memória.

w

std::list<int> w;

w.push_back(3)

w 3

w.push_back(7)

w 3 7

...w.push_back(2)

w 3 7 5 8 3 2

Aqui está uma lista ainda sem elementos. Éapenas um “descritor”.

Depois de um push_back a lista já tem um elemento, acessível através das ligações representadas pelas setas.

Agora a lista já tem dois elementos.

Agora já tem muitos, cada um ligado ao seguinte e ao anterior. O primeiro e o último elementos são acessíveis directamente a partir do “descritor”.

Page 150: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 150

Listas na memória (2)Fazer push_front numa lista custa tanto como fazer push_back: é tudo uma questão de ligações. Observe, em esquema:

w.push_front(6)

w 3 7 5 8 3 2

6

w 3 7 5 8 3 2

6

w.push_front(9)

9 Repare que fazer push_front num vector obrigaria a “deslocar” para a frente todos os elementos do vector para abrir espaço para o novo elemento. A classe std::vector<T> não fornece essa operação.

Page 151: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 151

Resolução de equaçõesQueremos resolver no computador equações que não sabemos resolver algebricamente, por exemplo:

Vamos considerar dois métodos: o da bissecção e o de Newton.

Page 152: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 152

Considerações gerais• Colocaremos as nossas equações na forma f(x) = 0. Assim,

resolver a equação original é o mesmo do que encontrar os zeros da função f .

• Em cada caso queremos encontrar apenas um zero.• Normalmente estudamos a função num intervalo onde

sabemos existir apenas um zero.• Inicialmente, se ajudar, desenhamos o gráfico da função no

intervalo, para delimitarmos grosseiramente o zero que nos interessa.

• A função f é “bem comportada” no intervalo: é contínua, não toma valores muito grandes (em valor absoluto), éestritamente crescente ou decrescente.

Page 153: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 153

Exemplo: ex – 2 = 0Exemplo: queremos resolver a equação.

A função é crescente, tem um zero entre 0 e 2, claramente.Desenhemos o gráfico com a ajuda do Excel, para confirmar.Começamos com uma classe RealFunction, para representar funções reais de variável real.

Page 154: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 154

Classe RealFunctionTemos duas funções: a função Function, que calcula a função ex-2 num ponto e a função Plot, que escreve uma tabela de valores da função:class RealFunction {public:

double Function(double x){

return ::exp(x) - 2.0;}

void Plot(double x0, double x1, int n, std::ostream& output){

double delta = (x1 - x0) / n;for (int i = 0; i < n; i++){

double x = x0 + i * delta;double y = Function(x);output << x << "\t" << y << std::endl;

}output << x1 << "\t" << Function(x1) << std::endl;

}};

class RealFunction {public:

double Function(double x){

return ::exp(x) - 2.0;}

void Plot(double x0, double x1, int n, std::ostream& output){

double delta = (x1 - x0) / n;for (int i = 0; i < n; i++){

double x = x0 + i * delta;double y = Function(x);output << x << "\t" << y << std::endl;

}output << x1 << "\t" << Function(x1) << std::endl;

}};

O intervalo x1..x2 é dividido em nsubintervalos. A função é calculada em n+1 pontos. Em cada linha escrevemos um valor de x e o correspondente valor de y, separados por um carácter tab.

Aqui está a nossa função ex-2, definida em C++.

Page 155: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 155

Tabelando a funçãoPrimeiro tabelamos a função, indicando o intervalo e o número de subintervalos.

void TestEquation1(){

Exp1 r;std::ofstream output ("out1.xls");r.Plot(0, 2, 10, output);

}

void TestEquation1(){

Exp1 r;std::ofstream output ("out1.xls");r.Plot(0, 2, 10, output);

}

0 -10.2 -0.7785970.4 -0.5081750.6 -0.1778810.8 0.2255411 0.7182821.2 1.320121.4 2.05521.6 2.953031.8 4.049652 5.38906

0 -10.2 -0.7785970.4 -0.5081750.6 -0.1778810.8 0.2255411 0.7182821.2 1.320121.4 2.05521.6 2.953031.8 4.049652 5.38906

Eis uma função de teste:Eis o ficheiro “out1.xls”produzido:

Em cada linha os dois números vêm separados por um carácter tab.

Usamos a extensão “.xls” para abrir directamente no Excel.

Page 156: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 156

Desenhando o gráficoAbrimos o ficheiro criado pela função Plot no Excel e inserimos um gráfico (“chart”) de tipo “Scatter” e subtipo “Scatter with data points connected with smooth lines”:

O zero está entre 0.6 e 0.8.

Note bem: para isto dar, épreciso que o Excel aceite os pontos decimais em vez das vírgulas.

Page 157: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 157

O método da bissecçãoDividimos o intervalo ao meio e escolhemos a metade que contém a raiz, repetindo até a largura do intervalo ser suficientemente pequena:class RealFunction {public:// ...

double Zero(double x0, double x1, double tolerance){

double a = x0;double b = x1;double fa = Function(a);while (b - a >= tolerance){

double m = (a + b) / 2;double fm = Function(m);if (fa * fm <= 0)

b = m;else

a = m;}return (a + b) / 2;

}};

class RealFunction {public:// ...

double Zero(double x0, double x1, double tolerance){

double a = x0;double b = x1;double fa = Function(a);while (b - a >= tolerance){

double m = (a + b) / 2;double fm = Function(m);if (fa * fm <= 0)

b = m;else

a = m;}return (a + b) / 2;

}};

fa guarda o valor da função no extremo esquerdo do intervalo.

fm é valor da função no ponto médio do intervalo.

Se fa e fm tiverem sinais diferentes, escolhe-se o intervalo da esquerda, se não escolhe-se o da direita.

Paramos quando a largura do intervalo for inferior à tolerância.

O valor calculado pode não ser exacto, mas o erro é inferior a metade da tolerância.

Page 158: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 158

O método de NewtonO método de Newton parte de uma estimativa inicial da raiz, x0, e calcula uma sequência de aproximações através da seguinte fórmula, parando quando a diferença entre duas aproximações sucessivas for suficientemente pequena:

O método de Newton é mesmo bom para programar. Mas precisamos da derivada.

Page 159: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 159

Funções para o método de NewtonTemos de acrescentar uma função para a derivada e outra para o método de Newton:class RealFunction {public:

// ...double Derivative(double x){

return ::exp(x);}

//...double Newton(double x0, double tolerance){

double x = x0;for (;;){

double delta = Function(x) / Derivative(x);if (::fabs(delta) < tolerance)

break;x -= delta;

}return x;

}};

class RealFunction {public:

// ...double Derivative(double x){

return ::exp(x);}

//...double Newton(double x0, double tolerance){

double x = x0;for (;;){

double delta = Function(x) / Derivative(x);if (::fabs(delta) < tolerance)

break;x -= delta;

}return x;

}};

Qual dos dois métodos é melhor?O da bissecção é mais robusto, o de Newton normalmente converge mais depressa. Façamos uma experiência

Page 160: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 160

Experimentando os dois métodosEis uma função de teste:

void TestEquation1(){

Exp1 r;//std::ofstream output ("out1.xls");//r.Plot(0, 2, 10, output);std::cout << std::fixed << std::setprecision(10); double x = r.Zero(0.6, 0.8, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;x = r.Newton(0.6, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;x = r.Newton(0.8, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;

}

void TestEquation1(){

Exp1 r;//std::ofstream output ("out1.xls");//r.Plot(0, 2, 10, output);std::cout << std::fixed << std::setprecision(10); double x = r.Zero(0.6, 0.8, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;x = r.Newton(0.6, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;x = r.Newton(0.8, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;

}

Estamos a calcular a menos de 0.000001, no intervalo [0.6, 0.8].

Aparentemente, o método de Newton émais preciso.

Page 161: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 161

Observando a convergênciaAcrescentemos instruções para mostrar os valores ao longo dos ciclos:class RealFunction {public:// ...double ZeroByBisection(double x0, double x1, double tolerance) {//...while (b - a >= tolerance){//...std::cout << "***" << m << " " << fm << std::endl;if (fa * fm <= 0)//...

}return (a + b) / 2;

}

double ZeroByNewton(double x0, double tolerance){double x = x0;for (;;){std::cout << "+++" << x << " " << Function(x) << std::endl;//...

}return x;

}};

class RealFunction {public:// ...double ZeroByBisection(double x0, double x1, double tolerance) {//...while (b - a >= tolerance){//...std::cout << "***" << m << " " << fm << std::endl;if (fa * fm <= 0)//...

}return (a + b) / 2;

}

double ZeroByNewton(double x0, double tolerance){double x = x0;for (;;){std::cout << "+++" << x << " " << Function(x) << std::endl;//...

}return x;

}};

O método da bissecção precisa de 18 passos no ciclo, o de Newton apenas 4, em cada caso.

Page 162: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 162

Interpretação geométrica, Newton

y=f(x)

x0x1

f’(x0)(x-x0)+f(x0) = 0

x2

y=f’(x1)(x-x1)+f(x1)

x1 = x0 - f(x0) / f’(x0)x1 é o ponto onde a recta verde se anula:Logo:Analogamente para x2 : x2 = x1 - f(x1) / f’(x1)Generalizando : xi = xi-1 - f(xi-1) / f’(xi-1)

y=f’(x0)(x-x0)+f(x0)

A recta verde é tangente àcurva no ponto <x0, f(x0)>. A recta azul é tangente à curva no ponto <x1, f(x1)>.

Page 163: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 163

Outra equaçãoAgora queremos resolver outra equação:

class RealFunction {public:

double Function(double x){

return ::tan(x) - x - 0.5;}

double Derivative(double x){

return ::pow(1.0 / ::cos(x), 2) - 1;}

// ...};

class RealFunction {public:

double Function(double x){

return ::tan(x) - x - 0.5;}

double Derivative(double x){

return ::pow(1.0 / ::cos(x), 2) - 1;}

// ...};

Bastaria mudar as definições de Function e de Derivative na classe RealFunction:

No entanto, há uma solução bem melhor.

Lembrete: a derivada da tangente é a secante quadrada.

Page 164: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 164

Funções virtuais, classes abstractasDeferimos a definição das funções Function e Derivative na classe RealFunction, tornando essas funções virtuais puras, assim:class RealFunction {public:

virtual double Function(double x) = 0;virtual double Derivative(double x) = 0;

void Plot(double x0, double x1, int n, std::ostream& output){

//...}

double Zero(double x0, double x1, double tolerance){

// ...}

double Newton(double x0, double tolerance){

//...}

};

class RealFunction {public:

virtual double Function(double x) = 0;virtual double Derivative(double x) = 0;

void Plot(double x0, double x1, int n, std::ostream& output){

//...}

double Zero(double x0, double x1, double tolerance){

// ...}

double Newton(double x0, double tolerance){

//...}

};

Repare bem nestas declarações.

Uma classe com funções virtuais puras é uma classe abstracta. Uma classe abstracta não éutilizável directamente, porque faltam as definições das funções virtuais puras. Essas definições serão fornecidas em classes derivadas, especializadas.Cada classe derivada servirápara uma função.

Uma função virtual pura é só declarada, mas não implementada nesta classe.

Page 165: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 165

Classes derivadasVamos definir uma classe Tan1 para a nova função cujos zeros queremos calcular. A classe Tan1 herda de RealFunction e redefine as duas funções virtuais. Observe:

class Tan1: public RealFunction {public:

double Function(double x){

return ::tan(x) - x - 0.5;}

double Derivative(double x){

return ::pow(1.0 / ::cos(x), 2) - 1;}

};

class Tan1: public RealFunction {public:

double Function(double x){

return ::tan(x) - x - 0.5;}

double Derivative(double x){

return ::pow(1.0 / ::cos(x), 2) - 1;}

};

Agora usamos a classe Tan1, em vez da classe RealFunction.

RealFunction é a classe de base; Tan1 é a classe derivada.

Page 166: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 166

Usando a classe derivadaFazemos como antes, declarando um objecto de tipo Tan1:void TestEquation2(){

Tan1 r;std::cout << std::fixed << std::setprecision(15); double x = r.Zero(0.5, 1.2, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;x = r.Newton(0.85, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;

}

void TestEquation2(){

Tan1 r;std::cout << std::fixed << std::setprecision(15); double x = r.Zero(0.5, 1.2, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;x = r.Newton(0.85, 0.000001);std::cout << x << " " << r.Function(x) << std::endl;

}

Note bem: convém estudar a função, desenhando o gráfico, para delimitar os intervalos onde queremos encontrar os zeros.Esta função tem uma infinidade de zeros.

Estamos a calcular o zero do intervalo [0.5, 1.2].

A estimativa inicial é 0.85.

Page 167: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 167

Integração numéricaTrata-se de calcular numericamente o integral definido de uma função dada, entre dois limites dados. Veremos dois métodos: o do trapézio e a regra de Simpson.

class RealFunction {public:

virtual double Function(double x) = 0;virtual double Derivative(double x) = 0;

// ...

double Trapezoidal(double x0, double x1, double tolerance){

//...}

double Simpson(double x0, double x1, double tolerance){

//...}

};

class RealFunction {public:

virtual double Function(double x) = 0;virtual double Derivative(double x) = 0;

// ...

double Trapezoidal(double x0, double x1, double tolerance){

//...}

double Simpson(double x0, double x1, double tolerance){

//...}

};

Cada um serárepresentado por uma função na classe RealFunction:

As funções a integrar serão definidas em classes derivadas. Se a função Derivative não fizer falta, porque queremos integrar e não calcular zeros pelo método de Newton, então pode ficar apenas com return 0;.

Page 168: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 168

Método do trapézioA ideia é dividir o intervalo de integração em muitos intervalos, e, em cada intervalo, aproximar a função por uma recta. Calculando a área sob a recta, que é a área de um trapézio, em cada intervalo, e somando tudo, obtemos uma

x0 x1 x2 x3 x4

y0

y1y2 y3

y4aproximação do integral da função original.Em relação à figura, e admitindo que xi-xi-1=h, a área colorida é calculada pela expressão:

Esta expressão é mesmo boa para programar.

Page 169: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 169

Função Trapezoidalclass RealFunction {public:

//...double Trapezoidal(double x0, double x1, double tolerance){

double fa = Function(x0);double fb = Function(x1);int n = 1; // number of intervalsdouble h = x1 - x0; // length of each intervaldouble result = (fa + fb) / 2 * h;double previous;do{

previous = result;n *= 2;h = (x1 - x0) / n;double sum = fa;for (int i = 1; i <= n-1; i++)

sum += 2 * Function(x0 + i * h);sum += fb;result = sum * h / 2;

} while (::fabs(result - previous) >= tolerance);return result;

}};

class RealFunction {public:

//...double Trapezoidal(double x0, double x1, double tolerance){

double fa = Function(x0);double fb = Function(x1);int n = 1; // number of intervalsdouble h = x1 - x0; // length of each intervaldouble result = (fa + fb) / 2 * h;double previous;do{

previous = result;n *= 2;h = (x1 - x0) / n;double sum = fa;for (int i = 1; i <= n-1; i++)

sum += 2 * Function(x0 + i * h);sum += fb;result = sum * h / 2;

} while (::fabs(result - previous) >= tolerance);return result;

}};

Repetimos os cálculos, duplicando o número de intervalos, até não haver diferença apreciável na soma (isto é, até a diferença em valor absoluto ser menor do que a tolerância).

Os valores da função nos extremos dos intervalos entram uma vez só. Os outros entram duas vezes.

Page 170: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 170

Experimentando o método do trapézioclass Sine: public RealFunction {public:

double Function(double x){

return ::sin(x);}

double Derivative(double x){

return ::cos(x);}

};

class Sine: public RealFunction {public:

double Function(double x){

return ::sin(x);}

double Derivative(double x){

return ::cos(x);}

};

Calculemos o integral do seno entre 0 e π/2, que sabemos valer exactamente 1.0. Começamos com uma classe derivada para o seno:

void TestIntegral1(){

Sine c;std::cout << std::fixed << std::setprecision(10); double z = c.Trapezoidal(0.0, M_PI_2, 0.000001);std::cout << z << std::endl;

}

void TestIntegral1(){

Sine c;std::cout << std::fixed << std::setprecision(10); double z = c.Trapezoidal(0.0, M_PI_2, 0.000001);std::cout << z << std::endl;

}

Eis o exemplo de utilização:

O resultado calculado é0.9999998039.

A tolerância é 0.000001.

Quantos intervalos terão sido necessários?

Page 171: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 171

Observando a convergência, de novoAcrescentemos instruções para mostrar os valores ao longo do ciclo, tal como fizemos com as funções Zero e Newton:class RealFunction {public://...double Trapezoidal(double x0, double x1, double tolerance){//...do{// ...std::cout << "****" << n << " " << result << std::endl;

} while (::fabs(result - previous) >= tolerance);return result;

}};

class RealFunction {public://...double Trapezoidal(double x0, double x1, double tolerance){//...do{// ...std::cout << "****" << n << " " << result << std::endl;

} while (::fabs(result - previous) >= tolerance);return result;

}};

Foram precisos 1024 intervalos para obter a precisão de 0.000001, mas teriam sido precisos só 16 para a precisão de 0.001

Page 172: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 172

Regra de SimpsonNo método do trapézio, aproximava-se a função em cada intervalo por uma recta. Na regra de Simpson, aproxima-se por uma parábola. A matemática é um pouco mais elaborada, mas as contas são igualmente simples.

x0 x1 x2

y0

y1y2

A função y=f(x) é qualquer. A área por baixo da parábola que passa nos pontos (x0, y0), (x1, y1), (x2, y2), onde x2 – x1 =x1 – x0 = h, é dada pela expressão:

Isto programa-se como o método do trapézio.

Page 173: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 173

Função Simpsonclass RealFunction {public:

//...double Simpson(double x0, double x1, double tolerance){

double fa = Function(x0);double fb = Function(x1);int n = 1; // number of intervalsdouble h = x1 - x0; // length of each intervaldouble result = (fa + fb) / 2 * h;double previous;do{

previous = result;n *= 2;h = (x1 - x0) / n;double sum = fa;for (int i = 1; i <= n-1; i++)

sum += (2 + 2 * (i % 2)) * Function(x0 + i * h);sum += fb;result = sum * h / 3;

} while (::fabs(result - previous) >= tolerance);return result;

}};

class RealFunction {public:

//...double Simpson(double x0, double x1, double tolerance){

double fa = Function(x0);double fb = Function(x1);int n = 1; // number of intervalsdouble h = x1 - x0; // length of each intervaldouble result = (fa + fb) / 2 * h;double previous;do{

previous = result;n *= 2;h = (x1 - x0) / n;double sum = fa;for (int i = 1; i <= n-1; i++)

sum += (2 + 2 * (i % 2)) * Function(x0 + i * h);sum += fb;result = sum * h / 3;

} while (::fabs(result - previous) >= tolerance);return result;

}};

A regra de Simpson converge mais depressa.

Isto é muito parecido com a função para o método do trapézio.

Repare na técnica para alternar os pesos dos termos de ordem ímpar e de ordem par: os de ordem ímpar entram quatro vezes, os de ordem par duas vezes.

Page 174: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 174

Comparando os dois métodos

void TestIntegral1(){

Sine c;std::cout << std::fixed << std::setprecision(10); double z = c.Trapezoidal(0.0, M_PI_2, 0.000001);std::cout << z << std::endl;z = c.Simpson(0.0, M_PI_2, 0.000001);std::cout << z << std::endl;

}

void TestIntegral1(){

Sine c;std::cout << std::fixed << std::setprecision(10); double z = c.Trapezoidal(0.0, M_PI_2, 0.000001);std::cout << z << std::endl;z = c.Simpson(0.0, M_PI_2, 0.000001);std::cout << z << std::endl;

}

Eis uma função de teste que chama as duas funções, Trapezoidal e Simpson:

A função Simpson foi instrumentada com uma instrução de escrita, análoga à da função Trapezoidal.

A função de Simpson precisou apenas de 32 intervalos para calcular o resultado, contra 1024 do método do trapézio.

Page 175: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 175

Sistemas de equações linearesQueremos um programa para resolver sistemas de equações lineares, como o seguinte:

O sistema será lido de um ficheiro de texto. Na primeira linha vem o número de equações e nas restantes os coeficientes do sistema, uma equação por linha. Por exemplo, o ficheiro para o sistema acima pode ser o seguinte:

31 1 1 61 2 3 143 0 -1 0

31 1 1 61 2 3 143 0 -1 0

Page 176: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 176

Método de GaussPara resolver um sistema de equações pelo método de Gauss, primeiro triangula-se o sistema, aplicando sistematicamente os princípios de equivalência, e depois resolve-se o sistema triangular, o que se pode fazer “directamente”.Para triangular, primeiro anulam-se os coeficientes da primeira coluna, excepto o primeiro, depois os da segunda coluna excepto os dois primeiros, etc., sempre mantendo a equivalência dos sistemas obtidos. Para eliminar o primeiro coeficiente da segunda equação, a1,0, subtrai-se dos coeficientes desta equação os da primeira multiplicados por a1,0/a0,0, onde ai,j representa o coeficiente j da equação i. A equação i é representada pelo vector de números ai,0, ai,1, ..., ai,N, onde N é o número de equações. O termo independente éai,N. A primeira equação é a equação zero.

Page 177: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 177

Classe LinearEquationsAgrupamos nesta classe as operações relacionadas com a resolução de sistemas de equações lineares:class LinearEquations {public:std::vector<std::vector<double> > coefficients;

void Read(std::istream& input){int n;input >> n;coefficients.clear();for (int i = 0; i < n; i++){coefficients.push_back(std::vector<double> ());for (int j = 0; j < n + 1; j++){double x;input >> x;coefficients.back().push_back(x);

}}

}

void Write(std::ostream& output){mas::WriteLine(coefficients, "\n", output);

}//...

};

class LinearEquations {public:std::vector<std::vector<double> > coefficients;

void Read(std::istream& input){int n;input >> n;coefficients.clear();for (int i = 0; i < n; i++){coefficients.push_back(std::vector<double> ());for (int j = 0; j < n + 1; j++){double x;input >> x;coefficients.back().push_back(x);

}}

}

void Write(std::ostream& output){mas::WriteLine(coefficients, "\n", output);

}//...

};

Assim se lê e assim se escreve, num formato simples, o sistema de equações.

Mais operações nas páginas seguintes.

Page 178: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 178

Eliminação para a frente

void ForwardEliminateCoefficient(int row, int column)

{

double factor = coefficients[row][column] / coefficients[column][column];

coefficients[row][column] = 0.0;

for (int j = column + 1; j < static_cast<int>(coefficients[row].size()); j++)

coefficients[row][j] -= coefficients[column][j] * factor;

}

void ForwardEliminateCoefficient(int row, int column)

{

double factor = coefficients[row][column] / coefficients[column][column];

coefficients[row][column] = 0.0;

for (int j = column + 1; j < static_cast<int>(coefficients[row].size()); j++)

coefficients[row][j] -= coefficients[column][j] * factor;

}

Vejamos a função para anular o coeficiente ai,j, com i>j, supondo que os coeficientes a0,j, a2,j,..., ai-1,j já são zero. Baseamo-nos na equação j, que é uma equação que vem antes da equação i, cujo j-ésimo coeficiente vamos anular:

Esta função elimina (isto é, anula) o coeficiente na linha row, coluna column, usando os coeficientes da linha column (!) Note que row > column, pelo que a linha column vem antes da linha row. Na linha row todos os coeficientes 0, 1, ..., column-1 já valem zero.

Anula-se directamente.

Page 179: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 179

Triangulação

A triangulação consegue-se eliminando as sucessivas colunas, excepto a última (onde já não haverá nada a fazer):

No método de Gauss, a eliminação prossegue coluna a coluna. Na coluna 1, eliminam-se todos os coeficientes excepto o primeiro, na coluna 2, todos excepto os dois primeiros, etc:

void ForwardEliminate(int column){

for (int i = column + 1; i < static_cast<int>(coefficients.size()); i++)ForwardEliminateCoefficient(i, column);

}

void ForwardEliminate(int column){

for (int i = column + 1; i < static_cast<int>(coefficients.size()); i++)ForwardEliminateCoefficient(i, column);

}

void Triangulate(){

for (int i = 0; i < static_cast<int>(coefficients.size()) - 1; i++)ForwardEliminate(i);

}

void Triangulate(){

for (int i = 0; i < static_cast<int>(coefficients.size()) - 1; i++)ForwardEliminate(i);

}

Page 180: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 180

Experimentando a triangulaçãoEis uma função de teste para observar o efeito da triangulação:

void TestTriangulate(){

std::cout << std::fixed << std::setprecision(3);LinearEquations s;std::ifstream input("sistema_01.txt");s.Read(input);s.Write(std::cout);std::cout << "------------" << std::endl;s.Triangulate();s.Write(std::cout);

}

void TestTriangulate(){

std::cout << std::fixed << std::setprecision(3);LinearEquations s;std::ifstream input("sistema_01.txt");s.Read(input);s.Write(std::cout);std::cout << "------------" << std::endl;s.Triangulate();s.Write(std::cout);

}Um exemplo com três equações...

... e

out

ro c

om c

inco

.

Primeiro escreve-se o sistema tal como lido e depois escreve-se o sistema depois de triangulado.

Page 181: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 181

Substituição para trás

void BackwardSubstitute(int row){

for (int i = row+1; i < static_cast<int>(coefficients.size()) ; i++){

coefficients[row].back() -= coefficients[row][i] * coefficients[i].back();coefficients[row][i] = 0.0;

}coefficients[row].back() /= coefficients[row][row];coefficients[row][row] = 1.0;

}

void BackwardSubstitute(int row){

for (int i = row+1; i < static_cast<int>(coefficients.size()) ; i++){

coefficients[row].back() -= coefficients[row][i] * coefficients[i].back();coefficients[row][i] = 0.0;

}coefficients[row].back() /= coefficients[row][row];coefficients[row][row] = 1.0;

}

Num sistema triangular, a última incógnita calcula-se simplesmente com uma divisão. Depois da última, pode calcular-se a penúltima, e assim por diante. É o processo de substituição para trás:

Calcula o valor da incógnita row, supondo que as incógnitas row+1, row+2, etc., já foram calculadas. Faz isto aplicando os princípios de equivalência, para que o sistema obtido continue equivalente ao original.

Anula-se directamente (em vez de explicitar a subtracção).

Atribui-se 1 directamente (em vez de fazer a divisão).

Isto é o valor da i-ésima incógnita, que já é conhecido.

Page 182: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 182

Resolução Gauss

void SolveTriangular(){

for (int i = static_cast<int>(coefficients.size()) - 1; i >= 0; i--)BackwardSubstitute(i);

}

void SolveTriangular(){

for (int i = static_cast<int>(coefficients.size()) - 1; i >= 0; i--)BackwardSubstitute(i);

}

Para resolver um sistema triangular, basta fazer as substituições para trás:

O método de Gauss para resolução de sistemas de equações lineares tem duas fases: triangulação do sistema original; resolução do sistema triangular:

void Gauss(){

Triangulate();SolveTriangular();

}

void Gauss(){

Triangulate();SolveTriangular();

}

Recorde que todas estas funções pertencem à classe LinearEquations.

Page 183: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 183

Experimentando o método de GaussEis a função de teste: void TestGauss()

{std::cout << std::fixed << std::setprecision(3);LinearEquations s;std::ifstream input("sistema_01.txt");s.Read(input);s.Write(std::cout);std::cout << "===========" << std::endl;s.Gauss();s.Write(std::cout);

}

void TestGauss(){std::cout << std::fixed << std::setprecision(3);LinearEquations s;std::ifstream input("sistema_01.txt");s.Read(input);s.Write(std::cout);std::cout << "===========" << std::endl;s.Gauss();s.Write(std::cout);

}Para o sistema com cinco equações, as soluções são 1, -1, 0, 3 e 2.

Esta é a resolução do sistema com três equações de há pouco. As soluções são 1, 2 e 3.

Repare que os sistemas a que se chega no final são sistemas diagonais unitários, isto é, sistemas em que os coeficientes da diagonal são 1 e os outros coeficientes são zero. Assim, os valores dos termos independentes são as soluções.

Page 184: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 184

A soluçãoA solução do sistema é um vector de números. Podemos recolhê-la da última coluna da matriz do sistema:

std::vector<double> Solution(){

std::vector<double> result;for (int i = 0; i < static_cast<int>(coefficients.size()); i++)

result.push_back(coefficients[i].back());return result;

}

std::vector<double> Solution(){

std::vector<double> result;for (int i = 0; i < static_cast<int>(coefficients.size()); i++)

result.push_back(coefficients[i].back());return result;

}

void TestSolution(){std::cout << std::fixed << std::setprecision(3);LinearEquations s;std::ifstream input("sistema_02.txt");s.Read(input);s.Write(std::cout);s.Gauss();std::cout << "===========" << std::endl;mas::WriteLine(s.Solution());

}

void TestSolution(){std::cout << std::fixed << std::setprecision(3);LinearEquations s;std::ifstream input("sistema_02.txt");s.Read(input);s.Write(std::cout);s.Gauss();std::cout << "===========" << std::endl;mas::WriteLine(s.Solution());

}

Experimentando:

Page 185: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 185

Problemas na triangulaçãoO que é que pode correr mal na triangulação?Se, ao eliminar uma coluna, o coeficiente da diagonal principal usado como denominador for zero, o que pode realmente acontecer, o processo descamba.

void ForwardEliminateCoefficient(int row, int column)

{

double factor = coefficients[row][column] / coefficients[column][column];

//...

}

void ForwardEliminateCoefficient(int row, int column)

{

double factor = coefficients[row][column] / coefficients[column][column];

//...

}

Logo, se for esse o caso, o melhor é trocar essa linha com outra mais abaixo que evite o problema. Aliás, do ponto de vista computacional é melhor usar na eliminação a linha cujo coeficiente da coluna a eliminar seja máximo em valor absoluto.

Se coefficients[column][column] for zero, ficamos em maus lençóis.

Page 186: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 186

Triangulação com pivô (1)Ao eliminar a coluna column (depois de ter eliminado as colunas 0, 1, ..., column-1), procura-se primeiro a equação cujo coeficiente na coluna column é maior em valor absoluto. Esse coeficiente é o pivô. Depois troca-se a linha que tem o pivô com a linha column. Eis a função que calcula o pivô:

int Pivot(int column)

{

int result = column;

for (int i = column + 1; i < static_cast<int>(coefficients.size()); i++)

if (::fabs(coefficients[result][column]) < ::fabs(coefficients[i][column]))

result = i;

return result;

}

int Pivot(int column)

{

int result = column;

for (int i = column + 1; i < static_cast<int>(coefficients.size()); i++)

if (::fabs(coefficients[result][column]) < ::fabs(coefficients[i][column]))

result = i;

return result;

}

Page 187: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 187

Triangulação com pivô (2)Observe: antes de fazer a eliminação para a frente, calculamos o pivô e trocamos as linhas, se for caso disso.

void Triangulate(){

for (int i = 0; i < static_cast<int>(coefficients.size()) - 1; i++){int p = Pivot(i);if (p != i)

coefficients[i].swap(coefficients[p]);ForwardEliminate(i);

}}

void Triangulate(){

for (int i = 0; i < static_cast<int>(coefficients.size()) - 1; i++){int p = Pivot(i);if (p != i)

coefficients[i].swap(coefficients[p]);ForwardEliminate(i);

}}

Assim, os valores que ficam na diagonal serão os maiores (em valor absoluto) que se pode arranjar.

Isto não garante que coefficients[i][i] em cada passo do ciclo, após a troca, seja diferente do que zero. Se for zero, isso significa que as equações não são linearmente independentes, e que o sistema é indeterminado ou impossível. Nesse caso, não nos interessa resolvê-lo.

Repare como se trocam as linhas. Em geral, para trocar um vector v1 com um vector v2faz-se v1.swap(v2). Isto é melhor do que fazer std::swap(v1, v2).

Page 188: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 188

Sistema impossível ou indeterminadoSe o pivô for zero, o sistema é impossível ou indeterminado. Interrompemos os cálculos lançando uma excepção:

void ForwardEliminateCoefficient(int row, int column){

if (::fabs(coefficients[column][column]) < epsilon)throw (std::string("Sistema impossível ou indeterminado."));

double factor = coefficients[row][column] / coefficients[column][column];coefficients[row][column] = 0.0;for (int j = column + 1; j < static_cast<int>(coefficients[row].size()); j++)

coefficients[row][j] -= coefficients[column][j] * factor;}

void ForwardEliminateCoefficient(int row, int column){

if (::fabs(coefficients[column][column]) < epsilon)throw (std::string("Sistema impossível ou indeterminado."));

double factor = coefficients[row][column] / coefficients[column][column];coefficients[row][column] = 0.0;for (int j = column + 1; j < static_cast<int>(coefficients[row].size()); j++)

coefficients[row][j] -= coefficients[column][j] * factor;}

Como também haverá erros devidos à imprecisão dos cálculos, é melhor interromper se o pivô for muito pequeno, menor que um valor epsilon dado. Este valor é representado por um membro dados da classe LinearEquations.

Page 189: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 189

Inicialização no construtorDeclaramos epsilon na classe e inicializamos no construtor:

class LinearEquations {public:

std::vector<std::vector<double> > coefficients;double epsilon;

LinearEquations(){

epsilon = 0.000001;}

//...};

class LinearEquations {public:

std::vector<std::vector<double> > coefficients;double epsilon;

LinearEquations(){

epsilon = 0.000001;}

//...};

Confere página 16, classe Score.

Page 190: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 190

Problemas no sistema triangularMesmo não tendo havendo problemas na triangulação, pode o último coeficiente da diagonal, coefficients[N][N] (sendo N o número de equações), ter ficado a valer zero. Isso irá causar dano na resolução do sistema triangular. Por isso, analisamos essa hipótese e lançamos uma excepção se ela se verificar.

void Gauss(){

Triangulate();if (::fabs(coefficients.back()[coefficients.size()-1]) < epsilon)

throw (std::string("Sistema impossível ou indeterminado."));SolveTriangular();

}

void Gauss(){

Triangulate();if (::fabs(coefficients.back()[coefficients.size()-1]) < epsilon)

throw (std::string("Sistema impossível ou indeterminado."));SolveTriangular();

}

Page 191: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 191

Método de Gauss-JordanO método de Gauss–Jordan é uma variante do de Gauss. Depois de triangular o sistema, diagonaliza-o, e finalmente resolve o sistema diagonal (o que é simples). A diagonalização faz-se por eliminação para trás, a partir de equações mais abaixo (no sistema). A eliminação para trás é mais simples do que a eliminação para a frente, porque não precisa procurar o pivô (porque nas equações mais abaixo apenas o elemento na diagonal não será zero), e porque em cada linha basta considerar o termo independente, uma vez que todos os restantes coeficientes excepto o da diagonal, serão zero.

Page 192: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 192

Eliminação para trásTrata-se de anular os coeficientes ai,j, para i<j, Baseamo-nos na equação j, que vem depois, no sistema, da equações i, cujos j-ésimos coeficientes vamos anular. Na equação j todos os coeficientes (não contando o termo independente) já são zero, excepto aj,j. Faz-se assim:

void BackwardEliminate(int column){

for (int i = column - 1; i >= 0; i--){

coefficients[i].back() -= coefficients[column].back()* coefficients[i][column] / coefficients[column][column];

coefficients[i][column] = 0.0;}

}

void BackwardEliminate(int column){

for (int i = column - 1; i >= 0; i--){

coefficients[i].back() -= coefficients[column].back()* coefficients[i][column] / coefficients[column][column];

coefficients[i][column] = 0.0;}

}

Page 193: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 193

Diagonalização

void Diagonalize(){

for (int i = static_cast<int>(coefficients.size()) - 1; i >= 1; i--)BackwardEliminate(i);

}

void Diagonalize(){

for (int i = static_cast<int>(coefficients.size()) - 1; i >= 1; i--)BackwardEliminate(i);

}

Diagonaliza-se a partir de um sistema triangular, por eliminação para trás de todas as colunas:

O sistema diagonal resolve-se assim:

void SolveDiagonal(){

for (int i = 0; i < static_cast<int>(coefficients.size()); i++){

coefficients[i].back() /= coefficients[i][i];coefficients[i][i] = 1.0;

}}

void SolveDiagonal(){

for (int i = 0; i < static_cast<int>(coefficients.size()); i++){

coefficients[i].back() /= coefficients[i][i];coefficients[i][i] = 1.0;

}}

Page 194: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 194

Resolução Gauss-Jordan

void GaussJordan()

{

Triangulate();

if (::fabs(coefficients.back()[coefficients.size()]) < epsilon)

throw (std::string("Sistema impossível ou indeterminado."));

Diagonalize();

SolveDiagonal();

}

void GaussJordan()

{

Triangulate();

if (::fabs(coefficients.back()[coefficients.size()]) < epsilon)

throw (std::string("Sistema impossível ou indeterminado."));

Diagonalize();

SolveDiagonal();

}

Em relação ao método de Gauss, só muda a segunda parte:

Page 195: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 195

Resolvendo sistemas múltiplosPor vezes, queremos resolver vários sistemas que só diferem nos termos independentes. Podemos resolvê-los todos ao mesmo tempo, com uma generalização do método de Gauss-Jordan.Mas primeiro temos de modificar a função de leitura:

void ReadMultiple(std::istream& input){

coefficients.clear();int n; // number of equations in each systemint m; // number of systemsinput >> n >> m;for (int i = 0; i < n; i++){

coefficients.push_back(std::vector<double> ());for (int j = 0; j < n + m; j++){

double x;input >> x;coefficients.back().push_back(x);

}}

}

void ReadMultiple(std::istream& input){

coefficients.clear();int n; // number of equations in each systemint m; // number of systemsinput >> n >> m;for (int i = 0; i < n; i++){

coefficients.push_back(std::vector<double> ());for (int j = 0; j < n + m; j++){

double x;input >> x;coefficients.back().push_back(x);

}}

}

Podemos continuar a usar a mesma função de escrita, felizmente.

Page 196: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 196

Eliminação múltipla para trásA triangulação é como antes. Só a eliminação para trás é que muda, pois é preciso tratar várias colunas de termos independentes:

void BackwardEliminateMultiple(int column){

for (int i = column - 1; i >= 0; i--){for (int j = static_cast<int>(coefficients.size());

j < static_cast<int>(coefficients[i].size()); j++)

coefficients[i][j] -= coefficients[column][j]* coefficients[i][column] / coefficients[column][column];

coefficients[i][column] = 0.0;}

}

void BackwardEliminateMultiple(int column){

for (int i = column - 1; i >= 0; i--){for (int j = static_cast<int>(coefficients.size());

j < static_cast<int>(coefficients[i].size()); j++)

coefficients[i][j] -= coefficients[column][j]* coefficients[i][column] / coefficients[column][column];

coefficients[i][column] = 0.0;}

}

O ciclo for interno trata as várias colunas de termos independentes.

Agora a diagonalização usa esta função, em vez da outra.

Page 197: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 197

Diagonalização múltiplaA diagonalização múltipla usa a eliminação múltipla para trás:

void DiagonalizeMultiple(){

for (int i = static_cast<int>(coefficients.size()) - 1; i >= 1; i--)BackwardEliminateMultiple(i);

}

void DiagonalizeMultiple(){

for (int i = static_cast<int>(coefficients.size()) - 1; i >= 1; i--)BackwardEliminateMultiple(i);

}

A resolução do sistema diagonal é simples:void SolveDiagonalMultiple(){

for (int i = 0; i < static_cast<int>(coefficients.size()); i++){for (int j = static_cast<int>(coefficients.size());

j < static_cast<int>(coefficients[i].size());j++)

coefficients[i][j] /= coefficients[i][i];coefficients[i][i] = 1.0;

}}

void SolveDiagonalMultiple(){

for (int i = 0; i < static_cast<int>(coefficients.size()); i++){for (int j = static_cast<int>(coefficients.size());

j < static_cast<int>(coefficients[i].size());j++)

coefficients[i][j] /= coefficients[i][i];coefficients[i][i] = 1.0;

}}

Page 198: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 198

Resolução múltipla Gauss-Jordan

void GaussJordanMultiple()

{

Triangulate();

if (::fabs(coefficients.back()[coefficients.size() - 1]) < epsilon)

throw (std::string("Sistema impossível ou indeterminado."));

DiagonalizeMultiple();

SolveDiagonalMultiple();

}

void GaussJordanMultiple()

{

Triangulate();

if (::fabs(coefficients.back()[coefficients.size() - 1]) < epsilon)

throw (std::string("Sistema impossível ou indeterminado."));

DiagonalizeMultiple();

SolveDiagonalMultiple();

}

Em relação ao método de Gauss-Jordan simples, só muda a segunda parte, que agora usa as funções DiagonalizeMultiplee SolveDiagonalMultiple (em vez das funções simples Diagonalize e SolveDiagonal):

Page 199: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 199

Invertendo uma matriz (1)

2 1 -3-1 3 23 1 -3

2 1 -3-1 3 23 1 -3

Podemos usar o método de Gauss-Jordan para inverter uma matriz. Por exemplo, para inverter a seguinte matriz 3*3:

basta resolver os três sistemas de equações indicados no ficheiro:

Eis o resultado na consola, mostrando os sistemas antes e depois da resolução Gauss-Jordan múltipla.

Aqui está a matriz inversa.

3 32 1 -3 1 0 0-1 3 2 0 1 03 1 -3 0 0 1

3 32 1 -3 1 0 0-1 3 2 0 1 03 1 -3 0 0 1

Page 200: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 200

Invertendo uma matriz (2)Dada a matriz num ficheiro, podemos estendê-la por programa antes da resolução múltipla Gauss-Jordan. Primeiro uma função para ler matrizes quadradas:

void ReadSquareMatrix(std::istream& input){coefficients.clear();int n;input >> n;for (int i = 0; i < n; i++){coefficients.push_back(std::vector<double> ());for (int j = 0; j < n; j++){double x;input >> x;coefficients.back().push_back(x);

}}

}

void ReadSquareMatrix(std::istream& input){coefficients.clear();int n;input >> n;for (int i = 0; i < n; i++){coefficients.push_back(std::vector<double> ());for (int j = 0; j < n; j++){double x;input >> x;coefficients.back().push_back(x);

}}

}

void Extend(){

for (int i = 0; i < static_cast<int>(coefficients.size()); i++)for (int j = 0; j < static_cast<int>(coefficients.size()); j++)

coefficients[i].push_back(static_cast<double>(i == j));}

void Extend(){

for (int i = 0; i < static_cast<int>(coefficients.size()); i++)for (int j = 0; j < static_cast<int>(coefficients.size()); j++)

coefficients[i].push_back(static_cast<double>(i == j));}

E agora a função que estende as linhas da matriz quadrada com zeros e uns, conforme necessário:

Na primeira linha vem a dimensão (número de linhas, que é igual ao número de colunas)

Repare na conversão de bool para double.

É quase igual àfunção Read inicial.

Page 201: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 201

Invertendo uma matriz (3)

void EraseColumns(int x, int n) // erase columns x, x+1, x+2, ..., x + n -1.{

for (int i = 0; i < static_cast<int>(coefficients.size()); i++)coefficients[i].erase(coefficients[i].begin() + x, coefficients[i].begin() + x + n);

}

void EraseColumns(int x, int n) // erase columns x, x+1, x+2, ..., x + n -1.{

for (int i = 0; i < static_cast<int>(coefficients.size()); i++)coefficients[i].erase(coefficients[i].begin() + x, coefficients[i].begin() + x + n);

}

Terminada a resolução, elimina-se a parte esquerda de cada linha, ficando assim a matriz inversa no lugar da matriz original. Eis uma função para eliminar colunas de uma matriz.

Repare na utilização da função erase.

Finalmente, a função Invert, que inverte a matriz dos coeficientes, substituindo-a pela sua inversa:

void Invert(){

Extend();GaussJordanMultiple();EraseColumns(0, static_cast<int>(coefficients.size()));

}

void Invert(){

Extend();GaussJordanMultiple();EraseColumns(0, static_cast<int>(coefficients.size()));

}

Page 202: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 202

Lendo a partir de strings

void TestInvert(){

std::cout << std::fixed << std::setprecision(3);LinearEquations s;std::string x = "3 2 1 -3 -1 3 2 3 1 -3";std::stringstream input(x);s.ReadSquareMatrix(input);try {

s.Invert();s.Write(std::cout);

} catch (std::string message) {

std::cout << "Erro: " << message << std::endl;}

}

void TestInvert(){

std::cout << std::fixed << std::setprecision(3);LinearEquations s;std::string x = "3 2 1 -3 -1 3 2 3 1 -3";std::stringstream input(x);s.ReadSquareMatrix(input);try {

s.Invert();s.Write(std::cout);

} catch (std::string message) {

std::cout << "Erro: " << message << std::endl;}

}

Se os coeficientes do sistema que queremos resolver forem fixos, escusamos de os ler de um ficheiro. Podemos colocá-los numa string, com esta construir uma stringstream e depois ler da stringstream (tal como antes líamos do ficheiro). Observe o seguinte exemplo:

Isto assim é mais prático do que preencher os coeficientes àmão, um a um.

Eis o resultado na consola:

Page 203: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 203

Desenhando nas janelasVamos agora aprender a escrever programas que fazem desenhos nas janelas do Windows.Primeiro problema: ao mover o rato sobre a janela queremos deixar o rasto do rato, desde que o botão esquerdo esteja em baixo.Começamos por criar um projecto visual, dos do costume. Apagamos todos os controlos da caixa de diálogo. De início temos apenas uma janela vazia onde não acontece nada:

É o projecto MouseTrack1, que gera os ficheiros MouseTrack1Dlg.h, MouseTrack1Dlg.c, MouseTrack1.h e MouseTrack1.cpp.

Page 204: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 204

Eventos do ratoQueremos detectar quando o rato se move. E, ao detectar movimento do rato, queremos colocar a negro o píxel onde o rato está.

Começamos por abrir o modo Messages nas propriedades da caixa de diálogo, seleccio-nando WM_MOUSEMOVE e adicionando a respectiva função OnMouseMove:Em resultado disso, a função aparece no ficheiro ...Dlg.cpp:

void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){

// TODO: Add your message handler code here and/or call default

CDialog::OnMouseMove(nFlags, point);}

void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){

// TODO: Add your message handler code here and/or call default

CDialog::OnMouseMove(nFlags, point);}

Page 205: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 205

Função OnMouseMoveO primeiro argumento serve para sabermos se algum dos botões do rato estava em baixo quando o evento ocorreu. Por exemplo, para ver se o botão esquerdo estava em baixo, usa-se a expressão (nFlags & MK_LBUTTON) == MK_LBUTTON, assim:void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){

if ((nFlags & MK_LBUTTON) == MK_LBUTTON){

// ...}

CDialog::OnMouseMove(nFlags, point);}

void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){

if ((nFlags & MK_LBUTTON) == MK_LBUTTON){

// ...}

CDialog::OnMouseMove(nFlags, point);}

Note bem: é & e não &&. O operador & representa a conjunção bit a bit e o operador && representa a conjunção lógica habitual.

Além de MK_LBUTTON, há MK_RBUTTON (o botão direito está em baixo?), MK_SHIFT (a tecla Shift está em baixo?) e MK_CONTROL (a tecla Control está em baixo?). Usam-se da mesma maneira.

Atenção: aqueles parêntesis suplementares são necessários porque a precedência do operador == é maior do que a do operador &.

Page 206: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 206

O contexto do dispositivoDesenhamos, não na janela directamente, mas num device context associado à janela. Observe:

void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){if ((nFlags & MK_LBUTTON) == MK_LBUTTON){CClientDC dc(this);dc.SetPixel(point, RGB(0, 0, 0));

}

CDialog::OnMouseMove(nFlags, point);}

void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){if ((nFlags & MK_LBUTTON) == MK_LBUTTON){CClientDC dc(this);dc.SetPixel(point, RGB(0, 0, 0));

}

CDialog::OnMouseMove(nFlags, point);}

A variável dc representa o device contextassociada à janela onde ocorreu o evento, sendo esta representada pelo apontador this.

A função SetPixel atribui ao píxel correspondente ao ponto indicado no primeiro argumento a cor indicada no segundo argumento.

Já podemos experimentar o programa.

A cor RGB(0, 0, 0) é o preto.

Page 207: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 207

Escrevendo com o rato na janelaTemos de escrever devagarinho:

No entanto, se ocultarmos a janela, parcialmente ou totalmente, a parte que ficar oculta, ao reaparecer vem apagada. Não é erro: é mesmo assim. Como fazer para evitar este comportamento?

Só escreve quando carregamos no botão esquerdo enquanto movemos o rato.

Page 208: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 208

Guardando os pontosEm vez de desenhar logo cada píxel, guardamos os pontos num vector:

Na função OnMouseMove acrescentamos ao vector o ponto onde o rato está:

class CMouseTrack1Dlg : public CDialog{private:

std::vector<CPoint> points;// Constructionpublic:

// ...};

class CMouseTrack1Dlg : public CDialog{private:

std::vector<CPoint> points;// Constructionpublic:

// ...};

void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){

if ((nFlags & MK_LBUTTON) == MK_LBUTTON){

points.push_back(point);// ...

}

void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){

if ((nFlags & MK_LBUTTON) == MK_LBUTTON){

points.push_back(point);// ...

}

Não esquecer o #include <vector>nos dois ficheiros .cpp.

Aqui teremos de colocar a instrução para desenhar, uma vez que agora há mais um ponto do que antes.

Page 209: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 209

RepintandoAcrescentamos à classe uma função Repaint, que se encarrega de “pintar” todos os pontos do vector, isto é, de mudar os píxeis respectivos: class CMouseTrack1Dlg : public CDialog

{// ...

public:// ...void Repaint();

};

A função Repaint usa um device context de tipo CPaintDC:

class CMouseTrack1Dlg : public CDialog{

// ...public:

// ...void Repaint();

};

void CMouseTrack1Dlg::Repaint(){

CPaintDC dc(this);for (int i = 0; i < static_cast<int>(points.size()); i++)

dc.SetPixel(points[i], RGB(0, 0, 0));}

void CMouseTrack1Dlg::Repaint(){

CPaintDC dc(this);for (int i = 0; i < static_cast<int>(points.size()); i++)

dc.SetPixel(points[i], RGB(0, 0, 0));}

Note bem: na versão inicial, o device context era de tipo CClientDC. Não confunda.

Page 210: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 210

A função OnPaintA função Repaint é chamada pela função OnPaint, a qual, por sua vez, é chamada automaticamente sempre que é preciso reafixar o conteúdo da janela:void CMouseTrack1Dlg::OnPaint() {

if (IsIconic()){// ...

}else{

Repaint();CDialog::OnPaint();

}}

void CMouseTrack1Dlg::OnPaint() {

if (IsIconic()){// ...

}else{

Repaint();CDialog::OnPaint();

}}

Nesta parte não mexemos.

Apenas acrescentamos a chamada da função Repaint.

Também deveríamos chamar a função Repaint depois de acrescentar cada ponto, na função OnMouseMove. No entanto, faremos isso indirectamente, invalidando a janela.

Tipicamente, é preciso reafixar o conteúdo da janela quando ela reaparece depois de ter sido ocultada totalmente ou parcialmente.

Page 211: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 211

InvalidandoAo invalidar uma janela, instruímos o sistema para repintar a janela por meio da função OnPaint.void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){

if ((nFlags & MK_LBUTTON) == MK_LBUTTON){

points.push_back(point);Invalidate();

}}

void CMouseTrack1Dlg::OnMouseMove(UINT nFlags, CPoint point){

if ((nFlags & MK_LBUTTON) == MK_LBUTTON){

points.push_back(point);Invalidate();

}}

Invalidamos chamando a função Invalidate.

Agora tudo funciona como esperado. Se a janela for ocultada por outra, ao reaparecer, reaparece com o mesmo aspecto.

Antes. Minimiza-se. Restaura-se.

Page 212: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 212

Desenhando com o ratoEm vez de marcar os pontos um a um, queremos unir os pontos por segmentos de recta. Não todos os pontos, claro: quando levantamos o rato, deixamos de desenhar.Começamos um novo projecto MouseDraw1, como o anterior.Agora, além do movimentos do rato, queremos detectar quando

void CMouseDraw1Dlg::OnMouseMove(UINT nFlags, CPoint point){// ...

}

void CMouseDraw1Dlg::OnLButtonDown(UINT nFlags, CPoint point){// ...

}

void CMouseDraw1Dlg::OnLButtonUp(UINT nFlags, CPoint point){// ...

}

void CMouseDraw1Dlg::OnMouseMove(UINT nFlags, CPoint point){// ...

}

void CMouseDraw1Dlg::OnLButtonDown(UINT nFlags, CPoint point){// ...

}

void CMouseDraw1Dlg::OnLButtonUp(UINT nFlags, CPoint point){// ...

}

o botão esquerdo vai abaixo e quando vem acima. Quando vai abaixo, começamos a desenhar; quando vem acima, paramos.

Mensagem WM_MOUSEMOVE.

Mensagem WM_LBUTTONDOWN.

Mensagem WM_LBUTTONUP.

Page 213: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 213

Guardando as sequências de pontosPara guardar cada sequência de pontos detectados enquanto o botão esquerdo está em baixo, usamos um vector de pontos.

class CMouseDraw1Dlg : public CDialog{private:

std::vector<std::vector<CPoint> > lines;//...};

class CMouseDraw1Dlg : public CDialog{private:

std::vector<std::vector<CPoint> > lines;//...};

Para guardar a sequência de sequências, usamos um vector de vectores de pontos:

Cada novo ponto é acrescentado ao último vector:void CMouseDraw1Dlg::OnMouseMove(UINT nFlags, CPoint point){

if ((nFlags & MK_LBUTTON) == MK_LBUTTON){

if (!lines.empty())lines.back().push_back(point);

Invalidate();}CDialog::OnMouseMove(nFlags, point);

}

void CMouseDraw1Dlg::OnMouseMove(UINT nFlags, CPoint point){

if ((nFlags & MK_LBUTTON) == MK_LBUTTON){

if (!lines.empty())lines.back().push_back(point);

Invalidate();}CDialog::OnMouseMove(nFlags, point);

}

Depois de acrescentar um ponto, invalidamos a janela.

Cada sequência de pontos constitui uma linha.

Page 214: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 214

Separando as sequênciasQuando o botão esquerdo vai abaixo, começamos uma nova sequência de pontos:void CMouseDraw1Dlg::OnLButtonDown(UINT nFlags, CPoint point){

lines.push_back(std::vector<CPoint>());lines.back().push_back(point);CDialog::OnLButtonDown(nFlags, point);

}

void CMouseDraw1Dlg::OnLButtonDown(UINT nFlags, CPoint point){

lines.push_back(std::vector<CPoint>());lines.back().push_back(point);CDialog::OnLButtonDown(nFlags, point);

}

void CMouseDraw1Dlg::OnLButtonUp(UINT nFlags, CPoint point){

if (!lines.empty() && lines.back().size() == 1)lines.erase(lines.end()-1);

CDialog::OnLButtonUp(nFlags, point);}

void CMouseDraw1Dlg::OnLButtonUp(UINT nFlags, CPoint point){

if (!lines.empty() && lines.back().size() == 1)lines.erase(lines.end()-1);

CDialog::OnLButtonUp(nFlags, point);}

Quando o botão esquerdo vai acima, apagamos a última sequência de pontos, caso tenha apenas um ponto:

Note bem: em princípio, se esta função foi chamada, já haverá pelo menos uma linha, pois antes terá sido chamada a função OnLButtonDown. No entanto, os eventos são gerados fora do controlo do nosso programa: por isso é melhor sermos prudentes.

Page 215: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 215

Desenhando a sequência de linhasA função Repaint desenha a sequência de linhas, usando as funções MoveTo, para começar a linha no início de cada sequência, e LineTo, para desenhar os segmentos determinados por dois pontos consecutivos:void CMouseDraw1Dlg::Repaint(){

CPaintDC dc(this);for (int i = 0; i < static_cast<int>(lines.size()); i++){

dc.MoveTo(lines[i][0]);for (int j = 1; j < static_cast<int>(lines[i].size()); j++)

dc.LineTo(lines[i][j]);}

}

void CMouseDraw1Dlg::Repaint(){

CPaintDC dc(this);for (int i = 0; i < static_cast<int>(lines.size()); i++){

dc.MoveTo(lines[i][0]);for (int j = 1; j < static_cast<int>(lines[i].size()); j++)

dc.LineTo(lines[i][j]);}

}

O desenho fica muito melhor e não dá muito mais trabalho. No entanto, perdemos a possibilidade de controlar a cor directamente.

Page 216: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 216

A classe SimpleGraphicsA classe SimpleGraphics contém algumas funções que simplificam a programação dos desenhos. Por exemplo, para desenhar a linha formada pelos segmentos que unem uma sequência do pontos, usaremos a função DrawPolyline:class SimpleGraphics {private:

CPaintDC dc;

// ...void DrawPolyline(const std::vector<CPoint>& points){

// ...}

};

class SimpleGraphics {private:

CPaintDC dc;

// ...void DrawPolyline(const std::vector<CPoint>& points){

// ...}

};

void CMouseDraw2Dlg::Repaint(){

SimpleGraphics sg(this);for (int i = 0; i < static_cast<int>(lines.size()); i++)

sg.DrawPolyline(lines[i]);}

void CMouseDraw2Dlg::Repaint(){

SimpleGraphics sg(this);for (int i = 0; i < static_cast<int>(lines.size()); i++)

sg.DrawPolyline(lines[i]);}

Eis a nova versão da função Repaint, usando a classe SimpleGraphics:

Estamos agora no projecto MouseDraw2.

Não é preciso explicitar o device context.

Page 217: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 217

CanetasAo desenhar cada segmento, o programa usa a caneta que estiver seleccionada no device context. A caneta especifica a cor a e grossura do traço.A classe SimpleGraphics faz a gestão das canetas automatica-mente. Apenas temos de indicar a cor (por exemplo, usando a

class SimpleGraphics {// ...

void SetPenColor(COLORREF color){

penColor = color;}

void SetPenThickness(int thickness){

penThickness = thickness;}

};

class SimpleGraphics {// ...

void SetPenColor(COLORREF color){

penColor = color;}

void SetPenThickness(int thickness){

penThickness = thickness;}

};

função RGB ou uma das cores fornecidas) na função SetPenColor e a grossura (um número inteiro), na função SetPenThickness:Cores fornecidas pela classe SimpleGraphics: black, blue, green, cyan, red, magenta, yellow, white, grey, darkGrey, darkBlue. darkGreen, darkCyan, darkRed, darkMagenta, darkYellow.

Page 218: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 218

Amarelo, grossura 5Eis um desenho a amarelo, grossura 5:

void CMouseDraw2Dlg::Repaint(){SimpleGraphics sg(this);sg.SetPenColor(sg.yellow);sg.SetPenThickness(5);for (int i = 0; i < static_cast<int>(lines.size()); i++)

sg.DrawPolyline(lines[i]);}

void CMouseDraw2Dlg::Repaint(){SimpleGraphics sg(this);sg.SetPenColor(sg.yellow);sg.SetPenThickness(5);for (int i = 0; i < static_cast<int>(lines.size()); i++)

sg.DrawPolyline(lines[i]);}

Como se fez? Definindo aqueles valores na função Repaint:

Amarelo é RGB(225, 225, 0).

Page 219: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 219

Controlando a cor e o traçoUsemos uma caixa de diálogo com controlos para definir a cor e a grossura do traço. O desenho será feito numa segunda janela, sem controlos.Eis a primeira caixa, em construção:

Page 220: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 220

As variáveis e a função do botãoAssociamos uma variável a cada botão (m_red, m_green, m_blue, m_thickness), acrescentamos à classe quatro variáveis inteiras red, green, blue e thickness para guardarem os valores, e ainda a função do botão, como de costume:class CMouseDraw3Dlg : public CDialog{public:

int red;int green;int blue;int thickness;

//...

public:CEdit m_red;CEdit m_green;CEdit m_blue;CEdit m_thickness;afx_msg void OnBnClickedButton1();

};

class CMouseDraw3Dlg : public CDialog{public:

int red;int green;int blue;int thickness;

//...

public:CEdit m_red;CEdit m_green;CEdit m_blue;CEdit m_thickness;afx_msg void OnBnClickedButton1();

};

void CMouseDraw3Dlg::OnBnClickedButton1(){

try {red = mas::GetInt(m_red);green = mas::GetInt(m_green);blue = mas::GetInt(m_blue);thickness = mas::GetInt(m_thickness);

}catch (std::string e){

mas::ErrorMessage(e);}

}

void CMouseDraw3Dlg::OnBnClickedButton1(){

try {red = mas::GetInt(m_red);green = mas::GetInt(m_green);blue = mas::GetInt(m_blue);thickness = mas::GetInt(m_thickness);

}catch (std::string e){

mas::ErrorMessage(e);}

}

Page 221: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 221

Juntando a segunda janelaVamos tratar a segunda janela como se fosse mais um controlo da primeira, com Insert Dialog, sobre o recurso que representa a caixa de diálogo principal, na árvore de recursos.Depois, removemos todos os controlos, aumentamos o tamanho, colocamos a false a propriedade System Menu(para não ter botões na barra). Veja o efeito na página seguinte.

Page 222: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 222

Classe para a segunda janelaAgora, clicando sobre a caixa de diálogo, junta-se a classe que o representa, dando-lhe o nome CPaintDlg, e fazendo com que a classe de base seja CDialog:

Atencão: é preciso mudar aqui para CDialog. O Dialog IDidentifica a caixa de diálogo.

Page 223: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 223

Abrindo a segunda janelaComeçamos por juntar uma variável àclasse da janela principal para representar a segunda janela:

O tipo é CPaintDlg, o nome é à escolha (m_paint, neste caso).

Page 224: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 224

Criando e mostrando a segunda janelaNa função OnInitDialog, inicializamos as variáveis red, green, blue e thickness, criamos a janela secundária e mostramo-la:BOOL CMouseDraw3Dlg::OnInitDialog(){

CDialog::OnInitDialog();

// Set the icon for this dialog. The framework does this automatically// when the application's main window is not a dialogSetIcon(m_hIcon, TRUE); // Set big iconSetIcon(m_hIcon, FALSE); // Set small icon

// TODO: Add extra initialization herered = 0;green = 0;blue = 0;thickness = 1;

m_paint.Create(IDD_DIALOG1, this);m_paint.ShowWindow(SW_SHOW);

return TRUE; // return TRUE unless you set the focus to a control}

BOOL CMouseDraw3Dlg::OnInitDialog(){

CDialog::OnInitDialog();

// Set the icon for this dialog. The framework does this automatically// when the application's main window is not a dialogSetIcon(m_hIcon, TRUE); // Set big iconSetIcon(m_hIcon, FALSE); // Set small icon

// TODO: Add extra initialization herered = 0;green = 0;blue = 0;thickness = 1;

m_paint.Create(IDD_DIALOG1, this);m_paint.ShowWindow(SW_SHOW);

return TRUE; // return TRUE unless you set the focus to a control}

Page 225: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 225

ExperimentandoCorrendo o programa, surgem as duas janelas:

Falta ainda juntar as capacidade de desenhar.

Page 226: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 226

Desenha-se na janela secundáriaDeclaramos o vector das linhas e preparamos as funções OnMouseMove, OnLButtonDown, OnLButtonUp, e OnPaint:

clas

s CPai

ntD

lg:

public

CD

ialo

g{ priva

te:

std::

vect

or<

std::

vect

or<

CPoin

t> >

lin

es;

//..

.};clas

s CPai

ntD

lg:

public

CD

ialo

g{ priva

te:

std::

vect

or<

std::

vect

or<

CPoin

t> >

lin

es;

//..

.};

void CPaintDlg::OnMouseMove(UINT nFlags, CPoint point){// TODO: Add your message handler code here and/or call defaultCDialog::OnMouseMove(nFlags, point);

}

void CPaintDlg::OnLButtonDown(UINT nFlags, CPoint point){// TODO: Add your message handler code here and/or call defaultCDialog::OnLButtonDown(nFlags, point);

}

void CPaintDlg::OnLButtonUp(UINT nFlags, CPoint point){// TODO: Add your message handler code here and/or call defaultCDialog::OnLButtonUp(nFlags, point);

}

void CPaintDlg::OnPaint(){CPaintDC dc(this); // device context for painting// TODO: Add your message handler code here// Do not call CDialog::OnPaint() for painting messages

}

void CPaintDlg::OnMouseMove(UINT nFlags, CPoint point){// TODO: Add your message handler code here and/or call defaultCDialog::OnMouseMove(nFlags, point);

}

void CPaintDlg::OnLButtonDown(UINT nFlags, CPoint point){// TODO: Add your message handler code here and/or call defaultCDialog::OnLButtonDown(nFlags, point);

}

void CPaintDlg::OnLButtonUp(UINT nFlags, CPoint point){// TODO: Add your message handler code here and/or call defaultCDialog::OnLButtonUp(nFlags, point);

}

void CPaintDlg::OnPaint(){CPaintDC dc(this); // device context for painting// TODO: Add your message handler code here// Do not call CDialog::OnPaint() for painting messages

}

Aqui a mensagem é WM_PAINT.

Page 227: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 227

Desenha-se como antesAs funções OnMouseMove, OnLButtonDown, OnLButton-Up são como antes:void CPaintDlg::OnMouseMove(UINT nFlags, CPoint point){if ((nFlags & MK_LBUTTON) == MK_LBUTTON){if (!lines.empty())lines.back().push_back(point);

Invalidate();}CDialog::OnMouseMove(nFlags, point);

}

void CPaintDlg::OnMouseMove(UINT nFlags, CPoint point){if ((nFlags & MK_LBUTTON) == MK_LBUTTON){if (!lines.empty())lines.back().push_back(point);

Invalidate();}CDialog::OnMouseMove(nFlags, point);

}void CPaintDlg::OnLButtonDown(UINT nFlags, CPoint point){lines.push_back(std::vector<CPoint>());lines.back().push_back(point);CDialog::OnLButtonDown(nFlags, point);

}

void CPaintDlg::OnLButtonDown(UINT nFlags, CPoint point){lines.push_back(std::vector<CPoint>());lines.back().push_back(point);CDialog::OnLButtonDown(nFlags, point);

}void CPaintDlg::OnLButtonUp(UINT nFlags, CPoint point){if (!lines.empty() && lines.back().size() == 1)lines.erase(lines.end()-1);

CDialog::OnLButtonUp(nFlags, point);}

void CPaintDlg::OnLButtonUp(UINT nFlags, CPoint point){if (!lines.empty() && lines.back().size() == 1)lines.erase(lines.end()-1);

CDialog::OnLButtonUp(nFlags, point);}

Compare com as páginas 213 e 214.

Page 228: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 228

OnPaint e RepaintA função OnPaint apenas chama a função Repaint:void CPaintDlg::OnPaint(){//CPaintDC dc(this); // device context for painting //comment this out! pg// TODO: Add your message handler code hereRepaint();// Do not call CDialog::OnPaint() for painting messages

}

void CPaintDlg::OnPaint(){//CPaintDC dc(this); // device context for painting //comment this out! pg// TODO: Add your message handler code hereRepaint();// Do not call CDialog::OnPaint() for painting messages

}

void CPaintDlg::Repaint(){

CMouseDraw3Dlg *pWnd = dynamic_cast<CMouseDraw3Dlg *>(GetParent());SimpleGraphics sg(this);sg.SetPenColor(RGB(pWnd->red, pWnd->green, pWnd->blue));sg.SetPenThickness(pWnd->thickness);for (int i = 0; i < static_cast<int>(lines.size()); i++)

sg.DrawPolyline(lines[i]);}

void CPaintDlg::Repaint(){

CMouseDraw3Dlg *pWnd = dynamic_cast<CMouseDraw3Dlg *>(GetParent());SimpleGraphics sg(this);sg.SetPenColor(RGB(pWnd->red, pWnd->green, pWnd->blue));sg.SetPenThickness(pWnd->thickness);for (int i = 0; i < static_cast<int>(lines.size()); i++)

sg.DrawPolyline(lines[i]);}

A função Repaint, que pertence à classe CPaintDlg, precisa das variáveis da janela principal. Observe como se faz:

Tecnicamente, pWnd é um apontador para a janela principal. A janela principal é a janela-pai da janela secundária.

Note bem: é preciso apagar (ou comentar) a declaração do device context que aparece automaticamente.

Repare no dynamic_cast, para “acertar” os tipos. No Visual C++, épreciso mudar a propriedade Enable Run-Time Type Info para Yes.

Page 229: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 229

Invalidando no botãoPara que o desenho mude quando mudam os valores nos controlos de edição, invalidamos no botão:void CMouseDraw3Dlg::OnBnClickedButton1(){

try {red = mas::GetInt(m_red);green = mas::GetInt(m_green);blue = mas::GetInt(m_blue);thickness = mas::GetInt(m_thickness);m_paint.Invalidate();

}catch (std::string e){

mas::ErrorMessage(e);}

}

void CMouseDraw3Dlg::OnBnClickedButton1(){

try {red = mas::GetInt(m_red);green = mas::GetInt(m_green);blue = mas::GetInt(m_blue);thickness = mas::GetInt(m_thickness);m_paint.Invalidate();

}catch (std::string e){

mas::ErrorMessage(e);}

}

Está pronto!

Page 230: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 230

Tudo OK?Primeiro a preto, grossura 1:

Depois mudando a cor e a grossura:

Desenhando mais um pouco:

Page 231: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 231

Retoques finaisÉ melhor que os campos da caixa de diálogo de controlo apareçam com os valores iniciais, em vez de virem em branco. Tratamos disso na função OnInitDialog (confere página 224):BOOL CMouseDraw3Dlg::OnInitDialog(){CDialog::OnInitDialog();// ...

// TODO: Add extra initialization herered = 0;green = 0;blue = 0;thickness = 1;mas::SetInt(m_red, red);mas::SetInt(m_green, green);mas::SetInt(m_blue, blue);mas::SetInt(m_thickness, thickness);

m_paint.Create(IDD_DIALOG1, this);m_paint.ShowWindow(SW_SHOW);

return TRUE; // return TRUE unless you set the focus to a control}

BOOL CMouseDraw3Dlg::OnInitDialog(){CDialog::OnInitDialog();// ...

// TODO: Add extra initialization herered = 0;green = 0;blue = 0;thickness = 1;mas::SetInt(m_red, red);mas::SetInt(m_green, green);mas::SetInt(m_blue, blue);mas::SetInt(m_thickness, thickness);

m_paint.Create(IDD_DIALOG1, this);m_paint.ShowWindow(SW_SHOW);

return TRUE; // return TRUE unless you set the focus to a control}

Page 232: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 232

Problema das vacasUm pastor está a guardar vacas num prado. No meio do prado há uma elevação poligonal. Dadas as posições do pastor, das vacas e os vértices do polígono da base da elevação, quantas vacas vê o pastor e quantas ficam ocultas atrás da elevação?Exemplo

150 150

4

50 50

150 200

400 200

200 50

3

150 100

350 100

250 300

150 150

4

50 50

150 200

400 200

200 50

3

150 100

350 100

250 300

<150, 100> <350, 100>

<250, 300>

<150, 150>

<150, 200>

<50, 50>

<400, 200>

<200, 50>

A vermelho o pastor, a azul as vacas e a preto a elevação.

Eis o ficheiro que descreve esta pastagem.

Page 233: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 233

Componente gráficaQueremos não só calcular mas também desenhar a configura-ção da pastagem. O pastor será representado por um “p”, cada vaca por um “v” e a elevação pela linha poligonal respectiva. Isto aparecerá na janela secundária. A janela principal, de controlo (como no programa MouseDraw3) terádois botões: um para escolher, abrir e ler o ficheiro, outro para desencadear os cálculos.Montamos um projecto Cows1, com duas janelas, como anteriormente. De início, não faz nada:

Mantemos os nomes da classe, CPaintDlg, e da variável, m_paint.

Page 234: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 234

Classe para o campoA classe Field representa o campo com a pastagem: tem um pastor, um vector de vacas e um vector de vértices (para o polígono):

class Field {public:

std::vector<CPoint> cowboys;std::vector<CPoint> cows;std::vector<CPoint> rock;//...

}

class Field {public:

std::vector<CPoint> cowboys;std::vector<CPoint> cows;std::vector<CPoint> rock;//...

}

void Read(std::istream& input){cowboys.resize(1);input >> cowboys[0].x >> cowboys[0].y;cows.clear();int countCows;input >> countCows;for (int i = 0; i < countCows; i++){int x;int y;input >> x >> y;cows.push_back(CPoint(x, y));

}rock.clear();int countVertices;input >> countVertices;for (int i = 0; i < countVertices; i++){int x;int y;input >> x >> y;rock.push_back(CPoint(x, y));

}}

void Read(std::istream& input){cowboys.resize(1);input >> cowboys[0].x >> cowboys[0].y;cows.clear();int countCows;input >> countCows;for (int i = 0; i < countCows; i++){int x;int y;input >> x >> y;cows.push_back(CPoint(x, y));

}rock.clear();int countVertices;input >> countVertices;for (int i = 0; i < countVertices; i++){int x;int y;input >> x >> y;rock.push_back(CPoint(x, y));

}}

Preenchemos estes membros de dados por leitura de ficheiro:

O vector cowboys terá um sóelemento. Usamos um vector e não uma variável simples, porque simplifica a função que desenha.

Page 235: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 235

Desenhando o campoA classe Field tem uma função Draw que desenha o campo na janela passada em argumento:

void Draw(CWnd* pWnd){

SimpleGraphics sg(pWnd);sg.DrawPolygon(rock);for (int i = 0; i < static_cast<int>(cows.size()); i++)

sg.DrawText("v", cows[i]);for (int i = 0; i < static_cast<int>(cowboys.size()); i++)

sg.DrawText("p", cowboys[i]);}

void Draw(CWnd* pWnd){

SimpleGraphics sg(pWnd);sg.DrawPolygon(rock);for (int i = 0; i < static_cast<int>(cows.size()); i++)

sg.DrawText("v", cows[i]);for (int i = 0; i < static_cast<int>(cowboys.size()); i++)

sg.DrawText("p", cowboys[i]);}

Usa a classe SimpleGraphics. A função DrawPolygon desenha o polígono formado pelos pontos presentes no argumento, que éum vector de pontos.

Note bem: a classe CWnd é a classe que representa as janelas em geral. O argumento pWnd é de tipo CWnd*, isto é, apontador para CWnd.

Desenharia vários pastores, se houvesse mais do que um.

Page 236: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 236

OnPaint, RepaintNa classe CPaintDlg:

void CPaintDlg::OnPaint(){

//CPaintDC dc(this); // device context for painting // This must be commented out!// TODO: Add your message handler code here// Do not call CDialog::OnPaint() for painting messagesRepaint();

}

void CPaintDlg::OnPaint(){

//CPaintDC dc(this); // device context for painting // This must be commented out!// TODO: Add your message handler code here// Do not call CDialog::OnPaint() for painting messagesRepaint();

}

void CPaintDlg::Repaint(){

CCows1Dlg *pWnd = dynamic_cast<CCows1Dlg*>(GetParent());pWnd->field.Draw(this);

}

void CPaintDlg::Repaint(){

CCows1Dlg *pWnd = dynamic_cast<CCows1Dlg*>(GetParent());pWnd->field.Draw(this);

} Desenhar nesta janela o campo fieldque está declarado na janela-pai.

A função OnPaint surge na classe CPaintDlg via mensagem WM_PAINT.

Repare: a função GetParent é uma função da biblioteca MFC (não confunda com a biblioteca STL) que devolve um apontador para a janela-pai. Esse apontador é de tipo CWnd*. Ora aqui nós precisamos de um apontador CCows1Dlg*, pois, neste caso, a janela-pai é de tipo CCows1Dlg. Acertamos os tipos usando o dynamic_cast<CCows1Dlg*>. (Confere com a página 228.)

Page 237: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 237

Abrindo o ficheiroPara seleccionar o ficheiro com a descrição da pastagem, usamos a função mas::SelectFileForReading():void CCows1Dlg::OnBnClickedButton1(){

// TODO: Add your control notification handler code herestd::string fileName = mas::SelectFileForReading();if (!fileName.empty()){

std::ifstream input(fileName.c_str());if (!input)

mas::ErrorMessage("File not available.");else{field.Read(input);m_paint.Invalidate();

}}

}

void CCows1Dlg::OnBnClickedButton1(){

// TODO: Add your control notification handler code herestd::string fileName = mas::SelectFileForReading();if (!fileName.empty()){

std::ifstream input(fileName.c_str());if (!input)

mas::ErrorMessage("File not available.");else{field.Read(input);m_paint.Invalidate();

}}

}

A função SelectFileForReading vem no ficheiro UtilitiesVisual.h, que é preciso incluir no CCows1Dlg. Também há uma função SelectFileForWriting que se usa da mesma maneira.

Feita a leitura, invalida-se a janela secundária, para que o desenho apareça

Page 238: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 238

Já corre!Usamos o botão Abrir... para seleccionar o ficheiro field_00.txt que contém a descrição da pastagem.

Após a leitura, o desenho aparecerá na janela secundária.

Page 239: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 239

O desenho vem de pernas para o ar E além disso, o desenho não aparece todo (falta uma vaca). Seráque podemos esticar a janela? Podemos, mas temos de mudar a propriedade Border, para Resizing. (Ver página seguinte.)

O interior do polígono é preenchido com a cor actual do pincel (brush). Mudamos a cor do pincel com SetBrushColor, na classe SimpleGraphics.

Page 240: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 240

Esticando a janela Muda-se para Resizing nas propriedades:

Quando a janela muda de tamanho (mensagem WM_SIZE), invalida-se o desenho:

void CPaintDlg::OnSize(UINT nType, int cx, int cy){

CDialog::OnSize(nType, cx, cy);// TODO: Add your message handler code hereInvalidate();

}

void CPaintDlg::OnSize(UINT nType, int cx, int cy){

CDialog::OnSize(nType, cx, cy);// TODO: Add your message handler code hereInvalidate();

}

Page 241: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 241

As vacas todasEsticando a janela, já conseguimos ver as quatro vacas:

O desenho está de pernas para o ar porque o y cresce para baixo, claro.

x

y

Page 242: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 242

Gráficos euclideanosUsando a classe EuclideanGraphics, em vez de SimpleGraphics, o desenho já sai na posição certa. Observe:void Draw(CWnd* pWnd){

EuclideanGraphics eg(pWnd);eg.DrawPolygon(rock);for (int i = 0; i < static_cast<int>(cows.size()); i++)eg.DrawText("v", cows[i]);

for (int i = 0; i < static_cast<int>(cowboys.size()); i++)eg.DrawText("p", cowboys[i]);

eg.DrawAxes();}

void Draw(CWnd* pWnd){

EuclideanGraphics eg(pWnd);eg.DrawPolygon(rock);for (int i = 0; i < static_cast<int>(cows.size()); i++)eg.DrawText("v", cows[i]);

for (int i = 0; i < static_cast<int>(cowboys.size()); i++)eg.DrawText("p", cowboys[i]);

eg.DrawAxes();}

Está na posição certa, mas parece muito pequeno. Porquê?Porque o desenho é escalado automaticamente, usando a extensão da janela.

A classe EuclideanGraphics herda da classe SimpleGraphics. Assim, tem tudo o que esta tem, e mais algumas coisas.

A classe Euclidean-Graphics desenha em modo isotrópico, com a mesma escala nos dois eixos e a coordenada ycrescendo para cima.

Page 243: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 243

A extensão da janelaA extensão da janela é a dimensão lógica (isto é, em coordenadas do desenho e não em coordenadas do ecrã) da largura da janela, se for menor do que a altura, ou da altura, se for menor do que a largura.Por defeito a extensão é 1000. Por isso o desenho é pequeno. Uma extensão 500 seria mais apropriada:

void Draw(CWnd* pWnd){

EuclideanGraphics eg(pWnd, 500);eg.DrawPolygon(rock);for (int i = 0; i < static_cast<int>(cows.size()); i++)eg.DrawText("v", cows[i]);

for (int i = 0; i < static_cast<int>(cowboys.size()); i++)eg.DrawText("p", cowboys[i]);

}

void Draw(CWnd* pWnd){

EuclideanGraphics eg(pWnd, 500);eg.DrawPolygon(rock);for (int i = 0; i < static_cast<int>(cows.size()); i++)eg.DrawText("v", cows[i]);

for (int i = 0; i < static_cast<int>(cowboys.size()); i++)eg.DrawText("p", cowboys[i]);

}A origem, ponto de coordenadas (0, 0) está no canto inferior esquerdo.

Page 244: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 244

Escalamento automáticoSe esticarmos a janela, o desenho é escalado automatica-mente, atendendo à extensão. Todas estas janelas estão com extensão

500. (Mas note nas duas maiores é a altura que determina a extensão, pois émenor do que a largura.)

Page 245: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 245

Origem no centro da janelaQuerendo, podemos colocar a origem no centro da janela. Observe, o seguinte desenho, onde se usa a função DrawAxespara desenhar os eixos:

void Draw(CWnd* pWnd){

EuclideanGraphics eg(pWnd, 800, true);eg.DrawPolygon(rock);for (int i = 0; i < static_cast<int>(cows.size()); i++)eg.DrawText("v", cows[i]);

for (int i = 0; i < static_cast<int>(cowboys.size()); i++)eg.DrawText("p", cowboys[i]);

eg.DrawAxes();}

void Draw(CWnd* pWnd){

EuclideanGraphics eg(pWnd, 800, true);eg.DrawPolygon(rock);for (int i = 0; i < static_cast<int>(cows.size()); i++)eg.DrawText("v", cows[i]);

for (int i = 0; i < static_cast<int>(cowboys.size()); i++)eg.DrawText("p", cowboys[i]);

eg.DrawAxes();}

O comprimento dos eixos desenhados é igual àextensão, 800. (Neste caso, a extensão corresponde àaltura, pois a janela é mais larga do que alta.)

A origem estáno centro da janela

Page 246: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 246

Transformações gráficas básicasSão três:

• Translação• Escalamento• Rotação*

*Rotação em torno da origem.

Page 247: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 247

TranslaçãoCada translação é especificada por um deslocamento horizontal, dx, e um deslocamento vertical, dy. O ponto de coordenadas <x, y> transforma-se no ponto de coordenadas <x+dx, y+dy>.Eis a função que realiza esta operação sobre um ponto de tipo CPoint:

void Translate(CPoint& p, int dx, int dy){

p.x += dx;p.y += dy;

}

void Translate(CPoint& p, int dx, int dy){

p.x += dx;p.y += dy;

}

Aquele & a seguir ao CPoint que indica o tipo do argumento p significa que o valor desse argumento vai ser modificado pela função. Sem o & as modificações descritas (p.x += dx; py += dy;) acabariam por não ter efeito. Note muito bem: o & éindispensável em casos como este.

Esta função e as das páginas seguintes vêm no ficheiro GraphicalTransformations.h e pertencem ao espaço de nomes mas.

Page 248: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 248

EscalamentoCada escalamento é especificado por um factor de escala horizontal, fx, e um factor de escala vertical, fy. O ponto de coordenadas <x, y> transforma-se no ponto de coordenadas <x*fx, y*fy>.Eis a função que realiza esta operação sobre um ponto de tipo CPoint:void Scale(CPoint& p, double fx, double fy){

p.x = Round(p.x * fx);p.y = Round(p.y * fy);

}

void Scale(CPoint& p, double fx, double fy){

p.x = Round(p.x * fx);p.y = Round(p.y * fy);

} int Round(double x){int result = static_cast<int>(::fabs(x) + 0.5);if (x < 0)result = -result;

return result;}

int Round(double x){int result = static_cast<int>(::fabs(x) + 0.5);if (x < 0)result = -result;

return result;}

Como as coordenadas são números inteiros e os factores de escala são números reais, temos de arredondar:

Page 249: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 249

RotaçãoTrata-se da rotação em torno da origem, especificada pelo ângulo (de rotação). O ângulo é indicado em radianos ou em graus. Eis as funções:void Rotate(CPoint& p, double radians){

int x0 = p.x;int y0 = p.y;p.x = Round(x0 * ::cos(radians) - y0 * ::sin(radians));p.y = Round(x0 * ::sin(radians) + y0 * ::cos(radians));

}

void Rotate(CPoint& p, double radians){

int x0 = p.x;int y0 = p.y;p.x = Round(x0 * ::cos(radians) - y0 * ::sin(radians));p.y = Round(x0 * ::sin(radians) + y0 * ::cos(radians));

}

void RotateDegrees(CPoint& p, int degrees){

Rotate(p, degrees * M_PI / 180);}

void RotateDegrees(CPoint& p, int degrees){

Rotate(p, degrees * M_PI / 180);}

Para poder usar a constante simbólica M_PI é preciso fazer#define _USE_MATH_DEFINES

antes do #include <cmath>.

Page 250: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 250

Transformando vectores de pontosPara transladar, escalar ou rodar um vector de pontos, trans-lada-se, escala-se ou roda-se cada um dos pontos do vector:void Translate(std::vector<CPoint>& v, int dx, int dy){for (int i = 0; i < static_cast<int>(v.size()); i++)Translate(v[i], dx, dy);

}

void Translate(std::vector<CPoint>& v, int dx, int dy){for (int i = 0; i < static_cast<int>(v.size()); i++)Translate(v[i], dx, dy);

}void Scale(std::vector<CPoint>& v, double fx, double fy){for (int i = 0; i < static_cast<int>(v.size()); i++)Scale(v[i], fx, fy);

}

void Scale(std::vector<CPoint>& v, double fx, double fy){for (int i = 0; i < static_cast<int>(v.size()); i++)Scale(v[i], fx, fy);

} void Rotate(std::vector<CPoint>& v, double radians){for (int i = 0; i < static_cast<int>(v.size()); i++)Rotate(v[i], radians);

}

void RotateDegrees(std::vector<CPoint>& v, int degrees){Rotate(v, degrees * M_PI / 180);

}

void Rotate(std::vector<CPoint>& v, double radians){for (int i = 0; i < static_cast<int>(v.size()); i++)Rotate(v[i], radians);

}

void RotateDegrees(std::vector<CPoint>& v, int degrees){Rotate(v, degrees * M_PI / 180);

}

Page 251: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 251

Programa de demonstraçãoEscrevamos um programa para demonstrar as três transfor-mações gráficas básicas. Temos uma janela de controlo, com três botões (um para cada operação) e controlos de edição para os argumentos, e uma janela para o desenho. Usamos um triângulo, inicialmente com vértices nos pontos <100, 100>,

<100, 300> , <200, 200> , desenhado na janela secundária.

Page 252: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 252

O triânguloO triângulo é representado por um vector de pontos, na classe CTriangles1Dlg:

class CTriangles1Dlg : public CDialog{public:std::vector<CPoint> triangle;

// ...

class CTriangles1Dlg : public CDialog{public:std::vector<CPoint> triangle;

// ...

Nesta classe, também estão declaradas as variáveis para os controlos de edição:

// ...public:CEdit m_dx;CEdit m_dy;CEdit m_fx;CEdit m_fy;CEdit m_degrees;

// ...

E igualmente as variáveis com os valores dos argumentos das transformações:

// ...public:CEdit m_dx;CEdit m_dy;CEdit m_fx;CEdit m_fy;CEdit m_degrees;

// ...

// ...int dx;int dy;double fx;double fy;int degrees;bool dataOK;

// ...

// ...int dx;int dy;double fx;double fy;int degrees;bool dataOK;

// ...

A variável booleana dataOK valerátrue quando os valores recebidos dos controlos de edição forem válidos.

Page 253: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 253

Desenhando o triânguloDesenhamos o triângulo na classe da janela secundária, CPaintDlg:void CPaintDlg::OnPaint(){//CPaintDC dc(this); // device context for painting// TODO: Add your message handler code here// Do not call CDialog::OnPaint() for painting messagesRepaint();

}

void CPaintDlg::OnPaint(){//CPaintDC dc(this); // device context for painting// TODO: Add your message handler code here// Do not call CDialog::OnPaint() for painting messagesRepaint();

}void CPaintDlg::Repaint(){EuclideanGraphics eg(this, 1000, true);CTriangles1Dlg *pWnd = dynamic_cast<CTriangles1Dlg*>(GetParent());eg.DrawPolygon(pWnd->triangle);eg.DrawAxes();

}

void CPaintDlg::Repaint(){EuclideanGraphics eg(this, 1000, true);CTriangles1Dlg *pWnd = dynamic_cast<CTriangles1Dlg*>(GetParent());eg.DrawPolygon(pWnd->triangle);eg.DrawAxes();

}void CPaintDlg::OnSize(UINT nType, int cx, int cy){CDialog::OnSize(nType, cx, cy);// TODO: Add your message handler code hereInvalidate();

}

void CPaintDlg::OnSize(UINT nType, int cx, int cy){CDialog::OnSize(nType, cx, cy);// TODO: Add your message handler code hereInvalidate();

}

Como de costume, no nosso estilo de programação, o message handler OnPaint chama a função Repaint, a qual, ela sim, faz o desenho.

Acrescentamos logo o message handler OnSize, para redesenhar quando a janela muda de tamanho.

Desenhamos em modo isotrópico, com extensão 1000 e origem no centro da janela.

Page 254: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 254

As funções dos botõesSão todas semelhantes:

void CTriangles1Dlg::OnBnClickedButton1(){// TODO: Add your control notification handler code hereGetData();if (dataOK){mas::Translate(triangle, dx, dy);m_paint.Invalidate();

}}

void CTriangles1Dlg::OnBnClickedButton1(){// TODO: Add your control notification handler code hereGetData();if (dataOK){mas::Translate(triangle, dx, dy);m_paint.Invalidate();

}}

Recorrem à função GetDataque recolhe os valores dos cinco controlos de edição. (Ver página seguinte.)

void CTriangles1Dlg::OnBnClickedButton2(){// TODO: Add your control notification handler code hereGetData();if (dataOK){mas::Scale(triangle, fx, fy);m_paint.Invalidate();

}}

void CTriangles1Dlg::OnBnClickedButton2(){// TODO: Add your control notification handler code hereGetData();if (dataOK){mas::Scale(triangle, fx, fy);m_paint.Invalidate();

}} void CTriangles1Dlg::OnBnClickedButton3()

{// TODO: Add your control notification handler code hereGetData();if (dataOK){mas::RotateDegrees(triangle, degrees);m_paint.Invalidate();

}}

void CTriangles1Dlg::OnBnClickedButton3(){// TODO: Add your control notification handler code hereGetData();if (dataOK){mas::RotateDegrees(triangle, degrees);m_paint.Invalidate();

}}

Page 255: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 255

Recolhendo os valores nas caixasA função GetData trata de recolher todos os valores presentes nas caixas de edição. Em caso de erro, detectado por uma excepção levantada na função mas::GetInt ou na função ::GetDouble, deixa a variável dataOK a false:

void CTriangles1Dlg::GetData(){

dataOK = false;try {

dx = mas::GetInt(m_dx);dy = mas::GetInt(m_dy);fx = mas::GetDouble(m_fx);fy = mas::GetDouble(m_fy);degrees = mas::GetInt(m_degrees);dataOK = true;

}catch (std::string e){

mas::ErrorMessage(e);}

}

void CTriangles1Dlg::GetData(){

dataOK = false;try {

dx = mas::GetInt(m_dx);dy = mas::GetInt(m_dy);fx = mas::GetDouble(m_fx);fy = mas::GetDouble(m_fy);degrees = mas::GetInt(m_degrees);dataOK = true;

}catch (std::string e){

mas::ErrorMessage(e);}

}

Quando um dos valores nas caixas de edição estiver errado, aparece a correspondente mensagem de erro

Page 256: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 256

Experimentando

Original Translação: dx = −50dy = 50.

Escalamento:fx = 1.5fy = 2.

Escalamento:fx = 0.5fy = −1

Translação: dx = 0dy = 100

Rotação: degrees = 90

Rotação: degrees = 45

Rotação: degrees = −120

Page 257: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 257

O problema dos degrausÀ porta de casa do João há uma escada com 10 degraus. João é um rapaz ginasticado e consegue subir as escadas com passadas de um degrau ou de dois degraus. De quantas maneiras diferentes pode o João subir a escada?Para o problema ser mais interessante, vamos considerar que a escada tem N degraus e que a passada máxima do João é P. O formulário do programa seráassim:O problema é equivalente a descobrir de quantas maneiras diferentes podemos adicionar 1 e 2, várias vezes cada, somando 10. Pensemos nisso e nas somas parciais. Por exemplo 1+2+2+1+1+2+1 = 10 corresponde às somas parciais 0, 1, 3, 5, 6, 7, 9, 10. Todas as somas parciais começam em zero e acabam em 10, e cada uma delas representa uma maneira de subir as escadas.

Page 258: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 258

O grafo dos degrausSe o João estiver no degrau X, para que degraus pode subir? Para o degrau X+1 e para o degrau X+2 (se estes valores não forem maiores que 10). O rés-do-chão corresponde ao degrau 0 e o último degrau é o degrau 10.Podemos, portanto, desenhar o grafo dos degraus:

0

1

2

3 4 5

6

7

8

910

O problema consiste em contar os caminhos desde o vértice 0 até ao vértice 10.

Note que agora o grafo é“orientado”: a seta de 3 para 4, por exemplo, significa que se pode passar de 3 para 4. A ligação de 4 para 3 teria que ser indicada por outra seta.

Estamos a simplifi-car, fixando a pas-sada máxima em 2.

Page 259: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 259

Construindo o grafoProgramamos na classe Steps:

class Steps {public:

int steps;int jump;

std::vector<std::list<int> > allPaths;

std::vector<std::vector<int> > graph;

Steps(int steps, int jump);void MakeGraph();

};

class Steps {public:

int steps;int jump;

std::vector<std::list<int> > allPaths;

std::vector<std::vector<int> > graph;

Steps(int steps, int jump);void MakeGraph();

}; void Steps::MakeGraph(){

graph.clear();graph.resize(steps+1);for (int i = 0; i <= steps; i++)

for (int j = 1; j <= jump && i + j <= steps; j++)graph[i].push_back(i+j);

}

void Steps::MakeGraph(){

graph.clear();graph.resize(steps+1);for (int i = 0; i <= steps; i++)

for (int j = 1; j <= jump && i + j <= steps; j++)graph[i].push_back(i+j);

}

Steps::Steps(int steps, int jump){

this->steps = steps;this->jump = jump;

}

Steps::Steps(int steps, int jump){

this->steps = steps;this->jump = jump;

}

O vértice i está ligado aos vértices i+1, ..., i+jump, desde que estes valores não sejam superiores a steps.

Usamos o construtor para inicializar os membros de dados steps e jump:

Repare: this->stepsé o membro de dados; steps é o argumento.

As declarações estão no ficheiro .h; as definições estão no ficheiro .cpp.

Page 260: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 260

Calculando os caminhosUsamos a técnica da busca em profundidade, com a função Clone, invocada na função ComputeAllPaths:

class Steps {public:

//...void ComputeAllPaths();void ComputeAllPaths(int x, int y);void Clone(int x, int y, std::list<int> w);

};

class Steps {public:

//...void ComputeAllPaths();void ComputeAllPaths(int x, int y);void Clone(int x, int y, std::list<int> w);

};

void Steps::ComputeAllPaths(){

ComputeAllPaths(0, steps);}

void Steps::ComputeAllPaths(){

ComputeAllPaths(0, steps);}

void Steps::ComputeAllPaths(int x, int y){

allPaths.clear();Clone(x, y, std::list<int>());

}

void Steps::ComputeAllPaths(int x, int y){

allPaths.clear();Clone(x, y, std::list<int>());

}

void Steps::Clone (int x, int y, std::list<int> w){

w.push_back(x);if (x == y)

allPaths.push_back(w);else

for (int i = 0; i < static_cast<int>(graph[x].size()); i++)//if (!mas::Find(w, graph[x][i]))Clone(graph[x][i], y, w);

}

void Steps::Clone (int x, int y, std::list<int> w){

w.push_back(x);if (x == y)

allPaths.push_back(w);else

for (int i = 0; i < static_cast<int>(graph[x].size()); i++)//if (!mas::Find(w, graph[x][i]))Clone(graph[x][i], y, w);

}

É desnecessário verificar se o vértice graph[x][i] pertence ao caminho w em construção: não pertence, de certeza, dada a forma do grafo, só com arestas “para a frente”.

Sem argumentos, calcula do vértice 0 até ao último vértice.

Page 261: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 261

A função do botãoRecolhe os valores dos controlos de edição, inicializa um objecto de tipos Steps, constrói o grafo, gera os caminhos e mostra quantos caminhos há:

void CStepsVisualDlg::OnBnClickedButton1(){

try {steps = mas::GetInt(m_steps);jump = mas::GetInt(m_jump);Steps s(steps, jump);s.MakeGraph();s.ComputeAllPaths();mas::SetInt(m_result, static_cast<int>(s.allPaths.size()));

}catch (std::string e){

mas::ErrorMessage(e);}

}

void CStepsVisualDlg::OnBnClickedButton1(){

try {steps = mas::GetInt(m_steps);jump = mas::GetInt(m_jump);Steps s(steps, jump);s.MakeGraph();s.ComputeAllPaths();mas::SetInt(m_result, static_cast<int>(s.allPaths.size()));

}catch (std::string e){

mas::ErrorMessage(e);}

}

Na verdade, não é preciso calcular explicitamente os caminhos para os contar. Podemos contar directamente, adaptando a função Clone.

Page 262: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 262

Contando os caminhosEis as funções para contar directamente:

class Steps {public:

//...int countPaths;

void CountPaths();void CountPaths(int x, int y);void CloneSimple(int x, int y);

};

class Steps {public:

//...int countPaths;

void CountPaths();void CountPaths(int x, int y);void CloneSimple(int x, int y);

};

void Steps::CountPaths(){

CountPaths(0, steps);}

void Steps::CountPaths(){

CountPaths(0, steps);}

void Steps::CountPaths(int x, int y){

countPaths = 0;CloneSimple(x, y);

}

void Steps::CountPaths(int x, int y){

countPaths = 0;CloneSimple(x, y);

}

void Steps::CloneSimple(int x, int y){

if (x == y)countPaths++;

elsefor (int i = 0; i < static_cast<int>(graph[x].size()); i++)

CloneSimple(graph[x][i], y);}

void Steps::CloneSimple(int x, int y){

if (x == y)countPaths++;

elsefor (int i = 0; i < static_cast<int>(graph[x].size()); i++)

CloneSimple(graph[x][i], y);}

Usar-se-iam estas funções na função do botão, se não fosse ainda mais simples dispensar o grafo. (Ver página seguinte.)

Fica bem mais simples:

Page 263: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 263

Dispensando o grafoAfinal, vendo bem, podemos até dispensar o grafo, obser-vando que no ciclo for as únicas ligações que se aproveitam são as de x para x+1, ..., x+jump. Fazemos isso numa nova classe, StepsSimple:class StepsSimple {public:int steps;int jump;

std::vector<std::list<int> > allPaths;int countPaths;

StepsSimple(int steps, int jump);

void ComputeAllPaths();void ComputeAllPaths(int x, int y);void Clone(int x, int y, std::list<int> w);

void CountPaths();void CountPaths(int x, int y);void CloneSimple(int x, int y);

};

class StepsSimple {public:int steps;int jump;

std::vector<std::list<int> > allPaths;int countPaths;

StepsSimple(int steps, int jump);

void ComputeAllPaths();void ComputeAllPaths(int x, int y);void Clone(int x, int y, std::list<int> w);

void CountPaths();void CountPaths(int x, int y);void CloneSimple(int x, int y);

};

void StepsSimple::Clone(int x, int y, std::list<int> w){w.push_back(x);if (x == y)allPaths.push_back(w);

elsefor (int i = 1; i <= jump && x + i <= steps; i++)

Clone(x + i, y, w);}

void StepsSimple::Clone(int x, int y, std::list<int> w){w.push_back(x);if (x == y)allPaths.push_back(w);

elsefor (int i = 1; i <= jump && x + i <= steps; i++)

Clone(x + i, y, w);}

void StepsSimple::CloneSimple(int x, int y){if (x == y)countPaths++;

elsefor (int i = 1; i <= jump && x + i <= steps; i++)

CloneSimple(x + i, y);}

void StepsSimple::CloneSimple(int x, int y){if (x == y)countPaths++;

elsefor (int i = 1; i <= jump && x + i <= steps; i++)

CloneSimple(x + i, y);}

As outras funções são como na outra classe.

Page 264: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 264

Estudando o número de caminhosVejamos como varia o número de caminhos, quando a passada máxima é 2, num projecto de consola:void TestStepsMany(){

int x;std::cin >> x;for (int i = 0; i <= x; i++){

StepsSimple ss(i, 2);ss.CountPaths(0, i);std::cout << i << " " << ss.countPaths << std::endl;

}}

void TestStepsMany(){

int x;std::cin >> x;for (int i = 0; i <= x; i++){

StepsSimple ss(i, 2);ss.CountPaths(0, i);std::cout << i << " " << ss.countPaths << std::endl;

}}

Cada número é a soma dos dois anteriores, como na sequência de Fibonacci.

Page 265: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 265

Números de Fibonacci

class Fibonacci {public:int At(int x){return x == 0 ? 0 : x == 1 ? 1 : At(x-2) + At(x-1);

}};

class Fibonacci {public:int At(int x){return x == 0 ? 0 : x == 1 ? 1 : At(x-2) + At(x-1);

}};

Programamos na classe Fibonacci:

Todos os programadores devem conhecer a função de Fibonacci, assim definida:

Note bem: esta função não dá exacta-mente o mesmo que a da página anterior: aqui At(0) é 0. Antes, para i = 0 o número de caminhos é 1. Ou seja, o número de caminho quando há x degraus é At(x+1).

Fibonacci(0) = 0Fibonacci(1) = 1Fibonacci(x) = Fibonacci(x – 2) + Fibonacci(x – 1), se x ≥ 2

Page 266: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 266

Tabelando os números de Fibonacci

class Fibonacci {public:

std::vector<int> sequence;

void Compute(int x){

sequence.clear();sequence.push_back(0);sequence.push_back(1);for (int i = 2; i < x; i++)

sequence.push_back(sequence[i-2] + sequence[i-1]);}

int At(int x){

return x == 0 ? 0 : x == 1 ? 1 : At(x-2) + At(x-1);}

};

class Fibonacci {public:

std::vector<int> sequence;

void Compute(int x){

sequence.clear();sequence.push_back(0);sequence.push_back(1);for (int i = 2; i < x; i++)

sequence.push_back(sequence[i-2] + sequence[i-1]);}

int At(int x){

return x == 0 ? 0 : x == 1 ? 1 : At(x-2) + At(x-1);}

};

Juntemos à classe uma tabela onde colocamos os números, calculados todos de seguida:

Se precisarmos de muitos números de Fibonacci, o melhor é usar a tabela. Se precisarmos só de um, podemos usar a função At.

void TestFibonacci_2(){

Fibonacci fib;int x;std::cin >> x;fib.Compute(x);mas::WriteLine(fib.sequence);

}

void TestFibonacci_2(){

Fibonacci fib;int x;std::cin >> x;fib.Compute(x);mas::WriteLine(fib.sequence);

}

void TestFibonacci_1(){

Fibonacci fib;for (;;){

int x;std::cin >> x;if (!std::cin)

break;std::cout << fib.At(x) << std::endl;

}}

void TestFibonacci_1(){

Fibonacci fib;for (;;){

int x;std::cin >> x;if (!std::cin)

break;std::cout << fib.At(x) << std::endl;

}}

Page 267: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 267

Problemas dos degraus, de novoO problema é contar as maneiras diferentes de subir as escadas. Podemos abordá-lo de outra maneira. Seja F(x) o número de maneiras diferentes de subir uma escada de xdegraus, com uma passada máxima de 2. Suponhamos que já resolvemos o problema para todas as escadas com 1, 2, ..., x − 1 degraus, e que portanto conhecemos F(1), F(2), ..., F(x − 1). Então quanto vale F(x)? Ora bem, para subir x degraus, ou se sobem x − 1 degraus e depois mais um, ou se sobem x − 2 degraus e depois mais dois (de uma vez). Ou seja: F(x) = F(x − 1) + F(x − 2). Além disso, F(1) = 1e F(2) = 2, nitidamente. Logo: F(x) = fib.At(x+1), sendo fib um objecto de tipo Fibonacci.

Page 268: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 268

PermutaçõesQueremos gerar todas as permutações de um conjunto com Nelementos, representados pelos números inteiros 0, 1, ..., N-1.Cada permutação é um caminho de comprimento N, no grafo com N vértices 0, 1, ..., N-1, em que cada vértice está ligado a todos os outros. O caminho não tem vértices repetidos.

0

1

2 3

4

5

Tal como no problema dos degraus, podemos dispensar a construção explícita do grafo. Para cada vértice, construímos todos caminhos que partem dele, com comprimento N. Assim, não há propriamente vértice de chegada: o caminho acaba quando atinge o comprimento N.

Page 269: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 269

Classe Permutationsclass Permutations {public:

std::vector<std::list<int> > permutations;int n;

Permutations(int n){this->n = n;for (int i = 0; i < n; i++)Clone(i, std::list<int>());

}

void Clone(int x, std::list<int> w){

w.push_back(x);if (static_cast<int>(w.size()) == n)

permutations.push_back(w);else

for (int i = 0; i < n; i++)if (!mas::Find(w, i))Clone(i, w);

}

};

class Permutations {public:

std::vector<std::list<int> > permutations;int n;

Permutations(int n){this->n = n;for (int i = 0; i < n; i++)Clone(i, std::list<int>());

}

void Clone(int x, std::list<int> w){

w.push_back(x);if (static_cast<int>(w.size()) == n)

permutations.push_back(w);else

for (int i = 0; i < n; i++)if (!mas::Find(w, i))Clone(i, w);

}

};

void TestPermutations(){int x;std::cin >> x;Permutations p(x);mas::WriteLine(p.permutations, "\n");

}

void TestPermutations(){int x;std::cin >> x;Permutations p(x);mas::WriteLine(p.permutations, "\n");

}

Aqui evita-se que o caminho passe duas vezes pelo mesmo vértice.

Cada vértice está ligado a todos os outros (incluindo o próprio).

Se o comprimento for n, este caminho chegou ao fim.

Page 270: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 270

CombinaçõesQueremos gerar todas as combinações de um conjunto com Nelementos, representados pelos números inteiros 0, 1, ..., N-1, com K elementos tomados de cada vez: combinações de N, K a K.Cada combinação é um caminho de comprimento K, no grafo com N vértices 0, 1, ..., N-1, em que cada vértice i está ligado unidireccionalmente aos vértices i+1, i+2, ..., N-1.

0

1

2 3

4

5

Dispensamos a construção explícita do grafo. Para cada vértice, construímos todos os caminhos que partem dele, com comprimento K. Como nas permutações, não hávértice de chegada: o caminho acaba quando atinge o comprimento K.

Page 271: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 271

Combinações com repetiçãoNas combinações com repetição, um dado elemento pode aparecer várias vezes. No grafo, em cada vértice há um laço:

Tratamos os dois casos em conjunto com a classe Combinations.

0

1

2 3

4

5

Page 272: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 272

Classe Combinationsclass Combinations {public:

std::vector<std::list<int> > combinations;int n;int k;bool repetition;

Combinations(int n, int k, bool repetition = false){this->n = n;this->k = k;this->repetition = repetition;for (int i = 0; i < n; i++)Clone(i, std::list<int>());

}

void Clone(int x, std::list<int> w){

w.push_back(x);if (static_cast<int>(w.size()) == k)

combinations.push_back(w);else

for (int i = x+!repetition; i < n; i++)Clone(i, w);

}

};

class Combinations {public:

std::vector<std::list<int> > combinations;int n;int k;bool repetition;

Combinations(int n, int k, bool repetition = false){this->n = n;this->k = k;this->repetition = repetition;for (int i = 0; i < n; i++)Clone(i, std::list<int>());

}

void Clone(int x, std::list<int> w){

w.push_back(x);if (static_cast<int>(w.size()) == k)

combinations.push_back(w);else

for (int i = x+!repetition; i < n; i++)Clone(i, w);

}

};

Não é preciso verificar se o vértice pertence ao caminho (nem faria sentido, se for com repetição) pois não há setas para trás.

Se repetition valer false, então !repetition vale true e o vértice i estáligado aos vértices i+1, i+2, ... n –1. Se repetition valer true, então o vértice iestá ligado a si próprio.

Na ausência do terceiro argumento, ou se for false, as combinações são sem repetição

Page 273: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 273

Gerando as combinações

void TestCombinations(){

int x;int y;std::cin >> x >> y;Combinations c(x, y);mas::WriteLine(c.combinations, "\n");

}

void TestCombinations(){

int x;int y;std::cin >> x >> y;Combinations c(x, y);mas::WriteLine(c.combinations, "\n");

}

void TestCombinationsWithRepetition(){

int x;int y;std::cin >> x >> y;Combinations c(x, y, true);mas::WriteLine(c.combinations, "\n");

}

void TestCombinationsWithRepetition(){

int x;int y;std::cin >> x >> y;Combinations c(x, y, true);mas::WriteLine(c.combinations, "\n");

}

Sem repetição:

Com repetição:

Há mais do que estas: a última é <5 5 5 5>.

Page 274: A Verdadeira Programação Um - Moodle @ FCTUNL · Escrevemos isto a seguir à classe, no ficheiro M_Score.cpp. Observe a técnica: declaramos s como sendo um objecto de tipo Score

20-02-2005 Programação I © Pedro Guerreiro 2003 274

Subconjuntos

class Subsets {public:

std::vector<std::list<int> > subsets;int n;

Subsets(int n){this->n = n;for (int i = 0; i < n; i++)Clone(i, std::list<int>());

}

void Clone(int x, std::list<int> w){

w.push_back(x);subsets.push_back(w);for (int i = x + 1; i < n; i++)Clone(i, w);

}

};

class Subsets {public:

std::vector<std::list<int> > subsets;int n;

Subsets(int n){this->n = n;for (int i = 0; i < n; i++)Clone(i, std::list<int>());

}

void Clone(int x, std::list<int> w){

w.push_back(x);subsets.push_back(w);for (int i = x + 1; i < n; i++)Clone(i, w);

}

};

Para gerar todos os subconjuntos não vazios do conjunto [0..N-1], podemos adaptar o procedimento

void TestSubsets(){int x;std::cin >> x;Subsets s(x);mas::WriteLine(s.subsets, "\n");

}

void TestSubsets(){int x;std::cin >> x;Subsets s(x);mas::WriteLine(s.subsets, "\n");

}

das combinações sem repetição, de maneira a apro-veitar todos os caminhos parciais.