#include <iostream>com este outrousing namespace std;
int main()
{
int i = 1;
{
int i = i;
cout << i << endl;
}
}
#include <iostream>O segundo escreve 1 no ecrã. O primeiro escreve lixo (só por azar, ou sorte, será 1). Porquê?using namespace std;
int main()
{
int i = 1;
{
int i(i);
cout << i << endl;
}
}
Porque no primeiro a variável mais interior já está definida (embora contendo lixo) quando se escreve = i, pelo que a variável é inicializada com o seu próprio valor (tinha lixo... com lixo fica). No segundo caso a variável mais interior não está ainda definida quando se escreve (i), pois estes parênteses fazem parte da definição. Assim, a variável interior é inicializada com o valor da variável mais exterior...
class A {
public:
A(int i, int j, int k)
: i(k), // i membro inicializada com k parâmetro!
j(j), // j membro inicializada com j parâmetro!
k(i) // k membro inicializada com i parâmetro!
{
}
private:
int i;
int j;
int k;
};
A solução passa por usar uma referência constante pública para aceder a uma variável membro privada! Por exemplo, se a variável for do tipo int e se chamar valor:
class A {Com esta classe é possível escrever o seguinte código:
public:
// O construtor faz valor referenciar valor_:
A(int v)
: valor(valor_), valor_(v) {
}// Quando se usa este truque é fundamental fornecer um construtor por cópia apropriado. Porquê?
// Porque senão a referência da cópia refere-se à variável do original!
A(A const& a)
: valor(valor_), valor_(a.valor_) {
}// Da mesma forma tem de se fornecer o operador de atribuição por cópia. Caso contrário não se
// poderiam fazer atribuições entre instâncias de classes com o truque. É que o C++ não pode fornecer
// o operador de atribuição por cópia automaticamente a classes com membros que sejam referências!
A& operator = (A const& a) {
valor_ = a.valor_;
return *this;
}// A referência valor para a variável valor_ é constante para evitar alterações por entidades
// externas à classe:
int const& valor;// Um procedimento para atribuir um novo valor à variável. Só para efeitos de teste:
void poeValor(int v) {
valor_ = v;
}private:
// A variável propriamente dita é privada...
int valor_;
};
#include <iostream>Quem disse que não havia a categoria "só para leitura" em C++?using namespace std;
int main()
{
A a(10);
cout << a.valor << endl; // Mostra 10.
a.valor = 20;// Dá erro!
a.poeValor(30);
cout << a.valor << endl; // mostra 30.
A b = a;
cout << b.valor << endl; // mostra 30.
A c(40);
c = a;
cout << c.valor << endl; // mostra 30.
}
class Conta {Definem-se agora duas concretizações do conceito. A primeira é uma conta simples. Uma conta simples tem um saldo como atributo. Possui um construtor que inicializa o saldo e sobrepõe uma função de inspecção especializada que se limita a devolver esse saldo (ou seja, fornece um método para a operação abstracta saldo()). A segunda é um portfolio de contas, i.e., uma "conta de contas". O portfolio de contas tem um procedimento acrescentaConta() que permite acrescentar uma conta ao portfolio, um destrutor para destruir as suas contas e fornece também um método para a operação virtual saldo():
public:
virtual ~Conta() {}
virtual double saldo() const = 0;
};
#include <list>Estas classes podem ser usadas como se segue:class ContaSimples : public Conta {
public:
ContaSimples(double saldo)
: saldo_(saldo) {
}
virtual double saldo() const {
return saldo_;
}
private:
double saldo_;
};class Portfolio : public Conta {
public:
~Portfolio() {
// Destrói contas do portfolio:
for(std::list<Conta*>::iterator i = contas.begin();
i != contas.end(); ++i)
delete *i;
}
void acrescentaConta(Conta* nova_conta) {
contas.push_back(nova_conta);
}
virtual double saldo() const {
// É a soma dos saldos das contas no portfolio:
double saldo = 0.0;
for(std::list<Conta*>::const_iterator i = contas.begin();
i != contas.end(); ++i)
saldo += (*i)->saldo();
return saldo;
}
private:
std::list<Conta*> contas;
};
#include <iostream>Note-se que o posimorfismo é fundamental para que a invocação da operação saldo() leve à execução do método saldo() apropriado à classe do objecto apontado e não à classe do ponteiro! Isto seria muito difícil de reproduzir se se tivesse colocado uma variável membro para guardar o saldo na classe base, mesmo que fosse privada. Porquê? Porque nesse caso a operação saldo() não seria abstracta, estando definida na classe base como devolvendo a valor dessa variável membro. Por isso o valor dessa variável teria de ser mantido coerente com o saldo da conta. Isso é fácil de garantir para uma conta simples mas muito mais difícil num portfolio, pois sempre que uma classe num portfolio tiver uma mudança de saldo terá de avisar a conta portfolio desse facto para que esta tenha a oportunidade de actualizar o valor da variável.using namespace std;
int main()
{
// Constrói duas contas simples (ponteiros para Conta!):
Conta* c1 = new ContaSimples(10);
Conta* c2 = new ContaSimples(30);// Mostra saldos das contas simples:
cout << c1->saldo() << endl; // mostra 10.
cout << c2->saldo() << endl; // mostra 30.// Constrói uma conta portfolio e acrescenta-lhe as duas contas:
Portfolio* p = new Portfolio;
p->acrescentaConta(c1);
p->acrescentaConta(c2);// Guarda endereço da conta portfolio no ponteiro c para Conta:
Conta* c = p;// Mostra saldo da conta portfolio:
cout << c->saldo() << endl; // mostra 40.
}
Se essa variável fosse pública ter-se-ia a desvantagem adicional de futuras alterações na implementação terem impacto na interface da classe, o que é indesejável.
Logo, não se pode usar uma variável membro pública neste caso para guardar o saldo...
.......................................................................
Ok. Porquê este discurso todo no apêndice de curiosidades e fenómenos estranhos do C++ e numa secção chamada Variáveis virtuais? Porque... é possível ter variáveis membro públicas, sem nenhum dos problemas apontados, e ainda apenas com permissão para leitura (restrição fácil de eliminar e que fica como exercício para o leitor).
O truque passa por definir essa variável membro como sendo de uma classe extra DoubleVirtual (amiga da classe Conta) e que possui:
class Conta {O programa de teste é agora mais simples, pois aparenta aceder directamente a um atributo para obter o saldo das contas:
public:
class DoubleVirtual {
public:
DoubleVirtual(Conta& base)
: base(base) {
}
operator double () const {
return base.calculaSaldo();
}
private:
// Guarda-se uma referência para a conta a que o saldo diz respeito:
Conta& base;
};
friend DoubleVirtual;
Conta()
: saldo(*this) {
}
Conta(Const const&)
: saldo(*this) {
}
Conta& operator = (Conta const&) {
return *this;
}
virtual ~Conta() {}
DoubleVirtual saldo;
private:
virtual double calculaSaldo() const = 0;
};#include <list>
class ContaSimples : public Conta {
public:
ContaSimples(double saldo)
: saldo_(saldo) {
}
private:
double saldo_;
virtual double calculaSaldo() const {
return saldo_;
}
};class Portfolio : public Conta {
public:
~Portfolio() {
for(std::list<Conta*>::iterator i = contas.begin();
i != contas.end(); ++i)
delete *i;
}
void acrescentaConta(Conta* nova_conta) {
contas.push_back(nova_conta);
}
private:
std::list<Conta*> contas;
virtual double calculaSaldo() const {
double saldo = 0.0;
for(std::list<Conta*>::const_iterator i = contas.begin();
i != contas.end(); ++i)
saldo += (*i)->saldo;
return saldo;
}
};
#include <iostream>Este truque pode ser refinado definindo uma classe modelo TipoVirtual que simplifique a sua utilização mais genérica:using namespace std;
int main()
{
// Constroi duas contas simples (ponteiros para Conta!):
Conta* c1 = new ContaSimples(10);
Conta* c2 = new ContaSimples(30);// Mostra saldos das contas simples:
cout << c1->saldo << endl;
cout << c2->saldo << endl;// Constroi uma conta portfolio e acrescenta-lhe as duas contas:
Portfolio* p = new Portfolio;
p->acrescentaConta(c1);
p->acrescentaConta(c2);// Guarda endereço da conta portfolio no ponteiro c para Conta:
Conta* c = p;// Mostra saldo da conta portfolio:
cout << c->saldo << endl;
}
template <typename B, typename T>Com esta classe modelo definida, a utilização de "variáveis virtuais" exige apenas pequenas alterações na hierarquia de classes. Note-se que:
class TipoVirtual {
public:
TipoVirtual(B& base, T (B::*calcula)() const)
: base(base), calcula(calcula) {
}
operator T () const {
return (base.*calcula)();
}
private:
// Guarda-se uma referência para a classe a que variável diz respeito:
B& base;
// Guarda-se um ponteiro para a função membro que devolve o valor da variável:
T (B::*calcula)() const;
};class Conta {
public:
Conta()
: saldo(*this, &calculaSaldo) {
}
Conta(Const const&)
: saldo(*this, &calculaSaldo) {
}
Conta& operator = (Conta const&) {
return *this;
}
virtual ~Conta() {}
friend TipoVirtual<Conta, double>;
TipoVirtual<Conta, double> saldo;
private:
virtual double calculaSaldo() const = 0;
};// O resto tudo igual.