Tag: poo teórica

POO, introdução

A programação é orientada não pela decomposição de tarefas mas pela identificação das entidades que constituem o problema e como são modelizadas e manipuladas

os mecanismos:
Encapsulamento;
Herança;

Encapsulamento:
integridade da informação numa única estrutura a informação, e as funções que trabalham essa informação
acesso à informação é muito controlada

Herança:
coisas em comuns entre conceitos
(funcionários, funcionário administrativo, gestor, …)
sub-classes, surgem as estruturas que derivadas ou herdar do “pai”

Polimorfismo:
poli, várias, morfismos, formas
a mesma instrução pode ser usada em diferentes ações com significado análogo

as Classes, têm:
Estruturas
Classes
Construtores
Destrutor
Funções membros inline
Membros variáveis estáticos
Funções membros estáticas
Funções membros constantes
Funções friend
Construtores – lista de inicialização
Construtores – membros constantes não estáticos
Construtores – membro referência
Membros variáveis inicializados na declaração ( C++11)
Construtores que chamam construtores ( C++11)
Membros default e delete ( C++11)

passagens por referencia vs passagem por valor

nas classes deve haver relações explicitas entre a estrutura e as funções surgem as funções membros
. é a notação para aceder aos membros das classes

os membros podem ser:
privados (por defeito se fosse uma struct era ao contrário), em privado, as variáveis públicas, as funções membro por norma (funções: set, imprimir..)

funções:
get, servem para obter valores, para comparar (retornam copias dos valores)
criamos as funções/métodos usando :: (por exemplo: Estrutura::get..)
:: são funções que pertencem à classe, não são globais

funções inline:
o código é expandido no local da chamada
funções pequenas
chamadas muitas vezes
p.e. detetar se um numero é par
declara-se a função com a expressão inline
inline int par(int numero){

}

funções overloaded:
têm o mesmo nome no mesmo contexto
distinguem-se pelo número de argumentos
p.e.
data(“11/11/11”);
data(11,11,11);
void data (char * s)
void data(int n1, int n2, int n3)

função destrutor:
funciona automaticamente, quando um objeto é destruído ou perde a validade;
podemos precisar numa classe;
usada quando o objeto é destruído e precisamos de executar uma ação de seguida;
libertar por exemplo um espaço de memória dinâmica
o destrutor não tem parâmetros como o construtores
Os objetos locais não estáticos são destruídos quando a execução do programa sai do bloco onde foram declarados.
Os objetos globais são destruídos quando o programa termina.
Os objetos dinâmicos são destruídos com o operador delete.
e os objetos são destruídos por ordem inversa da construção

funções com argumentos por omissão:
A lista de parâmetros com os valores por omissão deve estar apenas na declaração da função cuidado com os erros de ambiguidade (onde existe mais do que uma hipótese)

membros e funções membros estáticos:
têm que ser criados fora dos construtores
são partilhados por todos os objetos da classe
existe apenas uma única versão
está sempre fora dos objetos
p.e.
um predio e um elevador
o prédio tem andares que são objectos
o elevador é o membro estático, que todos usam
e quem usar o elevador pode deixar lá um aviso/atualização
(é uma especie de global na classe)
também existem funções estáticas
a chamada de uma função estática deve ser sempre com ::, mesmo se o objeto existir

funções
no fim, não altera os membros dos objetos

Funções membros constantes, const :
não podem alterar os membros variáveis do objecto
são funções de consulta
poem-se na declaração e na implementação
se um objecto é const, então todas as funções têm que ser const, mesmo que essa função não altere nada
Apenas uma função membro declarada como constante pode ser invocada por objectos constantes.
p.e.:
const int * p;
ou
int const * p;
const antes do *, o ponteiro é um ponteiro que só serve para consultar o apontado, não altera o apontado, mas pode ser apontando para outro local em memória
const depois do *, o ponteiro é constante, tem um endereço e não pode ter outro, não pode apontar para outro sitio
int * const p = &i;
ponteiro constante para constante, const antes e depois do *
const int * const p = &i;
ou
int const * const p = &i;
aponta para uma localização de memória e não se pode alterar essa localização de memória
e também não pode alterar o apontado

parâmetros do tipo ponteiro para constante:
apenas se passa o endereço
garantia de não se alterar a variável
p.e.:
void g(const Elemento * p) //eficiência e segurança
int main(){
Elemento val;
g(&val);
}
ou
void f(const Elemento & arg) //referencia por constante: eficiência e segurança – c++

Funções friend
é uma função que, não sendo membro da classe, tem acesso aos seus membros privados
cuidado que isto é uma quebra do encapsulamento e da integridade dos dados
só deve ser usada com uma justificação forte!

vectores:
notação:
vector v1 = {…};
ou
vector v1(..);
limpar vectores:
v1.clear()
percorrer vectores:
for (const string &c : canais0) {canais.push_back(c);}
ou usando interadores
for(auto it = canais0.begin; it!= canais0.end(); ++it){
canais.push_back(*it);
}
o iterador trata-se como se fosse um ponteiro

initializer_list:
é uma coleção de vectores constantes de um tipo especificado
p.e.
class Televisao{
public:
Televisao(initializer_list<string>canais0);
void setCanais(initializer_list<string>canais0);
}
para criar um objecto do tipo Televisao:
Televisao tv ={“um”, “dois”}; //tv é um objecto Televisao
Usar initializer_list como argumento:
tv.setCanais({“um”, “dois”});
Criar um objeto anónimo
tv = {“um”, “dois”};

inicialização de vectores com construtores:
class Televisao{
vector canais;
public:
Televisao(string canais0[], int n);
}
em que p.e.:
string canais[] = {“um”, “dois”};
Televisao tv = {canais, 2};
ou
Televisao tv {canais, 2};

dedução de tipo: auto
o compilador determina automaticamente o tipo de uma variável
p.e.:
vector::const_rever_interator pesquisa();
p.e.:
int tabela[10];
for(auto & elemento : tabela)
instrução
o uso da referência &, podemos alterar os elementos da coleção (mais eficiente, não faz cópias)
o não uso da referência &, é uma copia do coleção
ou em alternativa para obter eficiência e segurança
for(const auto & elemento : tabela) //não alteramos nada

o nulltpr, é uma constante que representa um ponteiro nulo
0 e NULL são do tipo int em funções overloaded

as referências:
é um outro nome para uma variável, é como que um ponteiro constante que automaticamente conduz ao objeto apontado
p.e.:
void f(int &n);

int main(){
int i =0;
f(i);
cout << x << endl; //100
}

void f(int &n){
n=100;
}

uma referencia como retorno:
int & f();
int x = 0;//global
int main(){
f() = 100;
cout << x << endl; // 100
}
int & f(){
return x;
}

