| Categoria de acesso dos membros | Acesso |
|---|---|
| privado (private:) | Só membros e amigos (mas a amizade não é hereditária) da classe. |
| protegido (protected:) | Membros e amigos (mas a amizade não é hereditária) da classe e de classes derivadas. |
| público (public:) | Sem restrições. |
Quando se deriva uma classe a partir de outra a derivação pode também ser pública, protegida ou privada. Sendo B uma classe derivada de A:
class A {onde categoria pode ser public, protected ou private, tem-se:
public:
...
protected:
...
private:
...
};class B : categoria A {
public:
...
protected:
...
private:
...
};class C : categoria B {
...
};
| Categoria de acesso da herança | Acesso aos membros de A (classe base) | Relação entre B (classe derivada) e A (classe base) | |||
|---|---|---|---|---|---|
| Categoria de acesso dos membros de A (classe base) | Acesso através de B (classe derivada) | Do ponto de vista do público em geral | Do ponto de vista das classes derivadas de B (e.g. C) | Do ponto de vista de B (classe derivada) | |
| privado (private) | privado (private:) | Ninguém. | Não há relação.
(Ou melhor, pode ser considerada uma relação de composição, ou seja, a classe derivada tem uma classe base.) |
Não há relação. | B é um A. |
| protegido (protected:) | Só membros e amigos de B. | ||||
| público (public:) | Só membros e amigos de B. | ||||
| protegido (protegido) | privado (private:) | Ninguém. | Não há relação. | Não há relação.
Mas, do ponto de vista de C, há relação entre C e A: C é um A. |
B é um A. |
| protegido (protected:) | Membros e amigos de B e de classes derivadas desta (e.g., C). | ||||
| público (public:) | Membros e amigos de B e de classes derivadas desta (e.g., C). | ||||
| público (public) | privado (private:) | Ninguém. | B é um A. | B é um A. | B é um A. |
| protegido (protected:) | Membros e amigos de B e de classes derivadas desta (e.g., C). | ||||
| público (public:) | Sem restrições. | ||||
Não tente decorar nem perceber à primeira esta tabela. Recorra a ela quando sentir dificuldades.
ListaPonteiroEmpregado pessoal;embora compilasse correctamente, tinha um comportamento pouco desejado: todos os objectos na lista, incluindo chefes e empregados, eram mostrados como simples empregados. Este comportamento não é surpreendente, visto que a todos os objectos se faz referência através de ponteiros para a classe Empregado.
// Inserção dos empregados:
pessoal.põeFim(new Empregado("Manela", "Rua B", feminino));
pessoal.põeFim(new Chefe("Zé", "Rua A", masculino, 2));
...
// Visualização dos empregados:
for(ListaPonteiroEmpregado::Iterador i = pessoal.primeiro();
i != pessoal.fim();
++i)
(*i)->mostra();
Ponteiros de uma classe base pública podem conter endereços de objectos de classes derivadas. Dois tipos de comportamento são portanto plausíveis ao se invocar através de um ponteiro para a classe base uma função ou procedimento membro dessa classe que a classe derivada ocultou fornecendo a sua própria versão:
Ao se desenhar a classe Empregado, e reconhecendo-se a vantagem de permitir derivações futuras, dever-se-ia ter declarado o procedimento mostra() como virtual:
class Empregado {Note-se que a classe Empregado é polimórfica e no entanto as funções membro nome(), morada() e sexo() não são virtuais. Aliás não o são por boas razões: não é suposto que estas funções sejam redefinidas em classes derivadas!
public:
Empregado(string const& nome, string const& morada, Sexo sexo)
: nome_(nome), morada_(morada), sexo_(sexo) {
}
string nome() const {
return nome_;
}
string morada() const {
return morada_;
}
Sexo sexo() const {
return sexo_;
}
virtual void mostra() const {
cout << "Nome: " << nome() << endl
<< "Morada: " << morada() << endl
<< "Sexo: " << sexo() << endl;
}private:
string nome_;
string morada_;
Sexo sexo_;
};
Se uma classe derivada de uma classe polimórfica redefinir uma função ou procedimento membro virtual de uma classe base, diz-se que sobrepôs a sua própria especialização da função ou procedimento. Note-se que só é uma sobreposição se a a função ou procedimento original for virtual! Caso contrário trata-se de uma simples ocultação. Por exemplo, o procedimento mostra() da classe Chefe sobrepõe-se ao procedimento mostra() da classe Empregado:
class Chefe : public Empregado {Uma função ou procedimento membro sobreposto a uma função ou procedimento membro virtual de uma clase base é sempre também virtual. Note-se, no entanto, que a sobreposição não é obrigatória. Uma classe derivada pode-se limitar a herdar uma função ou procedimento membro virtual sem lhe sobrepor uma versão própria. Por exemplo:
public:
Chefe(string const& nome, string const& morada, Sexo sexo, int nível)
: Empregado(nome, morada, sexo), nível_(nível) {
}
int nível() const {
return nível_;
}
virtual void mostra() const {
Empregado::mostra();
cout << "Nível: " << nível() << endl;
}
private:
int nível_;
};
class A {Neste exemplo existem três classes, todas elas polimórficas. Todas possuem um procedimento virtual void f(). A classe base A define o procedimento virtual f(), a classe B herda-o, e a classe C herda-o por intermédio de B e sobrepõe-lhe uma versão especializada, também virtual por se sobrepor a um procedimento virtual. Neste caso a invocação de f() através de um ponteiro para A (tipo A*) resulta na invocação de:
public:
virtual void f() {
...
}
...
};class B : public A {
...
};class C : public B {
public:
void f() {
...
}
...
};
class C : public B {Aliás, mesmo sendo redundante, é boa prática deixar claro que uma função ou procedimento membro é virtual, pois isso facilita a vida de um programador que pretenda definir uma nova classe por derivação da classe em causa: é que se o especificador virtual for omitido, o programador terá de inspeccionar as sucessivas classes base até esclarecer se a função ou procedimento membro é ou não virtual.
...
public:
virtual void f() {
...
}
...
};
Note-se que quando uma classe derivada define uma função ou procedimento com o mesmo nome que uma função ou procedimento de uma classe base (directa ou indirecta), a função ou procedimento da classe base fica oculta. Isto passa-se mesmo que a função ou procedimento da classe derivada tenha uma assinatura diferente da da classe base. Recorda-se que uma assinatura consiste no número e tipo de parâmetros de uma função ou procedimento.
Por exemplo:
class A {Uma ocultação só é uma sobreposição quando:
public:
int f(int i) {
return i;
}
};class B : public A {
public:
void f() { // ocultação de A::f()
int j =f(10);// erro: compilador não encontra A::f() porque está oculta.
int k = A::f(10); // ok: o prefixo A:: permite ao compilador encontrar a função
// pretendida.
}
};
class A {* Na realidade o tipo de devolução pode ser diferente, desde que seja covariante (ver [2, pág. 425]).
public:
virtual int g(int);
};class B : public A {
public:
double g(int);// erro: mesma assinatura que A::g() virtual (sobreposição), mas com tipo
// de devolução incompatível.
};
class A {O que sucede quando, na função main(), se destrói a variável dinâmica da classe B cujo endereço está guardado na variável pa do tipo A*? Como sempre, o destrutor é invocado. Mas, como o destrutor de A (fornecido automaticamente pela linguagem) não é virtual, é o destrutor de A que é invocado, e não o de B, não se chegando portanto a destruir a variável dinâmica do tipo float construída no construtor de B! Isto significa que classes polimórficas devem ter sempre destrutores virtuais! Ou seja, a classe A deveria ter sido definida como:
public:
virtual void f();
...
// Sem destrutor explícito (compilador fornece um automaticamente).
};class B : public A {
public:
B() {
pf = new float(0.0f);
}
~B() {
delete pf;
}
...
private:
float* pf;
...
};...
int main() {
B* pb = new B;
A* pa = pb;
delete pa;
}
class A {Logo a base da hierarquia de classes representando tipos de empregados na empresa deve ter também um destrutor virtual:
...
public:
...
// Com destrutor explícito virtual, mesmo que vazio:
virtual ~A() {}
};
class Empregado {
public:
Empregado(string const& nome, string const& morada, Sexo sexo)
: nome_(nome), morada_(morada), sexo_(sexo) {
}
virtual ~Empregado() {}
string nome() const {
return nome_;
}
string morada() const {
return morada_;
}
Sexo sexo() const {
return sexo_;
}
virtual void mostra() const {
cout << "Nome: " << nome() << endl
<< "Morada: " << morada() << endl
<< "Sexo: " << sexo() << endl;
}private:
string nome_;
string morada_;
Sexo sexo_;
};
A este comportamento, em que a função ou procedimento a executar só é determinado durante a execução do programa, chama-se ligação dinâmica (dinamic binding), por oposição a ligação estática (static binding).
Recorde-se de novo o exemplo da lista de empregados:
// Visualização dos empregados:O compilador, ao observar o código acima, não tem qualquer possibilidade de saber se os ponteiros na lista apontam para objectos que são empregados, chefes, ou instâncias de qualquer outra classe derivada directa ou indirectamente da classe Empregado. Isso significa que, sendo o procedimento mostra() virtual na classe base Empregado, a decisão de qual o procedimento a invocar em cada caso tem de ser tomada durante a execução do programa.
for(ListaPonteiroEmpregado::Iterador i = pessoal.primeiro();
i != pessoal.fim();
++i)
(*i)->mostra();
Aliás, a função ou procedimento realmente invocada pode pertencer a uma classe que nem sequer existia quando a classe base foi criada. Esta é uma das enormes vantagens das funções e procedimentos membro virtuais e da ligação dinâmica: para acrescentar mais um tipo de empregado da empresa basta definir uma nova classe derivada de empregado. A classe empregado e todo o código já existente para lidar com os empregados não precisa de ser alterado (salvo, bem entendido, no local onde se criam os empregados, por exemplo para os inserir na lista).
A ligação dinâmica, que está na base de toda a verdadeira programação orientada para objectos, leva a que a invocação de funções ou procedimentos membro virtuais seja um pouco menos eficiente do que a invocação de funções ou procedimentos normais. Mas isso não deve servir de justificação para a não utilizar o polimorfismo onde ele for apropriado!
// Visualização dos empregados:A qualificação explícita dá sempre origem à ligação estática: o compilador fica a saber imediatamente a versão da função ou procedimento a invocar. De resto, é isto que permite, durante a especialização do procedimento mostra() feito pela classe Chefe, invocar o procedimento mostra() da classe Empregado usando ligação estática (se a ligação fosse dinâmica também neste caso acabar-se-ia com uma chamada recursiva ao procedimento mostra() da classe Chefe):
for(ListaPonteiroEmpregado::Iterador i = pessoal.primeiro();
i != pessoal.fim();
++i)
(*i)->Empregado::mostra();
void Chefe::mostra() const {
Empregado::mostra(); // invocação com ligação estática.
cout << "Nível: " << nível() << endl;
}
/**
Classe Quadro. Representa um quadro onde se pode desenhar. O quadro está
dividido em rectângulos organizados em linhas e colunas. Cada rectângulo
chama-se um píxel. A linha do topo é a linha 0 (zero). A coluna da
esquerda é a coluna 0 (zero).
Os píxeis podem ser pintados de branco ou de preto. A razão entre a
largura e altura dos píxeis chama-se aspecto. É possível escrever um
quadro num canal, usualmente ligado ao ecrã. Nesse caso cada píxel é
representado por um caracter, sendo ' ' usado para a cor preta e '×' usado
para a cor branca.
*/
class Quadro {
public:
// Usa-se preto e branco:
typedef PB Cor;/**
Constrói um quadro com tamanho dado (24x80 por omissão) e com fundo
da cor fundo (preto por omissão). Os píxeis válidos têm posições entre 0 e
tamanho.linhas() - 1 (para a linha) e entre 0 e tamanho.colunas() - 1
(para a coluna). A razão entre a largura e a altura dos píxeis é dada por aspecto:
*/
Quadro(Tamanho const& tamanho = Tamanho(24, 80),
Cor fundo = preto,
double aspecto = 1.0);// Pintura de um pixel numa dada posição com uma dada cor:
void pinta(Posicao const& posicao, Cor const& cor);// Pintura de um pixel numa dada posição com um cor contrastante com o
// fundo do quadro:
void pinta(Posicao const& posicao);// "Despinta" um pixel numa dada posição pintando-o com a cor do fundo:
void limpa(Posicao const& posicao);// Pinta todo o quadro com a cor do fundo:
void apaga();// Escreve o quadro no canal saida (que se presume ligado a um ecrã em modo
// texto), com conversão para os caracteres ' ' e '×' (preto e branco
// respectivamente):
void mostra(std::ostream& saida) const;// Devolve a cor do pixel na posição dada. Se a posição estiver fora do
// quadro devolve a cor do fundo:
Cor cor(Posicao const& posicao) const;// Indica se uma dada posição está dentro do quadro:
bool dentro(Posicao const& posicao) const;// Devolve a cor do fundo:
Cor fundo() const;// Devolve a relação largura/altura dos píxeis do quadro:
double aspecto() const;// Devolve o tamanho do quadro:
Tamanho tamanho() const;private:
...
};
Note-se que a classe base Forma não foi introduzida apenas para que se pudessem criar listas de ponteiros para objectos de vários tipos de forma. De facto, é possível falar de formas em abstracto sem que se refira nenhum tipo de forma em concreto. Sob este ponto de vista, a classe Forma representa um conceito abstrato. Por outro lado, a classe Quadrado representa o conceito concreto de quadrado. Note-se que a diferença que se estabeleceu entre conceito concreto ou abstracto tem a ver com a possibilidade de existência física de exemplos (ou instâncias) desse conceito. Quadrados já todos vimos, mas nunca vimos uma forma que fosse simplesmente uma forma... As formas que se vêem são sempre concretizações vêem são exemplos de conceitos mais concretos: triângulos, polígonos, mas não simplesmente formas.
A mesma observação se poderia ter feito acerca da hierarquia de classes representando os seres vivos (de acordo com a taxonomia usada na biologia) apresentada na Aula 7. Dessa hierarquia fazem parte, por exemplo, gorilas e chimpanzés:
class Pongídeo /* família */ : public Primata {Uma ida ao Jardim Zoológico permitirá observar exemplares (instâncias) das espécies (classes) Gorilla gorilla e Pan troglodytes (o chimpanzé), bem como, do outro lado das grades, a sub-espécie Homo sapiens sapiens. Mas não se encontrará em qualquer jaula um exemplar da família dos primatas: encontram-se exemplares de espécies e sub-espécies que são primatas, mas não exemplares que sejam simplesmente primatas, não pertencendo a qualquer espécie. A classe Primata, na hierarquia apresentada, representa pois um conceito abstrato que sumariza as características comuns às várias espécies dessa família, essas sim representadas por classes concretas como Homo_sapiens_sapiens ou Pan_troglodytes.
...
};
class Gorilla /* género */ : public Pongídeo {
...
};
class Gorilla_gorilla /* espécie */ : public Gorilla {
...
};
class Pan /* género */ : public Pongídeo {
...
};
class Pan_troglodytes /* espécie */ : public Pan {
...
};
class Forma {A classe possui uma variável membro posicao_ que guarda a posição corrente da forma e que pode ser inicializada através do construtor. A função posicao() devolve essa posição e é uma função membro normal, não-virtual, por parecer pouco plausível que alguma classe derivada necessite de sobrepor uma versão especializada a esta função. O mesmo não se passa com o procedimento move(), pois pode haver necessidade de fornecer especializações em classes derivadas, quer porque se podem conceber formas com restrições nos modos de movimentação, quer porque para determinadas formas pode haver movimentos que não impliquem uma alteração da posição de origem. Assim, o procedimento move() foi declarado virtual, para que a classe forma apresente comportamento polimórfico durante invocações deste procedimento por intermédio de ponteiros ou referências e portanto formas diferentes se possam mover de forma diferente.
public:
// Construtor (dada uma posição):
Forma(Posicao const& posicao)
: posicao_(posicao) {
}// Destrutor virtual. Garante que os destrutores das classes derivadas
// são invocados mesmo que através dum ponteiro para a classe Forma:
virtual ~Forma() {}// Devolve a posição da forma:
Posicao posicao() const {
return posicao_;
}// Desloca a forma para nova posição:
virtual void move(Posicao const& posicao) {
posicao_ = posicao;
}// Desenha a forma num quadro.
virtual void desenha(Quadro& quadro) const;private:
// A posição da forma (um ponto particular, como um centro ou canto):
Posicao posicao_;
};
O procedimento desenha() é um pouco mais delicado. É óbvio que desenha() deve ser declarado como procedimento virtual, pois cada classe derivada concreta saberá como desenhar-se e portanto sobreporá uma especialização a este procedimento. Mas como definir desenha() na classe Forma? Sendo Forma uma representação duma abstracção, conclui-se que não tem qualquer sentido definir esse procedimento: é possível desenhar um quadrado ou um círculo, mas não uma forma que não seja nenhuma forma concreta. Não se pode desenhar uma pura abstracção.
Mas a linguagem C++ obriga a definir todas as funções ou procedimentos declarados. Que fazer? O C++ permite, nestes casos, declarar a função ou procedimento membro não apenas virtual mas puramente virtual. Para isso coloca-se a estranha construção = 0 após o cabeçalho da função ou procedimento. É que as funções ou procedimentos membro puramente virtuais não precisam de ser definidos!
Claro está que um função ou procedimento membro puramente virtual que não tenha sido definido também não podem ser invocados. Ou melhor, pode, desde que o seja uma forma polimórfica, isto é, invocados através dum ponteiro que endereça na realidade um objecto de uma classe derivada que sobreponha uma versão própria dessa função ou procedimento.
A linguagem C++ resolve este problema dizendo que as classes com funções ou procedimentos membro puramente virtuais são abstractas, i.e., são classes das quais não se podem criar instâncias.
A solução no caso das figuras passa, então, por tornar a classe Forma abstracta, o que de resto está de acordo com a discussão anterior, onde se viu que forma era um conceito abstracto:
class Forma {Classes derivadas são abstractas se declararem alguma função membro como puramente virtual ou se não sobrepuserem definições a todas as funções ou procedimentos puramente virtuais herdados das classes base. Assim, as formas concretas, como círculos ou quadrados, derivadas por herança pública da classe base Forma, deverão sempre sobrepor uma especialização do procedimento desenha(), pois caso contrário seriam também classes abstractas.
...
/**
Desenha a forma num quadro. Sendo uma forma um conceito abstracto, é
claro não se pode dizer COMO desenhá-la. Formas concretas (que serão
derivadas desta classe) saberão como fazê-lo. Por exemplo, um quadrado
é uma forma e sabe-se desenhar. Este procedimento é, portanto,
puramente virtual, sendo a sua existência que torna a classe Forma
abstracta.
*/
virtual void desenha(Quadro& quadro) const = 0;
...
};
Como não se podem criar instâncias de classes abstractas, o código que se segue produz um erro de compilação:
Forma trambolho(Posicao(10, 30));
// Um tipo específico de forma: quadrado aberto com lados paralelos aos eixosA sobreposição da versão especializada do procedimento desenha(), que é o único puramente virtual herdado, tem como resultado que Quadrado seja uma classe concreta, i.e., uma classe da qual se podem criar instâncias. Por exemplo, pode-se criar um quadrado como se segue:
// coordenados. Posição é o canto superior direito:
class Quadrado : public Forma {
public:
// Construtor, dadas a posição e a dimensão do lado (em colunas do quadro):
Quadrado(Posicao const& posicao, int lado);
: Forma(posicao), lado_(lado) {
}// Devolve dimensão do lado em colunas do quadro:
int lado() const {
return lado_;
}// Desenha o quadrado no quadro, compensando o facto dos píxeis serem
// rectangulares:
virtual void desenha(Quadro& quadro) const;private:
// Dimensão do lado (em colunas):
int lado_;
};// Desenha o quadrado no quadro q, compensando píxeis com relação largura/altura
// quadro.aspecto():
void Quadrado::desenha(Quadro& quadro) const {
... // Ver implementação mais abaixo...
}
Quadrado quadrado(Posicao(10, 20), 7); // quadrado em (10, 20) com lado 7.Da mesma forma se poderiam definir outras formas concretas, tais como círculos e triângulos. Todas elas derivariam directa ou indirectamente, e sempre de forma pública, da classe Forma. Assim, devido ao facto de este tipo de herança representar relações é um, seria possível guardar endereços de objectos (instâncias) de qualquer forma concreta numa lista de ponteiros do tipo Forma*.
Note-se que essa lista só poderia conter endereços de instâncias de classes concretas, pois o C++ proíbe a criação de instâncias de classes abstractas, independentemente de serem variáveis estáticas ou dinâmicas. É isso que permite garantir que a invocação do procedimento desenha() através dos ponteiros na lista está sempre bem definida (ver programa de teste na Secção 2.4.9).
Note-se que a construção da classe abstracta Forma e das classes concretas que dela derivam permite resolver vários problemas.
Em primeiro lugar, permite quardar num único contentor (e.g., uma lista), ponteiros do tipo Forma* (ou referências do tipo Forma&) para objectos das várias classes concretas derivadas garantindo, devido ao polimorfismo introduzido pelas funções e procedimentos virtuais, que a invocação de desenha() através desses ponteiros (ou referências) invoca a especialização do procedimento para a classe efectiva do objecto.
Em segundo lugar, consegue-se garantir que não podem ser criadas intâncias de uma classe que representa um conceito abstracto como o de forma, uma vez que existe um procedimento puramente virtual que torna a classe abstracta.
Finalmente, reaproveita-se código, pois aquilo que é realmente comum a todas as formas, a sua posição, fica representado directamente na classe base, sem necessidade de ser reproduzido em cada classe derivada. As classes derivadas dedicam-se apenas ao que é essencial: representar aquilo que lhes é específico.
#ifndef UTIL_Hutil_impl.H
#define UTIL_Htemplate <class R, class T>
R arredonda(T v);template <class T>
T quadrado(T v);#include "util_impl.H"
#endif // UTIL_H
template <class R, class T>Os modelos (templates) serão vistos na Aula 11.
inline R arredonda(T v) {
return v < 0 ? R(v - 0.5) : R(v + 0.5);
}template <class T>
inline T quadrado(T v) {
return v * v;
}
#ifndef COR_Hcor_impl.H
#define COR_H// Um tipo para preto e branco:
enum PB {preto, branco};// Devolve a cor que contrasta com cor:
PB contrasta(const PB& cor);// Devolve um caracter que represente aproximadamente a cor cor num ecrã
// em modo texto:
char caractere(const PB& c);#include "cor_impl.H"
#endif // COR_H
inline PB contrasta(const PB& cor) {
return cor == preto ? branco : preto;
}inline char caractere(const PB& cor) {
return cor == preto ? ' ' : '×';
}
#ifndef POSICAO_Hposicao_impl.H
#define POSICAO_H// Classe para representar posições:
class Posicao {
public:
Posicao(int linha = 0, int ccluna = 0);void move(int linha = 0, int coluna = 0);
int linha() const;
int coluna() const;private:
int linha_;
int coluna_;
};#include "posicao_impl.H"
#endif // POSICAO_H
inline Posicao::Posicao(int linha, int coluna)
: linha_(linha), coluna_(coluna) {
}inline void Posicao::move(int linha, int coluna) {
linha_ = linha;
coluna_ = coluna;
}inline int Posicao::linha() const {
return linha_;
}inline int Posicao::coluna() const {
return coluna_;
}
#ifndef TAMANHO_Htamanho_impl.H
#define TAMANHO_H// Classe para representar tamanhos:
class Tamanho {
public:
Tamanho(int linhas = 0, int colunas = 0);int linhas() const;
int colunas() const;private:
int linhas_;
int colunas_;
};#include "tamanho_impl.H"
#endif // TAMANHO_H
#include <cassert>inline Tamanho::Tamanho(int linhas, int colunas)
: linhas_(linhas), colunas_(colunas) {
assert(linhas_ >= 0 && colunas_ >= 0);
}inline int Tamanho::linhas() const {
return linhas_;
}inline int Tamanho::colunas() const {
return colunas_;
}
#ifndef QUADRO_Hquadro_impl.H
#define QUADRO_H#include <iostream>
#include <vector>#include "cor.H"
#include "posicao.H"
#include "tamanho.H"/**
Classe Quadro. Representa um quadro onde se pode desenhar. O quadro está
dividido em rectângulos organizados em linhas e colunas. Cada rectângulo
chama-se um píxel. A linha do topo é a linha 0 (zero). A coluna da
esquerda é a coluna 0 (zero).
Os píxeis podem ser pintados de branco ou de preto. A razão entre a
largura e altura dos píxeis chama-se aspecto. É possível escrever um
quadro num canal, usualmente ligado ao ecrã. Nesse caso cada píxel é
representado por um caracter, sendo ' ' usado para a cor preta e '×' usado
para a cor branca.
*/
class Quadro {
public:
// Usa-se preto e branco:
typedef PB Cor;/**
Constrói um quadro com tamanho dado (24x80 por omissão) e com fundo
da cor fundo (preto por omissão). Os píxeis válidos têm posições entre 0 e
tamanho.linhas() - 1 (para a linha) e entre 0 e tamanho.colunas() - 1
(para a coluna). A razão entre a largura e a altura dos píxeis é dada por aspecto:
*/
Quadro(Tamanho const& tamanho = Tamanho(24, 80),
Cor fundo = preto,
double aspecto = 1.0);// Pintura de um pixel numa dada posição com uma dada cor:
void pinta(Posicao const& posicao, Cor const& cor);// Pintura de um pixel numa dada posição com um cor contrastante com o
// fundo do quadro:
void pinta(Posicao const& posicao);// "Despinta" um pixel numa dada posição pintando-o com a cor do fundo:
void limpa(Posicao const& posicao);// Pinta todo o quadro com a cor do fundo:
void apaga();// Escreve o quadro no canal saida (que se presume ligado a um ecrã em modo
// texto), com conversão para os caracteres ' ' e '×' (preto e branco
// respectivamente):
void mostra(std::ostream& saida) const;// Devolve a cor do pixel na posição dada. Se a posição estiver fora do
// quadro devolve a cor do fundo:
Cor cor(Posicao const& posicao) const;// Indica se uma dada posição está dentro do quadro:
bool dentro(Posicao const& posicao) const;// Devolve a cor do fundo:
Cor fundo() const;// Devolve a relação largura/altura dos píxeis do quadro:
double aspecto() const;// Devolve o tamanho do quadro:
Tamanho tamanho() const;private:
Tamanho tamanho_; // tamanho do quadro.
Cor fundo_; // cor de fundo do quadro.
double aspecto_;int numero_de_pixeis; // número de píxeis do quadro.
std::vector<Cor> pixeis; // vector com os píxeis do quadro.// Devolve o índice no vector de píxeis correspondente a uma dada posição, que
// se admite estar dentro do quadro:
int indice(Posicao const& posicao) const;
};#include "quadro_impl.H"
#endif // QUADRO_H
inline Quadro::Quadro(Tamanho const& tamanho, Cor fundo, double aspecto)quadro.C
: tamanho_(tamanho), fundo_(fundo), aspecto_(aspecto),
numero_de_pixeis(tamanho.linhas() * tamanho.colunas()),
pixeis(numero_de_pixeis) {
apaga();
}inline void Quadro::pinta(Posicao const& posicao, Cor const& cor) {
if(dentro(posicao))
pixeis[indice(posicao)] = cor;
}inline void Quadro::pinta(Posicao const& posicao) {
if(dentro(posicao))
pixeis[indice(posicao)] = contrasta(fundo());
}inline void Quadro::limpa(Posicao const& posicao) {
pinta(posicao, fundo());
}inline Quadro::Cor Quadro::cor(Posicao const& posicao) const {
return dentro(posicao) ? pixeis[indice(posicao)] : fundo();
}inline bool Quadro::dentro(Posicao const& posicao) const {
return (0 <= posicao.linha() && posicao.linha() < tamanho().linhas() &&
0 <= posicao.coluna() && posicao.coluna() <
tamanho().colunas());
}inline Quadro::Cor Quadro::fundo() const {
return fundo_;
}inline double Quadro::aspecto() const {
return aspecto_;
}inline Tamanho Quadro::tamanho() const {
return tamanho_;
}inline int Quadro::indice(Posicao const& posicao) const {
return posicao.linha() * tamanho().colunas() + posicao.coluna();
}
#include "quadro.H"using namespace std;
void Quadro::apaga() {
for(int i = 0; i != numero_de_pixeis; ++i)
pixeis[i] = fundo();
}void Quadro::mostra(ostream& saida) const {
saida << '\n';
// Não se mostra a última coluna da última linha. Experimente para ver
// porquê...
for(int i = 0; i != numero_de_pixeis - 1; ++i)
saida << caractere(pixeis[i]);
saida << flush;
}
#ifndef FORMA_Hforma_impl.H
#define FORMA_H#include "quadro.H"
// Classe abstracta representando o conceito de forma com algo que tem uma
// posição, que se pode mover, e que se sabe desenhar num quadro.
class Forma {
public:
// Construtor (dada uma posição):
Forma(Posicao const& posicao);// Destrutor virtual. Garante que os destrutores das classes derivadas
// são invocados mesmo que através dum ponteiro para a classe Forma:
virtual ~Forma() {}// Devolve a posição da forma:
Posicao posicao() const;// Desloca a forma para nova posição:
virtual void move(Posicao const& posicao);/**
Desenha a forma num quadro. Sendo uma forma um conceito abstracto, é
claro não se pode dizer COMO desenhá-la. Formas concretas (que serão
derivadas desta classe) saberão como fazê-lo. Por exemplo, um quadrado
é uma forma e sabe-se desenhar. Este procedimento é, portanto,
puramente virtual, sendo a sua existência que torna a classe Forma
abstracta.
*/
virtual void desenha(Quadro& quadro) const = 0;private:
// A posição da forma (um ponto particular, como um centro ou canto):
Posicao posicao_;
};#include "forma_impl.H"
#endif // FORMA_H
inline Forma::Forma(Posicao const& posicao)
: posicao_(posicao) {
}inline Posicao Forma::posicao() const {
return posicao_;
}inline void Forma::move(Posicao const& posicao) {
posicao_ = posicao;
}
#ifndef QUADRADO_Hquadrado_impl.H
#define QUADRADO_H#include "forma.H"
// Um tipo específico de forma: quadrado aberto com lados paralelos aos eixos
// coordenados. Posição é o canto superior esquerdo:
class Quadrado : public Forma {
public:
// Construtor, dadas a posição e a dimensão do lado (em colunas do quadro):
Quadrado(Posicao const& posicao, int lado);// Devolve dimensão do lado em colunas do quadro:
int lado() const;// Desenha o quadrado no quadro, compensando o facto dos píxeis serem
// rectangulares:
virtual void desenha(Quadro& quadro) const;private:
// Dimensão do lado (em colunas):
int lado_;
};#include "quadrado_impl.H"
#endif // QUADRADO_H
inline Quadrado::Quadrado(Posicao const& posicao, int lado)quadrado.C
: Forma(posicao), lado_(lado) {
}inline int Quadrado::lado() const {
return lado_;
}
#include "quadrado.H"#include "util.H"
// Desenha o quadrado no quadro q, compensando píxeis com relação largura/altura
// quadro.aspecto():
void Quadrado::desenha(Quadro& quadro) const {
// Posição do quadrado:
int l = posicao().linha();
int c = posicao().coluna();int altura = arredonda<int>(lado() * quadro.aspecto());
// Desenho dos lados horizontais:
for(int i = 0; i != lado() - 1; ++i) {
quadro.pinta(Posicao(l, c + i));
// Altura compensada pelo rácio largura/altura dos píxeis:
quadro.pinta(Posicao(l + altura - 1, c + 1 + i));
}
// Desenho dos lados verticais (altura compensada pelo rácio
// largura/altura dos píxeis):
for(int i = 0; i != altura - 1; ++i) {
quadro.pinta(Posicao(l + 1 + i, c));
quadro.pinta(Posicao(l + i, c + lado() - 1));
}
}
#ifndef CIRCULO_Hcirculo_impl.H
#define CIRCULO_H#include "forma.H"
// Um tipo específico de forma: círculo aberto. Posição é o centro.
class Circulo : public Forma {
public:
// Construtor, dadas a posição e a dimensão do raio (em colunas do quadro):
Circulo(Posicao const& posicao, int raio);// Devolve dimensão do raio em colunas do quadro:
int raio() const;// Desenha o círculo no quadro, compensando o facto dos píxeis serem
// rectangulares:
virtual void desenha(Quadro& quadro) const;private:
// Dimensão do raio (em colunas):
int raio_;
};#include "circulo_impl.H"
#endif // CIRCULO_H
inline int Circulo::raio() const {circulo.C
return raio_;
}inline Circulo::Circulo(Posicao const& posicao, int raio)
: Forma(posicao), raio_(raio) {
}
#include "circulo.H"#include <cmath>
#include "util.H"// Desenha o círculo compensando píxeis com relação largura/altura quadro.aspecto()
// do quadro quadro. É uma implementação naïve. Ver bons livros de computação
// gráfica para melhores métodos.
void Circulo::desenha(Quadro& quadro) const {
// Posição do círculo:
int l = posicao().linha();
int c = posicao().coluna();
double aspecto = quadro.aspecto();if(aspecto <= 1.0) {
// Tamanho do ciclo de colunas para meio semi-círculo:
int meio = arredonda<int>(raio() /
sqrt(quadrado(aspecto) + 1));// Ciclo de colunas ("quartos" de círculo superior e inferior):
for(int i = - meio; i != meio + 1; ++i) {
Posicao ps(l - arredonda<int>(aspecto * sqrt(quadrado(raio())
- quadrado(i))),
c + i);
quadro.pinta(ps);
Posicao pi(l + arredonda<int>(aspecto * sqrt(quadrado(raio())
- quadrado(i))),
c + i);
quadro.pinta(pi);
}// Ciclo de linhas ("quartos" de círculo esquerdo e direito):
for(int i = - arredonda<int>(aspecto * meio);
i != arredonda<int>(aspecto * meio) + 1; ++i) {
Posicao pe(l + i,
c - arredonda<int>(sqrt(quadrado(raio()) -
quadrado(i / aspecto))));
quadro.pinta(pe);
Posicao pd(l + i,
c + arredonda<int>(sqrt(quadrado(raio()) -
quadrado(i / aspecto))));
quadro.pinta(pd);
}
} else {
// Tamanho do ciclo de linhas para meio semi-círculo:
int meio = arredonda<int>(raio() * quadrado(aspecto) /
sqrt(quadrado(aspecto) + 1));// Ciclo de linhas ("quartos" de círculo esquerdo e direito):
for(int i = - meio; i != meio + 1; ++i) {
Posicao pe(l + i,
c - arredonda<int>(sqrt(quadrado(raio())
- quadrado(i / aspecto))));
quadro.pinta(pe);
Posicao pd(l + i,
c + arredonda<int>(sqrt(quadrado(raio())
- quadrado(i / aspecto))));
quadro.pinta(pd);
}// Ciclo de colunas ("quartos" de círculo superior e inferior):
for(int i = - arredonda<int>(meio / aspecto);
i != arredonda<int>(meio / aspecto) + 1; ++i) {
Posicao ps(l - arredonda<int>(aspecto*sqrt(quadrado(raio())
- quadrado(i))),
c + i);
quadro.pinta(ps);
Posicao pi(l + arredonda<int>(aspecto*sqrt(quadrado(raio())
- quadrado(i))),
c + i);
quadro.pinta(pi);
}
}
}
// Corra numa consola com 24 linhas por 80 colunas!
#include <cstdlib> // para rand().
#include <list>using namespace std;
#include "quadro.H"
#include "quadrado.H"
#include "circulo.H"int main()
{
int const linhas = 24;
int const colunas = 80;
Quadro quadro(Tamanho(linhas, colunas), preto, 0.5);
list<Forma*> lista;for(int i = 0; i != 10; ++i) {
int l = rand() % linhas;
int c = rand() % colunas;
lista.push_back(new Quadrado(Posicao(l, c), rand() % 15 + 1));
l = rand() % linhas;
c = rand() % colunas;
lista.push_back(new Circulo(Posicao(l, c), rand() % 7 + 1));
}
for(list<Forma*>::iterator i = lista.begin(); i != lista.end(); ++i) {
(*i)->desenha(quadro);
quadro.mostra(cout);
cin.get();
}
}
teste: circulo.o quadrado.o quadro.o teste.ocirculo.o: circulo.H forma.H quadro.H cor.H cor_impl.H posicao.H \
posicao_impl.H tamanho.H tamanho_impl.H quadro_impl.H \
forma_impl.H circulo_impl.H util.H util_impl.Hquadrado.o: quadrado.H forma.H quadro.H cor.H cor_impl.H posicao.H \
posicao_impl.H tamanho.H tamanho_impl.H quadro_impl.H \
forma_impl.H util.H util_impl.Hquadro.o: quadro.H cor.H cor_impl.H posicao.H posicao_impl.H tamanho.H \
tamanho_impl.H quadro_impl.Hteste.o: quadro.H cor.H cor_impl.H posicao.H posicao_impl.H tamanho.H \
tamanho_impl.H quadro_impl.H quadrado.H forma.H forma_impl.H \
circulo.H circulo_impl.H
Tédio fatalO vector guarda ponteiros para variáveis dinâmicas das classes Filme, FilmeEstrangeiro e EdicaoDoRealizador.
Manoel Oliveira
250
Star Wars
Steven Spielberg
230
Inglês
EUA
Clockers
Spike Lee
96
Nenhumas...
Finalmente, o programa deve percorrer o vector pedindo a cada filme para se mostrar no ecrã (i.e., invocando o procedimento membro mostra()).
Execute o programa. Aconteceu o que previa? Aconteceu o desejável?
1.b) À luz do que aprendeu sobre funções e procedimentos membro virtuais e polimorfismo, leve o programa de teste a mostrar correctamente cada uma dos filmes (i.e., um filme estrangeiro deve ser mostrada como tal, incluindo a nacionalidade e o idioma). Ou seja, pretende-se que invocações do procedimento membro mostra() através de ponteiros para a classe Filme levem à execução do procedimento membro mostra() da classe a que pertence o objecto apontado (ligação dinâmica).
2. Usando a biblioteca Slang++, escreva um programa que desenhe no centro do ecrã uma cruz de asteriscos amarelos sobre fundo vermelho, escreva "carregue em qualquer tecla para sair" no canto inferior direito do ecrã, e espere por uma tecla antes de terminar (Manual do Slang++ em linha ou em PDF).
3. Escreva um programa (usando a biblioteca Slang++) que exiba um rectângulo no ecrã. O rectângulo deve ser branco sobre fundo azul, com borda representada por asteriscos e interior representado por pontos. Deve ter origem na linha 10 coluna 30 e deve ter 5 linhas de altura por 30 de largura. Deve definir e usar uma classe Rectangulo com um construtor apropriado para poder representar qualquer rectângulo que se pretenda e com um procedimento desenha() para desenhar os rectângulos no ecrã.
4. Melhore o programa anterior, introduzindo uma classe Forma da qual Rectangulo deve ser derivada (usando herança pública: repare que um Rectangulo é uma Forma). Deste modo, podemos colocar na classe Forma as responsabilidades comuns a todas as formas. Escreva também uma classe Ponto (e um ponto também é uma forma...) que represente um simples ponto no ecrã. Altere o programa de modo a colocar um rectângulo e um ponto num vector de ponteiros para Forma e por fim percorrer esse vector pedindo às formas apontadas para se desenharem no ecrã.
[2] Bjarne Stroustrup, "The C++ Programming
Language", 3ª edição, Addison-Wesley, Reading, Massachusetts,
1997. (Existem três exemplares na biblioteca do ISCTE.)