You are on page 1of 98

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, input-
output 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: class
classPoint
Point{{
Membros de dados
private:
private:
double
doublex;x;
Isto é a declaração double
doubley;y; Membros funcionais
public:
public:
da classe Point.
Point();
Point();
Point(double
Point(doublex, x,double
doubley);
y);
Point(const
Point(const Point&other);
Point& other);
virtual
virtual~Point();
~Point(); Regra: os membros de
dados são privados; os
virtual
virtualvoid
voidTranslate(double
Translate(doubledx,
dx,double
doubledy);
dy); membros funcionais são
virtual
virtual void Scale(double fx, doublefy);
void Scale(double fx, double fy);
virtual públicos (quase sempre.)
virtualvoid
voidRotate(double
Rotate(doubleangle);
angle);
virtual
virtual doubleDistanceTo(const
double DistanceTo(constPoint&
Point&other)
other)const;
const;
};
};
2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 6
Tipos básicos
Os tipos numéricos (int e double) têm os
ƒ Números inteiros: tipo int. 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
ƒ Números reais: tipo double. operador % para o resto da divisão inteira.

ƒ Booleanos: tipo bool. 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 Um ponto
Pointp1;
p1;
Point p2;
Point p2; 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); Isto é apenas um
double x = p2.DistanceTo(p1);
double x = p2.DistanceTo(p1); exemplo parvo. Não é
a[0].Rotate(1.57079632679489661923); parte de nenhum
a[0].Rotate(1.57079632679489661923);
programa significativo.
a[3].Scale(-1.0,
a[3].Scale(-1.0,1.0);
1.0);

2003-07-19 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 é o objecto, 2.0 e –3-25 são os argumentos. O