………..construtores, membros constantes não estáticos:
se numa classe existem membros const é obrigatório inicializar no construtor
class Test
{
const int t;
public:
Test(int t0) : t(t0)

………..construtores, membro referência:
class Test2
{
int & t;
public:
Test2(int &t0) : t(t0)
{
};

int getT() const
{
return t;
}

};

int main()
{
int x = 10;
Test2 t2(x);
cout << t2.getT() << endl;

}

membro default:
construtor = default, damos à classe um comportamento default
respeita a inicialização na zona privada
assim o default disponibiliza

membro delete:
vai tornar indisponível a implementação de construtor por omissão, construtor por cópia, operador atribuição, e destrutor
assim o delete impede/proíbe que exista:
construtor por omissão, construtor por cópia, operador atribuição, e destrutor

………..Variáveis em memória dinâmica:
operador new: requisitar memória, e que invoca o construtor
operador delete: para libertar a memória dinâmica
p.e.:
pVar = new tipo;
delete pVar;
p.e.:
Produto* p = new Produto();
cout << p->getAsString() << endl;
delete p;

posso fazer uma reserva com:
int *v2 = new int() //() garantem que vem 0
delete v2

objectos em memória dinâmica:
uso do new, reserva memória para uma variável do tipo classe, e desencadeia o construtor
uso do delete, activa a execução do destrutor

p.e.:
class Ponto {
int x; // abcissa
int y; // ordenada
public:
Ponto() { cout << “Construindo \n”; x = 0; y = 0; }
~Ponto() { cout << “Destruindo \n”; }
void imprime() { cout << “x=” << x << ” y=” << y << endl; } }; int main(void) { Ponto * p = new Ponto; p->imprime();
delete p;
}

existe uma alternativa a:
Ponto * p = new Ponto;
que pode ser com argumentos
Ponto * p = new Ponto{2,2};
criando por exemplo:

criando por exemplo:
Ponto(int x0, int y0) { cout << “Construindo \n”; x = x0; y = y0; }

………..criar um array dinâmico de variváveis de um tipo:
criar:
Ponto * p = new Ponto[4];
libertar:
delete [] pVar

os operadores:
o ponteiro this:
implicitamente nas funções normal membro, não estáticia, a palavra this representa o objecto que invocou a função

………..operadores overloaded:
p.e. existem dois ou mais objectos de uma determinada classe
podemos querer aplicar os operadores que se usam para os tipos primitivos, como é o caso de:
+

*
/
%
=
+=
e vão ter as mesmas regras de prioridade e associatividade
expandidmos assim para operandos das classes
usamos assim o operator
p.e. operator+

………..operadores binários:
p.e.: se o operador estiver relacionado com a atribuição, =, em que os dois operandos não têm o mesmo estatuto é um operador membro, para dar resposta a z+=x ou (z+=x)=y, e também para dar resposta a operações de cadeia
public:
Complexo & operator+=( const Complexo & x); //operador membro
//global, mas membro da classe,
Complexo & Complexo::operator+=( const Complexo & x){
a += x.a;
b += x.b;
return *this; //estatuto de referência
}
//a opção agora é global, porque têm o mesmo estatutuo, são consultados apenas
Complexo operator+(const Complexo & x, const Complexo & y){
// const Complexo & -> para funcionar em cadeia, p.e., para responder a z = x + y +z; (exame)
return Complexo( x.getA() + y.getA() , x.getB() + y.getB() );
}
//e global
ostream & operator<<( ostream & saida, const Complexo & x){
// recebe cout: ostream & saida -> é alterado logo usa-se o &, com a informação do objecto
// ostream & operator -> para funcionar em cadeia, p.e., para responder a cout << x << y; (exame)
return saida << “(“<<x.getA() << ” , ” << x.getB() << “)”;
}
para fazer:
z = x + y;
cout << z << endl;

………..os operadores unários:
++

que podem ser:
pre-fixados (recomendado que sejam membros), p.e.: ++a, que vai ser interpretada como aa.operator++()
ou
pos-fixados (recomendado que sejam membros), p.e.: a++, que vai ser interpretada como aa.operator++(0)

p.e.:
pre-fixado
public:
Complexo & operator++();
e global
Compelxo & Complexo::operator++(){
a +=1;
retun *this;
}

pos-fixado
public:
Complexo operator++(int);
e global
Compelxo Complexo::operator++(int){
Complexo anterior(a,b); //guardamos o valor velho num outro objecto
a +=1;
retun anterior; //retornamos o velho
}
//neste caso não retrno uma referência

………..operador []
usado em classes, classes com coleções, como arrays ou vectores
operator[]()
x[y] é equivalente x.operator[](y)
serve:
para acedermos a um elemento da coleção, e quebra com o encapsulamento
p.e.:
class Tabela {
static const int DIM = 10;
static double erro;
double a[DIM];
int n; // numero de elementos utilizados
public:
// …
double & operator[](int i);
};
double Tabela::erro = 0;
usando:
double & Tabela::operator[](int i) {
if (i >= 0 && i < DIM) return a[i];
else {
cout << “\nIndice ” << i << ” fora dos limites”;
return erro;
}
}
int main() {
Tabela x;
x[0] = 20; // equivalente a x.operator[](0) = 20;
cout << “\n x[0]=” << x[0] << endl;
x[40] = 55;
}

………..operador chamada de função
usada em situações bidimensionais, por exemplo obter um numero de uma matriz bidimensional
usa dois parentesis curvos
operação tem que ser membro
chama.se usando operator()()
p.e.:
class Matriz {
static const int DIM = 10;
static double erro;
double a[DIM][DIM];
int lin; // numero de linhas utilizadas
int col; // numero de colunas utilizadas
public:
//….
double & operator()(int i, int j);
};
double Matriz::erro = 0;

double & Matriz::operator( )(int i, int j) {
if (i >= 0 && i < DIM && j >= 0 && j < DIM)
return a[i][j]; //com estatuto de referência
else {
cout << “\nIndices i=”
<< i << ” e j=” << j << ” fora dos limites”;
return erro;
}
}

int main() {
Matriz x;
x(1, 0) = 44; // x.operator()( 1,0) = 44; (escrever 44 lá..)
cout << “\n x(1,0)=” << x(1, 0);
x(-1, 100) = 12.1;
}

Tags : , , , , ,

POO, introdução III

………..herança
se for composição um classe tem outras classes
se for derivação, sendo um caso particular com caracteristicas adicionais
uma classe derivada: tem membros herdados, e membros adicionais

class Terrestre : public Veiculo { // classe derivada: Terrestre, classe base: Veiculo
int velocidade;
public:
// …
};
a classe derivada acede aos gets e sets das funções que herdou

class Terrestre : public Veiculo
por se usar public:
herança publica
os membros publicos da classe base, são também da classe derivada
e por essa via será também um membro publico da classe derivada

class Terrestre : private Veiculo
ou
class Terrestre : Veiculo
por se usar private:
os membros publicos da herança, são privados de terrestre

o uso de protected:
é um nivel de acesso
são visiveis na classe
nas classes derivadas são visiveis
no código exterior, que não a classe ou classe derivadas não são acessiveis

Forma de derivação
private
membros que na classe base são:
private
protected
public
na classe derivada são:
inacessíveis
private
private

Forma de derivação
protected
membros que na classe base são:
private
protected
public
na classe derivada são:
inacessíveis
private
private

Forma de derivação
public (normal)
membros que na classe base são:
private
protected
public
na classe derivada são:
inacessíveis
protected
public

p.e.:
class Veiculo {
int peso;
protected:
int comprimento;
public:
Veiculo( int p = 0, int c=0): peso (p), comprimento(c){}
int getPeso()const{ return peso; }
int getComprimento()const{ return comprimento; }
void setPeso( int p){ peso = p;}
void setComprimento( int c){ comprimento = c;}
};

class Terrestre : public Veiculo {
int velocidade;
public:
Terrestre(int p=0,int c=0,int vel=0):Veiculo(p,c) {
velocidade = vel;
}

int getVelocidade()const{ return velocidade; }

void setVelocidade( int vel){ velocidade = vel;}

void imprime(ostream & saida )const{
saida << “Peso: ” << getPeso() << endl;
saida << “Compr: ” << comprimento << endl;
saida << “Veloc: ” << velocidade << endl;
}
};

int main(){
Veiculo v(700);
cout << “Peso: ” << v.getPeso() << endl;
//cout <<“Compr:” << v.comprimento << endl; ERRO , é protected em veiculo

Terrestre t(600, 4, 90);
cout << “Peso: ” << t.getPeso() << endl;
//cout <<“Compr:” << t.comprimento << endl; ERRO, é protected em veiculo

cout<<“Veloc: “<< t.getVelocidade() << endl;
t.imprime( cout);
}

Quando um objecto de uma classe derivada é destruído, são chamados os destrutores por ordem inversa da ordem de derivação: primeiro o destrutor da classe derivada, depois o destrutor da classe base

situação:
temos duas classes iguais, temos que usar o nome da herança Herança::funcao

situação: uma classe derivada e composta
p.e.:
class Base {
int i;
public:
Base(int ii = 0) : i(ii) { cout << “Base(int ii)\n”; }
~Base() { cout << “~Base()\n”; }
void imprime(ostream& saida)const {
saida << “Base: i=” << i << endl;
}
};

class Membro {
int k;
public:
Membro(int kk = 0) : k(kk) { cout << “Membro(int kk)\n”; }
~Membro() { cout << “~Membro()\n”; }
void imprime(ostream& saida)const {
saida << “Membro: k=” << k << endl;
}
};
class Derivada : public Base {
int j;
Membro m;
public:
Derivada(int jj) : Base(jj), j(jj), m(jj) {
cout << “Derivada(int jj)\n”;
}
~Derivada() { cout << “~Derivada()\n”; }
void imprime(ostream& saida)const {
saida << “Derivada: j=” << j << endl;
Base::imprime(saida);
m.imprime(saida);
saida << endl;
}
};
int main() {
Derivada d(2);
cout << “\nValores em d:\n”;
d.imprime(cout);
// chamada ao destrutor para d
}

situação: a classe derivada nao tem construtor nem destrutor
p.e.:
class Base {
int i;
public:
Base(int ii = 0) : i(ii) { cout << “Base(int ii)\n”; }
~Base() { cout << “~Base()\n”; }
void imprime(ostream& saida)const {
saida << “Base: i=” << i << endl;
}
};

class Membro {
int k;
public:
Membro(int kk = 0) : k(kk) { cout << “Membro(int kk)\n”; }
~Membro() { cout << “~Membro()\n”; }
void imprime(ostream& saida)const {
saida << “Membro: k=” << k << endl;
}
};

class Derivada : public Base {
Membro m;
public:
void imprime(ostream& saida)const {
saida << “Derivada: \n”;
Base::imprime(saida);
m.imprime(saida);
saida << endl;
}
};

int main() {
Derivada d;
cout << “\nValores em d:\n”;
d.imprime(cout);
// destruição de d
}

análise:
surge o construtor gerado automaticamente
é chamado o construtor da base e do membro
e na destruição invoca o membro e depois a base
também é respeitado o funcionamento da base e derivada, pela ordem com que são construídos e destruídos

………..funções com tratamento especial relativamente à herança
situações:
Construtores:
um construtor de uma classe derivada chama explicitamente ou implicitamente o construtor da classe base correspondente.
Construtor por cópia:
no construtor por cópia de uma classe derivada é preciso chamar explicitamente o construtor por cópia da classe base.
Destrutores:
um destrutor de uma classe derivada chama o destrutor da classe base correspondente.
Operador atribuição:
a atribuição entre dois objectos de uma classe pode não ser suficiente para fazer a atribuição entre dois objectos de uma classe derivada. O operador atribuição não é herdado normalmente como pode ver-se no exemplo seguinte.

p.e.:

class Base {
int i;
public:
Base(int ii) : i(ii) {}
Base(const Base & b) : i(b.i) {
cout << “Base(const Base &)\n”;
}
Base & operator=(const Base & b){
i = b.i;
cout << “Base::operator=()\n”;
return *this;
}
void imprime( ostream & saida)const{
saida << “Base: i=” << i << endl;
}
};

class Membro {
int k;
public:
Membro(int kk) : k(kk) {}
Membro(const Membro& m) : k(m.k) {
cout << “Membro(const Membro&)\n”;
}
Membro & operator=(const Membro & m){
k = m.k;
cout << “Membro::operator=()\n”;
return *this;
}
void imprime( ostream & saida)const{
saida << “Membro: k=” << k << endl;
}
};

class Derivada : public Base {
int j;
Membro m;
public:
Derivada(int jj) : Base(jj), j(jj), m(jj) {}

void imprime( ostream & saida)const{
saida << “Derivada: j=” << j << endl;
Base::imprime(saida);
m.imprime(saida);
}
};
int main() {
Derivada d(2);
cout << “\nConstrução por copia : ” << endl;
Derivada d2 = d; // construção por copia
cout << “\nValores em d2:\n”;
d2.imprime(cout);
Derivada d3(3);
cout << “\nAtribuicao: ” << endl;
d3 = d; // atribuição

cout << “\nValores em d3:\n”;
d3.imprime(cout);
}

………..herança, composição e construtor por cópia
outra versão da classe Derivada, com construtor por cópia e operador atribuição explícitos.
se nada dissermos na derivada, o construtor que é chamado é o da base por omissão e ainda assim temos que ter em atenção de que Base pode preencher com coisas por default
assim é necessário que se explicitem nestas chamadas a lista de inicialização do construtor por cópia, seriam chamados em seus lugares os correspondentes pois caso não sejam, construtores por defeito. O sub-objecto adquirido por herança e os membros objectos seriam inicializados pelos construtores por defeito em vez de receberem cópias dos correspondentes sub-objectos do objecto da classe Derivada considerado origem da cópia.
p.e.:

class Base {
int i;
public:
Base(int ii) : i(ii) {}
Base(const Base& b) : i(b.i) {
cout << “Base(const Base &)\n”;
}
Base& operator=(const Base& b) {
i = b.i;
cout << “Base::operator=()\n”;
return *this;
}
void imprime(ostream& saida)const {
saida << “Base: i=” << i << endl;
}
};

class Membro {
int k;
public:
Membro(int kk) : k(kk) {}
Membro(const Membro& m) : k(m.k) {
cout << “Membro(const Membro&)\n”;
}
Membro& operator=(const Membro& m) {
k = m.k;
cout << “Membro::operator=()\n”;
return *this;
}
void imprime(ostream& saida)const {
saida << “Membro: k=” << k << endl;
}
};
class Derivada : public Base {
int j;
Membro m;
public:
Derivada(int jj) : Base(jj), j(jj), m(jj) {}
Derivada(const Derivada& d) : Base(d), m(d.m), j(d.j) {
cout << “Derivada(const Derivada&)\n”;
}
Derivada& operator=(const Derivada& d) {
Base::operator =(d);
m = d.m;
j = d.j;
cout << “Derivada::operator=()\n”;
return *this;
}
void imprime(ostream& saida)const {
saida << “Derivada: j=” << j << endl;
Base::imprime(saida);
m.imprime(saida);
}
};

………..polimorfismo, clases derivadas
uma espécie de, ou um tipo de, é uma herança
o upcasting, é feito do tipo ascendente, e não é preciso fazer nada, nenhum cast explicito.
p.e.:
class Veiculo {
/* … */
};
class Terrestre : public Veiculo {
/* … */
};
class Automovel : public Terrestre {
/* … */
};
assim é possivel que:
conversão de um ponteiro para Terrestre num ponteiro para Veiculo;
conversão de um ponteiro para Automovel num ponteiro para Veiculo;
conversão de uma referência para Terrestre numa referência para Veiculo;
conversão de uma referência para Automovel numa referência para Veiculo.
isto é:
class Veiculo {
/* … */
};
class Terrestre : public Veiculo {
/* … */
};
int main(){
Veiculo *p;
Veiculo v;
Terrestre t;
p=&v;
p=&t; // upcasting
Veiculo & r = t; // upcasting
}

p=&t; // upcasting
um ponteiro para uma classe base pode receber um endereço de um objecto de uma classe derivada ou derivada da derivada..
pode assim ser feito por inteiro ou pode ser feito por referencia:
Veiculo & r = t; // upcasting

o upcasting é sempre verdade
o downcasting nem sempre é verdade

uso na base da expressão virtual na base e nas suas funções (desta forma será sempre usada a referência para o objeto que é referido)
e na derivada faz-se uso nas funções de override

Um ponteiro ou uma referência para uma classe base podem apontar para, ou referir, um objecto dessa classe ou de uma classe derivada directa ou indiretamente e é invocada:
é executada a versão da função da classe correspondente ao ponteiro ou referência, se a função não for virtual;
é executada a versão da função correspondente à classe a que pertence o objecto, se a função for virtual;

p.e.:
class Veiculo { //classe base
int peso;
public:
Veiculo(int p = 0) : peso(p) {}
virtual void imprime(ostream& saida) const { //virtual
saida << “\nPeso: ” << peso << endl;
}
};

class Terrestre : public Veiculo { //classe derivada
int velocidade;
public:
Terrestre(int p = 0, int vel = 0) : Veiculo(p), velocidade(vel) {}
void imprime(ostream& saida)const override { //lidar com o virtual
Veiculo::imprime(saida);
saida << “Veloc: ” << velocidade << endl;
}
};

class Automovel :public Terrestre { //classe derivada de derivada
int cilindrada;
public:
Automovel(int p = 0, int vel = 0, int cil = 0) : Terrestre(p, vel), cilindrada(cil) {}
void imprime(ostream& saida)const override { //lidar com o virtual
Terrestre::imprime(saida);
saida << “Cilind: ” << cilindrada << endl;
}
};

class Aereo :public Veiculo {
int altura_voo;
public:
Aereo(int p = 0, int a = 0) : Veiculo(p), altura_voo(a) {}
//não tem imprime recebe por herança da base
};
ostream& operator<<(ostream& saida, const Veiculo& x) {
x.imprime(saida);
return saida;
}

int main() {
Veiculo* a[] = { // array de ponteiros para a classe base, Veiculo
new Veiculo(700), // inicialização
new Terrestre(800,80), // inicialização: upcasting
new Automovel(900,90,1400), // inicialização: upcasting
new Aereo(2000,50) // inicialização: upcasting
};
int n = sizeof(a) / sizeof(Veiculo*); //contar elementos
for (int i = 0; i < n; i++) //perocrrer o array
a[i]->imprime(cout); // ou cout << *a[i];

for (int i = 0; i < n; i++)
delete a[i];
}

………..polimorfismo, classes abstractas e funções virtuais
uma classe é abstracta se a classe tem pelo menos um função virtual pura
uma classe abstracta não pode criar objetos
uma classe abstracta pode ter ponteiros e referências
faz sentido existir um prototipo da função que representa apenas um interface comum das classes desta hierarquia
p.e.:
virtual string meio() = 0; // é uma função virtual pura e fica uma classe abstracta
e esta classe pode conter ou pode não conter uma definição da função.
vais er necessário definir na classe derivada todas as funções virtuais puras adquiridas por herança

a classe derivada continua abstracta, se não for concretizada pelo menos uma função virtual pura adquirida por herança e por esse motivo não pode haver objectos desta classe

p.e.:
class Veiculo { //classe abstracta
int peso;
public:
Veiculo(int p = 0) : peso(p) {}
virtual void imprime(ostream & saida) const = 0; //função abstracta, ou virtuais puras
virtual string meioOndeSeDesloca()const = 0; //função abstracta, ou virtuais puras
int getPeso()const { return peso; }
};

class Terrestre : public Veiculo {
int velocidade;
public:
Terrestre(int p = 0, int vel = 0) :
Veiculo(p), velocidade(vel) {}
void imprime(ostream & saida)const override {
saida << “\nPeso: ” << getPeso() << endl << “Veloc: ” << velocidade << endl;
} //faz o overide da função abstracta imprime, da base: herdou duas, concretizou uma
// não se define ainda a função meioOndeSeDesloca(), e por este motivo esta é uma classe abstracta
};

class Automovel :public Terrestre { //esta classe tem as duas funções concretas: concretizou meioOndeSeDesloca e redefiniu a concreta imprime
int cilindrada;
public:
Automovel(int p = 0, int vel = 0, int cil = 0) : Terrestre(p, vel), cilindrada(cil) {}
virtual void imprime(ostream & saida)const override {
Terrestre::imprime(saida);
saida << “Cilind: ” << cilindrada << endl;
} //redefine a função imprime
string meioOndeSeDesloca()const override {
return string(” desloca-se em estrada \n”);
}//herdou uma função asbtracta e concretizou
};
class Aereo :public Veiculo { //concretizou as duas funções, logo é uma classe concreta
int alturaVoo;
public:
Aereo(int p = 0, int a = 0) : Veiculo(p), alturaVoo(a) {}
void imprime(ostream & saida)const override {
saida << “\nPeso: ” << getPeso() << endl << “Altura de voo: ” << alturaVoo << endl;
}
string meioOndeSeDesloca()const override {
return string(” desloca-se por via aerea \n”);
}
};
ostream & operator<<(ostream & saida, const Veiculo & x){
x.imprime(saida);
return saida;
}
int main() {
Veiculo* a[] = { //pode-se criar um array de ponteiros de classes abstractas
//new Veiculo(700), // ERRO, é uma classe abstracta, e não se podem criar objectos de classes abstractas
//new Terrestre(800,80), // ERRO, é uma classe abstracta, e não se podem criar objectos de classes abstractas
new Automovel(900,90,1400), //é uma classe concreta
new Aereo(2000,50) //é uma classe concreta
};
int n = sizeof(a) / sizeof(Veiculo*);
for (int i = 0; i < n; i++)
cout << *a[i] << a[i]->meioOndeSeDesloca();
delete a[0];
delete a[1];
}

………..polimorfismo, destrutores virtuais
O destrutor de uma classe base pode não ser suficiente para “destruir” um objecto de uma classe derivada
o uso delete, vai invocar o destrutor:
se o destrutor for virtual na classe do ponteiro, vai ser usado o destrutor de classe apontado
se o destrutor não for virtual na classe do ponteiro, vai ser usado o destrutor da classe ponteiro, que não é o destrutor da classe do objeto que está a ser destruído

na herança, o destrutor da classe derivada é executado mas depois chama o destrutor da base

p.e.:
class Veiculo {
int peso;
public:
Veiculo(int p = 0) : peso(p) {}
virtual ~Veiculo() { cout << “Destruindo Veiculo\n”; } //é virtual
};
class Aereo :public Veiculo {
int altura_voo;
string * historico;
int n;
public:
Aereo(int p = 0, int a = 0) : Veiculo(p), altura_voo(a), historico(nullptr), n(0) {}
virtual ~Aereo()override { //foi feito o override
delete[] historico;
cout << “Destruindo Aereo\n”;
}
};
int main() {
Veiculo* p = new Aereo(2000, 50);
// …
delete p; // funciona o destrutor de Aereo
// porque o destrutor foi declarado como virtual na classe Veiculo
}

………..polimorfismo, Colecção polimórfica e composição, duplicação polimórfica
existem agora elementos diferentes na colecção
(ver exemplo do aquário)
queremos fazer uma copia (duplicar), se fossem todos iguais eram com o new, mas neste caso temos elementos diferentes.
a solução:
duplicação polimorfica
na classe base é definida uma função virtual que duplica o objeto
mas não podem haver objectos da classe base, porque ela é a abstracta
nas classes derivadas é concretizada a função duplica, e esta função vai fazer o return new Derivada(*this) <-“é o construtor por cópia gerado automaticamente” a funcionar
e na classe base, surge na função de atribuição a duplicação dos objetos, recorrendo a Peixe* p = orig.peixes[i]->duplica();

p.e.:
class Peixe {
// …
};
class TetraPeixe : public Peixe { //classe derivada
// …
};
class NeonPeixe : public Peixe { //classe derivada
// …
};

class Aquario {
// o aquario contém uma colecção de peixes que podem ser de diversos tipos
vector<Peixe*> peixes; //vector de ponteiros para peixes, e pode ser cada um de sua classe, é um vector polimorfico
// …
public:
Aquario() = default;
// Os peixes são criados e destruídos no aquario
// A relação entre o aquário e os peixes é de composição
Aquario(const Aquario& orig); // construtor por copia
Aquario& operator=(const Aquario& orig); // operador atribuicao
virtual ~Aquario(); // destrutor
// …

// funcao abstracta, nao se podem criar objectos desta classe
virtual void alimentar(int quantidade, Aquario* aquario) = 0;
// duplicação polimórfica dos peixes
// como a classe é abstracta, esta função também tem que ser abstracta porque
// não se podem criar objectos da classse Peixe
virtual Peixe* duplica()const = 0; //duplicação polimórfica
};

Aquario::~Aquario() {
for (Peixe* p : peixes) {
delete p;
}
}

class TetraPeixe : public Peixe {
// …
public:
// …
// cria uma cópia de si mesmo em memória dinâmica
Peixe* duplica()const override;
};
Peixe* TetraPeixe::duplica()const { //garante a duplicação
return new TetraPeixe(*this);
}

class NeonPeixe : public Peixe {
// …
public:
// …
// cria uma cópia de si mesmo em memória dinâmica
Peixe* duplica()const override;
};
Peixe* NeonPeixe::duplica()const { //garante a duplicação
return new NeonPeixe(*this);
}

Aquario& Aquario::operator=(const Aquario& orig) { //operador atribuição
// prevencao da auto-atribuicao
if (this == &orig) {
return *this;
}
// libertar memoria dinamica do primeiro membro da atribuição
for (int i = 0; i < peixes.size(); i++) {
delete peixes[i];
}
//esvaziar o vector
peixes.clear();
// copiar a informacao de orig, duplicando os objectos dinamicos do tipo Peixe
for (int i = 0; i < orig.peixes.size(); i++) {
Peixe* p = orig.peixes[i]->duplica();
peixes.push_back(p);
}
// …
return *this;
}

Aquario::Aquario(const Aquario& orig) { //construtor por cópia
//funcionou o construtor por omissao do vector de ponteiros
//o vector está vazio
// não há ponteiros com valores indefinidos
*this = orig;
}

Tags : , , , , ,

POO, introdução II

………..Conversões de tipo primitivos
p.e.:
long l = 10;
int i = static_cast<int>(l);

………..Conversões de tipo de objectos das classes
de um tipo qualquer num objeto da nossa classe e é feito com construtores que aceitam um só argumento do tipo da origem quando é de um objecto da nossa classe num outro tipo são feitas com operadores de conversão e estas conversões podem ser implícitas ou explícitas.

p.e.:
conversão de um outro tipo num objecto da nossa classe
class Complexo {
double a; // parte real
double b; // coeficiente da parte imaginária
public:
// construtor que admite um argumento => conversão:
// argumento -> objecto da classe
Complexo(double aa = 0, double bb = 0);
Complexo & operator+=(const Complexo & x); //operador membro
// …
};
Complexo operator+(const Complexo & x, const Complexo & y);
ostream & operator<<(ostream & saida, const Complexo & x);
assim:
Complexo(double aa = 0, double bb = 0);
pode receber nenhum argumento
um argumento //-> esté o que interessa neste caso
ou dois argumentos

conversão implícita:
Complexo b = 2; // pois o que acontece é Complexo b = Complexo(2)
conversão explicita
a = b + Complexo(2); //

………..extra: operador + e – devem ser global, recomendado, são mais abrangentes
ERRO:
a = 4 + b ,
pois neste caso equivaleria a
a = 4.operator+( b).
sendo global:

………..Construtores e conversões , Construtores explicit
p.e.:
na classe String,
String(int n)
e é feito na iniclização:
String s = ‘a’; //estávamos a reservar bytes
então podemos ter um conversor com a palavra explicit ele só vai trabalhar quando quisermos
class String{
// …
String(const char * s); // construtor conversor, conversão implícita
explicit String ( int n); // construtor conversor, conversão explicita
};

………..operador de conversão
é uma função obrigatoriamente membro
tem o nome operator
tem o tipo de destino
não tem parâmetros
não tem tipo de retorno, pois retornar o tipo de destino
const não altera o objecto
p.e.:
class Relogio {
int hora; // 0 – 23
int minuto; // 0 – 59
int segundo; // 0 – 59
public:
Relogio( int hora = 0, int minuto = 0, int segundo = 0);
operator int( )const; //serve para converter em inteiro
//…
};
e o operator surgia como:
Relogio::operator int( )const{
return segundo+60*(minuto+60*hora);
}
eficiência:
int main( ){
Relogio t(2, 10, 5);
int s = static_cast<int>(t); // conversão explicita
cout << “\n Total de segundos: ” << s;
s = t; // conversão implícita
cout << “\n Total de segundos: ” << s;
}

assim as Conversões implícitas devem ser para:
argumento com que uma função é chamada para o tipo do correspondente parâmetro com que é definida;
valor de retorno de uma função para o tipo de retorno especificado no protótipo da função;
operandos em expressões;
inicializadores.

………..Operador e Opção recomendada
resumo:
operadores unários
funções membros

=()[] operadores de conversão
têm que ser funções membros

+= -= /= *= etc (observar a diferença de “estatuto” entre o 1º e o 2º operandos)
funções membros

outros operadores binários (operandos com “estatuto” análogo)
funções globais

operadores binários em que o 1º operando não pertence à classe em causa da classe em causa
não podem ser funções membros

(construtores por cópia, operador atribuição, .. )
………..composição
a associação (relacionamento de duas classes) de objectos pode ter duas formas:
ou agregação (ter, tem, agrega p.e.: classe A tem objecto classe B, sendo que os objectos da classe B existem sempre, mesmo que A seja destruída; p.e.: turma e alunos)
ou composição (tem mas, se a classe A uca objecto da classes B, se A desaparecer, B também desaparece)

composição vs agregação
p.e.:
existe um relação de composição
se o poema for destruído, os versos são destruídos
um objecto poema tem posso exclusiva dos seus versos
class Poema {
string titulo;
string * pVersos; // ponteiro para um array dinâmico de strings
int nVersos; // obrigatório se não for char *c: numero de elementos do array dinâmico
public:
Poema(const string & t);
~Poema(); // destrutor, para libertar antes
void acrescentaVerso(const string & verso);
void eliminaVerso(int ordem);
// . . .
string getAsString()const;
};

exame: não se pode usar vectores, para usar este tipo de notação

assim surge uma situação de a = b
a atribuição não pode funcionar por omissão, a solução é usar um operador atribuição, e desta forma tudo o que a atribuição faz vai ficar bloqueado
o operador atribuição vai surgir na classe
possibilitando que os objectos sejam fisicamente independentes
public:
Poema(const string & t);
void acrescentaVerso(const string & verso);
void eliminaVerso(int ordem);
Poema & operator=(const Poema & ob);//operador atribuição, operador membro
~Poema(); // destrutor
// . . .
string getAsString()const;
};

