a verdadeira e Única programação dois · implementação chama-se x.cpp. isto é uma convenção...

254
20-02-2005 Programação II © Pedro Guerreiro 2004 1 A Verdadeira e Única Programação Dois 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 http://ctp.di.fct.unl.pt/lei/p2/ Março-Junho de 2004

Upload: others

Post on 20-Jul-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 1

A Verdadeira e Única Programação Dois

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

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

Março-Junho de 2004

Page 2: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 2

Objectivos do C++

Ser um C melhor.Suportar a programação com tipos abstractos.Suportar a programação orientada pelos objectos.Suportar a programação genérica.

Logo, a linguagem C++ pode ser usada de várias maneiras, com várias ênfases.

A linguagem de programação C++ foi inventada por Bjarne Stroustrup, nos laboratórios Bell, com o objectivo de:

Uma linguagem tão ambiciosa, com objectivos tão vastos, provavelmente écomplicada

Page 3: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 3

Bibliografia GeralThe C++ Programming Language (3rd edition), Bjarne Stroustrup, 1997.

Programação com Classes em C++, …, 2003.

STL Tutorial and Reference Guide, David Musser, GillmerDerge, Atul Saini, 2001.

Page 4: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 4

Bibliografia ComplementarIntroduction to Algorithms, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein, 2001.Object-Oriented Software Construction, Bertrand Meyer, 1997.Elementos de Programação com C, ..., 2001.

Page 5: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 5

O C++ e o CO C++ baseia-se no C. O C é um subconjunto do C++. O C++ é muito mais do que o C.Precisamos aprender C antes de aprender C++? Não. (Provavelmente, até é melhor nem saber C ao aprender C++...)O C++ tem as mesmas construções linguísticas básicas do que o C: funções, variáveis, tipos, expressões, instruções, operadores, input-output, bibliotecas.Quais são as novidades?• As classes e os conceitos relacionados: objectos, herança,

funções virtuais, polimorfismo, classes genéricas...• Melhoramentos em relação ao C: o operador new, input-

output seguro, referências, funções inline, excepções, ...

Page 6: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 6

O que é uma classe?Em C++ uma classe é uma estrutura com membros de dados e membros funcionais. Os membros funcionais operam nos membros de dados. Alguns membros da classe são públicos, outros são privados.

Exemplo: class Point {private:double x;double y;

public:Point();Point(double x, double y);Point(const Point& other);virtual ~Point();

virtual void Translate(double dx, double dy);virtual void Scale(double fx, double fy);virtual void Rotate(double angle);virtual double DistanceTo(const Point& other) const;

};

class Point {private:double x;double y;

public:Point();Point(double x, double y);Point(const Point& other);virtual ~Point();

virtual void Translate(double dx, double dy);virtual void Scale(double fx, double fy);virtual void Rotate(double angle);virtual double DistanceTo(const Point& other) const;

};

Isto é a declaração da classe Point.

Membros de dados

Membros funcionais

Regra: os membros de dados são privados; os membros funcionais são públicos (quase sempre.)

Page 7: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 7

Tipos básicosNúmeros inteiros: tipo int.

Números reais: tipo double.

Booleanos: tipo bool.

Caracteres: tipo char.

E ainda:

Cadeias de caracteres: tipo std::string.

Os tipos numéricos (int e double) têm os operadores aritméticos usuais: +, -, *, /. No tipo int, a divisão é a divisão inteira, no tipo doubleé a divisão exacta. No tipo int há ainda o operador % para o resto da divisão inteira.

O tipo bool tem os operadores && (conjunção lógica), || disjunção lógica) e ! (negação).

Na verdade o tipo char também é um tipo numérico...

Em rigor, este não é um tipo básico. É sim uma classe da biblioteca STL.

Page 8: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 8

As classes são tiposA classe Point é usada para declarar objectos de tipo Point.

Um pontoPoint p1;

Point p2;

Point a[10];

std::vector<Point> b;

Point p1;

Point p2;

Point a[10];

std::vector<Point> b;

Outro ponto

Um quadro (array) com 10 pontos, indexados de 0 a 9.

Com os objectos de tipo ponto usam-se as funções da classe Point. Observe a sintaxe:

p1.Translate(2.0, -3.25);

double x = p2.DistanceTo(p1);

a[0].Rotate(1.57079632679489661923);

a[3].Scale(-1.0, 1.0);

a[5] = p2;

b.push_back(Point(-1.0, 3.5));

p1.Translate(2.0, -3.25);

double x = p2.DistanceTo(p1);

a[0].Rotate(1.57079632679489661923);

a[3].Scale(-1.0, 1.0);

a[5] = p2;

b.push_back(Point(-1.0, 3.5));

Isto é apenas um exemplo parvo. Não éparte de nenhum programa significativo.

Um vector de pontos, por enquanto sem elementos.

Page 9: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 9

As funções têm um objectoA funções da classe Point actuam sobre um objecto da classe Point. Em cada caso, esse objecto é o objecto da função.

p1.Translate(2.0, -3.25);

double x = p2.DistanceTo(p1);

a[0].Rotate(1.57079632679489661923);

a[3].Scale(-1.0, 1.0);

a[5] = p2;

b.push_back(Point(-1.0, 3.5));

p1.Translate(2.0, -3.25);

double x = p2.DistanceTo(p1);

a[0].Rotate(1.57079632679489661923);

a[3].Scale(-1.0, 1.0);

a[5] = p2;

b.push_back(Point(-1.0, 3.5));

p1 é o objecto, 2.0 e –3-25 são os argumentos. O valor de p1 muda.

O objecto da função é quem “sofre” o efeito da função.

p2 é o objecto, p1 é o argumento. O resultado da função é guardado na variável x, aqui declarada.

a[0] é o objecto, 1.57... é o argumento. O valor de a[0] muda.

a[3] é o objecto, -1.0 e 1.0 são os argumentos. O valor de a[3] muda.

Tecnicamente a afectação na classe Point é uma função. O objecto é o ponto do lado esquerdo.

A função push_back não é da classe Point, mas sim da classe std::vector<Point>, neste caso. O objecto é o vector de pontos b.

Page 10: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 10

A classe PointHá quatro qualidades de funções membro:

class Point {private:double x;double y;

public:Point();Point(double x, double y);Point(const Point& other);virtual ~Point();

virtual double X() const;virtual double Y() const;

virtual void Translate(double dx, double dy);virtual void Scale(double fx, double fy);virtual void Rotate(double angle);virtual double DistanceTo(const Point& other) const;

virtual double Angle() const;virtual double Modulus() const;

virtual void Write() const;virtual void WriteLine() const;

};

class Point {private:double x;double y;

public:Point();Point(double x, double y);Point(const Point& other);virtual ~Point();

virtual double X() const;virtual double Y() const;

virtual void Translate(double dx, double dy);virtual void Scale(double fx, double fy);virtual void Rotate(double angle);virtual double DistanceTo(const Point& other) const;

virtual double Angle() const;virtual double Modulus() const;

virtual void Write() const;virtual void WriteLine() const;

};

• Construtores• Destrutor• Selectores• Modificadores

Construtores

Destrutor

Selectores

Modificadores

Mais selectores

Page 11: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 11

ConstrutoresCada classe tem um ou vários construtores.Os construtores têm o mesmo nome do que a classe. São usados para inicializar os objectos, sempre que um objecto da classe é criado.

// ...

Point();

Point(double x, double y);

Point(const Point& other);

// ...

// ...

Point();

Point(double x, double y);

Point(const Point& other);

// ...

Este é o construtor por defeito: inicializa com zero, zero.

Este é o construtor elementar: inicializa com x, y.

Este é o construtor de cópia: inicializa com os valores dos membros de dados do argumento other.

Exemplo: Point p;Point q(3.0, 4.0);Point r(q);Point z[32];

Point p;Point q(3.0, 4.0);Point r(q);Point z[32];

Inicialização por defeito.Inicialização elementar.Inicialização por cópia.

Os 32 elementos do quadro z são inicializados por defeito.

Page 12: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 12

DestrutorCada classe tem um destrutor.O destrutor de uma classe é chamado implicitamente quando um objecto da classe é destruído. Nós programamos o destrutor, mas não o chamamos.

// ...

virtual ~Point();

// ...

// ...

virtual ~Point();

// ...

O nome do destrutor numa classe X é ~X.

Note bem: em rigor, não é o destrutor que “destrói” o objecto. O objecto é destruído por um outro meio e o destrutor éinvocado automaticamente nessa altura para “arrumar a casa”.

Regra: todos os destrutores são declarados virtual.Os destrutores servem para libertar a memória que foi alocada dinamicamente pelo objecto durante a sua existência, no momento em que o objecto está prestes a deixar de existir.

Page 13: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 13

SelectoresOs selectores são as funções da classe que consultam o estado do objecto, sem o modificar.

Os nomes das funções sugerem o seu significado. Note que as funções X e Y são indispensáveis para consultar os valores dos membros de dados x e y, que são privados.

// ...virtual double X() const;virtual double Y() const;// ...virtual double Angle() const;virtual double Modulus() const;virtual double DistanceTo(const Point& other) const;

virtual void Write() const;virtual void WriteLine() const;

// ...virtual double X() const;virtual double Y() const;// ...virtual double Angle() const;virtual double Modulus() const;virtual double DistanceTo(const Point& other) const;

virtual void Write() const;virtual void WriteLine() const;

Regra: todos os selectores são declarados virtual e const.

Page 14: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 14

ModificadoresOs modificadores são as funções da classe que modificam o estado do objecto. Não devolvem qualquer informação.

Regra: todos os modificadores são declarados virtual e retornam void.

Os nomes das funções sugerem o seu significado.

// ...virtual void Translate(double dx, double dy);virtual void Scale(double fx, double fy);virtual void Rotate(double angle);// ...

// ...virtual void Translate(double dx, double dy);virtual void Scale(double fx, double fy);virtual void Rotate(double angle);// ...

Retornar void significa não retornar nada. Usando uma nomenclatura mais convencional, as funções C++ que retornam void são procedimentos, e as funções C++ que retornam valores mesmo são funções no sentido habitual.

Page 15: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 15

Programação das classesCada classe C++ ocupa dois ficheiros. O ficheiro da declaração e o ficheiro da implementação.

Além dos ficheiros das classes, cada programa mais um ficheiro onde reside a função main. Um programa C++ começa pela primeira instrução da função main e termina quando a função main retorna.

Para uma classe X, o ficheiro da declaração chama-se X.h e o ficheiro de implementação chama-se X.cpp.

Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer.

O ficheiro da declaração contém apenas a declaração da classe, na forma que já observámos. O ficheiro de implementação contém as definições das funções. Ainda não vimos isso. Na gíria do inglês, o ficheiro de declaração é o header file e o de

implementação o source file.

Page 16: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 16

Exemplo: ponto médioEscrever um programa (uma função main) que aceite as coordenadas de dois pontos, construa os pontos e calcule o ponto médio do segmento por eles formado. No final, para confirmar que a geometria não é uma batata, mostra a distância do ponto médio a cada um dos dois pontos iniciais (!)

int main(){double x;double y;std::cout << "Coordenadas do primeiro ponto: ";std::cin >> x >> y;Point p1(x, y);std::cout << "Coordenadas do segundo ponto: ";std::cin >> x >> y;Point p2(x, y);Point pm((p1.X() + p2.X()) / 2, (p1.Y() + p2.Y()) / 2);std::cout << "Ponto médio: ";pm.WriteLine();double d1 = p1.DistanceTo(pm);double d2 = p2.DistanceTo(pm);std::cout << "Distâncias: " << d1 << " " << d2 << std::endl;return 0;

}

int main(){double x;double y;std::cout << "Coordenadas do primeiro ponto: ";std::cin >> x >> y;Point p1(x, y);std::cout << "Coordenadas do segundo ponto: ";std::cin >> x >> y;Point p2(x, y);Point pm((p1.X() + p2.X()) / 2, (p1.Y() + p2.Y()) / 2);std::cout << "Ponto médio: ";pm.WriteLine();double d1 = p1.DistanceTo(pm);double d2 = p2.DistanceTo(pm);std::cout << "Distâncias: " << d1 << " " << d2 << std::endl;return 0;

}

Infelizmente, as letras acentuadas e os cês cedilhados saem mal na consola

Page 17: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 17

Ficheiros de inclusãoPara podermos usar a classe Point no nosso programa, temos de incluir o ficheiro Point.h. Isso faz-se por meio das directivas #include, no início do ficheiro:

#include <iostream>

#include "Point.h"

int main(){

int x;int y;// ...Point p1(x, y);// ...std::cout << "Distâncias: " << d1 << " " << d2 << std::endl;

}

#include <iostream>

#include "Point.h"

int main(){

int x;int y;// ...Point p1(x, y);// ...std::cout << "Distâncias: " << d1 << " " << d2 << std::endl;

}

Este outro #include corresponde a um ficheiro do sistema e é necessário para podermos ler e escrever na consola usando aqueles operadores << e >>.

O ficheiro que contém este programa chamar-se-áM_Point.cpp. Havendo vários parecidos, numeramos: M_Point_1.cpp, M_Point_2.cpp, etc.

Page 18: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 18

Exercícios 11. Escreva uma função main que aceite as coordenadas de

dois pontos e calcule os dois pontos que dividem o segmento definido pelos dois primeiros em três partes iguais.

2. Escreva uma função main que aceite dois números reais representando a parte real e a parte imaginária de um número complexo e calcule, por intermédio da classe Point, a parte real e a parte imaginária da raiz quadrada desse número complexo. Investigue no ficheiro Point.cpp para descobrir como é que se calcula a raiz quadrada de um número double. Não se esqueça do ficheiro de inclusão <cmath>.

3. Escreva uma função main para calcular os vértices do triângulo equilátero com centro na origem e base horizontal, dado o comprimento do lado.

Page 19: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 19

Passagem de argumentos

Escolher uma ou outra é uma decisão de desenho, que habitualmente não interfere com o significado da função.

Em C++, os argumentos podem passar de três maneiras: por valor, por referência e por referência constante.class Point {

...

virtual double DistanceTo(Point other) const;

...

};

class Point {

...

virtual double DistanceTo(Point other) const;

...

}; class Point {

...

virtual double DistanceTo(Point& other) const;

...

};

class Point {

...

virtual double DistanceTo(Point& other) const;

...

}; class Point {

...

virtual double DistanceTo(const Point& other) const;

...

};

class Point {

...

virtual double DistanceTo(const Point& other) const;

...

};

Passagem por valor.

Passagem por referência.

Passagem por referência constante.

Page 20: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 20

Por valor, por referência• Quando um argumento passa por valor, a função trabalha

sobre uma cópia do valor do argumento. Logo, mesmo que o argumento seja uma variável, nada que a função possa fazer muda o valor dessa variável.

• Quando um argumento passa por referência, a função trabalha directamente sobre a variável que constitui o argumento. Poupa-se o trabalho de copiar o valor da variável e ganha-se a possibilidade de modificar esse valor. Mas o argumento tem mesmo de ser uma variável, não pode ser uma expressão.

• Quando um argumento passa por referência constante, écomo no caso anterior, excepto que renunciamos explicitamente ao direito de modificar o valor da variável. (Se inadvertidamente o fizermos, o compilador dá erro.) Além disso, se o argumento for uma expressão, o compilador gera automaticamente uma variável com o valor da expressão.

Page 21: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 21

Passagem de argumentos: regra práticaOs argumentos de um tipo simples (int, double, char, bool, apontador) passam por valor:

virtual void Scale(double fx, double fy);

virtual void Rotate(double angle);

A passagem por referência não constante usa-se em casos particulares, por exemplo, quando os argumentos representam ficheiros:

virtual void Scale(double fx, double fy);

virtual void Rotate(double angle);

virtual double DistanceTo(const Point& other) const;virtual double DistanceTo(const Point& other) const;

virtual void Write(std::ostream& output = std::cout) const;

virtual void WriteLine(std::ostream& output = std::cout) const;

virtual void Read(std::istream& input = std::cin);

virtual void Write(std::ostream& output = std::cout) const;

virtual void WriteLine(std::ostream& output = std::cout) const;

virtual void Read(std::istream& input = std::cin);

Os argumentos de um tipo classe passam por referência constante:

Ao passar sempre por valor ou por referência constante, garantimos que o valor dos argumento não muda. Para mudar os valores de variáveis usamos ou a afectação ou um modificador da classe respectiva.

Page 22: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 22

Argumentos por defeitoOcasionalmente, aparecem nas classes funções que têm argumentos por defeito:

virtual void Write(std::ostream& output = std::cout) const;

virtual void WriteLine(std::ostream& output = std::cout) const;

virtual void Read(std::istream& input = std::cin);

virtual void Write(std::ostream& output = std::cout) const;

virtual void WriteLine(std::ostream& output = std::cout) const;

virtual void Read(std::istream& input = std::cin);

Se uma função destas for chamada sem argumento, usa-se o argumento por defeito. Por exemplo:

Point p;

...

p.WriteLine();

Point p;

...

p.WriteLine();

Point p;

...

p.WriteLine(std::cout);

Point p;

...

p.WriteLine(std::cout);é o mesmo do que

Outro exemplo: se quiséssemos que por defeito as rotações fossem de 90 graus, declararíamos:

virtual void Rotate(double angle = 1.57079632679489661923);virtual void Rotate(double angle = 1.57079632679489661923);

Não abusar! Às vezes complica mais do que simplifica.

Page 23: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 23

Definindo as funçõesAs funções da classe Point são definidas no ficheiro Point.cpp.

double Point::X() const{return x;

}

double Point::Y() const{return y;

}

void Point::Translate(double dx, double dy){x += dx;y += dy;

}

void Point::Scale(double fx, double fy){x *= fx;y *= fy;

}

void Point::Rotate(double angle){double x0 = x;double y0 = y;x = x0 * ::cos(angle) - y0 * ::sin(angle);y = x0 * ::sin(angle) + y0 * ::cos(angle);

}

double Point::X() const{return x;

}

double Point::Y() const{return y;

}

void Point::Translate(double dx, double dy){x += dx;y += dy;

}

void Point::Scale(double fx, double fy){x *= fx;y *= fy;

}

void Point::Rotate(double angle){double x0 = x;double y0 = y;x = x0 * ::cos(angle) - y0 * ::sin(angle);y = x0 * ::sin(angle) + y0 * ::cos(angle);

}

Isto é apenas uma parte do ficheiro. Há mais funções além destas.

Ao definir as funções temos de qualificar com o nome da classe: Point::X(), Point::Scale(), etc.

O qualificador virtual não reaparece na definição.

::sin(...) e ::cos(...): funções de biblioteca para o seno e coseno. Requerem#include <cmath>.

Page 24: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 24

A instrução return

Avalia a expressão e termina a função onde ocorre, devolvendo o valor calculado.

return expressão;

Sintaxe: Semântica:

double Point::X() const{return x;

}

double Point::X() const{return x;

}

double Point::DistanceTo(const Point& other) const{return ::sqrt(::pow(x - other.x, 2) + ::pow(y - other.y, 2));

}

double Point::Angle() const{return ::atan2(y, x);

}

double Point::Modulus() const{return ::sqrt(x*x + y*y);

}

double Point::DistanceTo(const Point& other) const{return ::sqrt(::pow(x - other.x, 2) + ::pow(y - other.y, 2));

}

double Point::Angle() const{return ::atan2(y, x);

}

double Point::Modulus() const{return ::sqrt(x*x + y*y);

}

::sqrt(x): raiz quadrada de x. ::atan2(y, x): arcotangentede y/x.::pow(x, y): x elevado a y.

Page 25: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 25

Instrução de afectação

Avalia a expressão após o que o valor calculado passa a constituir o valor da variável.

variável = expressão;

Sintaxe: Semântica:

void Point::Set(double x, double y){this->x = x;this->y = y;

}

void Point::Rotate(double angle){double x0 = x;double y0 = y;x = x0 * ::cos(angle) - y0 * ::sin(angle);y = x0 * ::sin(angle) + y0 * ::cos(angle);

}

void Point::Set(double x, double y){this->x = x;this->y = y;

}

void Point::Rotate(double angle){double x0 = x;double y0 = y;x = x0 * ::cos(angle) - y0 * ::sin(angle);y = x0 * ::sin(angle) + y0 * ::cos(angle);

}

Atenção: this->x é o membro de dados, x sozinho é o argumento.

class Point {

...

virtual void Set(double x, double y);

...

};

class Point {

...

virtual void Set(double x, double y);

...

};

Evitaríamos o this usando argumentos com nomes diferentes dos dos membros de dados.

Mais uma função...

Page 26: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 26

Afectações mistasNormalmente, a variável e a expressão são do mesmo tipo. As únicas excepções razoáveis a esta regra envolvem os tipos simples:

int n;

double x;

bool b;

char c;

...

x = n;

n = x;

n = b;

b = n;

n = c;

c = n;

int n;

double x;

bool b;

char c;

...

x = n;

n = x;

n = b;

b = n;

n = c;

c = n;

Tem o efeito esperado. Não há problema.

Inseguro: truncaria a parte decimal. O compilador gera warning: “'=' : conversion from 'double' to 'int', possible loss of data.”

OK: false dá zero, true dá 1.

Inseguro: zero dá false, não zero dá true. O compilador gera warning:” 'int' : forcing value to bool 'true' or 'false' (performance warning)”

OK: n fica com o valor numérico de c.

OK, mas inseguro pois o valor de n pode não ser representável no tipo char.

Page 27: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 27

Conversão explícitaEvitam-se aqueles warnings fazendo uma conversão explícita para o tipo da variável. No caso conversão para int usa-se o

int n;

double x;

bool b;

char c;

...

x = n;

n = static_cast<int>(x);

n = b;

b = n != 0;

n = c;

c = n;

int n;

double x;

bool b;

char c;

...

x = n;

n = static_cast<int>(x);

n = b;

b = n != 0;

n = c;

c = n;

operador static_cast<T>, no caso da conversão para bool, usa-se o operador != (operador de desigualdade).

Nota: usa-se static_cast<double> para forçar conversão para double em expressões:

int n;

int sum;

...

double average = static_cast<double>(sum) / n;

int n;

int sum;

...

double average = static_cast<double>(sum) / n;

Page 28: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 28

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).void Point::Translate(double dx, double dy){

x += dx;y += dy;

}

void Point::Scale(double fx, double fy){

x *= fx;y *= fy;

}

void Point::Translate(double dx, double dy){

x += dx;y += dy;

}

void Point::Scale(double fx, double fy){

x *= fx;y *= fy;

}

Este é o operador de afectação não simples *=.

Programar assim:

void Point::Translate(...){

x = x + dx;y = x + dy;

}

seria mau estilo.

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 usuais:

Use Em vez de

Page 29: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 29

Afectações de classePodemos afectar um objecto de um tipo classe a outro? Sim, podemos. Significa a afectação membro a membro:

...Point p(4, 7);Point q(0, 2);...p.Rotate(1.57079632679489661923);q.Scale(0, -1);...q = p;...

...Point p(4, 7);Point q(0, 2);...p.Rotate(1.57079632679489661923);q.Scale(0, -1);...q = p;...

Frequentemente o que queremos é a construção por cópia e não a afectação:

...Point p(4, 7);...p.Rotate(1.57079632679489661923);Point q;q = p;...

...Point p(4, 7);...p.Rotate(1.57079632679489661923);Point q;q = p;...

...Point p(4, 7);...p.Rotate(1.57079632679489661923);Point q(p);...

...Point p(4, 7);...p.Rotate(1.57079632679489661923);Point q(p);...Mau estilo: inicialização logo

seguida de afectação.

Afectação membro a membro.

Bom estilo...

Page 30: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 30

Definição das funçõesCada função declarada num programa deve ser definida algures, exactamente uma vez.A definição da função é composta por um cabeçalho e por um corpo.O cabeçalho reproduz o protótipo usado na declaração, omitindo qualificadores como virtual e os valores dos argumentos por defeito, se houver. Se for uma função que pertence a uma classe, na definição o nome da função équalificado pelo nome da classe.O corpo é uma sequência de instruções colocadas entre chavetas.void Point::Rotate(double angle){

double x0 = x;double y0 = y;x = x0 * ::cos(angle) - y0 * ::sin(angle);y = x0 * ::sin(angle) + y0 * ::cos(angle);

}

void Point::Rotate(double angle){

double x0 = x;double y0 = y;x = x0 * ::cos(angle) - y0 * ::sin(angle);y = x0 * ::sin(angle) + y0 * ::cos(angle);

}

Variáveis locais

Nos selectores que retornam diferente de void a última instrução é sempre um return.

double Point::Angle() const{return ::atan2(y, x);

}

double Point::Angle() const{return ::atan2(y, x);

}

Page 31: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 31

thisQuando definimos uma função, frequentemente o objecto da função fica implícito:

Ocasionalmente, queremos explicitar o objecto, por exemplo quando os argumentos têm o mesmo nome do que os membros de dados. Usamos então o apontador this:

double Point::Modulus() const{

return ::sqrt(x*x + y*y);}

double Point::DistanceTo(const Point& other) const{

return ::sqrt(::pow(x - other.x, 2) + ::pow(y - other.y, 2));}

double Point::Modulus() const{

return ::sqrt(x*x + y*y);}

double Point::DistanceTo(const Point& other) const{

return ::sqrt(::pow(x - other.x, 2) + ::pow(y - other.y, 2));}

void Point::Set(double x, double y){

this->x = x;this->y = y;

}

void Point::Set(double x, double y){

this->x = x;this->y = y;

}

Este x e este y são referenciam os membros de dados do objecto da função, em cada chamada.

Aqui x e y sozinhos referenciam os argumentos e this->x e this->y referenciam os membros de dados do objecto.

Page 32: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 32

EscrevendoEm C++, escreve-se usando o operador de inserção <<:void Point::Write(std::ostream& output) const{

output << x << " " << y;}

void Point::WriteLine(std::ostream& output) const{

Write(output);output << std::endl;

}

Para escrever na consola, usa-se a stream std::cout:

void Point::Write(std::ostream& output) const{

output << x << " " << y;}

void Point::WriteLine(std::ostream& output) const{

Write(output);output << std::endl;

}

...std::cout << "Coordenadas do primeiro ponto: ";...Point pm((p1.X() + p2.X()) / 2, (p1.Y() + p2.Y()) / 2);std::cout << "Ponto médio: ";pm.WriteLine();double d1 = ...;double d2 = ...;std::cout << "Distâncias: " << d1 << " " << d2 << std::endl;

...std::cout << "Coordenadas do primeiro ponto: ";...Point pm((p1.X() + p2.X()) / 2, (p1.Y() + p2.Y()) / 2);std::cout << "Ponto médio: ";pm.WriteLine();double d1 = ...;double d2 = ...;std::cout << "Distâncias: " << d1 << " " << d2 << std::endl;

Como std::cout é o argumento por defeito da função WriteLine, escrever pm.WriteLine() é o mesmo do que escrever pm.WriteLine(std::cout).

Tecnicamente, este std::endl é um manipulador que quando enviado para uma stream de escrita provoca uma mudança de linha.

Page 33: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 33

LendoEm C++, lê-se usando o operador de extracção >>:

void Point::Read(std::istream& input)

{

input >> x >> y;

}

Para ler da consola, usa-se a stream std::cin:

void Point::Read(std::istream& input)

{

input >> x >> y;

}

...int x;int y;std::cout << "Coordenadas do primeiro ponto: ";std::cin >> x >> y;Point p1(x, y);std::cout << "Coordenadas do segundo ponto: ";std::cin >> x >> y;...

...int x;int y;std::cout << "Coordenadas do primeiro ponto: ";std::cin >> x >> y;Point p1(x, y);std::cout << "Coordenadas do segundo ponto: ";std::cin >> x >> y;... Não surge no exemplo mas para uma variável p de tipo

Point escrever p.Read() seria o mesmo do que escrever p.Read(std::cin).

class Point {...virtual void Read(std::istream& input = std::cin);...

};

class Point {...virtual void Read(std::istream& input = std::cin);...

};

Nova função:

Page 34: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 34

Definindo os construtoresOs construtores são funções especiais que têm uma sintaxe especial. Sempre que possível inicializamos os membros de dados na lista de inicializadores:Point::Point():

x(0),y(0)

{}

Point::Point(double x, double y):x(x),y(y)

{}

Point::Point(const Point& other):x(other.x),y(other.y)

{}

Point::Point():x(0),y(0)

{}

Point::Point(double x, double y):x(x),y(y)

{}

Point::Point(const Point& other):x(other.x),y(other.y)

{}

A lista de inicializadores vem entre aquele sinal de dois pontos a seguir ao cabeçalho e a chaveta a abrir. Só os construtores é que têm lista de inicializadores.

No corpo dos construtores programamos o que fizer falta.Por exemplo, podemos escrever mensagens para debug:

Point::Point():x(0),y(0)

{std::cout << "Default constructor. " << std::endl;

}

Point::Point():x(0),y(0)

{std::cout << "Default constructor. " << std::endl;

}

Este caso é engraçado: o xde fora é o membro de dados, o x de dentro é o argumento. (Idem para o y.)

Page 35: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 35

Definindo o destrutorNa classe Point o destrutor não faz nada. Por isso, fica em branco:

Point::~Point(){}

Point::~Point(){}

Point::~Point(){

std::cout << "Destructor: ";WriteLine();

}

Point::~Point(){

std::cout << "Destructor: ";WriteLine();

}

Podemos aproveitar para escrever uma mensagem, para ajudar a perceber o funcionamento dos destrutores:

