Page 1

Curso de Programação com C++ http://ctp.di.fct.unl.pt/~pg/cpp2003 Fevereiro de 2003 por Pedro Guerreiro pg@di.fct.unl.pt, 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 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

1

Objectivos do C++ A linguagem de programação C++ foi inventada por Bjarne Stroustrup, nos laboratórios Bell, com o objectivo de: ƒ Ser um C melhor. ƒ Suportar a programação com tipos abstractos. ƒ Suportar a programação orientada pelos objectos. ƒ Suportar a programação genérica.

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

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

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

2


Bibliografia Geral ƒ The C++ Programming Language (3rd edition), Bjarne Stroustrup, 1997. ƒ Programação com Classes em C++, …, 2000. ƒ STL Tutorial and Reference Guide, David Musser, Gillmer Derge, Atul Saini, 2001.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

3

Bibliografia Complementar ƒ Introduction 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.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

4


O C++ e o C O 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, inputoutput seguro, referências, funções inline, excepções, ... 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

5

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: Isto é a declaração da classe Point.

class classPoint Point{{ Membros de dados private: private: double doublex; x; double Membros funcionais doubley; y; public: public: Point(); Point(); Point(double Point(doublex, x,double doubley); y); Point(const Point& Point(const Point&other); other); virtual virtual~Point(); ~Point();

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

virtual virtualvoid voidTranslate(double Translate(doubledx, dx,double doubledy); dy); virtual void Scale(double fx, double virtual void Scale(double fx, doublefy); fy); virtual virtualvoid voidRotate(double Rotate(doubleangle); angle); virtual double virtual doubleDistanceTo(const DistanceTo(constPoint& Point&other) other)const; const; }; }; 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

6


Tipos básicos ƒ Números inteiros: tipo int. ƒ Números reais: tipo double. ƒ Booleanos: tipo bool.

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).

ƒ Caracteres: tipo char.

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

E ainda: ƒ Cadeias de caracteres: tipo std::string. Em rigor, este não é um tipo básico. É sim uma classe da biblioteca STL. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

7

As classes são tipos A classe Point é usada para declarar objectos de tipo Point. Point Pointp1; p1; Point p2; Point p2;

Um ponto Outro ponto

Point Pointa[10]; a[10];

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, p1.Translate(2.0,-3.25); -3.25); double x = p2.DistanceTo(p1); double x = p2.DistanceTo(p1); a[0].Rotate(1.57079632679489661923); a[0].Rotate(1.57079632679489661923); a[3].Scale(-1.0, a[3].Scale(-1.0,1.0); 1.0);

2003-07-19

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

Curso de Programação com C++ © Pedro Guerreiro 2003

8


As funções têm um objecto A funções da classe Point actuam sobre um objecto da clase Point. Em cada caso, esse objecto é o objecto da função. p1.Translate(2.0, p1.Translate(2.0, -3.25); -3.25);

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

double double xx = = p2.DistanceTo(p1); p2.DistanceTo(p1); função é guardado na variável x, aqui declarada. a[0] é o objecto, 1.57... é o

a[0].Rotate(1.57079632679489661923); a[0].Rotate(1.57079632679489661923); argumento. O valor de a[0] muda. a[3].Scale(-1.0, a[3].Scale(-1.0, 1.0); 1.0);

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

O objecto da função é quem “sofre” o efeito da função. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

9

A classe Point Há quatro qualidades de funções membro: • Construtores • Destrutor

class classPoint Point{{ private: private: double doublex; x; double doubley; y; Construtores public: public: Point(); Point(); Point(double Point(doublex, x,double doubley); y); Point(const Point& Point(const Point&other); other); Destrutor virtual virtual~Point(); ~Point(); virtual virtualdouble doubleX() X()const; const; virtual double Y() virtual double Y()const; const;

• Selectores • Modificadores

Selectores

virtual virtualvoid voidTranslate(double Translate(doubledx, dx,double doubledy); dy); virtual void Scale(double fx, double Modificadores virtual void Scale(double fx, doublefy); fy); virtual void Rotate(double angle); virtual void Rotate(double angle); virtual virtualdouble doubleDistanceTo(const DistanceTo(constPoint& Point&other) other)const; const; virtual virtualdouble doubleAngle() Angle()const; const; virtual double Modulus() virtual double Modulus()const; const;

Mais selectores

virtual virtualvoid voidWrite() Write()const; const; virtual void WriteLine() virtual void WriteLine()const; const; }; }; 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

10


Construtores Cada 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();

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

Point(double Point(doublex, x, double double y); y); Point(const Point(const Point& Point& other); other); // // ... ...

Exemplo:

2003-07-19

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.

Inicialização por defeito. Point Pointp; p; Point Pointq(3.0, q(3.0,4.0); 4.0); Inicialização elementar. Inicialização por cópia. Point Pointr(q); r(q); Point Pointz[32]; z[32]; Os 32 elementos do quadro z são inicializados por defeito. Curso de Programação com C++ © Pedro Guerreiro 2003

11

Destrutor Cada 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 virtual ~Point(); ~Point();

O nome do destrutor numa classe X é ~X.

// // ... ...

Regra: todos os destrutores são declarados virtual. Note bem: em rigor, não é o Os destrutores servem para libertar a destrutor que “destrói” o objecto. memória que foi alocada dinamicamente O objecto é destruído por um meio e o destrutor é pelo objecto durante a sua existência, no outro invocado automaticamente momento em que o objecto está prestes a nessa altura para “arrumar a casa”. deixar de existir. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

12


Selectores Os selectores são as funções da classe que consultam o estado do objecto, sem o modificar. // // ... ... virtual virtual double double X() X() const; const; Regra: todos os selectores são virtual double Y() const; virtual double Y() const; declarados virtual e const. // // ... ... virtual virtual double double Angle() Angle() const; const; virtual double Modulus() virtual double Modulus() const; const; virtual double DistanceTo(const virtual double DistanceTo(const Point& Point& other) other) const; const; virtual virtual void void Write() Write() const; const; virtual void WriteLine() virtual void WriteLine() const; const;

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

13

Modificadores Os modificadores são as funções da classe que modificam o estado do objecto. Não devolvem qualquer informação. // // ... ... virtual virtual void void Translate(double Translate(double dx, dx, double double dy); dy); virtual void Scale(double fx, double fy); virtual void Scale(double fx, double fy); virtual virtual void void Rotate(double Rotate(double angle); angle); // ... // ...

Os nomes das funções sugerem o seu significado. Regra: todos os modificadores são declarados virtual e retornam void. 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

14


Programação das classes Cada classe C++ ocupa dois ficheiros. O ficheiro da declaração e o ficheiro da implementação. 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. Isto é uma convenção nossa, Para uma classe X, o ficheiro da para não nos perdermos. Em declaração chama-se X.h e o ficheiro de rigor, os nomes dos ficheiros implementação chama-se X.cpp. podem ser quaisquer. 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

15