//operador atribuição a=b
Poema& Poema::operator=(const Poema& ob) { //é o A, e é o *this, sendo B o op
if (this == &ob) { // prevenção da auto-atribuição
return *this;
}
// se os membros da atribuição forem objetos diferentes
titulo = ob.titulo;
nVersos = ob.nVersos;
// libertar a memoria dinâmica usada exclusivamente pelo primeiro membro
delete[] pVersos;
pVersos = nullptr;
// se a origem da copia for um poema sem versos …
if (ob.pVersos == nullptr || ob.nVersos == 0) {
return *this;
}
// reservar memoria dinâmica para conter uma copia exclusiva dos versos do segundo membro
pVersos = new string[ob.nVersos]; //array dinamico, reserar espaço em memória
// fazer essa copia
for (int i = 0; i < ob.nVersos; ++i) {
pVersos[i] = ob.pVersos[i];
}
return *this;
}
mesmo que a classe não tenha o construtor por cópia, ela existe sempre pois é gerada automaticamente como a atribuição.
e constrói por cópia membro a membro
neste caso copiar o ponteiro pVersos, ficando todos a apontar para a mesma zona de memória

………..construtor por cópia
análogo à atribuição

p.e.:
na classe Poema surge o construtor por cópia
class Poema {
string titulo;
string* pVersos; // ponteiro para um array dinamico de strings
int nVersos; // numero de elementos do array dinamico
public:
Poema(const string& t);
void acrescentaVerso(const string& verso);
void eliminaVerso(int ordem);
Poema& operator=(const Poema & ob);//operador atribuicao, operador membro
Poema(const Poema & ob); //construtor por cópia
~Poema(); // destrutor
// . . .
string getAsString()const;
};