Nem sempre os destrutores serão tão simples. Normalmente não são complicados, mas às vezes são enganadores. As linguagens mais recentes (Eiffel, Java, C#) não têm destrutores.

Page 36: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 36

Formatando os números doublePara controlar o formato com que os número double aparecem escritos usamos os manipuladores std::fixed, std::scientific, std::setprecision(...) e std::setf(...). Observe o exemplo:void TestDoubleOutput(){double x;double y;std::cout << "Coordinates (x and y), please: ";std::cin >> x >> y;Point p(x, y);

double d = p.DistanceTo(Point());

std::cout << "General format: " << std::endl;std::cout << "Distance to origin: " << d << std::endl;

std::cout << "Fixed format, two decimal digits: " << std::endl;std::cout << std::fixed << std::setprecision(2);std::cout << "Distance to origin: " << d << std::endl;

std::cout << "Scientific format, eight decimal digits: " << std::endl;std::cout << std::scientific << std::setprecision(8);std::cout << "Distance to origin: " << d << std::endl;

std::cout << "Returning to default format, six digits: " << std::endl;std::cout.setf(0, std::ios_base::floatfield);std::cout << std::setprecision(6);std::cout << "Distance to origin: " << d << std::endl;

}

void TestDoubleOutput(){double x;double y;std::cout << "Coordinates (x and y), please: ";std::cin >> x >> y;Point p(x, y);

double d = p.DistanceTo(Point());

std::cout << "General format: " << std::endl;std::cout << "Distance to origin: " << d << std::endl;

std::cout << "Fixed format, two decimal digits: " << std::endl;std::cout << std::fixed << std::setprecision(2);std::cout << "Distance to origin: " << d << std::endl;

std::cout << "Scientific format, eight decimal digits: " << std::endl;std::cout << std::scientific << std::setprecision(8);std::cout << "Distance to origin: " << d << std::endl;

std::cout << "Returning to default format, six digits: " << std::endl;std::cout.setf(0, std::ios_base::floatfield);std::cout << std::setprecision(6);std::cout << "Distance to origin: " << d << std::endl;

}

Para usar os manipuladores com argumentos, é preciso fazer #include <iomanip>.

Page 37: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 37

Exemplos de formatação doubleEstes são os resultados de três execuções da função TestDoubleOuput, da página anterior.

Page 38: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 38

Funções de testePara simplificar a organização do nosso programa, usamos funções de teste. A função main limita-se a chamar uma das funções de teste.

#include <iostream>// ...

#include "Point.h"

void TestPoints();void TestDoubleOutput();

int main(){//TestPoints();TestDoubleOutput();return 0;

}

void TestPoints(){//...

}

void TestDoubleOutput(){//...

}

#include <iostream>// ...

#include "Point.h"

void TestPoints();void TestDoubleOutput();

int main(){//TestPoints();TestDoubleOutput();return 0;

}

void TestPoints(){//...

}

void TestDoubleOutput(){//...

}

Cada função de teste tem um protótipo, que constitui a respectiva declaração. O protótipo é indispensável porque as função vão ser chamadas na função main, e as definições só aparecem depois.

A função main termina sempre com return 0;

Tipicamente, a função main chama apenas uma das funções de teste. As outras ficam em comentário.

Page 39: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 39

Funções matemáticas de bibliotecaA biblioteca do C++ traz as funções matemáticas do costume:double abs(double x);

double floor(double x);double ceil(double x);

double cos(double x);double sin(double x);double tan(double x);double acos(double x);double asin(double x);double atan(double x);double atan2(double y, double x);

double sinh(double x);double cosh(double x);double tanh(double x);

double sqrt(double x);double exp(double x);double log(double x);double log10(double x);double pow(double x, double y);

double abs(double x);

double floor(double x);double ceil(double x);

double cos(double x);double sin(double x);double tan(double x);double acos(double x);double asin(double x);double atan(double x);double atan2(double y, double x);

double sinh(double x);double cosh(double x);double tanh(double x);

double sqrt(double x);double exp(double x);double log(double x);double log10(double x);double pow(double x, double y);

abs: valor absoluto.

floor: maior número inteiro (enquanto número double) menor ou igual a x: ceil: menor número inteiro (enquanto número double) maior ou igual a x .

Funções trigonométricas coseno, seno, tangente, arco-coseno, arcosseno, arcotangente. A função arcotangente tem duas variantes: atan tem resultado no intervalo –π/2, π/2; atan2(y, x) calcula o arcotangente de y/x no intervalo –π, π, usando os sinais de y e x para determinar o quadrante. Calcula mesmo se x valer zero, mas não se x e y valerem zero.

Para usar isto é preciso fazer #include <cmath>.

Raiz quadrada, exponencial, logaritmo natural, logaritmo decimal, potência (x elevado a y).

Funções hiperbólicas.

Page 40: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 40

Exercícios 21. Defina uma classe Segment para representar segmento de

recta. Preveja funções para o calcular comprimento do segmento, para obter o ponto médio, para escalar o segmento, para transladar, para rodar o segmento em torno da origem e ainda em torno das extremidades e em torno do ponto médio, para ver se um ponto pertence a um segmento, para ver se dois segmentos se intersectam e, caso se intersectem, calcular o ponto de intersecção, etc.

2. Defina uma classe Triangle para representar triângulos, com funções para a área e para o perímetro, para escalar, transladar e rodar, para ver se um ponto está no interior do triângulo, para calcular os centros, etc.

3. Defina uma classe Rectangle, para representar rectângulos com base horizontal, com dois membros de dados, um o canto inferior esquerdo e outro para canto superior direito. Preveja funções para a área, perímetro, para escalar, transladar, para calcular a intersecção com outro rectângulo, etc.

Page 41: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 41

Evitando inclusões repetidasSe um ficheiro A incluir os ficheiros B e C e se o ficheiro Bincluir o ficheiro C, então quando processar o ficheiro A o compilador inclui o ficheiro C duas vezes. Isto pode causar problemas. Evitamo-los com a directiva #ifndef e com as constantes simbólicas. O ficheiro Point.h fica assim:#ifndef _H_Point

#define _H_Point

class Point {

private:

double x;

double y;

// ...

};

#endif

#ifndef _H_Point

#define _H_Point

class Point {

private:

double x;

double y;

// ...

};

#endif

Isto significa: se a constante simbólica _H_Point ainda não estiver definida, então fica definida e o resto do ficheiro éprocessado pelo compilador; se já estiver definida, então o compilador ignora tudo até à correspondente directiva #endif, lá em baixo.

Regra: todas as declarações de classes ficarão dentro de uma zona #ifndef-#define-#endif. O nome da constante simbólica é o nome da classe prefixado por _H_, convencionalmente.

Page 42: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 42

Os ficheiros de inclusão não incluemOutra regra: só há directivas #include nos ficheiros .cpp.

#ifndef _H_Point#define _H_Point

class Point {private:double x;double y;

// ...};

#endif

#ifndef _H_Point#define _H_Point

class Point {private:double x;double y;

// ...};

#endif

#include <iostream>#include <cmath>

#include "Point.h"

Point::Point():x(0),y(0)

{}

// ...

#include <iostream>#include <cmath>

#include "Point.h"

Point::Point():x(0),y(0)

{}

// ...

#include <iostream>#include <iomanip>

#include "Point.h"

void TestPoints();void TestDoubleOutput();

int main(){//TestPoints();TestDoubleOutput();return 0;

}

//...

#include <iostream>#include <iomanip>

#include "Point.h"

void TestPoints();void TestDoubleOutput();

int main(){//TestPoints();TestDoubleOutput();return 0;

}

//...

Point.hPoint.cpp

M_Point.cpp

Às vezes, parece que esta regra nos complica a vida, pois obriga-nos a repetir inclusões. Na verdade, ajuda-nos muito.

Só incluímos o que faz falta. Por exemplo, no Point.cpp faz falta o <cmath>, mas no M_Point.cpp não faz.

Page 43: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 43

std, o espaço de nomes da bibliotecaQuando usamos algo proveniente da biblioteca standard do C++, tal como as streams cout e cin, os manipuladores endl, fixed, etc., devemos deixar claro que sabemos de onde isso vem, qualificando com std::. É assim porque a biblioteca estádefinida no espaço de nomes std.void TestDoubleOutput(){double x;double y;std::cout << "Coordinates (x and y), please: ";std::cin >> x >> y;Point p(x, y);

double d = p.DistanceTo(Point());

std::cout << "General format: " << std::endl;std::cout << "Distance to origin: " << d << std::endl;

std::cout << "Fixed format, two decimal digits: " << std::endl;std::cout << std::fixed << std::setprecision(2);std::cout << "Distance to origin: " << d << std::endl;//...

void TestDoubleOutput(){double x;double y;std::cout << "Coordinates (x and y), please: ";std::cin >> x >> y;Point p(x, y);

double d = p.DistanceTo(Point());

std::cout << "General format: " << std::endl;std::cout << "Distance to origin: " << d << std::endl;

std::cout << "Fixed format, two decimal digits: " << std::endl;std::cout << std::fixed << std::setprecision(2);std::cout << "Distance to origin: " << d << std::endl;//...

Programadores preguiçosos usam a directiva using namespace std; para não ter de escrever std:: muitas vezes:

//...

using namespace std;

//...

void TestDoubleOutput(){

double x;double y;cout << "Coordinates (x and y), please: ";cin >> x >> y;Point p(x, y);

double d = p.DistanceTo(Point());

cout << "General format: " << endl;cout << "Distance to origin: " << d << endl;

cout << "Fixed format, two decimal digits: " << endl;cout << fixed << setprecision(2);cout << "Distance to origin: " << d << endl;

//...

using namespace std;

//...

void TestDoubleOutput(){

double x;double y;cout << "Coordinates (x and y), please: ";cin >> x >> y;Point p(x, y);

double d = p.DistanceTo(Point());

cout << "General format: " << endl;cout << "Distance to origin: " << d << endl;

cout << "Fixed format, two decimal digits: " << endl;cout << fixed << setprecision(2);cout << "Distance to origin: " << d << endl;

Page 44: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 44

O espaço dos nossos nomesDevemos também arranjar um espaço de nomes para as nossas classes. As minhas estão no espaço mas.

#ifndef _H_Point#define _H_Point

namespace mas {

class Point {private:double x;double y;

//...};

}

#endif

#ifndef _H_Point#define _H_Point

namespace mas {

class Point {private:double x;double y;

//...};

}

#endif

#include <iostream>#include <cmath>

#include "Point.h"

namespace mas {

Point::Point():x(0),y(0)

{}

//...

}

#include <iostream>#include <cmath>

#include "Point.h"

namespace mas {

Point::Point():x(0),y(0)

{}

//...

}

A classe Point é declarada dentro do espaço de nomes mas:

As definições das funções também ficam dentro do espaço de nomes mas:

Dentro de um espaço de nomes, os nomes desse espaço não precisam de qualificação.

Page 45: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 45

Fora do espaço qualifica-seFora do espaço de nomes, tipicamente nas funções de teste, qualificamos os nomes do nosso espaço de nomes:

void TestMidpoint(){int x;int y;std::cout << "Coordenadas do primeiro ponto: ";std::cin >> x >> y;mas::Point p1(x, y);std::cout << "Coordenadas do segundo ponto: ";std::cin >> x >> y;mas::Point p2(x, y);mas::Point pm((p1.X() + p2.X()) / 2, (p1.Y() + p2.Y()) / 2);std::cout << "Ponto médio: ";pm.WriteLine();double d1 = p1.DistanceTo(pm);double d2 = p2.DistanceTo(pm);std::cout << "Distâncias: " << d1 << " " << d2 << std::endl;

}

void TestMidpoint(){int x;int y;std::cout << "Coordenadas do primeiro ponto: ";std::cin >> x >> y;mas::Point p1(x, y);std::cout << "Coordenadas do segundo ponto: ";std::cin >> x >> y;mas::Point p2(x, y);mas::Point pm((p1.X() + p2.X()) / 2, (p1.Y() + p2.Y()) / 2);std::cout << "Ponto médio: ";pm.WriteLine();double d1 = p1.DistanceTo(pm);double d2 = p2.DistanceTo(pm);std::cout << "Distâncias: " << d1 << " " << d2 << std::endl;

}

Assim, até podia haver várias classes Point, cada uma no seu espaço de nomes.

Page 46: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 46

Limites numéricosÀs vezes precisamos de conhecer os limites dos tipos numéricos nos nossos programas. Eis um programa que os revela:

void TestNumericLimits(){int intMax = std::numeric_limits<int>::max();int intMin = std::numeric_limits<int>::min();double doubleInfinity = std::numeric_limits<double>::infinity();double doubleMax = std::numeric_limits<double>::max();double doubleMin = std::numeric_limits<double>::min();double epsilon = std::numeric_limits<double>::epsilon();

std::cout << "Maior número inteiro: " << intMax << std::endl;std::cout << "Menor número inteiro: " << intMin << std::endl;

std::cout << "Mais infinito (double): " << doubleInfinity << std::endl;std::cout << "Maior número positivo double: " << doubleMax << std::endl;std::cout << "Menor número positivo double: " << doubleMin << std::endl;std::cout << "epsilon: " << epsilon << std::endl;

}

void TestNumericLimits(){int intMax = std::numeric_limits<int>::max();int intMin = std::numeric_limits<int>::min();double doubleInfinity = std::numeric_limits<double>::infinity();double doubleMax = std::numeric_limits<double>::max();double doubleMin = std::numeric_limits<double>::min();double epsilon = std::numeric_limits<double>::epsilon();

std::cout << "Maior número inteiro: " << intMax << std::endl;std::cout << "Menor número inteiro: " << intMin << std::endl;

std::cout << "Mais infinito (double): " << doubleInfinity << std::endl;std::cout << "Maior número positivo double: " << doubleMax << std::endl;std::cout << "Menor número positivo double: " << doubleMin << std::endl;std::cout << "epsilon: " << epsilon << std::endl;

}

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

Page 47: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 47

Precisão numéricaOs números double têm precisão de 17 algarismos decimais. O valor epsilon, obtido pela função std::numeric_limits<double>::epsilon(), é o menor número double representável tal que 1.0 + epsilon – 1.0 não é zero. Observe: void TestEpsilon(){double epsilon = std::numeric_limits<double>::epsilon();std::cout << std::setprecision(17);std::cout << epsilon << std::endl;double epsilon1 = epsilon + 1.0;std::cout << epsilon1 - 1.0 << std::endl;double epsilon2 = epsilon / 2.0 + 1.0;std::cout << epsilon2 - 1.0 << std::endl;std::cout << std::setprecision(30);std::cout << epsilon << std::endl;

}

void TestEpsilon(){double epsilon = std::numeric_limits<double>::epsilon();std::cout << std::setprecision(17);std::cout << epsilon << std::endl;double epsilon1 = epsilon + 1.0;std::cout << epsilon1 - 1.0 << std::endl;double epsilon2 = epsilon / 2.0 + 1.0;std::cout << epsilon2 - 1.0 << std::endl;std::cout << std::setprecision(30);std::cout << epsilon << std::endl;

}Quando calculamos 1.0 + epsilon – 1.0 obtemos epsilon, mas quando calculamos 1.0 + epsilon / 2 – 1.0 obtemos 0. Isto é assim porque a representação dos números doublenos computadores usa um número finito de dígitos.

Page 48: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 48

Classe genérica numeric_limits<T>

void TestNumericMinMax(){using std::cout;using std::endl;cout << "char Min: " << static_cast<int>(std::numeric_limits<char>::min()) << endl;cout << "char Max: " << static_cast<int>(std::numeric_limits<char>::max()) << endl;cout << "short Min: " << std::numeric_limits<short>::min() << endl;cout << "short Max: " << std::numeric_limits<short>::max() << endl;cout << "int Min: " << std::numeric_limits<int>::min() << endl;cout << "int Max: " << std::numeric_limits<int>::max() << endl;cout << "long Min: " << std::numeric_limits<long>::min() << endl;cout << "long Max: " << std::numeric_limits<long>::max() << endl;cout << "unsigned Min: " << std::numeric_limits<unsigned>::min() << endl;cout << "unsigned Max: " << std::numeric_limits<unsigned>::max() << endl;cout << "_int64 Min: " << std::numeric_limits<_int64>::min() << endl;cout << "_int64 Max: " << std::numeric_limits<_int64>::max() << endl;

}

void TestNumericMinMax(){using std::cout;using std::endl;cout << "char Min: " << static_cast<int>(std::numeric_limits<char>::min()) << endl;cout << "char Max: " << static_cast<int>(std::numeric_limits<char>::max()) << endl;cout << "short Min: " << std::numeric_limits<short>::min() << endl;cout << "short Max: " << std::numeric_limits<short>::max() << endl;cout << "int Min: " << std::numeric_limits<int>::min() << endl;cout << "int Max: " << std::numeric_limits<int>::max() << endl;cout << "long Min: " << std::numeric_limits<long>::min() << endl;cout << "long Max: " << std::numeric_limits<long>::max() << endl;cout << "unsigned Min: " << std::numeric_limits<unsigned>::min() << endl;cout << "unsigned Max: " << std::numeric_limits<unsigned>::max() << endl;cout << "_int64 Min: " << std::numeric_limits<_int64>::min() << endl;cout << "_int64 Max: " << std::numeric_limits<_int64>::max() << endl;

}

O tipo _int64 éespecífico da Microsoft.

A classe std::numeric_limits<T>é uma classe genérica (“template class”). Podemos instanciá-la com os diversos tipos numéricos e assim obter informação sobre esses tipos:

Declarações using. Usar com moderação.

Tipos inteiros do C++: char, short, int, long, unsigned, _int64.

Page 49: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 49

O infinito

void TestInfinity(){double max = std::numeric_limits<double>::max();double infinity = std::numeric_limits<double>::infinity();std::cout << "Maior double: " << max << std::endl;std::cout << "Infinito: " << infinity << std::endl;std::cout << "1.0 + infinity = " << 1.0 + infinity << std::endl;std::cout << "1.0 - infinity = " << 1.0 - infinity << std::endl;std::cout << "1.0 * infinity = " << 1.0 * infinity << std::endl;std::cout << "1.0 / infinity = " << 1.0 / infinity << std::endl;std::cout << "0.0 * infinity = " << 0.0 * infinity << std::endl;std::cout << "infinity / 0.0 = " << infinity/ 0.0 << std::endl;

std::cout << std::boolalpha;std::cout << "max < infinity = " << (max < infinity) << std::endl;std::cout << "infinity - max = " << infinity - max << std::endl;

}

void TestInfinity(){double max = std::numeric_limits<double>::max();double infinity = std::numeric_limits<double>::infinity();std::cout << "Maior double: " << max << std::endl;std::cout << "Infinito: " << infinity << std::endl;std::cout << "1.0 + infinity = " << 1.0 + infinity << std::endl;std::cout << "1.0 - infinity = " << 1.0 - infinity << std::endl;std::cout << "1.0 * infinity = " << 1.0 * infinity << std::endl;std::cout << "1.0 / infinity = " << 1.0 / infinity << std::endl;std::cout << "0.0 * infinity = " << 0.0 * infinity << std::endl;std::cout << "infinity / 0.0 = " << infinity/ 0.0 << std::endl;

std::cout << std::boolalpha;std::cout << "max < infinity = " << (max < infinity) << std::endl;std::cout << "infinity - max = " << infinity - max << std::endl;

}

O manipulador std::boolalpha faz com que os valores booleanos sejam escritos false e true, em vez de 0 e 1.

O tipo double tem o infinito. Este valor comporta-se mesmo como um verdadeiro infinito:

“warning: potential divide by 0”.

0 * ∞ é indeterminado.

Nota: o tipo int não tem infinito .

Page 50: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 50

Erros de principiante• Chamar uma função sem objecto (por exemplo, Write(p) em vez de p.Write()).• Esquecer o nome da classe ao definir uma função.• Tentar usar os membros privados fora da classe.• Tentar mudar o valor do objecto num selector (função const).• Esquecer alguns dos #include.• Usar parêntesis ao declarar um objecto com o construtor por defeito (por exemplo,

Point p(); em vez de Point p;.• Usar listas de inicializadores em funções que não são construtores.• Esquecer os parêntesis ao chamar uma função sem argumentos.• Esquecer alguns consts,• Usar cout, cin e endl sem o qualificador std::.• Passar por valor argumentos de um tipo classe.• Esquecer o especificador virtual nas declarações das funções ou incluí-lo nas

definições.• Escrever um ponto e vírgula no final do cabeçalho numa definição de função.• Esquecer o ponto e vírgula no final da classe.• Esquecer o espaço de nomes.• Usar a divisão inteira quando se queria a divisão exacta.

Page 51: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 51

VectoresO C++ fornece directamente suporte para vectores, através dos arrays:

//...

int a[32];

char m[80];

double x[100];

double y[100];

mas::Point p[8];

//...

//...

int a[32];

char m[80];

double x[100];

double y[100];

mas::Point p[8];

//...

Nalguns casos, em vez de dois vectores “paralelos”, é melhor usar um vector de pares:

Mas é melhor usar a classe std::vector<T> e, para as cadeias de caracteres, a classe std::string:

//...

std::vector<int> a(32);

std::string m(79, ' ');

std::vector<double> x(100);

std::vector<double> y(100);

std::vector<mas::Point> p(8);

//...

//...

std::vector<int> a(32);

std::string m(79, ' ');

std::vector<double> x(100);

std::vector<double> y(100);

std::vector<mas::Point> p(8);

//...

std::vector<std::pair<double, double> > z(100);std::vector<std::pair<double, double> > z(100);

Para usar vectores destes é preciso fazer #include <vector>; para usar strings, fazer #include <string>.

Page 52: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 52

É melhor usar os vectores da STLOs arrays do C++ são básicos: apenas têm a operação de indexação:

//...

for (int i = 0; i < 32; i++)

a[i] = i * i;

int d = a[31] - a[30];

p[0] = mas::Point(3.0, 4.0);

//...

double dist = p[0].DistanceTo(p[5]);

//...

//...

for (int i = 0; i < 32; i++)

a[i] = i * i;

int d = a[31] - a[30];

p[0] = mas::Point(3.0, 4.0);

//...

double dist = p[0].DistanceTo(p[5]);

//...

A sua capacidade é fixa. Após a declaração, têm de ser inicializados explicitamente.A classe std::vector<T> é uma classe genérica. Tem muito mais funcionalidade. Faz parte da STL (Standard Template Library).

Page 53: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 53

Exemplo com std::vector<int>Criar um vector com 16 números aleatórios entre 0 e 99, escrevê-lo, ordená-lo, escrevê-lo de novo:

void Test_3()

{

::srand(static_cast<unsigned>(::time(0)));

std::vector<int> a;

a.reserve(16);

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

a.push_back(::rand() % 100);

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

std::cout << " " << a[i];

std::cout << std::endl;

std::sort(a.begin(), a.end());

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

std::cout << " " << a[i];

std::cout << std::endl;

}

void Test_3()

{

::srand(static_cast<unsigned>(::time(0)));

std::vector<int> a;

a.reserve(16);

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

a.push_back(::rand() % 100);

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

std::cout << " " << a[i];

std::cout << std::endl;

std::sort(a.begin(), a.end());

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

std::cout << " " << a[i];

std::cout << std::endl;

}

Inicializamos a semente do gerador de números aleatórios.

Declaramos o vector. Não indicamos a capacidade.Agora reservamos a capacidade pretendida.

Preenchemos o vector com um ciclo for.

Escrevemos o vector.

Ordenamos usando a função genérica std::sort.

Escrevemos de novo.

Page 54: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 54

A instrução for

for (expressão1; expressão2; expressão3)

instrução

Sintaxe (caso tradicional):

Semântica:1. 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.

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

Page 55: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 55

Variável de controlo num ciclo forFrequentemente, as instruções for têm uma variável de controlo que é inicializada na expressão1, testada na expressão2 e incrementada na expressão3. Nesse caso, é melhor declará-la dentro da instrução for, no local da expressão1:

//...

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

a.push_back(::rand() % 100);

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

std::cout << " " << a[i];

//...

//...

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

a.push_back(::rand() % 100);

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

std::cout << " " << a[i];

//...

O âmbito das variáveis declaradas no ciclo for é o próprio ciclo for. (Por isso é que declaramos de novo, no segundo ciclo.)

Page 56: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 56

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;

Há uma diferença subtil entre x += 1 e x++. Ambas as expressões incrementam o valor da variável x. No entanto o valor da expressão x += 1 é x+1 e o valor da expressão x++ é x.

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 C++ vem deste operador...

Page 57: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 57

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(){using std::cout;using std::cin;using std::endl;int x;cout << "Um número: ";cin >> x;cout << x << endl;cout << x++ << endl;cout << x << endl;

}

void Test_plus_plus_suffix(){using std::cout;using std::cin;using std::endl;int x;cout << "Um número: ";cin >> x;cout << x << endl;cout << x++ << endl;cout << x << endl;

}

void Test_plus_plus_prefix(){using std::cout;using std::cin;using std::endl;int x;cout << "Um número: ";cin >> x;cout << x << endl;cout << ++x << endl;cout << x << endl;

}

void Test_plus_plus_prefix(){using std::cout;using std::cin;using std::endl;int x;cout << "Um número: ";cin >> x;cout << x << endl;cout << ++x << endl;cout << x << endl;

}

Claro que há

também

dois operadores --análogos a estes.

Page 58: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 58

Números aleatóriosA função ::rand() retorna um número pseudoaleatório entre 0 e uma constante RAND_MAX (que normalmente vale 32767).A função ::srand(x) faz de x a semente do gerador.Para obter a mesma sequência pseudoaleatória repetidamente, basta dar a mesma semente.Para obter sempre sequências diferentes, dá-se um semente sempre diferente. Que semente? A hora actual em milésimos de segundo, tal como medida instantaneamente pelo relógio do computador:

::srand(static_cast<unsigned>(::time(0)));

//...

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

a.push_back(::rand() % 100);

::srand(static_cast<unsigned>(::time(0)));

//...

for (int i = 0; i < static_cast<int>(a.capacity()); i++)

a.push_back(::rand() % 100);

O valor ::time(0) representa a hora actual medida em milésimos de segundos desde um certo dia no anos 70. Para usar a função ::time é preciso fazer #include <ctime>.

Page 59: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 59

Funções utilitárias para vectoresComo a classe genérica std::vector<T> não tem funções de escrita, programamo-las nós, genericamente também. Observe:namespace mas {

template <class T>void Write(const std::vector<T>& v,

const std::string& separator = " ", std::ostream& output = std::cout){for (unsigned i = 0; i < v.size() ; i++)output << (i != 0 ? separator : "") << v[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 (unsigned i = 0; i < v.size() ; i++)output << (i != 0 ? separator : "") << v[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;

}

}

O segundo e o terceiro parâmetro têm valores por defeito.

Estas funções pertencem ao espaço de nomes mas e estão no ficheiro Utilities_vector.h. Frequentemente, as funções genéricas são logo programadas no ficheiro .h.

(i != 0 ? separator : ""): expressão condicional: o separador não é escrito antes do primeiro elemento.

Page 60: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 60

Escrevendo vectores genericamenteAs funções genéricas da página anterior escrevem vectores com elementos de qualquer tipo, desde que o operador <<reconheça esse tipo. Eis um exemplo, escrevendo números inteiros:void Test_4(){::srand(static_cast<unsigned>(::time(0)));std::vector<int> a;a.reserve(16);for (int i = 0; i < static_cast<int>(a.capacity()); i++)a.push_back(::rand() % 100);

mas::WriteLine(a);std::sort(a.begin(), a.end());mas::WriteLine(a);

}

void Test_4(){::srand(static_cast<unsigned>(::time(0)));std::vector<int> a;a.reserve(16);for (int i = 0; i < static_cast<int>(a.capacity()); i++)a.push_back(::rand() % 100);

mas::WriteLine(a);std::sort(a.begin(), a.end());mas::WriteLine(a);

}

void Test_5(){std::vector<mas::Point> v;v.reserve(6);v.push_back(mas::Point(2.0, -1.0));v.push_back(mas::Point());v.push_back(mas::Point(v[0]));v.push_back(mas::Point(3.0, 4.0));mas::WriteLine(v, "\n");

}

void Test_5(){std::vector<mas::Point> v;v.reserve(6);v.push_back(mas::Point(2.0, -1.0));v.push_back(mas::Point());v.push_back(mas::Point(v[0]));v.push_back(mas::Point(3.0, 4.0));mas::WriteLine(v, "\n");

}

Batota: a classe Pointnão tem o operador <<.

E outro, escrevendo pontos:

Page 61: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 61

O operador << na classe PointPara poder escrever vectores de pontos com as funções utilitárias, é preciso que a classe Point disponha de um operador amigo <<:class Point {private:double x;double y;

public:Point();Point(double x, double y);//...friend std::ostream& operator << (std::ostream& output, const Point& p);

};

class Point {private:double x;double y;

public:Point();Point(double x, double y);//...friend std::ostream& operator << (std::ostream& output, const Point& p);

};

std::ostream& operator << (std::ostream& output, const Point& p){p.Write(output);return output;

}

std::ostream& operator << (std::ostream& output, const Point& p){p.Write(output);return output;

}

Os operadores amigos não pertencem à classe (não têm objecto da classe) mas aceitam operando do tipo da classe. Por isso, arrumamo-los “dentro” da classe. Normalmente, exprimem-se em termos de alguma das funções da classe.

A definição vem junto das outras, no ficheiro .cpp:

Page 62: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 62

Expressão condicionalVimos há pouco uma expressão condicional:

for (unsigned i = 0; i < v.size() ; i++)output << (i != 0 ? separator : "") << v[i];

for (unsigned i = 0; i < v.size() ; i++)output << (i != 0 ? separator : "") << v[i];

expressão1 ? expressão2 : expressão3

Sintaxe:

Semântica:Primeiro avalia-se a expressão1. Se der true, avalia-se a expressão2 e o resultado da expressão condicional é o resultado da avaliação da expressão2. Se não, isto é, se a expressão1 valer false, avalia-se a expressão3 e o resultado da expressão condicional é o resultado da avaliação da expressão3. Só uma das expressões 2 e 3 é avaliada.

Page 63: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 63

Expressão condicional, exemplosAs funções Min e Max programadas genericamente:template <class T>

T Min(const T& x, const T&y)

{

return x <= y ? x : y;

}

template <class T>

T Min(const T& x, const T&y)

{

return x <= y ? x : y;

}

template <class T>

T Max(const T& x, const T&y)

{

return x <= y ? y : x;

}

template <class T>

T Max(const T& x, const T&y)

{

return x <= y ? y : x;

}

template <class T>

T Min(const T& x, const T&y)

{

if (x <= y)

return x;

else

return y;

}

template <class T>

T Min(const T& x, const T&y)

{

if (x <= y)

return x;

else

return y;

}

template <class T>

T Max(const T& x, const T&y)

{

if (x <= y)

return y;

else

return x;

}

template <class T>

T Max(const T& x, const T&y)

{

if (x <= y)

return y;

else

return x;

}

Programar isto com a ajuda de instruções if seria mau estilo:

Page 64: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 64

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 65: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 65

Operações sobre vectores (1)Construtores, capacidade, tamanho, operador []:

vector();

explicit vector(size_type n);

vector(size_type n, const T& x);

vector(const vector<T>& other);

size_type capacity() const;

size_type size() const;

T& operator[](size_type x);

const T& operator[](size_type x ) const;

vector();

explicit vector(size_type n);

vector(size_type n, const T& x);

vector(const vector<T>& other);

size_type capacity() const;

size_type size() const;

T& operator[](size_type x);

const T& operator[](size_type x ) const;

Construtores. O primeiro construtor constrói um vector vazio. O segundo constrói um vector com tamanho n, e todos os n elementos são inicializados com o construtor por defeito do tipo T. O terceiro éparecido, mas os elementos são inicializados com x. O quarto é o construtor de cópia.

Capacidade: número de elementos que o vector pode conter sem precisar de crescer. Tamanho: número de elementos presentes no vector.

O operador [] só pode ser usado para referenciar um elemento que exista. Por outras palavras, o argumento x deve ser tal que0 <= x && x < size().

Page 66: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 66

Operações sobre vectores (2)Eis as mais simples:

void clear();

bool empty() const;

void pop_back();

void push_back(const T& x);

void resize(size_type n);

void resize(size_type n, T x);

void reserve(size_type n);

void clear();

bool empty() const;

void pop_back();

void push_back(const T& x);

void resize(size_type n);

void resize(size_type n, T x);

void reserve(size_type n);

A função clear remove todos os elemento do vector. A capacidade mantém-se e o tamanho fica zero.

A função pop_back remove o último elemento do vector.

A função resize muda o tamanho do vector. Note bem: muda o tamanho, acrescentando ou removendo elementos. A primeira versão, quando acrescenta, inicializa os elemento com o construtor por defeito da classe T. A segunda inicializa com x.

A função empty dá true se o vector tiver zero elementos.

A função push_back acrescenta um elemento ao vector. Se necessário, o vector cresce automaticamente (isto é, a capacidade aumenta).

A função reserve aumenta a capacidade do vector conforme indicado no argumento. Note bem: aumenta a capacidade mas não o tamanho.

Page 67: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 67

AlgoritmosCertas operações sobre vectores não provêm da classe std::vector<T> mas são aplicação de funções genéricas de natureza algorítmica:

int n = std::count(v.begin(), v.end(), x); int n = std::count(v.begin(), v.end(), x);

if (std::find(v.begin(), v.end(), x)!= v.end())

cout << "Existe. " << endl;

else

cout << "Não existe. " << endl;

if (std::find(v.begin(), v.end(), x)!= v.end())

cout << "Existe. " << endl;

else

cout << "Não existe. " << endl;

std::sort(v.begin(), v.end()); std::sort(v.begin(), v.end());

Quanto elementos iguais a x?

Existe algum elemento igual a x?

Ordenar o vector.

As expressões v.begin() e v.end() são iteradores que representam o início e o fim do vector, respectivamente.

Para usar isto épreciso fazer #include <algorithm>.

Page 68: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 68

Exercício: problema do almoçoN amigos vivendo numa cidade reticular (com ruas e avenidas perpendiculares) 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

Page 69: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 69

PolinómiosQueremos uma classe para polinómios. Porquê? Porque sim!Queremos somar polinómios, multiplicar polinómios, etc.No final, queremos calcular o polinómio que passa num dado conjunto de pontos, usando a interpolação de Lagrange.

namespace mas {

class Polynomial {

private:

int degree;

std::vector<double> a;

public:

//...

};

}

namespace mas {

class Polynomial {

private:

int degree;

std::vector<double> a;

public:

//...

};

}

Os coeficientes vêm num vector de números double a, tal que a[k] é o coeficiente de grau k. O número de coeficientes é maior ou igual a degree + 1.

O grau do polinómio vem no membro de dados degree.

O coeficiente de índice de grau degree nunca ézero, excepto se se tratar do polinómio nulo.

Page 70: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 70

ConstrutoresUm construtor constrói um polinómio de grau zero, conhecido o coeficiente de grau zero. O outro constrói um polinómio a partir de um vector de coeficientes:

class Polynomial {// ...public:explicit Polynomial(double a0 = 0.0);Polynomial(const std::vector<double>& coefs); // ...

};

class Polynomial {// ...public:explicit Polynomial(double a0 = 0.0);Polynomial(const std::vector<double>& coefs); // ...

};

Regra: quando um construtor tem um só argumento, qualificamo-lo explicit, para evitar que seja invocado para realizar conversões automáticas.

Polynomial::Polynomial(double a0):degree(0),a(1)

{a[0] = a0;

}

Polynomial::Polynomial(double a0):degree(0),a(1)

{a[0] = a0;

}

Polynomial::Polynomial(const std::vector<double>& coefs):degree(static_cast<int>(coefs.size()) - 1),a(static_cast<int>(coefs.size()))

{for (int i = 0; i <= degree; i++)a[i] = coefs[degree - i];

}

Polynomial::Polynomial(const std::vector<double>& coefs):degree(static_cast<int>(coefs.size()) - 1),a(static_cast<int>(coefs.size()))

{for (int i = 0; i <= degree; i++)a[i] = coefs[degree - i];

}

Page 71: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 71

ValorPara calcular o valor de um polinómio num ponto, usamos ou a função Value ou o operador ():class Polynomial {// ...virtual double Value(double x) const;virtual double operator ()(double x) const;// ...

};

class Polynomial {// ...virtual double Value(double x) const;virtual double operator ()(double x) const;// ...

};

Este operador é um bocado estranho. Veremos já a seguir como se usa.

double Polynomial::Value(double x) const{double result = 0.0;double m = 1.0;for (int i = 0; i <= degree; i++){result += a[i] * m;m *= x;

}return result;

}

double Polynomial::Value(double x) const{double result = 0.0;double m = 1.0;for (int i = 0; i <= degree; i++){result += a[i] * m;m *= x;

}return result;

}

double Polynomial::Value(double x) const{double result = 0.0;for (int i = degree; i >= 0; i--)result = result * x + a[i];

return result;}

double Polynomial::Value(double x) const{double result = 0.0;for (int i = degree; i >= 0; i--)result = result * x + a[i];

return result;}

Avaliamos com o método de Horner:

Assim trabalharíamos mais do que é preciso:

Este esquema usa o dobro das multiplicações.

Page 72: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 72

Operador ()O operador () é equivalente à função Value:

double Polynomial::operator ()(double x) const{

return Value(x);}

double Polynomial::operator ()(double x) const{

return Value(x);}

Eis uma função de teste que mostra como se usa:void TestValue(){double d[4] = {1.0, -2, 0.0, 5.0};std::vector<double> coefs(d, d+4);mas::Polynomial p(coefs);for (;;){double x;std::cout << "x = ";std::cin >> x;double y1 = p.Value(x);double y2 = p(x);std::cout << y1 << " " << y2 << std::endl;

}}

void TestValue(){double d[4] = {1.0, -2, 0.0, 5.0};std::vector<double> coefs(d, d+4);mas::Polynomial p(coefs);for (;;){double x;std::cout << "x = ";std::cin >> x;double y1 = p.Value(x);double y2 = p(x);std::cout << y1 << " " << y2 << std::endl;

}}

Observe esta inicialização. Ilustra um outro construtor da classe std::vector<T>.

Escrever p(x), usando o operador () é apenas uma maneira mais conveniente de escrever p.Value(x).

Page 73: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 73

Funções de gestão de memóriaA função Resize muda o número de coeficientes. A função Grow idem, mas só se crescer. A função SwapOut troca a representação interna do objecto com a do argumento.

virtual void Resize(int size); // size is number of coefficients.virtual void Grow(int size); // size is number of coefficients.virtual void SwapOut(Polynomial& other);

virtual void Resize(int size); // size is number of coefficients.virtual void Grow(int size); // size is number of coefficients.virtual void SwapOut(Polynomial& other);

void Polynomial::Resize(int size){a.resize(size);

}

void Polynomial::Resize(int size){a.resize(size);

}

Resize e Grow são simples:

void Polynomial::SwapOut(Polynomial& other){a.swap(other.a);std::swap(degree, other.degree);

}

void Polynomial::SwapOut(Polynomial& other){a.swap(other.a);std::swap(degree, other.degree);

}

void Polynomial::Grow(int size){if (size > static_cast<int>(a.size()))a.resize(size);

}

void Polynomial::Grow(int size){if (size > static_cast<int>(a.size()))a.resize(size);

}

SwapOut usa a função swap da classe std::vector<T>, que troca as representações internas de vectores, e à função genérica std::swap(..., ...), que troca os valores dos argumentos:

Estas funções são sobretudo usadas nas outras funções da classe.

Page 74: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 74

Mais funções simplesA função Degree dá o grau. A função IsNull dá true se for o polinómio nulo. A função Nullify anula o polinómio.

virtual int Degree() const;virtual bool Null() const;virtual void Nullify();

virtual int Degree() const;virtual bool Null() const;virtual void Nullify();

int Polynomial::Degree() const{return degree;

}

int Polynomial::Degree() const{return degree;

}

Implementações:

bool Polynomial::Null() const{return degree == 0 && a[0] == 0.0;

}

bool Polynomial::Null() const{return degree == 0 && a[0] == 0.0;

}

void Polynomial::Nullify(){for (int i = 0; i <= degree; i++)a[i] = 0.0;

degree = 0;}

void Polynomial::Nullify(){for (int i = 0; i <= degree; i++)a[i] = 0.0;

degree = 0;}

Page 75: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 75

O operador de igualdadeO operador == dá true se o polinómio objecto for igual ao polinómio argumento.virtual bool operator == (const Polynomial& other) const;virtual bool operator == (const Polynomial& other) const;

bool Polynomial::operator == (const Polynomial& other) const{if (degree != other.degree)return false;

for (int i = 0; i <= degree; i++)if (a[i] != other.a[i])return false;

return true;}

bool Polynomial::operator == (const Polynomial& other) const{if (degree != other.degree)return false;

for (int i = 0; i <= degree; i++)if (a[i] != other.a[i])return false;

return true;}

Se o grau não for o mesmo, os polinómios não são iguais. Sendo o grau o mesmo, a função dará false logo que encontrar um par de coeficientes do mesmo grau que sejam diferentes. Se não encontrar, dará true:

Note bem: os operadores são funções como as outras. Escrever p1 == p2 , para dois polinómios p1 e p2 é o mesmo do que escrever p1.operator == (p2), mas émais prático e mais expressivo. Analogamente, para o operador (): escrever p1(x) é o mesmo do que escrever p1.operator()(x).

Page 76: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 76

Somando polinómiosPara somar polinómios usamos o modificador Add, o operador += e o operador +:

virtual void Add(const Polynomial& other);

virtual Polynomial& operator += (const Polynomial& other);

virtual Polynomial operator + (const Polynomial& other);

virtual void Add(const Polynomial& other);

virtual Polynomial& operator += (const Polynomial& other);

virtual Polynomial operator + (const Polynomial& other);

void Polynomial::Add(const Polynomial& other)

{

Grow(other.degree + 1);

for (int i = 0; i <= other.degree; i++)

a[i] += other.a[i];

RecomputeDegree();

}

void Polynomial::Add(const Polynomial& other)

{

Grow(other.degree + 1);

for (int i = 0; i <= other.degree; i++)

a[i] += other.a[i];

RecomputeDegree();

}

Quem trabalha mesmo é a função Add:

A função RecomputeDegreerecalcula o grau após a adição, pois os coeficientes de maior grau podem ter-se anulado, ou o grau pode ter aumentado, porque o grau do argumento era maior:

void Polynomial::RecomputeDegree(){int x = static_cast<int>(a.size()) - 1;while (x > 0 && a[x] == 0.0)x--;

degree = x;}

void Polynomial::RecomputeDegree(){int x = static_cast<int>(a.size()) - 1;while (x > 0 && a[x] == 0.0)x--;

degree = x;}

Page 77: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 77

Os operadores += e +O operador += soma e retorna uma referência para o objecto.

Polynomial& Polynomial::operator +=(const Polynomial& other){Add(other);return *this;

}

Polynomial& Polynomial::operator +=(const Polynomial& other){Add(other);return *this;

}

Polynomial Polynomial::operator + (const Polynomial& other){Polynomial result(*this);return result += other;

}

Polynomial Polynomial::operator + (const Polynomial& other){Polynomial result(*this);return result += other;

}

O operador + retorna a soma por valor, isto é, retorna uma cópia da variável local onde foi calculada a soma:

Qual é a diferença entre

(p1 + p2).WriteLine();

e

(p1+=p2).WriteLine();?

Normalmente preferimos +=, pois não envolve a variáveis temporárias para os cálculos.

Esta declaração cria uma variável local result inicializada com uma cópia do objecto.

Page 78: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 78

Sobrecarregando Add, += e +Às vezes, só queremos somar um número:

virtual void Add(double y);virtual Polynomial& operator += (double y);virtual Polynomial operator + (double y);

virtual void Add(double y);virtual Polynomial& operator += (double y);virtual Polynomial operator + (double y);

void Polynomial::Add(double y){a[0] += y;

}

void Polynomial::Add(double y){a[0] += y;

} Polynomial& Polynomial::operator +=(double y){a[0] += y;return *this;

}

Basta somar ao termo de grau zero:

Polynomial& Polynomial::operator +=(double y){a[0] += y;return *this;

}Polynomial Polynomial::operator + (double y){Polynomial result(*this);return result += y;

}

Polynomial Polynomial::operator + (double y){Polynomial result(*this);return result += y;

}

Page 79: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 79

Subtraindo polinómiosSubtrair é análogo, com a função Subtract e os operadores -= e -:

virtual void Subtract(const Polynomial& other);

virtual Polynomial& operator -= (const Polynomial& other);

virtual Polynomial operator - (const Polynomial& other);

virtual void Subtract(double y);

virtual Polynomial& operator -= (double y);

virtual Polynomial operator - (double y);

virtual void Subtract(const Polynomial& other);

virtual Polynomial& operator -= (const Polynomial& other);

virtual Polynomial operator - (const Polynomial& other);

virtual void Subtract(double y);

virtual Polynomial& operator -= (double y);

virtual Polynomial operator - (double y);

As implementações são análogas às das somas.

Page 80: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 80

Multiplicando polinómiosTemos três grupos de funções sobrecarregadas Multiply, *= e *: para polinómios, para binómios representados por um par de números e para número double:

virtual void Multiply(const Polynomial& other);

virtual Polynomial& operator *= (const Polynomial& other);

virtual Polynomial operator * (const Polynomial& other);

virtual void Multiply(const std::pair<double, double>& m);

virtual Polynomial& operator *= (const std::pair<double, double>& m);

virtual Polynomial operator * (const std::pair<double, double>& m);

virtual void Multiply(double y);

virtual Polynomial& operator *= (double y);

virtual Polynomial operator * (double y);

virtual void Multiply(const Polynomial& other);

virtual Polynomial& operator *= (const Polynomial& other);

virtual Polynomial operator * (const Polynomial& other);

virtual void Multiply(const std::pair<double, double>& m);

virtual Polynomial& operator *= (const std::pair<double, double>& m);

virtual Polynomial operator * (const std::pair<double, double>& m);

virtual void Multiply(double y);

virtual Polynomial& operator *= (double y);

virtual Polynomial operator * (double y);

Vejamos o segundo grupo, que é mais interessante.

Multiplicando por outro polinómio.

Multiplicando por um binómio.

Multiplicando por uma constante.

Page 81: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 81

Multiplicando por um binómioFrequentemente queremos multiplicar um polinómio por um binómio na forma ax+b. Usamos o par std::make_pair(a, b)para representar esse binómio.void Polynomial::Multiply(const std::pair<double, double>& m){degree++;Grow(degree + 1);a[degree] = a[degree - 1] * m.first;for (int i = degree - 1; i > 0; i--)a[i] = a[i-1] * m.first + a[i] * m.second;

a[0] *= m.second;}

void Polynomial::Multiply(const std::pair<double, double>& m){degree++;Grow(degree + 1);a[degree] = a[degree - 1] * m.first;for (int i = degree - 1; i > 0; i--)a[i] = a[i-1] * m.first + a[i] * m.second;

a[0] *= m.second;}

Polynomial& Polynomial::operator *= (const std::pair<double, double>& m){Multiply(m);return *this;

}

Polynomial& Polynomial::operator *= (const std::pair<double, double>& m){Multiply(m);return *this;

}Polynomial Polynomial::operator * (const std::pair<double, double>& m){Polynomial result(*this);return result *= m;

}

Polynomial Polynomial::operator * (const std::pair<double, double>& m){Polynomial result(*this);return result *= m;

}

Parece complicado mas até nem é.

Page 82: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 82

Dividindo por um binómioApenas incluímos as operações para dividir um polinómio por um binómio:

virtual void Divide(const std::pair<double, double>& m, double& remainder);

virtual Polynomial& operator /= (const std::pair<double, double>& m);

virtual Polynomial operator / (const std::pair<double, double>& m);

virtual void Divide(const std::pair<double, double>& m, double& remainder);

virtual Polynomial& operator /= (const std::pair<double, double>& m);

virtual Polynomial operator / (const std::pair<double, double>& m);

Polynomial& Polynomial::operator /= (const std::pair<double, double>& m){double temp;Divide(m, temp);return *this;

}

Polynomial& Polynomial::operator /= (const std::pair<double, double>& m){double temp;Divide(m, temp);return *this;

}

Polynomial Polynomial::operator / (const std::pair<double, double>& m){Polynomial result(*this);return result /= m;

}

Polynomial Polynomial::operator / (const std::pair<double, double>& m){Polynomial result(*this);return result /= m;

}

A divisão faz-se usando a regra de Ruffini (ver página seguinte). As outras duas operações são como de costume, mas ignoram o resto da divisão:

O resto é passado de volta num argu-mento de saída, por referência.

Page 83: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 83

Regra de RuffiniEis a regra de Ruffini, ligeiramente modificada para dividir por ax+b (e não por x-k):void Polynomial::Divide(const std::pair<double, double>& m, double& remainder)

{

// Ruffini's Rule

Polynomial temp;

temp.degree = degree - 1;

temp.Resize(temp.degree + 1);

temp.a[temp.degree] = a[degree];

double k = -m.second / m.first;

for (int i = temp.degree ; i >= 1; i--)

temp.a[i - 1] = temp.a[i] * k + a[i];

remainder = temp.a[0] * k + a[0];

temp.Multiply(1 / m.first);

SwapOut(temp);

}

void Polynomial::Divide(const std::pair<double, double>& m, double& remainder)

{

// Ruffini's Rule

Polynomial temp;

temp.degree = degree - 1;

temp.Resize(temp.degree + 1);

temp.a[temp.degree] = a[degree];

double k = -m.second / m.first;

for (int i = temp.degree ; i >= 1; i--)

temp.a[i - 1] = temp.a[i] * k + a[i];

remainder = temp.a[0] * k + a[0];

temp.Multiply(1 / m.first);

SwapOut(temp);

}

Observe a técnica do SwapOut: o resultado, que é do tipo da classe, é calculado numa variável local. No final, o objecto é swapped out com a variável local. Como resultado, o objecto fica com o valor da variável local, tal como pretendido, e o anterior valor do objecto fica na variável local, que vai desaparecer assim que a função termina.

Page 84: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 84

Derivando e primitivandoA derivada de um polinómio é um polinómio. A primitiva também:

virtual void Differentiate();

virtual void Integrate(double a0 = 0.0);

virtual void Differentiate();

virtual void Integrate(double a0 = 0.0);

void Polynomial::Differentiate()

{

for (int i = 1; i <= degree; i++)

a[i-1] = i * a[i];

if (degree > 0)

degree--;

else

a[0] = 0.0;

}

void Polynomial::Differentiate()

{

for (int i = 1; i <= degree; i++)

a[i-1] = i * a[i];

if (degree > 0)

degree--;

else

a[0] = 0.0;

}

void Polynomial::Integrate(double a0){if (Null())a[0] = a0;

else{Grow(degree + 1);for (int i = degree; i >= 0; i--)a[i+1] = a[i] / (i+1);

a[0] = a0;degree++;

}}

void Polynomial::Integrate(double a0){if (Null())a[0] = a0;

else{Grow(degree + 1);for (int i = degree; i >= 0; i--)a[i+1] = a[i] / (i+1);

a[0] = a0;degree++;

}}

Page 85: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 85

Derivada num ponto e integralTambém sabemos calcular a derivada num ponto e o integral num intervalo:

virtual double Derivative(double x) const;

virtual double Integral(double x0, double x1) const;

virtual double Derivative(double x) const;

virtual double Integral(double x0, double x1) const;

double Polynomial::Derivative(double x) const{Polynomial temp(*this);temp.Differentiate();return temp(x);

}

double Polynomial::Derivative(double x) const{Polynomial temp(*this);temp.Differentiate();return temp(x);

}

double Polynomial::Integral(double x0, double x1) const{Polynomial temp(*this);temp.Integrate();return temp(x1) - temp(x0);

}

double Polynomial::Integral(double x0, double x1) const{Polynomial temp(*this);temp.Integrate();return temp(x1) - temp(x0);

}

Usam as duas a mesma técnica:

Page 86: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 86

Lendo e escrevendoA classe inclui funções para ler e escrever um polinómio, num formato simples:

virtual void Write(std::ostream& output = std::cout) const;virtual void WriteLine(std::ostream& output = std::cout) const;

virtual void Read(std::istream& input = std::cin);

virtual void Write(std::ostream& output = std::cout) const;virtual void WriteLine(std::ostream& output = std::cout) const;

virtual void Read(std::istream& input = std::cin);

void Polynomial::Write(std::ostream& output) const{int degree = Degree();for (int i = 0; i <= degree; i++)output << " " << a[degree - i];

}

void Polynomial::Write(std::ostream& output) const{int degree = Degree();for (int i = 0; i <= degree; i++)output << " " << a[degree - i];

}

void Polynomial::WriteLine(std::ostream& output) const{Write(output);output << std::endl;

}

void Polynomial::WriteLine(std::ostream& output) const{Write(output);output << std::endl;

}

void Polynomial::Read(std::istream& input){input >> degree;Grow(degree + 1);for (int i = 0; i <= degree; i++)input >> a[degree - i];

for (int i = degree + 1; i < static_cast<int>(a.size()); i++)a[i] = 0.0;

}

void Polynomial::Read(std::istream& input){input >> degree;Grow(degree + 1);for (int i = 0; i <= degree; i++)input >> a[degree - i];

for (int i = degree + 1; i < static_cast<int>(a.size()); i++)a[i] = 0.0;

}

Ao ler, primeiro lê-se o grau e depois os coeficientes. As demais posições são anuladas.

Ao escrever, escrevem-se sóos coeficientes.

Page 87: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 87

Desenhando o gráficoA função Plot escreve num ficheiro uma tabela de valores para posterior envio para um programa capaz de desenhar gráficos:

virtual void Plot(std::ostream& output, double x0, double x1, double h) const;virtual void Plot(std::ostream& output, double x0, double x1, double h) const;

void Polynomial::Plot(double x0, double x1, double h, std::ostream& output) const{output << std::fixed << std::setprecision(4);if (x0 > x1)return;

double x = x0;while (x < x1){output << x << "\t" << Value(x) << std::endl;x += h;

}output << x1 << "\t" << Value(x1) << std::endl;

}

void Polynomial::Plot(double x0, double x1, double h, std::ostream& output) const{output << std::fixed << std::setprecision(4);if (x0 > x1)return;

double x = x0;while (x < x1){output << x << "\t" << Value(x) << std::endl;x += h;

}output << x1 << "\t" << Value(x1) << std::endl;

}

Ficam dois valores em cada linha, separados por um tab:

O último par de valores é escrito fora do ciclo, para acertar.

Page 88: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 88

Testando o gráficoEis uma função de teste para a função Plot:

void TestPlot(){std::ofstream output("plot.xls");mas::Polynomial p;std::cout << "grau e coeficientes: ";p.Read();std::cout << p.Degree() << "- ";p.WriteLine();double a;double b;std::cout << "Intervalo: ";std::cin >> a >> b;double delta;std::cout << "Delta: ";std::cin >> delta;p.Plot(a, b, delta, output);

}

void TestPlot(){std::ofstream output("plot.xls");mas::Polynomial p;std::cout << "grau e coeficientes: ";p.Read();std::cout << p.Degree() << "- ";p.WriteLine();double a;double b;std::cout << "Intervalo: ";std::cin >> a >> b;double delta;std::cout << "Delta: ";std::cin >> delta;p.Plot(a, b, delta, output);

}

-40

-30

-20

-10

0

10

20

30

40

-4 -2 0 2 4 6

O ficheiro abre directamente no Excel e produz o seguinte gráfico:

Exemplo de execução:

Page 89: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 89

Funções estáticasAcrescentemos à classe Polynomial uma função para o binómio de Newton, com expoente inteiro não negativo. A função devolve um polinómio, mas não tem objecto, pois não parte de nenhum polinómio para o fazer. (Por outras palavras, não é um selector nem um modificador.) É uma função estática.class Polynomial {private:int degree;std::vector<double> a;

public:// ...virtual double Value(double x) const;virtual double operator ()(double x) const;// ...

public: // static functionsstatic Polynomial Newton(double a, double b, int n); // computes (ax+b)^n;

};

class Polynomial {private:int degree;std::vector<double> a;

public:// ...virtual double Value(double x) const;virtual double operator ()(double x) const;// ...

public: // static functionsstatic Polynomial Newton(double a, double b, int n); // computes (ax+b)^n;

};

As funções estáticas não têm objecto. Ao chamá-las, qualificamos com o nome da classe, por exemplo:

//...mas::Polynomial p;// ...p = mas::Polynomial::Newton(1.0, -2.0, 8);

Não é obrigatório ter uma zona para funções estáticas, mas fica bem.

Page 90: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 90

Triângulo de PascalUm exercício clássico de programação é escrever o triângulo de Pascal:11 11 2 11 3 3 11 4 6 4 11 5 10 10 5 1....Programe uma função estática na classe Polynomial para fazer isto, até à ordem n.Baseie-se numa função estática que calcula os números da n-ésima linha:static Polynomial Pascal(int n); // computes (x + 1)^n;static Polynomial Pascal(int n); // computes (x + 1)^n;

Page 91: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 91

Interpolação de Lagrange (1)Já sabemos que por n pontos do plano com abcissas diferentes passa um único polinómio de grau inferior ou igual a n-1. A sua missão, should you decide to accept it, é escrever uma função estática na classe Polynomial para calcular esse polinómio, usando o método da interpolação de Lagrange.Expliquemos o método por meio de um exemplo. Os pontos são (-2, 5), (1, -4) e (5, 12). (Claro que neste caso vai ser um polinómio do segundo grau cujos coeficientes se podem calcular com um sistema de três equações, mas nós queremos é a interpolação de Lagrange.)Consideremos o polinómio P(x) = (x+2)(x-1)(x-5). Então P(x) = x3-4x2-7x+10. Este polinómio anula-se para x=-2, para x=1 e para x=5, claro. Se o dividirmos por (x+2) obtemos Q1(x) = x2-6x+5. Ora Q1(x) anula-se para x=1 e para x=5 e vale 21 para x=-2. Logo o polinómio R1(x) = Q1(x) * 5 / 21 anula-se para x=1 e para x=5 e vale 5 para x=-2.

Page 92: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 92

Interpolação de Lagrange (2)(Continuação)Calculemos de seguida os polinómios Q2(x) e R2(x), fazendo os mesmos cálculos para o segundo ponto, e os polinómios Q3(x) e R3(x), para o terceiro ponto.O polinómio que procuramos é R1(x) + R2(x) + R3(x), claramente, e o seu grau não será maior do que 2. É isto a interpolação de Lagrange.A declaração da função deve ser assim:

static Polynomial Lagrange(const std::vector<double>& x, const std::vector<double>& y);// pre x.size() >= 1 && x.size() == y.size();

static Polynomial Lagrange(const std::vector<double>& x, const std::vector<double>& y);// pre x.size() >= 1 && x.size() == y.size();

Cuidado com o caso dos pontos da forma (x, 0). Para estes, o polinómio Q(x), que se anula nas abcissas dos outros pontos e na deste também, é o polinómio nulo.

O primeiro argumento é o vector das abcissas e o segundo o das ordenadas.

A solução do exemplo é o polinómio x2-2x-3.

Page 93: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 93

Mais classes: a classe DateQueremos uma classe Date para representar datas do calendário gregoriano. O calendário Gregoriano foi instituído pelo papa Gregório XIII em 1582. (Sobre outros calendários veja http://www.oal.ul.pt/publicacoes/eras.html.)Queremos funções para calcular quantos dias vão de uma data a outra, para calcular a data daqui a n dias, para calcular o dia da semana, etc.O construtor por defeito dá a data de “hoje”.Haverá selectores para o ano, para o mês e para o dia e ainda para número de ordem do dia no ano.Há-de ser preciso saber se um ano é bissexto e se três números ano, mês e dia formam uma data válida.

Page 94: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 94

Classe Date

namespace mas {

class Date {private:

int year;int month;int day;

public:enum WeekdayType {MONDAY, TUESDAY, WEDNESDAY, THURSDAY,

FRIDAY, SATURDAY, SUNDAY};

Date();Date(const Date& other);Date(int year, int month, int day); // pre Valid(year, month, day);

virtual ~Date();//...

};

}

namespace mas {

class Date {private:

int year;int month;int day;

public:enum WeekdayType {MONDAY, TUESDAY, WEDNESDAY, THURSDAY,

FRIDAY, SATURDAY, SUNDAY};

Date();Date(const Date& other);Date(int year, int month, int day); // pre Valid(year, month, day);

virtual ~Date();//...

};

}

A classe Date reside no espaço de nomes mas:Três membros de dados, para o ano, para o mês e para o dia.

Um tipo enumerado para os dias da semana.

Construtor por defeito, construtor de cópia, construtor elementar.

Destrutor.

Esta precondição usa uma função estática Valid, ainda por declarar.

Page 95: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 95

Mais classe Dateclass Date {//...

virtual int Year() const;virtual int Month() const;virtual int Day() const;virtual int Count() const;

virtual void Set(int year, int month, int day); // pre Valid(year, month, day);virtual void Forth();virtual void Back(); // pre operator > (First());virtual void Add(int x); // pre x >= 0;virtual void Subtract(int x); // pre x >= 0 && DaysSince(First()) >= x;virtual const Date& First() const;virtual int MonthSize() const;

virtual int DaysTo(const Date& other) const; // pre operator <= (other);virtual int DaysSince(const Date& other) const; // pre operator >= (other);

//...};

class Date {//...

virtual int Year() const;virtual int Month() const;virtual int Day() const;virtual int Count() const;

virtual void Set(int year, int month, int day); // pre Valid(year, month, day);virtual void Forth();virtual void Back(); // pre operator > (First());virtual void Add(int x); // pre x >= 0;virtual void Subtract(int x); // pre x >= 0 && DaysSince(First()) >= x;virtual const Date& First() const;virtual int MonthSize() const;

virtual int DaysTo(const Date& other) const; // pre operator <= (other);virtual int DaysSince(const Date& other) const; // pre operator >= (other);

//...};

Selectores para o ano, para o mês, para o dia, e para o número de dias desde o início. Início de quê? Início do nosso calendário.

Dias que faltam para other. Dias que passaram desde other.

Mudar o valor do objecto, com novos valores para o ano, mês e dia.

Avançar 1 dia. Recuar 1 dia.

Avançar x dias. Recuar x dias.

Data do primeiro dia do nosso calendário.

Quantos dias tem o mês do objecto?

Page 96: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 96

Classe Date: operadoresclass Date {//...

virtual bool operator == (const Date& other) const;virtual bool operator != (const Date& other) const;virtual bool operator <= (const Date& other) const;virtual bool operator < (const Date& other) const;virtual bool operator >= (const Date& other) const;virtual bool operator > (const Date& other) const;

virtual Date& operator ++ (int);virtual Date& operator -- (int); // pre *this > First();

virtual Date operator +(int x) const; // pre x >= 0;virtual Date& operator +=(int x); // pre x >= 0;virtual Date operator -(int x) const; // pre x >= 0 && Count() >= x;virtual Date& operator -=(int x); // pre x >= 0 && Count() >= x;//...

};

class Date {//...

virtual bool operator == (const Date& other) const;virtual bool operator != (const Date& other) const;virtual bool operator <= (const Date& other) const;virtual bool operator < (const Date& other) const;virtual bool operator >= (const Date& other) const;virtual bool operator > (const Date& other) const;

virtual Date& operator ++ (int);virtual Date& operator -- (int); // pre *this > First();

virtual Date operator +(int x) const; // pre x >= 0;virtual Date& operator +=(int x); // pre x >= 0;virtual Date operator -(int x) const; // pre x >= 0 && Count() >= x;virtual Date& operator -=(int x); // pre x >= 0 && Count() >= x;//...

};

Certas funções são operadores: Operadores de comparação. Permitir-nos-ão escrever coisas como:Date d1;Date d2;// ...

if (d1 <= d2)...

//...while (d1 != d2)

...

Operadores de incremen-tação e decrementação. Poderemos escrever d++em vez de d.Forth() e d--em vez de d.Back().

Operadores +, -, += e -=. Note que += e -= são modificadores e que + e – são “pseudo-construtores”. (Os pseudo-construtores são selectores que retornam um objecto do tipo da classe.)

Page 97: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 97

Classe Date: lendo e escrevendo

class Date {//...

virtual void Write(std::ostream& output = std::cout) const;virtual void WriteLine(std::ostream& output = std::cout) const;virtual void Read(std::istream& input = std::cin);virtual void Accept(const std::string& prompt, const std::string& errorMessage);friend std::ostream& operator << (std::ostream& output, const Date& d);//...

};

class Date {//...

virtual void Write(std::ostream& output = std::cout) const;virtual void WriteLine(std::ostream& output = std::cout) const;virtual void Read(std::istream& input = std::cin);virtual void Accept(const std::string& prompt, const std::string& errorMessage);friend std::ostream& operator << (std::ostream& output, const Date& d);//...

};

A classe Date contém as três funções habituais de leitura e escrita: Write, WriteLine e Read. Por defeito, estas três usam a consola. A função Accept é para leituras interactivas. Os argumentos são o pronto e a mensagem de erro que será afixada caso a data entrada não seja válida.O operador amigo << permite incluir datas nos comboios <<.

Com este operador poderemos programas coisas como:Date d;//...std::cout << “A data é " << d << " e a do dia seguinte é " << d+1 << std::endl;

Page 98: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 98

Classe Date: semanas

class Date {//...

virtual WeekdayType Weekday() const;virtual void AddWeek(int x);virtual void SubtractWeek(int x); // pre Count() >= 7 * x;virtual bool Weekend() const;

//... };

class Date {//...

virtual WeekdayType Weekday() const;virtual void AddWeek(int x);virtual void SubtractWeek(int x); // pre Count() >= 7 * x;virtual bool Weekend() const;

//... };

Eis algumas funções sobre semanas:

Repare que o resultado é de tipo WeekdayType.

void Date::AddWeek(int x){

Add(7 * x);}

void Date::AddWeek(int x){

Add(7 * x);}

void Date::SubtractWeek(int x){

Subtract(7 * x);}

void Date::SubtractWeek(int x){

Subtract(7 * x);}

Podemos programar logo algumas delas:

bool Date::Weekend() const{

return Weekday() >= SATURDAY;}

bool Date::Weekend() const{

return Weekday() >= SATURDAY;}

Note bem: as semanas começam na segunda-feira e acabam no domingo.

Page 99: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 99

Classe Date: funções estáticas

class Date {//...public: // static

static bool Valid(int y, int m, int d);static bool Valid(int y, int m);static bool Valid(int y);

static bool LeapYear(int y); // pre Valid(y);static int DaysInMonth(int y, int m); // pre Valid(y, m);static int DaysInYear(int y); // pre Valid(y);

private: // static data membersstatic const int firstYear;static const Date first;static const WeekdayType firstWeekday;static const int daysInMonth[];

};

class Date {//...public: // static

static bool Valid(int y, int m, int d);static bool Valid(int y, int m);static bool Valid(int y);

static bool LeapYear(int y); // pre Valid(y);static int DaysInMonth(int y, int m); // pre Valid(y, m);static int DaysInYear(int y); // pre Valid(y);

private: // static data membersstatic const int firstYear;static const Date first;static const WeekdayType firstWeekday;static const int daysInMonth[];

};

As funções estáticas pertencem à classe, mas não operam sobre os objectos. Operam apenas sobre os seus argumentos e sobre os membros estáticos da classe. Não confunda membros

estáticos com mem-bros de dados. Cada objecto tem os seus próprios membros de dados. Os mem-bros estáticos são da classe e são partilhados por todos os objectos.

Os membros estáticos são inicializados no ficheiro .cpp.

Primeiro ano do calendário, primeiro dia do calendário, dia da semana respectivo e tabela dos números de dias dos meses nos anos comuns,

Page 100: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 100

Construtores de datasO construtor por defeito, que dá a data de “hoje”, é o mais sofisticado: Date::Date(){

time_t time0 = ::time(0);struct ::tm *now = ::localtime(&time0);year = now->tm_year + 1900;month = now->tm_mon + 1;day = now->tm_mday;

}

Date::Date(){

time_t time0 = ::time(0);struct ::tm *now = ::localtime(&time0);year = now->tm_year + 1900;month = now->tm_mon + 1;day = now->tm_mday;

}

A variável time0 recebe o tempo corrente, calculado pela função de biblioteca ::time, que dá o número de segundos desde as zero horas de 1 de Janeiro de 1970, UTC. Depois, a função ::localtime converte esse número para uma variável de tipo ::tm que tem membros para o ano, mês, dia (e ainda outros que não usamos aqui), tendo em conta a zona horária. Essa variável é apontada pela variável local now. Para usar estas funções é preciso fazer#include <ctime>. (Para mais informação, consulte a documentação.)

Date::Date(int year, int month, int day):year(year),month(month),day(day)

{}

Date::Date(int year, int month, int day):year(year),month(month),day(day)

{}

Date::Date(const Date& other):year(other.year),month(other.month),day(other.day)

{}

Date::Date(const Date& other):year(other.year),month(other.month),day(other.day)

{}

Os outros construtores são rotineiros:

Page 101: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 101

Destrutor de datasO destrutor das datas não faz nada. No entanto, devemos programá-lo:

Date::~Date()

{

}

Date::~Date()

{

}

Claro que mais à frente veremos classes cujos destrutores fazem coisas interessantes.

Page 102: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 102

Selectores de datasOs selectores para o ano, para o mês, e para o dia são muito simples:int Date::Year() const{

return year;}

int Date::Month() const{

return month;}

int Date::Day() const{

return day;}

int Date::Year() const{

return year;}

int Date::Month() const{

return month;}

int Date::Day() const{

return day;}

int Date::Count() const{

return DaysSince(first);}

int Date::Count() const{

return DaysSince(first);}

O selector para o número de dias desde o início do calendário recorre à função DaysSince:

first é o membro estático que dáa data do primeiro dia do calendário. É inicializado assim:const int Date::firstYear = 1901;const Date Date::first(Date::firstYear, 1, 1);

const int Date::firstYear = 1901;const Date Date::first(Date::firstYear, 1, 1);

const Date& Date::First() const{

return first;}

const Date& Date::First() const{

return first;}

A função Firstdevolve a data do primeiro dia.

Page 103: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 103

Modificadores de datas, avançandoAvança-se um dia com a função Forth:void Date::Forth(){

if (day < MonthSize())day++;

else{

day = 1;if (month < 12)

month++;else{

month = 1;year++;

}}

}

void Date::Forth(){

if (day < MonthSize())day++;

else{

day = 1;if (month < 12)

month++;else{

month = 1;year++;

}}

}

void Date::Add(int x){

for (int i = 0; i < x; i++)Forth();

}

void Date::Add(int x){

for (int i = 0; i < x; i++)Forth();

}

Se não estivermos no fim do mês, incrementamos o dia. Se não, o dia é 1 e se o mês não for Dezembro, incrementamos o mês. Se não, o mês éJaneiro e incrementamos o ano.

Avança-se x dias com a função Add:

A função MonthSizediz quantos dias tem o mês. Qual mês? O mês a que pertence o objecto.

Isto não é lá muito eficiente, se x for grande . Por outro lado, se x for negativo, nada acontece.

Avança-se x semanas somando 7*x dias:void Date::AddWeek(int x){

Add(7 * x);}

void Date::AddWeek(int x){

Add(7 * x);}

Page 104: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 104

Modificadores de datas, recuando

Recua-se um dia com a função Back:void Date::Back(){

if (day > 1)day--;

else{

if (month > 1)month--;

else{

month = 12;year--;

}day = MonthSize();

}}

void Date::Back(){

if (day > 1)day--;

else{

if (month > 1)month--;

else{

month = 12;year--;

}day = MonthSize();

}}

void Date::Subtract(int x){

for (int i = 0; i < x; i++)Back();

}

void Date::Subtract(int x){

for (int i = 0; i < x; i++)Back();

}

Recua-se x dias com a função Subtract:

Recua-se x semanas subtraindo 7*x dias:void Date::SubtractWeek(int x){

Subtract(7 * x);}

void Date::SubtractWeek(int x){

Subtract(7 * x);}

São os três muito parecidos com os três para avançar.

Page 105: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 105

Quantos dias tem o mês?A variável estática daysInMonth regista o número de dias de cada mês num ano comum. É inicializada assim:const int Date::daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

A função estática DaysInMonth baseia-se neste quadro de valores, corrigindo o caso de Fevereiro nos anos bissextos:

const int Date::daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int Date::MonthSize() const{

return DaysInMonth(year, month);}

int Date::MonthSize() const{

return DaysInMonth(year, month);}

int Date::DaysInMonth(int y, int m){

return daysInMonth[m - 1] + (m == 2 && LeapYear(y));}

int Date::DaysInMonth(int y, int m){

return daysInMonth[m - 1] + (m == 2 && LeapYear(y));}

A função MonthSize é um selector. Recorre àfunção estática DaysInMonth:

Esta função éestática: trabalha sobre os seus argumentos e sobre os membros estáticos.

Esta função é virtual (não é estática): trabalha sobre os membros de dados do objecto.

Page 106: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 106

Anos bissextosNo calendário gregoriano, são bissextos os anos múltiplos de 4, excepto os múltiplos de 100, excepto (de entre destes) os múltiplos de 400:

int Date::DaysInYear(int y){

return 365 + LeapYear(y);}

int Date::DaysInYear(int y){

return 365 + LeapYear(y);}

bool Date::LeapYear(int y){

return y % 400 == 0 || y % 4 == 0 && y % 100 != 0;}

bool Date::LeapYear(int y){

return y % 400 == 0 || y % 4 == 0 && y % 100 != 0;}

A função estática DaysInYear calcula o número de dias do ano passado em argumento:

Na verdade, o que estáprogramado é: um ano ébissexto se for múltiplo de 400 ou se for múltiplo de 4 mas não de 100.

Esta função também é estática: só trabalha sobre o seu argumento.

Page 107: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 107

Quantos dias faltam?A função DaysTo conta os dias até uma data no futuro, isto é, uma data posterior (ou igual) ao objecto:

int Date::DaysSince(const Date& other) const{

return other.DaysTo(*this);}

int Date::DaysSince(const Date& other) const{

return other.DaysTo(*this);}

int Date::DaysTo(const Date& other) const{

int result = 0;Date temp(*this);while (temp != other){temp.Forth();result++;

}return result;

}

int Date::DaysTo(const Date& other) const{

int result = 0;Date temp(*this);while (temp != other){temp.Forth();result++;

}return result;

}

A função DaysInYear conta os dias desde uma data no passado:

Repare que se a data other for anterior à data objecto da função, o ciclo nunca termina.

Isto é o construtor de cópia: a data temp é inicializado com uma cópia da data objecto da função, isto é, da data para a qual a função DaysTo foi chamada.

Recorde que em cada função membro, o apontador this édeclarado implicitamente e “aponta” para o objecto da função. A expressão *this representa esse objecto.

Page 108: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 108

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.

A instrução while já tinha surgido antes. Fica aqui registada para referência.

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 109: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 109

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 110: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 110

Operadores de comparaçãoUma data é igual a outra se os seus membros de dados tiverem os mesmos valores do que os da outra:

bool Date::operator != (const Date& other) const{

return ! operator == (other);}

bool Date::operator != (const Date& other) const{

return ! operator == (other);}

bool Date::operator == (const Date& other) const{

return this->day == other.day&& this->month == other.month&& this->year == other.year;

}

bool Date::operator == (const Date& other) const{

return this->day == other.day&& this->month == other.month&& this->year == other.year;

}

Recorde que, para o compilador, d1==d2 (sendo d1 e d2 duas expressões de tipo Date) é o mesmo que d1.operator ==(d2).

Usamos o this aqui apenas por uma questão de estilo, para contrabalançar o other, mas poderíamos ter simplificado:

return day == other.day && ...;

Ser diferente é não ser igual: Aqui podíamos ter escritoreturn !(*this == other);

mas não return *this != other;

pois seria uma definição recursiva, errada neste caso.

Page 111: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 111

Antes e depoisUma data é menor ou igual do que outra se o ano for menor, ou sendo o ano igual, se o mês for menor ou sendo o mês igual, se dia for menor ou igual:bool Date::operator <= (const Date& other) const{

return this->year < other.year|| this->year == other.year&& (this->month < other.month

|| this->month == other.month&& this->day <= other.day);

}

bool Date::operator <= (const Date& other) const{

return this->year < other.year|| this->year == other.year&& (this->month < other.month

|| this->month == other.month&& this->day <= other.day);

}bool Date::operator < (const Date& other) const{

return operator <= (other) && ! operator == (other);}

bool Date::operator < (const Date& other) const{

return operator <= (other) && ! operator == (other);}

bool Date::operator >= (const Date& other) const{

return ! operator <= (other) || operator == (other);}

bool Date::operator >= (const Date& other) const{

return ! operator <= (other) || operator == (other);}

bool Date::operator > (const Date& other) const{

return ! operator <= (other);}

bool Date::operator > (const Date& other) const{

return ! operator <= (other);}

os outros operadoressão programados em termos dos anteriores.

Para deixar as coi-sas mais simples,

Page 112: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 112

Incrementando, decrementando datasNa “espírito” do C++, habitualmente preferimos “somar 1”usando o operador ++ e “subtrair 1” usando o operador --:Date& Date::operator ++ (int){

Forth();return *this;

}

Date& Date::operator ++ (int){

Forth();return *this;

}

Date& Date::operator -- (int){

Back();return *this;

}

Date& Date::operator -- (int){

Back();return *this;

}

Agora podemos escrever d++ em vez de d.Forth(). Aliás, sendo d++ uma expressão de tipo Date, até podemos escrever d++.WriteLine(), por exemplo.Repare que estamos a definir os operadores pós-fixos. Não temos na classe Date operadores prefixos (que se usariam na forma ++d).

O argumento não é usado. Serve apenas para assinalar convencionalmente que estes são os operadores pós-fixos. Sem este argumento fictício, estaríamos a definir os operadores prefixos.

Page 113: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 113

Somando, subtraindoOs operadores +, +=, -, -= são análogos aos da classe Polynomial:

Date Date::operator +(int x) const

{

return Date(*this) += x;

}

Date& Date::operator +=(int x)

{

Add(x);

return *this;

}

Date Date::operator +(int x) const

{

return Date(*this) += x;

}

Date& Date::operator +=(int x)

{

Add(x);

return *this;

}

Date Date::operator -(int x) const

{

return Date(*this) -= x;

}

Date& Date::operator -=(int x)

{

Subtract(x);

return *this;

}

Date Date::operator -(int x) const

{

return Date(*this) -= x;

}

Date& Date::operator -=(int x)

{

Subtract(x);

return *this;

}

Repare nesta técnica.

Page 114: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 114

Escrevendo datasTemos as funções Write, WriteLine e o operador << na combinação que se tornará habitual:void Date::Write(std::ostream& output) const{

output << year << " " << month << " " << day;}

void Date::Write(std::ostream& output) const{

output << year << " " << month << " " << day;}

std::ostream& operator << (std::ostream& output, const Date& d){

d.Write(output);return output;

}

std::ostream& operator << (std::ostream& output, const Date& d){

d.Write(output);return output;

}

void Date::WriteLine(std::ostream& output) const

{

Write(output);

output << std::endl;

}

void Date::WriteLine(std::ostream& output) const

{

Write(output);

output << std::endl;

}

Estas duas são funções virtuais. Pertencem à classe Date.

Esta é uma função amiga. Está fora da classe.

Page 115: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 115

Lendo datasPara ler ano, mês e dia a partir de um ficheiro, usamos a função Read:

void Date::Read(std::istream& input){

input >> year >> month>> day;}

void Date::Read(std::istream& input){

input >> year >> month>> day;}

void Date::Accept(const std::string& prompt, const std::string& errorMessage){

int year = 0;int month = 0;int day = 0;for(;;){

std::cout << prompt;std::cin >> year >> month >> day;if (std::cin.fail()) // if something went wrong

std::cin.clear(); // reset io state flagsstd::cin.ignore(std::numeric_limits<int>::max(), '\n'); // skip to end of lineif (Date::Valid(year, month, day))

break;std::cout << errorMessage << std::endl;

}Set(year, month, day);

}

void Date::Accept(const std::string& prompt, const std::string& errorMessage){

int year = 0;int month = 0;int day = 0;for(;;){

std::cout << prompt;std::cin >> year >> month >> day;if (std::cin.fail()) // if something went wrong

std::cin.clear(); // reset io state flagsstd::cin.ignore(std::numeric_limits<int>::max(), '\n'); // skip to end of lineif (Date::Valid(year, month, day))

break;std::cout << errorMessage << std::endl;

}Set(year, month, day);

}

Para ler interactivamente a partir da consola, usamos a função Accept:

Ciclo infinito. Quebra quando os três números lidos formarem uma data válida.

Quando a função termina, o objecto é uma data válida.

Repare na técnica usada para saltar o resto da linha corrente.

A leitura falhará se o utilizador entrar caracteres que não possam pertencer a um número decimal (letras, por exemplo.)

Page 116: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 116

Datas válidasTodas as datas são válidas, isto é, todos os objectos da classe Date representam datas válidas no calendário gregoriano, se as funções forem usadas de acordo com as precondições.No construtor elementar, por exemplo, a precondição determina que os três argumentos devem poder formar uma data válida. A função Valid exprime essa “validade”:bool Date::Valid(int y, int m, int d){

return Valid(y, m) && 1 <= d && d <= DaysInMonth(y, m);}

bool Date::Valid(int y, int m, int d){

return Valid(y, m) && 1 <= d && d <= DaysInMonth(y, m);}

bool Date::Valid(int y, int m){

return Valid(y) && 1 <= m && m <= 12;}

bool Date::Valid(int y, int m){

return Valid(y) && 1 <= m && m <= 12;}

bool Date::Valid(int y){

return firstYear <= y;}

bool Date::Valid(int y){

return firstYear <= y;}

Estas três funções são

funções estáticas.

A função Valid com três argumentos (o ano, o mês e o dia) usa a função Valid com dois argumentos (o ano e o mês) e esta usa a função Valid com um argumento (o ano):

Page 117: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 117

O dia da semanaPara calcular o dia da semana, basta recordar que o primeiro dia do nosso calendário, 1 de Janeiro de 1901, foi uma terça-feira:const Date::WeekdayType Date::firstWeekday = TUESDAY;const Date::WeekdayType Date::firstWeekday = TUESDAY;

Date::WeekdayType Date::Weekday() const{

return static_cast<WeekdayType>((Count() + firstWeekday) % 7);}

Date::WeekdayType Date::Weekday() const{

return static_cast<WeekdayType>((Count() + firstWeekday) % 7);}

Contamos os dias que passaram, ajustamos porque a primeira semana do calendário começou numa terça-feira e usamos o resto da divisão por 7:

Inicialização da constante estática.

Repare que é preciso fazer uma conversão estática para passar de um tipo inteiro para um tipo enumerado, mas não para o contrário.

Page 118: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 118

Sexta-feira 13Nós, engenheiros, não somos supersticiosos. Mas é preciso cuidado com as sextas-feiras 13. Destas, as piores são a 13ªsexta-feira 13 de cada século, a 26ª, a 39ª, etc. Escrevamos um programa para mostrar na consola as piores sextas-feiras 13 do século XXI (e marquemo-las já nas nossas agendas):void TestWorstFridays13OfThisCentury(){

mas::Date d(2001, 1, 1);int count = 0;while ((d.Year() - 1) / 100 == 20){

if (d.Day() == 13 && d.Weekday() == mas::Date::FRIDAY){

count++;if (count % 13 == 0)

d.WriteLine();}d.Forth();

}}

void TestWorstFridays13OfThisCentury(){

mas::Date d(2001, 1, 1);int count = 0;while ((d.Year() - 1) / 100 == 20){

if (d.Day() == 13 && d.Weekday() == mas::Date::FRIDAY){

count++;if (count % 13 == 0)

d.WriteLine();}d.Forth();

}}

Eis o resultado na consola. Infelizmente, o programa leva muito tempo a calcular isto. Porquê?

Quantas são as piores sextas-feiras 13 do século XXI?

Page 119: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 119

Classe Date, versão 2O programa das sextas-feiras 13 é muito lento porque a função Weekday chama a função Count, a qual conta os dias desde 1901 um a um.Numa outra versão da classe Date, haveria um único membro de dados que representa o número de dias desde o início do calendário. A função Count apenas teria de devolver esse número:class Date {private:

int count; // number of days since first.public:

//...};

class Date {private:

int count; // number of days since first.public:

//...};

Com esta implementação, a maior parte das funções ficam mais simples. Apenas as funções que convertem entre a representação interna (um único número) e a representação externa (ano, mês, dia) ficam mais complicadas.

Usando esta nova implementação, o programa das piores sextas-feiras 13 fica instantâneo.

Page 120: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 120

Datas portuguesasQuando usamos datas em português, queremos, além das operações gerais sobre datas, funções para nomes dos dias da semana e dos meses e funções para ver se um dia éferiado.Será a classe DatePortuguese.A classe DatePortuguese herda da classe Date e acrescenta funções estáticas para os nomes dos dias semana e para os nomes dos meses e funções booleanas para os feriados.

namespace mas {

class DatePortuguese: public Date {//...

virtual const std::string& MonthName() const;virtual const std::string& WeekdayName() const;//...

};

}

namespace mas {

class DatePortuguese: public Date {//...

virtual const std::string& MonthName() const;virtual const std::string& WeekdayName() const;//...

};

}

Estas são as funções que dão o nome do mês e o nome do dia da semana do objecto para o qual são chamadas.

Repare na maneira de especificar que a classe DatePortugueseherda da classe Date.

Page 121: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 121

FeriadosHá uma função booleana para cada feriado:class DatePortuguese: public Date {

//...virtual bool Holiday() const;

virtual bool NewYear() const; virtual bool Liberty() const; virtual bool Workers() const;virtual bool Nation() const;virtual bool Assumption() const;virtual bool Republic() const;virtual bool AllSaints() const;virtual bool Independence() const;virtual bool OurLady() const;virtual bool Christmas() const;virtual bool Carnival() const;virtual bool GoodFriday() const;virtual bool Easter() const;virtual bool CorpusChristi() const;//...

};

class DatePortuguese: public Date {//...virtual bool Holiday() const;

virtual bool NewYear() const; virtual bool Liberty() const; virtual bool Workers() const;virtual bool Nation() const;virtual bool Assumption() const;virtual bool Republic() const;virtual bool AllSaints() const;virtual bool Independence() const;virtual bool OurLady() const;virtual bool Christmas() const;virtual bool Carnival() const;virtual bool GoodFriday() const;virtual bool Easter() const;virtual bool CorpusChristi() const;//...

};

bool DatePortuguese::Holiday() const{

return NewYear() || Liberty() || Workers()|| Nation() || Assumption() || Republic()|| AllSaints() || Independence() || OurLady()|| Christmas() || Carnival() || GoodFriday()|| Easter() || CorpusChristi();

}

bool DatePortuguese::Holiday() const{

return NewYear() || Liberty() || Workers()|| Nation() || Assumption() || Republic()|| AllSaints() || Independence() || OurLady()|| Christmas() || Carnival() || GoodFriday()|| Easter() || CorpusChristi();

}

Os últimos quatro são os feriados móveis: movem-se com a Páscoa.

Page 122: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 122

Formatos de escritaA função Write escreve de acordo com o formato, que éespecificado estaticamente (isto é, que é o mesmo para todos os objectos da classe) pela função SetFormat:class DatePortuguese: public Date {public:

enum FormatType {DEFAULT, STANDARD, STANDARD_FULL,TEXT, TEXT_WITH_WEEKDAY, ARMY, NUMERIC};

private: // non const static data membersstatic FormatType format;

public://...

virtual void Write(std::ostream& output = std::cout) const;//...public: // static

static void SetFormat(FormatType newFormat);};

class DatePortuguese: public Date {public:

enum FormatType {DEFAULT, STANDARD, STANDARD_FULL,TEXT, TEXT_WITH_WEEKDAY, ARMY, NUMERIC};

private: // non const static data membersstatic FormatType format;

public://...

virtual void Write(std::ostream& output = std::cout) const;//...public: // static

static void SetFormat(FormatType newFormat);};

A nova função Write redefine a função Write herdada.

Por exemplo, eis o aspecto da data de início da segunda guerra do golfo nos vários formatos, respectivamente: 2003 3 20; 03/03/20; 2003/03/20; 20 de Março de 2003; quinta-feira, 20 de Março de 2003; 20MAR03; 20030320.

Page 123: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 123

Escrevendo a data portuguesaA função Write agulha segundo o formato, usando uma instrução switch:void DatePortuguese::Write(std::ostream& output) const{

switch (format){case DEFAULT:

Date::Write(output);break;

case STANDARD:output << std::setfill('0')

<< std::setw(2) << Year() % 100 << separator<< std::setw(2) << Month() << separator<< std::setw(2) << Day()<< std::setfill(' ');

break;case STANDARD_FULL:

// ...break;

case TEXT:output << Day() << " de " << MonthName() << " de " << Year();break;

//...}

}

void DatePortuguese::Write(std::ostream& output) const{

switch (format){case DEFAULT:

Date::Write(output);break;

case STANDARD:output << std::setfill('0')

<< std::setw(2) << Year() % 100 << separator<< std::setw(2) << Month() << separator<< std::setw(2) << Day()<< std::setfill(' ');

break;case STANDARD_FULL:

// ...break;

case TEXT:output << Day() << " de " << MonthName() << " de " << Year();break;

//...}

}

Cada alternativa case termina por uma instrução break. Sem isso, o controlo passava à alternativa case seguinte.

Restantes alternativas.

Note bem: se o formato for DEFAULT, usa-se a função Write da classe Date.

Repare nos manipuladores: setfilldetermina o carácter de preenchimento; setw determina a largura do campo de escrita. Épreciso #include <iomanip>.

Page 124: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 124

A instrução switch

switch (expression) {case expr_const1:

statement1_1;...break;

case expr_const2:statement2_1;...break;

...default:

statement0_1;...break;

}

Sintaxe: Semântica:

Primeiro avalia-se a expression. Depois, se houver uma etiqueta expr_const cujo valor éigual ao da expression, o controlo passa para a instrução que vem a seguir a essa etiqueta, e a execução prossegue sequencialmente atéà próxima instrução break, a qual terminará a instrução switch. Se nenhuma das etiquetas tiver o valor igual ao da expression, usa-se a etiqueta default, se houver. Se não houver, a instrução switch não tem efeito (para além do efeito eventualmente provocado pela avaliação da expression.)Note que se não houvesse aquelas instruções break, o controlo seguiria sequencialmente até à chaveta } final.

É a mais complicada de todas as instruções do C++.

Page 125: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 125

O fim do prazoEm muitas situações burocráticas temos um prazo de x dias úteis para entregar certos papéis. Eis uma função de teste que pede a data de início do prazo, o número de dias úteis e escreve por extenso a data do fim do prazo.void TestDeadline(){

mas::DatePortuguese d;d.Accept("Início do prazo (ano, mês, dia)? ", "Data inválida.");std::cout << "Prazo de quantos dias úteis? ";int x;std::cin >> x;while (x > 0) {

if (!d.Weekend() && !d.Holiday())x--;

d.Forth();}while (d.Weekend() || d.Holiday())

d.Forth();d.SetFormat(mas::DatePortuguese::TEXT_WITH_WEEKDAY);d.WriteLine();

}

void TestDeadline(){

mas::DatePortuguese d;d.Accept("Início do prazo (ano, mês, dia)? ", "Data inválida.");std::cout << "Prazo de quantos dias úteis? ";int x;std::cin >> x;while (x > 0) {

if (!d.Weekend() && !d.Holiday())x--;

d.Forth();}while (d.Weekend() || d.Holiday())

d.Forth();d.SetFormat(mas::DatePortuguese::TEXT_WITH_WEEKDAY);d.WriteLine();

}

Aqui chamamos uma função estática através de um objecto da classe. Poderíamos ter feito de outra maneira, assim:

mas::DatePortuguese::SetFormat(...)

O primeiro ciclo conta os dias. O segundo avança até ao próximo dia útil.

Page 126: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 126

Resumo das instruções

labeled-statementexpression-statementcompound-statementselection-statementiteration-statementjump-statementdeclaration-statement

case constant-expression : statementdefault : statement

expressionopt ;

{ statement-seqopt }

if ( condition ) statementif ( condition ) statement else statementswitch ( condition ) statement

while ( condition ) statementdo statement while ( condition ) ;for ( for-init-statement conditionopt ; expressionopt ) statement

break ; continue ; return expressionopt ;

statement

statementstatement-seq statement

expression-statementsimple-declaration

block-declaration simple-declaration...

The null statement (just a semicolon) is in fact an expression-statement without an expression...

Page 127: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 127

Cadeias std::string (1)Já sabemos que a biblioteca STL dispõe de uma classe para cadeias de caracteres, a classe std::string. Que operações há para trabalhar com objectos desta classe?Para aceder aos caracteres individuais, temos o operador de indexação [] e a função at. Os índices começam em zero e vão até size() – 1.Para procurar caracteres em cadeias, temos as funções find_first_of, find_first_not_of, find_last_of e find_last_not_of. Retornam o índice onde está o argumento (de tipo char) ou –1 se não houver.Para procurar subcadeias, temos as funções find e rfind (esta procura do fim para o princípio).Para obter uma subcadeia, temos a função substr. Leva dois argumentos: a posição inicial e o número de caracteres. Para eliminar uma subcadeia, temos a função erase (com os mesmos argumentos da outra).

Page 128: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 128

Cadeias std::string (2)Para acrescentar caracteres no fim da cadeia, temos a função push_back. Para acrescentar cadeias, temos a função append. Ambas podem ser substituídas pelo operador +=.Para inserir no meio, temos a função insert.Para escrever cadeias, temos <<. Para ler uma linha inteira, temos getline. Para ler uma cadeia terminada por espaço, temos >>. Estas funções não são da classe std::string, mas aceitam argumentos de tipo std::string. No caso da getline, trata-se de uma função global no espaço de nomes std, que tem dois argumentos: a stream e a cadeia.Para comparar, temos os operadores ==, !=, <=, >=, <, >, com os significados habituais.Para afectar, temos a afectação e ainda a função assign.

Page 129: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 129

Cadeias std::string (3)O construtor por defeito cria uma cadeia vazia com alguma capacidade.

void Test_string_constructors(){std::string s;std::cout << s << "<" << std::endl;std::cout << static_cast<int>(s.size()) << " " << static_cast<int>(s.capacity()) << std::endl;std::string r(32, '*');std::cout << r << "<" << std::endl;std::cout << static_cast<int>(r.size()) << " " << static_cast<int>(r.capacity()) << std::endl;std::string q("Our initial assessment is that they will all die.");std::cout << q << "<" << std::endl;std::cout << static_cast<int>(q.size()) << " " << static_cast<int>(q.capacity()) << std::endl;std::string p(q, 31, 9);std::cout << p << "<" << std::endl;std::cout << static_cast<int>(p.size()) << " " << static_cast<int>(p.capacity()) << std::endl;

}

void Test_string_constructors(){std::string s;std::cout << s << "<" << std::endl;std::cout << static_cast<int>(s.size()) << " " << static_cast<int>(s.capacity()) << std::endl;std::string r(32, '*');std::cout << r << "<" << std::endl;std::cout << static_cast<int>(r.size()) << " " << static_cast<int>(r.capacity()) << std::endl;std::string q("Our initial assessment is that they will all die.");std::cout << q << "<" << std::endl;std::cout << static_cast<int>(q.size()) << " " << static_cast<int>(q.capacity()) << std::endl;std::string p(q, 31, 9);std::cout << p << "<" << std::endl;std::cout << static_cast<int>(p.size()) << " " << static_cast<int>(p.capacity()) << std::endl;

}

A função empty dá true quando a função size dá zero. A função capacity dá a capacidade. Não confunda capacitye size. Veja:

Page 130: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 130

Exemplo: SecretExemplifiquemos a classe std::string com um problema de cifra.As mensagens em claro são formadas pelas 26 letras maiúsculas, sem espaços, por exemplo ATACAMOSAOAMANHECER.A cifra faz-se usando uma chave secreta, por exemplo FCTUNL, e um número N secreto, por exemplo 4. As letras são cifradas, uma a uma, da esquerda para a direita.Uma letra que não pertença à chave é cifrada pela letra N posições à frente no alfabeto, circularmente.Uma letra que pertença à chave é cifrada por uma sequência de três letras: a m-ésima letra da chave, seguida da letra N posições à frente no alfabeto (tal como antes), seguida da (m+1)-ésima letra da chave. A variável m vale inicialmente 1 e é incrementada de uma unidade de cada vez que esta regra éutilizada.O número N é escolhido de maneira a que a distância entre duas letras da chave não seja N. Assim, conseguiremos sempre decifrar univocamente.A mensagem do exemplo dá EFXCECGTEQSWESEQETRULIUGNIV.

Page 131: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 131

Classe Secret

namespace mas {

class Secret {private:

std::string key;int number;

public:Secret(const std::string& key, int number = 1);virtual ~Secret();virtual std::string Ciphered(const std::string& x) const;virtual std::string Deciphered(const std::string& x) const;virtual void SetNumber(int x);

private:static char Shifted(char x, int n);

};

}

namespace mas {

class Secret {private:

std::string key;int number;

public:Secret(const std::string& key, int number = 1);virtual ~Secret();virtual std::string Ciphered(const std::string& x) const;virtual std::string Deciphered(const std::string& x) const;virtual void SetNumber(int x);

private:static char Shifted(char x, int n);

};

}

Há um membro de dados para a chave e outro para o número. Por defeito o número é 1, mas tem um modificador. Há funções para cifrar e para decifrar. Há uma função privada, estática

para “shiftar”uma letra de um certo número de lugares no alfabeto.

char Secret::Shifted(char x, int n){

return static_cast<char>('A' + (x - 'A' + n) % 26);}

char Secret::Shifted(char x, int n){

return static_cast<char>('A' + (x - 'A' + n) % 26);}

Page 132: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 132

Cifrandostd::string Secret::Ciphered(const std::string& s) const{

std::string result;result.reserve(3*s.size());int m = 0;for (int i = 0; i < static_cast<int>(s.size()); i++){

std::string::size_type k = key.find(s[i]);if (k == std::string::npos)

result.push_back(Shifted(s[i], number));else{

result.push_back(key[m % key.size()]);result.push_back(Shifted(s[i], number));result.push_back(key[(m+1) % key.size()]);m++;

}}return result;

}

std::string Secret::Ciphered(const std::string& s) const{

std::string result;result.reserve(3*s.size());int m = 0;for (int i = 0; i < static_cast<int>(s.size()); i++){

std::string::size_type k = key.find(s[i]);if (k == std::string::npos)

result.push_back(Shifted(s[i], number));else{

result.push_back(key[m % key.size()]);result.push_back(Shifted(s[i], number));result.push_back(key[(m+1) % key.size()]);m++;

}}return result;

}

O resultado tem no máximo o triplo dos caracteres da mensagem. Reservamos espaço com fartura, para a cadeia não ter de crescer a meio do processamento.

Se a letra não pertence à chave.

Se a letra pertence à chave.

Esta função ilustra o construtor por defeito, as funções reserve, size, find e push_back e o operador [].

Atenção: repare na declaração da variável k e na utilização da constante npos. Faça sempre assim.

Page 133: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 133

push_back e []

std::string Secret::CipheredWrong(const std::string& s) const{

//...int j = 0;for (int i = 0; i < static_cast<int>(s.size()); i++){

std::string::size_type k = key.find(s[i]);if (k == std::string::npos)

result[j++] = Shifted(s[i], number);else{

result[j++] = key[m % key.size()];//...

}}return result;

}

std::string Secret::CipheredWrong(const std::string& s) const{

//...int j = 0;for (int i = 0; i < static_cast<int>(s.size()); i++){

std::string::size_type k = key.find(s[i]);if (k == std::string::npos)

result[j++] = Shifted(s[i], number);else{

result[j++] = key[m % key.size()];//...

}}return result;

}

Não se esqueça: o argumento do operador de indexação deve ser um índice válido, isto é, deve corresponder a uma posição onde antes tenha sido colocado um carácter. Se não for válido, o comportamento da função éindefinido.

A função push_back faz crescer a cadeia, acrescentando caracteres no fim. Não poderíamos usar a afectação a posições da cadeia para esse efeito. A seguinte função está errada:

Page 134: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 134

Decifrandostd::string Secret::Deciphered(const std::string& s) const{

std::string result;// left as an exercise...return result;

}

std::string Secret::Deciphered(const std::string& s) const{

std::string result;// left as an exercise...return result;

}

Recorde que a restrição sobre o valor de Ngarante que a decifração é unívoca.

A propósito da constante npos (“not a position”):Ela é inicializada com –1. No entanto, o seu tipo é std::string::size_type, um tipo inteiro sem sinal. A conversão faz com que npos fique a ser o maior inteiro sem sinal. O tipo de retorno das funções find também éstd::string::size_type. Evitamos surpresas e garantimos portabilidade comparando duas expressões do tipo original, como na função Cipher:

std::string::size_type k = key.find(s[i]);if (k == std::string::npos)//...

Page 135: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 135

Testando a classe Secretvoid TestSecret(){

mas::Secret secret("FCTUNL", 4);for (;;){

std::string s;std::cout << "Cadeia para cifrar: ";std::getline(std::cin, s);if (!std::cin)

break;

std::string ss = secret.Ciphered(s);std::cout << "Cadeia cifrada: " << ss << std::endl;

std::string ds = secret.Deciphered(ss);std::cout << "Cadeia decifrada: " << ds << std::endl;std::cout << std::endl;

}}

void TestSecret(){

mas::Secret secret("FCTUNL", 4);for (;;){

std::string s;std::cout << "Cadeia para cifrar: ";std::getline(std::cin, s);if (!std::cin)

break;

std::string ss = secret.Ciphered(s);std::cout << "Cadeia cifrada: " << ss << std::endl;

std::string ds = secret.Deciphered(ss);std::cout << "Cadeia decifrada: " << ds << std::endl;std::cout << std::endl;

}}

Repare na utilização da função std::getline.

Page 136: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 136

class Secret {//...

virtual std::list<int> SecretNumbers() const;};

class Secret {//...

virtual std::list<int> SecretNumbers() const;};

Bónus: os números possíveis

std::list<int> Secret::SecretNumbers() const{

std::list<int> result;std::vector<bool> numbers(27, true);for (int i = 0; i < static_cast<int>(key.size()); i++)

for (int j = i; j < static_cast<int>(key.size()); j++){int x = ::abs(key[i] - key[j]);numbers[x] = numbers[26-x] = false;

}for (int i = 1; i < 26; i++)

if (numbers[i])result.push_back(i);

return result;}

std::list<int> Secret::SecretNumbers() const{

std::list<int> result;std::vector<bool> numbers(27, true);for (int i = 0; i < static_cast<int>(key.size()); i++)

for (int j = i; j < static_cast<int>(key.size()); j++){int x = ::abs(key[i] - key[j]);numbers[x] = numbers[26-x] = false;

}for (int i = 1; i < 26; i++)

if (numbers[i])result.push_back(i);

return result;}

Vector de booleanos, inicializado com 27 elementos, índices de zero a 26, todos a true.

Eis uma função para calcular a lista dos números possíveis, dada a chave:

Algumas das posições passam a false.

As que ficaram a truecorrespondem aos números possíveis.

Os números possíveis são os que permitem a decifração unívoca, dada a chave.

Page 137: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 137

Escrevendo listas

namespace mas {

template <class T>void Write(const std::list<T>& v,

const std::string& separator = " ", std::ostream& output = std::cout){for (typename std::list<T>::const_iterator i = v.begin(); i != v.end(); i++)output << (i != v.begin() ? separator : "") << *i;

}

template <class T>void WriteLine(const std::list<T>& v,

const std::string& separator = " ", std::ostream& output = std::cout){Write(v, separator, output);output << std::endl;

}

template <class T>std::ostream& operator << (std::ostream& output, const std::list<T>& v){Write(v, " ", output);return output;

}

namespace mas {

template <class T>void Write(const std::list<T>& v,

const std::string& separator = " ", std::ostream& output = std::cout){for (typename std::list<T>::const_iterator i = v.begin(); i != v.end(); i++)output << (i != v.begin() ? separator : "") << *i;

}

template <class T>void WriteLine(const std::list<T>& v,

const std::string& separator = " ", std::ostream& output = std::cout){Write(v, separator, output);output << std::endl;

}

template <class T>std::ostream& operator << (std::ostream& output, const std::list<T>& v){Write(v, " ", output);return output;

}

As funções para escrever listas são funções globais, no espaço de nomes mas:

Também há funções para escrever listas de trás para a frente: WriteBackwards e WriteBackwardsLine.

Aprecie esta técnica desde já. Os pormenores virão depois.

Estas funções estão no ficheiro Utilities_list.h. Confira com as funções análogas para vectores, página 59. Para usar listas é preciso fazer #include <list>.

Page 138: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 138

Escolhendo o número secreto

void TestSecret_1(){

std::string key;std::cout << "Chave: ";std::getline(std::cin, key);mas::Secret secret(key);std::cout << "Números possíveis: ";mas::WriteLine(secret.SecretNumbers());std::cout << "Número: ";int number;std::cin >> number;secret.SetNumber(number);std::string dummy;std::getline(std::cin, dummy);for (;;){

//...}

}

void TestSecret_1(){

std::string key;std::cout << "Chave: ";std::getline(std::cin, key);mas::Secret secret(key);std::cout << "Números possíveis: ";mas::WriteLine(secret.SecretNumbers());std::cout << "Número: ";int number;std::cin >> number;secret.SetNumber(number);std::string dummy;std::getline(std::cin, dummy);for (;;){

//...}

}

Eis outra função de teste. Pede a chave, mostra os números possíveis, pede o número, etc.:

Repare que é preciso ler o resto da linha depois de ter lido o número.

void Secret::SetNumber(int number){

this->number = number;}

void Secret::SetNumber(int number){

this->number = number;}

Page 139: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 139

Classe std::string, sumário• Inclui-se #include <string>.• Constrói-se vazio e reserva-se espaço com reserve.• Tamanho (size) é uma coisa, capacidade (capacity) é outra.• Afecta-se com = e assign.• Compara-se com ==, !=, <=, <, >=, >.• Procura-se com find, etc., verificando com npos.• Acrescenta-se com push_back, append, +=.• Elimina-se parte com erase.• Insere-se com insert.• Copia-se parte com substr.• Acede-se aos caracteres com [] e at.• Escreve-se com <<.• Lê-se com std::getline (linha toda) e >> (termina em

espaço.)

Para os pormenores, veja os livros ou os helps.

Page 140: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 140

Outras cadeias: classe StringBasicJá vimos a classe std::string, que faz parte da biblioteca do C++ e que está sempre à mão.O Visual C++ tem uma classe CString cujos objectos também são cadeias de caracteres. Quando usamos programação visual é esta a classe que serve para processar as diversas cadeias que aparecem nas janelas, nos menus, nos botões, etc.Vamos ver agora uma outra classe para cadeias: a classe StringBasic. Esta classe é nossa. Através dela estudaremos uma quantidade de técnicas interessantes.Como a classe StringBasic tem muito mais funcionalidade do que a classe std::string, às vezes fica mais prático usá-la do que a esta outra.

Page 141: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 141

Classe StringBasic: construtoresnamespace mas {

class StringBasic: public Clonable {private:

//...public:

StringBasic();StringBasic(const StringBasic& other);StringBasic(const std::string& s);StringBasic(const char *s);StringBasic(const StringBasic& other1, const StringBasic& other2);StringBasic(const StringBasic& other, int startPos, int endPos);

// pre other.ValidRange(startPos, endPos);explicit StringBasic(char c);StringBasic(char lowerBound, char upperBound);explicit StringBasic(int capacity); // pre capacity >= 1;StringBasic(const StringBasic& other, int capacity);

// pre !other.Empty() && capacity >= 1;// post Full();

virtual ~StringBasic();//...}

};

namespace mas {

class StringBasic: public Clonable {private:

//...public:

StringBasic();StringBasic(const StringBasic& other);StringBasic(const std::string& s);StringBasic(const char *s);StringBasic(const StringBasic& other1, const StringBasic& other2);StringBasic(const StringBasic& other, int startPos, int endPos);

// pre other.ValidRange(startPos, endPos);explicit StringBasic(char c);StringBasic(char lowerBound, char upperBound);explicit StringBasic(int capacity); // pre capacity >= 1;StringBasic(const StringBasic& other, int capacity);

// pre !other.Empty() && capacity >= 1;// post Full();

virtual ~StringBasic();//...}

};

Construtor por defeito.

Construtor de cópia

Construtores de conversão.

Construtor de concatenação.

Construtor de cópia parcial.

Construtor de intervalo de caracteres.

Construtor de preenchimento repetitivo.StringBasic herda de Clonable.

Construtor de reserva (de capacidade).

Page 142: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 142

Construtor de conversão básicaclass StringBasic: public Clonable {private:

int capacity;char *p;

public://...StringBasic(const char *s);

};

class StringBasic: public Clonable {private:

int capacity;char *p;

public://...StringBasic(const char *s);

};

StringBasic::StringBasic(const char *s):capacity(static_cast<int>(::strlen(s)+1)),p(new char[capacity])

{::strcpy(p, s);

}

StringBasic::StringBasic(const char *s):capacity(static_cast<int>(::strlen(s)+1)),p(new char[capacity])

{::strcpy(p, s);

}

A classe StringBasic tem dois membros de dados: um inteiro que indica a capacidade corrente e um apontador para uma cadeia básica:

Este construtor tem como argumento uma cadeia básica. Ele reserva memória dinâmica à justa e copia para lá os caracteres do argumento:

Repare na utilização da funções ::strlen, que dá o comprimento de uma cadeia básica, e ::strcpy, que copia a cadeia básica que começa no endereço indicado no segundo argumento para outra cadeia que começa na endereço indicado no primeiro argumento.

Page 143: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 143

Cadeias básicasAs cadeias básicas são arrays de caracteres representados por um apontador para o primeiro carácter e terminados pelo carácter de código zero.

Tecnicamente, as cadeias básicas são de tipo char*(apontador para char).

A expressão new char[N] aloca um array de N caracteres, onde pode residir uma cadeia básica com até N-1 caracteres úteis. O valor da expressão é um apontador para a memória alocada, isto é, é o endereço da posição onde começa o array.

Note que uma das posições do array será ocupada pelo terminador da cadeia básica.

Page 144: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 144

Destrutor

StringBasic::~StringBasic(){

delete [] p;}

StringBasic::~StringBasic(){

delete [] p;}

Os caracteres alocados dinamicamente com o operador newno construtor têm de ser desalocados com o operador delete[] no destrutor.

Page 145: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 145

Classe Clonable

namespace mas {

class Clonable {public:

virtual Clonable* Clone() const = 0;virtual ~Clonable() {};

};

}

namespace mas {

class Clonable {public:

virtual Clonable* Clone() const = 0;virtual ~Clonable() {};

};

}

Clonable é uma classe abstracta. Tem apenas a função Clone, declarada virtual pura:

A função Clone criarádinamicamente um objecto igual ao objecto para o qual échamada, do mesmo tipo, e retorna um apontador para esse novo objecto.

A classe StringBasic implementa a função Clone:

Clonable* StringBasic::Clone() const{return new StringBasic(*this);

}

Clonable* StringBasic::Clone() const{return new StringBasic(*this);

}

As funções Clone são sempre programadas assim, com recurso ao construtor de cópia.

Page 146: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 146

Classe StringBasic: cópia, afectaçãoclass StringBasic: public Clonable {

//...virtual void Copy(const StringBasic& other);virtual StringBasic& operator = (const StringBasic& other);virtual void SwapOut(StringBasic& other);virtual void CopySwap(const StringBasic& other);

virtual void Set(const StringBasic& other);virtual void Set(const StringBasic& other, int startPos, int endPos);

// pre other.ValidRange(startPos, endPos);};

class StringBasic: public Clonable {//...virtual void Copy(const StringBasic& other);virtual StringBasic& operator = (const StringBasic& other);virtual void SwapOut(StringBasic& other);virtual void CopySwap(const StringBasic& other);

virtual void Set(const StringBasic& other);virtual void Set(const StringBasic& other, int startPos, int endPos);

// pre other.ValidRange(startPos, endPos);};

Todas estas funções copiam o argumento para o objecto, usando técnicas diversas. A afectação é uma cópia que devolve uma referência para o objecto.

Page 147: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 147

Cópia tradicional

void StringBasic::Copy(const StringBasic& other){

if (this != &other){

delete [] this->p;this->capacity = other.Count() + 1;this->p = new char[this->capacity];::strcpy(this->p, other.p);

}}

void StringBasic::Copy(const StringBasic& other){

if (this != &other){

delete [] this->p;this->capacity = other.Count() + 1;this->p = new char[this->capacity];::strcpy(this->p, other.p);

}}

Para copiar, primeiro liberta-se a memória do objecto, depois aloca-se espaço à justa para um cópia do argumento, finalmente copiam-se os caracteres para esse espaço:

Afectar é sempre copiar e depois devolver o objecto por referência:StringBasic& StringBasic::operator = (const StringBasic& other){

Copy(other);return *this;

}

StringBasic& StringBasic::operator = (const StringBasic& other){

Copy(other);return *this;

}

Repare no if. Ele protege contra situações do género s.Copy(s).

Page 148: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 148

A técnica do SwapOut

void StringBasic::SwapOut(StringBasic& other){

std::swap(capacity, other.capacity);std::swap(p, other.p);

}

void StringBasic::SwapOut(StringBasic& other){

std::swap(capacity, other.capacity);std::swap(p, other.p);

}

A função SwapOut troca a representação interna da objecto com a do argumento. O argumento é passado por referência não constante:

Note que a troca de valor se faz sem trocar os caracteres: apenas se trocam os apontadores (e as capacidades). Fica muito eficiente.Eis uma pequena função de teste:void TestSwapOut(){

mas::StringBasic s("lisboa");mas::StringBasic r("porto");std::cout << s << "-" << r << std::endl;s.SwapOut(r);std::cout << s << "-" << r << std::endl;

}

void TestSwapOut(){

mas::StringBasic s("lisboa");mas::StringBasic r("porto");std::cout << s << "-" << r << std::endl;s.SwapOut(r);std::cout << s << "-" << r << std::endl;

}

A função de biblioteca std::swap é uma função genérica global que troca os valores dos seus argumentos

Page 149: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 149

Cópia com SwapOut

void StringBasic::CopySwap(const StringBasic& other){

StringBasic temp(other);SwapOut(temp);

}

void StringBasic::CopySwap(const StringBasic& other){

StringBasic temp(other);SwapOut(temp);

}

Copiar com SwapOut é muito simples: troca-se (com SwapOut) o objecto com uma variável local inicializada com uma cópia do argumento:

Não é preciso desalocar explicitamente a memória do objecto: ela é libertada automaticamente pelo destrutor da variável local temp, quando a função termina.

void StringBasic::CopySwap(const StringBasic& other){SwapOut(StringBasic(other));

}

void StringBasic::CopySwap(const StringBasic& other){SwapOut(StringBasic(other));

}

Pormenor técnico: a variável local é mesmo necessária. Não podemos usar uma variável temporária implícita, porque o argumento da função SwapOut não é passado por referência constante. Isto está errado (mesmo

que alguns compiladores deixem passar...)

Page 150: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 150

Count, Capacity, Empty, FullFrequentemente estes quatro selectores aparecem juntos:

class StringBasic: public Clonable {//...virtual int Capacity() const;virtual int Count() const;virtual bool Empty() const;virtual bool Full() const;

};

class StringBasic: public Clonable {//...virtual int Capacity() const;virtual int Count() const;virtual bool Empty() const;virtual bool Full() const;

};

int StringBasic::Capacity() const{

return capacity;}

int StringBasic::Capacity() const{

return capacity;}

int StringBasic::Count() const{

return static_cast<int>(::strlen(p));}

int StringBasic::Count() const{

return static_cast<int>(::strlen(p));}

bool StringBasic::Empty() const{

return *p == 0;}

bool StringBasic::Empty() const{

return *p == 0;}

bool StringBasic::Full() const{

return Count() == capacity - 1;}

bool StringBasic::Full() const{

return Count() == capacity - 1;}

Dá true se o primeiro carácter da cadeia (apontado por p) é o terminador (código zero).

São todos muito simples:

A função ::strlen conta os caracteres da cadeia básica até ao terminador. Quanto maior a cadeia, mais tempo demora a contar...

Page 151: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 151

Validação de índice, posição, intervalo

Um índice é válido se lá existir um carácter:

class StringBasic: public Clonable {//...virtual bool ValidIndex(int x) const;virtual bool ValidPosition(int x) const;virtual bool ValidRange(int x, int y) const;

};

class StringBasic: public Clonable {//...virtual bool ValidIndex(int x) const;virtual bool ValidPosition(int x) const;virtual bool ValidRange(int x, int y) const;

};

bool StringBasic::ValidIndex(int x) const{

return 0 <= x && x < Count();}

Uma posição é válida se corresponder a um índice válido ou ao primeiro índice não válido:

Um intervalo é válido se todos os índices desse intervalo forem válidos:

bool StringBasic::ValidIndex(int x) const{

return 0 <= x && x < Count();}

bool StringBasic::ValidPosition(int x) const{

return 0 <= x && x <= Count();}

bool StringBasic::ValidPosition(int x) const{

return 0 <= x && x <= Count();}

bool StringBasic::ValidRange(int x, int y) const{

return ValidPosition(x) && ValidPosition(y + 1) && x <= y + 1;

}

bool StringBasic::ValidRange(int x, int y) const{

return ValidPosition(x) && ValidPosition(y + 1) && x <= y + 1;

}

Estas funções são muito usadas em precondições. Veja as páginas seguintes.

Page 152: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 152

Acrescentando caracteresPara acrescentar um carácter no final de uma cadeia que não está cheia, usamos Put. Se não pudermos garantir que a cadeia não está cheia, usamos Extend. Para acrescentar no início, usamos Precede. Para inserir no meio, usamos Insert:

class StringBasic: public Clonable {//...virtual void Put(char c); // pre !Full();virtual void Extend(char c);virtual void Precede(char c);virtual void Insert(char c, int x);

// pre ValidPosition(x);};

class StringBasic: public Clonable {//...virtual void Put(char c); // pre !Full();virtual void Extend(char c);virtual void Precede(char c);virtual void Insert(char c, int x);

// pre ValidPosition(x);};

void StringBasic::Put(char c){

int x = Count();p[x] = c;p[x+1] = 0;

}

void StringBasic::Put(char c){

int x = Count();p[x] = c;p[x+1] = 0;

}

void StringBasic::Extend(char c){

GrowTo(Count() + 2);Put(c);

}

void StringBasic::Extend(char c){

GrowTo(Count() + 2);Put(c);

}

Na função Put, sabemos que háespaço para o novo carácter e acrescentamos à vontade.

Na função Extend, fazemos a cadeia crescer, se for preciso, para garantir que há espaço para mais um carácter.

De preferência, usamos Put.

Extend é menos eficiente, claro.

Page 153: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 153

Os apontadores são arrays? Não.Observe a função Put: void StringBasic::Put(char c)

{int x = Count();p[x] = c;p[x+1] = 0;

}

void StringBasic::Put(char c){int x = Count();p[x] = c;p[x+1] = 0;

}

Qual é o significado de p[x]? p é um apontador, não um array. Pois bem, sendo p um apontador char* (apontador para char), p[x] designa o carácter que está x posições depois de p. Note bem: p é um apontador, não um array, mas está a ser operado como se fosse um array. Um array tem uma certa memória reservada para si na declaração. Um apontador não. Um apontador é apenas um endereço de memória.

Page 154: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 154

Inserindo caracteresNa verdade, Precede insere na posição zero:

void StringBasic::Insert(char c, int x){

int count = Count();GrowTo(count + 2);::memmove(p+x+1, p+x, count+1-x);p[x] = c;

}

void StringBasic::Insert(char c, int x){

int count = Count();GrowTo(count + 2);::memmove(p+x+1, p+x, count+1-x);p[x] = c;

}

void StringBasic::Precede(char c){

Insert(c, 0);}

void StringBasic::Precede(char c){

Insert(c, 0);}

A função Insert é mais interessante. Observe:

Primeiro faz crescer a cadeia se for preciso; depois “move” (ou “desloca”) os caracteres a partir da posição x para a posição x+1 e seguintes; finalmente, coloca c na posição x (que tinha ficado livre).

A função ::memmove é uma função “de baixo nível”, do C. Serve para mover sequências de bytes contíguos na memória de um lado para o outro. Por exemplo ::memmove(q, r, n) move os bytes das posições r, r+1, ..., r+n-1 para a posições q, q+1, ..., q+n-1, respectivamente.

Page 155: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 155

Outros modificadores

PutAt coloca c na posição x (mas observe a precondição e também as das outras funções); Append acrescenta o argumento ao objecto, no fim; Select conserva no objecto apenas os caracteres entre as posições startPos e endPos; Insert insere other na posição x; Replace substitui os caracteres startPos e endPos por other; Erase elimina os caracteres entre startPos e endPos; Clear elimina tudo (observe a pós-condição).

class StringBasic: public Clonable {//...virtual void PutAt(char c, int x); // pre ValidIndex(x);virtual void Append(const StringBasic& other);virtual void Select(int startPos, int endPos); // pre ValidRange(startPos, endPos);virtual void Insert(const StringBasic& other, int x); // pre ValidPosition(x);virtual void Replace(int startPos, int endPos, const StringBasic& other);

// pre ValidRange(startPos, endPos)virtual void Erase(int startPos, int endPos); // pre ValidRange(startPos, endPos);virtual void Clear(); // post Empty();

};

class StringBasic: public Clonable {//...virtual void PutAt(char c, int x); // pre ValidIndex(x);virtual void Append(const StringBasic& other);virtual void Select(int startPos, int endPos); // pre ValidRange(startPos, endPos);virtual void Insert(const StringBasic& other, int x); // pre ValidPosition(x);virtual void Replace(int startPos, int endPos, const StringBasic& other);

// pre ValidRange(startPos, endPos)virtual void Erase(int startPos, int endPos); // pre ValidRange(startPos, endPos);virtual void Clear(); // post Empty();

};

Page 156: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 156

Acedendo aos caracteres um a um

void StringBasic::PutAt(char c, int x){

p[x] = c;}

void StringBasic::PutAt(char c, int x){

p[x] = c;}

A função PutAt afecta o carácter que está na posição x com o valor c:

Também há a função At, um selector, que dá o carácter que está na posição indicada:

A precondição (que indica que x é um índice válido) garante que esta operação vai correr bem.

class StringBasic: public Clonable {//...virtual const char& At(int x) const; // pre ValidIndex(x);};

class StringBasic: public Clonable {//...virtual const char& At(int x) const; // pre ValidIndex(x);};

const char& StringBasic::At(int x) const{

return p[x];}

const char& StringBasic::At(int x) const{

return p[x];}

Esta função retorna por referência constante, e não por valor, por razões de compatibilidade com outras classes que hão-de aparecer mais à frente. Se não fosse isso, retornaria por valor.

Page 157: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 157

Operadores de indexaçãoPara aceder aos caracteres individualmente, por vezes é mais prático usar os operadores de indexação:

Com isto, podemos escrever:

class StringBasic: public Clonable {//...virtual const char& operator [](int x) const; // pre ValidIndex(x);virtual char& operator [](int x); // pre ValidIndex(x);

};

class StringBasic: public Clonable {//...virtual const char& operator [](int x) const; // pre ValidIndex(x);virtual char& operator [](int x); // pre ValidIndex(x);

};

void TestIndexOperators1(){

mas::StringBasic s ("lixboa");s[2] = 's';s.Extend(s[5]);s.WriteLine();

}

void TestIndexOperators1(){

mas::StringBasic s ("lixboa");s[2] = 's';s.Extend(s[5]);s.WriteLine();

}

Em vez de:void TestIndexOperators2(){

mas::StringBasic s ("lixboa");s.PutAt('s', 2);s.Extend(s.At(5));s.WriteLine();

}

void TestIndexOperators2(){

mas::StringBasic s ("lixboa");s.PutAt('s', 2);s.Extend(s.At(5));s.WriteLine();

}

Page 158: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 158

Porquê dois operadores []?O primeiro é usado com objectos const. O segundo com objectos não const:

Não temos de nos preocupar em cada caso se o objecto é const ou não. Uma vez programados os dois operadores, o compilador escolhe sozinho qual usar. Se esquecêssemos um deles, mais tarde arrepender-nos-íamos.

virtual const char& operator [](int x) const; // pre ValidIndex(x);virtual char& operator [](int x); // pre ValidIndex(x);

virtual const char& operator [](int x) const; // pre ValidIndex(x);virtual char& operator [](int x); // pre ValidIndex(x);

void TestIndexOperators1(){

mas::StringBasic s ("lixboa");s[2] = 's';s.Extend(s[5]);s.WriteLine();

}

void TestIndexOperators1(){

mas::StringBasic s ("lixboa");s[2] = 's';s.Extend(s[5]);s.WriteLine();

}

void TestIndexOperators3(){

const mas::StringBasic s("coimbra");char c = s[2];// ...

}

void TestIndexOperators3(){

const mas::StringBasic s("coimbra");char c = s[2];// ...

}

error: l-value specifies const object

Por exemplo, se só houvesse o operador const, isto estava errado:

Por exemplo, se só houvesse o operador não const, isto estava errado:

error: binary '[' : no operator found which takes a left-hand operand of type 'const mas::StringBasic' (or there is no acceptable conversion)

Page 159: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 159

Programam-se igualmenteO corpo dos dois operadores [] é idêntico:const char& StringBasic::operator [](int x) const{

return p[x];}

const char& StringBasic::operator [](int x) const{

return p[x];}

char& StringBasic::operator [](int x){

return p[x];}

char& StringBasic::operator [](int x){

return p[x];}

Também há uma função At não constante:

A função At é mais prática para indexar os caracteres do objecto na definição das funções da classe StringBasic ou das classes derivadas. Sem ela teríamos de escrever, por exemplo, (*this)[i], em vez de At(i), simplesmente.

Este padrão, dois operadores [] e duas funções At é muito frequente. Também existe na STL (mas aqui a função chama-se at, com minúscula).

char& StringBasic::At(int x){

return p[x];}

char& StringBasic::At(int x){

return p[x];}

class StringBasic: public Clonable {//...virtual char& At(int x); // pre ValidIndex(x);};

class StringBasic: public Clonable {//...virtual char& At(int x); // pre ValidIndex(x);};

Page 160: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 160

Acrescentando outra cadeiaA função Append acrescenta o argumento, que é uma cadeia, ao objecto. Observe a programação:void StringBasic::Append(const StringBasic& other){

if (this != &other){GrowTo(this->Count() + other.Count() + 1);::strcat(this->p, other.p);

}else

Append(StringBasic(*this)); }

Também há uma função Prepend que acrescenta no início:

void StringBasic::Append(const StringBasic& other){

if (this != &other){GrowTo(this->Count() + other.Count() + 1);::strcat(this->p, other.p);

}else

Append(StringBasic(*this)); }

Primeiro o objecto cresce, se necessário, para acomodar os novos caracteres. Estes são acrescentados com a função ::strcat, uma função de baixo nível.

A instrução if serve para evitar problemas com a situação em que o argumento é o objecto: s.Append(s);. Neste caso, fazemos uma cópia do objecto e é esta cópia que acrescentamos. Sem este cuidado, os dois argumentos da função ::strcat seriam o mesmo endereço de memória, e isso daria mau resultado.

void StringBasic::Prepend(const StringBasic &other){

Insert(other, 0);}

void StringBasic::Prepend(const StringBasic &other){

Insert(other, 0);}

Acrescentar no início é inserir na posição zero.

Page 161: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 161

SubstituindoPara substituir uma subcadeia por outra cadeia, primeiro partimos o objecto em três partes: a primeira que é para ficar, a segunda que corresponde à subcadeia a substituir (e que épara desaparecer) e a terceira que também é para ficar. Guardamos esta terceira parte numa variável local. Guardamos a primeira parte no próprio objecto. Acrescentamos a nova cadeia e acrescentamos a terceira parte:void StringBasic::Replace(int startPos, int endPos, const StringBasic& other){

if (this != &other){

StringBasic temp(*this, endPos+1, Count() - 1);Head(startPos);GrowTo(Count() - (endPos - startPos + 1) + other.Count() + 1);Append(other);Append(temp);

}elseReplace(startPos, endPos, StringBasic(*this));

}

void StringBasic::Replace(int startPos, int endPos, const StringBasic& other){

if (this != &other){

StringBasic temp(*this, endPos+1, Count() - 1);Head(startPos);GrowTo(Count() - (endPos - startPos + 1) + other.Count() + 1);Append(other);Append(temp);

}elseReplace(startPos, endPos, StringBasic(*this));

}

Sempre que um dos argumentos de um modificador é do mesmo tipo que o objecto, temos de ver se háproblema em que sejam o mesmo objecto. Aqui haveria, e por isso temos a instrução if a controlar, como na função Append.

Função Head: ver página seguinte.

Page 162: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 162

Head e TailA função Head é um modificador que serve para guardar só os n primeiros caracteres do objecto (os outros são eliminados). Também há uma função Tail, que guarda os n últimos caracteres:class StringBasic: public Clonable {

//...virtual void Head(int n); // pre n >= 0;virtual void Tail(int n); // pre n >= 0;

};

class StringBasic: public Clonable {//...virtual void Head(int n); // pre n >= 0;virtual void Tail(int n); // pre n >= 0;

};

void StringBasic::Head(int n){

if (n < Count())p[n] = 0;

}

void StringBasic::Head(int n){

if (n < Count())p[n] = 0;

}

void StringBasic::Tail(int n){

int count = Count();if (n < count)

::memmove(p, p + count - n, n+1);}

void StringBasic::Tail(int n){

int count = Count();if (n < count)

::memmove(p, p + count - n, n+1);}

Head é muito simples: basta colocar o terminador na posição certa:

Tail “puxa” para o princípio os caracteres que ficam, usando ::memmove:

Page 163: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 163

Inserindo, seleccionando, apagandoInserir é um caso especial de substituir, em que a subcadeia a substituir é a cadeia vazia:void StringBasic::Insert(const StringBasic& other, int x){

Replace(x, x-1, other);}

void StringBasic::Insert(const StringBasic& other, int x){

Replace(x, x-1, other);}

void StringBasic::Select(int startPos, int endPos){

Head(endPos + 1);Erase(0, startPos - 1);

}

void StringBasic::Select(int startPos, int endPos){

Head(endPos + 1);Erase(0, startPos - 1);

}

void StringBasic::Erase(int startPos, int endPos){

::memmove(p+startPos, p+endPos+1, Count() - endPos);}

void StringBasic::Erase(int startPos, int endPos){

::memmove(p+startPos, p+endPos+1, Count() - endPos);}

Para apagar, veja bem:

Para seleccionar, deita-se fora o que não interessa:

Movem-se os caracteres que estão depois da zona a apagar para a primeira posição apagada. Note que o terminador também é movido.

Page 164: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 164

EsvaziandoClear não limpa, esvazia! Esvaziar uma cadeia é simples: basta colocar o terminador na primeira posição:

void StringBasic::Clear(){

*p = 0;}

void StringBasic::Clear(){

*p = 0;}

Também se podia ter escrito p[0] = 0. Era a mesma coisa, mas assim é mais “castiço”.

Esta é uma das minhas preferidas.

Page 165: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 165

ComparandoA classe StringBasic dispõe dos seis operadores de comparação:class StringBasic: public Clonable {

//...virtual bool operator == (const StringBasic& other) const;virtual bool operator != (const StringBasic& other) const;virtual bool operator <= (const StringBasic& other) const;virtual bool operator < (const StringBasic& other) const;virtual bool operator >= (const StringBasic& other) const;virtual bool operator > (const StringBasic& other) const;

};

class StringBasic: public Clonable {//...virtual bool operator == (const StringBasic& other) const;virtual bool operator != (const StringBasic& other) const;virtual bool operator <= (const StringBasic& other) const;virtual bool operator < (const StringBasic& other) const;virtual bool operator >= (const StringBasic& other) const;virtual bool operator > (const StringBasic& other) const;

};

Isto é a comparação lexicográfica, induzida pelos valores numéricos dos caracteres que compõem a cadeia.

Não esqueçamos que, dadas duas expressões s1 e s2 de tipo StringBasic, escrever é o mesmo do que escrever e analogamente para os outros operadores.

s1 <= s2s1.operator <= (s2)

Page 166: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 166

bool StringBasic::operator == (const StringBasic& other) const{

return ::strcmp(this->p, other.p) == 0;}

bool StringBasic::operator <= (const StringBasic& other) const{

return ::strcmp(this->p, other.p) <= 0;}

bool StringBasic::operator < (const StringBasic& other) const{

return ::strcmp(this->p, other.p) < 0;}

bool StringBasic::operator == (const StringBasic& other) const{

return ::strcmp(this->p, other.p) == 0;}

bool StringBasic::operator <= (const StringBasic& other) const{

return ::strcmp(this->p, other.p) <= 0;}

bool StringBasic::operator < (const StringBasic& other) const{

return ::strcmp(this->p, other.p) < 0;}

Recorremos basicamente à função ::strcmp:Implementado as comparações (1)

Os argumentos de ::strcmp são de tipo char*. O valor de ::strcmp(x, y), para x e y de tipo char* é um número inde-terminado menor do que zero se a cadeia básica que começa em x for lexicograficamente menor do que a cadeia básica que começa em y, é zero se for igual, e é um número indeterminado maior do que zero se for lexicograficamente maior.

Page 167: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 167

Implementado as comparações (2)As outras três programam-se por negação das primeiras:

bool StringBasic::operator != (const StringBasic& other) const{

return ! operator == (other);}

bool StringBasic::operator >= (const StringBasic& other) const{return ! operator < (other);}

bool StringBasic::operator > (const StringBasic& other) const{

return ! operator <= (other);}

bool StringBasic::operator != (const StringBasic& other) const{

return ! operator == (other);}

bool StringBasic::operator >= (const StringBasic& other) const{return ! operator < (other);}

bool StringBasic::operator > (const StringBasic& other) const{

return ! operator <= (other);}

Page 168: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 168

Buscando caracteres e subcadeiasA função booleana Has dá true se o argumento, de tipo char, pertencer ao objecto. A função booleana HasString dá true se o argumento, de tipo StringBasic, for uma subcadeia do objecto:class StringBasic: public Clonable {

//...virtual bool Has(char c) const;virtual bool HasString(const StringBasic& other) const;

};

class StringBasic: public Clonable {//...virtual bool Has(char c) const;virtual bool HasString(const StringBasic& other) const;

};

bool StringBasic::Has(char c) const{

return ::strchr(p, c) != 0;}

bool StringBasic::Has(char c) const{

return ::strchr(p, c) != 0;}

Programam-se directamente com as funções ::strchr e ::strstr:

bool StringBasic::HasString(const StringBasic& other) const{

return ::strstr(this->p, other.p) != 0;}

bool StringBasic::HasString(const StringBasic& other) const{

return ::strstr(this->p, other.p) != 0;}

A função ::strchr devolve um apontador (de tipo char*) para a primeira ocorrência do segundo argu-mento, de tipo char, a partir da posição de memória indicada no primeiro argumento, ou zero se não houver (isto é, se não houver antes de aparecer o terminador.)

A função ::strstr éanáloga, mas procura cadeias e não caracteres sozinhos.

Page 169: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 169

Posição de caracteres e de subcadeiasAs funções Has... são booleanas. As funções Position... são inteiras, devolvendo o índice onde está o que procuravam ou –1, convencionalmente, se não encontrarem.class StringBasic: public Clonable {

//...virtual int Position(char c) const;virtual int Position(char c, int start) const; // pre ValidPosition(start);virtual int Position(const StringBasic& other, int start) const;

// pre ValidPosition(start);virtual int LastPosition(char c) const;virtual int LastPosition(char c, int start) const; // pre start < Count();virtual int LastPosition(const StringBasic& other, int start) const;

// pre start < Count();};

class StringBasic: public Clonable {//...virtual int Position(char c) const;virtual int Position(char c, int start) const; // pre ValidPosition(start);virtual int Position(const StringBasic& other, int start) const;

// pre ValidPosition(start);virtual int LastPosition(char c) const;virtual int LastPosition(char c, int start) const; // pre start < Count();virtual int LastPosition(const StringBasic& other, int start) const;

// pre start < Count();};

int StringBasic::Position(char c, int start) const{

const char* q = ::strchr(p+start, c);return static_cast<int>(q != 0 ? q - p : -1);

}

int StringBasic::Position(char c, int start) const{

const char* q = ::strchr(p+start, c);return static_cast<int>(q != 0 ? q - p : -1);

}

Eis uma delas, como exemplo:

Page 170: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 170

Aritmética de apontadores

int StringBasic::Position(char c, int start) const{

const char* q = ::strchr(p+start, c);return static_cast<int>(q != 0 ? q - p : -1);

}

int StringBasic::Position(char c, int start) const{

const char* q = ::strchr(p+start, c);return static_cast<int>(q != 0 ? q - p : -1);

}

A função Position exibe dois exemplos de aritmética de apontadores:

int StringBasic::Position(char c, int start) const{

const char* q = ::strchr(p+start, c);return static_cast<int>(q != 0 ? q - p : -1);

}

int StringBasic::Position(char c, int start) const{

const char* q = ::strchr(p+start, c);return static_cast<int>(q != 0 ? q - p : -1);

}

Se p e q são apontadores char*, a expressão q-p éum número inteiro que dáo número de posições entre p e q.

Se p é um apontador char* e n é um inteiro int, a expressão p+n é um apontador char* que aponta n posições à direita de p.Começamos a procurar start

posições à direita de p, isto é, começamos a procurar na posição start da cadeia.

A diferença q – p dá precisamente o índice onde está o carácter c, quando q não é zero. (Começámos a procurar na posição start.)

Logo:q – p == n se e só se p + n == q.

Page 171: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 171

Contando caracteresPodemos contar os caracteres que sim, com CountIf e os que não, com CountIfNot. (Que sim, que pertencem ao argumento; que não, que não pertencem ao argumento):class StringBasic: public Clonable {

//...virtual int CountIf(const StringBasic& other) const;virtual int CountIfNot(const StringBasic& other) const;

};

class StringBasic: public Clonable {//...virtual int CountIf(const StringBasic& other) const;virtual int CountIfNot(const StringBasic& other) const;

};

int StringBasic::CountIf(const StringBasic& other) const{

int result = 0;for (int i=0; p[i] != 0; i++)

result += other.Has(p[i]);return result;

}

int StringBasic::CountIfNot(const StringBasic& other) const{

return Count() - CountIf(other);}

int StringBasic::CountIf(const StringBasic& other) const{

int result = 0;for (int i=0; p[i] != 0; i++)

result += other.Has(p[i]);return result;

}

int StringBasic::CountIfNot(const StringBasic& other) const{

return Count() - CountIf(other);}

Esta função é pouco eficiente, pois compara cada carácter do objecto com cada carácter do argumento. É para usar casualmente em situações não muito exigentes.

Por exemplo, a expressão s.CountIf("AEIOUaeiou")

representa o número de vogais na cadeia s.

Page 172: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 172

Posições condicionaisHá uma série de funções PositionIf... que calculam a posição de caracteres condicionalmente:class StringBasic: public Clonable {

//...virtual int PositionIf(const StringBasic& other) const;virtual int PositionIfNot(const StringBasic& other) const;virtual int LastPositionIf(const StringBasic& other) const;virtual int LastPositionIfNot(const StringBasic& other) const;virtual int PositionIf(const StringBasic& other, int start) const; // pre start >= 0;virtual int PositionIfNot(const StringBasic& other, int start) const; // pre start >= 0;virtual int LastPositionIf(const StringBasic& other, int start) const;

// pre start < Count();virtual int LastPositionIfNot(const StringBasic& other, int start) const;

// pre start < Count();};

class StringBasic: public Clonable {//...virtual int PositionIf(const StringBasic& other) const;virtual int PositionIfNot(const StringBasic& other) const;virtual int LastPositionIf(const StringBasic& other) const;virtual int LastPositionIfNot(const StringBasic& other) const;virtual int PositionIf(const StringBasic& other, int start) const; // pre start >= 0;virtual int PositionIfNot(const StringBasic& other, int start) const; // pre start >= 0;virtual int LastPositionIf(const StringBasic& other, int start) const;

// pre start < Count();virtual int LastPositionIfNot(const StringBasic& other, int start) const;

// pre start < Count();};

int StringBasic::PositionIf(const StringBasic& other, int start) const{

for (int i = start; p[i] != 0; i++)if (other.Has(p[i]))

return i;return -1;

}

int StringBasic::PositionIf(const StringBasic& other, int start) const{

for (int i = start; p[i] != 0; i++)if (other.Has(p[i]))

return i;return -1;

}

Eis a defini-ção de duas delas, sobre-carregadas:

int StringBasic::PositionIf(const StringBasic& other) const{return PositionIf(other, 0);

}

int StringBasic::PositionIf(const StringBasic& other) const{return PositionIf(other, 0);

}

Page 173: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 173

Podando e aparandoAs funções Prune... eliminam caracteres, por valor. As funções Trim... eliminam espaços no início e no fim:class StringBasic: public Clonable {

//...virtual void Prune(char c);virtual void PruneLast(char c);virtual void PruneAll(char c);

virtual void PruneIf(const StringBasic& other);virtual void PruneIfNot(const StringBasic& other);

virtual void Trim(); virtual void TrimLeft(); virtual void TrimRight();

};

class StringBasic: public Clonable {//...virtual void Prune(char c);virtual void PruneLast(char c);virtual void PruneAll(char c);

virtual void PruneIf(const StringBasic& other);virtual void PruneIfNot(const StringBasic& other);

virtual void Trim(); virtual void TrimLeft(); virtual void TrimRight();

};void StringBasic::Prune(char c){

int x = Position(c);if (x != -1)RemoveAt(x);

}

void StringBasic::Prune(char c){

int x = Position(c);if (x != -1)RemoveAt(x);

}

void StringBasic::TrimLeft(){int x = PositionIfNot(" ");if (x == -1)Clear();

elseErase(0, x-1);

}

void StringBasic::TrimRight(){Head(LastPositionIfNot(" ") + 1);

}

void StringBasic::Trim(){TrimRight();TrimLeft();

}

void StringBasic::TrimLeft(){int x = PositionIfNot(" ");if (x == -1)Clear();

elseErase(0, x-1);

}

void StringBasic::TrimRight(){Head(LastPositionIfNot(" ") + 1);

}

void StringBasic::Trim(){TrimRight();TrimLeft();

}

Eis a definição da função Prune:

Eis as três Trim:

Page 174: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 174

Funções váriasIsto nunca mais acaba:class StringBasic: public Clonable {

//...virtual bool StartsBy(const StringBasic& other) const;virtual bool EndsBy(const StringBasic& other) const;

virtual void Reverse();virtual void Swap(int x, int y); // pre ValidIndex(x) && ValidIndex(y);virtual void SendToBack();virtual void SendToBack(int n); // pre n >= 0;virtual void BringToFront();virtual void BringToFront(int n); // pre n >= 0;

virtual void ToLowercase();virtual void ToUppercase();

};

class StringBasic: public Clonable {//...virtual bool StartsBy(const StringBasic& other) const;virtual bool EndsBy(const StringBasic& other) const;

virtual void Reverse();virtual void Swap(int x, int y); // pre ValidIndex(x) && ValidIndex(y);virtual void SendToBack();virtual void SendToBack(int n); // pre n >= 0;virtual void BringToFront();virtual void BringToFront(int n); // pre n >= 0;

virtual void ToLowercase();virtual void ToUppercase();

};

SendToBack sem argumentos “manda” o primeiro carácter para o fim. Com argumentos, manda os n primeiros caracteres para o fim. Analogamente para BringToFront. As outras são intuitivas.

bool StringBasic::StartsBy(const StringBasic& other) const{

return ::strncmp(this->p, other.p, other.Count()) == 0;}

bool StringBasic::StartsBy(const StringBasic& other) const{

return ::strncmp(this->p, other.p, other.Count()) == 0;}

Observe a definição de StartsBy, que usa a função ::strncmp:A função ::strncmp não compara até ao terminador, mas apenas até ao número de caracteres indicado no terceiro argumento.

void StringBasic::Reverse(){for (int i=0, j=Count() - 1; i < j; i++, j--)Swap(i, j);

}

void StringBasic::Reverse(){for (int i=0, j=Count() - 1; i < j; i++, j--)Swap(i, j);

}

A função Reverse use um ciclo for com duas variáveis de controlo:

Page 175: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 175

Mais funções váriasAinda há mais? Sim:class StringBasic: public Clonable {

//...virtual void GrowTo(int n);virtual void Adjust(); // post Full();

virtual int Sum() const;virtual int Hash() const;

virtual void Map(const StringBasic& from, const StringBasic& to);//pre from.Count() <= to.Count();

};

class StringBasic: public Clonable {//...virtual void GrowTo(int n);virtual void Adjust(); // post Full();

virtual int Sum() const;virtual int Hash() const;

virtual void Map(const StringBasic& from, const StringBasic& to);//pre from.Count() <= to.Count();

};

void StringBasic::Map(const StringBasic& from, const StringBasic& to){

int x;for(int i=0; p[i] != 0; i++)

if ((x = from.Position(p[i])) != -1)p[i] = to[x];

}

void StringBasic::Map(const StringBasic& from, const StringBasic& to){

int x;for(int i=0; p[i] != 0; i++)

if ((x = from.Position(p[i])) != -1)p[i] = to[x];

}

A função Map transforma cada ocorrência de um carácter do primeiro argumento no correspondente carácter do segundo argumento:

Aumentar a capacidade.

Ajustar a capacidade (libertando as posições não ocupadas).

Soma dos valores numéricos dos caracteres.

Função de dispersão (para tabelas de dispersão).

Exemplos disto mais àfrente.

Page 176: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 176

Ordenação dos caracteresPara ordenar os caracteres, temos a função Sort; para eliminar os duplicado contíguos, temos a função Unique.class StringBasic: public Clonable {

//...virtual bool IsSorted() const;virtual void Sort(); // post IsSorted();virtual void Unique();

};

class StringBasic: public Clonable {//...virtual bool IsSorted() const;virtual void Sort(); // post IsSorted();virtual void Unique();

};

void StringBasic::Sort(){

if (!Empty())Quicksort(0, Count() - 1);

}

void StringBasic::Sort(){

if (!Empty())Quicksort(0, Count() - 1);

}

void StringBasic::Unique(){

int j = 0;int i = 0;while (p[i] != 0){

char c = p[i];doi++;

while (p[i] == c);p[j++] = c;

}p[j] = 0;

}

void StringBasic::Unique(){

int j = 0;int i = 0;while (p[i] != 0){

char c = p[i];doi++;

while (p[i] == c);p[j++] = c;

}p[j] = 0;

}

A função Sort usa o quicksort:

A função Unique usa um ciclo duplo. Veja com atenção:

Note bem: função Unique não elimina os duplicados todos, mas apenas os duplicados contíguos. Se a cadeia estiver ordenada, todos os duplicados iguais estão contíguos e, neste caso, são todos eliminados.

Page 177: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 177

Quicksort de caracteresEis o quicksort para StringBasics:void StringBasic::Quicksort(int lowerBound, int upperBound){

int i = lowerBound;int j = upperBound;char pivot = p[(i+j)/2];do{

while (static_cast<unsigned char>(p[i]) < static_cast<unsigned char>(pivot))i++;

while (static_cast<unsigned char>(p[j]) > static_cast<unsigned char>(pivot))j--;

if (i <= j)Swap(i++, j--);

} while (i <= j);if (lowerBound < j)

Quicksort(lowerBound, j);if (i < upperBound)

Quicksort(i, upperBound);}

void StringBasic::Quicksort(int lowerBound, int upperBound){

int i = lowerBound;int j = upperBound;char pivot = p[(i+j)/2];do{

while (static_cast<unsigned char>(p[i]) < static_cast<unsigned char>(pivot))i++;

while (static_cast<unsigned char>(p[j]) > static_cast<unsigned char>(pivot))j--;

if (i <= j)Swap(i++, j--);

} while (i <= j);if (lowerBound < j)

Quicksort(lowerBound, j);if (i < upperBound)

Quicksort(i, upperBound);}

Nunca é demais recordar o quicksort.

Os valores numéricos dos caracteres em C++ podem ir de –128 a 127 ou de zero a 255, consoante as implementações. Para evitar surpresas, convertemos para unsigned char antes de comparar.

Page 178: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 178

Cadeias como conjuntos de caracteresEm C++, por vezes usamos cadeias de caracteres para representar conjuntos de caracteres. Atrás já vimos a função Has.

class StringBasic: public Clonable {//...virtual void Intersect(const StringBasic& other);virtual void Subtract(const StringBasic& other);virtual bool Contains(const StringBasic& other) const;

};

class StringBasic: public Clonable {//...virtual void Intersect(const StringBasic& other);virtual void Subtract(const StringBasic& other);virtual bool Contains(const StringBasic& other) const;

};void StringBasic::Intersect(const StringBasic& other){StringBasic result(Min(Count(), other.Count()) + 1);int i = 0;int j = 0;int k = 0;while (p[i] != 0 && other.p[j] != 0)if (p[i] == other.p[j]){result.p[k++] = p[i];i++, j++;

}else if (p[i] < other.p[j])i++;

elsej++;

result[k] = 0;SwapOut(result);

}

void StringBasic::Intersect(const StringBasic& other){StringBasic result(Min(Count(), other.Count()) + 1);int i = 0;int j = 0;int k = 0;while (p[i] != 0 && other.p[j] != 0)if (p[i] == other.p[j]){result.p[k++] = p[i];i++, j++;

}else if (p[i] < other.p[j])i++;

elsej++;

result[k] = 0;SwapOut(result);

}

Eis outras, inspiradas na teoria dos conjuntos:

Atenção: estas funções sótêm o significado esperado se o objecto e o argumento estiverem ordenados. Observe a função Intersect. Se as cadeias não estiverem ordenadas o resultado não é a intersecção.

Observe a técnica do SwapOut.

Page 179: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 179

Conversões numéricasSe uma cadeia apenas tiver algarismos, podemos querer saber qual é o valor numérico. Há dois casos: números inteiros e números reais (com ponto decimal):class StringBasic: public Clonable {

//...virtual int AsInt(int base = 10) const; // pre 2 <= base && base <= 36;virtual double AsDouble() const;virtual bool IsInt(int base = 10) const; // pre 2 <= base && base <= 36;virtual bool IsDouble() const;

};

class StringBasic: public Clonable {//...virtual int AsInt(int base = 10) const; // pre 2 <= base && base <= 36;virtual double AsDouble() const;virtual bool IsInt(int base = 10) const; // pre 2 <= base && base <= 36;virtual bool IsDouble() const;

};

E se a cadeia não puder ser convertida, que fazer? Será que devemos incluir a precondição de que a cadeia é convertível? Para ver se uma cadeia é convertível, o que temos de fazer éconvertê-la: se chegarmos ao fim sem problemas, ainda bem. Se houver azar, lançamos uma excepção.

AsInt dá para qualquer base, de 2 a 36.

As cadeias inteiras podem ter sinal mais ou menos. As cadeias reais podem ter sinal, ponto decimal e podem estar na notação científica.

Page 180: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 180

ExcepçõesObserve a função AsInt:int StringBasic::AsInt(int base) const{

int result;char *endp;errno = 0; result = ::strtol(p, &endp, base);if (!*p || errno || *endp){errno = 0;StringBasic message("\"", *this + "\" is not a legal int value.");throw message;

}return result;

}

int StringBasic::AsInt(int base) const{

int result;char *endp;errno = 0; result = ::strtol(p, &endp, base);if (!*p || errno || *endp){errno = 0;StringBasic message("\"", *this + "\" is not a legal int value.");throw message;

}return result;

}

Quem realmente faz o trabalho de conversão éa função ::strtol. (Veja a documentação.)

Quando uma função “lança” uma excepção não faz mais nada. O controlo passa à função que a chamou. Aí, das duas uma: ou háum tratador de excepções catch que “apanha” a excepção e a trata ou a excepção é propagada à função que chamou essa.

Aqui a excepção é uma StringBasic que contém uma mensagem informativa construída à medida.

Page 181: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 181

Tratando excepçõesA função IsInt (não confundir com AsInt) contém um tratador de excepções. A ideia é simples: para saber se a cadeia é uma cadeia inteira, converte-se com AsInt. Se der excepção, afinal não era:

bool StringBasic::IsInt(int base) const{

bool result = true;try {

AsInt(base);} catch (const mas::StringBasic&) {

result = false;}return result;

}

bool StringBasic::IsInt(int base) const{

bool result = true;try {

AsInt(base);} catch (const mas::StringBasic&) {

result = false;}return result;

}

Repare no try-block seguido do tratador de excepções. Quando há uma excepção no try-block para a qual existe um tratador, o tratador intervém, substituindo o resto do bloco. Se não houver tratador, a excepção propaga-se para o chamador.

Page 182: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 182

Ilustração com função de testeEis uma função de teste que ilustra o funcionamento das funções AsInt e IsInt e o tratamento de excepções:void TestConversionToInt(){

for(;;)try {

mas::StringBasic s;s.Accept("Uma cadeia decimal: ");if (!std::cin)

break;if (s.IsInt())

std::cout << "Yes, this is a valid integer." << std::endl;else

std::cout << "No, this is NOT a valid integer." << std::endl;int x = s.AsInt();std::cout << x << std::endl;

} catch (const mas::StringBasic& e) {std::cout << "Exception: " << e << std::endl;

}}

void TestConversionToInt(){

for(;;)try {

mas::StringBasic s;s.Accept("Uma cadeia decimal: ");if (!std::cin)

break;if (s.IsInt())

std::cout << "Yes, this is a valid integer." << std::endl;else

std::cout << "No, this is NOT a valid integer." << std::endl;int x = s.AsInt();std::cout << x << std::endl;

} catch (const mas::StringBasic& e) {std::cout << "Exception: " << e << std::endl;

}}

Note bem: as excepções não são a cura de todos os males. Devem ser usadas com muita moderação. Se não, acabam por introduzir mais erros do que os que pretende apanhar.

Page 183: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 183

Caso da conversão para doubleÉ parecido:double StringBasic::AsDouble() const{

double result;char *endp;errno = 0;result = ::strtod(p, &endp);if (!*p || errno || *endp){errno = 0;StringBasic message("\"", *this + "\" is not a legal double value.");throw message;

}return result;

}

double StringBasic::AsDouble() const{

double result;char *endp;errno = 0;result = ::strtod(p, &endp);if (!*p || errno || *endp){errno = 0;StringBasic message("\"", *this + "\" is not a legal double value.");throw message;

}return result;

}

Agora é a função ::strtod.

bool StringBasic::IsDouble() const{

bool result = true;try {

AsDouble();} catch (const mas::StringBasic&) {

result = false;}return result;

}

bool StringBasic::IsDouble() const{

bool result = true;try {

AsDouble();} catch (const mas::StringBasic&) {

result = false;}return result;

}

Nesta função, tal como na outra, só queremos saber se há excepção ou não.

Page 184: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 184

De número para cadeiaAsInt e AsDouble são selectores. Para converter um número para StringBasic, precisamos de pseudo-construtores estáticos:class StringBasic: public Clonable {

//...public: // static pseudo-constructors

static StringBasic Numeral(int n, int base = 10); // pre 2 <= base && base <= 36;static StringBasic Fixed(double x, int precision = 6);static StringBasic Scientific(double x, int precision = 6);

};

class StringBasic: public Clonable {//...

public: // static pseudo-constructorsstatic StringBasic Numeral(int n, int base = 10); // pre 2 <= base && base <= 36;static StringBasic Fixed(double x, int precision = 6);static StringBasic Scientific(double x, int precision = 6);

};

Eis uma função de teste, que mostra como se usa:

void TestStaticPseudoConstructors(){for(;;){double x;std::cout << "um numero: ";std::cin >> x;if (!std::cin)break;

int n = static_cast<int>(x);mas::StringBasic::Numeral(n).WriteLine();mas::StringBasic::Numeral(n, 2).WriteLine();mas::StringBasic::Numeral(n, 5).WriteLine();mas::StringBasic::Fixed(x).WriteLine();mas::StringBasic::Fixed(x, 2).WriteLine();mas::StringBasic::Scientific(x).WriteLine();mas::StringBasic::Scientific(x, 4).WriteLine();

}}

void TestStaticPseudoConstructors(){for(;;){double x;std::cout << "um numero: ";std::cin >> x;if (!std::cin)break;

int n = static_cast<int>(x);mas::StringBasic::Numeral(n).WriteLine();mas::StringBasic::Numeral(n, 2).WriteLine();mas::StringBasic::Numeral(n, 5).WriteLine();mas::StringBasic::Fixed(x).WriteLine();mas::StringBasic::Fixed(x, 2).WriteLine();mas::StringBasic::Scientific(x).WriteLine();mas::StringBasic::Scientific(x, 4).WriteLine();

}}

Observe que o resultado das funções é um objecto de tipo StringBasic.

Na função Numeral, podemos escolher a base. Por defeito, é 10.

Page 185: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 185

Lendo e escrevendoPara escrever StringBasics, temos Write, WriteLine e <<.Para ler, temos Read e >>. Para ler interactivamente temos Accept:class StringBasic: public Clonable {

//...virtual void Write(std::ostream& output = std::cout) const;virtual void WriteLine(std::ostream& output= std::cout) const;friend std::ostream& operator << (std::ostream& output, const StringBasic& s);

virtual void Read(std::istream& input = std::cin);friend std::istream& operator >> (std::istream& input, StringBasic& s);virtual void Accept(const StringBasic& prompt);

};

class StringBasic: public Clonable {//...virtual void Write(std::ostream& output = std::cout) const;virtual void WriteLine(std::ostream& output= std::cout) const;friend std::ostream& operator << (std::ostream& output, const StringBasic& s);

virtual void Read(std::istream& input = std::cin);friend std::istream& operator >> (std::istream& input, StringBasic& s);virtual void Accept(const StringBasic& prompt);

};

Eis duas das funções, que são como de costume:void StringBasic::Write(std::ostream& output) const{output << p;

}

void StringBasic::Write(std::ostream& output) const{output << p;

}std::ostream& operator << (std::ostream& output, const StringBasic& s){s.Write(output);return output;

}

std::ostream& operator << (std::ostream& output, const StringBasic& s){s.Write(output);return output;

}

Note bem: Read e >> lêem atéao fim da linha.

Page 186: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 186

Constantes públicasEis cinco constantes prontas a usar:class StringBasic: public Clonable {

//...public: // static constants

static const StringBasic uppercaseLetters;static const StringBasic lowercaseLetters;static const StringBasic letters;static const StringBasic digits;static const StringBasic null;

};

class StringBasic: public Clonable {//...

public: // static constantsstatic const StringBasic uppercaseLetters;static const StringBasic lowercaseLetters;static const StringBasic letters;static const StringBasic digits;static const StringBasic null;

};

const StringBasic StringBasic::uppercaseLetters('A', 'Z');const StringBasic StringBasic::lowercaseLetters('a', 'z');const StringBasic StringBasic::letters(uppercaseLetters, lowercaseLetters);const StringBasic StringBasic::digits('0', '9');const StringBasic StringBasic::null;

const StringBasic StringBasic::uppercaseLetters('A', 'Z');const StringBasic StringBasic::lowercaseLetters('a', 'z');const StringBasic StringBasic::letters(uppercaseLetters, lowercaseLetters);const StringBasic StringBasic::digits('0', '9');const StringBasic StringBasic::null;

São inicializadas assim:

Temos aqui membros de dados públicos, o que só é perdoável porque se trata de constantes.

Page 187: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 187

O Gene DominanteComo exemplo, vejamos o famoso problema do gene dominante.

Um gene é uma sequência das bases azotadas adenosina (A), timina (T), guanina (G) e citosina (C). Convencionalmente cada gene é representado por uma cadeia de caracteres formada pelas letras A, T, G e C. Por exemplo, “ATCGGAT” representa um determinado gene.

Temos um ficheiro de texto com milhares de genes (isto é, milhares de cadeias) e queremos saber qual é o gene que aparece mais vezes, dito “gene dominante”. Sabemos que não há genes com mais do que oito bases. Em caso de empate, queremos todos os genes empatados com maior frequência.Problema inventado por Luís Caires para o Concurso de Programação da Nova, CPN2003.

Page 188: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 188

EstratégiaAssociamos a cada gene um número único, imaginando no gene as letras substituídas pelos algarismos 1, 2, 3 e 4. Assim, cada gene ficaria um numeral na base 5. Como não há mais do que 8 bases em cada gene, o maior número é 58-1, isto é, 390624. Assim, os genes indexarão directamente o vector de contadores.Uma vez terminada a contagem, criamos um vector de pares <índice, frequência> e ordenamos por frequência. Finalmente, escolhemos os índices de frequência máxima, são os primeiros do vector. Usaremos duas classes novas: Gene, para representar um gene, e Genes, para representar o contador de genes.

Page 189: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 189

Classe GeneUm gene é uma StringBasic, com mais uma operação para dar o número (o tal da base 5) e a operação inversa:

namespace mas {

class Gene: public StringBasic {private:public:

Gene();Gene(const StringBasic& s);virtual int AsInt() const;static Gene FromInt(int x);

};

}

namespace mas {

class Gene: public StringBasic {private:public:

Gene();Gene(const StringBasic& s);virtual int AsInt() const;static Gene FromInt(int x);

};

}

Gene::Gene(){}

Gene::Gene(const StringBasic& s):StringBasic(s)

{}

int Gene::AsInt() const{StringBasic temp(*this);temp.Map("ATGC", "1234");return temp.StringBasic::AsInt(5);

}

Gene Gene::FromInt(int x){Gene result = Numeral(x, 5);result.Map("1234", "ATGC");return result;

}

Gene::Gene(){}

Gene::Gene(const StringBasic& s):StringBasic(s)

{}

int Gene::AsInt() const{StringBasic temp(*this);temp.Map("ATGC", "1234");return temp.StringBasic::AsInt(5);

}

Gene Gene::FromInt(int x){Gene result = Numeral(x, 5);result.Map("1234", "ATGC");return result;

}

Page 190: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 190

Classe GenesTemos um vector de inteiros e um vector de pares de inteiros. A função Read lê e conta. A função Count diz quantos há de um certo gene. A função Top dá a lista dos n genes mais frequentes. A função ThoseMoreFrequentThan dá a lista dos genes cuja frequência é maior ou igual a x:class Genes {private:

std::vector<int> count;std::vector<std::pair<int, int> > frequencies;

public:virtual ~Genes();virtual void Read(std::istream& input);virtual int Count(const Gene& g) const;virtual std::list<Gene> Top(int n) const;virtual std::list<Gene> ThoseMoreFrequentThan(int x) const;

};

class Genes {private:

std::vector<int> count;std::vector<std::pair<int, int> > frequencies;

public:virtual ~Genes();virtual void Read(std::istream& input);virtual int Count(const Gene& g) const;virtual std::list<Gene> Top(int n) const;virtual std::list<Gene> ThoseMoreFrequentThan(int x) const;

};

Page 191: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 191

Lendo genes e contando-osAo ler contamos, usando o vector count. Depois preenchemos o vector frequencies e ordenamo-lo:void Genes::Read(std::istream& input){

int n = 0;count.clear();count.resize(5*5*5*5*5*5*5*5);std::string s;while (input >> s){

count[Gene(s).AsInt()]++;n++;

}frequencies.clear();frequencies.reserve(n);for (int i = 0; i < static_cast<int>(count.size()); i++)

if (count[i] > 0)frequencies.push_back(std::make_pair(count[i], i));

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

void Genes::Read(std::istream& input){

int n = 0;count.clear();count.resize(5*5*5*5*5*5*5*5);std::string s;while (input >> s){

count[Gene(s).AsInt()]++;n++;

}frequencies.clear();frequencies.reserve(n);for (int i = 0; i < static_cast<int>(count.size()); i++)

if (count[i] > 0)frequencies.push_back(std::make_pair(count[i], i));

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

Isto não é lá muito ortodoxo, mas é prático. (Deveríamos usar uma constante, ...)

Repare na técnica de leitura. A variável s é de tipo std::string (e não de tipo StringBasic). Assim, cada leitura termina num espaço ou fim de linha e os espaços são ignorados, sem mais esforço, como convém.

O primeiro elemento para é a frequência, o segundo éo gene. Assim, ao ordenar, o critério principal é a frequência.

A ordenação é crescente. Os mais frequentes ficam no fim.

Page 192: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 192

Os mais frequentesOs n mais frequentes:std::list<Gene> Genes::Top(int n) const{

std::list<Gene> result;for (int i = 0, j = static_cast<int>(frequencies.size()) - 1; i < n; i++, j--)

result.push_back(Gene::FromInt(frequencies[j].second));return result;

}

std::list<Gene> Genes::Top(int n) const{

std::list<Gene> result;for (int i = 0, j = static_cast<int>(frequencies.size()) - 1; i < n; i++, j--)

result.push_back(Gene::FromInt(frequencies[j].second));return result;

}

std::list<Gene> Genes::ThoseMoreFrequentThan(int x) const{

std::list<Gene> result;int j = static_cast<int>(frequencies.size()) - 1;while (j >= 0 && frequencies[j].first >= x)

result.push_back(Gene::FromInt(frequencies[j--].second));return result;

}

std::list<Gene> Genes::ThoseMoreFrequentThan(int x) const{

std::list<Gene> result;int j = static_cast<int>(frequencies.size()) - 1;while (j >= 0 && frequencies[j].first >= x)

result.push_back(Gene::FromInt(frequencies[j--].second));return result;

}

Os que aparecem pelo menos x vezes:

Page 193: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 193

Função de teste para os genesEis a função de teste que resolve o problema:

void TestDominantGene(std::istream& input, std::ostream& output)

{

mas::Genes g;

g.Read(input);

mas::WriteLine(g.ThoseMoreFrequentThan(g.Count(g.Top(1).front())), " ", output);

}

void TestDominantGene(std::istream& input, std::ostream& output)

{

mas::Genes g;

g.Read(input);

mas::WriteLine(g.ThoseMoreFrequentThan(g.Count(g.Top(1).front())), " ", output);

}

int Genes::Count(const Gene& g) const

{

return count[g.AsInt()];

}

int Genes::Count(const Gene& g) const

{

return count[g.AsInt()];

}

Esta função escreve no ficheiro output os genes que aparecem pelo menos tantas vezes como o gene mais frequente.A função Count define-se assim:

Claro que para calcular apenas os de frequência máxima não era preciso ordenar. Bastava achar a frequência máxima numa passagem e depois todos os dessa frequência noutra passagem.

Page 194: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 194

Solução com STL puroA solução do vector só é viável porque cada gene não tem mais do que oito bases azotadas. Como fazer se houver um número arbitrário de bases?Solução: usar um mapa, isto é, um contentor std::map. Neste caso, podemos fazer tudo com a classe std::string, dispensando a nossa StringBasic .Em vez de um vector, usamos um mapa. Observe:class GenesMap {private:

std::map<std::string, int> count;std::vector<std::pair<int, std::string> > frequencies;

public:virtual ~GenesMap();virtual void Read(std::istream& input);virtual int Count(const std::string& g) const;virtual std::list<std::string> Top(int n) const;virtual std::list<std::string> ThoseMoreFrequentThan(int x) const;

};

class GenesMap {private:

std::map<std::string, int> count;std::vector<std::pair<int, std::string> > frequencies;

public:virtual ~GenesMap();virtual void Read(std::istream& input);virtual int Count(const std::string& g) const;virtual std::list<std::string> Top(int n) const;virtual std::list<std::string> ThoseMoreFrequentThan(int x) const;

};

Um mapa é um contentor associativo: associa uma chave a um valor. Neste caso, a chave é uma std::string e o valor é um int. Neste caso, o valor representa o número de ocorrências na chave.

Estas três funções agora usam o tipo std::string, em vez do tipo Gene.

Page 195: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 195

Contando com o mapaVeja bem:

void GenesMap::Read(std::istream& input)

{

count.clear();

std::string s;

while (input >> s)

count[s]++;

frequencies.clear();

frequencies.reserve(count.size());

for (std::map<std::string, int>::iterator i = count.begin(); i != count.end(); i++)

frequencies.push_back(std::make_pair(i->second, i->first));

std::sort(frequencies.begin(), frequencies.end());

}

void GenesMap::Read(std::istream& input)

{

count.clear();

std::string s;

while (input >> s)

count[s]++;

frequencies.clear();

frequencies.reserve(count.size());

for (std::map<std::string, int>::iterator i = count.begin(); i != count.end(); i++)

frequencies.push_back(std::make_pair(i->second, i->first));

std::sort(frequencies.begin(), frequencies.end());

}

Perceba a expressão count[s]:1) se não existir no mapa um elemento com chave s, écriado um, com valor zero, e a expressão representa um referência para o valor (que vale zero)2) se existir, a expressão representa a referência para o valor associado.Logo, a expressão count[s]++, “conta” mais uma ocorrência de s.

O iterador de mapas dáum par chave-valor de cada vez.

Page 196: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 196

Os mais frequentes, de novoÉ muito parecido, mas observe:

int GenesMap::Count(const std::string& g) const{return const_cast<GenesMap*>(this)->count[g];

}

int GenesMap::Count(const std::string& g) const{return const_cast<GenesMap*>(this)->count[g];

}

Temos de usar o const_castaqui porque a função Count éconst e o operador [] da classe std::map<K, T> não é const. Repare na técnica.

std::list<std::string> GenesMap::Top(int n) const{std::list<std::string> result;for (int i = 0, j = static_cast<int>(frequencies.size()) - 1; i < n; i++, j--)result.push_back(frequencies[j].second);

return result;}

std::list<std::string> GenesMap::Top(int n) const{std::list<std::string> result;for (int i = 0, j = static_cast<int>(frequencies.size()) - 1; i < n; i++, j--)result.push_back(frequencies[j].second);

return result;}

std::list<std::string> GenesMap::ThoseMoreFrequentThan(int x) const{std::list<std::string> result;int j = static_cast<int>(frequencies.size()) - 1;while (j >= 0 && frequencies[j].first >= x)result.push_back(frequencies[j--].second);

return result;}

std::list<std::string> GenesMap::ThoseMoreFrequentThan(int x) const{std::list<std::string> result;int j = static_cast<int>(frequencies.size()) - 1;while (j >= 0 && frequencies[j].first >= x)result.push_back(frequencies[j--].second);

return result;}

As outras são ainda mais simples do que antes:Dizemos que o const_cast remove a “constância”. De facto, o que estamos a fazer éa aceder ao mapa através de um apontador não constante para o objecto da função. Esse apontador é obtido “ignorando” a constância do apontador this. Note bem: na funções const, o apontador this é um apontador const; nas funções não const, o apontador this é um apontador não const.

Page 197: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 197

PartículasEstamos num problema de Física, e precisamos modelar partículas no nosso programa. Partículas são pontos com massa. Podem ser transladadas, escaladas, rodadas, como os pontos. Podemos calcular a distância de uma partícula a outra. Há operações novas sobre partículas, que não existem sobre pontos: calcular a força de atracção que uma partícula exerce sobre outra, calcular a energia de uma partícula, energizar uma partícula.

class Particle: public Point {private: // ...

public:// ...

};

class Particle: public Point {private: // ...

public:// ...

};

Como as partículas são pontos, neste sentido de que tudo o que se pode fazer com pontos se pode fazer com partículas (mas não inversamente), mandamos a nova classe Particle herdar da classe Point:

Assim se especifica a herança.

Page 198: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 198

HerançaQuando uma classe herda de outra, recebe dela os membros de dados e as funções, acrescenta outros membros de dados, acrescenta outras funções e redefine (porventura) algumas das funções herdadas:class Particle: public Point {private:

double mass;public:

// Construtores e destrutor

virtual double Mass() const;virtual double Energy() const;virtual void Energize(double energy);

virtual double Force(const Particle& other) const;

virtual void Write(std::ostream& output = std::cout) const;};

class Particle: public Point {private:

double mass;public:

// Construtores e destrutor

virtual double Mass() const;virtual double Energy() const;virtual void Energize(double energy);

virtual double Force(const Particle& other) const;

virtual void Write(std::ostream& output = std::cout) const;};

Cada objecto de tipo Particle tem três membros de dados; x e y, herdados, e mass, declarado aqui.

Que construtores queremos? (Ver adiante.)

Estas quatro funções, Mass, Energy, Energize e Force são novas, não são herdadas.

A função Write éherdada. Se aparece aqui é porque vai ser redefinida.

Point é a classe de base, Particle é a classe derivada.

Page 199: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 199

EnergiaA energia de uma partícula é dada pela fórmula de Einstein:E = mc2, onde E representa a energia, m a massa e c a velocidade na luz. A massa é a massa:double Particle::Mass() const{

return mass;}

double Particle::Energy() const{

return mass * speedOfLight * speedOfLight;}

void Particle::Energize(double energy){

mass += energy / (speedOfLight * speedOfLight);}

double Particle::Mass() const{

return mass;}

double Particle::Energy() const{

return mass * speedOfLight * speedOfLight;}

void Particle::Energize(double energy){

mass += energy / (speedOfLight * speedOfLight);}

Sobre constantes estáticas, ver um pouco mais adiante.

speedOfLight éuma constante estática cujo valor é299792458 metros por segundo (velocidade da luz no vazio).

Page 200: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 200

ForçaA força gravitacional entre duas partículas é dada pela fórmula de Newton, F = G m1 m2/r2, onde F é a força, G é a constante de gravitação universal, m1 e m2 são as massas das partículas e r a distância entre elas:

double Particle::Force(const Particle& other) const

{

return gravitational * mass * other.mass / mas::Square(DistanceTo(other));

}

double Particle::Force(const Particle& other) const

{

return gravitational * mass * other.mass / mas::Square(DistanceTo(other));

}

Esta função recorre a uma função herdada, DistanceTo, para calcular a distância da partícula objecto à partícula argumento.

gravitational é outra constante estática cujo valor é6.6742*10-11 (expressa em unidades do sistema internacional, SI).

mas::Square é uma função que vem no ficheiro Utilities.h. Calcula o quadrado do seu argumento.

Page 201: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 201

EscritaPara escrever uma partícula, escreve-se o ponto que existe “dentro” da partícula e depois escreve-se a massa. Observe:

void Particle::Write(std::ostream& output) const

{

Point::Write(output);

output << " " << mass;

}

void Particle::Write(std::ostream& output) const

{

Point::Write(output);

output << " " << mass;

}

Note que não poderíamos escrever os valores de x e y directamente, assim:

Note bem: a instrução Point::Write(output); invoca a função Write da classe Point, e essa função escreve os valores dos membros de dados x e y. Sem a notação Point:: estaríamos perante uma chamada recursiva da função que estamos a definir.

void Particle::Write(std::ostream& output) const

{

output << x << " " << y << " " << mass;

}

void Particle::Write(std::ostream& output) const

{

output << x << " " << y << " " << mass;

}

error C2248: 'mas::Point::x' : cannot access private member declared in class 'mas::Point‘

error C2248: 'mas::Point::y' : cannot access private member declared in class 'mas::Point'

Os membros de dados x e y, apesar de herdados, continuam privados da classe Point.

Page 202: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 202

Construtores na classe derivadaOs construtores não são herdados, pois precisamos inicializar a massa (que não existe na classe Point).

class Particle: public Point {

private:

double mass;

public:

Particle();

Particle(double x, double y, double mass);

Particle(const Particle& other);

virtual ~Particle();

// ...

};

class Particle: public Point {

private:

double mass;

public:

Particle();

Particle(double x, double y, double mass);

Particle(const Particle& other);

virtual ~Particle();

// ...

};

Construtor por defeito.

Construtor elementar.

Construtor de cópia.

Destrutor.

Page 203: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 203

Programação dos construtoresNa lista de inicialização, invocamos o construtor apropriado da classe de base. Observe:Particle::Particle():

Point(),mass(1.0)

{}

Particle::Particle():Point(),mass(1.0)

{}

Particle::Particle(double x, double y, double mass):Point(x, y),mass(mass)

{}

Particle::Particle(double x, double y, double mass):Point(x, y),mass(mass)

{}

Particle::Particle(const Particle& other):Point(other),mass(other.mass)

{}

Particle::Particle(const Particle& other):Point(other),mass(other.mass)

{}

Aqui invocamos o construtor por defeito da classe Point e inicializamos a massa com 1.

Aqui invocamos o construtor por elementar da classe Point com os argumentos x e y e inicializamos a massa com o argumento mass.

Aqui invocamos o construtor de cópia da classe Pointcom o argumento other, que é de tipo Particle, e inicializamos a massa com a massa do argumento.

Note bem: aqui o construtor de cópia da classe Point recebe como argumento uma Particle, mas não se atrapalha, pois apenas “liga” àquilo que conhece, isto é, ao ponto que existe “dentro” da partícula.

Page 204: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 204

Constantes estáticasNeste exemplo simples, as constantes para a velocidade da luz e para a gravitação universal ficam na classe Particle. Declaram-se assim:

class Particle: public Point {private:

// ...public:

// ...public: // static consts

static const double speedOfLight;static const double gravitational;

};

class Particle: public Point {private:

// ...public:

// ...public: // static consts

static const double speedOfLight;static const double gravitational;

};

Definem-se no ficheiro .cpp, junto com as funções:

const double Particle::speedOfLight = 299792458;const double Particle::gravitational = 6.6742e-11;

const double Particle::speedOfLight = 299792458;const double Particle::gravitational = 6.6742e-11;

Estas constantes são públicas. Fora da classe Particle temos de usar o nome da classe: Particle::speedOfLight.

Numa versão mais elaborada, teríamos uma classe Physics, na qual estariam estas constantes e muitas outras também.

Page 205: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 205

Testando as partículasUma partícula de massa 1 na origem, que se move, que éenergizada, etc.:

void TestParticle(){

mas::Particle t;std::cout << t.Energy() << std::endl;t.WriteLine();t.Translate(2, 5);t.Energize(1.0e15);std::cout << t.Energy() << std::endl;t.WriteLine();t.Energize(-1.0e15);std::cout << t.Energy() << std::endl;t.WriteLine();double f = t.Force(mas::Particle());std::cout << f << std::endl;

}

void TestParticle(){

mas::Particle t;std::cout << t.Energy() << std::endl;t.WriteLine();t.Translate(2, 5);t.Energize(1.0e15);std::cout << t.Energy() << std::endl;t.WriteLine();t.Energize(-1.0e15);std::cout << t.Energy() << std::endl;t.WriteLine();double f = t.Force(mas::Particle());std::cout << f << std::endl;

}

Interessante: a classe Particle não tem função WriteLine, logo a que estáa ser usada só pode ser a da classe Point, e mesmo assim aparece a massa (a qual não existe na classe Point). Que se passará?

Page 206: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 206

Polimorfismo (1)Observemos o significado da chamada pt.WriteLine().

void TestParticle()

{

mas::Particle pt;

// ...

pt.WriteLine();

// ...

}

void TestParticle()

{

mas::Particle pt;

// ...

pt.WriteLine();

// ...

}

Não existindo uma função WriteLine na classe Particle, é usada a função WriteLine herdada da classe Point. Esta está programada assim: void Point::WriteLine(std::ostream& output) const{

Write(output);output << std::endl;

}

void Point::WriteLine(std::ostream& output) const{

Write(output);output << std::endl;

}

Logo, a função WriteLine da classe Point vai chamar a função Write. Mas qual? A da classe Point (que está mesmo ali à mão) ou a da classe Particle, já que originalmente a função WriteLinefoi chamada para um objecto da classe Particle e a classe Particle redefine a função Write?

Page 207: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 207

Polimorfismo (2)Resposta: é a da classe Particle.Claro que é. Não só é isso que nos convém, como é isso que observámos na consola. (Ao escrever uma partícula surgem três números: coordenada x, coordenada y e massa.)

Mas nós, quando programámos a função WriteLine na classe Point, estávamos a pensar na função Write da mesma classe. E agora é outra que é chamada?!Exactamente. Apesar de não termos dado por isso, a chamada da função Write na definição da função WriteLine é uma chamada polimórfica. A função Write que efectivamente seráchamada depende do objecto usado com a função WriteLine.

Page 208: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 208

Funções virtuaisO comportamento polimórfico é consequência de a função Writeser virtual. Façamos uma experiência, retirando o especificador virtual na função Write na classe Point, e apagando declaração da função Write na classe Figure:class Figure {// ...//virtual void Write(std::ostream& = std::cout) const = 0;//virtual void WriteLine(std::ostream& = std::cout) const = 0;

// ...};

class Figure {// ...//virtual void Write(std::ostream& = std::cout) const = 0;//virtual void WriteLine(std::ostream& = std::cout) const = 0;

// ...};

Correndo o programa de novo, a massa não é escrita, sinal que foi usada a função Write da classe Point:

class Point: public Figure {//...void Write(std::ostream& output = std::cout) const;

//...};

class Point: public Figure {//...void Write(std::ostream& output = std::cout) const;

//...};

Page 209: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 209

Funções virtuais e polimorfismo (1)Observe as duas classe B e D, em que D deriva de B:class B {public:

virtual void F();virtual void G();

};

class B {public:

virtual void F();virtual void G();

};

#include <iostream>

#include "B.h"

void B::F(){

std::cout << "B::F" << std::endl;}

void B::G(){

F();}

#include <iostream>

#include "B.h"

void B::F(){

std::cout << "B::F" << std::endl;}

void B::G(){

F();}

class D: public B {public:

virtual void F();};

class D: public B {public:

virtual void F();};

#include <iostream>

#include "B.h"#include "D.h"

void D::F(){

std::cout << "D::F" << std::endl;}

#include <iostream>

#include "B.h"#include "D.h"

void D::F(){

std::cout << "D::F" << std::endl;}

Qual é o efeito de x.G()?Depende. Se x for de tipo B éuma coisa, se for de tipo D éoutra.

Page 210: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 210

Funções virtuais e polimorfismo (2)Função de teste:#include <iostream>

#include "B.h"#include "D.h"

int main(){

B b;b.F();b.G();

D d;d.F();d.G();

}

#include <iostream>

#include "B.h"#include "D.h"

int main(){

B b;b.F();b.G();

D d;d.F();d.G();

}

Mas se a função F não fosse virtual o resultado seria:

Aqui a chamada de Ffoi polimórfica.

Aqui a chamada de Ffoi estática (contrário de polimórfica, neste contexto).

Page 211: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 211

Um ponto não é uma partículaAos pontos não podemos aplicar as funções das partículas, claro. Observe:

void TestWrong_1(){

mas::Point p;double x = p.Energy();std::cout << x << std::endl;

}

void TestWrong_1(){

mas::Point p;double x = p.Energy();std::cout << x << std::endl;

}

error C2039: 'Energy' : is not a member of 'mas::Point'

error C2039: 'Force' : is not a member of 'mas::Point'

void TestWrong_2(){

mas::Point p;mas::Particle pt(2, 2, 10);double x = p.Force(pt);std::cout << x << std::endl;

}

void TestWrong_2(){

mas::Point p;mas::Particle pt(2, 2, 10);double x = p.Force(pt);std::cout << x << std::endl;

}

Mas recorde que as partículas são pontos e, portanto, podemos aplicar às partículas as funções dos pontos.

Page 212: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 212

Uma partícula é um pontoÀs partículas podemos aplicar as funções dos pontos, como jávimos:

void TestParticlesAsPoints(){

mas::Particle t;t.Translate(2.0, -1-5);mas::Particle u(-1, 3, 10);double d = t.DistanceTo(u);std::cout << d << std::endl;double a = u.Angle();std::cout << a << std::endl;// ...

}

void TestParticlesAsPoints(){

mas::Particle t;t.Translate(2.0, -1-5);mas::Particle u(-1, 3, 10);double d = t.DistanceTo(u);std::cout << d << std::endl;double a = u.Angle();std::cout << a << std::endl;// ...

}

Tudo o que podemos fazer aos pontos, podemos fazer às partículas. Isto é uma consequência da herança.

Page 213: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 213

Construtor de conversãoPodemos usar um ponto como argumento de uma função em que o parâmetro é uma partícula? Não. Observe.

void TestConversion(){

mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(p);std::cout << x << std::endl;

}

void TestConversion(){

mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(p);std::cout << x << std::endl;

}

No entanto, se fizer sentido, podemos ter na nossa classe um construtor de conversão:

error C2664: 'mas::Particle::Force' : cannot convert parameter 1 from 'mas::Point' to 'const mas::Particle &'

class Particle: public Point {// ...

public:// ...Particle(const Point& other);// ...

};

class Particle: public Point {// ...

public:// ...Particle(const Point& other);// ...

}; Cria um objecto de tipo Particle, a partir de um objecto de tipo Point. A massa toma um valor por defeito.

Particle::Particle(const Point& other):Point(other),mass(1.0)

{}

Particle::Particle(const Point& other):Point(other),mass(1.0)

{} Invocamos o construtor de cópia da

classe Point com o argumento other, que é de tipo Point e inicializamos a massa com 1.

Page 214: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 214

Conversão automáticaA programação do construtor de conversão é rotineira:

Particle::Particle(const Point& other):Point(other),mass(1.0)

{}

Particle::Particle(const Point& other):Point(other),mass(1.0)

{}A conversão é automática:

nem sequer é preciso invocar o construtor:

void TestConversion(){

mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(p);std::cout << x << std::endl;

}

void TestConversion(){

mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(p);std::cout << x << std::endl;

}

void TestConversion(){

mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(mas::Particle(p));std::cout << x << std::endl;

}

void TestConversion(){

mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(mas::Particle(p));std::cout << x << std::endl;

}

Mas note muito bem: o argumento da função Forcenão é o ponto p, mas uma partícula construída automaticamente, como se tivéssemos escrito assim:

Page 215: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 215

Conversão explícitaNormalmente não queremos a conversão automática. É mais seguro, em regra, fazermos nós as conversões explicitamente.Para evitar a conversão automática, qualifica-se de explicit o construtor de conversão:class Particle: public Point {

// ...public:

// ...explicit Particle(const Point& other);// ...

};

class Particle: public Point {// ...

public:// ...explicit Particle(const Point& other);// ...

};

Assim, este construtor não seráchamado automaticamente.

void TestConversion(){mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(p);std::cout << x << std::endl;

}

void TestConversion(){mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(p);std::cout << x << std::endl;

} error C2664: 'mas::Particle::Force' : cannot convert parameter 1 from 'mas::Point' to 'const mas::Particle &'

Agora temos de converter explicitamente:void TestConversion(){

mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(mas::Particle(p));std::cout << x << std::endl;

}

void TestConversion(){

mas::Point p(1, 2);mas::Particle t(0, 0, 5);double x = t.Force(mas::Particle(p));std::cout << x << std::endl;

}

Regra prática: os construtores com um argumento excepto os de cópia são declarados explicit, a não ser que tenhamos uma razão forte em contrário.

Page 216: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 216

Tipos dos argumentosPodemos calcular a distância de um ponto a uma partícula?Sim, porque o argumento da função DistanceTo é const Point& e a classe Particle deriva da classe Point.

A regra geral é: se uma função tem um parâmetro declarado de um tipo classe X passado por referência constante, então o argumento pode ser um objecto de tipo X ou um objecto de um tipo derivado de X. Faz sentido: se na função o argumento só usa operações da classe X então um objecto de um tipo derivado de X pode ser usado sem problemas, pois todas as operações do tipo Xpodem ser usadas com ele. É outra consequência da herança.

class Point: public Figure {// ...

virtual double DistanceTo(const Point& other) const;// ...};

class Point: public Figure {// ...

virtual double DistanceTo(const Point& other) const;// ...};

Page 217: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 217

Operador << para partículas?Temos de programá-lo?Não, não é preciso. Na classe de base, Point, o operador estádefinido em termos da função Write e o argumento do operador é de tipo const Point&. Logo, o operador aceita argumentos de classes derivadas de Point.class Point: public Figure {// ...

friend std::ostream& operator << (std::ostream& output, const Point& p);// ...};

class Point: public Figure {// ...

friend std::ostream& operator << (std::ostream& output, const Point& p);// ...};

std::ostream& operator << (std::ostream& output, const Point& p){

p.Write(output);return output;

}

Polimorficamente, a chamada da função Write invoca a função da classe do argumento, e é isso mesmo que nós queremos.

std::ostream& operator << (std::ostream& output, const Point& p){

p.Write(output);return output;

}

Page 218: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 218

Funções amigasAs funções amigas escapam à disciplina das classes. Não pertencem à classe, mas nós declaramo-las dentro da classe a que estão ligadas. Aqui o operador << está ligado à classe Point.O especificador friend pretende sugerir que, mesmo não pertencendo à classe, a função, sendo amiga, tem acesso aos membros privados da classe.Na verdade, na classe Point nós não usamos essa possibilidade. Mas podíamos ter programado assim, sem recorrer à função Write:std::ostream& operator << (std::ostream& output, const Point& p){

output << p.x << " " << p.y;return output;

}

std::ostream& operator << (std::ostream& output, const Point& p){

output << p.x << " " << p.y;return output;

} Repare que a função, mesmo não pertencendo à classe Point, tem acesso aos membros privados x e y.

Esta programação é legítima mas não recomendável, pois perdemos o comportamento polimórfico. Se a usarmos para escrever partículas, não veremos a massa. Por isso, neste caso, deveríamos definir o operador << também na classe Particle.

Page 219: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 219

Cifra de CésarComo exemplo de programação com a classe std::string, estudemos o problema da cifra de César. Reza a história que Júlio César cifrava as suas comunicações militares substituindo cada letra da mensagem secreta pela letra três posições mais àfrente no alfabeto. Por exemplo

ALEA JACTA ESTdaria

DOHD MDFWD HVWou, suprimindo os espaços,

DOHDMDFWDHVWo que com certeza deixava os gauleses e outros inimigos verdadeiramente embasbacados.Se os espiões de Júlio César soubessem C++, que programa escreveriam para cifrar e depois decifrar as mensagens?

Bom, na verdade, no tempo de Júlio César, o alfabeto romano não tinha as letras J, U e W. Veja http://en.wikipedia.org/wiki/Latin_alphabet.

Page 220: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 220

Classe CaesarNa classe Caesar teremos uma função para cifrar e outra para decifrar. No construtor indicamos a chave, que diz de quantos caracteres vamos “rodar” a mensagem ao cifrá-la.

namespace mas {

class Caesar {private:

int key;public:

explicit Caesar(char key = 'C');virtual ~Caesar();

virtual std::string Cipher(const std::string& s) const;virtual std::string Decipher(const std::string& s) const;

};

}

namespace mas {

class Caesar {private:

int key;public:

explicit Caesar(char key = 'C');virtual ~Caesar();

virtual std::string Cipher(const std::string& s) const;virtual std::string Decipher(const std::string& s) const;

};

}

Por defeito, a chave é ‘C’, que corresponde ao caso histórico, em que a rotação é de 3 unidades. Assim, ‘A’ significa rodar de 1, ‘B’ rodar de 2, etc. ‘Y’ rodar de 25 e ‘Z’ rodar de 26 (o que deixa a mensagem na mesma).

Internamente, a chave éum número.

Page 221: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 221

CifrandoO construtor faz a conversão da chave-letra para a chave--número, e o destrutor não faz nada:Caesar::Caesar(char key):

key(key - 'A' + 1){}

Caesar::Caesar(char key):key(key - 'A' + 1)

{}

Caesar::~Caesar(){}

Caesar::~Caesar(){}

Para cifrar, temos de “rodar” no alfabeto cada uma das letras:

std::string Caesar::Cipher(const std::string& s) const{

std::string result;result.reserve(s.size());for (int i = 0; i < static_cast<int>(s.size()); i++)

result.push_back(static_cast<char>('A' + (s[i] - 'A' + key) % 26));return result;

}

std::string Caesar::Cipher(const std::string& s) const{

std::string result;result.reserve(s.size());for (int i = 0; i < static_cast<int>(s.size()); i++)

result.push_back(static_cast<char>('A' + (s[i] - 'A' + key) % 26));return result;

}

Perceba bem esta aritmética.

Repare no static_cast<char>, necessário para converter para char o valor da expressão 'A' + (s[i] - 'A' + key) % 26, a qual é de tipo int.

Page 222: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 222

Usando iteradoresQuando ao percorrer uma cadeia (ou um vector) não usamos a variável de controlo senão para aceder ao elemento corrente, preferimos usar iteradores. Observe a seguinte programação alternativa para a função Cipher:

std::string Caesar::Cipher(const std::string& s) const

{

std::string result;

result.reserve(s.size());

for (std::string::const_iterator i = s.begin(); i != s.end(); i++)

result.push_back(static_cast<char>('A' + (*i - 'A' + key) % 26));

return result;

}

std::string Caesar::Cipher(const std::string& s) const

{

std::string result;

result.reserve(s.size());

for (std::string::const_iterator i = s.begin(); i != s.end(); i++)

result.push_back(static_cast<char>('A' + (*i - 'A' + key) % 26));

return result;

}

Page 223: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 223

IteradoresOs iteradores representam de maneira abstracta posições dentro de uma estrutura: um vector, uma cadeia, uma lista, etc.Por exemplo, para uma estrutura s, s.begin() representa a posição do primeiro elemento de s e s.end() representa a posição a seguir ao último elemento de s. O operador ++ faz o iterador avançar, isto é, leva-o para a próxima posição. O operador * serve para obter o elemento na posição representada pelo iterador.Há iteradores de vários tipos. O tipo std::string::const_iterator é o dos iteradores de std::string que percorrem as cadeias do princípio para o fim, sem modificar os caracteres visitados. Quando quisermos percorrer uma cadeia do fim para o princípio, usaremos iteradores std::string::const_reverse_iterator.

Page 224: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 224

Testando a cifraEis uma função de teste para experimentar a função Cipher:void TestCipher(){

std::cout << "Chave: ";std::string line;std::getline(std::cin, line);if (line.empty())

line = "C";mas::Caesar c(line[0]);for (;;){

std::cout << ">>";std::string line;std::getline(std::cin, line);if (!std::cin)

break;std::string s = c.Cipher(line);std::cout << "<<" << s << std::endl;

}}

void TestCipher(){

std::cout << "Chave: ";std::string line;std::getline(std::cin, line);if (line.empty())

line = "C";mas::Caesar c(line[0]);for (;;){

std::cout << ">>";std::string line;std::getline(std::cin, line);if (!std::cin)

break;std::string s = c.Cipher(line);std::cout << "<<" << s << std::endl;

}}

Note bem: todas as linhas são lidas com std::getline.

Ooops, os espaços não aparecem bem.

Pois não. A função Cipher só funciona devidamente se todos os caracteres da mensagem forem letras maiúsculas.

Page 225: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 225

Preprocessamento da mensagemAntes de cifrar, devemos normalizar a mensagem, eliminando todos os caracteres que não sejam letras e passando todas as minúsculas para maiúsculas. Será a função UpperCaseLettersOnly.OK, mas essa função será definida onde, em que classe?Na classe Caesar (para já). Mas, como não usa nenhum dos membros de dados da classe Caesar (que, aliás, só tem um membro de dados), declaramo-la como estática:class Caesar {// ...public: // static std::string functions

static std::string UpperCaseLettersOnly(const std::string& s);};

class Caesar {// ...public: // static std::string functions

static std::string UpperCaseLettersOnly(const std::string& s);};

Eventualmente esta função poderá ser agrupada com outras do mesmo género numa biblioteca de funções utilitárias.

As funções estáticas não actuam sobre os objectos da classe. Logo não são const nem deixam de ser.

Page 226: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 226

Funções estáticasSe uma função de uma classe não acede aos membros de dados da classe devemos declará-la com estática. As funções estáticas apenas trabalham sobre os seus argumentos e, eventualmente, sobre os membros estáticos da classe. A função UpperCaseLettersOnly só trabalha sobre o seu argumento. Observe:

std::string Caesar::UpperCaseLettersOnly(const std::string& s)

{

std::string result;

result.reserve(s.size());

for (std::string::const_iterator i = s.begin(); i != s.end(); i++)

if (::isalpha(*i))

result.push_back(static_cast<char>(::toupper(*i)));

return result;

}

std::string Caesar::UpperCaseLettersOnly(const std::string& s)

{

std::string result;

result.reserve(s.size());

for (std::string::const_iterator i = s.begin(); i != s.end(); i++)

if (::isalpha(*i))

result.push_back(static_cast<char>(::toupper(*i)));

return result;

}

Usamos a função ::isalpha para verificar se um carácter é uma letra. Usamos a função ::toupper para obter a letra maiúscula correspondente a uma dada letra minúscula. Mas repare que o tipo do resultado de ::toupper é int. Épor isso que precisamos do static_cast<char>.

Page 227: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 227

Utilização das funções estáticasFora da classe, as funções estáticas são qualificadas pelo nome da classe (com quatro pontos ::) ou por um objecto da classe. Eis a função de teste reformulada:

void TestCipher2()

{

std::cout << "Chave: ";

// ...

mas::Caesar c(line[0]);

for (;;)

{

// ...

std::string s = c.Cipher(mas::Caesar::UpperCaseLettersOnly(line));

//std::string s = c.Cipher(c.UpperCaseLettersOnly(line));

std::cout << "<<" << s << std::endl;

}

}

void TestCipher2()

{

std::cout << "Chave: ";

// ...

mas::Caesar c(line[0]);

for (;;)

{

// ...

std::string s = c.Cipher(mas::Caesar::UpperCaseLettersOnly(line));

//std::string s = c.Cipher(c.UpperCaseLettersOnly(line));

std::cout << "<<" << s << std::endl;

}

}

Esta instrução em comentário poderia ter sido usada em vez da outra, com o mesmo significado.

Page 228: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 228

DecifrandoSe a chave for ‘A’, para decifrar uma mensagem cifrada basta cifrá-la com chave ‘Y’, não é? E se a chave for ‘B’, com ‘X’, etc.

Considere também a seguinte programação alternativa, que dispensa as variáveis locais:

std::string Caesar::Decipher(const std::string& s) const{

char k = static_cast<char>('A' + (26 - key % 26) - 1);Caesar c(k);return c.Cipher(s);

}

std::string Caesar::Decipher(const std::string& s) const{

char k = static_cast<char>('A' + (26 - key % 26) - 1);Caesar c(k);return c.Cipher(s);

}

A variável c denota um objecto local de tipo Caesar, com o qual ciframos (para decifrar...)

std::string Caesar::Decipher(const std::string& s) const{

return Caesar(static_cast<char>('A' + (26 - key % 26) - 1)).Cipher(s);}

std::string Caesar::Decipher(const std::string& s) const{

return Caesar(static_cast<char>('A' + (26 - key % 26) - 1)).Cipher(s);}

Page 229: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 229

Quebrando a cifraQuebrar uma cifra é decifrar a mensagem cifrada sem conhecer previamente a chave. Como fazer, neste caso?Como há só 26 chaves possíveis, podemos decifrar com todas elas e escolher de entre as mensagens resultantes a mais plausível. Simplifiquemos, considerando que a mais plausível será, das 26, a que tiver mais vogais.Logo, precisamos de uma função para contar vogais:

int Caesar::CountVowels(const std::string& s){

int result = 0;static const std::string vowels = "AEIOU";for (std::string::const_iterator i = s.begin(); i != s.end(); i++)

result += vowels.find(*i) != std::string::npos;return result;

}

int Caesar::CountVowels(const std::string& s){

int result = 0;static const std::string vowels = "AEIOU";for (std::string::const_iterator i = s.begin(); i != s.end(); i++)

result += vowels.find(*i) != std::string::npos;return result;

}

Sim, é uma função estática.

Recorde que o resultado da função find é o índice onde o argumento foi encontrado no objecto, ou std::string::npos, se não houver. Sobre esta constante std::string::npos, veja na página 134.

Sim, é uma constante estática local à função. Sendo estática, só é inicializada uma vez em cada execução do programa, como convém.

Page 230: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 230

Função Caesar::Break(...)A função para que-brar a cifra também éestática (já que não usa a chave):

std::string Caesar::Break(const std::string& s){

std::string result;int currentMax = 0;for (char i = 'A'; i <= 'Z'; i++){

std::string temp = Caesar(i).Decipher(s);int count = CountVowels(temp);std::cout << temp << " " << count << std::endl;if (count > currentMax){

currentMax = count;result = temp;

}}return result;

}

std::string Caesar::Break(const std::string& s){

std::string result;int currentMax = 0;for (char i = 'A'; i <= 'Z'; i++){

std::string temp = Caesar(i).Decipher(s);int count = CountVowels(temp);std::cout << temp << " " << count << std::endl;if (count > currentMax){

currentMax = count;result = temp;

}}return result;

}

class Caesar {// ...public: // static

static std::string Break(const std::string& s);// ...};

class Caesar {// ...public: // static

static std::string Break(const std::string& s);// ...};

Trata-se do algoritmo para achar o máximo de uma função: qual das 26 cadeias é a que tem mais vogais? (Em caso de empate, usa-se a primeira.)

Será que este método para quebrar cifras é eficaz? Experimente!

Page 231: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 231

Variante: cifrando às avessasO Júlio César não se lembrou disso, mas as mensagens secretas ficariam ainda mais secretas se fossem escritas do fim para o princípio. Eis a função CipherBack para tratar disso:

std::string Caesar::CipherBack(const std::string& s) const{

std::string result;result.reserve(s.size());for (std::string::const_reverse_iterator i = s.rbegin(); i != s.rend(); i++)

result.push_back(static_cast<char>('A' + (*i - 'A' + key) % 26));return result;

}

std::string Caesar::CipherBack(const std::string& s) const{

std::string result;result.reserve(s.size());for (std::string::const_reverse_iterator i = s.rbegin(); i != s.rend(); i++)

result.push_back(static_cast<char>('A' + (*i - 'A' + key) % 26));return result;

}

Observe o funcionamento do iterador reverso. É inicializado com s.rbegin(), que representa a posição do último carácter da cadeia s, e termina quando atinge s.rend(), que representa a posição antes do primeiro carácter. Num iterador reverso, avançar com ++ quer dizer recuar!

Page 232: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 232

Variante: cifrando palavra a palavraCifrar palavra a palavra, respeitando os espaços, não é boa ideia, pois fica menos difícil quebrar a cifra. Mas façamos isso, mesmo assim, a partir de função CipherMany que cifra de uma vez um vector de cadeias:class Caesar {// ...

virtual std::vector<std::string> CipherMany(const std::vector<std::string>& b) const;};

class Caesar {// ...

virtual std::vector<std::string> CipherMany(const std::vector<std::string>& b) const;};

Programamos isto usando iteradores de vectores, claro:std::vector<std::string> Caesar::CipherMany(const std::vector<std::string>& b) const{

std::vector<std::string> result;result.reserve(b.size());for (std::vector<std::string>::const_iterator i = b.begin(); i != b.end(); i++)

result.push_back(Cipher(*i));return result;

}

std::vector<std::string> Caesar::CipherMany(const std::vector<std::string>& b) const{

std::vector<std::string> result;result.reserve(b.size());for (std::vector<std::string>::const_iterator i = b.begin(); i != b.end(); i++)

result.push_back(Cipher(*i));return result;

}

Page 233: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 233

Separando as palavrasPrecisamos de uma função que, dada uma cadeia, dela extraia as palavras presentes para um vector, respeitando um conjunto de delimitadores. Será a função estática GetWords:class Caesar {// ...static std::vector<std::string> GetWords(const std::string& s,const std::string& delimiters = " \t");

};

class Caesar {// ...static std::vector<std::string> GetWords(const std::string& s,const std::string& delimiters = " \t");

};std::vector<std::string> Caesar::GetWords(const std::string& s,const std::string& delimiters)

{std::vector<std::string> result;std::string temp(s);std::string::size_type x;while ((x = temp.find_first_not_of(delimiters)) != std::string::npos){temp.erase(0, x);std::string::size_type y = temp.find_first_of(delimiters);result.push_back(temp.substr(0, y));temp.erase(0, y);

}return result;

}

std::vector<std::string> Caesar::GetWords(const std::string& s,const std::string& delimiters)

{std::vector<std::string> result;std::string temp(s);std::string::size_type x;while ((x = temp.find_first_not_of(delimiters)) != std::string::npos){temp.erase(0, x);std::string::size_type y = temp.find_first_of(delimiters);result.push_back(temp.substr(0, y));temp.erase(0, y);

}return result;

}

Por defeito, os delimitadores são o espaço e o tab.

Aprenda está técnica!

Repare na utilização coordenada das funções find_first_not_of, erase, find_first_of e substr,

Page 234: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 234

Separando as palavras, melhorDispensamos a variável local temp e a função erase, percorrendo directamente o argumento, tirando partido das funções find_... com um segundo argumento que indica a posição onde começa a busca:std::vector<std::string> Caesar::GetWords(const std::string& s,

const std::string& delimiters){

std::vector<std::string> result;std::string::size_type x = 0;while ((x = s.find_first_not_of(delimiters, x)) != std::string::npos){

std::string::size_type y = s.find_first_of(delimiters, x);result.push_back(s.substr(x, y - x));x = y;

}return result;

}

std::vector<std::string> Caesar::GetWords(const std::string& s,const std::string& delimiters)

{std::vector<std::string> result;std::string::size_type x = 0;while ((x = s.find_first_not_of(delimiters, x)) != std::string::npos){

std::string::size_type y = s.find_first_of(delimiters, x);result.push_back(s.substr(x, y - x));x = y;

}return result;

}

O primeiro argumento da função substr indica onde começa a extracção e o segundo o número de caracteres a extrair. Se este número for excessivo a função extrai até ao fim da cadeia. Note que aqui “extrair” significa “copiar”. Os caracteres “extraídos” para o resultado continuam no objecto, que fica na mesma, em qualquer caso.

Page 235: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 235

Concatenando cadeiasA função inversa da função GetWords concatenará numa cadeia única todas as cadeias de um vector, separando-as por um dado separador:

std::string Caesar::Concatenated(const std::vector<std::string>& b,const std::string& separator)

{std::string result;for (std::vector<std::string>::const_iterator i = b.begin(); i != b.end(); i++)

result += (i != b.begin() ? separator : "") + *i;return result;

}

std::string Caesar::Concatenated(const std::vector<std::string>& b,const std::string& separator)

{std::string result;for (std::vector<std::string>::const_iterator i = b.begin(); i != b.end(); i++)

result += (i != b.begin() ? separator : "") + *i;return result;

}

class Caesar {// ...

static std::string Concatenated(const std::vector<std::string>& b, const std::string& separator = " ");

};

class Caesar {// ...

static std::string Concatenated(const std::vector<std::string>& b, const std::string& separator = " ");

};

Repare na utilização do operador += da classe std::string. Serve para concatenar a cadeia argumento à cadeia objecto.

Page 236: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 236

Testando a cifra múltiplaEis uma função de teste que ilustra estas últimas funções.void TestCipherMany(){

std::cout << "Chave: ";std::string line;std::getline(std::cin, line);mas::Caesar c(line[0]);for (;;){

std::cout << ">>";std::string line;std::getline(std::cin, line);if (!std::cin)

break;std::vector<std::string> w = c.GetWords(c.UpperCase(line));std::vector<std::string> u = c.CipherMany(w);std::string r = c.Concatenated(u, "/");std::cout << ">>" << r << std::endl;

}}

void TestCipherMany(){

std::cout << "Chave: ";std::string line;std::getline(std::cin, line);mas::Caesar c(line[0]);for (;;){

std::cout << ">>";std::string line;std::getline(std::cin, line);if (!std::cin)

break;

std::string Caesar::UpperCase(const std::string& s){std::string result;result.reserve(s.size());for (std::string::const_iterator i = s.begin(); i != s.end(); i++)result.push_back(static_cast<char>(::toupper(*i)));

return result;}

std::vector<std::string> w = c.GetWords(c.UpperCase(line));std::vector<std::string> u = c.CipherMany(w);std::string r = c.Concatenated(u, "/");std::cout << ">>" << r << std::endl;

}}

std::string Caesar::UpperCase(const std::string& s){std::string result;result.reserve(s.size());for (std::string::const_iterator i = s.begin(); i != s.end(); i++)result.push_back(static_cast<char>(::toupper(*i)));

return result;}

A função UpperCase é uma função estática que faz o que o seu nome indica (mas não confunda com a da página 226):

Aqui termina o nosso exercício com a cifra de César.

Page 237: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 237

MeteorologiaOs registos da temperatura provenientes das nossas estações meteorológicas vão sendo acrescentados a um ficheiro de texto à medida que chegam. Em cada linha vem o nome da cidade e a temperatura (um número double), separados por um tab.Qual é a temperatura mínima registada para uma dada cidade?

namespace mas {

typedef std::pair<std::string, double> City;

class Meteo {private:

std::vector<City> cities;public:

explicit Meteo(int capacity);virtual ~Meteo();

virtual void Read(std::istream& input = std::cin);virtual void Write(std::ostream& output = std::cout) const;

};

}

namespace mas {

typedef std::pair<std::string, double> City;

class Meteo {private:

std::vector<City> cities;public:

explicit Meteo(int capacity);virtual ~Meteo();

virtual void Read(std::istream& input = std::cin);virtual void Write(std::ostream& output = std::cout) const;

};

}

Começamos com uma classe Meteo, com um membro de dados para registar todas as observações:

Observe este typedef, que atribui o nome City tipo std::pair<std::string, double>. Note bem: Citynão é um novo tipo, é apenas um sinónimo para o tipo declarado.

Neste caso, o typedef não é essencial, mas aligeira a programação e aumenta a legibilidade.

Page 238: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 238

Lendo linhas com tabsQuando os campos de uma linha estão delimitados, podemos lê-los todos de uma vez com a função utilitária GetTokens:std::vector<std::string> GetTokens(const std::string& s, const std::string& delimiters){

std::vector<std::string> result;std::string::size_type x = 0;while ((x = s.find_first_not_of(delimiters, x)) != std::string::npos){

std::string::size_type y = s.find_first_of(delimiters, x);result.push_back(s.substr(x, y - x));x = y;

}return result;

}

std::vector<std::string> GetTokens(const std::string& s, const std::string& delimiters){

std::vector<std::string> result;std::string::size_type x = 0;while ((x = s.find_first_not_of(delimiters, x)) != std::string::npos){

std::string::size_type y = s.find_first_of(delimiters, x);result.push_back(s.substr(x, y - x));x = y;

}return result;

}

void Meteo::Read(std::istream& input){

std::string line;while (std::getline(input, line)){

std::vector<std::string> tokens = mas::GetTokens(line, "\t");cities.push_back(std::make_pair(tokens[0], mas::AsDouble(tokens[1])));

}}

void Meteo::Read(std::istream& input){

std::string line;while (std::getline(input, line)){

std::vector<std::string> tokens = mas::GetTokens(line, "\t");cities.push_back(std::make_pair(tokens[0], mas::AsDouble(tokens[1])));

}}

Com isto, ler as cidades e as temperaturas fica simples:

Sim, isto é como a função da página 234.

A função AsDouble é uma função utilitária que converte de std::string para double. Veja mais adiante, página 254.

Page 239: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 239

Testando a leitura

A função de teste lê um ficheiro e escreve outro, com os mesmos valores:void TestReadWrite(){

mas::Meteo m(40);std::ifstream input("../../../Data/meteo_in_01.txt");m.Read(input);std::ofstream output("../../../Data/meteo_out.txt");m.Write(output);

}

void TestReadWrite(){

mas::Meteo m(40);std::ifstream input("../../../Data/meteo_in_01.txt");m.Read(input);std::ofstream output("../../../Data/meteo_out.txt");m.Write(output);

}

void Meteo::Write(std::ostream& output) const{

mas::Write2Line(cities, "\n", ";", output);}

void Meteo::Write(std::ostream& output) const{

mas::Write2Line(cities, "\n", ";", output);}

Eis a função Write, que se baseia na função utilitária Write2Line, a qual serve para escrever vectores de pares, permitindo parametrizar o separador de pares e o separador entre os dois elementos do par:

Page 240: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 240

Procurando uma cidade

Usamos busca linear, para maior generalidade (pois não assumimos que o vector está ordenado):std::vector<City>::const_iterator Meteo::FindCity(const std::string& name) const{

std::vector<City>::const_iterator result = cities.begin();while (result != cities.end() && result->first != name)

result++;return result;

}

std::vector<City>::const_iterator Meteo::FindCity(const std::string& name) const{

std::vector<City>::const_iterator result = cities.begin();while (result != cities.end() && result->first != name)

result++;return result;

}

Ao procurar no vector cities um elemento correspondente a uma cidade, dado o seu nome, retornamos um iterador para esse elemento ou o iterador cities.end() se não houver. Fazemos isso na função FindCity:class Meteo {// ...

virtual std::vector<City>::const_iterator FindCity(const std::string& name) const;};

class Meteo {// ...

virtual std::vector<City>::const_iterator FindCity(const std::string& name) const;};

Podíamos ter usado um ciclo for, é verdade, mas assim fica bem melhor, não fica?

Page 241: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 241

A temperatura numa cidade

Programa-se recorrendo à função FindCity:

Por construção, a função que dá a temperatura de uma cidade, dá a primeira temperatura, isto é, a temperatura do primeiro elemento do vector respeitante a essa cidade, se houver, ou lança uma excepção, se não houver.class Meteo {// ...virtual double TemperatureOf(const std::string& name) const;};

class Meteo {// ...virtual double TemperatureOf(const std::string& name) const;};

double Meteo::TemperatureOf(const std::string& name) const{

std::vector<City>::const_iterator x = FindCity(name);if (x == cities.end())throw ("\"" + name + "\": Localidade inexistente");

return x->second;}

double Meteo::TemperatureOf(const std::string& name) const{

std::vector<City>::const_iterator x = FindCity(name);if (x == cities.end())throw ("\"" + name + "\": Localidade inexistente");

return x->second;}

Quando uma função lança uma excepção, com a instrução throw, a função termina logo a seguir e o controlo passa para a função que a chamou, onde a excepção ou é tratada ou se propaga “para cima”, isto é, para a função que chamou essa.

Page 242: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 242

Tratando a excepçãoA função de teste trata a excepção, afixando uma mensagem apropriada. Para podermos tratar excepções, temos de colocar as instruções que as podem lançar dentro de try-blocks. Observe:void TestTemperatures(){

mas::Meteo m(40);std::ifstream input("../../../Data/meteo_in_01.txt");m.Read(input);for (;;){

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

break;try {

std::cout << m.TemperatureOf(city) << std::endl;} catch (const std::string& e){

std::cout << "Erro: " << e << std::endl;}

}}

void TestTemperatures(){

mas::Meteo m(40);std::ifstream input("../../../Data/meteo_in_01.txt");m.Read(input);for (;;){

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

break;try {

std::cout << m.TemperatureOf(city) << std::endl;} catch (const std::string& e){

std::cout << "Erro: " << e << std::endl;}

}}

Repare: a excepção é uma std::string, a qual é escrita na mensagem de erro.É na parte catch que

tratamos a excepção.

Page 243: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 243

Ordenando o vectorSe houver várias observações para a mesma cidade e quisermos a temperatura mínima, basta ordenar o vector, pois a ordenação por defeito é pelo primeiro campo do par (o nome), desempatando por ordem crescente do segundo tempo (a temperatura).class Meteo {// ...virtual void Sort();};

class Meteo {// ...virtual void Sort();};

void TestTemperaturesWithDuplicates(){

mas::Meteo m(40);std::ifstream input("../../../Data/meteo_in_02.txt");m.Read(input);m.Sort();for (;;){

//...}

}

void TestTemperaturesWithDuplicates(){

mas::Meteo m(40);std::ifstream input("../../../Data/meteo_in_02.txt");m.Read(input);m.Sort();for (;;){

//...}

}

void Meteo::Sort(){

mas::Sort(cities);}

void Meteo::Sort(){

mas::Sort(cities);}

Page 244: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 244

Ordenando de outras maneirasSe quiséssemos a temperatura máxima ou ordenávamos por nome e por ordem decrescente de temperatura ou buscávamos do fim para o princípio. Vejamos como ordenar dessa maneira:class Meteo {// ...virtual void SortByNameUpTemperatureDown();static bool LessByNameUpTemperatureDown(const City& x, const City& y);

};

class Meteo {// ...virtual void SortByNameUpTemperatureDown();static bool LessByNameUpTemperatureDown(const City& x, const City& y);

};

void Meteo::SortByNameUpTemperatureDown(){

std::sort(cities.begin(), cities.end(), &LessByNameUpTemperatureDown);}

void Meteo::SortByNameUpTemperatureDown(){

std::sort(cities.begin(), cities.end(), &LessByNameUpTemperatureDown);}

bool Meteo::LessByNameUpTemperatureDown(const City& x, const City& y){

return mas::LessFirstGreaterSecond(x, y);}

bool Meteo::LessByNameUpTemperatureDown(const City& x, const City& y){

return mas::LessFirstGreaterSecond(x, y);}

A função booleana representa a relação de ordem que queremos impor. É usada para parametrizar a função std::sort.

Esta função mas::LessFirstGreaterSecond éuma função genérica utilitária que representa a relação de ordem de pares crescente pelo primeiro elemento desempatando por ordem decrescente do segundo. (É o que convém neste caso.)

Repare no &, necessário porque o argumento da função é uma função.

Page 245: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 245

Testando a nova relação de ordemEis uma função de teste para comprovar a ordenação:

void TestSortByNameUpTemperatureDown()

{

mas::Meteo m(40);

std::ifstream input("../../../Data/meteo_in_02.txt");

m.Read(input);

m.SortByNameUpTemperatureDown();

std::ofstream output("../../../Data/meteo_out.txt");

m.Write(output);

}

void TestSortByNameUpTemperatureDown()

{

mas::Meteo m(40);

std::ifstream input("../../../Data/meteo_in_02.txt");

m.Read(input);

m.SortByNameUpTemperatureDown();

std::ofstream output("../../../Data/meteo_out.txt");

m.Write(output);

}

E se quiséssemos por ordem decrescente de nomes e crescente de temperaturas? Na verdade, de quantas maneiras podemos ordenar um vector de pares?

Page 246: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 246

Relações de ordem para pares, simplesSe não houver duplicados no primeiro elemento do par, temos a ordem crescente e a ordem decrescente pelo primeiro elemento:template <class T, class U>bool LessFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){return x.first < y.first;

}

template <class T, class U>bool LessFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){return x.first < y.first;

}template <class T, class U>bool GreaterFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){return x.first > y.first;

}

Analogamente, não havendo duplicados no segundo elemento do par, temos a ordem crescente e a ordem decrescente pelo segundo elemento:

template <class T, class U>bool GreaterFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){return x.first > y.first;

}

template <class T, class U>bool LessSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){return x.second < y.second;

}

template <class T, class U>bool LessSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){return x.second < y.second;

}template <class T, class U>bool GreaterSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){return x.second > y.second;

}

template <class T, class U>bool GreaterSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){return x.second > y.second;

}

Page 247: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 247

Relações de ordem com desempateHá oito casos, correspondentes a cada um dos anteriores, desempatando por ordem crescente ou decrescente do outro elemento.O caso padrão é o que corresponde à ordem crescente nos dois elementos:

template <class T, class U>

bool LessFirstLessSecond(const std::pair<T, U>& x, const std::pair<T, U>& y)

{

return !(y.first < x.first) && (!(x.first == y.first) || x.second < y.second);

}

template <class T, class U>

bool LessFirstLessSecond(const std::pair<T, U>& x, const std::pair<T, U>& y)

{

return !(y.first < x.first) && (!(x.first == y.first) || x.second < y.second);

}

Esta função não é reflexiva. Quer dizer, se os dois argumentos forem iguais, a função retorna false. É preciso que seja assim para que a função std::sort funcione devidamente.

Page 248: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 248

Oito ordens com desempateEis as oito relações de ordem para pares com desempate:template <class T, class U>bool LessFirstLessSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool LessFirstGreaterSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool GreaterFirstLessSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool GreaterFirstGreaterSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool LessSecondLessFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool LessSecondGreaterFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool GreaterSecondLessFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool GreaterSecondGreaterFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool LessFirstLessSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool LessFirstGreaterSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool GreaterFirstLessSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool GreaterFirstGreaterSecond(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool LessSecondLessFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool LessSecondGreaterFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool GreaterSecondLessFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

template <class T, class U>bool GreaterSecondGreaterFirst(const std::pair<T, U>& x, const std::pair<T, U>& y){...}

Page 249: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 249

Temperaturas médiasQueremos construir o vector das temperaturas médias. É um vector de pares.A ideia é partir do vector já ordenado por nome e fazer o processamento numa só passagem, o que é favorável, pois todos os registos de cada cidade vêm de seguida.class Meteo {// ...virtual std::vector<City> AverageTemperatures() const;

// pre "sorted by SortByName";};

class Meteo {// ...virtual std::vector<City> AverageTemperatures() const;

// pre "sorted by SortByName";};

O comentário de pré-condição indica que antes de a função ser chamada o vector deve ter sido ordenado com a função SortByName (ou com uma função compatível). Note que a função AverageTemperatures é um selector e não lhe compete ordenar o vector.

Page 250: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 250

Ciclo duploO processamento envolve um ciclo duplo. Observe:std::vector<City> Meteo::AverageTemperatures() const{

std::vector<City> result;result.reserve(cities.size());std::vector<City>::const_iterator i = cities.begin();while (i != cities.end()){

std::string current = i->first;double sum = 0;int count = 0;do{

sum += i->second;count++;i++;

}while (i != cities.end() && i->first == current);result.push_back(std::make_pair(current, sum/ count));

}return result;

}

std::vector<City> Meteo::AverageTemperatures() const{

std::vector<City> result;result.reserve(cities.size());std::vector<City>::const_iterator i = cities.begin();while (i != cities.end()){

std::string current = i->first;double sum = 0;int count = 0;do{

sum += i->second;count++;i++;

}while (i != cities.end() && i->first == current);result.push_back(std::make_pair(current, sum/ count));

}return result;

}

O ciclo exterior processa a sequência de grupos de registos da mesma cidade.O ciclo interior processa cada um dos registos da mesma cidade.

Cada grupo éidentificado pelo valor da variável current.

Page 251: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 251

Temperaturas máximasAgora queremos o vector das temperaturas máximas. Podemos usar a mesma técnica, se o vector estiver ordenado por nome e temperatura decrescente:class Meteo {// ...virtual std::vector<City> MaxTemperatures() const;

// pre "sorted by SortByNameUpTemperatureDown";};

class Meteo {// ...virtual std::vector<City> MaxTemperatures() const;

// pre "sorted by SortByNameUpTemperatureDown";};

std::vector<City> Meteo::MaxTemperatures() const{

std::vector<City> result;result.reserve(cities.size());std::vector<City>::const_iterator i = cities.begin();while (i != cities.end()){

result.push_back(*i);doi++;

while (i != cities.end() && i->first == result.back().first);}return result;

}

std::vector<City> Meteo::MaxTemperatures() const{

std::vector<City> result;result.reserve(cities.size());std::vector<City>::const_iterator i = cities.begin();while (i != cities.end()){

result.push_back(*i);doi++;

while (i != cities.end() && i->first == result.back().first);}return result;

}

Cá está o ciclo duplo outra vez.

Temos de apanhar o primeiro registo de cada grupo.

Page 252: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 252

Temperaturas mínimasPara as temperaturas mínimas, temos de apanhar o último registo de cada grupo (admitindo que o vector estáordenado como no caso anterior).

class Meteo {// ...virtual std::vector<City> MinTemperatures() const;

// pre "sorted by SortByNameUpTemperatureDown";};

class Meteo {// ...virtual std::vector<City> MinTemperatures() const;

// pre "sorted by SortByNameUpTemperatureDown";};

std::vector<City> Meteo::MinTemperatures() const{

std::vector<City> result;result.reserve(cities.size());std::vector<City>::const_iterator i = cities.begin();while (i != cities.end()){

std::string current = i->first;double previous;do{

previous = i->second;i++;

}while (i != cities.end() && i->first == current);result.push_back(std::make_pair(current, previous));

}return result;

}

std::vector<City> Meteo::MinTemperatures() const{

std::vector<City> result;result.reserve(cities.size());std::vector<City>::const_iterator i = cities.begin();while (i != cities.end()){

std::string current = i->first;double previous;do{

previous = i->second;i++;

}while (i != cities.end() && i->first == current);result.push_back(std::make_pair(current, previous));

}return result;

}

Apanhar o último dásempre mais trabalho do que apanhar o primeiro...

Page 253: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 253

Testando as temperaturas todasEis uma função de teste para as três funções Average-Temperatures, MaxTemperatures e MinTemperatures:void TestTemperaturesAll(){

mas::Meteo m(40);std::ifstream input("../../../Data/meteo_in_02.txt");m.Read(input);m.SortByNameUpTemperatureDown();std::vector<mas::City> av = m.AverageTemperatures();std::vector<mas::City> ma = m.MaxTemperatures();std::vector<mas::City> mi = m.MinTemperatures();for (std::vector<mas::City>::const_iterator i = av.begin(),

j = ma.begin(),k = mi.begin(); i != av.end(); i++, j++, k++)

std::cout << std::left << std::setw(20) << i->first << std::fixed << std::setprecision(1) << std::right<< std::setw(6) << i->second<< std::setw(6) << j->second<< std::setw(6) <<k->second << std::endl;

}

void TestTemperaturesAll(){

mas::Meteo m(40);std::ifstream input("../../../Data/meteo_in_02.txt");m.Read(input);m.SortByNameUpTemperatureDown();std::vector<mas::City> av = m.AverageTemperatures();std::vector<mas::City> ma = m.MaxTemperatures();std::vector<mas::City> mi = m.MinTemperatures();for (std::vector<mas::City>::const_iterator i = av.begin(),

j = ma.begin(),k = mi.begin(); i != av.end(); i++, j++, k++)

std::cout << std::left << std::setw(20) << i->first << std::fixed << std::setprecision(1) << std::right<< std::setw(6) << i->second<< std::setw(6) << j->second<< std::setw(6) <<k->second << std::endl;

}

Observe o ciclo for com três iteradores.

Observe os manipuladores std::lefte std::right que mandam encostar àesquerda e à direita no campo de escrita.

Page 254: A Verdadeira e Única Programação Dois · implementação chama-se X.cpp. Isto é uma convenção nossa, para não nos perdermos. Em rigor, os nomes dos ficheiros podem ser quaisquer

20-02-2005 Programação II © Pedro Guerreiro 2004 254

Convertendo de cadeia para númeroSe um valor numérico foi lido para uma std::stringprecisamos depois de convertê-lo para int ou para double(consoante o caso). Temos as funções utilitárias AsInt e AsDouble para isso:

int AsInt(const std::string& s, int base){int result;char *endp;errno = 0; result = ::strtol(s.c_str(), &endp, base);if (s.empty() || errno || *endp){errno = 0;std::string message("\"" + s + "\" is not a legal int value.");throw message;

}return result;

}

double AsDouble(const std::string& s){double result;char *endp;errno = 0;result = ::strtod(s.c_str(), &endp);if (s.empty() || errno || *endp){errno = 0;std::string message("\"" + s + "\" is not a legal double value.");throw message;

}return result;

}

int AsInt(const std::string& s, int base){int result;char *endp;errno = 0; result = ::strtol(s.c_str(), &endp, base);if (s.empty() || errno || *endp){errno = 0;std::string message("\"" + s + "\" is not a legal int value.");throw message;

}return result;

}

double AsDouble(const std::string& s){double result;char *endp;errno = 0;result = ::strtod(s.c_str(), &endp);if (s.empty() || errno || *endp){errno = 0;std::string message("\"" + s + "\" is not a legal double value.");throw message;

}return result;

}

Ambas lançam excepções se a conversão não for possível.Ambas recorrem a funções de biblioteca para fazerem a conversão propriamente dita.

Ambas as funções pertencem ao espaço de nomes mas.

Na função AsInt, por defeito a base é 10.

A função AsDoublesurgiu na página 238.