Exemplo: ponto médio Escrever 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 intmain() main() {{ double doublex; x; double doubley; y; std::cout std::cout<< <<"Coordenadas "Coordenadasdo doprimeiro primeiroponto: ponto:";"; std::cin >> x >> y; std::cin >> x >> y; Point Pointp1(x, p1(x,y); y); std::cout std::cout<< <<"Coordenadas "Coordenadasdo dosegundo segundoponto: ponto:";"; std::cin >> x >> y; std::cin >> x >> y; Point Pointp2(x, p2(x,y); y); Point pm((p1.X() Point pm((p1.X()++p2.X()) p2.X())//2, 2,(p1.Y() (p1.Y()++p2.Y()) p2.Y())//2); 2); std::cout << "Ponto médio: "; std::cout << "Ponto médio: "; pm.WriteLine(); pm.WriteLine(); double doubled1 d1==p1.DistanceTo(pm); p1.DistanceTo(pm); double d2 double d2==p2.DistanceTo(pm); p2.DistanceTo(pm); std::cout std::cout<< <<"Distâncias: "Distâncias:""<< <<d1 d1<< <<""""<< <<d2 d2<< <<std::endl; std::endl; return 0; return 0; }}

Infelizmente, as letras acentuadas e os cês cedilhados saem mal na consola / 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

16


Ficheiros de inclusão Para 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 #include<iostream> <iostream> #include #include"Point.h" "Point.h"

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

int intmain() main() {{ int int x; x; int int y; y; // ... // ... Point Pointp1(x, p1(x,y); y); // ... // ... std::cout std::cout<< <<"Distâncias: "Distâncias:""<< <<d1 d1<< <<""""<< <<d2 d2<< <<std::endl; std::endl; }} 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.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

17

Exercícios 1 1. 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

18


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

Passagem por valor.

virtual virtualdouble doubleDistanceTo(Point DistanceTo(Pointother) other)const; const; ... ... class classPoint Point{{ }; }; ... ...

Passagem por referência.

virtual virtualdouble doubleDistanceTo(Point& DistanceTo(Point&other) other)const; const; ... ... class }; classPoint Point{{ }; Passagem por referência constante. ... ... virtual virtualdouble doubleDistanceTo(const DistanceTo(constPoint& Point&other) other)const; const; ... ... }; };

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

Curso de Programação com C++ © Pedro Guerreiro 2003

19

Por valor, por referência •

2003-07-19

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. Curso de Programação com C++ © Pedro Guerreiro 2003

20


Passagem de argumentos: regra prática Os argumentos de um tipo simples (int, double, char, bool, apontador) passam por valor: virtual virtualvoid voidScale(double Scale(doublefx, fx,double doublefy); fy); virtual void Rotate(double angle); virtual void Rotate(double angle);

Os argumentos de um tipo classe passam por referência constante: virtual virtualdouble doubleDistanceTo(const DistanceTo(constPoint& Point&other) other)const; const;

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.

A passagem por referência não constante usa-se em casos particulares, por exemplo, quando os argumentos representam ficheiros: virtual virtualvoid voidWrite(std::ostream& Write(std::ostream&output output==std::cout) std::cout)const; const; virtual void WriteLine(std::ostream& output = std::cout) virtual void WriteLine(std::ostream& output = std::cout)const; const; virtual virtualvoid voidRead(std::istream& Read(std::istream&input input==std::cin); std::cin); 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

21

Argumentos por defeito Ocasionalmente, aparecem nas classes funções que têm argumentos por defeito: virtual virtualvoid voidWrite(std::ostream& Write(std::ostream&output output==std::cout) std::cout)const; const; virtual void WriteLine(std::ostream& output = std::cout) virtual void WriteLine(std::ostream& output = std::cout)const; const; virtual virtualvoid voidRead(std::istream& Read(std::istream&input input==std::cin); std::cin);

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

p.WriteLine(); p.WriteLine();

é o mesmo do que

Point Pointp; p; ... ...

p.WriteLine(std::cout); p.WriteLine(std::cout);

Outro exemplo: se quiséssemos que por defeito as rotações fossem de 90 graus, declararíamos: virtual virtualvoid voidRotate(double Rotate(doubleangle angle==1.57079632679489661923); 1.57079632679489661923);

2003-07-19

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

Curso de Programação com C++ © Pedro Guerreiro 2003

22


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

double doublePoint::X() Point::X()const const {{ return returnx; x; }}

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

void voidPoint::Translate(double Point::Translate(doubledx, dx,double doubledy) dy) {{ xx+= +=dx; dx; yy+= +=dy; dy; }}

double doublePoint::Y() Point::Y()const const {{ return returny; y; }}

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

void voidPoint::Scale(double Point::Scale(doublefx, fx,double doublefy) fy) {{ xx*= *=fx; fx; yy*= *=fy; fy; ::sin(...) e ::cos(...): }}

funções de biblioteca

void voidPoint::Rotate(double Point::Rotate(doubleangle) angle) para o seno e coseno. {{ O qualificador virtual não reaparece na Requerem double x0 ==x; #include <cmath>. double x0 x; definição. double doubley0 y0==y; y; xx==x0 x0**::cos(angle) ::cos(angle)--y0 y0**::sin(angle); ::sin(angle); yy==x0 x0**::sin(angle) ::sin(angle)++y0 y0**::cos(angle); ::cos(angle); 2003-07-19 Curso de Programação com 23 }} C++ © Pedro Guerreiro 2003

A instrução return Sintaxe:

Semântica:

return expressão;

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

double doublePoint::X() Point::X()const const {{ return returnx; x; }}

2003-07-19

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

::sqrt(x): raiz quadrada de x.

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

::pow(x, y): x elevado a y.

::atan2(y, x): arcotangente de y/x.

Curso de Programação com C++ © Pedro Guerreiro 2003

24


Instrução de afectação Sintaxe:

Semântica:

variável = expressão;

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

Mais uma função... class classPoint Point{{ ... ...

void voidPoint::Set(double Point::Set(doublex, x,double doubley) y) {{ this->x this->x==x; x; Atenção: this->x é o membro de this->y = y; this->y = y; dados, x sozinho é o argumento. }}

}; };

void voidPoint::Rotate(double Point::Rotate(doubleangle) angle) {{ double doublex0 x0==x; x; double y0 double y0==y; y; xx==x0 x0**::cos(angle) ::cos(angle)--y0 y0**::sin(angle); ::sin(angle); yy==x0 * ::sin(angle) + y0 x0 * ::sin(angle) + y0**::cos(angle); ::cos(angle); }}

virtual virtualvoid voidSet(double Set(doublex, x,double doubley); y); ... ...

Evitaríamos o this usando argumentos com nomes diferentes dos dos membros de dados. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

25

Afectações mistas Normalmente, 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 intn; n; double doublex; x; bool boolb; b; char c; char c; ... ... xx==n; n; nn==x; x; nn==b; b; bb==n; n; nn==c; c; cc==n; n; 2003-07-19

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. Curso de Programação com C++ © Pedro Guerreiro 2003

26


Conversão explícita Evitam-se aqueles warnings fazendo uma conversão explícita para o tipo da variável. No caso conversão para int usa-se o operador static_cast<T>, no caso da int intn; n; double conversão para bool, usa-se o operador doublex; x; bool boolb; b; != (operador de desigualdade). char charc; c; ... ... xx==n; n; nn==static_cast<int>(x); static_cast<int>(x); nn==b; b; bb==nn!= !=0; 0; nn==c; c; cc==n; n; 2003-07-19

Nota: usa-se static_cast<double> para forçar conversão para double em expressões: int intn; n; int intsum; sum;

... ... double doubleaverage average==static_cast<double>(sum) static_cast<double>(sum)//n; n; Curso de Programação com C++ © Pedro Guerreiro 2003

27

Afectações não simples

O 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 voidPoint::Translate(double Point::Translate(doubledx, dx,double doubledy) dy) {{ xx+= +=dx; dx; yy+= +=dy; dy; }} void voidPoint::Scale(double Point::Scale(doublefx, fx,double doublefy) fy) {{ xx*= Este é o operador de afectação *=fx; fx; yy*= *=fy; fy; não simples *=. }}

Eis a tabela dos operadores de afectação não simples mais usuais: 2003-07-19

Use

Programar assim: void Point::Translate(...) { x = x + dx; y = x + dy; } seria mau estilo.

Em vez de

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

Curso de Programação com C++ © Pedro Guerreiro 2003

28


Afectações de classe Podemos afectar um objecto de um tipo classe a outro? Sim, podemos. Significa a afectação membro a membro: ... ... Point Pointp(4, p(4,7); 7); Point q(0, Point q(0,2); 2); ... ... p.Rotate(1.57079632679489661923); p.Rotate(1.57079632679489661923); q.Scale(0, q.Scale(0,-1); -1); ... ... qq==p; Afectação membro a membro. p; ... ...

Frequentemente o que queremos é a construção por cópia e não a afectação: ... ... Point Pointp(4, p(4,7); 7); ... ... p.Rotate(1.57079632679489661923); p.Rotate(1.57079632679489661923); Point Pointq; q; Mau estilo: inicialização logo qq==p; p; ... seguida de afectação. ...

2003-07-19

... ... Point Pointp(4, p(4,7); 7); ... ... p.Rotate(1.57079632679489661923); p.Rotate(1.57079632679489661923); Point Pointq(p); q(p); ... Bom estilo... ...

Curso de Programação com C++ © Pedro Guerreiro 2003

29

Definição das funções Cada 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 voidPoint::Rotate(double Point::Rotate(doubleangle) angle) {{ double Variáveis locais doublex0 x0==x; x; double y0 = y; double y0 = y; xx==x0 x0**::cos(angle) ::cos(angle)--y0 y0**::sin(angle); ::sin(angle); yy==x0 * ::sin(angle) + y0 x0 * ::sin(angle) + y0**::cos(angle); ::cos(angle); }}

2003-07-19

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

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

Curso de Programação com C++ © Pedro Guerreiro 2003

30


this Quando definimos uma função, frequentemente o objecto da função fica implícito: double doublePoint::Modulus() Point::Modulus()const const {{ Este x e este y são referenciam os membros de return return::sqrt(x*x ::sqrt(x*x++y*y); y*y); dados do objecto da função, em cada chamada. }} double doublePoint::DistanceTo(const Point::DistanceTo(constPoint& Point&other) other)const const {{ return return::sqrt(::pow(x ::sqrt(::pow(x--other.x, other.x,2) 2)++::pow(y ::pow(y--other.y, other.y,2)); 2)); }}

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: void voidPoint::Set(double Point::Set(doublex, x,double doubley) y) {{ Aqui x e y sozinhos referenciam os argumentos e this->x this->x==x; x; this->x e this->y referenciam os membros de this->y this->y==y; y; dados do objecto. }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

31

Escrevendo

Em C++, escreve-se usando o operador de inserção <<: void voidPoint::Write(std::ostream& Point::Write(std::ostream&output) output)const const {{ output output<< <<xx<< <<""""<< <<y; y; }} void voidPoint::WriteLine(std::ostream& Point::WriteLine(std::ostream&output) output)const const {{ Tecnicamente, este std::endl é um manipulador que quando Write(output); Write(output); enviado para uma stream de escrita provoca uma mudança output output<< <<std::endl; std::endl; de linha. }}

Para escrever na consola, usa-se a stream std::cout: ... ... std::cout std::cout<< <<"Coordenadas "Coordenadasdo doprimeiro primeiroponto: ponto:";"; ... ... Point Pointpm((p1.X() pm((p1.X()++p2.X()) p2.X())//2, 2,(p1.Y() (p1.Y()++p2.Y()) p2.Y())//2); 2); std::cout << "Ponto médio: "; std::cout << "Ponto médio: "; Como std::cout é o argumento por defeito da função pm.WriteLine(); pm.WriteLine(); WriteLine, escrever pm.WriteLine() é o mesmo do double que escrever pm.WriteLine(std::cout). doubled1 d1==...; ...; double doubled2 d2==...; ...; std::cout std::cout<< <<"Distâncias: "Distâncias:""<< <<d1 d1<< <<""""<< <<d2 d2<< <<std::endl; std::endl; 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003

32


Lendo

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

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

class classPoint Point{{ ... ... virtual virtualvoid voidRead(std::istream& Read(std::istream&input input==std::cin); std::cin); ... ... }; };

{{ input input>> >>xx>> >>y; y; }}

Para ler da consola, usa-se a stream std::cin: ... ... int intx; x; int inty; y; std::cout std::cout<< <<"Coordenadas "Coordenadasdo doprimeiro primeiroponto: ponto:";"; std::cin >> x >> y; std::cin >> x >> y; Point Pointp1(x, p1(x,y); y); std::cout std::cout<< <<"Coordenadas "Coordenadasdo dosegundo segundoponto: ponto:";"; std::cin >> x >> y; 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).

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

33

Definindo os construtores Os 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(): Point::Point(): x(0), x(0), A lista de inicializadores vem entre aquele sinal de dois pontos a seguir ao y(0) y(0) cabeçalho e a chaveta a abrir. Só os construtores é que têm lista de {{ inicializadores. }} Point::Point(double Point::Point(doublex, x,double doubley): y): x(x), x(x), Este caso é engraçado: o x y(y) y(y) de fora é o membro de dados, {{ o x de dentro é o argumento. }} (Idem para o y.) Point::Point(const Point::Point(constPoint& Point&other): other): x(other.x), x(other.x), y(other.y) y(other.y) {{ }} 2003-07-19

No corpo dos construtores programamos o que fizer falta.

Por exemplo, podemos escrever mensagens para debug: Point::Point(): Point::Point(): x(0), x(0), y(0) y(0) {{ std::cout std::cout<< <<"Default "Defaultconstructor. constructor.""<< <<std::endl; std::endl; }}

Curso de Programação com C++ © Pedro Guerreiro 2003

34


Definindo o destrutor

Na classe Point o destrutor não faz nada. Por isso, fica em branco: Point::~Point() Point::~Point() {{ }}

Podemos aproveitar para escrever uma mensagem, para ajudar a perceber o funcionamento dos destrutores: Point::~Point() Point::~Point() {{ std::cout std::cout<< <<"Destructor: "Destructor:"; "; WriteLine(); WriteLine(); }}

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

35

Formatando os números double Para 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() TestDoubleOutput() {void {double x; doubley; x; double double y;<< "Coordinates (x and y), please: "; std::cout std::cout << "Coordinates (x and y), please: "; std::cin >> xx>> std::cin >> >>y; y; Point p(x, y); Point p(x, y);

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

double doubledd==p.DistanceTo(Point()); p.DistanceTo(Point()); std::cout << ""<< std::cout<< <<"General "Generalformat: format: <<std::endl; std::endl; std::cout std::cout <<"Distance "Distanceto toorigin: origin:""<< <<dd<< <<std::endl; std::endl; std::cout two std::cout<< <<"Fixed "Fixedformat, format, twodecimal decimaldigits: digits:""<< <<std::endl; std::endl; std::cout << std::fixed << std::setprecision(2); std::cout << std::fixed << std::setprecision(2); std::cout << "Distance to origin: " << d << std::endl; std::cout << "Distance to origin: " << d << std::endl; std::cout << eight std::cout<< <<"Scientific "Scientificformat, format, eightdecimal decimaldigits: digits:""<< <<std::endl; std::endl; std::cout std::scientific << std::setprecision(8); std::cout << std::scientific << std::setprecision(8); std::cout std::cout<< <<"Distance "Distanceto toorigin: origin:""<< <<dd<< <<std::endl; std::endl; std::cout << "Returning to default format, six std::cout << "Returning to default format, sixdigits: digits:""<< <<std::endl; std::endl; std::cout.setf(0, std::ios_base::floatfield); std::cout.setf(0, std::ios_base::floatfield); std::cout << std::setprecision(6); std::cout<< <<"Distance std::setprecision(6); std::cout to std::cout << "Distance toorigin: origin:""<< <<dd<< <<std::endl; std::endl; }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

36


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

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

37

Funções de teste Para 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. 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. Tipicamente, a função main chama apenas uma das funções de teste. As outras ficam em comentário. A função main termina sempre com return 0;

2003-07-19

#include #include<iostream> <iostream> ////... ... #include #include"Point.h" "Point.h" void voidTestPoints(); TestPoints(); void voidTestDoubleOutput(); TestDoubleOutput(); int intmain() main() {{ //TestPoints(); //TestPoints(); TestDoubleOutput(); TestDoubleOutput(); return return0; 0; }} void voidTestPoints() TestPoints() {{ //... //... }} void voidTestDoubleOutput() TestDoubleOutput() {{ //... //... }}

Curso de Programação com C++ © Pedro Guerreiro 2003

38


Funções matemáticas de biblioteca A biblioteca do C++ traz as funções matemáticas do costume: abs: valor absoluto.

double doubleabs(double abs(doublex); x); double doublefloor(double floor(doublex); x); double ceil(double double ceil(doublex); x);

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 .

double doublecos(double cos(doublex); x); double sin(double double sin(doublex); x); double doubletan(double tan(doublex); x); double acos(double double acos(doublex); x); double doubleasin(double asin(doublex); x); double doubleatan(double atan(doublex); x); double atan2(double double atan2(doubley, y,double doublex); x);

Funções trigonométricas coseno, seno, tangente, arcocoseno, arcosseno, arcotangente. A função arcotangente tem duas variantes: atan tem resultado no intervalo – p /2, p /2; atan2(y, x) calcula o arcotangente de y/x no intervalo –p , p , 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.

double doublesinh(double sinh(doublex); x); double cosh(double double cosh(doublex); x); double doubletanh(double tanh(doublex); x);

Funções hiperbólicas. Para usar isto é preciso fazer #include <cmath>.

double doublesqrt(double sqrt(doublex); x); double exp(double double exp(doublex); x); Raiz quadrada, exponencial, logaritmo natural, logaritmo double doublelog(double log(doublex); x); decimal, potência (x elevado a y). double doublelog10(double log10(doublex); x); double doublepow(double pow(doublex, x,double doubley); y); 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 39

Exercícios 2

1. 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

40


Evitando inclusões repetidas Se um ficheiro A incluir os ficheiros B e C e se o ficheiro B incluir 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 #ifndef_H_Point _H_Point #define _H_Point #define _H_Point class classPoint Point{{ private: private: double doublex; x; double y; double y;

////... ... }; };

#endif #endif 2003-07-19

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. Curso de Programação com C++ © Pedro Guerreiro 2003

41

Os ficheiros de inclusão não incluem Outra regra: só há directivas #include nos ficheiros .cpp. #ifndef #ifndef_H_Point _H_Point #define #define_H_Point _H_Point

#include #include<iostream> <iostream> #include #include<cmath> <cmath>

#include #include<iostream> <iostream> #include #include<iomanip> <iomanip>

class classPoint Point{{ private: private: double doublex; x; double y; double y; ////... ... }; };

#include #include"Point.h" "Point.h"

#include #include"Point.h" "Point.h"

Point::Point(): Point::Point(): x(0), x(0), Só incluímos o que faz y(0) y(0) falta. Por exemplo, no {{ Point.cpp faz falta o }} <cmath>, mas no

void voidTestPoints(); TestPoints(); void TestDoubleOutput(); void TestDoubleOutput();

#endif #endif

Point.h

M_Point.cpp não faz.

////... ...

Point.cpp

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

int intmain() main() {{ //TestPoints(); //TestPoints(); TestDoubleOutput(); TestDoubleOutput(); return return0; 0; }} //... //...

M_Point.cpp

Curso de Programação com C++ © Pedro Guerreiro 2003

42


std, o espaço de nomes da biblioteca Quando 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á Programadores preguiçosos usam a definida no espaço de nomes std. directiva using namespace std; para

void voidTestDoubleOutput() TestDoubleOutput() {{ double doublex; x; double doubley; y; std::cout std::cout<< <<"Coordinates "Coordinates(x (xand andy), y),please: please:";"; std::cin std::cin>> >>xx>> >>y; y; Point Pointp(x, p(x,y); y);

não ter de escrever std:: muitas vezes: //... //... using namespace std; using namespace std; //... //... void TestDoubleOutput() void TestDoubleOutput() { { double x; double x; double y; double y; cout << "Coordinates (x and y), please: "; cout << "Coordinates (x and y), please: "; cin >> x >> y; cin >> x >> y; Point p(x, y); Point p(x, y);

double doubledd==p.DistanceTo(Point()); p.DistanceTo(Point()); std::cout std::cout<< <<"General "Generalformat: format:""<< <<std::endl; std::endl; std::cout << "Distance to origin: std::cout << "Distance to origin:""<< <<dd<< <<std::endl; std::endl;

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

std::cout std::cout<< <<"Fixed "Fixedformat, format,two twodecimal decimaldigits: digits:""<< <<std::endl; std::endl; std::cout << std::fixed << std::setprecision(2); std::cout << std::fixed << std::setprecision(2); std::cout std::cout<< <<"Distance "Distanceto toorigin: origin:""<< <<dd<< <<std::endl; std::endl; //... //... 2003-07-19

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

Curso de Programação com C++ © Pedro Guerreiro 2003

43

O espaço dos nossos nomes Devemos também arranjar um espaço de nomes para as nossas classes. As minhas estão no espaço mas. A classe Point é declarada dentro do espaço de nomes mas: #ifndef _H_Point #ifndef _H_Point #define #define_H_Point _H_Point

#include <iostream> #include #include<cmath> <cmath>

namespace namespacemas mas{{

#include #include"Point.h" "Point.h"

class classPoint Point{{ private: private: double doublex; x; double doubley; y; //... //... }; };

namespace namespacemas mas{{

}} #endif #endif 2003-07-19

As definições das funções também ficam dentro do espaço de nomes mas: #include <iostream>

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

Point::Point(): Point::Point(): x(0), x(0), y(0) y(0) {{ }} //... //... }}

Curso de Programação com C++ © Pedro Guerreiro 2003

44


Fora do espaço qualifica-se Fora do espaço de nomes, tipicamente nas funções de teste, qualificamos os nomes do nosso espaço de nomes: void voidTestMidpoint() TestMidpoint() {{ int intx; x; int y; int y; std::cout std::cout<< <<"Coordenadas "Coordenadasdo doprimeiro primeiroponto: ponto:";"; Assim, até podia haver std::cin >> x >> y; std::cin >> x >> y; várias classes Point, mas::Point mas::Pointp1(x, p1(x,y); y); cada uma no seu espaço std::cout std::cout<< <<"Coordenadas "Coordenadasdo dosegundo segundoponto: ponto:";"; de nomes. std::cin std::cin>> >>xx>> >>y; y; mas::Point p2(x, y); mas::Point p2(x, y); mas::Point mas::Pointpm((p1.X() pm((p1.X()++p2.X()) p2.X())//2, 2,(p1.Y() (p1.Y()++p2.Y()) p2.Y())//2); 2); std::cout << "Ponto médio: "; std::cout << "Ponto médio: "; pm.WriteLine(); pm.WriteLine(); double doubled1 d1==p1.DistanceTo(pm); p1.DistanceTo(pm); double d2 double d2==p2.DistanceTo(pm); p2.DistanceTo(pm); std::cout << std::cout <<"Distâncias: "Distâncias:""<< <<d1 d1<< <<""""<< <<d2 d2<< <<std::endl; std::endl; }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

45

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

void voidTestNumericLimits() TestNumericLimits() {{ int intintMax intMax==std::numeric_limits<int>::max(); std::numeric_limits<int>::max(); int intMin int intMin==std::numeric_limits<int>::min(); std::numeric_limits<int>::min(); double doubledoubleInfinity doubleInfinity==std::numeric_limits<double>::infinity(); std::numeric_limits<double>::infinity(); double doubleMax double doubleMax==std::numeric_limits<double>::max(); std::numeric_limits<double>::max(); double doubledoubleMin doubleMin==std::numeric_limits<double>::min(); std::numeric_limits<double>::min(); double epsilon double epsilon==std::numeric_limits<double>::epsilon(); std::numeric_limits<double>::epsilon(); std::cout std::cout<< <<"Maior "Maiornúmero númerointeiro: inteiro:""<< <<intMax intMax<< <<std::endl; std::endl; std::cout << "Menor número inteiro: " << intMin << std::cout << "Menor número inteiro: " << intMin <<std::endl; std::endl; std::cout std::cout<< <<"Mais "Maisinfinito infinito(double): (double):""<< <<doubleInfinity doubleInfinity<< <<std::endl; std::endl; std::cout << "Maior número positivo double: " << doubleMax std::cout << "Maior número positivo double: " << doubleMax<< <<std::endl; std::endl; std::cout << "Menor número positivo double: " << doubleMin << std::cout << "Menor número positivo double: " << doubleMin <<std::endl; std::endl; std::cout std::cout<< <<"epsilon: "epsilon:""<< <<epsilon epsilon<< <<std::endl; std::endl;

}}

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

Curso de Programação com C++ © Pedro Guerreiro 2003

46


Precisão numérica Os 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 voidTestEpsilon() TestEpsilon() {{ double doubleepsilon epsilon==std::numeric_limits<double>::epsilon(); std::numeric_limits<double>::epsilon(); std::cout std::cout<< <<std::setprecision(17); std::setprecision(17); std::cout std::cout<< <<epsilon epsilon<< <<std::endl; std::endl; double epsilon1 = epsilon double epsilon1 = epsilon++1.0; 1.0; std::cout std::cout<< <<epsilon1 epsilon1--1.0 1.0<< <<std::endl; std::endl; double doubleepsilon2 epsilon2==epsilon epsilon//2.0 2.0++1.0; 1.0; std::cout std::cout<< <<epsilon2 epsilon2--1.0 1.0<< <<std::endl; std::endl; std::cout std::cout<< <<std::setprecision(30); std::setprecision(30); std::cout << std::cout <<epsilon epsilon<< <<std::endl; 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 double nos computadores usa um número finito de dígitos.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

47

Classe genérica numeric_limits<T> 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:

void voidTestNumericMinMax() TestNumericMinMax() {{ using Declarações using. Usar com moderação. usingstd::cout; std::cout; using usingstd::endl; std::endl; cout cout<< <<"char "charMin: Min:""<< <<static_cast<int>(std::numeric_limits<char>::min()) static_cast<int>(std::numeric_limits<char>::min())<< <<endl; endl; cout << "char Max: " << static_cast<int>(std::numeric_limits<char>::max()) cout << "char Max: " << static_cast<int>(std::numeric_limits<char>::max())<< <<endl; endl; cout cout<< <<"short "shortMin: Min:""<< <<std::numeric_limits<short>::min() std::numeric_limits<short>::min()<< <<endl; endl; Tipos inteiros do C++: cout cout<< <<"short "shortMax: Max:""<< <<std::numeric_limits<short>::max() std::numeric_limits<short>::max()<< <<endl; endl; cout << "int Min: " << std::numeric_limits<int>::min() << endl; char, short, int, long, cout << "int Min: " << std::numeric_limits<int>::min() << endl; cout unsigned, _int64. cout<< <<"int "intMax: Max:""<< <<std::numeric_limits<int>::max() std::numeric_limits<int>::max()<< <<endl; endl; cout cout<< <<"long "longMin: Min:""<< <<std::numeric_limits<long>::min() std::numeric_limits<long>::min()<< <<endl; endl; cout cout<< <<"long "longMax: Max:""<< <<std::numeric_limits<long>::max() std::numeric_limits<long>::max()<< <<endl; endl; cout << "unsigned Min: " << std::numeric_limits<unsigned>::min() cout << "unsigned Min: " << std::numeric_limits<unsigned>::min()<< <<endl; endl; cout cout<< <<"unsigned "unsignedMax: Max:""<< <<std::numeric_limits<unsigned>::max() std::numeric_limits<unsigned>::max()<< <<endl; endl; O tipo _int64 é cout cout<< <<"_int64 "_int64Min: Min:""<< <<std::numeric_limits<_int64>::min() std::numeric_limits<_int64>::min()<< <<endl; endl; específico da cout cout<< <<"_int64 "_int64Max: Max:""<< <<std::numeric_limits<_int64>::max() std::numeric_limits<_int64>::max()<< <<endl; endl; Microsoft. }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

48


O infinito O tipo double tem o infinito. Este valor comporta-se mesmo como um verdadeiro infinito: void voidTestInfinity() TestInfinity() {{ double doublemax max==std::numeric_limits<double>::max(); std::numeric_limits<double>::max(); double infinity double infinity==std::numeric_limits<double>::infinity(); std::numeric_limits<double>::infinity(); std::cout << "Maior std::cout << "Maiordouble: double:""<< <<max max<< <<std::endl; std::endl; std::cout << "Infinito: " << infinity << std::endl; std::cout << "Infinito: " << infinity << std::endl; std::cout std::cout<< <<"1.0 "1.0++infinity infinity==""<< <<1.0 1.0++infinity infinity<< <<std::endl; std::endl; std::cout << "1.0 infinity = " << 1.0 infinity << std::cout << "1.0 - infinity = " << 1.0 - infinity <<std::endl; std::endl; std::cout std::cout<< <<"1.0 "1.0**infinity infinity==""<< <<1.0 1.0**infinity infinity<< <<std::endl; std::endl; 0 * ∞ é indeterminado. std::cout << "1.0 / infinity = " << 1.0 / infinity << std::cout << "1.0 / infinity = " << 1.0 / infinity <<std::endl; std::endl; std::cout std::cout<< <<"0.0 "0.0**infinity infinity==""<< <<0.0 0.0**infinity infinity<< <<std::endl; std::endl; “warning: potential divide by 0”. std::cout << "infinity / 0.0 = " << infinity/ 0.0 << std::cout << "infinity / 0.0 = " << infinity/ 0.0 <<std::endl; std::endl;

O manipulador std::boolalpha faz com que os valores

std::cout booleanos sejam escritos false e true, em vez de 0 e 1. std::cout<< <<std::boolalpha; std::boolalpha; std::cout << "max < infinity = " << (max std::cout << "max < infinity = " << (max<<infinity) infinity)<< <<std::endl; std::endl; std::cout << "infinity max = " << infinity max << std::endl; Nota: o tipo int não std::cout << "infinity - max = " << infinity - max << std::endl;

tem infinito /.

}}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

49

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.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

50


Vectores O C++ fornece directamente suporte para vectores, através dos arrays: //... //... int inta[32]; a[32]; char charm[80]; m[80];

double doublex[100]; x[100]; double y[100]; double y[100];

mas::Point mas::Pointp[8]; p[8]; //... //...

Mas é melhor usar a classe std::vector<T> e, para as cadeias de caracteres, a classe std::string: Nalguns casos, em vez de dois vectores “paralelos”, é melhor usar um vector de pares:

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

std::string std::stringm(79, m(79,' ''); '); std::vector<double> std::vector<double>x(100); x(100);

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

//... //... 2003-07-19

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

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

Curso de Programação com C++ © Pedro Guerreiro 2003

51

É melhor usar os vectores da STL Os arrays do C++ são básicos: apenas têm a operação de indexação: //... //... for for(int (inti i==0; 0;i i<<32; 32;i++) i++) a[i] a[i]==i i**i;i; int intdd==a[31] a[31]--a[30]; a[30];

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

double doubledist dist==p[0].DistanceTo(p[5]); 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). 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

52


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: Inicializamos a semente do gerador de números aleatórios.

void voidTest_3() Test_3() {{

::srand(static_cast<unsigned>(::time(0))); ::srand(static_cast<unsigned>(::time(0))); Declaramos o vector. Não indicamos a capacidade. std::vector<int> std::vector<int>a; a; a.reserve(16); Agora reservamos a capacidade pretendida. a.reserve(16); for (int i = 0; i < static_cast<int>(a.capacity()); for (int i = 0; i < static_cast<int>(a.capacity());i++) i++) a.push_back(::rand() Preenchemos o vector com um ciclo for. a.push_back(::rand()% %100); 100); for (int i = 0; i < static_cast<int>(a.capacity()); for (int i = 0; i < static_cast<int>(a.capacity());i++) i++) std::cout std::cout<< <<""""<< <<a[i]; a[i]; std::cout << std::endl; std::cout << std::endl;

Escrevemos o vector.

Ordenamos usando a função genérica std::sort. std::sort(a.begin(), std::sort(a.begin(),a.end()); a.end()); for for(int (inti i==0; 0;i i<<static_cast<int>(a.capacity()); static_cast<int>(a.capacity());i++) i++) std::cout std::cout<< <<""""<< <<a[i]; a[i]; std::cout << std::endl; std::cout << std::endl;

Escrevemos de novo.

}}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

53

A instrução for Sintaxe (caso tradicional): for (expressão1; expressão2; expressão3) instrução

Qualquer destas expressões pode faltar.

Semântica: 1. Avalia a expressão1.

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

2. Avalia a expressão2; se der false, termina; 3. Executa a instrução; 4. Avalia a expressão3; 5. Volta ao passo 2. 2003-07-19

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.

Curso de Programação com C++ © Pedro Guerreiro 2003

54


Variável de controlo num ciclo for Frequentemente, 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 for(int (inti i==0; 0;i i<<static_cast<int>(a.capacity()); static_cast<int>(a.capacity());i++) i++) a.push_back(::rand() a.push_back(::rand()% %100); 100); for for(int (inti i==0; 0;i i<<static_cast<int>(a.capacity()); static_cast<int>(a.capacity());i++) i++) std::cout std::cout<< <<""""<< <<a[i]; 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.) 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

55

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

preferimos

ou

xx+= +=1; 1;

x++;

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

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.) 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

56


As duas funções de teste ilustram a diferença entre o operador sufixo (que é o que usamos quase sempre) e o operador prefixo: void voidTest_plus_plus_suffix() Test_plus_plus_suffix() {{ using usingstd::cout; std::cout; using std::cin; using std::cin; using usingstd::endl; std::endl; int intx; x; cout cout<< <<"Um "Umnúmero: número:";"; cin cin>> >>x; x; cout cout<< <<xx<< <<endl; endl; cout << x++ cout << x++<< <<endl; endl; cout << x << endl; cout << x << endl; }}

2003-07-19

void voidTest_plus_plus_prefix() Test_plus_plus_prefix() {{ using usingstd::cout; std::cout; using std::cin; using std::cin; using usingstd::endl; std::endl; int intx; x; cout cout<< <<"Um "Umnúmero: número:";"; cin cin>> >>x; x; cout cout<< <<xx<< <<endl; endl; cout << ++x cout << ++x<< <<endl; endl; cout << x << endl; cout << x << endl; }}

Curso de Programação com C++ © Pedro Guerreiro 2003

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

x++ e ++x

57

Números aleatórios A 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: 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 for for(int (inti i==0; 0;i i<<static_cast<int>(a.capacity()); static_cast<int>(a.capacity());i++) i++) fazer #include <ctime>. a.push_back(::rand() % 100); ::srand(static_cast<unsigned>(::time(0))); ::srand(static_cast<unsigned>(::time(0))); //... //... a.push_back(::rand() % 100);

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

58


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

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

template template<class <classT> T> void Write(const std::vector<T>& void Write(const std::vector<T>&v, v, const conststd::string& std::string&separator separator==""",",std::ostream& std::ostream&output output==std::cout) std::cout) {{ for (i != 0 ? separator : ""): expressão for(unsigned (unsignedi i==0; 0;i i<<v.size() v.size();;i++) i++) output << (i != 0 ? separator : "") << v[i]; condicional: o separador não é escrito output << (i != 0 ? separator : "") << v[i]; }}

antes do primeiro elemento.

template template<class <classT> T> void WriteLine(const void WriteLine(conststd::vector<T>& std::vector<T>&v, v, const std::string& const std::string&separator separator==""",",std::ostream& std::ostream&output output==std::cout) std::cout) {{ Write(v, Write(v,separator, separator,output); output); Estas funções pertencem ao espaço de nomes mas e estão output << output <<std::endl; std::endl; no ficheiro Utilities_vector.h. Frequentemente, as funções }}

genéricas são logo programadas no ficheiro .h.

}} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

59

Escrevendo vectores genericamente As 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 voidTest_4() Test_4() {{ ::srand(static_cast<unsigned>(::time(0))); ::srand(static_cast<unsigned>(::time(0))); std::vector<int> std::vector<int>a; a; a.reserve(16); a.reserve(16); for for(int (inti i==0; 0;i i<<static_cast<int>(a.capacity()); static_cast<int>(a.capacity());i++) i++) a.push_back(::rand() a.push_back(::rand()% %100); 100); mas::WriteLine(a); mas::WriteLine(a); std::sort(a.begin(), std::sort(a.begin(),a.end()); a.end()); void voidTest_5() Test_5() mas::WriteLine(a); mas::WriteLine(a); {{ }} std::vector<mas::Point> std::vector<mas::Point>v; v; v.reserve(6); v.reserve(6); v.push_back(mas::Point(2.0, v.push_back(mas::Point(2.0,-1.0)); -1.0)); v.push_back(mas::Point()); v.push_back(mas::Point()); v.push_back(mas::Point(v[0])); v.push_back(mas::Point(v[0])); v.push_back(mas::Point(3.0, v.push_back(mas::Point(3.0,4.0)); 4.0)); mas::WriteLine(v, mas::WriteLine(v,"\n"); "\n"); }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003

E outro, escrevendo pontos:

Batota: a classe Point não tem o operador <<. /

60


O operador << na classe Point Para poder escrever vectores de pontos com as funções utilitárias, é preciso que a classe Point disponha de um operador amigo <<:

Os operadores amigos não pertencem à classe (não

class classPoint Point{{ têm objecto da classe) mas aceitam operando do private: private: tipo da classe. Por isso, arrumamo-los “dentro” da double doublex; x; classe. Normalmente, exprimem-se em termos de double doubley; y; public: alguma das funções da classe. public: Point(); Point(); Point(double Point(doublex, x,double doubley); y); //... //... friend friendstd::ostream& std::ostream&operator operator<< <<(std::ostream& (std::ostream&output, output,const constPoint& Point&p); p); }; };

A definição vem junto das outras, no ficheiro .cpp: std::ostream& std::ostream&operator operator<< <<(std::ostream& (std::ostream&output, output,const constPoint& Point&p) p) {{ p.Write(output); p.Write(output); return returnoutput; output; }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

61

Expressão condicional Vimos há pouco uma expressão condicional: for for(unsigned (unsignedi i==0; 0;i i<<v.size() v.size();;i++) i++) output << (i != 0 ? separator : output << (i != 0 ? separator : "") "") << <<v[i]; v[i];

Sintaxe: expressão1 ? expressão2 : expressão3 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

62


Expressão condicional, exemplos As funções Min e Max programadas genericamente: template template<class <classT> T> TTMin(const T& x, Min(const T& x,const constT&y) T&y)

template template<class <classT> T> TTMax(const T& x, Max(const T& x,const constT&y) T&y)

}}

}}

{{ return returnxx<= <=yy??xx::y; y;

{{ return returnxx<= <=yy??yy::x; x;

Programar isto com a ajuda de instruções if seria mau estilo: template template<class <classT> T> TTMin(const T& x, Min(const T& x,const constT&y) T&y) {{ ifif(x (x<= <=y) y) return returnx; x; else else

}}

{{ ifif(x (x<= <=y) y) return returny; y; else else

return returny; y;

2003-07-19

template template<class <classT> T> TTMax(const T& x, Max(const T& x,const constT&y) T&y)

}}

return returnx; x;

Curso de Programação com C++ © Pedro Guerreiro 2003

63

A instrução if-else Sintaxe: if (expressão) instrução1 else instrução2

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

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

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. A parte else pode faltar, caso em que a instrução if não tem efeito se a expressão der false.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

64


Operações sobre vectores (1)

Construtores, capacidade, tamanho, operador []: vector(); vector(); explicit explicitvector(size_type vector(size_typen); n); vector(size_type vector(size_typen, n,const constT& T&x); x); vector(const vector(constvector<T>& vector<T>&other); other);

size_type size_typecapacity() capacity()const; const; size_type size_typesize() size()const; 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 que const constT& T&operator[](size_type operator[](size_typexx))const; const; 0 <= x && x <= size(). T& T&operator[](size_type operator[](size_typex); x);

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

65

Operações sobre vectores (2) Eis as mais simples: void voidclear(); clear(); bool boolempty() empty()const; const;

A função clear remove todos os elemento do vector. A capacidade mantém-se e o tamanho fica zero. A função empty dá true se o vector tiver zero elementos. A função pop_back remove o último elemento do vector.

void voidpop_back(); pop_back(); void voidpush_back(const push_back(constT& T&x); x);

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

void voidresize(size_type resize(size_typen); n);

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.

void voidresize(size_type resize(size_typen, n,TTx); x);

void voidreserve(size_type reserve(size_typen); n);

2003-07-19

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

Curso de Programação com C++ © Pedro Guerreiro 2003

66


Algoritmos Certas 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 intnn==std::count(v.begin(), std::count(v.begin(),v.end(), v.end(),x); x); ifif(std::find(v.begin(), (std::find(v.begin(),v.end(), v.end(),x)!= x)!=v.end()) v.end()) cout cout<< <<"Existe. "Existe.""<< <<endl; endl;

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

Quanto elementos iguais a x?

Existe algum elemento igual a x?

else else cout cout<< <<"Não "Nãoexiste. existe.""<< <<endl; endl; std::sort(v.begin(), std::sort(v.begin(),v.end()); v.end());

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

67

Exercício: problema do almoço N 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? Exemplo Dados: um ficheiro onde na primeira linha vem N, 55 seguida de uma linha com o número de ruas e o 20 2014 14 número de avenidas da cidade, seguida de N linhas 10 1044 com dois números: o primeiro é a rua e o segundo a 2255 avenida onde cada um dos amigos mora (sim, todos 2277 eles moram em cruzamentos também ..) Trata-se 7733 de uma cidade matemática em que a primeira rua é a 10 rua zero e o mesmo para as avenidas. 1011 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

68


Polinómios Queremos 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 namespacemas mas{{ class classPolynomial Polynomial{{ private: private:

int intdegree; degree; std::vector<double> std::vector<double>a; a;

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

public: 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 coeficiente de índice de grau degree nunca é zero, excepto se se tratar do polinómio nulo.

}; };

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

69

Construtores Um 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 classPolynomial Polynomial{{ ////... ... public: public:

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

explicit explicitPolynomial(double Polynomial(doublea0 a0==0.0); 0.0); Polynomial(const std::vector<double>& Polynomial(const std::vector<double>&coefs); coefs);

////... ... }; };

Polynomial::Polynomial(double Polynomial::Polynomial(doublea0): a0): degree(0), degree(0), Polynomial::Polynomial(const Polynomial::Polynomial(conststd::vector<double>& std::vector<double>&coefs): coefs): a(1) a(1) degree(static_cast<int>(coefs.size()) 1), degree(static_cast<int>(coefs.size()) - 1), {{ a(static_cast<int>(coefs.size())) a(static_cast<int>(coefs.size())) a[0] a[0]==a0; a0; {{ }} for for(int (inti i==0; 0;i i<= <=degree; degree;i++) i++) a[i] a[i]==coefs[degree coefs[degree--i]; i];

2003-07-19

}} Curso de Programação com C++ © Pedro Guerreiro 2003

70


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

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

Avaliamos com o método de Horner:

Assim trabalharíamos mais do que é preciso:

double doublePolynomial::Value(double Polynomial::Value(doublex) x)const const {{ double doubleresult result==0.0; 0.0; double m double result = 0.0; double m==1.0; 1.0; double result = 0.0; for for(int (inti i==0; 0;i i<= <=degree; degree;i++) i++) for {{ for(int (inti i==degree; degree;i i>= >=0; 0;i--) i--) result result result==result result**xx++a[i]; a[i]; result+= +=a[i] a[i]**m; m; Este esquema usa o m *= x; return result; m *= x; dobro das return result; }} }} multiplicações. return returnresult; result; }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 double doublePolynomial::Value(double Polynomial::Value(doublex) x)const const {{

71

Operador ()

O operador () é equivalente à função Value: double doublePolynomial::operator Polynomial::operator()(double ()(doublex) x)const const {{ return returnValue(x); Value(x); }}

Eis uma função de teste que mostra como se usa: void voidTestValue() TestValue() Observe esta inicialização. {{ double d[4] = {1.0, -2, 0.0, 5.0}; Ilustra um outro construtor da double d[4] = {1.0, -2, 0.0, 5.0}; std::vector<double> classe std::vector<T>. std::vector<double>coefs(d, coefs(d,d+4); d+4); mas::Polynomial mas::Polynomialp(coefs); p(coefs); for for(;;) (;;) Escrever p(x), usando o {{ double operador () é apenas uma doublex; x; std::cout << "x = "; maneira mais conveniente std::cout << "x = "; std::cin >> x; std::cin >> x; de escrever p.Value(x). double doubley1 y1==p.Value(x); p.Value(x); double doubley2 y2==p(x); p(x); std::cout std::cout<< <<y1 y1<< <<""""<< <<y2 y2<< <<std::endl; std::endl; }} }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003

72


Funções de gestão de memória A 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 virtualvoid voidResize(int Resize(intsize); size); ////size sizeisisnumber numberof ofcoefficients. coefficients. virtual void Grow(int size); // size is number of virtual void Grow(int size); // size is number ofcoefficients. coefficients. virtual virtualvoid voidSwapOut(Polynomial& SwapOut(Polynomial&other); other);

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

Resize e Grow são simples: SwapOut usa a função swap da classe std::vector<T>, que void Polynomial::Resize(int size) void Polynomial::Resize(int size) troca as representações internas {{ de vectores, e à função genérica a.resize(size); a.resize(size); std::swap(..., ...), que troca os }} valores dos argumentos: void voidPolynomial::Grow(int Polynomial::Grow(intsize) size) {{

ifif(size (size>>static_cast<int>(a.size())) static_cast<int>(a.size())) a.resize(size); a.resize(size);

}} 2003-07-19

void voidPolynomial::SwapOut(Polynomial& Polynomial::SwapOut(Polynomial&other) other) {{ a.swap(other.a); a.swap(other.a);

std::swap(degree, std::swap(degree,other.degree); other.degree); }} Curso de Programação com C++ © Pedro Guerreiro 2003

73

Mais funções simples A 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 virtualint intDegree() Degree()const; const; virtual bool Null() const; virtual bool Null() const; virtual virtualvoid voidNullify(); Nullify();

Implementações: int intPolynomial::Degree() Polynomial::Degree()const const {{ return returndegree; degree; }}

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

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

}} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

74


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

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: bool boolPolynomial::operator Polynomial::operator== ==(const (constPolynomial& Polynomial&other) other)const const {{ ifif(degree (degree!= !=other.degree) other.degree) return false; return false;

for for(int (inti i==0; 0;i i<= <=degree; degree;i++) i++) ifif(a[i] != other.a[i]) (a[i] != other.a[i]) return returnfalse; false; return true; return true;

}}

2003-07-19

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).

Curso de Programação com C++ © Pedro Guerreiro 2003

75

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

virtual virtualvoid voidAdd(const Add(constPolynomial& Polynomial&other); other); virtual Polynomial& operator += (const virtual Polynomial& operator += (constPolynomial& Polynomial&other); other); virtual virtualPolynomial Polynomialoperator operator++(const (constPolynomial& Polynomial&other); other);

Quem trabalha mesmo é a função Add: void voidPolynomial::Add(const Polynomial::Add(constPolynomial& Polynomial&other) other) {{ Grow(other.degree Grow(other.degree++1); 1); for (int i = 0; i <= other.degree; for (int i = 0; i <= other.degree;i++) i++) a[i] a[i]+= +=other.a[i]; other.a[i]; RecomputeDegree(); RecomputeDegree(); }} 2003-07-19

A função RecomputeDegree recalcula 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 voidPolynomial::RecomputeDegree() Polynomial::RecomputeDegree() {{ int intxx==static_cast<int>(a.size()) static_cast<int>(a.size())--1; 1; while (x while (x>>00&& &&a[x] a[x]== ==0.0) 0.0) x--; x--; degree degree==x; x; }}

Curso de Programação com C++ © Pedro Guerreiro 2003

76


Os operadores += e +

O operador += soma e retorna uma referência para o objecto. Polynomial& Polynomial&Polynomial::operator Polynomial::operator+=(const +=(constPolynomial& Polynomial&other) other) {{ Add(other); Add(other); return return*this; *this;

}}

O operador + retorna a soma por valor, isto é, retorna uma cópia da variável local onde foi calculada a soma: Polynomial PolynomialPolynomial::operator Polynomial::operator++(const (constPolynomial& Polynomial&other) other) {{ Polynomial Polynomialresult(*this); result(*this); return returnresult result+= +=other; other;

}}

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

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

77

Sobrecarregando Add, += e + Às vezes, só queremos somar um número: virtual virtualvoid voidAdd(double Add(doubley); y); virtual Polynomial& operator virtual Polynomial& operator+= +=(double (doubley); y); virtual virtualPolynomial Polynomialoperator operator++(double (doubley); y);

Basta somar ao termo de grau zero: void voidPolynomial::Add(double Polynomial::Add(doubley) y) {{ a[0] a[0]+= +=y; y;

}}

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

}}

Polynomial PolynomialPolynomial::operator Polynomial::operator++(double (doubley) y) {{ Polynomial Polynomialresult(*this); result(*this); return returnresult result+= +=y; y;

}} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

78


Subtraindo polinómios Subtrair é análogo, com a função Subtract e os operadores -= e -: virtual virtualvoid voidSubtract(const Subtract(constPolynomial& Polynomial&other); other); virtual virtualPolynomial& Polynomial&operator operator-= -=(const (constPolynomial& Polynomial&other); other); virtual virtualPolynomial Polynomialoperator operator--(const (constPolynomial& Polynomial&other); other); virtual virtualvoid voidSubtract(double Subtract(doubley); y); virtual Polynomial& operator -= virtual Polynomial& operator -=(double (doubley); y); virtual virtualPolynomial Polynomialoperator operator--(double (doubley); y);

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

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

79

Multiplicando polinómios Temos 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 virtualvoid voidMultiply(const Multiply(constPolynomial& Polynomial&other); other); virtual Polynomial& operator *= (const virtual Polynomial& operator *= (constPolynomial& Polynomial&other); other);

Multiplicando por outro polinómio.

virtual virtualPolynomial Polynomialoperator operator**(const (constPolynomial& Polynomial&other); other); virtual Multiplicando por virtualvoid voidMultiply(const Multiply(conststd::pair<double, std::pair<double,double>& double>&m); m); virtual virtualPolynomial& Polynomial&operator operator*= *=(const (conststd::pair<double, std::pair<double,double>& double>&m); m); um binómio. virtual virtualPolynomial Polynomialoperator operator**(const (conststd::pair<double, std::pair<double,double>& double>&m); m); virtual virtualvoid voidMultiply(double Multiply(doubley); y); virtual Polynomial& operator virtual Polynomial& operator*= *=(double (doubley); y); virtual virtualPolynomial Polynomialoperator operator**(double (doubley); y);

Multiplicando por uma constante.

Vejamos o segundo grupo, que é mais interessante. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

80


Multiplicando por um binómio Frequentemente 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 voidPolynomial::Multiply(const Polynomial::Multiply(conststd::pair<double, std::pair<double,double>& double>&m) m) {{ degree++; degree++; Grow(degree Grow(degree++1); 1); Parece complicado a[degree] a[degree]==a[degree a[degree--1] 1]**m.first; m.first; mas até nem é. for for(int (inti i==degree degree--1; 1;i i>>0; 0;i--) i--) a[i] a[i]==a[i-1] a[i-1]**m.first m.first++a[i] a[i]**m.second; m.second; a[0] *= m.second; a[0] *= m.second; }} Polynomial& Polynomial&Polynomial::operator Polynomial::operator*= *=(const (conststd::pair<double, std::pair<double,double>& double>&m) m) {{ Multiply(m); Multiply(m); return return*this; *this; }} Polynomial PolynomialPolynomial::operator Polynomial::operator**(const (conststd::pair<double, std::pair<double,double>& double>&m) m) {{ Polynomial Polynomialresult(*this); result(*this); return returnresult result*= *=m; m; }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

81

Dividindo por um binómio Apenas incluímos as operações para dividir um polinómio por um binómio: O resto é passado de volta num arguvirtual virtualvoid voidDivide(const Divide(conststd::pair<double, std::pair<double,double>& double>&m, m,double& double&remainder); remainder); mento de saída, virtual virtualPolynomial& Polynomial&operator operator/= /=(const (conststd::pair<double, std::pair<double,double>& double>&m); m); virtual virtualPolynomial Polynomialoperator operator//(const (conststd::pair<double, std::pair<double,double>& double>&m); m);

por referência.

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: Polynomial& Polynomial&Polynomial::operator Polynomial::operator/= /=(const (conststd::pair<double, std::pair<double,double>& double>&m) m) {{ double doubletemp; temp; Polynomial Polynomial::operator / (const std::pair<double, double>& m) Divide(m, Divide(m,temp); temp); Polynomial Polynomial::operator / (const std::pair<double, double>& m) {{ return return*this; *this; Polynomial }} Polynomialresult(*this); result(*this); return result return result/= /=m; m; }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

82


Regra de Ruffini Eis a regra de Ruffini, ligeiramente modificada para dividir por ax+b (e não por x-k): void voidPolynomial::Divide(const Polynomial::Divide(conststd::pair<double, std::pair<double,double>& double>&m, m,double& double&remainder) remainder) {{ ////Ruffini's Ruffini'sRule Rule Polynomial temp; Polynomial temp; temp.degree temp.degree==degree degree--1; 1; temp.Resize(temp.degree temp.Resize(temp.degree++1); 1); temp.a[temp.degree] temp.a[temp.degree]==a[degree]; a[degree]; double k = -m.second / m.first; double k = -m.second / m.first; for for(int (inti i==temp.degree temp.degree;;i i>= >=1; 1;i--) i--) temp.a[i 1] = temp.a[i] * k + a[i]; temp.a[i - 1] = temp.a[i] * k + a[i]; remainder remainder==temp.a[0] temp.a[0]**kk++a[0]; a[0]; temp.Multiply(1 / m.first); temp.Multiply(1 / m.first); SwapOut(temp); SwapOut(temp); }} 2003-07-19

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.

Curso de Programação com C++ © Pedro Guerreiro 2003

83

Derivando e primitivando A derivada de um polinómio é um polinómio. A primitiva também: virtual virtualvoid voidDifferentiate(); Differentiate(); virtual void Integrate(double virtual void Integrate(doublea0 a0==0.0); 0.0); void voidPolynomial::Differentiate() Polynomial::Differentiate() {{ for for(int (inti i==1; 1;i i<= <=degree; degree;i++) i++) a[i-1] a[i-1]==i i**a[i]; a[i]; ifif(degree (degree>>0) 0) degree--; degree--; else else a[0] a[0]==0.0; 0.0; }}

2003-07-19

void voidPolynomial::Integrate(double Polynomial::Integrate(doublea0) a0) {{ ifif(Null()) (Null()) a[0] a[0]==a0; a0; else else {{ Grow(degree Grow(degree++1); 1); for (int i = degree; for (int i = degree;i i>= >=0; 0;i--) i--) a[i+1] = a[i] / (i+1); a[i+1] = a[i] / (i+1); a[0] a[0]==a0; a0; degree++; degree++; }} }}

Curso de Programação com C++ © Pedro Guerreiro 2003

84


Derivada num ponto e integral Também sabemos calcular a derivada num ponto e o integral num intervalo: virtual virtualdouble doubleDerivative(double Derivative(doublex) x)const; const; virtual double Integral(double x0, double virtual double Integral(double x0, doublex1) x1)const; const;

Usam as duas a mesma técnica: double doublePolynomial::Derivative(double Polynomial::Derivative(doublex) x)const const {{ Polynomial Polynomialtemp(*this); temp(*this); temp.Differentiate(); temp.Differentiate(); return returntemp(x); temp(x); }} double doublePolynomial::Integral(double Polynomial::Integral(doublex0, x0,double doublex1) x1)const const {{ Polynomial Polynomialtemp(*this); temp(*this); temp.Integrate(); temp.Integrate(); return returntemp(x1) temp(x1)--temp(x0); temp(x0); }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

85

Lendo e escrevendo A classe inclui funções para ler e escrever um polinómio, num formato simples: virtual virtualvoid voidWrite(std::ostream& Write(std::ostream&output output==std::cout) std::cout)const; const; virtual void WriteLine(std::ostream& output virtual void WriteLine(std::ostream& output==std::cout) std::cout)const; const; virtual virtualvoid voidRead(std::istream& Read(std::istream&input input==std::cin); std::cin); void voidPolynomial::Write(std::ostream& Polynomial::Write(std::ostream&output) output)const const {{ int intdegree degree==Degree(); Degree(); void Polynomial::WriteLine(std::ostream& output) const for (int for (inti i==0; 0;i i<= <=degree; degree;i++) i++) {void Polynomial::WriteLine(std::ostream& output) const output output<< <<""""<< <<a[degree a[degree--i]; i]; {Write(output); }} Write(output); output output<< <<std::endl; std::endl; }} void voidPolynomial::Read(std::istream& Polynomial::Read(std::istream&input) input) Ao escrever, {{ Ao ler, primeiro lê-se o grau e escrevem-se só input input>> >>degree; degree; depois os coeficientes. As Grow(degree os coeficientes. Grow(degree++1); 1); for (int i = 0; i <= degree; i++) demais posições são anuladas. for (int i = 0; i <= degree; i++) input input>> >>a[degree a[degree--i]; i]; for for(int (inti i==degree degree++1; 1;i i<<static_cast<int>(a.size()); static_cast<int>(a.size());i++) i++) a[i] = 0.0; a[i] = 0.0; }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003

86


Desenhando o gráfico A função Plot escreve num ficheiro uma tabela de valores para posterior envio para um programa capaz de desenhar gráficos: virtual virtualvoid voidPlot(std::ostream& Plot(std::ostream&output, output,double doublex0, x0,double doublex1, x1,double doubleh) h)const; const;

Ficam dois valores em cada linha, separados por um tab: void voidPolynomial::Plot(double Polynomial::Plot(doublex0, x0,double doublex1, x1,double doubleh, h,std::ostream& std::ostream&output) output)const const {{ output output<< <<std::fixed std::fixed<< <<std::setprecision(4); std::setprecision(4); ifif(x0 > x1) (x0 > x1)

return; return; double doublexx==x0; x0;

while while(x (x<<x1) x1) {{ output output<< <<xx<< <<"\t" "\t"<< <<Value(x) Value(x)<< <<std::endl; std::endl;

xx+= +=h; h; }} output output<< <<x1 x1<< <<"\t" "\t"<< <<Value(x1) Value(x1)<< <<std::endl; std::endl;

}} 2003-07-19

O último par de valores é escrito fora do ciclo, para acertar.

Curso de Programação com C++ © Pedro Guerreiro 2003

87

Testando o gráfico Eis uma função de teste para a função Plot: void voidTestPlot() TestPlot() {{ std::ofstream std::ofstreamoutput("plot.xls"); output("plot.xls"); mas::Polynomial mas::Polynomialp; p; std::cout std::cout<< <<"grau "graueecoeficientes: coeficientes:";"; p.Read(); p.Read(); std::cout std::cout<< <<p.Degree() p.Degree()<< <<""-"; "; p.WriteLine(); p.WriteLine(); double doublea; a; double b; double b; std::cout std::cout<< <<"Intervalo: "Intervalo:";"; std::cin >> a >> std::cin >> a >>b; b; double delta; double delta; std::cout std::cout<< <<"Delta: "Delta:";"; std::cin >> delta; std::cin >> delta; p.Plot(a, p.Plot(a,b, b,delta, delta,output); output); }}

Exemplo de execução:

O ficheiro abre directamente no Excel e produz o seguinte gráfico: 40 30 20 10 0 -4

-2

-10

0

2

4

6

-20 -30 -40

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

88


Funções estáticas Acrescentemos à 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 classPolynomial Polynomial{{ private: private: As funções estáticas não têm objecto. Ao chamá-las, int intdegree; degree; qualificamos com o nome da classe, por exemplo: std::vector<double> std::vector<double>a; a; public: public: //... ////... ... mas::Polynomial p; virtual virtualdouble doubleValue(double Value(doublex) x)const; const; // ... virtual virtualdouble doubleoperator operator()(double ()(doublex) x)const; const; p = mas::Polynomial::Newton(1.0, -2.0, 8); ////... ... public: public: ////static staticfunctions functions static Polynomial static PolynomialNewton(double Newton(doublea, a,double doubleb, b,int intn); n); ////computes computes(ax+b)^n; (ax+b)^n; }; };

Não é obrigatório ter uma zona para funções estáticas, mas fica bem. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

89

Triângulo de Pascal Um exercício clássico de programação é escrever o triângulo de Pascal: 1 11 121 1331 14641 1 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 staticPolynomial PolynomialPascal(int Pascal(intn); n);////computes computes(x (x++1)^n; 1)^n; 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

90


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) = x26x+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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

91

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. O primeiro argumento é o vector A declaração da função deve ser assim: das abcissas e o segundo o das ordenadas.

static staticPolynomial PolynomialLagrange(const Lagrange(conststd::vector<double>& std::vector<double>&x, x,const conststd::vector<double>& std::vector<double>&y); y); ////pre x.size() >= 1 && x.size() == y.size(); 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. A solução do exemplo é o polinómio x2-2x-3. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

92


Mais classes: a classe Date Queremos 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

93

Classe Date

A classe Date reside no espaço de nomes mas: namespace namespacemas mas{{ class classDate Date{{ private: private: int intyear; year; int intmonth; month; int intday; day;

Três membros de dados, para o ano, para o mês e para o dia. Um tipo enumerado para os dias da semana.

public: public: enum enumWeekdayType WeekdayType{MONDAY, {MONDAY,TUESDAY, TUESDAY,WEDNESDAY, WEDNESDAY,THURSDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}; FRIDAY, SATURDAY, SUNDAY}; Construtor por defeito, construtor Date(); de cópia, construtor elementar. Date(); Date(const Date(constDate& Date&other); other); Date(int Date(intyear, year,int intmonth, month,int intday); day); // //pre preValid(year, Valid(year,month, month,day); day); virtual virtual~Date(); ~Date(); //... //... Destrutor. }; };

Esta precondição usa uma função estática Valid, ainda por declarar.

}} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

94


Mais classe Date class classDate Date{{ //... //...

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.

virtual virtualint intYear() Year()const; const; virtual int Month() virtual int Month()const; const; virtual int Day() const; virtual int Day() const; virtual virtualint intCount() Count()const; const; Mudar o valor do objecto, com novos valores para o ano, mês e dia. virtual virtualvoid voidSet(int Set(intyear, year,int intmonth, month,int intday); day); // //pre preValid(year, Valid(year,month, month,day); day); virtual void Forth(); Avançar 1 dia. Recuar 1 dia. virtual void Forth(); virtual // virtualvoid voidBack(); Back(); //pre preoperator operator>>(First()); (First()); Avançar x dias. Recuar x dias. virtual void Add(int x); // pre x >= 0; virtual void Add(int x); // pre x >= 0; virtual // virtualvoid voidSubtract(int Subtract(intx); x); //pre prexx>= >=00&& &&DaysSince(First()) DaysSince(First())>= >=x; x; virtual const Date& First() const; Data do primeiro dia do nosso calendário. virtual const Date& First() const; virtual int MonthSize() const; virtual int MonthSize() const; Quantos dias tem o mês do objecto? virtual // virtualint intDaysTo(const DaysTo(constDate& Date&other) other)const; const; //pre preoperator operator<= <=(other); (other); virtual int DaysSince(const Date& other) const; // pre operator >= (other); virtual int DaysSince(const Date& other) const; // pre operator >= (other); //... //... Dias que faltam para other. }; Dias que passaram desde other. };

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

95

Classe Date: operadores Operadores de comparação.

Certas funções são operadores: class classDate Date{{ //... //...

virtual virtualbool booloperator operator== ==(const (constDate& Date&other) other)const; const; virtual bool operator != (const Date& other) const; virtual bool operator != (const Date& other) const; virtual virtualbool booloperator operator<= <=(const (constDate& Date&other) other)const; const; virtual bool operator < (const Date& other) virtual bool operator < (const Date& other)const; const; virtual virtualbool booloperator operator>= >=(const (constDate& Date&other) other)const; const; virtual bool operator > (const Date& other) virtual bool operator > (const Date& other)const; const; virtual virtualDate& Date&operator operator++ ++ (int); (int); virtual Date& operator -virtual Date& operator -- (int); (int); // //pre pre*this *this>>First(); First();

Permitir-nos-ão escrever coisas como: Date d1; Date d2; // ... if (d1 <= d2) ... //... while (d1 != d2) ...

Operadores de incrementação e decrementação. Poderemos escrever d++ em vez de d.Forth() e d-em vez de d.Back().

virtual virtualDate Dateoperator operator+(int +(intx) x)const; const; // //pre prexx>= >=0; 0; virtual Date& operator +=(int x); // pre x >= virtual Date& operator +=(int x); // pre x >=0; 0; virtual virtualDate Dateoperator operator-(int -(intx) x)const; const; // //pre prexx>= >=00&& &&Count() Count()>= >=x; x; virtual Date& operator -=(int x); // pre x >= 0 && Count() >= x; virtual Date& operator -=(int x); // pre x >= 0 && Count() >= x; //... //... 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.) }; 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

96


Classe Date: lendo e escrevendo

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 <<. class classDate Date{{ //... //... virtual virtualvoid voidWrite(std::ostream& Write(std::ostream&output output==std::cout) std::cout)const; const; virtual void WriteLine(std::ostream& output = std::cout) virtual void WriteLine(std::ostream& output = std::cout)const; const; virtual virtualvoid voidRead(std::istream& Read(std::istream&input input==std::cin); std::cin); virtual virtualvoid voidAccept(const Accept(conststd::string& std::string&prompt, prompt,const conststd::string& std::string&errorMessage); errorMessage); friend std::ostream& operator << (std::ostream& output, const Date& friend std::ostream& operator << (std::ostream& output, const Date&d); d); //... //... }; };Com este operador poderemos programas coisas como: Date d; //... std::cout << “A data é " << d << " e a do dia seguinte é " << d+1 << std::endl; 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

97

Classe Date: semanas

Eis algumas funções sobre semanas:

class classDate Date{{ //... //... Repare que o resultado é de virtual tipo WeekdayType. virtualWeekdayType WeekdayTypeWeekday() Weekday()const; const; virtual void AddWeek(int x); virtual void AddWeek(int x); virtual virtualvoid voidSubtractWeek(int SubtractWeek(intx); x); // //pre preCount() Count()>= >=77**x; x; virtual bool Weekend() const; virtual bool Weekend() const; //... //... }; };

Podemos programar logo algumas delas:

Note bem: as semanas começam na segunda-feira e acabam no domingo.

void voidDate::AddWeek(int Date::AddWeek(intx) x) {{ void voidDate::SubtractWeek(int Date::SubtractWeek(intx) x) Add(7 Add(7**x); x); {{ }} bool boolDate::Weekend() Date::Weekend()const const Subtract(7 Subtract(7**x); x); { { }} return returnWeekday() Weekday()>= >=SATURDAY; SATURDAY; }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

98


Classe Date: funções estáticas

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 memclass classDate Date{{ //... bros de dados. Cada //... public: // static public: // static objecto tem os seus próprios membros static staticbool boolValid(int Valid(inty, y,int intm, m,int intd); d); static bool Valid(int y, int m); de dados. Os memstatic bool Valid(int y, int m); static bool Valid(int y); static bool Valid(int y); bros estáticos são da classe e são static // staticbool boolLeapYear(int LeapYear(inty); y); //pre preValid(y); Valid(y); static staticint intDaysInMonth(int DaysInMonth(inty, y,int intm); m);// //pre preValid(y, Valid(y,m); m); partilhados por todos static int DaysInYear(int y); // pre Valid(y); static int DaysInYear(int y); // pre Valid(y); os objectos. Primeiro ano do calendário, primeiro dia do private: private: // //static staticdata datamembers members calendário, dia da semana respectivo e tabela dos static staticconst constint intfirstYear; firstYear; números de dias dos meses nos anos comuns, static const Date first; static const Date first; static staticconst constWeekdayType WeekdayTypefirstWeekday; firstWeekday; Os membros estáticos são static const int static const intdaysInMonth[]; daysInMonth[]; inicializados no ficheiro .cpp. }; };

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

99

Construtores de datas O construtor por defeito, que dá a data de “hoje”, é o mais sofisticado: A variável time0 recebe o tempo corrente, Date::Date() Date::Date() {{ time_t time_ttime0 time0==::time(0); ::time(0); struct ::tm *now struct ::tm *now==::localtime(&time0); ::localtime(&time0); year year==now->tm_year now->tm_year++1900; 1900; month month==now->tm_mon now->tm_mon++1; 1; day = now->tm_mday; day = now->tm_mday; }}

Os outros construtores são rotineiros: Date::Date(int Date::Date(intyear, year,int intmonth, month,int intday): day): year(year), year(year), month(month), month(month), day(day) day(day) {{ }} 2003-07-19

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(const Date::Date(constDate& Date&other): other): year(other.year), year(other.year), month(other.month), month(other.month), day(other.day) day(other.day) {{ }}

Curso de Programação com C++ © Pedro Guerreiro 2003

100


Destrutor de datas O 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.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

101

Selectores de datas Os selectores para o ano, para o mês, e para o dia são muito simples:

O selector para o número de dias desde o início do calendário recorre à função DaysSince:

int intDate::Year() Date::Year()const const {{ return returnyear; year; }}

int intDate::Count() Date::Count()const const {{ return returnDaysSince(first); DaysSince(first); }}

int intDate::Month() Date::Month()const const {{ return returnmonth; month; }}

first é o membro estático que dá a data do primeiro dia do calendário. É inicializado assim:

const constint intDate::firstYear Date::firstYear==1901; 1901; int intDate::Day() Date::Day()const const const Date Date::first(Date::firstYear, const Date Date::first(Date::firstYear,1, 1,1); 1); {{ return returnday; day; const A função First constDate& Date&Date::First() Date::First()const const }} {{ devolve a data do return returnfirst; first; primeiro dia. }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

102


Modificadores de datas, avançando Avança-se um dia com a função Forth:

Avança-se x dias com a função Add:

void voidDate::Forth() Date::Forth() {{ ifif(day (day<<MonthSize()) MonthSize()) day++; day++; else else A função MonthSize {{ diz quantos dias tem o day day==1; 1; mês. Qual mês? O mês ifif(month (month<<12) 12) a que pertence o month++; month++; objecto. else else {{ month month==1; 1; year++; year++; }} }} 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.

void voidDate::Add(int Date::Add(intx) x) {{ for for(int (inti i==0; 0;i i<<x; x;i++) i++) Forth(); Forth(); }} Isto não é lá muito eficiente, se x for grande /. Por outro lado, se x for negativo, nada acontece.

2003-07-19

Avança-se x semanas somando 7*x dias: void voidDate::AddWeek(int Date::AddWeek(intx) x) {{ Add(7 Add(7**x); x); }}

Curso de Programação com C++ © Pedro Guerreiro 2003

103

Modificadores de datas, recuando São os três muito parecidos com os três para avançar. Recua-se um dia com a Recua-se x dias com a função Back: função Subtract: void voidDate::Back() Date::Back() {{ ifif(day (day>>1) 1) day--; day--; else else {{ ifif(month (month>>1) 1) month--; month--; else else {{ month month==12; 12; year--; year--; }} day day==MonthSize(); MonthSize(); }} }} 2003-07-19

void voidDate::Subtract(int Date::Subtract(intx) x) {{ for for(int (inti i==0; 0;i i<<x; x;i++) i++) Back(); Back(); }}

Recua-se x semanas subtraindo 7*x dias: void voidDate::SubtractWeek(int Date::SubtractWeek(intx) x) {{ Subtract(7 Subtract(7**x); x); }}

Curso de Programação com C++ © Pedro Guerreiro 2003

104


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 constint intDate::daysInMonth[] Date::daysInMonth[]=={31, {31,28, 28,31, 31,30, 30,31, 31,30, 30,31, 31,31, 31,30, 30,31, 31,30, 30,31}; 31};

A função estática DaysInMonth baseia-se neste quadro de valores, corrigindo o caso de Fevereiro nos anos bissextos: int intDate::DaysInMonth(int Date::DaysInMonth(inty, y,int intm) m) {{ return returndaysInMonth[m daysInMonth[m--1] 1]++(m (m== ==22&& &&LeapYear(y)); LeapYear(y)); }}

Esta função é estática: trabalha sobre os seus argumentos e sobre os membros estáticos.

A função MonthSize é um selector. Recorre à função estática DaysInMonth: int intDate::MonthSize() Date::MonthSize()const const {{ return returnDaysInMonth(year, DaysInMonth(year,month); month); }} 2003-07-19

Esta função é virtual (não é estática): trabalha sobre os membros de dados do objecto.

Curso de Programação com C++ © Pedro Guerreiro 2003

105

Anos bissextos No 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: bool boolDate::LeapYear(int Date::LeapYear(inty) y) {{ return returnyy% %400 400== ==00|| ||yy% %44== ==00 && &&yy% %100 100!= !=0; 0; }}

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.

A função estática DaysInYear calcula o número de dias do ano passado em argumento: int intDate::DaysInYear(int Date::DaysInYear(inty) y) {{ return return365 365++LeapYear(y); LeapYear(y); }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

106


Quantos dias faltam? A função DaysTo conta os dias até uma data no futuro, isto é, uma data posterior (ou igual) ao objecto: int intDate::DaysTo(const Date::DaysTo(constDate& Date&other) other)const const {{ int Isto é o construtor de cópia: a data temp é inicializado intresult result==0; 0; Date temp(*this); com uma cópia da data objecto da função, isto é, da Date temp(*this); while (temp != other) data para a qual a função DaysTo foi chamada. while (temp != other) {{ temp.Forth(); Repare que se a data other for anterior à data objecto da temp.Forth(); result++; função, o ciclo nunca termina. result++; }} return returnresult; result; }}

A função DaysInYear conta os dias desde uma data no passado: int intDate::DaysSince(const Date::DaysSince(constDate& Date&other) other)const const {{ Recorde que em cada função membro, o apontador this é return returnother.DaysTo(*this); other.DaysTo(*this); declarado implicitamente e “aponta” para o objecto da }} função. A expressão *this representa esse objecto. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

107

A instrução while Sintaxe: A instrução while já tinha surgido antes. Fica aqui registada para referência.

while (expressão) instrução Semântica:

1. Avalia a expressão. Se der false, termina; se não: 2. Executa a instrução; 3. Volta ao passo 1.

2003-07-19

O que se faz com while pode fazer-se com for e viceversa. 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.

Curso de Programação com C++ © Pedro Guerreiro 2003

108


A instrução do A instrução do é semelhante à instrução while, mas o teste da expressão de continuação é feito depois da instrução: Sintaxe: do instrução while (condição); Semântica:

Usa-se quando, por natureza, a instrução tem 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.)

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

109

Operadores de comparação Uma data é igual a outra se os seus membros de dados tiverem os mesmos valores do que os da outra: bool boolDate::operator Date::operator== ==(const (constDate& Date&other) other)const const {{ Usamos o this aqui apenas por uma questão de return returnthis->day this->day== ==other.day other.day estilo, para contrabalançar o other, mas && &&this->month this->month== ==other.month other.month poderíamos ter simplificado: && &&this->year this->year== ==other.year; other.year; }} return day == other.day && ...;

Ser diferente é não ser igual: bool boolDate::operator Date::operator!= !=(const (constDate& Date&other) other)const const {{ return return!!operator operator== ==(other); (other); }}

Aqui podíamos ter escrito return !(*this == other); mas não return *this != other; pois seria uma definição recursiva, errada neste caso.

Recorde que, para o compilador, d1==d2 (sendo d1 e d2 duas expressões de tipo Date) é o mesmo que d1.operator ==(d2). 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

110


Antes e depois Uma 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 boolDate::operator Date::operator<= <=(const (constDate& Date&other) other)const const {{ return returnthis->year this->year<< other.year other.year || this->year || this->year== ==other.year other.year && && (this->month (this->month<<other.month other.month || this->month || this->month== ==other.month other.month && &&this->day this->day<= <=other.day); other.day); }} bool boolDate::operator Date::operator<< (const (constDate& Date&other) other)const const {{ Para deixar as coi- } return returnoperator operator<= <=(other) (other)&& &&!!operator operator== ==(other); (other); } bool Date::operator >= (const Date& other) const bool Date::operator >= (const Date& other) const sas mais simples, {{ return!!operator operator<= <=(other) (other)|| ||operator operator== ==(other); (other); os outros operadores }}return bool Date::operator > (const Date& other) bool Date::operator > (const Date& other)const const {{ são programados em return return!!operator operator<= <=(other); (other); termos dos anteriores. }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

111

Incrementando, decrementando datas Na “espírito” do C++, habitualmente preferimos “somar 1” usando o operador ++ e “subtrair 1” usando o operador --: Date& Date&Date::operator Date::operator++ ++ (int) (int) {{ Forth(); Forth(); return return*this; *this; }}

Date& Date&Date::operator Date::operator--- (int) (int) {{ Back(); Back(); return return*this; *this; }}

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.

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). 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

112


Somando, subtraindo Os operadores +, +=, -, -= são análogos aos da classe Polynomial: Date DateDate::operator Date::operator+(int +(intx) x)const const {{

Date DateDate::operator Date::operator-(int -(intx) x)const const {{

}}

}}

return returnDate(*this) Date(*this)+= +=x; x;

Repare nesta técnica.

return returnDate(*this) Date(*this)-= -=x; x;

Date& Date&Date::operator Date::operator+=(int +=(intx) x) {{

Date& Date&Date::operator Date::operator-=(int -=(intx) x) {{

}}

}}

Add(x); Add(x); return return*this; *this;

2003-07-19

Subtract(x); Subtract(x); return return*this; *this;

Curso de Programação com C++ © Pedro Guerreiro 2003

113

Escrevendo datas Temos as funções Write, WriteLine e o operador << na combinação que se tornará habitual: void voidDate::Write(std::ostream& Date::Write(std::ostream&output) output)const const {{ output output<< <<year year<< <<""""<< <<month month<< <<""""<< <<day; day; }} void voidDate::WriteLine(std::ostream& Date::WriteLine(std::ostream&output) output)const const {{

Estas duas são funções virtuais. Pertencem à classe Date.

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

}}

std::ostream& std::ostream&operator operator<< <<(std::ostream& (std::ostream&output, output,const constDate& Date&d) d) {{ Esta é uma função d.Write(output); d.Write(output); amiga. Está fora da return returnoutput; output; classe. }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

114


Lendo datas Para ler ano, mês e dia a partir de um ficheiro, usamos a função Read:

void voidDate::Read(std::istream& Date::Read(std::istream&input) input) {{ input input>> >>year year>> >>month>> month>>day; day; }}

Para ler interactivamente a partir da consola, usamos a função Accept:

void voidDate::Accept(const Date::Accept(conststd::string& std::string&prompt, prompt,const conststd::string& std::string&errorMessage) errorMessage) {{ Ciclo infinito. Quebra quando os três números lidos formarem int intyear year==0; 0; int uma data válida. intmonth month==0; 0; int intday day==0; 0; for(;;) A leitura falhará se o utilizador entrar caracteres for(;;) {{ que não possam pertencer a um número std::cout std::cout<< <<prompt; prompt; decimal (letras, por exemplo.) std::cin std::cin>> >>year year>> >>month month>> >>day; day; ifif(std::cin.fail()) (std::cin.fail()) // //ififsomething somethingwent wentwrong wrong std::cin.clear(); std::cin.clear(); // //reset resetio iostate stateflags flags std::cin.ignore(std::numeric_limits<int>::max(), std::cin.ignore(std::numeric_limits<int>::max(),'\n'); '\n'); // //skip skipto toend endof ofline line ifif(Date::Valid(year, (Date::Valid(year,month, month,day)) day)) Repare na técnica usada para saltar o break; break; std::cout << errorMessage << std::endl; std::cout << errorMessage << std::endl; resto da linha corrente. }} Set(year, Set(year,month, month,day); day); Quando a função termina, o objecto é uma data válida. }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

115

Datas válidas Todas 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”:

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): bool boolDate::Valid(int Date::Valid(inty, y,int intm) m) {{ return returnValid(y) Valid(y)&& &&11<= <=m m&& &&m m<= <=12; 12; }} 2003-07-19

bool boolDate::Valid(int Date::Valid(inty) y) {{ return returnfirstYear firstYear<= <=y; y; }}

Curso de Programação com C++ © Pedro Guerreiro 2003

Estas três funções são funções estáticas.

bool boolDate::Valid(int Date::Valid(inty, y,int intm, m,int intd) d) {{ return returnValid(y, Valid(y,m) m)&& &&11<= <=dd&& &&dd<= <=DaysInMonth(y, DaysInMonth(y,m); m); }}

116


O dia da semana Para calcular o dia da semana, basta recordar que o primeiro dia do nosso calendário, 1 de Janeiro de 1901, foi uma terçafeira: const constDate::WeekdayType Date::WeekdayTypeDate::firstWeekday Date::firstWeekday==TUESDAY; TUESDAY;

Inicialização da constante estática.

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: Date::WeekdayType Date::WeekdayTypeDate::Weekday() Date::Weekday()const const {{ return returnstatic_cast<WeekdayType>((Count() static_cast<WeekdayType>((Count()++firstWeekday) firstWeekday)% %7); 7); }} 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.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

117

Sexta-feira 13 Nó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 Eis o resultado na consola. voidTestWorstFridays13OfThisCentury() TestWorstFridays13OfThisCentury() {{ Infelizmente, o programa leva mas::Date muito tempo a calcular isto. mas::Dated(2001, d(2001,1, 1,1); 1); int intcount count==0; 0; Porquê? while while((d.Year() ((d.Year()--1) 1)//100 100== ==20) 20) {{ ifif(d.Day() (d.Day()== ==13 13&& &&d.Weekday() d.Weekday()== ==mas::Date::FRIDAY) mas::Date::FRIDAY) {{ count++; count++; ifif(count (count% %13 13== ==0) 0) d.WriteLine(); d.WriteLine(); Quantas são as piores }} d.Forth(); sextas-feiras 13 do d.Forth(); }} século XXI? }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

118


Classe Date, versão 2 O 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 classDate Date{{ private: private: int intcount; count; // //number numberof ofdays dayssince sincefirst. first. public: public: //... //... }; };

Usando esta nova implementação, o programa das piores sextasfeiras 13 fica instantâneo.

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

119

Datas portuguesas Quando 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 namespacemas mas{{

Repare na maneira de especificar que a classe DatePortuguese herda da classe Date.

class classDatePortuguese: DatePortuguese:public publicDate Date{{ //... //... virtual virtualconst conststd::string& std::string&MonthName() MonthName()const; const; virtual const std::string& WeekdayName() virtual const std::string& WeekdayName()const; 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.

}} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

120


Feriados Há uma função booleana para cada feriado: class classDatePortuguese: DatePortuguese:public publicDate Date{{ //... //... bool boolDatePortuguese::Holiday() DatePortuguese::Holiday()const const virtual virtualbool boolHoliday() Holiday()const; const; {{ return returnNewYear() NewYear()|| ||Liberty() Liberty()|| ||Workers() Workers() virtual virtualbool boolNewYear() NewYear()const; const; || Nation() || Assumption() || Republic() || Nation() || Assumption() || Republic() virtual virtualbool boolLiberty() Liberty()const; const; || ||AllSaints() AllSaints()|| ||Independence() Independence()|| ||OurLady() OurLady() virtual virtualbool boolWorkers() Workers()const; const; || Christmas() || Carnival() || GoodFriday() || Christmas() || Carnival() || GoodFriday() virtual virtualbool boolNation() Nation()const; const; || ||Easter() Easter()|| ||CorpusChristi(); CorpusChristi(); virtual bool Assumption() virtual bool Assumption()const; const; }} virtual virtualbool boolRepublic() Republic()const; const; virtual bool AllSaints() virtual bool AllSaints()const; const; virtual bool Independence() virtual bool Independence()const; const; virtual virtualbool boolOurLady() OurLady()const; const; virtual virtualbool boolChristmas() Christmas()const; const; virtual bool Carnival() const; virtual bool Carnival() const; Os últimos quatro são os feriados virtual virtualbool boolGoodFriday() GoodFriday()const; const; móveis: movem-se com a Páscoa. virtual virtualbool boolEaster() Easter()const; const; virtual virtualbool boolCorpusChristi() CorpusChristi()const; const; //... //... }; };

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

121

Formatos de escrita A 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 classDatePortuguese: DatePortuguese:public publicDate Date{{ public: public: enum enumFormatType FormatType{DEFAULT, {DEFAULT,STANDARD, STANDARD,STANDARD_FULL, STANDARD_FULL, TEXT, TEXT_WITH_WEEKDAY, TEXT, TEXT_WITH_WEEKDAY,ARMY, ARMY,NUMERIC}; NUMERIC}; private: private: // //non nonconst conststatic staticdata datamembers members static FormatType format; static FormatType format; A nova função Write redefine a public: public: função Write herdada. //... //... virtual virtualvoid voidWrite(std::ostream& Write(std::ostream&output output==std::cout) std::cout)const; const; //... //... public: public:// //static static static void static voidSetFormat(FormatType SetFormat(FormatTypenewFormat); newFormat); }; };

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

122


Escrevendo a data portuguesa

A função Write agulha segundo o formato, usando uma instrução switch:

void voidDatePortuguese::Write(std::ostream& DatePortuguese::Write(std::ostream&output) output)const const {{ switch Note bem: se o formato for DEFAULT, usaswitch(format){ (format){ case DEFAULT: se a função Write da classe Date. case DEFAULT: Date::Write(output); Date::Write(output); break; break; Repare nos manipuladores: setfill case caseSTANDARD: STANDARD: determina o carácter de output output<< <<std::setfill('0') std::setfill('0') << <<std::setw(2) std::setw(2)<< <<Year() Year()% %100 100<< <<separator separator preenchimento; setw determina a << std::setw(2) << Month() << separator largura do campo de escrita. É << std::setw(2) << Month() << separator << std::setw(2) << Day() preciso #include <iomanip>. << std::setw(2) << Day() << <<std::setfill(' std::setfill(''); '); break; break; Cada alternativa case termina por uma case caseSTANDARD_FULL: STANDARD_FULL: instrução break. Sem isso, o controlo // //... ... passava à alternativa case seguinte. break; break; case TEXT: case TEXT: output output<< <<Day() Day()<< <<""de de""<< <<MonthName() MonthName()<< <<""de de"" << <<Year(); Year(); break; break; //... //... Restantes alternativas. }} }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

123

A instrução switch Sintaxe:

Semântica:

switch (expression) { case expr_const1: statement1_1; ... break; case expr_const2: statement2_1; ... break; ... default: statement0_1; ... É a mais break; complicada de } todas as instruções

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.

do C++. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

124


O fim do prazo Em 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 voidTestDeadline() TestDeadline() {{ mas::DatePortuguese mas::DatePortuguesed; d; d.Accept("Início d.Accept("Iníciodo doprazo prazo(ano, (ano,mês, mês,dia)? dia)?",","Data "Datainválida."); inválida."); std::cout << "Prazo de quantos dias úteis? "; std::cout << "Prazo de quantos dias úteis? "; int intx; x; O primeiro ciclo conta os dias. O segundo std::cin std::cin>> >>x; x; avança até ao próximo dia útil. while (x > 0) while (x > 0) {{ ifif(!d.Weekend() Aqui chamamos uma função estática através (!d.Weekend()&& &&!d.Holiday()) !d.Holiday()) x--; x--; de um objecto da classe. Poderíamos ter d.Forth(); d.Forth(); feito de outra maneira, assim: }} while (d.Weekend() || d.Holiday()) while (d.Weekend() || d.Holiday()) mas::DatePortuguese::SetFormat(...) d.Forth(); d.Forth(); d.SetFormat(mas::DatePortuguese::TEXT_WITH_WEEKDAY); d.SetFormat(mas::DatePortuguese::TEXT_WITH_WEEKDAY); d.WriteLine(); d.WriteLine(); }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

125

Resumo das instruções statement

case constant-expression : statement default : statement

labeled-statement expression-statement compound-statement selection-statement iteration-statement jump-statement declaration-statement

expressionopt ;

The null statement (just a semicolon) is in fact an expressionstatement without an expression...

{ statement-seqopt }

statement statement-seq statement

if ( condition ) statement if ( condition ) statement else statement switch ( condition ) statement

while ( condition ) statement do statement while ( condition ) ; for ( for-init-statement conditionopt ; expressionopt ) statement break ; continue ; return expressionopt ; block-declaration

2003-07-19

expression-statement simple-declaration simple-declaration ...

Curso de Programação com C++ © Pedro Guerreiro 2003

126


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). 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

127

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

128


Cadeias std::string (3) O construtor por defeito cria uma cadeia vazia com alguma capacidade. A função empty dá true quando a função size dá zero. A função capacity dá a capacidade. Não confunda capacity e size. Veja: void voidTest_string_constructors() Test_string_constructors() {{ std::string std::strings; s; std::cout std::cout<< <<ss<< <<"<" "<"<< <<std::endl; std::endl; std::cout << static_cast<int>(s.size()) std::cout << static_cast<int>(s.size())<< <<""""<< <<static_cast<int>(s.capacity()) static_cast<int>(s.capacity())<< <<std::endl; std::endl; std::string std::stringr(32, r(32,'*'); '*'); std::cout std::cout<< <<rr<< <<"<" "<"<< <<std::endl; std::endl; std::cout << static_cast<int>(r.size()) std::cout << static_cast<int>(r.size())<< <<""""<< <<static_cast<int>(r.capacity()) static_cast<int>(r.capacity())<< <<std::endl; std::endl; std::string q("Our initial assessment is that they will all die."); std::string q("Our initial assessment is that they will all die."); std::cout std::cout<< <<qq<< <<"<" "<"<< <<std::endl; std::endl; std::cout << static_cast<int>(q.size()) std::cout << static_cast<int>(q.size())<< <<""""<< <<static_cast<int>(q.capacity()) static_cast<int>(q.capacity())<< <<std::endl; std::endl; std::string p(q, 31, 9); std::string p(q, 31, 9); std::cout std::cout<< <<pp<< <<"<" "<"<< <<std::endl; std::endl; std::cout << static_cast<int>(p.size()) std::cout << static_cast<int>(p.size())<< <<""""<< <<static_cast<int>(p.capacity()) static_cast<int>(p.capacity())<< <<std::endl; std::endl; }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 129

Exemplo: Secret Exemplifiquemos 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

130


Classe Secret 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” namespace namespacemas mas{{ uma letra de class Secret { class Secret { um certo private: private: número de std::string std::stringkey; key; int number; int number; lugares no public: public: alfabeto. Secret(const Secret(conststd::string& std::string&key, key,int intnumber number==1); 1);

virtual virtual~Secret(); ~Secret(); virtual std::string virtual std::stringCiphered(const Ciphered(conststd::string& std::string&x) x)const; const; virtual std::string Deciphered(const std::string& virtual std::string Deciphered(const std::string&x) x)const; const; virtual virtualvoid voidSetNumber(int SetNumber(intx); x); private: private: static staticchar charShifted(char Shifted(charx, x,int intn); n); char Secret::Shifted(char x, int n) char Secret::Shifted(char x, int n) }; }; {{ return returnstatic_cast<char>('A' static_cast<char>('A'++(x (x--'A' 'A'++n) n)% %26); 26); }} }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

131

Cifrando std::string std::stringSecret::Ciphered(const Secret::Ciphered(conststd::string& std::string&s) s)const const {{ O resultado tem no máximo o triplo std::string std::stringresult; result; dos caracteres da mensagem. result.reserve(3*s.size()); result.reserve(3*s.size()); Reservamos espaço com fartura, int para a cadeia não ter de crescer a intm m==0; 0; for for(int (inti i==0; 0;i i<<static_cast<int>(s.size()); static_cast<int>(s.size());i++) i++) meio do processamento. {{ std::string::size_type std::string::size_typekk==key.find(s[i]); key.find(s[i]); ifif(k (k== ==std::string::npos) std::string::npos) result.push_back(Shifted(s[i], Se a letra não pertence à chave. result.push_back(Shifted(s[i],number)); number)); else else {{ Se a letra pertence à chave. result.push_back(key[m result.push_back(key[m% %key.size()]); key.size()]); result.push_back(Shifted(s[i], result.push_back(Shifted(s[i],number)); number)); result.push_back(key[(m+1) result.push_back(key[(m+1)% %key.size()]); key.size()]); m++; m++; }} Atenção: repare na declaração Esta função ilustra o construtor por }} da variável k e na utilização da defeito, as funções reserve, size, constante npos. Faça sempre find e push_back e o operador []. return result; return result; assim. }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

132


push_back e []

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: std::string std::stringSecret::CipheredWrong(const Secret::CipheredWrong(conststd::string& std::string&s) s)const const {{ //... //... int intjj==0; 0; for for(int (inti i==0; 0;i i<<static_cast<int>(s.size()); static_cast<int>(s.size());i++) i++) {{ std::string::size_type std::string::size_typekk==key.find(s[i]); key.find(s[i]); ifif(k (k== ==std::string::npos) std::string::npos) result[j++] result[j++]==Shifted(s[i], Shifted(s[i],number); number); else else {{ Não se esqueça: o argumento do operador de result[j++] result[j++]==key[m key[m% %key.size()]; key.size()]; 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 é return result; indefinido. return result; }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

133

Decifrando std::string std::stringSecret::Deciphered(const Secret::Deciphered(conststd::string& std::string&s) s)const const {{ std::string std::stringresult; result; Recorde que a restrição sobre o valor de N // left as an // left as anexercise... exercise... garante que a decifração é unívoca. return returnresult; result; }}

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) //...

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

134


Testando a classe Secret

void voidTestSecret() TestSecret() {{ mas::Secret mas::Secretsecret("FCTUNL", secret("FCTUNL",4); 4); for (;;) for (;;) {{ std::string std::strings; s; std::cout std::cout<< <<"Cadeia "Cadeiapara paracifrar: cifrar:"; "; std::getline(std::cin, std::getline(std::cin,s); s); ifif(!std::cin) (!std::cin) break; break;

Repare na utilização da função std::getline.

std::string std::stringss ss==secret.Ciphered(s); secret.Ciphered(s); std::cout << std::cout <<"Cadeia "Cadeiacifrada: cifrada: ""<< <<ss ss<< <<std::endl; std::endl; std::string std::stringds ds==secret.Deciphered(ss); secret.Deciphered(ss); std::cout << std::cout <<"Cadeia "Cadeiadecifrada: decifrada: ""<< <<ds ds<< <<std::endl; std::endl; std::cout std::cout<< <<std::endl; std::endl; }} }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

135

Bónus: os números possíveis Eis uma função para calcular a lista dos números possíveis, dada a chave: Os números possíveis são os

class classSecret Secret{{ //... //... virtual virtualstd::list<int> std::list<int>SecretNumbers() SecretNumbers()const; const; }; };

std::list<int> std::list<int>Secret::SecretNumbers() Secret::SecretNumbers()const const {{ std::list<int> std::list<int>result; result; std::vector<bool> std::vector<bool>numbers(27, numbers(27,true); true); for (int i = 0; i < static_cast<int>(key.size()); for (int i = 0; i < static_cast<int>(key.size());i++) i++) for for(int (intjj==i;i;jj<<static_cast<int>(key.size()); static_cast<int>(key.size());j++) j++) {{ int intxx==::abs(key[i] ::abs(key[i]--key[j]); key[j]); numbers[x] numbers[x]==numbers[26-x] numbers[26-x]==false; false; }} for for(int (inti i==1; 1;i i<<26; 26;i++) i++) ifif(numbers[i]) (numbers[i]) result.push_back(i); result.push_back(i); return returnresult; result; }} 2003-07-19

que permitem a decifração unívoca, dada a chave.

Vector de booleanos, inicializado com 27 elementos, índices de zero a 26, todos a true.

Algumas das posições passam a false. As que ficaram a true correspondem aos números possíveis.

Curso de Programação com C++ © Pedro Guerreiro 2003

136


Escrevendo listas As funções para escrever listas são funções globais, no espaço de nomes mas: namespace namespacemas mas{{

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>.

template template<class <classT> T> void Write(const void Write(conststd::list<T>& std::list<T>&v, v, const std::string& const std::string&separator separator==""",",std::ostream& std::ostream&output output==std::cout) std::cout) {{ Aprecie esta técnica for for(typename (typenamestd::list<T>::const_iterator std::list<T>::const_iteratori i==v.begin(); v.begin();i i!= !=v.end(); v.end();i++) i++) desde já. Os output << (i != v.begin() ? separator : "") << *i; output << (i != v.begin() ? separator : "") << *i; pormenores virão }}

depois.

template template<class <classT> T> void WriteLine(const void WriteLine(conststd::list<T>& std::list<T>&v, v, const conststd::string& std::string&separator separator==""",",std::ostream& std::ostream&output output==std::cout) std::cout) {{ Write(v, Write(v,separator, separator,output); output); output output<< <<std::endl; std::endl; }}

template template<class <classT> T> std::ostream& std::ostream&operator operator<< <<(std::ostream& (std::ostream&output, output,const conststd::list<T>& std::list<T>&v) v) {{ Write(v, Também há funções para escrever listas de trás para a Write(v,""",",output); output); return returnoutput; output; frente: WriteBackwards e WriteBackwardsLine. }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003

137

Escolhendo o número secreto Eis outra função de teste. Pede a chave, mostra os números possíveis, pede o número, etc.: void voidTestSecret_1() TestSecret_1() {{ std::string std::stringkey; key; std::cout std::cout<< <<"Chave: "Chave:"; "; std::getline(std::cin, std::getline(std::cin,key); key); mas::Secret mas::Secretsecret(key); secret(key); std::cout std::cout<< <<"Números "Númerospossíveis: possíveis:"; "; mas::WriteLine(secret.SecretNumbers()); mas::WriteLine(secret.SecretNumbers()); std::cout std::cout<< <<"Número: "Número:"; "; int number; int number; std::cin std::cin>> >>number; number; secret.SetNumber(number); secret.SetNumber(number); std::string std::stringdummy; dummy; std::getline(std::cin, std::getline(std::cin,dummy); dummy); for for(;;) (;;) {{ //... //... }} Repare que é preciso ler o }} resto da linha depois de ter lido o número. 2003-07-19

void voidSecret::SetNumber(int Secret::SetNumber(intnumber) number) {{ this->number this->number==number; number; }}

Curso de Programação com C++ © Pedro Guerreiro 2003

138


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. Para os pormenores, veja Insere-se com insert. os livros ou os helps. 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.)

• • • • • • • • • • • • • 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

139

Outras cadeias: classe StringBasic Já 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

140


Classe StringBasic: construtores namespace namespacemas mas{{

Construtor por defeito.

class Construtor de cópia classStringBasic: StringBasic:public publicClonable Clonable{{ private: private: //... Construtores de conversão. //... public: public: StringBasic(); Construtor de concatenação. StringBasic(); StringBasic(const StringBasic(constStringBasic& StringBasic&other); other); StringBasic(const StringBasic(conststd::string& std::string&s); s); Construtor de cópia parcial. StringBasic(const char StringBasic(const char*s); *s); StringBasic(const StringBasic(constStringBasic& StringBasic&other1, other1,const constStringBasic& StringBasic&other2); other2); StringBasic(const StringBasic& other, int startPos, StringBasic(const StringBasic& other, int startPos,int intendPos); endPos); // //pre preother.ValidRange(startPos, other.ValidRange(startPos,endPos); endPos); explicit StringBasic(char c); explicit StringBasic(char c); Construtor de intervalo de caracteres. StringBasic(char StringBasic(charlowerBound, lowerBound,char charupperBound); upperBound); explicit explicitStringBasic(int StringBasic(intcapacity); capacity); // //pre precapacity capacity>= >=1; 1; StringBasic(const StringBasic& other, int capacity); StringBasic(const StringBasic& other, int capacity); // //pre pre!other.Empty() !other.Empty()&& &&capacity capacity>= >=1; 1; // post Full(); // post Full(); Construtor de reserva (de capacidade). virtual virtual~StringBasic(); ~StringBasic(); //... //... Construtor de preenchimento }} StringBasic herda de Clonable. repetitivo. }; };

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

141

Construtor de conversão básica A classe StringBasic tem dois membros de dados: um inteiro que indica a capacidade corrente e um apontador para uma cadeia básica:

class classStringBasic: StringBasic:public publicClonable Clonable{{ private: private: int intcapacity; capacity; char char*p; *p; public: public: //... //... StringBasic(const StringBasic(constchar char*s); *s); }; };

Este construtor tem como argumento uma cadeia básica. Ele reserva memória dinâmica à justa e copia para lá os caracteres do argumento: StringBasic::StringBasic(const StringBasic::StringBasic(constchar char*s): *s): capacity(static_cast<int>(::strlen(s)+1)), capacity(static_cast<int>(::strlen(s)+1)), p(new p(newchar[capacity]) char[capacity]) {{ ::strcpy(p, ::strcpy(p,s); s); }} 2003-07-19

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.

Curso de Programação com C++ © Pedro Guerreiro 2003

142


Cadeias básicas As 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

143

Destrutor Os caracteres alocados dinamicamente com o operador new no construtor têm de ser desalocados com o operador delete[] no destrutor. StringBasic::~StringBasic() StringBasic::~StringBasic() {{ delete delete [] [] p; p; }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

144


Classe Clonable Clonable é uma classe abstracta. Tem apenas a função Clone, declarada virtual pura: A função Clone criará namespace namespacemas mas{{ dinamicamente um objecto igual class classClonable Clonable{{ public: ao objecto para o qual é public: virtual Clonable* Clone() const = 0; virtual Clonable* Clone() const = 0; virtual chamada, do mesmo tipo, e virtual~Clonable() ~Clonable(){}; {}; }; }; retorna um apontador para esse }} novo objecto. A classe StringBasic implementa a função Clone: As funções Clone são sempre Clonable* StringBasic::Clone() const Clonable* StringBasic::Clone() const {{ programadas assim, com return new StringBasic(*this); recurso ao construtor de cópia. }} return new StringBasic(*this); 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

145

Classe StringBasic: cópia, afectação class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualvoid voidCopy(const Copy(constStringBasic& StringBasic&other); other); virtual StringBasic& operator = (const virtual StringBasic& operator = (constStringBasic& StringBasic&other); other); virtual void SwapOut(StringBasic& other); virtual void SwapOut(StringBasic& other); virtual virtualvoid voidCopySwap(const CopySwap(constStringBasic& StringBasic&other); other); virtual virtualvoid voidSet(const Set(constStringBasic& StringBasic&other); other); virtual void Set(const StringBasic& other, virtual void Set(const StringBasic& other,int intstartPos, startPos,int intendPos); endPos); // pre other.ValidRange(startPos, // pre other.ValidRange(startPos,endPos); 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

146


Cópia tradicional 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: void voidStringBasic::Copy(const StringBasic::Copy(constStringBasic& StringBasic&other) other) {{ ifif(this (this!= !=&other) &other) {{ delete delete[] []this->p; this->p; this->capacity this->capacity==other.Count() other.Count()++1; 1; this->p = new char[this->capacity]; this->p = new char[this->capacity]; ::strcpy(this->p, ::strcpy(this->p,other.p); other.p); }} }}

Repare no if. Ele protege contra situações do género s.Copy(s).

Afectar é sempre copiar e depois devolver o objecto por referência: StringBasic& StringBasic&StringBasic::operator StringBasic::operator==(const (constStringBasic& StringBasic&other) other) {{ Copy(other); Copy(other); return return*this; *this; }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

147

A técnica do SwapOut A função SwapOut troca a representação interna da objecto com a do argumento. O argumento é passado por referência não constante: void voidStringBasic::SwapOut(StringBasic& StringBasic::SwapOut(StringBasic&other) other) {{ std::swap(capacity, std::swap(capacity,other.capacity); other.capacity); std::swap(p, std::swap(p,other.p); other.p); }}

A função de biblioteca std::swap é uma função genérica global que troca os valores dos seus argumentos

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 voidTestSwapOut() TestSwapOut() {{ mas::StringBasic mas::StringBasics("lisboa"); s("lisboa"); mas::StringBasic mas::StringBasicr("porto"); r("porto"); std::cout std::cout<< <<ss<< <<"-" "-"<< <<rr<< <<std::endl; std::endl; s.SwapOut(r); s.SwapOut(r); std::cout std::cout<< <<ss<< <<"-" "-"<< <<rr<< <<std::endl; std::endl; }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

148


Cópia com SwapOut Copiar com SwapOut é muito simples: troca-se (com SwapOut) o objecto com uma variável local inicializada com uma cópia do argumento: void voidStringBasic::CopySwap(const StringBasic::CopySwap(constStringBasic& StringBasic&other) other) {{ StringBasic StringBasictemp(other); temp(other); SwapOut(temp); SwapOut(temp); }}

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. Pormenor técnico: a variável local é mesmo necessária. Não podemos usar uma variável temporária implícita, void voidStringBasic::CopySwap(const StringBasic::CopySwap(constStringBasic& StringBasic&other) other) porque o argumento da função {{ SwapOut(StringBasic(other)); SwapOut não é passado por SwapOut(StringBasic(other)); }} Isto está errado (mesmo referência constante. 2003-07-19

que alguns compiladores Curso de Programação com C++ © Pedro Guerreiro 2003 deixem passar...)

149

Count, Capacity, Empty, Full Frequentemente estes quatro selectores aparecem juntos:

São todos muito simples:

class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualint intCapacity() Capacity()const; const; virtual int Count() const; virtual int Count() const; virtual virtualbool boolEmpty() Empty()const; const; virtual bool Full() virtual bool Full()const; const; }; };

int intStringBasic::Count() StringBasic::Count()const const {{ return returnstatic_cast<int>(::strlen(p)); static_cast<int>(::strlen(p)); }} A função ::strlen conta os caracteres da cadeia básica até ao terminador. Quanto maior a cadeia, mais tempo demora a contar...

bool boolStringBasic::Empty() StringBasic::Empty()const const {{ return return*p *p== ==0; 0; }} Dá true se o primeiro carácter da cadeia (apontado por p) é o terminador (código zero).

int intStringBasic::Capacity() StringBasic::Capacity()const const {{ return returncapacity; capacity; }}

bool boolStringBasic::Full() StringBasic::Full()const const {{ return returnCount() Count()== ==capacity capacity--1; 1; }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

150


Validação de índice, posição, intervalo class classStringBasic: StringBasic:public publicClonable Clonable{{ Estas funções são muito usadas //... //... em precondições. Veja as virtual virtualbool boolValidIndex(int ValidIndex(intx) x)const; const; páginas seguintes. virtual virtualbool boolValidPosition(int ValidPosition(intx) x)const; const; virtual virtualbool boolValidRange(int ValidRange(intx, x,int inty) y)const; const; }; }; bool boolStringBasic::ValidIndex(int StringBasic::ValidIndex(intx) x)const const Um índice é válido se lá existir {{ return um carácter: return00<= <=xx&& &&xx<<Count(); 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: 2003-07-19

bool boolStringBasic::ValidPosition(int StringBasic::ValidPosition(intx) x)const const {{ return return00<= <=xx&& &&xx<= <=Count(); Count(); }} bool boolStringBasic::ValidRange(int StringBasic::ValidRange(intx, x,int inty) y)const const {{ return returnValidPosition(x) ValidPosition(x)&& &&ValidPosition(y ValidPosition(y++1) 1) && x <= y + 1; && x <= y + 1; }}

Curso de Programação com C++ © Pedro Guerreiro 2003

151

Acrescentando caracteres class Para acrescentar um carácter classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... no final de uma cadeia que não virtual void virtual voidPut(char Put(charc); c); // //pre pre!Full(); !Full(); está cheia, usamos Put. Se não virtual void Extend(char c); virtual void Extend(char c); pudermos garantir que a cadeia virtual virtualvoid voidPrecede(char Precede(charc); c); não está cheia, usamos virtual void Insert(char virtual void Insert(charc, c,int intx); x); // pre ValidPosition(x); Extend. Para acrescentar no // pre ValidPosition(x); }; }; início, usamos Precede. Para inserir no meio, usamos Insert: void voidStringBasic::Put(char StringBasic::Put(charc) c) Na função Put, sabemos que há {{ espaço para o novo carácter e int intxx==Count(); Count(); p[x] = c; p[x] = c; acrescentamos à vontade. De preferência, usamos p[x+1] = 0; }}

p[x+1] = 0; Put.

Na função Extend, fazemos a void voidStringBasic::Extend(char StringBasic::Extend(charc) c) {{ cadeia crescer, se for preciso, GrowTo(Count() + 2); GrowTo(Count() + 2); para garantir que há espaço para Put(c); Put(c); }} mais um carácter. Extend é menos eficiente, Curso claro. de Programação com C++ © Pedro Guerreiro 2003 2003-07-19 152


Os apontadores são arrays? Não. Observe a função Put:

void void StringBasic::Put(char StringBasic::Put(char c) c) {{ int int xx = = Count(); Count(); p[x] = c; p[x] = c; p[x+1] p[x+1] = = 0; 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.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

153

Inserindo caracteres Na verdade, Precede insere na posição zero: void voidStringBasic::Precede(char StringBasic::Precede(charc) c) {{ Insert(c, Insert(c,0); 0); }}

A função Insert é mais interessante. Observe: void voidStringBasic::Insert(char StringBasic::Insert(charc, c,int intx) x) {{ int intcount count==Count(); Count(); GrowTo(count GrowTo(count++2); 2); ::memmove(p+x+1, ::memmove(p+x+1,p+x, p+x,count+1-x); count+1-x); p[x] = c; p[x] = c; }}

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.

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). 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

154


Outros modificadores class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual // virtualvoid voidPutAt(char PutAt(charc, c,int intx); x); //pre preValidIndex(x); ValidIndex(x); virtual void Append(const StringBasic& other); virtual void Append(const StringBasic& other); virtual virtualvoid voidSelect(int Select(intstartPos, startPos,int intendPos); endPos); // //pre preValidRange(startPos, ValidRange(startPos,endPos); endPos); virtual void Insert(const StringBasic& other, int x); // virtual void Insert(const StringBasic& other, int x); //pre preValidPosition(x); ValidPosition(x); virtual virtualvoid voidReplace(int Replace(intstartPos, startPos,int intendPos, endPos,const constStringBasic& StringBasic&other); other); // pre ValidRange(startPos, // pre ValidRange(startPos,endPos) endPos) virtual void Erase(int startPos, int endPos); // virtual void Erase(int startPos, int endPos); //pre preValidRange(startPos, ValidRange(startPos,endPos); endPos); virtual // virtualvoid voidClear(); Clear(); //post postEmpty(); Empty(); }; };

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). 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

155

Acedendo aos caracteres um a um A função PutAt afecta o carácter que está na posição x com o valor c: void voidStringBasic::PutAt(char StringBasic::PutAt(charc, c,int intx) x) {{ p[x] p[x]==c; c; }}

A precondição (que indica que x é um índice válido) garante que esta operação vai correr bem.

Também há a função At, um selector, que dá o carácter que está na posição indicada: class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualconst constchar& char&At(int At(intx) x)const; const; // //pre preValidIndex(x); ValidIndex(x); }; }; Esta função retorna por referência constante, e não por valor, por razões const de compatibilidade com outras classes constchar& char&StringBasic::At(int StringBasic::At(intx) x)const const {{ que hão-de aparecer mais à frente. Se return p[x]; não fosse isso, retornaria por valor. return p[x]; }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

156


Operadores de indexação Para aceder aos caracteres individualmente, por vezes é mais prático usar os operadores de indexação: class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualconst constchar& char&operator operator[](int [](intx) x)const; const;// //pre preValidIndex(x); ValidIndex(x); virtual char& operator [](int x); // pre ValidIndex(x); virtual char& operator [](int x); // pre ValidIndex(x); }; };

Com isto, podemos escrever:

Em vez de:

void voidTestIndexOperators1() TestIndexOperators1() {{ mas::StringBasic mas::StringBasicss("lixboa"); ("lixboa"); s[2] s[2]=='s'; 's'; s.Extend(s[5]); s.Extend(s[5]); s.WriteLine(); s.WriteLine(); }}

void voidTestIndexOperators2() TestIndexOperators2() {{ mas::StringBasic mas::StringBasicss("lixboa"); ("lixboa"); s.PutAt('s', s.PutAt('s',2); 2); s.Extend(s.At(5)); s.Extend(s.At(5)); s.WriteLine(); s.WriteLine(); }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

157

Porquê dois operadores []? O primeiro é usado com objectos const. O segundo com objectos não const: virtual virtualconst constchar& char&operator operator[](int [](intx) x)const; const;// //pre preValidIndex(x); ValidIndex(x); virtual char& operator [](int x); // pre ValidIndex(x); virtual char& operator [](int x); // pre ValidIndex(x);

Não temos de nos preocupar em cada caso se o objecto é const ou não. Tendo programado os dois operadores, o compilador escolhe sozinho qual usar. Se esquecêssemos um deles, mais tarde arrepender-nos-íamos. Por exemplo, se não houvesse o operador const, isto estava errado: void voidTestIndexOperators1() TestIndexOperators1() {{ mas::StringBasic mas::StringBasicss("lixboa"); ("lixboa"); s[2] = 's'; error: l-value specifies s[2] = 's'; s.Extend(s[5]); const object s.Extend(s[5]); s.WriteLine(); s.WriteLine(); }}

2003-07-19

Por exemplo, se não houvesse o operador não const, isto estava errado:

void voidTestIndexOperators3() TestIndexOperators3() {{ const constmas::StringBasic mas::StringBasics("coimbra"); s("coimbra"); char c = s[2]; char c = s[2]; // error: binary '[' : no operator found //... ... }} which takes a left-hand operand of type 'const mas::StringBasic' (or there is no acceptable conversion) Curso de Programação com C++ © Pedro Guerreiro 2003

158


Programam-se igualmente O corpo dos dois operadores [] é idêntico: const char&StringBasic::operator StringBasic::operator[](int [](intx) x) constchar& char&StringBasic::operator StringBasic::operator[](int [](intx) x)const const char& {{ {{ return return returnp[x]; p[x]; returnp[x]; p[x]; }} }}

Também há uma função At não constante: class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualchar& char&At(int At(intx); x); // //pre preValidIndex(x); ValidIndex(x); }; };

char& char&StringBasic::At(int StringBasic::At(intx) x) {{ return returnp[x]; p[x]; }}

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).

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

159

Acrescentando outra cadeia

A função Append acrescenta o argumento, que é uma cadeia, ao objecto. Observe a programação: Primeiro o objecto cresce, se

void voidStringBasic::Append(const StringBasic::Append(constStringBasic& StringBasic&other) other) necessário, para acomodar os {{ novos caracteres. Estes são ifif(this != &other) acrescentados com a função (this != &other) {{ ::strcat, uma função de baixo GrowTo(this->Count() + other.Count() + 1); nível. GrowTo(this->Count() + other.Count() + 1); ::strcat(this->p, ::strcat(this->p,other.p); other.p); A instrução if serve para evitar problemas com a situação }} em que o argumento é o objecto: s.Append(s);. Neste else else Append(StringBasic(*this)); Append(StringBasic(*this)); 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.

Também há uma função Prepend que acrescenta no início: void voidStringBasic::Prepend(const StringBasic::Prepend(constStringBasic StringBasic&other) &other) {{ Acrescentar no início é inserir na Insert(other, 0); posição zero. Insert(other, 0); }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

160


Substituindo

Para 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 voidStringBasic::Replace(int StringBasic::Replace(intstartPos, startPos,int intendPos, endPos,const constStringBasic& StringBasic&other) other) {{ ifif(this (this!= !=&other) &other) {{ StringBasic StringBasictemp(*this, temp(*this,endPos+1, endPos+1,Count() Count()--1); 1); Função Head: ver página seguinte. Head(startPos); Head(startPos); GrowTo(Count() (endPos startPos + 1) + other.Count() GrowTo(Count() - (endPos - startPos + 1) + other.Count()++1); 1); Append(other); Append(other); Sempre que um dos argumentos de Append(temp); Append(temp); um modificador é do mesmo tipo }} que o objecto, temos de ver se há else else problema em que sejam o mesmo Replace(startPos, Replace(startPos,endPos, endPos,StringBasic(*this)); StringBasic(*this)); objecto. Aqui haveria, e por isso }} temos a instrução if a controlar, na função 2003-07-19 Curso de Programação com C++ © Pedrocomo Guerreiro 2003 Append. 161

Head e Tail A 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 classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualvoid voidHead(int Head(intn); n); // //pre prenn>= >=0; 0; virtual void Tail(int n); // pre n >= virtual void Tail(int n); // pre n >=0; 0; }; };

Head é muito simples: basta colocar o terminador na posição certa:

void voidStringBasic::Tail(int StringBasic::Tail(intn) n) {{ int intcount count==Count(); Count(); ifif(n (n<<count) count) ::memmove(p, ::memmove(p,pp++count count--n, n,n+1); n+1); }} Curso de Programação com C++ © Pedro Guerreiro 2003

void voidStringBasic::Head(int StringBasic::Head(intn) n) {{ ifif(n (n<<Count()) Count()) p[n] p[n]==0; 0; }}

2003-07-19

Tail “puxa” para o princípio os caracteres que ficam, usando ::memmove:

162


Inserindo, seleccionando, apagando

Inserir é um caso especial de substituir, em que a subcadeia a substituir é a cadeia vazia: void voidStringBasic::Insert(const StringBasic::Insert(constStringBasic& StringBasic&other, other,int intx) x) {{ Replace(x, Replace(x,x-1, x-1,other); other); }}

Para seleccionar, deita-se fora o que não interessa: void voidStringBasic::Select(int StringBasic::Select(intstartPos, startPos,int intendPos) endPos) {{ Head(endPos Head(endPos++1); 1); Erase(0, Erase(0,startPos startPos--1); 1); }}

Para apagar, veja bem: void voidStringBasic::Erase(int StringBasic::Erase(intstartPos, startPos,int intendPos) endPos) {{ ::memmove(p+startPos, ::memmove(p+startPos,p+endPos+1, p+endPos+1,Count() Count()--endPos); endPos); }} 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.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

163

Esvaziando Clear não limpa, esvazia! Esvaziar uma cadeia é simples: basta colocar o terminador na primeira posição: void void StringBasic::Clear() StringBasic::Clear() {{ *p Também se podia ter escrito p[0] = 0. Era *p = = 0; 0; a mesma coisa, mas assim é mais “castiço”. }} Esta é uma das minhas preferidas.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

164


Comparando A classe StringBasic dispõe dos seis operadores de comparação: class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualbool booloperator operator== ==(const (constStringBasic& StringBasic&other) other)const; const; virtual bool operator != (const StringBasic& other) const; virtual bool operator != (const StringBasic& other) const; virtual virtualbool booloperator operator<= <=(const (constStringBasic& StringBasic&other) other)const; const; virtual bool operator < (const StringBasic& other) const; virtual bool operator < (const StringBasic& other) const; virtual virtualbool booloperator operator>= >=(const (constStringBasic& StringBasic&other) other)const; 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 s1 <= s2 é o mesmo do que escrever s1.operator <= (s2) e analogamente para os outros operadores. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

165

Implementado as comparações (1) Recorremos basicamente à função ::strcmp: bool boolStringBasic::operator StringBasic::operator== ==(const (constStringBasic& StringBasic&other) other)const const {{ return return::strcmp(this->p, ::strcmp(this->p,other.p) other.p)== ==0; 0; }} bool boolStringBasic::operator StringBasic::operator<= <=(const (constStringBasic& StringBasic&other) other)const const {{ return return::strcmp(this->p, ::strcmp(this->p,other.p) other.p)<= <=0; 0; }} bool boolStringBasic::operator StringBasic::operator<<(const (constStringBasic& StringBasic&other) other)const const {{ return return::strcmp(this->p, ::strcmp(this->p,other.p) other.p)<<0; 0; }}

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 indeterminado 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

166


Implementado as comparações (2) As outras três programam-se por negação das primeiras: bool boolStringBasic::operator StringBasic::operator!= !=(const (constStringBasic& StringBasic&other) other)const const {{ return return!!operator operator== ==(other); (other); }} bool boolStringBasic::operator StringBasic::operator>= >=(const (constStringBasic& StringBasic&other) other)const const {{ return return!!operator operator<<(other); (other); }} bool boolStringBasic::operator StringBasic::operator>>(const (constStringBasic& StringBasic&other) other)const const {{ return return!!operator operator<= <=(other); (other); }}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

167

Buscando caracteres e subcadeias A 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 classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualbool boolHas(char Has(charc) c)const; const; virtual bool HasString(const virtual bool HasString(constStringBasic& StringBasic&other) other)const; const; }; };

Programam-se directamente com as funções ::strchr e ::strstr: A função ::strchr devolve um apontador (de tipo bool boolStringBasic::Has(char StringBasic::Has(charc) c)const const 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 return return::strchr(p, ::strchr(p,c) c)!= !=0; 0; (isto é, se não houver antes de aparecer o terminador.) }} bool boolStringBasic::HasString(const StringBasic::HasString(constStringBasic& StringBasic&other) other)const const A função ::strstr é {{ análoga, mas procura return cadeias e não return::strstr(this->p, ::strstr(this->p,other.p) other.p)!= !=0; 0; caracteres sozinhos. }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 168


Posição de caracteres e de subcadeias As 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 classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualint intPosition(char Position(charc) c)const; const; virtual int Position(char c, virtual int Position(char c,int intstart) start)const; const; // //pre preValidPosition(start); ValidPosition(start); virtual int Position(const StringBasic& other, int virtual int Position(const StringBasic& other, intstart) start)const; const; // pre ValidPosition(start); // pre ValidPosition(start); virtual virtualint intLastPosition(char LastPosition(charc) c)const; const; virtual virtualint intLastPosition(char LastPosition(charc, c,int intstart) start)const; const; // //pre prestart start<<Count(); Count(); virtual int LastPosition(const StringBasic& other, int start) virtual int LastPosition(const StringBasic& other, int start)const; const; // // pre prestart start<<Count(); Count(); }; }; int intStringBasic::Position(char StringBasic::Position(charc, c,int intstart) start)const const Eis uma delas, como {{ const exemplo: constchar* char*qq==::strchr(p+start, ::strchr(p+start,c); c); return static_cast<int>(q != 0 ? q return static_cast<int>(q != 0 ? q--pp::-1); -1); }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

169

Aritmética de apontadores A função Position exibe dois exemplos de aritmética de apontadores: Se p é um apontador int intStringBasic::Position(char StringBasic::Position(charc, c,int intstart) start)const const char* e n é um inteiro int, {{ a expressão p+n é um const constchar* char*qq==::strchr(p+start, ::strchr(p+start,c); c); apontador char* que return returnstatic_cast<int>(q static_cast<int>(q!= !=00??qq--pp::-1); -1); aponta n posições à direita }} Começamos a procurar start de p. posições à direita de p, isto é, começamos a procurar na posição start da cadeia.

int intStringBasic::Position(char StringBasic::Position(charc, c,int intstart) start)const const {{ const constchar* char*qq==::strchr(p+start, ::strchr(p+start,c); c); return static_cast<int>(q != 0 ? q return static_cast<int>(q != 0 ? q--pp::-1); -1); }} 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.) 2003-07-19

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. Logo: q – p == n se e só se p + n == q.

Curso de Programação com C++ © Pedro Guerreiro 2003

170


Contando caracteres Podemos 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 classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualint intCountIf(const CountIf(constStringBasic& StringBasic&other) other)const; const; virtual int CountIfNot(const StringBasic& virtual int CountIfNot(const StringBasic&other) other)const; const; }; };

Por exemplo, a expressão s.CountIf("AEIOUaeiou") representa o número de vogais na cadeia s.

int intStringBasic::CountIf(const StringBasic::CountIf(constStringBasic& StringBasic&other) other)const const {{ int Esta função é pouco eficiente, pois compara cada intresult result==0; 0; for (int i=0; p[i] != 0; i++) carácter do objecto com cada carácter do for (int i=0; p[i] != 0; i++) result += other.Has(p[i]); argumento. É para usar casualmente em situações result += other.Has(p[i]); return result; não muito exigentes. return result; }} int intStringBasic::CountIfNot(const StringBasic::CountIfNot(constStringBasic& StringBasic&other) other)const const {{ return returnCount() Count()--CountIf(other); CountIf(other); }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

171

Posições condicionais

Há uma série de funções PositionIf... que calculam a posição de caracteres condicionalmente: class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualint intPositionIf(const PositionIf(constStringBasic& StringBasic&other) other)const; const; virtual int PositionIfNot(const StringBasic& other) virtual int PositionIfNot(const StringBasic& other)const; const; virtual virtualint intLastPositionIf(const LastPositionIf(constStringBasic& StringBasic&other) other)const; const; virtual virtualint intLastPositionIfNot(const LastPositionIfNot(constStringBasic& StringBasic&other) other)const; const; virtual int PositionIf(const StringBasic& other, int start) // virtual int PositionIf(const StringBasic& other, int start)const; const; // pre prestart start>= >=0; 0; virtual int PositionIfNot(const StringBasic& other, int start) const; // pre start >= virtual int PositionIfNot(const StringBasic& other, int start) const; // pre start >=0; 0; virtual virtualint intLastPositionIf(const LastPositionIf(constStringBasic& StringBasic&other, other,int intstart) start)const; const; // // pre prestart start<<Count(); Count(); virtual int LastPositionIfNot(const StringBasic& other, int start) const; virtual int LastPositionIfNot(const StringBasic& other, int start) const; // // pre prestart start<<Count(); Count(); }; }; int intStringBasic::PositionIf(const StringBasic::PositionIf(constStringBasic& StringBasic&other, other,int intstart) start)const const {{ Eis a definifor for(int (inti i==start; start;p[i] p[i]!= !=0; 0;i++) i++) ifif(other.Has(p[i])) ção de duas (other.Has(p[i])) return returni;i; int delas, sobreintStringBasic::PositionIf(const StringBasic::PositionIf(constStringBasic& StringBasic&other) other)const const return -1; {{ return -1; carregadas: }} return returnPositionIf(other, PositionIf(other,0); 0); 2003-07-19

}} Curso de Programação com C++ © Pedro Guerreiro 2003

172


Podando e aparando As funções Prune... eliminam caracteres, por valor. As funções Trim... eliminam espaços no início e no fim: class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualvoid voidPrune(char Prune(charc); c); virtual void PruneLast(char virtual void PruneLast(charc); c); virtual virtualvoid voidPruneAll(char PruneAll(charc); c);

Eis as três Trim:

virtual virtualvoid voidPruneIf(const PruneIf(constStringBasic& StringBasic&other); other); virtual void PruneIfNot(const virtual void PruneIfNot(constStringBasic& StringBasic&other); other); virtual virtualvoid voidTrim(); Trim(); virtual void virtual voidTrimLeft(); TrimLeft(); virtual virtualvoid voidTrimRight(); TrimRight(); }; }; void voidStringBasic::Prune(char StringBasic::Prune(charc) c) { Eis a { int intxx==Position(c); Position(c); definição da ifif(x != -1) (x != -1) função Prune: RemoveAt(x); RemoveAt(x); }}

2003-07-19

void voidStringBasic::TrimLeft() StringBasic::TrimLeft() {{ int intxx==PositionIfNot(" PositionIfNot(""); "); ifif(x == (x ==-1) -1) Clear(); Clear(); else else Erase(0, Erase(0,x-1); x-1); }} void voidStringBasic::TrimRight() StringBasic::TrimRight() {{ Head(LastPositionIfNot(" Head(LastPositionIfNot("")")++1); 1); }} void voidStringBasic::Trim() StringBasic::Trim() {{ TrimRight(); TrimRight(); TrimLeft(); TrimLeft(); }}

Curso de Programação com C++ © Pedro Guerreiro 2003

173

Funções várias

Isto nunca mais acaba:

SendToBack sem argumentos “manda” o primeiro carácter para class o fim. Com argumentos, manda classStringBasic: StringBasic:public publicClonable Clonable{{ //... os n primeiros caracteres para o //... virtual virtualbool boolStartsBy(const StartsBy(constStringBasic& StringBasic&other) other)const; const; fim. Analogamente para virtual virtualbool boolEndsBy(const EndsBy(constStringBasic& StringBasic&other) other)const; const; BringToFront. As outras são intuitivas. virtual virtualvoid voidReverse(); Reverse(); virtual virtualvoid voidSwap(int Swap(intx, x,int inty); y); // //pre preValidIndex(x) ValidIndex(x)&& &&ValidIndex(y); ValidIndex(y); virtual void SendToBack(); virtual void SendToBack(); A função Reverse use um virtual virtualvoid voidSendToBack(int SendToBack(intn); n);// //pre prenn>= >=0; 0; ciclo for com duas variáveis virtual void BringToFront(); virtual void BringToFront(); de controlo: virtual void BringToFront(int n); // pre n >= 0; virtual void BringToFront(int n); // pre n >= 0; virtual virtualvoid voidToLowercase(); ToLowercase(); virtual void virtual voidToUppercase(); ToUppercase(); }; };

void voidStringBasic::Reverse() StringBasic::Reverse() {{ for for(int (inti=0, i=0,j=Count() j=Count()--1; 1;i i<<j;j;i++, i++,j--) j--) Swap(i, j); Swap(i, j); }}

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. Curso de Programação com C++ © Pedro Guerreiro 2003 174

bool boolStringBasic::StartsBy(const StringBasic::StartsBy(constStringBasic& StringBasic&other) other)const const {{ return return::strncmp(this->p, ::strncmp(this->p,other.p, other.p,other.Count()) other.Count())== ==0; 0; }}

2003-07-19


Mais funções várias

Ainda há mais? Sim:

class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualvoid voidGrowTo(int GrowTo(intn); n); virtual void Adjust(); virtual void Adjust(); // //post postFull(); Full(); virtual virtualint intSum() Sum()const; const; virtual int Hash() virtual int Hash()const; const;

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).

virtual virtualvoid voidMap(const Map(constStringBasic& StringBasic&from, from,const constStringBasic& StringBasic&to); to); //pre from.Count() <= to.Count(); //pre from.Count() <= to.Count(); }; };

A função Map transforma cada ocorrência de um carácter do primeiro argumento no correspondente carácter do segundo argumento: void voidStringBasic::Map(const StringBasic::Map(constStringBasic& StringBasic&from, from,const constStringBasic& StringBasic&to) to) {{ int intx; x; for(int Exemplos disto mais à for(inti=0; i=0;p[i] p[i]!= !=0; 0;i++) i++) ifif((x = from.Position(p[i])) != -1) frente. ((x = from.Position(p[i])) != -1) p[i] = to[x]; p[i] = to[x]; }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003

175

Ordenação dos caracteres Para ordenar os caracteres, temos a função Sort; para eliminar os duplicado contíguos, temos a função Unique. class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualbool boolIsSorted() IsSorted()const; const; virtual void Sort(); virtual void Sort(); // //post postIsSorted(); IsSorted(); virtual virtualvoid voidUnique(); Unique(); }; };

A função Unique usa um ciclo duplo. Veja com atenção:

void voidStringBasic::Unique() StringBasic::Unique() {{ int A função Sort usa o quicksort: intjj==0; 0; int inti i==0; 0; void while voidStringBasic::Sort() StringBasic::Sort() while(p[i] (p[i]!= !=0) 0) {{ {{ ifif(!Empty()) char (!Empty()) charcc==p[i]; p[i]; Quicksort(0, Count() 1); do Quicksort(0, Count() - 1); do }} i++; i++; while while(p[i] (p[i]== ==c); c); p[j++] = c; Note bem: função Unique não elimina os duplicados p[j++] = c; }} todos, mas apenas os duplicados contíguos. Se a cadeia p[j] estiver ordenada, todos os duplicados iguais estão p[j]==0; 0; }} contíguos e, neste caso, são todos eliminados.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

176


Quicksort de caracteres Eis o quicksort para StringBasics: void voidStringBasic::Quicksort(int StringBasic::Quicksort(intlowerBound, lowerBound,int intupperBound) upperBound) {{ int inti i==lowerBound; lowerBound; Os valores numéricos dos caracteres em C++ podem ir int j = upperBound; int j = upperBound; de –128 a 127 ou de zero a 255, consoante as char implementações. Para evitar surpresas, convertemos charpivot pivot==p[(i+j)/2]; p[(i+j)/2]; do para unsigned char antes de comparar. do {{ while while(static_cast<unsigned (static_cast<unsignedchar>(p[i]) char>(p[i])<<static_cast<unsigned static_cast<unsignedchar>(pivot)) char>(pivot)) i++; i++; while while(static_cast<unsigned (static_cast<unsignedchar>(p[j]) char>(p[j])>>static_cast<unsigned static_cast<unsignedchar>(pivot)) char>(pivot)) j--; j--; ifif(i(i<= <=j)j) Swap(i++, Swap(i++,j--); j--); }}while (i <= j); while (i <= j); ifif(lowerBound (lowerBound<<j)j) Nunca é demais Quicksort(lowerBound, Quicksort(lowerBound,j); j); recordar o quicksort. ifif(i(i<<upperBound) upperBound) Quicksort(i, Quicksort(i,upperBound); upperBound); }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

177

Cadeias como conjuntos de caracteres Em C++, por vezes usamos cadeias de caracteres para representar conjuntos de caracteres. Atrás já vimos a função Has. class classStringBasic: StringBasic:public publicClonable Clonable{{ Eis outras, //... //... inspiradas na teoria virtual virtualvoid voidIntersect(const Intersect(constStringBasic& StringBasic&other); other); virtual void Subtract(const StringBasic& virtual void Subtract(const StringBasic&other); other); dos conjuntos: virtual virtualbool boolContains(const Contains(constStringBasic& StringBasic&other) other)const; const; }; };

void voidStringBasic::Intersect(const StringBasic::Intersect(constStringBasic& StringBasic&other) other) {{ StringBasic StringBasicresult(Min(Count(), result(Min(Count(),other.Count()) other.Count())++1); 1); int inti i==0; 0; int intj j==0; 0; int intkk==0; 0; while while(p[i] (p[i]!= !=00&& &&other.p[j] other.p[j]!= !=0) 0) ifif(p[i] == other.p[j]) (p[i] == other.p[j]) {{ result.p[k++] result.p[k++]==p[i]; p[i]; i++, i++,j++; j++; }} else elseifif(p[i] (p[i]<<other.p[j]) other.p[j]) i++; i++; Observe a else else j++; técnica do j++; result[k] = 0; SwapOut. result[k] = 0; SwapOut(result); SwapOut(result); Curso de Programação}} com C++ © Pedro Guerreiro 2003 178

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. 2003-07-19


Conversões numéricas Se 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):

AsInt dá para qualquer class base, de 2 a 36. classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualint intAsInt(int AsInt(intbase base==10) 10)const; const; // //pre pre22<= <=base base&& &&base base<= <=36; 36; virtual double AsDouble() const; virtual double AsDouble() const; virtual virtualbool boolIsInt(int IsInt(intbase base==10) 10)const; const; // //pre pre22<= <=base base&& &&base base<= <=36; 36; virtual bool IsDouble() const; As cadeias inteiras podem ter sinal mais ou virtual bool IsDouble() const; menos. As cadeias reais podem ter sinal, ponto }; }; decimal e podem estar na notação científica.

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

179

Excepções Observe a função AsInt: int intStringBasic::AsInt(int StringBasic::AsInt(intbase) base)const const {{ Quem realmente faz o int trabalho de conversão é int result; result; char *endp; a função ::strtol. (Veja char *endp; a documentação.) errno = 0; errno = 0; result = ::strtol(p, &endp, base); result = ::strtol(p, &endp, base); ifif(!*p (!*p|| ||errno errno|| ||*endp) *endp) {{ errno errno==0; 0; StringBasic StringBasicmessage("\"", message("\"",*this *this++"\" "\"isisnot notaalegal legalint intvalue."); value."); throw message; throw message; }} Aqui a excepção é uma StringBasic que contém return result; uma mensagem informativa construída à medida. return result; }}

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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

180


Tratando excepções A 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: Repare no try-block seguido bool boolStringBasic::IsInt(int StringBasic::IsInt(intbase) base)const const do tratador de excepções. {{ bool Quando há uma excepção no boolresult result==true; true; try { try { try-block para a qual existe um AsInt(base); AsInt(base); tratador, o tratador intervém, }}catch catch(const (constmas::StringBasic&) mas::StringBasic&){{ substituindo o resto do bloco. result result==false; false; Se não houver tratador, a }} return result; excepção propaga-se para o return result; }} chamador.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

181

Ilustração com função de teste Eis uma função de teste que ilustra o funcionamento das funções AsInt e IsInt e o tratamento de excepções: void voidTestConversionToInt() TestConversionToInt() {{ for(;;) for(;;) try try{{ mas::StringBasic mas::StringBasics; s; s.Accept("Uma s.Accept("Umacadeia cadeiadecimal: decimal:"); "); ifif(!std::cin) (!std::cin) break; break; ifif(s.IsInt()) (s.IsInt()) std::cout std::cout<< <<"Yes, "Yes,this thisisisaavalid validinteger." integer."<< <<std::endl; std::endl; else else std::cout std::cout<< <<"No, "No,this thisisisNOT NOTaavalid validinteger." integer."<< <<std::endl; std::endl; int x = s.AsInt(); int x = s.AsInt(); std::cout std::cout<< <<xx<< <<std::endl; std::endl; Note bem: as excepções não }}catch (const mas::StringBasic& catch (const mas::StringBasic&e) e){{ são a cura de todos os males. std::cout std::cout<< <<"Exception: "Exception:""<< <<ee<< <<std::endl; std::endl; Devem ser usadas com muita }} moderação. Se não, acabam }} por introduzir mais erros do que os que pretende apanhar.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

182


Caso da conversão para double É parecido: double doubleStringBasic::AsDouble() StringBasic::AsDouble()const const {{ Agora é a função double ::strtod. double result; result; char *endp; char *endp; errno errno==0; 0; result result==::strtod(p, ::strtod(p,&endp); &endp); ifif(!*p || errno || *endp) (!*p || errno || *endp) {{ errno errno==0; 0; StringBasic StringBasicmessage("\"", message("\"",*this *this++"\" "\"isisnot notaalegal legaldouble doublevalue."); value."); throw message; throw message; bool }} boolStringBasic::IsDouble() StringBasic::IsDouble()const const { return result; { return result; bool }} boolresult result==true; true; try try{{ AsDouble(); AsDouble(); Nesta função, tal como }}catch catch(const (constmas::StringBasic&) mas::StringBasic&){{ na outra, só queremos result result==false; false; saber se há excepção ou }} não. return returnresult; result; } 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 }

183

De número para cadeia

AsInt e AsDouble são selectores. Para converter um número para StringBasic, precisamos de pseudo-construtores estáticos: class classStringBasic: StringBasic:public publicClonable Clonable{{ Na função Numeral, podemos //... //... escolher a base. Por defeito, é 10. public: public:// //static staticpseudo-constructors pseudo-constructors static staticStringBasic StringBasicNumeral(int Numeral(intn, n,int intbase base==10); 10); // //pre pre22<= <=base base&& &&base base<= <=36; 36; static StringBasic Fixed(double x, int precision = 6); static StringBasic Fixed(double x, int precision = 6); static staticStringBasic StringBasicScientific(double Scientific(doublex, x,int intprecision precision==6); 6); }; void TestStaticPseudoConstructors() }; void TestStaticPseudoConstructors()

{{ for(;;) for(;;) {{ double doublex; x; std::cout std::cout<< <<"um "umnumero: numero:";"; std::cin std::cin>> >>x; x; ifif(!std::cin) (!std::cin) break; break; int intnn==static_cast<int>(x); static_cast<int>(x); mas::StringBasic::Numeral(n).WriteLine(); mas::StringBasic::Numeral(n).WriteLine(); mas::StringBasic::Numeral(n, mas::StringBasic::Numeral(n,2).WriteLine(); 2).WriteLine(); mas::StringBasic::Numeral(n, mas::StringBasic::Numeral(n,5).WriteLine(); 5).WriteLine(); Observe que o resultado mas::StringBasic::Fixed(x).WriteLine(); mas::StringBasic::Fixed(x).WriteLine(); das funções é um objecto mas::StringBasic::Fixed(x, mas::StringBasic::Fixed(x,2).WriteLine(); 2).WriteLine(); de tipo StringBasic. mas::StringBasic::Scientific(x).WriteLine(); mas::StringBasic::Scientific(x).WriteLine(); mas::StringBasic::Scientific(x, mas::StringBasic::Scientific(x,4).WriteLine(); 4).WriteLine(); }} 2003-07-19 Curso de }}Programação com C++ © Pedro Guerreiro 2003

Eis uma função de teste, que mostra como se usa:

184


Lendo e escrevendo Para escrever StringBasics, temos Write, WriteLine e <<. Para ler, temos Read e >>. Para ler interactivamente temos Accept: class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... virtual virtualvoid voidWrite(std::ostream& Write(std::ostream&output output==std::cout) std::cout)const; const; virtual virtualvoid voidWriteLine(std::ostream& WriteLine(std::ostream&output= output=std::cout) std::cout)const; const; friend friendstd::ostream& std::ostream&operator operator<< <<(std::ostream& (std::ostream&output, output,const constStringBasic& StringBasic&s); s); virtual virtualvoid voidRead(std::istream& Read(std::istream&input input==std::cin); std::cin); friend std::istream& operator >> friend std::istream& operator >>(std::istream& (std::istream&input, input,StringBasic& StringBasic&s); s); virtual void Accept(const StringBasic& prompt); virtual void Accept(const StringBasic& prompt); Note bem: Read e >> lêem até }; }; ao fim da linha.

Eis duas das funções, que são como de costume: void voidStringBasic::Write(std::ostream& StringBasic::Write(std::ostream&output) output)const const {{ std::ostream& operator << (std::ostream& std::ostream& operator << (std::ostream&output, output,const constStringBasic& StringBasic&s) s) output output<< <<p; p; { { }} s.Write(output); s.Write(output); return returnoutput; output; }} 2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003

185

Constantes públicas Eis cinco constantes prontas a usar: class classStringBasic: StringBasic:public publicClonable Clonable{{ //... //... public: public:// //static staticconstants constants static const StringBasic static const StringBasicuppercaseLetters; uppercaseLetters; static const StringBasic lowercaseLetters; static const StringBasic lowercaseLetters; static staticconst constStringBasic StringBasicletters; letters; static const StringBasic digits; Temos aqui membros de dados static const StringBasic digits; públicos, o que só é perdoável static staticconst constStringBasic StringBasicnull; null; porque se trata de constantes. }; };

São inicializadas assim: const constStringBasic StringBasicStringBasic::uppercaseLetters('A', StringBasic::uppercaseLetters('A','Z'); 'Z'); const StringBasic StringBasic::lowercaseLetters('a', 'z'); const StringBasic StringBasic::lowercaseLetters('a', 'z'); const constStringBasic StringBasicStringBasic::letters(uppercaseLetters, StringBasic::letters(uppercaseLetters,lowercaseLetters); lowercaseLetters); const StringBasic StringBasic::digits('0', '9'); const StringBasic StringBasic::digits('0', '9'); const constStringBasic StringBasicStringBasic::null; StringBasic::null; 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

186


O Gene Dominante Como 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. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

187

Estratégia Associamos 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.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

188


Classe Gene Um gene é uma StringBasic, com mais uma operação para dar o número (o tal da base 5) e a operação inversa: namespace namespacemas mas{{ class classGene: Gene:public publicStringBasic StringBasic{{ private: private: public: public: Gene(); Gene(); Gene(const Gene(constStringBasic& StringBasic&s); s); virtual int AsInt() const; virtual int AsInt() const; static staticGene GeneFromInt(int FromInt(intx); x); }; }; }}

2003-07-19

Gene::Gene() Gene::Gene() {{ }} Gene::Gene(const Gene::Gene(constStringBasic& StringBasic&s): s): StringBasic(s) StringBasic(s) {{ }} int intGene::AsInt() Gene::AsInt()const const {{ StringBasic StringBasictemp(*this); temp(*this); temp.Map("ATGC", temp.Map("ATGC","1234"); "1234"); return returntemp.StringBasic::AsInt(5); temp.StringBasic::AsInt(5); }}

Gene GeneGene::FromInt(int Gene::FromInt(intx) x) {{ Gene Generesult result==Numeral(x, Numeral(x,5); 5); result.Map("1234", result.Map("1234","ATGC"); "ATGC"); return returnresult; result; } Curso de Programação com C++ } © Pedro Guerreiro 2003

189

Classe Genes Temos 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 classGenes Genes{{ private: private: std::vector<int> std::vector<int>count; count; std::vector<std::pair<int, std::vector<std::pair<int,int> int>>>frequencies; frequencies; public: public: virtual virtual~Genes(); ~Genes(); virtual void virtual voidRead(std::istream& Read(std::istream&input); input); virtual int Count(const Gene& g) virtual int Count(const Gene& g)const; const; virtual std::list<Gene> Top(int n) virtual std::list<Gene> Top(int n)const; const; virtual std::list<Gene> ThoseMoreFrequentThan(int virtual std::list<Gene> ThoseMoreFrequentThan(intx) x)const; const; }; }; 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

190


Lendo genes e contando-os Ao ler contamos, usando o vector count. Depois preenchemos o vector frequencies e ordenamo-lo: void voidGenes::Read(std::istream& Genes::Read(std::istream&input) input) {{ int Isto não é lá muito ortodoxo, mas é prático. intnn==0; 0; (Deveríamos usar uma constante, ...) count.clear(); count.clear(); count.resize(5*5*5*5*5*5*5*5); count.resize(5*5*5*5*5*5*5*5); Repare na técnica de leitura. A variável s é de tipo std::string std::strings; s; std::string (e não de tipo StringBasic). Assim, cada while while(input (input>> >>s) s) leitura termina num espaço ou fim de linha e os {{ espaços são ignorados, sem mais esforço, como count[Gene(s).AsInt()]++; convém. count[Gene(s).AsInt()]++; n++; n++; O primeiro elemento para é a frequência, o segundo é }} o gene. Assim, ao ordenar, o critério principal é a frequencies.clear(); frequencies.clear(); frequência. frequencies.reserve(n); frequencies.reserve(n); for for(int (inti i==0; 0;i i<<static_cast<int>(count.size()); static_cast<int>(count.size());i++) i++) ifif(count[i] > 0) (count[i] > 0) A ordenação é crescente. frequencies.push_back(std::make_pair(count[i], frequencies.push_back(std::make_pair(count[i],i)); i)); Os mais frequentes ficam std::sort(frequencies.begin(), std::sort(frequencies.begin(),frequencies.end()); frequencies.end()); no fim. }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

191

Os mais frequentes Os n mais frequentes: std::list<Gene> std::list<Gene>Genes::Top(int Genes::Top(intn) n)const const {{ std::list<Gene> std::list<Gene>result; result; for for(int (inti i==0, 0,jj==static_cast<int>(frequencies.size()) static_cast<int>(frequencies.size())--1; 1;i i<<n; n;i++, i++,j--) j--) result.push_back(Gene::FromInt(frequencies[j].second)); result.push_back(Gene::FromInt(frequencies[j].second)); return returnresult; result; }}

Os que aparecem pelo menos x vezes: std::list<Gene> std::list<Gene>Genes::ThoseMoreFrequentThan(int Genes::ThoseMoreFrequentThan(intx) x)const const {{ std::list<Gene> std::list<Gene>result; result; int j = static_cast<int>(frequencies.size()) int j = static_cast<int>(frequencies.size())--1; 1; while while(j(j>= >=00&& &&frequencies[j].first frequencies[j].first>= >=x) x) result.push_back(Gene::FromInt(frequencies[j--].second)); result.push_back(Gene::FromInt(frequencies[j--].second)); return returnresult; result; }} 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

192


Função de teste para os genes Eis a função de teste que resolve o problema: void voidTestDominantGene(std::istream& TestDominantGene(std::istream&input, input,std::ostream& std::ostream&output) output) {{ mas::Genes mas::Genesg; g; g.Read(input); g.Read(input);

mas::WriteLine(g.ThoseMoreFrequentThan(g.Count(g.Top(1).front())), mas::WriteLine(g.ThoseMoreFrequentThan(g.Count(g.Top(1).front())),""",",output); output);

}}

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: int intGenes::Count(const Genes::Count(constGene& Gene&g) g)const const {{ Claro que para calcular apenas os de frequência máxima não era preciso ordenar. Bastava achar a return returncount[g.AsInt()]; count[g.AsInt()]; frequência máxima numa passagem e depois todos os }} dessa frequência noutra passagem. 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

193

Solução com STL puro

A 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: Um mapa é um contentor associativo: associa uma class classGenesMap GenesMap{{ chave a um valor. Neste caso, a chave é uma private: std::string e o valor é um int. Neste caso, o valor private: std::map<std::string, int> count; std::map<std::string, int> count; representa o número de ocorrências na chave. std::vector<std::pair<int, std::vector<std::pair<int,std::string> std::string>>>frequencies; frequencies; public: public: virtual virtual~GenesMap(); ~GenesMap(); virtual void virtual voidRead(std::istream& Read(std::istream&input); input); Estas três virtual int Count(const std::string& g) const; funções agora virtual int Count(const std::string& g) const; usam o tipo virtual std::list<std::string> Top(int n) const; virtual std::list<std::string> Top(int n) const; std::string, em virtual virtualstd::list<std::string> std::list<std::string>ThoseMoreFrequentThan(int ThoseMoreFrequentThan(intx) x)const; const; vez do tipo Gene. }; }; 2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

194


Veja bem:

Contando com o mapa

void voidGenesMap::Read(std::istream& GenesMap::Read(std::istream&input) input) {{ Perceba a expressão count[s]: 1) se não existir no mapa um elemento com chave s, é count.clear(); count.clear(); criado um, com valor zero, e a expressão representa um referência para o valor (que vale zero) std::string std::strings; s; 2) se existir, a expressão representa a referência para while while(input (input>> >>s) s) o valor associado. count[s]++; count[s]++; frequencies.clear(); frequencies.clear();

Logo, a expressão count[s]++, “conta” mais uma ocorrência de s.

frequencies.reserve(count.size()); frequencies.reserve(count.size()); for for(std::map<std::string, (std::map<std::string,int>::iterator int>::iteratori i==count.begin(); count.begin();i i!= !=count.end(); count.end();i++) i++) frequencies.push_back(std::make_pair(i->second, frequencies.push_back(std::make_pair(i->second,i->first)); i->first)); std::sort(frequencies.begin(), frequencies.end()); std::sort(frequencies.begin(), frequencies.end()); O iterador de mapas dá }} um par chave-valor de cada vez.

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

195

Os mais frequentes, de novo

É muito parecido, mas observe:

Temos de usar o const_cast aqui porque a função Count é int GenesMap::Count(const std::string& g) const const e o operador [] da classe int GenesMap::Count(const std::string& g) const std::map<K, T> não é const. {{ Repare na técnica. return returnconst_cast<GenesMap*>(this)->count[g]; const_cast<GenesMap*>(this)->count[g]; }} Dizemos que o const_cast remove a De facto, As outras são ainda mais simples do que antes: “constância”. o que estamos a fazer é std::list<std::string> a aceder ao mapa std::list<std::string>GenesMap::Top(int GenesMap::Top(intn) n)const const {{ através de um std::list<std::string> apontador não std::list<std::string>result; result; for for(int (inti i==0, 0,j j==static_cast<int>(frequencies.size()) static_cast<int>(frequencies.size())--1; 1;i i<<n; n;i++, i++,j--) j--) constante para o result.push_back(frequencies[j].second); objecto da função. Esse result.push_back(frequencies[j].second); return apontador é obtido returnresult; result; }} “ignorando” a constância do std::list<std::string> apontador this. Note std::list<std::string>GenesMap::ThoseMoreFrequentThan(int GenesMap::ThoseMoreFrequentThan(intx) x)const const {{ bem: na funções const, std::list<std::string> o apontador this é um std::list<std::string>result; result; int apontador const; nas intj j==static_cast<int>(frequencies.size()) static_cast<int>(frequencies.size())--1; 1; while funções não const, o while(j(j>= >=00&& &&frequencies[j].first frequencies[j].first>= >=x) x) result.push_back(frequencies[j--].second); apontador this é um result.push_back(frequencies[j--].second); return returnresult; result; apontador não const.

}}

2003-07-19

Curso de Programação com C++ © Pedro Guerreiro 2003

196

dasdfaqwerdf  
dasdfaqwerdf  

asdawxcaw f we vsweg

Advertisement