e a função vem:
Poema::Poema(const Poema& ob):titulo(ob.titulo), nVersos(ob.nVersos) {
// titulo e nVersos inicializados com os mesmos valores de ob
// se a origem da copia for um poema sem versos …
if (ob.pVersos == nullptr || ob.nVersos == 0) {
pVersos = nullptr;
return;
}
// reservar memoria dinamica para onde copiar os versos de ob
pVersos = new string[ob.nVersos];
// copiar o conteudo do array dinamico
for (int i = 0; i < nVersos; ++i) {
pVersos[i] = ob.pVersos[i];
}
}

o construtor por cópia funciona:
quando se cria um objecto por cópia de outro;
quando passamos um objecto por valor a uma função (surge um novo objecto);
quando uma função retorna um objecto por valor, sendo que o que sair no return é a cópia;
assim
O construtor por cópia tem usualmente a forma
EstaClasse::EstaClasse( const EstaClasse & ob)

as diferenças entre Construtor por cópia / operador atribuição:
operador atribuição tem a prevenção da auto-atribuicao, que não aparece no construtor por cópia;
no construtor por cópia não é preciso tratar da memória velha, pois o objecto está a ser criado;
e no construtor por cópia não tem os retornos;

assim sendo podíamos escrever o construtor por cópia em função do operador atribuição:
(fazer um à custa do outro)