p1.Translate(2.0,
p1.Translate(2.0, -3.25);
-3.25); 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] é o objecto, -1.0 e 1.0 são os argumentos. O
a[3].Scale(-1.0,
a[3].Scale(-1.0, 1.0);
1.0); 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
class
classPoint
Point{{
Há quatro private:
private:
qualidades de double
doublex;
double
x;
doubley;y; Construtores
funções membro: public:
public:
Point();
Point();
Point(double
Point(doublex,x,double
doubley);
y);
Point(const
Point(const Point&other);
Point& other);
• Construtores virtual
virtual~Point();
~Point();
Destrutor

• Destrutor virtual
virtualdouble
virtual
doubleX()
X()const;
const; Selectores
virtual double Y()const;
double Y() const;
• Selectores virtual
virtualvoid
voidTranslate(double
Translate(doubledx,
dx,double
doubledy);
dy);
virtual
virtual void Scale(double fx, doublefy);
void Scale(double fx, double fy); Modificadores
• Modificadores 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 Mais selectores
virtual double Modulus()const;
double Modulus() const;
virtual
virtualvoid
voidWrite()
Write()const;
const;
virtual
virtual void WriteLine()const;
void WriteLine() 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); Este é o construtor elementar: inicializa com x, y.

Point(const
Point(const Point&
Point& other);
other); Este é o construtor de cópia: inicializa com os valores
dos membros de dados do argumento other.
//
// ...
...
Point Inicialização por defeito.
Exemplo: Pointp;
p;
Point
Pointq(3.0,
q(3.0,4.0);
4.0); Inicialização elementar.
Point
Pointr(q);
r(q); Inicialização por cópia.
Point
Pointz[32];
z[32]; Os 32 elementos do quadro z são inicializados por defeito.
2003-07-19 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.
Os destrutores servem para libertar a Note bem: em rigor, não é o
destrutor que “destrói” o objecto.
memória que foi alocada dinamicamente O objecto é destruído por um
pelo objecto durante a sua existência, no outro meio e o destrutor é
invocado automaticamente
momento em que o objecto está prestes a nessa altura para “arrumar a
deixar de existir. casa”.

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
virtual double Modulus() const;
double Modulus() const;
virtual
virtual double DistanceTo(const Point&
double DistanceTo(const Point& other)
other) const;
const;

virtual
virtual void
void Write()
Write() const;
const;
virtual
virtual void WriteLine() const;
void WriteLine() 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.

Para uma classe X, o ficheiro da Isto é uma convenção nossa,


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 int
intmain()
main()
{{
(uma função main) que double
doublex;x;
aceite as coordenadas double
doubley;y;
de dois pontos, std::cout
std::cout<<<<"Coordenadas
"Coordenadasdo doprimeiro
primeiroponto:
ponto:";";
std::cin >> x >> y;
construa os pontos e std::cin >> x >> y;
Point
calcule o ponto médio Pointp1(x,
p1(x,y);
y);
std::cout
std::cout<<<<"Coordenadas
"Coordenadasdo dosegundo
segundoponto:
ponto:";";
do segmento por eles std::cin >> x >>
std::cin >> x >> y;y;
formado. No final, para Point
Pointp2(x,
p2(x,y);
y);
Point
Point pm((p1.X()++p2.X())
pm((p1.X() p2.X())//2,
2,(p1.Y()
(p1.Y()++p2.Y())
p2.Y())//2);
confirmar que a std::cout << "Ponto médio: ";
2);
std::cout << "Ponto médio: ";
geometria não é uma pm.WriteLine();
pm.WriteLine();
batata, mostra a double
doubled1d1==p1.DistanceTo(pm);
p1.DistanceTo(pm);
double
double d2==p2.DistanceTo(pm);
d2
distância do ponto std::cout
p2.DistanceTo(pm);
médio a cada um dos std::cout<<<<"Distâncias:
"Distâncias:""<<
<<d1 d1<<
<<""""<<
<<d2
d2<< <<std::endl;
std::endl;
return 0;
return 0;
dois pontos iniciais (!) }}

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:
Este outro #include corresponde a
#include um ficheiro do sistema e é necessário
#include<iostream>
<iostream> para podermos ler e escrever na
consola usando aqueles operadores
#include
#include"Point.h"
"Point.h" << 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:""<< <<d1d1<<<<""""<<<<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


• 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.
2003-07-19 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
virtual void WriteLine(std::ostream& output = std::cout)const;
void WriteLine(std::ostream& output = std::cout) 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
virtual void WriteLine(std::ostream& output = std::cout)const;
void WriteLine(std::ostream& output = std::cout) 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; Point
Pointp;
p;
...
... é o mesmo do que ...
...
p.WriteLine();
p.WriteLine(); 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:
Não abusar! Às vezes
virtual
virtualvoid
voidRotate(double
Rotate(doubleangle
angle==1.57079632679489661923);
1.57079632679489661923);
complica mais do que
simplifica.

2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 22


Definindo as funções
double
doublePoint::X()
Point::X()const
As funções da classe {{
const
Isto é apenas uma parte
return
returnx;
Point são definidas no }}
x;
do ficheiro. Há mais
ficheiro Point.cpp. double
doublePoint::Y()
Point::Y()const
const
funções além destas.
{{
return
returny;
y;
}}

Ao definir as funções temos void


voidPoint::Translate(double
Point::Translate(doubledx,
dx,double
doubledy)
dy)
{{
de qualificar com o nome xx+=
+=dx;
dx;
yy+=
+=dy;
da classe: Point::X(), }}
dy;

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

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 double
doublePoint::DistanceTo(const
Point::DistanceTo(constPoint&
Point&other)
other)const
const
{{ {{
return
returnx;
x; 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 ::sqrt(x): raiz quadrada de x.
{{
return ::atan2(y, x): arcotangente
return::atan2(y,
::atan2(y,x);
x);
}} de y/x.
double ::pow(x, y): x elevado a y.
doublePoint::Modulus()
Point::Modulus()const
const
{{
return
return::sqrt(x*x
::sqrt(x*x++y*y);
y*y);
}}

2003-07-19 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 void
voidPoint::Set(double
Point::Set(doublex,
x,double
doubley)
classPoint
Point{{ {{
y)
...
... this->x
this->x==x;x; Atenção: this->x é o membro de
virtual
virtualvoid
voidSet(double
Set(doublex,
x,double
doubley);
y); this->y = y; dados, x sozinho é o argumento.
this->y = y;
}}
...
...
}; void
voidPoint::Rotate(double
Point::Rotate(doubleangle)
angle)
};
{{
double
doublex0
x0==x;x;
double
double y0==y;
y0 y;
Evitaríamos o this usando xx==x0
argumentos com nomes diferentes x0**::cos(angle)
::cos(angle)--y0
y0**::sin(angle);
::sin(angle);
yy==x0x0 * ::sin(angle) + y0**::cos(angle);
* ::sin(angle) + y0 ::cos(angle);
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:
Tem o efeito esperado. Não há problema.
int
intn;
n;
double
doublex;
x; Inseguro: truncaria a parte decimal. O compilador gera warning:
bool
boolb;
b; “'=' : conversion from 'double' to 'int', possible loss of data.”
char c;
char c;
OK: false dá zero, true dá 1.
...
...
Inseguro: zero dá false, não zero dá true. O compilador gera
xx==n;
n; warning:” 'int' : forcing value to bool 'true' or 'false' (performance
nn==x;
x; warning)”

nn==b;
b; OK: n fica com o valor numérico de c.
bb==n;
n;
OK, mas inseguro pois o valor de n pode não ser representável
nn==c;
c; no tipo char.
cc==n;
n;
2003-07-19 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
int
intn;
n;
operador static_cast<T>, no caso da
double
doublex; x; conversão para bool, usa-se o operador
bool
boolb;b; != (operador de desigualdade).
char
charc;
c;

...
...

xx==n;
n;
nn==static_cast<int>(x);
static_cast<int>(x); Nota: usa-se static_cast<double> para forçar
conversão para double em expressões:
nn==b;
b;
bb==nn!= int
intn;
!=0;
0; n;
int
intsum;
sum;
nn==c; ...
...
c;
cc==n; double
doubleaverage
average==static_cast<double>(sum)
static_cast<double>(sum)//n;
n;
n;

2003-07-19 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)
{{ Programar assim:
xx+=
+=dx;
dx;
yy+=
+=dy;
dy; void Point::Translate(...)
}} {
x = x + dx;
void
voidPoint::Scale(double
Point::Scale(doublefx,fx,double
doublefy)
fy) y = x + dy;
{{ }
xx*=
*=fx;
fx; Este é o operador de afectação
yy*=
*=fy;
fy;
}} não simples *=. seria mau estilo.

Use Em vez de
Eis a tabela dos operadores x += y x=x+y
de afectação não simples x -= y x=x-y
mais usuais: x *= y x=x*y
x /= y x=x/y
x %= y x=x%y
2003-07-19 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
Point q(0,2);
q(0, 2);
...
...
p.Rotate(1.57079632679489661923);
p.Rotate(1.57079632679489661923);
q.Scale(0,
q.Scale(0,-1);
-1);
...
...
qq==p;p; Afectação membro a membro.
...
...
Frequentemente o que queremos é a construção por cópia e não a
afectação:
...
... ...
...
Point
Pointp(4,
p(4,7);
7); Point
Pointp(4,
p(4,7);
7);
...
... ...
...
p.Rotate(1.57079632679489661923);
p.Rotate(1.57079632679489661923); p.Rotate(1.57079632679489661923);
p.Rotate(1.57079632679489661923);
Point
Pointq;
q; Point
Pointq(p);
q(p);
qq==p;p; Mau estilo: inicialização logo ...
... Bom estilo...
...
... seguida de afectação.
2003-07-19 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 Nos selectores que retornam
voidPoint::Rotate(double
Point::Rotate(doubleangle)
angle)
{{ diferente de void a última instrução
double
doublex0
x0==x;x; Variáveis locais é sempre um return.
double y0 = y;
double y0 = y; double
xx==x0 doublePoint::Angle()
Point::Angle()const
const
x0**::cos(angle)
::cos(angle)--y0
y0**::sin(angle);
::sin(angle); {{
yy==x0
x0 * ::sin(angle) + y0**::cos(angle);
* ::sin(angle) + y0 ::cos(angle); return
return::atan2(y,
::atan2(y,x);
x);
}} }}
2003-07-19 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
{{
return Este x e este y são referenciam os membros de
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)
{{
this->x Aqui x e y sozinhos referenciam os argumentos e
this->x==x;x;
this->y this->x e this->y referenciam os membros de
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
doubled1
d1==...;
...; que escrever pm.WriteLine(std::cout).
double
doubled2
d2==...;
...;
std::cout
std::cout<<
<<"Distâncias:
"Distâncias:""<<
<<d1d1<<
<<""""<<
<<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 >>
std::cin >> x >> y;y;
Point
Pointp1(x,
p1(x,y);
y);
std::cout
std::cout<<<<"Coordenadas
"Coordenadasdo
dosegundo
segundoponto:
ponto:";";
std::cin >> x >>
std::cin >> x >> y;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 No corpo dos construtores


Point::Point(doublex, x,double
doubley):
y):
x(x),
x(x), Este caso é engraçado: o x programamos o que fizer falta.
y(y)
y(y) de fora é o membro de dados,
{{ Por exemplo, podemos escrever mensagens
o x de dentro é o argumento.
}} (Idem para o y.) para debug:
Point::Point():
Point::Point():
Point::Point(const
Point::Point(constPoint&
Point&other):
other): x(0),
x(0),
x(other.x),
x(other.x), y(0)
y(0)
y(other.y)
y(other.y) {{
{{ std::cout
std::cout<<
<<"Default
"Defaultconstructor.
constructor.""<<
<<std::endl;
std::endl;
}} }}

2003-07-19 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()
{void TestDoubleOutput()
{double x; Para usar os manipuladores
doubley;
double x;
double y;<< "Coordinates (x and y), please: "; com argumentos, é preciso
std::cout
std::cout << "Coordinates (x and y), please: ";
std::cin
std::cin>>
>> xx>>
>>y;
y;
fazer #include <iomanip>.
Point p(x, y);
Point p(x, y);
double
doubledd==p.DistanceTo(Point());
p.DistanceTo(Point());
std::cout <<
<<"General
std::cout<< "Generalformat:
format: ""<<
<<std::endl;
std::endl;
std::cout
std::cout <<"Distance
"Distanceto toorigin:
origin:""<<
<<dd<< <<std::endl;
std::endl;
std::cout
std::cout<<
<<"Fixed
"Fixedformat,
format, two
twodecimal
decimaldigits:
digits:""<<
<<std::endl;
std::endl;
std::cout
std::cout<<
<< std::fixed
std::fixed <<<< std::setprecision(2);
std::setprecision(2);
std::cout << "Distance to origin: " << d << std::endl;
std::cout << "Distance to origin: " << d << std::endl;
std::cout <<
<<"Scientific
std::cout<< "Scientificformat,
format, eight
eightdecimal
decimaldigits:
digits:""<<
<<std::endl;
std::endl;
std::cout
std::cout << std::scientific
std::scientific <<
<< std::setprecision(8);
std::setprecision(8);
std::cout
std::cout<<
<<"Distance
"Distanceto toorigin:
origin:""<<
<<dd<< <<std::endl;
std::endl;
std::cout << "Returning
std::cout << "Returning toto default format, sixdigits:
default format, six digits:""<<<<std::endl;
std::endl;
std::cout.setf(0,
std::cout.setf(0,std::ios_base::floatfield);
std::ios_base::floatfield);
std::cout <<
std::cout<< std::setprecision(6);
<<"Distance
std::setprecision(6);
std::cout
std::cout << "Distance to
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
#include
#include<iostream>
<iostream>
Para simplificar a organização do ////...
...
nosso programa, usamos funções de #include
#include"Point.h"
"Point.h"
teste. A função main limita-se a void
voidTestPoints();
TestPoints();
void
voidTestDoubleOutput();
chamar uma das funções de teste. TestDoubleOutput();
int
intmain()
main()
Cada função de teste tem um protótipo, que constitui a {{
//TestPoints();
//TestPoints();
respectiva declaração. O protótipo é indispensável TestDoubleOutput();
TestDoubleOutput();
porque as função vão ser chamadas na função main, return
return0;
0;
e as definições só aparecem depois. }}

void
voidTestPoints()
TestPoints()
{{
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; void


voidTestDoubleOutput()
TestDoubleOutput()
{{
//...
//...
}}

2003-07-19 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:
double abs: valor absoluto.
doubleabs(double
abs(doublex);
x);
floor: maior número inteiro (enquanto número double)
double
doublefloor(double
floor(doublex);
x);
double
menor ou igual a x: ceil: menor número inteiro (enquanto
double ceil(doublex);
ceil(double x); número double) maior ou igual a x .
double
doublecos(double
cos(doublex);
x); Funções trigonométricas coseno, seno, tangente, arco-
double
double sin(doublex);
sin(double x); coseno, arcosseno, arcotangente. A função arcotangente
double
doubletan(double
tan(doublex);
x); tem duas variantes: atan tem resultado no intervalo –
double
double acos(doublex);
acos(double x); p /2, p /2; atan2(y, x) calcula o arcotangente de y/x no
double
doubleasin(double
asin(doublex);
x);
double intervalo –p , p , usando os sinais de y e x para
doubleatan(double
atan(doublex);
x);
double determinar o quadrante. Calcula mesmo se x valer zero,
double atan2(doubley,
atan2(double y,double
doublex);
x);
mas não se x e y valerem zero.
double
doublesinh(double
sinh(doublex);
x);
double Funções hiperbólicas.
double cosh(doublex);
cosh(double x);
double
doubletanh(double
tanh(doublex);
x); Para usar isto é preciso fazer
double #include <cmath>.
doublesqrt(double
sqrt(doublex);
x);
double
double exp(doublex);
exp(double x);
double
Raiz quadrada, exponencial, logaritmo natural, logaritmo
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 Isto significa: se a constante simbólica _H_Point ainda não
#define _H_Point
#define _H_Point estiver definida, então fica definida e o resto do ficheiro é
processado pelo compilador; se já estiver definida, então o
class
classPoint
Point{{ compilador ignora tudo até à correspondente directiva
private:
private: #endif, lá em baixo.
double
doublex;
x;
double y;
double y; 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
#endif
#endif
_H_, convencionalmente.
2003-07-19 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 #include
#include<iostream>
<iostream> #include
#include<iostream>
<iostream>
#define
#define_H_Point
_H_Point #include
#include<cmath>
<cmath> #include
#include<iomanip>
<iomanip>

class
classPoint
Point{{ #include
#include"Point.h"
"Point.h" #include
#include"Point.h"
"Point.h"
private:
private:
double
doublex;x; Point::Point():
Point::Point(): void
voidTestPoints();
TestPoints();
double y;
double y; x(0),
x(0), void TestDoubleOutput();
void TestDoubleOutput();
////... y(0) Só incluímos o que faz
... y(0) falta. Por exemplo, no
};
}; {{ Point.cpp faz falta o int
intmain()
main()
}} <cmath>, mas no {{
#endif
#endif M_Point.cpp não faz. //TestPoints();
//TestPoints();
////...
... TestDoubleOutput();
TestDoubleOutput();
Point.h return
Point.cpp return0;
0;
}}

Às vezes, parece que esta regra nos //...


//...
complica a vida, pois obriga-nos a repetir M_Point.cpp
inclusões. Na verdade, ajuda-nos muito.
2003-07-19 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á
definida no espaço de nomes std. Programadores preguiçosos usam a
directiva using namespace std; para
void
voidTestDoubleOutput()
TestDoubleOutput() não ter de escrever std:: muitas vezes:
{{ //...
//...
double
doublex;x; using namespace std;
double
doubley;y;
using namespace std;

std::cout
std::cout<<<<"Coordinates
"Coordinates(x
(xand
andy),
y),please:
please:";";
//...
//...
std::cin
std::cin>>
>>xx>>>>y;y; void TestDoubleOutput()
void TestDoubleOutput()
Point
Pointp(x,
p(x,y);
y);
{
{
double x;
double x;
double y;
double y;
cout << "Coordinates (x and y), please: ";
double
doubledd==p.DistanceTo(Point());
p.DistanceTo(Point()); cout << "Coordinates (x and y), please: ";
cin >> x >> y;
cin >> x >> y;
Point p(x, y);
Point p(x, y);
std::cout
std::cout<<
<<"General
"Generalformat:
format:""<<<<std::endl;
std::endl; double d = p.DistanceTo(Point());
double d = p.DistanceTo(Point());
std::cout
std::cout << "Distance to origin:""<<
<< "Distance to origin: <<dd<<<<std::endl;
std::endl; cout << "General format: " << endl;
cout << "General format: " << endl;
cout << "Distance to origin: " << d << endl;
cout << "Distance to origin: " << d << endl;
std::cout
std::cout<<
<<"Fixed
"Fixedformat,
format,two
twodecimal
decimaldigits:
digits:""<<
<<std::endl;
std::endl; cout << "Fixed format, two decimal digits: " << endl;
std::cout << std::fixed << std::setprecision(2);
std::cout << std::fixed << std::setprecision(2);
cout << "Fixed format, two decimal digits: " << endl;
cout << fixed << setprecision(2);
cout << fixed << setprecision(2);
cout << "Distance to origin: " << d << endl;
std::cout
std::cout<<
<<"Distance
"Distancetotoorigin:
origin:""<<
<<dd<<
<<std::endl;
std::endl; cout << "Distance to origin: " << d << endl;

//...
//...
2003-07-19 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 As definições das funções também


dentro do espaço de nomes ficam dentro do espaço de nomes
mas: #ifndef _H_Point mas: #include <iostream>
#ifndef _H_Point #include <iostream>
#define
#define_H_Point
_H_Point #include
#include<cmath>
<cmath>
namespace
namespacemas
mas{{ #include
#include"Point.h"
"Point.h"
class
classPoint
Point{{ namespace
namespacemas
mas{{
private:
private:
double
doublex;
x; Point::Point():
Point::Point():
double
doubley;
y; x(0),
x(0),
//...
//... y(0)
y(0)
};
}; {{
Dentro de um espaço de }}
}}
nomes, os nomes desse
//...
//...
#endif espaço não precisam de
#endif
qualificação. }}

2003-07-19 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:";";
std::cin >> x >> y; Assim, até podia haver
std::cin >> x >> y;
mas::Point várias classes Point,
mas::Pointp1(x,
p1(x,y);
y);
std::cout cada uma no seu espaço
std::cout<<<<"Coordenadas
"Coordenadasdo dosegundo
segundoponto:
ponto:";";
std::cin de nomes.
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
doubled1d1==p1.DistanceTo(pm);
p1.DistanceTo(pm);
double
double d2==p2.DistanceTo(pm);
d2 p2.DistanceTo(pm);
std::cout
std::cout <<"Distâncias:
<< "Distâncias:""<<
<<d1
d1<<<<""""<<<<d2d2<<<<std::endl;
std::endl;
}}

2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 45

Limites numéricos
void
voidTestNumericLimits()
Às vezes {{
TestNumericLimits()
precisamos int
intintMax
intMax==std::numeric_limits<int>::max();
std::numeric_limits<int>::max();
de conhecer int
int intMin==std::numeric_limits<int>::min();
intMin std::numeric_limits<int>::min();
double
doubledoubleInfinity
doubleInfinity==std::numeric_limits<double>::infinity();
os limites double
std::numeric_limits<double>::infinity();
double doubleMax==std::numeric_limits<double>::max();
doubleMax
dos tipos double
doubledoubleMin
std::numeric_limits<double>::max();
doubleMin==std::numeric_limits<double>::min();
std::numeric_limits<double>::min();
numéricos double
double epsilon==std::numeric_limits<double>::epsilon();
epsilon std::numeric_limits<double>::epsilon();
nos nossos std::cout
programas. std::cout<<
<<"Maior
"Maiornúmero
númerointeiro:
inteiro:""<<
<<intMax
intMax<<
<<std::endl;
std::endl;
std::cout
std::cout << "Menor número inteiro: " << intMin <<std::endl;
<< "Menor número inteiro: " << intMin << std::endl;
Eis um
programa std::cout
std::cout<<
<<"Mais
"Maisinfinito
infinito(double):
(double):""<<<<doubleInfinity
doubleInfinity<<
<<std::endl;
std::endl;
std::cout
std::cout << "Maior número positivo double: " << doubleMax<<
<< "Maior número positivo double: " << doubleMax <<std::endl;
que os std::cout
std::endl;
std::cout << "Menor número positivo double: " << doubleMin <<std::endl;
<< "Menor número positivo double: " << doubleMin << std::endl;
revela: 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
double epsilon1 = epsilon++1.0;
epsilon1 = epsilon 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
usingstd::cout;
std::cout; Declarações using. Usar com moderação.
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
cout << "char Max: " << static_cast<int>(std::numeric_limits<char>::max())<<
<< "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;
cout
cout<<<<"short
"shortMax:
Max:""<<<<std::numeric_limits<short>::max()
std::numeric_limits<short>::max()<<<<endl;
endl;
Tipos inteiros do C++:
cout << "int Min: " << std::numeric_limits<int>::min() << endl;
cout << "int Min: " << std::numeric_limits<int>::min() << endl; char, short, int, long,
cout
cout<<<<"int
"intMax:
Max:""<<<<std::numeric_limits<int>::max()
std::numeric_limits<int>::max()<< <<endl;
endl; unsigned, _int64.
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
cout << "unsigned Min: " << std::numeric_limits<unsigned>::min()<<
<< "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;
cout específico da
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
double infinity==std::numeric_limits<double>::infinity();
infinity std::numeric_limits<double>::infinity();
std::cout
std::cout << "Maiordouble:
<< "Maior double:""<< <<maxmax<< <<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.01.0++infinity
infinity<<
<<std::endl;
std::endl;
std::cout
std::cout << "1.0 - infinity = " << 1.0 - infinity <<std::endl;
<< "1.0 - infinity = " << 1.0 - 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 0 * ∞ é indeterminado.
std::cout << "1.0 / infinity = " << 1.0 / infinity <<std::endl;
<< "1.0 / infinity = " << 1.0 / infinity << std::endl;
std::cout
std::cout<<
<<"0.0
"0.0**infinity
infinity==""<< <<0.0
0.0**infinity
infinity<<
<<std::endl;
std::endl;
std::cout
std::cout << "infinity / 0.0 = " << infinity/ 0.0 <<std::endl;
<< "infinity / 0.0 = " << infinity/ 0.0 << std::endl;
“warning: potential divide by 0”.
O manipulador std::boolalpha faz com que os valores
std::cout
std::cout<<
<<std::boolalpha;
std::boolalpha; booleanos sejam escritos false e true, em vez de 0 e 1.
std::cout
std::cout << "max < infinity = " << (max<<infinity)
<< "max < infinity = " << (max infinity)<<
<<std::endl;
std::endl;
std::cout << "infinity - max = " << infinity - max << std::endl;
std::cout << "infinity - max = " << infinity - max << std::endl; Nota: o tipo int não
}} 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
std::vector<int> “paralelos”, é melhor usar um vector de pares:
std::vector<int>a(32);
a(32);
std::string
std::stringm(79,
m(79,' '');
');
std::vector<double> std::vector<std::pair<double,
std::vector<std::pair<double,double>
double>>>z(100);
std::vector<double>x(100);
x(100); z(100);
std::vector<double>
std::vector<double>y(100);
y(100); Para usar vectores destes é preciso fazer #include
std::vector<mas::Point>
std::vector<mas::Point>p(8);
p(8); <vector>; para usar strings, fazer #include <string>.
//...
//...
2003-07-19 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:
void Inicializamos a semente do gerador de números
voidTest_3()
Test_3()
{{ aleatórios.
::srand(static_cast<unsigned>(::time(0)));
::srand(static_cast<unsigned>(::time(0)));
std::vector<int>
std::vector<int>a;
a; Declaramos o vector. Não indicamos a capacidade.
a.reserve(16);
a.reserve(16); Agora reservamos a capacidade pretendida.
for
for (int i = 0; i < static_cast<int>(a.capacity());i++)
(int i = 0; i < static_cast<int>(a.capacity()); i++)
a.push_back(::rand()
a.push_back(::rand()% %100);
100); Preenchemos o vector com um ciclo for.
for
for (int i = 0; i < static_cast<int>(a.capacity());i++)
(int i = 0; i < static_cast<int>(a.capacity()); i++)
std::cout
std::cout<<
<<""""<<
<<a[i];
a[i]; Escrevemos o vector.
std::cout << std::endl;
std::cout << std::endl;
std::sort(a.begin(), Ordenamos usando a função genérica std::sort.
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]; Escrevemos de novo.
std::cout << std::endl;
std::cout << std::endl;
}}

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: Se a expressão2 faltar, é como se fosse


true. Ficamos com um ciclo “infinito” que
1. Avalia a expressão1. 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; As expressão1 e a expressão3 têm efeitos
laterais que mudam o estado do programa. A
5. Volta ao passo 2. expressão2 normalmente é uma expressão
booleana que apenas calcula, sem efeitos laterais.

2003-07-19 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; ou xx+=
+=1;
1;

x++;
preferimos 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


x++ e ++x

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


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

2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 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
::srand(static_cast<unsigned>(::time(0))); actual medida em milésimos de
::srand(static_cast<unsigned>(::time(0)));
//... segundos desde um certo dia no anos
//...
for 70. Para usar a função ::time é preciso
for(int
(inti i==0;
0;i i<<static_cast<int>(a.capacity());
static_cast<int>(a.capacity());i++)
i++)
a.push_back(::rand() % 100); fazer #include <ctime>.
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
void Write(const std::vector<T>&v,
Write(const std::vector<T>& v,
const
conststd::string&
std::string&separator
separator==""",",std::ostream&
std::ostream&output
output==std::cout)
std::cout)
{{
for
for(unsigned
(unsignedi i==0;
0;i i<<v.size()
v.size();;i++)
i++) (i != 0 ? separator : ""): expressão
output << (i != 0 ? separator : "") << v[i];
output << (i != 0 ? separator : "") << v[i]; condicional: o separador não é escrito
}}
antes do primeiro elemento.
template
template<class
<classT>
T>
void
void WriteLine(conststd::vector<T>&
WriteLine(const std::vector<T>&v, v,
const
const std::string&separator
std::string& 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
mas::WriteLine(a); voidTest_5()
Test_5()
mas::WriteLine(a); {{
}} std::vector<mas::Point>
std::vector<mas::Point>v;v;
v.reserve(6);
v.reserve(6); Batota: a classe Point
E outro, escrevendo v.push_back(mas::Point(2.0,
v.push_back(mas::Point(2.0,-1.0));
-1.0)); não tem o operador
pontos: 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 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:
double
tipo da classe. Por isso, arrumamo-los “dentro” da
doublex; x;
double classe. Normalmente, exprimem-se em termos de
doubley; y;
public:
public: alguma das funções da classe.
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
output << (i != 0 ? separator : "")
<< (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> template
template<class
<classT>
T>
TTMin(const
Min(const T& x,const
T& x, constT&y)
T&y) TTMax(const
Max(const T& x,const
T& x, 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> template
template<class
<classT>
T>
TTMin(const
Min(const T& x,const
T& x, constT&y)
T&y) TTMax(const
Max(const T& x,const
T& x, constT&y)
T&y)
{{ {{
ifif(x
(x<=
<=y)
y) ifif(x
(x<=
<=y)
y)
return
returnx;
x; return
returny;
y;
else
else else
else
return
returny;
y; return
returnx;
x;
}} }}

2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 63

A instrução if-else
Sintaxe:
Repare que a expressão tem
de estar entre parêntesis.
if (expressão)
instrução1
A instrução if é uma instrução: executa-se.
else A expressão condicional é uma expressão:
instrução2 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(); Construtores. O primeiro construtor constrói um
vector();
vector vazio. O segundo constrói um vector com
explicit
explicitvector(size_type
vector(size_typen);
n); tamanho n, e todos os n elementos são inicializados
com o construtor por defeito do tipo T. O terceiro é
vector(size_type
vector(size_typen,
n,const
constT&
T&x);
x); parecido, mas os elementos são inicializados com
vector(const
vector(constvector<T>&
vector<T>&other);
other); x. O quarto é o construtor de cópia.

Capacidade: número de elementos que o vector


size_type
size_typecapacity()
capacity()const;
const; pode conter sem precisar de crescer. Tamanho:
size_type número de elementos presentes no vector.
size_typesize()
size()const;
const;

O operador [] só pode ser usado para


T&
T&operator[](size_type
operator[](size_typex);
x); referenciar um elemento que exista. Por outras
const palavras, o argumento x deve ser tal que
constT&
T&operator[](size_type
operator[](size_typexx))const;
const;
0 <= x && x <= size().

2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 65

Operações sobre vectores (2)


Eis as mais simples:
A função clear remove todos os elemento do vector.
void A capacidade mantém-se e o tamanho fica zero.
voidclear();
clear();
bool A função empty dá true se o vector tiver zero
boolempty()
empty()const;
const; elementos.
A função pop_back remove o último elemento do
void vector.
voidpop_back();
pop_back();
void A função push_back acrescenta um elemento ao
voidpush_back(const
push_back(constT&
T&x);
x); vector. Se necessário, o vector cresce
automaticamente (isto é, a capacidade aumenta).

void A função resize muda o tamanho do vector. Note


voidresize(size_type
resize(size_typen);
n); bem: muda o tamanho, acrescentando ou
void removendo elementos. A primeira versão, quando
voidresize(size_type
resize(size_typen,
n,TTx);
x);
acrescenta, inicializa os elemento com o construtor
por defeito da classe T. A segunda inicializa com x.

void
voidreserve(size_type
reserve(size_typen);
n); A função reserve aumenta a capacidade do vector
conforme indicado no argumento. Note bem:
aumenta a capacidade mas não o tamanho.

2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 66


Algoritmos
Certas operações sobre vectores não provêm Para usar isto é
da classe std::vector<T> mas são aplicação preciso fazer #include
<algorithm>.
de funções genéricas de natureza algorítmica:
int Quanto elementos iguais a x?
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())
Existe algum elemento igual a x?
cout
cout<<
<<"Existe.
"Existe.""<<
<<endl;
endl;
else
else
cout
cout<<
<<"Não
"Nãoexiste.
existe.""<<
<<endl;
endl;

std::sort(v.begin(), Ordenar o vector.


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

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
número de avenidas da cidade, seguida de N linhas 14
com dois números: o primeiro é a rua e o segundo a 10
1044
avenida onde cada um dos amigos mora (sim, todos 2255

eles moram em cruzamentos também ..) Trata-se 2277


de uma cidade matemática em que a primeira rua é a 7733
rua zero e o mesmo para as avenidas. 10
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{{
O grau do polinómio vem no membro de dados
private:
private: degree.
int
intdegree;
degree;
std::vector<double>
std::vector<double>a;
a;
public: Os coeficientes vêm num vector de números double
public:
//...
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 Regra: quando um construtor tem um só argumento,
classPolynomial
Polynomial{{
////... qualificamo-lo explicit, para evitar que seja invocado para
...
public: realizar conversões automáticas.
public:
explicit
explicitPolynomial(double
Polynomial(doublea0
a0==0.0);
0.0);
Polynomial(const
Polynomial(const std::vector<double>&coefs);
std::vector<double>& 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{{
////...
... Este operador é um bocado
virtual
virtualdouble
doubleValue(double
Value(doublex)
x)const;
const; estranho. Veremos já a seguir
virtual
virtual double operator ()(doublex)
double operator ()(double x)const;
const; como se usa.
////...
...
};
};

Avaliamos com o método de Assim trabalharíamos mais do


Horner: que é preciso:
double
doublePolynomial::Value(double
Polynomial::Value(doublex) x)const
const
double
doublePolynomial::Value(double
Polynomial::Value(doublex)
x)const
const {{
{{ double
doubleresult
result==0.0;
0.0;
double result = 0.0; double
double mm==1.0;
1.0;
double result = 0.0; for
for for(int
(inti i==0;
0;i i<=
<=degree;
degree;i++)
i++)
for(int
(inti i==degree;
degree;i i>=
>=0;0;i--)
i--) {{
result
result==result
result**xx++a[i];
a[i]; result
result+=+=a[i]
a[i]**m;m; Este esquema usa o
return result; mm *=
*= x;x; dobro das
return result; }}
}} return multiplicações.
returnresult;
result;
}}
2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 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};
double d[4] = {1.0, -2, 0.0, 5.0}; Ilustra um outro construtor da
std::vector<double>
std::vector<double>coefs(d,
coefs(d,d+4);
d+4); classe std::vector<T>.
mas::Polynomial
mas::Polynomialp(coefs);
p(coefs);
for
for(;;)
(;;)
{{ Escrever p(x), usando o
double
doublex;x; operador () é apenas uma
std::cout << "x =
std::cout << "x = ";"; maneira mais conveniente
std::cin >> x;
std::cin >> x; de escrever p.Value(x).
double
doubley1y1==p.Value(x);
p.Value(x);
double
doubley2y2==p(x);
p(x);
std::cout
std::cout<<<<y1y1<<
<<""""<<<<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
Estas funções são
virtualvoid
voidResize(int
Resize(intsize);
size); ////size
sizeisisnumber
numberofofcoefficients.
coefficients.
virtual
sobretudo usadas nas
virtual void Grow(int size); // size is number ofcoefficients.
void Grow(int size); // size is number of coefficients.
virtual
outras funções da classe.
virtualvoid
voidSwapOut(Polynomial&
SwapOut(Polynomial&other);
other);

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) void
size) voidPolynomial::SwapOut(Polynomial&
Polynomial::SwapOut(Polynomial&other)
other)
{{ {{
ifif(size
(size>>static_cast<int>(a.size()))
static_cast<int>(a.size())) a.swap(other.a);
a.swap(other.a);
a.resize(size); std::swap(degree,
a.resize(size); std::swap(degree,other.degree);
other.degree);
}} }}
2003-07-19 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 void
voidPolynomial::Nullify()
Polynomial::Nullify()
{{ {{
return
returndegree
degree==
==00&&&&a[0]
a[0]==
==0.0;
0.0; 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; Note bem: os operadores são funções como as outras.
return false;
for Escrever p1 == p2 , para dois polinómios p1 e p2 é o
for(int
(inti i==0;
0;i i<=
<=degree;
degree;i++)
i++)
ifif(a[i] != other.a[i])
(a[i] != other.a[i])
mesmo do que escrever p1.operator == (p2), mas é
return
returnfalse;
false;
mais prático e mais expressivo. Analogamente, para o
return true;
return true; operador (): escrever p1(x) é o mesmo do que escrever
}} p1.operator()(x).

2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 75

Somando polinómios
Para somar
polinómios usamos virtual
virtualvoid
voidAdd(const
Add(constPolynomial&
Polynomial&other);
other);
o modificador Add, virtual
virtual Polynomial& operator += (constPolynomial&
Polynomial& operator += (const Polynomial&other);
other);
virtual
virtualPolynomial
Polynomialoperator
operator++(const
(constPolynomial&
Polynomial&other);
o operador += e o other);

operador +: A função RecomputeDegree


recalcula o grau após a adição,
Quem trabalha mesmo é a pois os coeficientes de maior
função Add: grau podem ter-se anulado, ou
void
voidPolynomial::Add(const
Polynomial::Add(constPolynomial&
Polynomial&other)
other)
o grau pode ter aumentado,
{{ porque o grau do argumento
Grow(other.degree
Grow(other.degree++1); 1); era maior:
for
for (int i = 0; i <= other.degree;i++)
(int i = 0; i <= other.degree; i++) void
voidPolynomial::RecomputeDegree()
Polynomial::RecomputeDegree()
a[i] {{
a[i]+=
+=other.a[i];
other.a[i];
int
intxx==static_cast<int>(a.size())
static_cast<int>(a.size())--1;
1;
RecomputeDegree(); while
RecomputeDegree(); while (x>>00&&
(x &&a[x]
a[x]==
==0.0)
0.0)
}} x--;
x--;
degree
degree==x; x;
}}
2003-07-19 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 Qual é a diferença entre
PolynomialPolynomial::operator
Polynomial::operator++(const
(constPolynomial&
Polynomial&other)
other)
{{ (p1 + p2).WriteLine();
Esta declaração cria uma
Polynomial
Polynomialresult(*this);
result(*this); variável local result inicializada e
return
returnresult
result+=
+=other;
other;
}}
com uma cópia do objecto.
(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
virtual Polynomial& operator+=
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; Polynomial
*this; 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
virtual Polynomial& operator -=(double
Polynomial& operator -= (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); Multiplicando por
virtual
virtual Polynomial& operator *= (constPolynomial&
Polynomial& operator *= (const Polynomial&other);
other); outro polinómio.
virtual
virtualPolynomial
Polynomialoperator
operator**(const
(constPolynomial&
Polynomial&other);
other);

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

virtual
virtualvoid
voidMultiply(double
Multiply(doubley);
y); Multiplicando por
virtual
virtual Polynomial& operator*=
Polynomial& operator *=(double
(doubley);
y); uma constante.
virtual
virtualPolynomial
Polynomialoperator
operator**(double
(doubley);
y);

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;
for mas até nem é.
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
virtual de volta num argu-
virtualvoid
voidDivide(const
Divide(conststd::pair<double,
std::pair<double,double>&
double>&m,
m,double&
double&remainder);
remainder);
virtual mento de saída,
virtualPolynomial&
Polynomial&operator
operator/=
/=(const
(conststd::pair<double,
std::pair<double,double>&
double>&m);
m);
por referência.
virtual
virtualPolynomial
Polynomialoperator
operator//(const
(conststd::pair<double,
std::pair<double,double>&
double>&m);
m);

A divisão faz-se usando a regra de Ruffini (ver página


seguinte). As outras duas operações são como de costume,
mas ignoram o resto da divisão:
Polynomial&
Polynomial&Polynomial::operator
Polynomial::operator/=/=(const
(conststd::pair<double,
std::pair<double,double>&
double>&m)
m)
{{
double
doubletemp;
temp;
Divide(m, Polynomial Polynomial::operator / (const std::pair<double, double>& m)
Divide(m,temp);
temp); Polynomial Polynomial::operator / (const std::pair<double, double>& m)
return {{
return*this;
*this;
}} Polynomial
Polynomialresult(*this);
result(*this);
return
return result/=
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; Observe a técnica do SwapOut: o resultado, que
for
for(int
(inti i==temp.degree
temp.degree;;i i>=
>=1;1;i--)
i--) é do tipo da classe, é calculado numa variável
temp.a[i - 1] = temp.a[i] * k + a[i];
temp.a[i - 1] = temp.a[i] * k + a[i]; local. No final, o objecto é swapped out com a
remainder
remainder==temp.a[0]
temp.a[0]**kk++a[0];
a[0];
variável local. Como resultado, o objecto fica com
temp.Multiply(1 / m.first); o valor da variável local, tal como pretendido, e o
temp.Multiply(1 / m.first);
SwapOut(temp);
anterior valor do objecto fica na variável local, que
SwapOut(temp);
vai desaparecer assim que a função termina.
}}

2003-07-19 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
virtual void Integrate(doublea0
void Integrate(double a0==0.0);
0.0);

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

2003-07-19 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
virtual double Integral(double x0, doublex1)
double Integral(double x0, double 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
virtual void WriteLine(std::ostream& output==std::cout)
void WriteLine(std::ostream& output 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();
for void Polynomial::WriteLine(std::ostream& output) const
for (inti i==0;
(int 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,
{{
escrevem-se só input
Ao ler, primeiro lê-se o grau e
input>> >>degree;
degree;
os coeficientes. Grow(degree
Grow(degree++1); 1);
depois os coeficientes. As
for (int i = 0; i <= degree;
for (int i = 0; i <= degree; i++)i++) demais posições são anuladas.
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 O último par de valores é escrito
output<<
<<x1
x1<<
<<"\t"
"\t"<<
<<Value(x1)
Value(x1)<<
<<std::endl;
std::endl;
}} fora do ciclo, para acertar.
2003-07-19 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()
Exemplo de execução:
{{
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(); O ficheiro abre directamente no
double
doublea;
double
a;
b; Excel e produz o seguinte gráfico:
double b;
std::cout
std::cout<<<<"Intervalo:
"Intervalo:";"; 40

std::cin
std::cin >> a >>b;
>> a >> b; 30
double delta;
double delta; 20
std::cout
std::cout<<<<"Delta:
"Delta:";"; 10
std::cin >> delta;
std::cin >> delta; 0
p.Plot(a,
p.Plot(a,b,
b,delta,
delta,output);
output);
-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:
int As funções estáticas não têm objecto. Ao chamá-las,
intdegree;
degree;
std::vector<double> qualificamos com o nome da classe, por exemplo:
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
static PolynomialNewton(double
Polynomial 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) = x2-
6x+5. Ora Q1(x) anula-se para x=1 e para x=5 e vale 21 para
x=-2. Logo o polinómio R1(x) = Q1(x) * 5 / 21 anula-se para
x=1 e para x=5 e vale 5 para x=-2.
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{{ Três membros de dados,
para o ano, para o mês e
class
classDate
Date{{ para o dia.
private:
private:
int
intyear;
year;
int Um tipo enumerado para
intmonth;
month; os dias da semana.
int
intday;
day;
public:
public:
enum
enumWeekdayType
WeekdayType{MONDAY,
{MONDAY,TUESDAY,
TUESDAY,WEDNESDAY,
WEDNESDAY,THURSDAY,
THURSDAY,
FRIDAY, SATURDAY, SUNDAY};
FRIDAY, SATURDAY, SUNDAY};
Construtor por defeito, construtor
Date();
Date(); de cópia, construtor elementar.
Date(const
Date(constDate&
Date&other);
other);
Date(int
Date(intyear,
year,int
intmonth,
month,intintday);
day); ////pre
preValid(year,
Valid(year,month,
month,day);
day);
virtual
virtual~Date();
~Date(); Esta precondição usa uma função
//...
//... estática Valid, ainda por declarar.
}; Destrutor.
};
}}
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
virtual desde o início. Início de quê? Início do
virtualint
intYear()
Year()const;
const; nosso calendário.
virtual
virtual int Month()const;
int Month() 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();
virtual void Forth(); Avançar 1 dia. Recuar 1 dia.
virtual
virtualvoid
voidBack();
Back(); //
//pre
preoperator
operator>>(First());
(First());
virtual void Add(int x); // pre x >=
virtual void Add(int x); // pre x >= 0; 0; Avançar x dias. Recuar x dias.
virtual
virtualvoid
voidSubtract(int
Subtract(intx);
x); //
//pre
prexx>=>=00&&&&DaysSince(First())
DaysSince(First())>= >=x;x;
virtual const Date& First() const;
virtual const Date& First() const; Data do primeiro dia do nosso calendário.
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: Permitir-nos-ão escrever
class coisas como:
classDate
Date{{
//...
//... Date d1;
Date d2;
// ...
virtual
virtualbool
booloperator
operator==
==(const
(constDate&
Date&other)
other)const;
const; if (d1 <= d2)
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 while (d1 != d2)
virtual bool operator < (const Date& other)const;
bool operator < (const Date& other) const; ...
virtual
virtualbool
booloperator
operator>=
>=(const
(constDate&
Date&other)
other)const;
const;
virtual
virtual bool operator > (const Date& other)const;
bool operator > (const Date& other) const; Operadores de incremen-
tação e decrementação.
virtual
virtualDate&
Date&operator
operator++
++ (int);
(int); Poderemos escrever d++
virtual
virtual Date& operator -- (int);
Date& operator -- (int); ////pre
pre*this
*this>>First();
First(); 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
virtual Date& operator +=(int x); x); //
// pre x >=0;
pre x >= 0;
virtual
virtualDate
Dateoperator
operator-(int
-(intx)
x)const;
const; ////pre
prexx>=
>=00&&
&&Count()
Count()>=>=x; x;
virtual Date& operator -=(int
virtual Date& operator -=(int x); x); // pre x >= 0 && Count() >=
// pre x >= 0 && Count() >= x; 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
virtual void WriteLine(std::ostream& output = std::cout)const;
void WriteLine(std::ostream& output = std::cout) 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
friend std::ostream& operator << (std::ostream& output, const Date&d);
std::ostream& operator << (std::ostream& output, const Date& 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
virtualWeekdayType
WeekdayTypeWeekday()
Weekday()const;
const; tipo WeekdayType.
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
void
voidDate::AddWeek(int
Date::AddWeek(intx)x) acabam no domingo.
{{
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
class
classDate
Date{{
estáticos com mem-
//...
//... bros de dados. Cada
public: // static
public: // static objecto tem os seus
static
staticbool
boolValid(int
Valid(inty,
y,int
intm,
m,int
intd);
d); próprios membros
static bool Valid(int y, int m);
static bool Valid(int y, int m); de dados. Os mem-
static bool Valid(int y);
static bool Valid(int y); bros estáticos são
static
staticbool
boolLeapYear(int
LeapYear(inty);y); //
//pre
preValid(y);
Valid(y); da classe e são
static
staticint
intDaysInMonth(int
DaysInMonth(inty, y,int
intm);
m);////pre
preValid(y, m); partilhados por todos
Valid(y,m);
static int DaysInYear(int y);
static int DaysInYear(int y); // pre Valid(y);
// pre Valid(y); os objectos.
private: Primeiro ano do calendário, primeiro dia do
private: ////static
staticdata
datamembers
members
static calendário, dia da semana respectivo e tabela dos
staticconst
constint
intfirstYear;
firstYear;
static const Date first;
static const Date first; números de dias dos meses nos anos comuns,
static
staticconst
constWeekdayType
WeekdayTypefirstWeekday;
firstWeekday;
static Os membros estáticos são
static const intdaysInMonth[];
const int 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() calculado pela função de biblioteca ::time, que
Date::Date()
{{ dá o número de segundos desde as zero horas
time_t de 1 de Janeiro de 1970, UTC. Depois, a
time_ttime0
time0==::time(0);
::time(0);
struct função ::localtime converte esse número para
struct ::tm *now==::localtime(&time0);
::tm *now ::localtime(&time0);
year uma variável de tipo ::tm que tem membros
year==now->tm_year
now->tm_year++1900;1900;
month para o ano, mês, dia (e ainda outros que não
month==now->tm_mon
now->tm_mon++1; 1;
day = now->tm_mday; usamos aqui), tendo em conta a zona horária.
day = now->tm_mday;
}} Essa variável é apontada pela variável local
now. Para usar estas funções é preciso fazer
#include <ctime>. (Para mais informação,
Os outros construtores são consulte a documentação.)
rotineiros:
Date::Date(int
Date::Date(intyear,
year,int
intmonth,
month,int
intday):
day): Date::Date(const
Date::Date(constDate&
Date&other):
other):
year(year),
year(year), year(other.year),
year(other.year),
month(month),
month(month), month(other.month),
month(other.month),
day(day)
day(day) day(other.day)
day(other.day)
{{ {{
}} }}
2003-07-19 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, O selector para o número de dias
para o mês, e para o dia desde o início do calendário
são muito simples: recorre à função DaysSince:
int
intDate::Year()
Date::Year()const
const int
intDate::Count()
Date::Count()const
const
{{ {{
return
returnyear;
year; return
returnDaysSince(first);
DaysSince(first);
}} }}

int
intDate::Month()
Date::Month()const
const first é o membro estático que dá
{{ a data do primeiro dia do
return
returnmonth;
month;
}} calendário. É inicializado assim:
const
constint
intDate::firstYear
Date::firstYear==1901;
1901;
int
intDate::Day()
Date::Day()const
const const
const Date Date::first(Date::firstYear,1,
Date Date::first(Date::firstYear, 1,1);
1);
{{
return
returnday;
day;
}}
const
constDate&
Date&Date::First()
Date::First()const
const A função First
{{
return
devolve a data do
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 Avança-se x dias com a
função Forth: função Add:
void
voidDate::Forth()
Date::Forth() void
voidDate::Add(int
Date::Add(intx) x)
{{ {{
ifif(day
(day<<MonthSize())
MonthSize()) for
for(int
(inti i==0;
0;i i<<x;
x;i++)
i++)
day++;
day++; Forth();
Forth();
else
else A função MonthSize }} Isto não é lá muito eficiente,
{{ diz quantos dias tem o se x for grande /. Por outro
day
day==1; 1; mês. Qual mês? O mês lado, se x for negativo, nada
ifif(month
(month<<12)12) acontece.
a que pertence o
month++;
month++; objecto.
else
else
{{ Avança-se x semanas
month
month==1; 1; somando 7*x dias:
year++;
year++;
}} void
voidDate::AddWeek(int
Date::AddWeek(intx)
x)
}} Se não estivermos no fim do mês, {{
}} incrementamos o dia. Se não, o dia é 1 e Add(7
Add(7**x);
x);
se o mês não for Dezembro, }}
incrementamos o mês. Se não, o mês é
Janeiro e incrementamos o ano.
2003-07-19 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 void
voidDate::Subtract(int
Date::Subtract(intx)
voidDate::Back()
Date::Back() x)
{{ {{
ifif(day
(day>>1)1) for
for(int
(inti i==0;
0;i i<<x;
x;i++)
i++)
day--;
day--; Back();
Back();
else }}
else
{{
ifif(month
(month>>1)1)
month--;
month--;
Recua-se x semanas
else
else subtraindo 7*x dias:
{{
month void
voidDate::SubtractWeek(int
Date::SubtractWeek(intx)
month==12;12; x)
year--; {{
year--;
}} Subtract(7
Subtract(7**x);
x);
day }}
day==MonthSize();
MonthSize();
}}
}}

2003-07-19 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) Esta função é
{{ estática: trabalha
return
returndaysInMonth[m
daysInMonth[m--1]
1]++(m(m==
==22&&
&&LeapYear(y));
LeapYear(y)); sobre os seus
}} argumentos e sobre
os membros
A função MonthSize é um selector. Recorre à estáticos.
função estática DaysInMonth:
int
intDate::MonthSize()
Date::MonthSize()const
const Esta função é virtual (não é estática):
{{ trabalha sobre os membros de dados
return
returnDaysInMonth(year,
DaysInMonth(year,month);
month); do objecto.
}}

2003-07-19 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 Na verdade, o que está
boolDate::LeapYear(int
Date::LeapYear(inty)
y)
{{ programado é: um ano é
return
returnyy%%400
400==
==00||||yy%%44==
==00 &&
&&yy%
%100
100!=
!=0;
0; 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
intresult
result==0;
0; Isto é o construtor de cópia: a data temp é inicializado
Date temp(*this);
Date temp(*this); com uma cópia da data objecto da função, isto é, da
while (temp != other)
while (temp != other) data para a qual a função DaysTo foi chamada.
{{
temp.Forth();
temp.Forth(); Repare que se a data other for anterior à data objecto da
result++;
result++; função, o ciclo nunca termina.
}}
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
{{
return Recorde que em cada função membro, o apontador this é
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.
while (expressão) Fica aqui registada para referência.

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. O que se faz com while pode fazer-se com for e vice-
versa. A instrução while é mais expressiva quando
queremos colocar a ênfase na expressão de continuação.
A instrução for é mais expressiva quando queremos
realçar o papel da variável de controlo.

2003-07-19 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 Usa-se quando, por natureza, a instrução
tem de ser executa pelo menos uma vez.
instrução (Isso acontece necessariamente quando
algumas das variáveis que intervêm na
while (condição); expressão de continuação são inicializadas
no seio da instrução.)
Semântica:
1. Executa a instrução;
2. Avalia a expressão. Se der false, termina; se não:
3. Volta ao passo 1.
A instrução do é muito menos frequente do que as instruções while e for.

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
{{
return Usamos o this aqui apenas por uma questão de
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 && ...;
Aqui podíamos ter escrito
Ser diferente é não ser igual:
return !(*this == other);
bool
boolDate::operator
Date::operator!=
!=(const
(constDate&
Date&other)
other)const
const mas não
{{
return *this != other;
return
return!!operator
operator==
==(other);
(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
sas mais simples, bool Date::operator >= (const Date& other) const
{{
os outros operadores }}return return!!operator
operator<=<=(other)
(other)||
||operator
operator==
==(other);
(other);
bool
bool Date::operator > (const Date& other)const
Date::operator > (const Date& other) 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) Date&
Date&Date::operator
Date::operator--
-- (int)
(int)
{{ {{
Forth();
Forth(); Back();
Back();
return
return*this;
*this; 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; return
returnDate(*this)
Date(*this)-=
-=x;
x;
}} }}
Repare nesta técnica.

Date&
Date&Date::operator
Date::operator+=(int
+=(intx)
x) Date&
Date&Date::operator
Date::operator-=(int
-=(intx)
x)
{{ {{
Add(x);
Add(x); Subtract(x);
Subtract(x);
return
return*this;
*this; return
return*this;
*this;
}} }}

2003-07-19 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;
}} Estas duas são
funções virtuais.
void Pertencem à classe
voidDate::WriteLine(std::ostream&
Date::WriteLine(std::ostream&output)
output)const
const 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)
{{
d.Write(output); Esta é uma função
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 void
voidDate::Read(std::istream&
Date::Read(std::istream&input)
input)
de um ficheiro, usamos a {{
input
input>>
>>year
year>>
>>month>>
month>>day;
função Read: }}
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,constconststd::string&
std::string&errorMessage)
errorMessage)
{{
int Ciclo infinito. Quebra quando os três números lidos formarem
intyear
year==0;0;
int
intmonth
month==0; 0; uma data válida.
int
intdayday==0;0;
for(;;)
for(;;) A leitura falhará se o utilizador entrar caracteres
{{ que não possam pertencer a um número
std::cout
std::cout<<<<prompt;
prompt;
std::cin decimal (letras, por exemplo.)
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
endofofline
line
ifif(Date::Valid(year,
(Date::Valid(year,month,
month,day)) day))
break; Repare na técnica usada para saltar o
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”:
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);
}}
funções estáticas.
Estas três funções são

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) bool
boolDate::Valid(int
Date::Valid(inty)
y)
{{ {{
return
returnValid(y)
Valid(y)&&
&&11<=<=m m&&&&m
m<=
<=12;
12; return
returnfirstYear
firstYear<=
<=y; y;
}} }}
2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 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ça-
feira:
Inicialização da
const
constDate::WeekdayType
Date::WeekdayTypeDate::firstWeekday
Date::firstWeekday==TUESDAY;
TUESDAY; 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
voidTestWorstFridays13OfThisCentury()
TestWorstFridays13OfThisCentury() Eis o resultado na consola.
{{ Infelizmente, o programa leva
mas::Date
mas::Dated(2001,
d(2001,1, 1,1);
1); muito tempo a calcular isto.
int
intcount
count==0;0; Porquê?
while
while((d.Year()
((d.Year()--1)
1)//100
100==
==20)
20)
{{
ifif(d.Day()
(d.Day()====1313&&&&d.Weekday()
d.Weekday()==
==mas::Date::FRIDAY)
mas::Date::FRIDAY)
{{
count++;
count++;
ifif(count
(count%%1313==
==0) 0)
d.WriteLine();
d.WriteLine();
}} Quantas são as piores
d.Forth();
d.Forth(); sextas-feiras 13 do
}} 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{{ Usando esta nova implementação,
private:
private: o programa das piores sextas-
int
intcount;
count; //
//number
numberof
ofdays
dayssince
sincefirst.
first. feiras 13 fica instantâneo.
public:
public:
//...
//...
};
};
Com esta implementação, a maior parte das funções ficam
mais simples. Apenas as funções que convertem entre a
representação interna (um único número) e a representação
externa (ano, mês, dia) ficam mais complicadas.
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 Repare na maneira de especificar
namespacemas
mas{{
que a classe DatePortuguese
class
classDatePortuguese:
DatePortuguese:public
publicDate
Date{{ herda da classe Date.
//...
//...
virtual
virtualconst
conststd::string&
std::string&MonthName()
MonthName()const;
const; Estas são as funções que dão o
virtual
virtual const std::string& WeekdayName()const;
const std::string& WeekdayName() const; 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
virtual boolDatePortuguese::Holiday()
DatePortuguese::Holiday()const const
virtualbool
boolHoliday()
Holiday()const;
const; {{
return
returnNewYear()
NewYear()|| ||Liberty()
Liberty()||
||Workers()
Workers()
virtual
virtualbool
boolNewYear()
NewYear()const;
const; || Nation() || Assumption() || Republic()
virtual || Nation() || Assumption() || Republic()
virtualbool
boolLiberty()
Liberty()const;
const; ||
||AllSaints()
AllSaints()||
||Independence()
Independence()|| ||OurLady()
OurLady()
virtual
virtualbool
boolWorkers()
Workers()const;
const; || Christmas() || Carnival() || GoodFriday()
virtual || Christmas() || Carnival() || GoodFriday()
virtualbool
boolNation()
Nation()const;
const; ||
||Easter()
Easter()||||CorpusChristi();
CorpusChristi();
virtual
virtual bool Assumption()const;
bool Assumption() const; }}
virtual
virtualbool
boolRepublic()
Republic()const;
const;
virtual
virtual bool AllSaints()const;
bool AllSaints() const;
virtual
virtual bool Independence()const;
bool Independence() 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, TEXT_WITH_WEEKDAY,ARMY,
TEXT_WITH_WEEKDAY, 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
static voidSetFormat(FormatType
void 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
switch(format){
(format){ Note bem: se o formato for DEFAULT, usa-
case DEFAULT:
case DEFAULT: se a função Write da classe Date.
Date::Write(output);
Date::Write(output);
break;
break;
case Repare nos manipuladores: setfill
caseSTANDARD:
STANDARD: determina o carácter de
output
output<< <<std::setfill('0')
std::setfill('0')
<<
<<std::setw(2)
std::setw(2)<< <<Year()
Year()%%100100<< separator preenchimento; setw determina a
<<separator
<< std::setw(2) << Month() <<
<< std::setw(2) << Month() << separatorseparator largura do campo de escrita. É
<< std::setw(2) <<
<< std::setw(2) << Day() Day() preciso #include <iomanip>.
<<
<<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) { Primeiro avalia-se a expression. Depois, se
case expr_const1: houver uma etiqueta expr_const cujo valor é
statement1_1; igual ao da expression, o controlo passa para
... a instrução que vem a seguir a essa etiqueta,
break; e a execução prossegue sequencialmente até
case expr_const2: à próxima instrução break, a qual terminará a
statement2_1; 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
break;
instrução switch não tem efeito (para além do
... efeito eventualmente provocado pela
default: avaliação da expression.)
statement0_1;
... Note que se não houvesse aquelas instruções
É a mais break, o controlo seguiria sequencialmente
break; complicada de
até à chaveta } final.
} todas as instruções
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;
std::cin O primeiro ciclo conta os dias. O segundo
std::cin>> >>x; x;
while (x >
while (x > 0)0) avança até ao próximo dia útil.
{{
ifif(!d.Weekend()
(!d.Weekend()&& &&!d.Holiday())
!d.Holiday()) Aqui chamamos uma função estática através
x--;
x--;
d.Forth(); de um objecto da classe. Poderíamos ter
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
The null statement (just a
labeled-statement expressionopt ; semicolon) is in fact an expression-
expression-statement statement without an expression...
compound-statement
selection-statement { statement-seqopt } statement
iteration-statement statement-seq statement
jump-statement if ( condition ) statement
declaration-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 ; expression-statement
return expressionopt ; simple-declaration

block-declaration simple-declaration
...

2003-07-19 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
std::cout << static_cast<int>(s.size())<<
<< 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
std::cout << static_cast<int>(r.size())<<
<< 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
std::cout << static_cast<int>(q.size())<<
<< 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
std::cout << static_cast<int>(p.size())<<
<< 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
namespace
namespacemas mas{{ para “shiftar”
uma letra de
class Secret {
class Secret {
private: um certo
private:
std::string
std::stringkey;
key; número de
int number;
int number; lugares no
public:
public:
Secret(const
Secret(conststd::string&
std::string&key,
key,int
intnumber
number==1);
1);
alfabeto.
virtual
virtual~Secret();
~Secret();
virtual
virtual std::stringCiphered(const
std::string Ciphered(conststd::string&
std::string&x)x)const;
const;
virtual
virtual std::string Deciphered(const std::string&x)
std::string Deciphered(const std::string& 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
intm m==0; 0; para a cadeia não ter de crescer a
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],
result.push_back(Shifted(s[i],number));
number)); Se a letra não pertence à chave.
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,
return result;
return result; constante npos. Faça sempre find e push_back e o operador [].
}} 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
{{
result[j++] Não se esqueça: o argumento do operador de
result[j++]==key[mkey[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;
return result; indefinido.
}}

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 anexercise...
left as an 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:";
"; Repare na utilização da
std::getline(std::cin,
std::getline(std::cin,s);
s);
ifif(!std::cin) função std::getline.
(!std::cin)
break;
break;
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{{ que permitem a decifração
//...
//... unívoca, dada a chave.
virtual
virtualstd::list<int>
std::list<int>SecretNumbers()
SecretNumbers()const;
const;
};
};
std::list<int>
std::list<int>Secret::SecretNumbers()
Secret::SecretNumbers()const const
{{ Vector de booleanos,
std::list<int>
std::list<int>result;result; inicializado com 27 elementos,
std::vector<bool>
std::vector<bool>numbers(27,
numbers(27,true);
true); índices de zero a 26, todos a
for
for (int i = 0; i < static_cast<int>(key.size());i++)
(int i = 0; i < static_cast<int>(key.size()); i++) true.
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] Algumas das posições passam
numbers[x]==numbers[26-x]
numbers[26-x]==false;
false;
}} a false.
for
for(int
(inti i==1;
1;i i<<26;
26;i++)
i++)
ifif(numbers[i])
(numbers[i])
result.push_back(i); As que ficaram a true
result.push_back(i); correspondem aos números
return
returnresult;
result;
}} possíveis.

2003-07-19 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:
Estas funções estão no ficheiro Utilities_list.h. Confira
namespace
namespacemas
mas{{ com as funções análogas para vectores, página 59.
template Para usar listas é preciso fazer #include <list>.
template<class
<classT>
T>
void
void Write(conststd::list<T>&
Write(const std::list<T>&v,v,
const
const std::string&separator
std::string& separator==""",",std::ostream&
std::ostream&output
output==std::cout)
std::cout)
{{
for Aprecie esta técnica
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
void WriteLine(conststd::list<T>&
WriteLine(const 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,
Write(v,""",",output);
output); Também há funções para escrever listas de trás para a
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:"; void
mas::WriteLine(secret.SecretNumbers());
"; voidSecret::SetNumber(int
Secret::SetNumber(intnumber)
number)
mas::WriteLine(secret.SecretNumbers()); {{
std::cout
std::cout<< <<"Número:
"Número:"; this->number
int number;
"; this->number==number;
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 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
classStringBasic:
StringBasic:public
publicClonable
Clonable{{ Construtor de cópia
private:
private:
//...
//... Construtores de conversão.
public:
public:
StringBasic();
StringBasic(); Construtor de concatenação.
StringBasic(const
StringBasic(constStringBasic&
StringBasic&other);
other);
StringBasic(const
StringBasic(conststd::string&
std::string&s);
s); Construtor de cópia parcial.
StringBasic(const
StringBasic(const char*s);
char *s);
StringBasic(const
StringBasic(constStringBasic&
StringBasic&other1,
other1,const
constStringBasic&
StringBasic&other2);
other2);
StringBasic(const
StringBasic(const StringBasic& other, int startPos,int
StringBasic& other, int startPos, intendPos);
endPos);
//
//pre
preother.ValidRange(startPos,
other.ValidRange(startPos,endPos);
endPos);
explicit StringBasic(char c);
explicit StringBasic(char c);
StringBasic(char Construtor de intervalo de caracteres.
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
virtual capacidade).
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 class
classStringBasic:
StringBasic:public
publicClonable
Clonable{{
private:
membros de dados: um inteiro que private:
int
intcapacity;
capacity;
indica a capacidade corrente e um char
char*p;*p;
public:
apontador para uma cadeia public:
//...
//...
básica: 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:
Repare na utilização da funções
StringBasic::StringBasic(const
StringBasic::StringBasic(constchar
char*s):
*s): ::strlen, que dá o comprimento de
capacity(static_cast<int>(::strlen(s)+1)),
capacity(static_cast<int>(::strlen(s)+1)), uma cadeia básica, e ::strcpy, que
p(new
p(newchar[capacity])
char[capacity]) copia a cadeia básica que começa no
{{ endereço indicado no segundo
::strcpy(p, argumento para outra cadeia que
::strcpy(p,s);
s); começa na endereço indicado no
}} primeiro argumento.
2003-07-19 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:
namespace
namespacemas mas{{
A função Clone criará
class
dinamicamente um objecto igual
classClonable
Clonable{{
public:
public: ao objecto para o qual é
virtual Clonable* Clone() const = 0;
virtual Clonable* Clone() const = 0;
virtual
virtual~Clonable()
~Clonable(){};
{}; chamada, do mesmo tipo, e
};
};
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
virtual StringBasic& operator = (constStringBasic&
StringBasic& operator = (const 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
virtual void Set(const StringBasic& other,int
void Set(const StringBasic& other, intstartPos,
startPos,int
intendPos);
endPos);
//
// pre other.ValidRange(startPos,endPos);
pre other.ValidRange(startPos, endPos);
};
};

Todas estas funções copiam o argumento para o objecto,


usando técnicas diversas.
A afectação é uma cópia que devolve uma referência para o
objecto.
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)
{{ Repare no if. Ele protege
ifif(this
(this!=
!=&other)
&other) contra situações do
{{ género s.Copy(s).
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);
}}
}}

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)
{{ A função de biblioteca
std::swap(capacity,
std::swap(capacity,other.capacity);
other.capacity); std::swap é uma função
std::swap(p,
std::swap(p,other.p);
other.p); 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)
porque o argumento da função {{
other)
SwapOut não é passado por SwapOut(StringBasic(other));
SwapOut(StringBasic(other));
}}
referência constante. Isto está errado (mesmo
que alguns compiladores
2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003
deixem passar...) 149

Count, Capacity, Empty, Full


class
classStringBasic:
StringBasic:public
publicClonable
Clonable{{
Frequentemente estes quatro //...
//...
selectores aparecem juntos: virtual
virtualint
intCapacity()
Capacity()const;
const;
virtual int Count() const;
virtual int Count() const;
virtual
virtualbool
boolEmpty()
Empty()const;
const;
virtual
virtual bool Full()const;
bool Full() const;
};
};
São todos muito simples:
int
intStringBasic::Count()
StringBasic::Count()const
const bool
boolStringBasic::Empty()
StringBasic::Empty()const
const
{{ {{
return
returnstatic_cast<int>(::strlen(p));
static_cast<int>(::strlen(p)); return
return*p*p==
==0;0;
}} A função ::strlen conta os caracteres da }} Dá true se o primeiro carácter
cadeia básica até ao terminador. Quanto maior da cadeia (apontado por p) é o
a cadeia, mais tempo demora a contar... terminador (código zero).

int
intStringBasic::Capacity()
StringBasic::Capacity()const
const bool
boolStringBasic::Full()
StringBasic::Full()const
const
{{ {{
return
returncapacity;
capacity; 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 {{
um carácter: return
return00<=<=xx&&&&xx<<Count();
Count();
}}

Uma posição é válida se bool


boolStringBasic::ValidPosition(int
StringBasic::ValidPosition(intx)
x)const
const
corresponder a um índice {{
válido ou ao primeiro índice return
return00<=
<=xx&&&&xx<=<=Count();
Count();
}}
não válido:
bool
boolStringBasic::ValidRange(int
StringBasic::ValidRange(intx,x,int
inty)
y)const
const
Um intervalo é válido se {{
todos os índices desse return
returnValidPosition(x)
ValidPosition(x)&&
&&ValidPosition(y
ValidPosition(y++1)
1)
intervalo forem válidos: && x <= y +
&& x <= y + 1;1;
}}
2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 151

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

void Na função Extend, fazemos a


voidStringBasic::Extend(char
StringBasic::Extend(charc) c)
{{ cadeia crescer, se for preciso,
GrowTo(Count() + 2);
GrowTo(Count() + 2);
Put(c); para garantir que há espaço para
Put(c);
}} Extend é menos mais um carácter.
2003-07-19 eficiente, Curso
claro. de Programação com C++ © Pedro Guerreiro 2003 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] =
p[x] = c;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) A função ::memmove é uma função “de
{{ baixo nível”, do C. Serve para mover
int
intcount
count==Count();
Count(); sequências de bytes contíguos na memória
GrowTo(count
GrowTo(count++2); 2); de um lado para o outro. Por exemplo
::memmove(p+x+1,
::memmove(p+x+1,p+x, p+x,count+1-x);
count+1-x); ::memmove(q, r, n) move os bytes das
p[x] = c;
p[x] = c; 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
virtual void Insert(const StringBasic& other, int x); //pre
void Insert(const StringBasic& other, int x); // preValidPosition(x);
ValidPosition(x);
virtual
virtualvoid
voidReplace(int
Replace(intstartPos,
startPos,int
intendPos,
endPos,const
constStringBasic&
StringBasic&other);
other);
//
// pre ValidRange(startPos,endPos)
pre ValidRange(startPos, endPos)
virtual
virtual void Erase(int startPos, int endPos); //pre
void Erase(int startPos, int endPos); // 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) A precondição (que indica que x é um
{{ índice válido) garante que esta
p[x]
p[x]==c;
c; 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
constchar&
char&StringBasic::At(int
StringBasic::At(intx)
x)const
const de compatibilidade com outras classes
{{ que hão-de aparecer mais à frente. Se
return p[x];
return p[x]; não fosse isso, retornaria por valor.
}}

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() void
voidTestIndexOperators2()
TestIndexOperators2()
{{ {{
mas::StringBasic
mas::StringBasicss("lixboa");
("lixboa"); mas::StringBasic
mas::StringBasicss("lixboa");
("lixboa");
s[2]
s[2]=='s';
's'; s.PutAt('s',
s.PutAt('s',2);
2);
s.Extend(s[5]);
s.Extend(s[5]); s.Extend(s.At(5));
s.Extend(s.At(5));
s.WriteLine();
s.WriteLine(); 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 Por exemplo, se não houvesse o
operador const, isto estava errado: operador não const, isto estava
errado:
void
voidTestIndexOperators1()
TestIndexOperators1() void
voidTestIndexOperators3()
TestIndexOperators3()
{{ {{
mas::StringBasic
mas::StringBasicss("lixboa");
("lixboa"); const
constmas::StringBasic
mas::StringBasics("coimbra");
s("coimbra");
s[2] = 's';
s[2] = 's'; error: l-value specifies char c = s[2];
char c = s[2];
s.Extend(s[5]);
s.Extend(s[5]); const object //
//...
error: binary '[' : no operator found
...
s.WriteLine();
s.WriteLine(); }} which takes a left-hand operand of type
}} 'const mas::StringBasic' (or there is no
2003-07-19 acceptable
Curso de Programação com C++ © Pedro conversion)
Guerreiro 2003 158
Programam-se igualmente
O corpo dos dois operadores [] é idêntico:
const
constchar&
char&StringBasic::operator
StringBasic::operator[](int
[](intx)
x)const
const char&
char&StringBasic::operator
StringBasic::operator[](int
[](intx)
x)
{{ {{
return
returnp[x];
p[x]; return
returnp[x];
p[x];
}} }}

Também há uma função At não constante:


class
classStringBasic:
StringBasic:public
publicClonable
Clonable{{ char&
char&StringBasic::At(int
StringBasic::At(intx)
x)
//...
//... {{
virtual
virtualchar&
char&At(int
At(intx);
x); //
//pre
preValidIndex(x);
ValidIndex(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)
(this != &other) acrescentados com a função
{{ ::strcat, uma função de baixo
GrowTo(this->Count() + other.Count() +
GrowTo(this->Count() + other.Count() + 1); 1); nível.
::strcat(this->p,
::strcat(this->p,other.p);
other.p);
}} A instrução if serve para evitar problemas com a situação
else
else em que o argumento é o objecto: s.Append(s);. Neste
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);
Insert(other, 0); posição zero.
}}
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);
Head(startPos); Função Head: ver página seguinte.
Head(startPos);
GrowTo(Count()
GrowTo(Count() - (endPos - startPos + 1) + other.Count()++1);
- (endPos - startPos + 1) + other.Count() 1);
Append(other);
Append(other);
Append(temp); Sempre que um dos argumentos de
Append(temp); um modificador é do mesmo tipo
}}
else que o objecto, temos de ver se há
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,
2003-07-19 Curso de Programação com C++ © Pedrocomo
Guerreiro 2003 Append.
na função 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);
virtual void Tail(int n); //
// pre n >=0;
pre n >= 0;
};
};

Head é muito simples: Tail “puxa” para o princípio os


basta colocar o terminador caracteres que ficam, usando
na posição certa: ::memmove:
void void
voidStringBasic::Tail(int
StringBasic::Tail(intn)
voidStringBasic::Head(int
StringBasic::Head(intn)
n) {{
n)
{{
ifif(n int
intcount
count==Count();
Count();
(n<<Count())
Count()) ifif(n
p[n] (n<<count)
count)
p[n]==0;0; ::memmove(p,
}} ::memmove(p,pp++count count--n,
n,n+1);
n+1);
}}
2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 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
*p =
= 0;
0; Também se podia ter escrito p[0] = 0. Era
}} 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{{
//...
//... Isto é a comparação
virtual
virtualbool
booloperator
operator==
==(const
(constStringBasic&
StringBasic&other)
other)const;
const; lexicográfica, induzida
virtual bool operator != (const StringBasic& other) const;
virtual bool operator != (const StringBasic& other) const; pelos valores numéricos
virtual
virtualbool
booloperator
operator<=
<=(const
(constStringBasic&
StringBasic&other)
other)const;
const; dos caracteres que
virtual bool operator < (const StringBasic& other) const;
virtual bool operator < (const StringBasic& other) const; compõem a cadeia.
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;
};
};

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 inde-
terminado menor do que zero se a cadeia básica que começa
em x for lexicograficamente menor do que a cadeia básica que
começa em y, é zero se for igual, e é um número indeterminado
maior do que zero se for lexicograficamente maior.
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
virtual bool HasString(constStringBasic&
bool HasString(const StringBasic&other)
other)const;
const;
};
};

Programam-se directamente com as funções ::strchr e ::strstr:


bool A função ::strchr devolve um apontador (de tipo
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
return indicada no primeiro argumento, ou zero se não houver
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
cadeias e não return
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
virtual int Position(char c,int
int Position(char c, intstart)
start)const;
const; // //pre
preValidPosition(start);
ValidPosition(start);
virtual
virtual int Position(const StringBasic& other, intstart)
int Position(const StringBasic& other, int 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
virtual int LastPosition(const StringBasic& other, int start)const;
int LastPosition(const StringBasic& other, int start) const;
//
// pre
prestart
start<<Count();
Count();
};
};
int
intStringBasic::Position(char
StringBasic::Position(charc, c,int
intstart)
start)const
const
Eis uma delas, como {{
exemplo: const
constchar*
char*qq==::strchr(p+start,
::strchr(p+start,c); c);
return
return static_cast<int>(q != 0 ? q--pp::-1);
static_cast<int>(q != 0 ? q -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:
int Se p é um apontador
intStringBasic::Position(char
StringBasic::Position(charc, c,int
intstart)
start)const
const
{{ char* e n é um inteiro int,
const a expressão p+n é um
constchar*
char*qq==::strchr(p+start,
::strchr(p+start,c); c);
return apontador char* que
returnstatic_cast<int>(q
static_cast<int>(q!=!=00??qq--pp::-1);-1);
}} Começamos a procurar start
aponta n posições à direita
posições à direita de p, isto é, de p.
começamos a procurar na posição
start da cadeia.

int
intStringBasic::Position(char
StringBasic::Position(charc, c,int
intstart)
start)const
const
Se p e q são apontadores
{{ char*, a expressão q-p é
const
constchar*
char*qq==::strchr(p+start,
::strchr(p+start,c);c);
um número inteiro que dá
return o número de posições
return static_cast<int>(q != 0 ? q--pp::-1);
static_cast<int>(q != 0 ? q -1);
}} entre p e q.
A diferença q – p dá precisamente o índice
onde está o carácter c, quando q não é zero. Logo:
(Começámos a procurar na posição start.) q – p == n se e só se p + n == q.
2003-07-19 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{{ Por exemplo, a expressão
//...
//... s.CountIf("AEIOUaeiou")
virtual
virtualint
intCountIf(const
CountIf(constStringBasic&
StringBasic&other)
other)const;
const;
virtual representa o número de
virtual int CountIfNot(const StringBasic&other)
int CountIfNot(const StringBasic& other)const;
const; vogais na cadeia s.
};
};
int
intStringBasic::CountIf(const
StringBasic::CountIf(constStringBasic&
StringBasic&other)
other)const
const
{{
int
intresult
result==0;
0; Esta função é pouco eficiente, pois compara cada
for (int i=0; p[i] != 0; i++)
for (int i=0; p[i] != 0; i++) carácter do objecto com cada carácter do
result += other.Has(p[i]);
result += other.Has(p[i]); argumento. É para usar casualmente em situações
return result;
return result; não muito exigentes.
}}

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
virtual int PositionIfNot(const StringBasic& other)const;
int PositionIfNot(const StringBasic& other) const;
virtual
virtualint
intLastPositionIf(const
LastPositionIf(constStringBasic&
StringBasic&other)
other)const;
const;
virtual
virtualint
intLastPositionIfNot(const
LastPositionIfNot(constStringBasic&
StringBasic&other)
other)const;
const;
virtual
virtual int PositionIf(const StringBasic& other, int start)const;
int PositionIf(const StringBasic& other, int start) const; //
// pre
prestart
start>=
>=0;0;
virtual
virtual int PositionIfNot(const StringBasic& other, int start) const; // pre start >=0;
int PositionIfNot(const StringBasic& other, int start) const; // pre start >= 0;
virtual
virtualint
intLastPositionIf(const
LastPositionIf(constStringBasic&
StringBasic&other,
other,intintstart)
start)const;
const;
//
// pre
prestart
start<<Count();
Count();
virtual int LastPositionIfNot(const StringBasic& other, int
virtual int LastPositionIfNot(const StringBasic& other, int start) const; start) const;
//
// pre
prestart
start<<Count();
Count();
};
}; int
intStringBasic::PositionIf(const
StringBasic::PositionIf(constStringBasic&
StringBasic&other,other,int
intstart)
start)const
const
{{
Eis a defini- for
for(int
(inti i==start;
start;p[i]
p[i]!=
!=0;0;i++)
i++)
ção de duas ifif(other.Has(p[i]))
(other.Has(p[i]))
delas, sobre- return
returni;i; int
return -1; intStringBasic::PositionIf(const
StringBasic::PositionIf(constStringBasic&
StringBasic&other)
other)const
const
carregadas: }} return -1; {{
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{{ Eis as três Trim:
//...
//...
virtual void
voidStringBasic::TrimLeft()
virtualvoid
voidPrune(char
Prune(charc);
c); {{
StringBasic::TrimLeft()
virtual
virtual void PruneLast(charc);
void PruneLast(char c); int
intxx==PositionIfNot("
PositionIfNot("");
");
virtual
virtualvoid
voidPruneAll(char
PruneAll(charc);
c); ifif(x
(x ==-1)
== -1)
Clear();
Clear();
virtual
virtualvoid
voidPruneIf(const
PruneIf(constStringBasic&
StringBasic&other);
other); else
else
virtual
virtual void PruneIfNot(constStringBasic&
void PruneIfNot(const StringBasic&other);
other); Erase(0,
Erase(0,x-1);
x-1);
}}
virtual
virtualvoid
voidTrim();
Trim(); void
voidStringBasic::TrimRight()
virtual
virtual voidTrimLeft();
void TrimLeft();
StringBasic::TrimRight()
{{
virtual
virtualvoid
voidTrimRight();
TrimRight(); Head(LastPositionIfNot("
}; Head(LastPositionIfNot("")")++1);
1);
}; void
}}
voidStringBasic::Prune(char
StringBasic::Prune(charc)
c)
Eis a {{ void
voidStringBasic::Trim()
StringBasic::Trim()
definição da int
intxx==Position(c);
Position(c); {{
ifif(x
(x!=
!= -1)
-1) TrimRight();
TrimRight();
função Prune: RemoveAt(x); TrimLeft();
TrimLeft();
RemoveAt(x); }}
}}
2003-07-19 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
classStringBasic:
StringBasic:public
publicClonable
Clonable{{ o fim. Com argumentos, manda
//...
//... 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();
virtual
virtualvoid
voidSendToBack(int
SendToBack(intn); n);//
//pre
prenn>=
>=0;0; A função Reverse use um
virtual void BringToFront();
virtual void BringToFront(); ciclo for com duas variáveis
virtual void BringToFront(int n); // pre n >=
virtual void BringToFront(int n); // pre n >= 0; 0; de controlo:
void
voidStringBasic::Reverse()
StringBasic::Reverse()
virtual
virtualvoid
voidToLowercase();
ToLowercase(); {{
virtual
virtual voidToUppercase();
void ToUppercase(); 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:


bool A função ::strncmp não
boolStringBasic::StartsBy(const
StringBasic::StartsBy(constStringBasic&
StringBasic&other)
other)const
const compara até ao terminador,
{{
mas apenas até ao número
return
return::strncmp(this->p,
::strncmp(this->p,other.p,
other.p,other.Count())
other.Count())==
==0;0; de caracteres indicado no
}} terceiro argumento.
2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 174
Mais funções várias
Ainda há mais? Sim:
class
classStringBasic:
StringBasic:public
publicClonable
Clonable{{ Aumentar a capacidade.
//...
//...
virtual Ajustar a capacidade (libertando as posições não
virtualvoid
voidGrowTo(int
GrowTo(intn);
n); ocupadas).
virtual
virtual void Adjust(); //
void Adjust(); //post
postFull();
Full();
Soma dos valores numéricos dos caracteres.
virtual
virtualint
intSum()
Sum()const;
const;
virtual
virtual int Hash()const;
int Hash() const; 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
for(inti=0;
i=0;p[i]
p[i]!=
!=0;
0;i++)
i++) Exemplos disto mais à
ifif((x = from.Position(p[i])) != -1)
((x = from.Position(p[i])) != -1) frente.
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
A função Unique usa um
virtualbool
boolIsSorted()
IsSorted()const;
const;
virtual
virtual void Sort(); //
void Sort(); //post
postIsSorted();
IsSorted(); ciclo duplo. Veja com
virtual
virtualvoid
voidUnique();
Unique();
};
};
atenção:
void
voidStringBasic::Unique()
StringBasic::Unique()
{{
A função Sort usa o quicksort: int
intjj==0;
int
0;
inti i==0;
0;
void
voidStringBasic::Sort() while
StringBasic::Sort() while(p[i]
(p[i]!=
!=0)
0)
{{ {{
ifif(!Empty()) char
(!Empty()) charcc==p[i];
p[i];
Quicksort(0, Count() - 1);
Quicksort(0, Count() - 1); do
do
}} i++;
i++;
while
while(p[i]
(p[i]==
==c);c);
Note bem: função Unique não elimina os duplicados p[j++] =
p[j++] = c;c;
todos, mas apenas os duplicados contíguos. Se a cadeia }}
estiver ordenada, todos os duplicados iguais estão p[j]
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,intintupperBound)
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
charpivotpivot==p[(i+j)/2];
p[(i+j)/2]; implementações. Para evitar surpresas, convertemos
dodo para unsigned char antes de comparar.
{{
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 <=
while (i <= j);j);
ifif(lowerBound
(lowerBound<<j)j)
Quicksort(lowerBound, Nunca é demais
Quicksort(lowerBound,j); j);
ifif(i(i<<upperBound) recordar o quicksort.
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
virtual void Subtract(const StringBasic&other);
void Subtract(const StringBasic& other);
dos conjuntos: virtual
virtualbool
boolContains(const
Contains(constStringBasic&
StringBasic&other)
other)const;
const;
};
}; void
voidStringBasic::Intersect(const
StringBasic::Intersect(constStringBasic&
StringBasic&other)
other)
{{
Atenção: estas funções só StringBasic
StringBasicresult(Min(Count(),
int
result(Min(Count(),other.Count())
other.Count())++1);
1);
inti i==0; 0;
têm o significado int
intj j==0; 0;
int
intkk==0;
esperado se o objecto e o while
while(p[i]
0;
(p[i]!=
!=00&& &&other.p[j]
other.p[j]!=
!=0)
0)
argumento estiverem ifif(p[i]
{{
== other.p[j])
(p[i] == other.p[j])
ordenados. Observe a result.p[k++]
result.p[k++]==p[i];p[i];
i++,
i++,j++;
função Intersect. Se as }}
j++;
else
elseifif(p[i]
(p[i]<<other.p[j])
cadeias não estiverem i++;
i++;
other.p[j])

ordenadas o resultado else Observe a


else
j++;
j++; técnica do
não é a intersecção. result[k] =
result[k] = 0; 0; SwapOut.
SwapOut(result);
SwapOut(result);
2003-07-19 Curso de Programação}}
com C++ © Pedro Guerreiro 2003 178
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
classStringBasic:
StringBasic:public
publicClonable
Clonable{{ base, de 2 a 36.
//...
//...
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;
virtual bool IsDouble() const; As cadeias inteiras podem ter sinal mais ou
};
}; menos. As cadeias reais podem ter sinal, ponto
decimal e podem estar na notação científica.
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
int result;
result; trabalho de conversão é
char *endp;
char *endp; a função ::strtol. (Veja
errno =
errno = 0;0; a documentação.)
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;
return result; uma mensagem informativa construída à medida.
}}

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:
bool Repare no try-block seguido
boolStringBasic::IsInt(int
StringBasic::IsInt(intbase)
base)const
const
{{ do tratador de excepções.
bool
boolresult
result==true;
true; Quando há uma excepção no
try {
try { try-block para a qual existe um
AsInt(base);
AsInt(base);
}}catch
tratador, o tratador intervém,
catch(const
(constmas::StringBasic&)
mas::StringBasic&){{
result
result==false;
false;
substituindo o resto do bloco.
}} Se não houver tratador, a
return result;
return result; excepção propaga-se para o
}}
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;
}}catch Note bem: as excepções não
catch (const mas::StringBasic&e)
(const mas::StringBasic& 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
double result;
result; ::strtod.
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()constconst
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
saber se há excepção ou result==false;
false;
}}
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 =
static StringBasic Fixed(double x, int precision = 6); 6);
static
staticStringBasic
StringBasicScientific(double
Scientific(doublex,x,int
intprecision
precision==6);
6);
};
}; void TestStaticPseudoConstructors()
void TestStaticPseudoConstructors()
{{
for(;;)
Eis uma função {{
for(;;)
double
doublex;
de teste, que std::cout
x;
std::cout<< <<"um
"umnumero:
numero:";";
std::cin
std::cin>>>>x;
mostra como se ifif(!std::cin)
(!std::cin)
x;

usa: int
break;
break;
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();
das funções é um objecto mas::StringBasic::Fixed(x).WriteLine();
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 }}Programação com C++ © Pedro Guerreiro 2003
Curso de 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
friend std::istream& operator >>(std::istream&
std::istream& operator >> (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
{{
output std::ostream&
std::ostream& operator << (std::ostream&output,
operator << (std::ostream& output,const
constStringBasic&
StringBasic&s)
s)
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
static const StringBasicuppercaseLetters;
const StringBasic uppercaseLetters;
static const StringBasic lowercaseLetters;
static const StringBasic lowercaseLetters;
static
staticconst
constStringBasic
StringBasicletters;
letters;
static const StringBasic digits;
static const StringBasic digits; Temos aqui membros de dados
static públicos, o que só é perdoável
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 Gene::Gene()
namespacemas
mas{{ Gene::Gene()
{{
}}
class
classGene:
Gene:public
publicStringBasic
StringBasic{{
Gene::Gene(const
Gene::Gene(constStringBasic&
StringBasic&s):
private:
private: s):
StringBasic(s)
StringBasic(s)
public:
public: {{
Gene(); }}
Gene();
Gene(const
Gene(constStringBasic&
StringBasic&s);
s); int
intGene::AsInt()
Gene::AsInt()const
const
virtual int AsInt() const;
virtual int AsInt() const; {{
static StringBasic
StringBasictemp(*this);
staticGene
GeneFromInt(int
FromInt(intx);
x); 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;
2003-07-19 }
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
virtual voidRead(std::istream&
void Read(std::istream&input);
input);
virtual
virtual int Count(const Gene& g)const;
int Count(const Gene& g) const;
virtual
virtual std::list<Gene> Top(int n)const;
std::list<Gene> Top(int n) const;
virtual
virtual std::list<Gene> ThoseMoreFrequentThan(intx)
std::list<Gene> ThoseMoreFrequentThan(int 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
intnn==0; 0; Isto não é lá muito ortodoxo, mas é prático.
count.clear();
count.clear(); (Deveríamos usar uma constante, ...)
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()]++;
count[Gene(s).AsInt()]++; convém.
n++;
n++;
}} O primeiro elemento para é a frequência, o segundo é
frequencies.clear(); o gene. Assim, ao ordenar, o critério principal é a
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] >
(count[i] > 0) 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
int j = static_cast<int>(frequencies.size())--1;
j = static_cast<int>(frequencies.size()) 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
return
returncount[g.AsInt()];
count[g.AsInt()]; máxima não era preciso ordenar. Bastava achar a
frequência máxima numa passagem e depois todos os
}} dessa frequência noutra passagem.
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:
private: std::string e o valor é um int. Neste caso, o valor
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
virtual voidRead(std::istream&
void Read(std::istream&input);
input); Estas três
virtual int Count(const std::string& g) const;
virtual int Count(const std::string& g) const; funções agora
virtual std::list<std::string> Top(int n) const;
virtual std::list<std::string> Top(int n) const; usam o tipo
virtual std::string, em
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
Contando com o mapa
Veja bem:
void
voidGenesMap::Read(std::istream&
GenesMap::Read(std::istream&input)
input)
{{ Perceba a expressão count[s]:
count.clear(); 1) se não existir no mapa um elemento com chave s, é
count.clear(); criado um, com valor zero, e a expressão representa
std::string
std::strings;
s; um referência para o valor (que vale zero)
2) se existir, a expressão representa a referência para
while
while(input
(input>>>>s)
s) o valor associado.
count[s]++;
count[s]++; Logo, a expressão count[s]++, “conta” mais uma
ocorrência de s.
frequencies.clear();
frequencies.clear();
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&
int GenesMap::Count(const std::string& g) const g) const const e o operador [] da classe
{{ std::map<K, T> não é const.
return Repare na técnica.
returnconst_cast<GenesMap*>(this)->count[g];
const_cast<GenesMap*>(this)->count[g];
}} Dizemos que o
const_cast remove a
As outras são ainda mais simples do que antes: “constância”. De facto,
o que estamos a fazer é
std::list<std::string>
std::list<std::string>GenesMap::Top(int
GenesMap::Top(intn) n)const
const a aceder ao mapa
{{ através de um
std::list<std::string>
std::list<std::string>result;
result; apontador não
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);
result.push_back(frequencies[j].second); objecto da função. Esse
return
returnresult;
result; apontador é obtido
}} “ignorando” a
constância do
std::list<std::string>
std::list<std::string>GenesMap::ThoseMoreFrequentThan(int
GenesMap::ThoseMoreFrequentThan(intx) x)const
const apontador this. Note
{{ bem: na funções const,
std::list<std::string>
std::list<std::string>result;
result; o apontador this é um
int
intj j==static_cast<int>(frequencies.size())
static_cast<int>(frequencies.size())--1; 1; apontador const; nas
while
while(j(j>= >=00&& &&frequencies[j].first
frequencies[j].first>=
>=x)
x) funções não const, o
result.push_back(frequencies[j--].second);
result.push_back(frequencies[j--].second); apontador this é um
return
returnresult;
result; apontador não const.
}}

2003-07-19 Curso de Programação com C++ © Pedro Guerreiro 2003 196

You might also like