Poema::Poema(const Poema & ob):pVersos(nullptr){ // preparar pVersos para ser operando de delete
// preparar o objeto que esta a ser criado para ser alvo do operador atribuicao
*this = ob; // aplicar o operador atribuicao: operator=(s)
}
Poema & Poema::operator=(const Poema & ob){
if (this == &ob) {// prevencao da auto-atribuição
return *this;
}
// se os dois membros da atribuicao forem objetos diferentes
titulo = ob.titulo;
nVersos = ob.nVersos;
// libertar a memoria dinamica usada exclusivamente pelo primeiro membro
delete[] pVersos;
pVersos = nullptr;
// etc.
// . . .
}

void Poema::acrescentaVerso(const string & verso){
// reserva espaço para mais um
string * aux = new string[nVersos + 1];
// copia os versos existentes
for (int i = 0; i < nVersos; ++i) {
aux[i] = pVersos[i];
}
// o novo verso está em último lugar
aux[nVersos] = verso;
++nVersos; // incrementa o número de versos
delete[] pVersos; // liberta array dinâmico anterior
// pVersos aponta para o novo array dinamico
pVersos = aux;
}

void Poema::eliminaVerso(int ordem) {
if (ordem < 0 || ordem >= nVersos){ return; } //fora dos limites
if (nVersos == 1) {// se retirar o ultimo
delete[] pVersos;
pVersos = nullptr;
nVersos = 0;
return;
}
// reserva espaço ajustado para menos um
string * aux = new string[nVersos – 1]; //nao for o ultimo, aloco novo espaço
// copia os versos anteriores ao verso a eliminar
for (int i = 0; i < ordem; ++i) {
aux[i] = pVersos[i];
}
// copia os versos posteriores ao verso a eliminar
for (int i = ordem; i < nVersos – 1; ++i) {
aux[i] = pVersos[i + 1];
}
–nVersos; // decrementa o número de versos
delete[] pVersos; // liberta array dinâmico anterior
pVersos = aux; // pVersos aponta para o novo array dinamico
}

………..composição de objectos
um objecto dentro de outro objecto
quando o objecto for criado, antes funcionam todos os construtores dos sub-objectos. funcionam porque nos especificamos ou então é por omissao.
p.e.:
class String {
char* p;
void setString(const char * s);
public:
String(); // construtor por defeito
String(const char * s); // construtor conversor
String(const String & s); // construtor por copia
~String(); // destrutor

const char* getString()const;
ostream & imprime(ostream & saida)const;
String & operator=(const String & x); // operador atribuicao
String & operator+=(const String & x);

static int strTam(const char * s);
};
String::String() {
p = 0;
cout << “Constr. string nula” << endl;
}

String::String(const char * s) {
setString(s);
cout << “Constr. string ” << (p ? p : “nula”) << endl;
}

String::String(const String & s) {
setString(s.p);
cout << “Constr. por copia string ” << (p ? p : “nula”) << endl;
}

String::~String() {
cout << “Destr. string ” << (p ? p : “nula”) << endl;
delete[] p;
}

void String::setString(const char * s) {
int t = strTam(s);
if (!t) {
p = 0;
return;
}
p = new char[t + 1];
if (!p) {
cout << “\nMemoria insuficiente”;
return;
}
strcpy_s(p, 5 ,s);
}

const char* String::getString()const { return p; }

ostream& String::imprime(ostream & saida) const {
if (p) saida << ” ” << p;
else saida << ” String nula”;
return saida;
}

String& String::operator=(const String & x) {
if (this != &x) {
delete[] p;
setString(x.p);
}
cout << “Oper. atrib. string ” << (p ? p : “nula”) << endl;
return *this;
}
String& String::operator+=(const String & x) {
char * aux = new char[strTam(p) + strTam(x.p) + 1];
if (!aux) {
cout << “\nMemoria insuficiente”;
return *this;
}
if (p) strcpy(aux, p);
if (x.p) strcpy(aux + strTam(p), x.p);
delete[] p;
p = aux;
return *this;
}
int String::strTam(const char * s) {
return (s ? strlen(s) : 0);
}
ostream& operator<<(ostream & saida, const String & x) {
return x.imprime(saida);
}

class Data {
int dia;
int mes;
int ano;
public:
Data(int d = 1, int m = 1, int a = 2000);
~Data();
int getDia() const;
int getMes() const;
int getAno() const;
void setDia(int d);
void setMes(int m);
void setAno(int a);
void anosDepois(int n);
};
Data::Data(int d, int m, int a) {
cout << “Constr. data ” << d << “/” << m << “/” << a << endl;
dia = d; mes = m; ano = a;
}
Data::~Data() {
cout << “Destr. data ” << dia << “/” << mes << “/” << ano << endl;
}
int Data::getDia() const { return dia; }
int Data::getMes() const { return mes; }
int Data::getAno() const { return ano; }
void Data::setDia(int d) { dia = d; }
void Data::setMes(int m) { mes = m; }
void Data::setAno(int a) { ano = a; }
void Data::anosDepois(int n) { ano += n; }

class Pessoa {
String nome;
String endereco;
String telefone;
Data nascimento;
public:
Pessoa(const char* n, const char* end, const char* tel, int d, int m, int a);
Pessoa(const char* n);
~Pessoa();
int getAnoNascimento()const;
};

Pessoa::~Pessoa() {
cout << “Fim\n”;
}

Pessoa::Pessoa(const char * n, const char * end, const char * tel, int d, int m, int a) : nome(n), endereco(end), telefone(tel), nascimento(d, m, a) {
cout << “Inicio\n”;
}

Pessoa::Pessoa(const char* n) : nome(n) {
cout << “Inicio\n”;
}

int Pessoa::getAnoNascimento()const {
return nascimento.getAno();
}

int main() {
Pessoa a(“Pedro Sousa”, “Rua do La Vai Um, 1, Coimbra”, “11111”, 1, 11, 1980);
Pessoa b(“Teresa Oliveira”);
cout << “Pedro Sousa nasceu em ” << a.getAnoNascimento() << endl;
cout << “Teresa Oliveira nasceu em “<< b.getAnoNascimento() << endl;
Pessoa c = a; // construção por cópia
c = a; // atribuição

}

Tags : , , , , ,

POO, introdução IV

………..enumerações
enum class Letras {a,b,c,d,e};
para se usar ou atribuir
Letras x = Letras::A;
x = Letras::C;
if (x == Letras::C) cout << ” x == C \n”;

………..conversões explicitas
para Conversões implícitas
double d = 0.0;
int x = static_cast<int>(d); // Mais explícita

………..Excepções
O tratamento de exceções geralmente é usado quando uma parte de um programa detecta um problema (excepção) que não pode resolver e …
ou precisa de uma maneira de sinalizar que algo aconteceu e que não pode continuar;
ou depois de sinalizar a condição excepcional, pára o processamento.

assim:
a instrução throw que a parte de um programa que detecta a excepção utiliza para lançar a excepção – sinalizar a situação que não consegue resolver
blocos try e cláusulas catch – as excepções lançadas do código executado dentro de um bloco try geralmente são tratadas por uma das cláusulas catch.

em que:
quando é lançado um throw o bloco de código fica interrompido (será lançada e não é tratada)
mas pode ser usado algo similar ao seguinte:
try {
// …
// Código que pode gerar excepções
// …
} catch( Erro1 e1 ) {
// Trata excepções do tipo Erro1
} catch( Erro2 e2 ) {
// Trata excepções do tipo Erro2
} catch( Erro3 e3 ) {
// Etc…
} catch( ErroN eN ){
// Trata excepções do tipo ErroN
}
// Continua a execução “normal”…
se existir pelo menos uma cláusula catch que possa corresponder ao tipo da excepção lançada
se não existir nenhuma cláusula catch que possa corresponder ao tipo da excepção lançada, a situação de erro continua
quando temos um try:
no throw nome(), nome() siginfica para procurar um catch com esse nome()
o catch (…), significa qualquer outro erro que não seja dos contemplados

p.e.:
class Excep {
public:
string what()const { return string(“Excep\n”); }
};
class A {
public:
A() { cout << “Construtor de A\n”; }
~A() { cout << “Destrutor de A\n”; }
};
void f() {
A obF;
cout << “–>f: antes de lancar a excepcao\n”;
throw Excep();
cout << “–>f: depois de lancar a excepcao\n”;
}
int main() {
try {
cout << “main:antes de chamar f()\n”;
f();
cout << “main:depois de chamar f()\n”;
}
catch (Excep & ex) { //é criado menos um objecto, porque a passagem é por referência
cout << ex.what();
}
cout << “main:depois de tratar a excepcao\n”;
}

Para relançar uma excepção capturada numa cláusula catch, utiliza-se a instrução throw sem argumentos. As cláusulas catch seguintes (correspondendo ao mesmo bloco try) são ultrapassadas apesar deste relançamento, sendo a excepção remetida para o próximo contexto envolvente.
Mesmo com o relançamento de uma excepção, nunca é executada mais do que uma cláusula catch de entre as que correspondem a um mesmo bloco try.
p.e.:
throw;

p.e.:
class Avaria {
string mensagem;
public:
Avaria(const char* msg = “”) :mensagem(msg) {}
string what() const { return mensagem; }
};
class AvariaLigeira : public Avaria {
public:
AvariaLigeira() : Avaria(“Avaria ligeira:\n”) {}
};
class AvariaGrave : public Avaria {
public:
AvariaGrave() : Avaria(“Avaria grave:\n”) {}
};
void assistenciaEmViagem(int avaria) {
try {
cout << “\n…assistencia em viagem…\n”;
switch (avaria) {
case 0: cout << “tudo em ordem!\n”;
break;
case 1: throw AvariaLigeira();
break;
case 2: throw AvariaGrave();
break;
}
}
catch (AvariaLigeira & e) {
cout << e.what() << “…resolvida no local\n”;
}
catch (AvariaGrave & e) {
cout << e.what() << “…rebocar para a oficina\n”;
throw; //relançar a excepção
}
cout << “…fim de etapa!\n”;
}

int main() {
try {
assistenciaEmViagem(0);
assistenciaEmViagem(1);
assistenciaEmViagem(2); //chamada à função que termina em erro
assistenciaEmViagem(0);
}
catch (AvariaGrave& e) {
cout << “\nmain()-> ” << e.what() << ” -> Avaria reparada na oficina\n” << ” -> canceladas etapas seguintes\n”;
}
}

………..Smart pointers
Permitem gerir de forma mais fácil e segura objectos em memória dinâmica.
Encapsulam ponteiros de tipo primitivo.

tres tipos:
unique_ptr (posso exclusiva do objecto apontado)
shared_ptr (objectos apontados por mais do que um ponteiro)
weak_ptr ()

exemplo de unique_ptr:
#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <regex>
#include <initializer_list>
#include <fstream>

using namespace std;

class Produto
{
string nome;
int preco;
public:

Produto(string nome0, const int preco0)
: nome(nome0),
preco(preco0)
{
cout << ” construtor ” << getAsString() << endl;
}
~Produto()
{
cout << ” destrutor ” << getAsString() << endl;
}
string getAsString() const
{
ostringstream oss;
oss << nome + ” preco: ” + to_string(preco);
return oss.str();
}
};

int main()
{
//smart pointer
unique_ptr<Produto> prod = make_unique<Produto>(“Arroz”,1);
//make_unique cria o objecto em mem dinamica
//make_unique cria o smart_pointer na stack
cout << prod->getAsString();

//versao2, dá erro porque não tem construtor por cópia nem atribuir
//unique_ptr<Produto> prod2 = make_unique<Produto>(“Maria”, 22);
//unique_ptr<Produto> prod3(prod2);
//cout << prod2->getAsString();

cout << “\nfim do main” << endl;
}

mas o unique_ptr Não dispõe de:
Construtor por cópia
Operador atribuição

o unique_ptr pode ser movido mas não copiado, existe um construtor move

exemplo de unique_ptr semantica move:
#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <regex>
#include <initializer_list>
#include <fstream>

using namespace std;

class Produto
{
string nome;
int preco;
public:

Produto(string nome0, const int preco0)
: nome(nome0),
preco(preco0)
{
cout << ” construtor ” << getAsString() << endl;
}
~Produto()
{
cout << ” destrutor ” << getAsString() << endl;
}
string getAsString() const
{
ostringstream oss;
oss << nome + ” preco: ” + to_string(preco);
return oss.str();
}
};

unique_ptr<Produto> cria_Produto() {
unique_ptr<Produto> local_ptr = make_unique<Produto>(“Bolachas”, 2);
return local_ptr; // local_ptr entrega a posse do objecto
}
void f() {
cout << “constr move:\n”;
unique_ptr<Produto> p1(cria_Produto()); // constructor move,
// recebe um objeto retornado por valor, um rvalue
// p1 tem agora a posse do objecto
cout << “atribuicao move:\n”;
unique_ptr<Produto> p2; // constructor por omissão, p2 não possui nada
p2 = cria_Produto(); // atribuição move
// o segundo membro da atribuicao é um objeto retornado por valor, um rvalue
// p2 tem agora a posse do objecto, o segundo objecto criado
}

int main()
{
unique_ptr<Produto> a = make_unique<Produto>(“Cafe”, 2); // a possui um objecto Produto
unique_ptr<Produto> b; // b não possui nada
// invocar atribuição move explicitamente
b = std::move(a); // move retorna uma referência rvalue para a
// funciona o operador atribuição move
// agora b possui o objecto Produto, a não possui nada
// invocar construção move explicitamente
unique_ptr<Produto> c(std::move(b)); // move retorna uma referência rvalue para b
cout << b->getAsString();

cout << “\nfim do main” << endl;
}

Tags : , , , , ,