Programação Orientada aos Objectos Paulo Marques Departamento de Eng. Informática Universidade de...

Post on 17-Apr-2015

116 views 2 download

Transcript of Programação Orientada aos Objectos Paulo Marques Departamento de Eng. Informática Universidade de...

ProgramaçãoOrientada aos Objectos

Paulo MarquesDepartamento de Eng. InformáticaUniversidade de Coimbrapmarques@dei.uc.pt O

ut/2

005

Core C++: Uma abordagem tutorial

2

Sobre o que é que vamos falar?

Estruturas básicas em C++ Criação Dinâmica de Objectos Construção e Destruição de Objectos Herança, Polimorfismo e Destrutores Virtuais Redefinição de Métodos Redefinição de Operadores “Construtor Cópia” e “Operador Atribuição” Gestão de Erros: Excepções

Esta sessão continua o trabalho desenvolvido na primeira formação “POO: Uma Introdução Usando C++”

3

Tipos de Dados Básicos

O tamanho de cada tipo de dados varia de plataforma para plataforma. Alguns tipos não estão especificados como sendo com ou sem sinal

4

Controlo de Fluxo (1)

5

Controlo de Fluxo (2)

6

Aspecto de um programa completo

// Importa biblioteca e passa a usar o espaço de nomes “standard”#include <iostream>#include <vector>using namespace std;

// Programa principalint main(){ // Declara uma tabela de inteiros de tamanho variável vector<int> myTable; // Adiciona-lhe 10 números for (unsigned i=0; i<10; i++) myTable.push_back(i);

// Imprime o seu conteúdo for (unsigned i=0; i<myTable.size(); i++) cout << myTable[i] << endl;

return 0;}

Inclusão de bibliotecas

Uso da biblioteca standard

STL: Biblioteca de estruturasde dados e algoritmos!

Envio para o ecrã

7

A classe Pessoa da última sessão

class Pessoa {private: string _nome; int _idade;

public: Pessoa(string nome, int idade); void imprime();};

Pessoa::Pessoa(string nome, int idade){ _nome = nome; _idade = idade;}

void Pessoa::imprime(){ cout << "[" << _nome << "/" << _idade << "]" << endl;}

Pessoa.cpp

Pessoa.h

8

Construtores, Destrutores e Alcance

Ao criar-se um objecto directamente, este é criado no stack da aplicação. Trata-se de uma variável automática. O “alcance da

variável” está limitado à função onde foi definido. O destrutor de uma classe é um “método especial” que é

invocado sempre que um objecto deixa de existir. É responsável por limpar todos os recursos associados a esse objecto (e.g. memória, handlers gráficos).

void f() { Pessoa cliente("Carlos Manuel", 30);

//... cliente.imprime();}

Criação do objecto no stack (construtor chamado)

Destruição automática do objecto(destrutor chamado)

9

Declaração do Destrutor

É semelhante ao Construtor, mas colocando um ~ antes do nome da classe. Não leva parâmetros e não retorna nada Nunca é chamado explicitamente

class Pessoa { ...

public: ~Pessoa();};

Pessoa::~Pessoa(){ // Limpa eventuais recursos associados à pessoa // ...}

Pessoa.cpp

Pessoa.h

10

Alocação Dinâmica de Memória

Muitas vezes é necessário ter variáveis com um alcance (scope) superior a um método Usa-se alocação dinâmica de memória.

Para alocar dinamicamente um objecto, utiliza-se o operador new Retorna um ponteiro para um novo objecto do tipo pedido. O construtor do objecto é sempre invocado

Não existe garbage collection, a memória tem de ser explicitamente libertada usando o operador delete! Caso tenha sido criado um array de objectos, é necessário usar o

operador delete[]. O destrutor é invocado automaticamente ao usar este operador.

Pessoa* cliente = new Pessoa("Carlos Manuel", 30);

delete cliente;

11

Classe Conjunto (Multi-conjunto)

Consideremos uma classe que representa um multi-conjunto de números inteiros. Pode-se adicionar números e verificar a sua presença no conjunto

12

A sua implementação

Inicialmente tem tamanho para um elemento(os elementos são guardados no heap!)

No final garante que todos os elementossão apagados!

13

A sua implementação (2)

O conjunto cresce automaticamente sempre que não existe mais espaço!

14

A sua implementação (3)

15

Pequeno programa de teste

16

Executando...

17

Qual é a grande limitação desta classe?

(Bem, uma das “grandes” limitações...)

Apenas permite lidar com inteiros. Mas, porquenão armazenar qualquer tipo de objectos??

18

Programação utilizando genéricos (templates)

Permite criar uma família de funções, parametrizadas por um tipo de dados abstracto. Meta-programação Existe há anos em C++: a STL é baseada neles Adição recente em Java (J2SE 5.0) e .NET (2.0)

Conjunto<int> c;c.adiciona(10);c.adiciona(20);

Conjunto<Pessoa> clientes;

Pessoa p(“João Carlos”, 20);clientes.adiciona(p);

19

Qual o aspecto da implementação dos métodos?

Têm de incluir a definição do template... (Incluído o construtor e destrutor!)

20

MonoConjunto

Imaginemos que queremos criar uma nova classe, derivada de Conjunto, que permite apenas ocorrências únicas dos seus elementos.

Tem de ser modificado para garantirque o elemento ainda não existe.

21

MonoConjunto::adiciona()

Mas, qual é o problema com o seguinte código?

Nota: Conjunto<T>::adiciona(valor) chama explicitamenteo método adiciona da classe Conjunto. I.e. da classe acima!

22

MonoConjunto::adiciona()

Mas, qual é o problema com o seguinte código?

Elementos privados de Conjunto!

23

Níveis de acesso em C++

private:Todos os elementos declarados como private apenas são acessíveis à classe em questão. Nota: também são acessíveis a elementos friend, falaremos

disto mais tarde.

protected:Todos os elementos declarados como protected são acessíveis à classe em questão e às classes derivadas desta. Usar com muito cuidado pois viola parcialmente o

encapsulamento. Em classes pensadas para serem herdadas, considerar quais são os elementos que eventualmente serão necessários serem acedidos por outras classes/funções.

public:Elementos declarados como public são acessíveis a todas as classes e funções do programa.

24

Nova versão de Conjunto

Admitindo que o programador pensou explicitamente Conjunto para criar outras classes, é provável que definisse essa classe da seguinte forma:

25

virtual?

Garante que case se usem ponteiros para aceder aos membrosdesta classe (ou derivadas) ou caso os mesmos sejam passados como parâmetros, são chamados os métodos correctos!

Porquê?

26

virtual (2)

Garante que case se usem ponteiros para aceder aos membros desta classe (ou derivadas) ou caso os mesmos sejam passados como parâmetros, são chamados os métodos correctos! Usando o exemplo de Pessoa, Empregado e Patrao da última sessão...

Chama Empregado::imprime() ou Patrao::imprime()consoante o “tipo real” do objecto na tabela.

27

Porquê o destrutor virtual?

Qual o resultado da execução??

28

Porquê o destrutor virtual? (2)

O destrutor de Empregado nunca é chamado.MEMORY LEAK!!!

O sistema não sabequal o verdadeiro tipo associado aoobjecto apontado por p

29

Com um destrutor virtual...

Garante que os destrutores são sempre chamados correctamente

OK!

30

REGRAS A SEGUIR – Classes Para Herança

Devem sempre definir um destrutor virtual Mesmo nos casos em que não é necessário realizar

nenhuma limpeza específica no objecto. Normalmente, a limpeza refere-se a objectos alocados

dinâmicamente ou a recursos não automáticos (e.g. handlers gráficos, semáforos, etc.)

Todos os métodos pensados para serem herdados, sendo modificados em classes derivadas, têm de ser virtual

Todos os campos ou métodos a que eventualmente seja necessário aceder em classes derivadas devem ser protected Utilizar com cuidado! É preferível ter métodos protected do que variáveis

protected: mantêm um maior encapsulamento

31

Conjunto Revisitado

32

MonoConjunto Revisitado

Nota: uma vez declarados como virtual na classe base não deixamde ser virtual nas classes derivadas. No entanto, é mais elegantetorná-lo explícito.

33

Implementação de MonoConjunto

Não é necessárianenhuma inicializaçãonem limpeza explícita

Afinal não é precisoaceder explicitamenteàs estruturas de dados(Óptimo!)

34

Nota sobre construtores e destrutores

Os construtores e destrutores são sempre invocados, quer tal seja feito explicitamente na lista de inicialização, quer não. Um “Empregado” tem de ter todos os campos de “Pessoa”.

Logo, antes de criar “Pessoa”, é necessário criar completamente “Empregado”.

A lista de inicialização permite algum controlo sobre a construção dos objectos acima. Os objectos acima são sempre completamente construídos antes de se entrar no corpo do construtor (i.e. { ... } )

A destruição é feita pela ordem inversa da construção

{ Empregado p;

}

1

24

3

35

Nota sobre construtores e destrutores (2)

À partida, todas as classes possuem um construtor por omissão, mesmo que o programador não o declare. Isto é, um construtor sem parâmetros.

Por vezes é útil não deixar os objectos serem construídos dessa forma (E.g. “Pattern Factory” ou classes internas) É possível desligar explicitamente o construtor por omissão

Caso se declare um construtor com parâmetros, o construtor por omissão é automaticamente desligado. Caso seja necessário, o programador tem de o definir

explicitamente.

36

Redefinição de Operadores

O C++ permite a redefinição de operadores Redefinir um operador é semelhante a implementar um

método

Seria interessante poder escrever este código!

37

Conjunto Revisitado

38

Operadores como métodos

conversão

conversão

39

Implementação

40

Execução

41

Operadores Redefiniveis em C++

Atenção: a redefinição de operadores é algo que implica conhecer bastantes regras e é potencialmente perigoso

Certos operadores são utilizados automaticamente em certas circunstâncias. Ao serem redefinidos, pode levar a problemas subtis. Por outro lado, muitas vezes é essencial redefini-los.

A sintaxe é relativamente uniforme mas alguns operadores são especiais.

Existem operadores que devem ser redefinidos conjuntamente (e.g. == e !=, <, <=, >, >=)

+ - * / % ^ & | ~

! , = < > <= >= ++ --

<< >> == != && || += -= /=

%= ^= &= |= *= <<= >>= [] ()

-> ->* new new[] delete delete[]

42

ostream

Como enviar um conjunto para o ecrã?

Basta uma redefinição global...

Notas:

43

friends

No caso anterior, Conjunto fornecia todos os métodos necessários para implementar o operador <<. Mas, o que é que acontece com Pessoa?

class Pessoa {protected: string _nome; int _idade;

public: Pessoa(string nome, int idade); virtual ~Pessoa();};

Se tentarmos escrever a função que implementa o operador <<(ostream&, Pessoa& p), este não temacesso nem a _nome nem a _idade!

44

friends

Ao declarar uma outra classe ou função como friend, dá-se acesso a todos os campos privados da mesma! Para declarar uma outra classe com friend: friend class X;class Pessoa {protected: string _nome; int _idade;

public: Pessoa(string nome, int idade); virtual ~Pessoa();

friend ostream& operator<<(ostream& os, Pessoa& p);};

45

friends em acção

class Pessoa {protected: string _nome; int _idade;

public: Pessoa(string nome, int idade); virtual ~Pessoa();

friend ostream& operator<<(ostream& os, Pessoa& p);};

46

Qual o resultado da execução deste código?

47

48

O problema das cópias

Sempre que é necessário copiar um objecto, por omissão, a cópia é realizada elemento a elemento da classe. Isto é problemático no caso de classes que utilizem ponteiros e

usem criação dinâmica de objectos!

3

4

_nElementos

_capacidade

_conjunto

10 20 30

conjA

3

4

_nElementos

_capacidade

_conjunto

conjB

49

Problema das cópias

Quando se acrescenta um elemento ao segundo conjunto, na verdade, também se está a manipular a informação do primeiro.

Quando ambos os objectos são destruídos, ambos vão tentar libertar a memória. Daí o Segmentation Fault!!!

3

4

_nElementos

_capacidade

_conjunto

10 20 30

conjA

3

4

_nElementos

_capacidade

_conjunto

conjB

Não se pode apagar a mesma memória duasvezes!

50

“Construtor de Cópia”

As seguintes operações correspondem à invocação do “Construtor de Cópia” da classe: O construtor de cópia tem como parâmetros uma referência

constante para um objecto do mesmo tipo da classe. E.g. Conjunto(const Conjunto<T>& outro)

Também é chamado sempre que um objecto é passado por valor para dentro de uma função e, em certas circunstâncias, quando é retornado de uma função.

51

“Operador de Atribuição”

O operador de atribuição (=) é chamado sempre que é necessário substituir um objecto já existente por um novo O destrutor do existente não é invocado!

52

Definição do Construtor de Cópia

53

Definição do operador de atribuição

54

Cópia de Objectos, Pontos Importantes

Por omissão, todas as classes possuem um construtor de cópia e um operador de atribuição Tal como no caso do construtor por omissão, também é possível

desligá-los, tornando-os privados. Sempre que é necessário criar um novo objecto a partir de

outro, é chamado o construtor de cópia. Sempre que é necessário copiar um objecto para outro já

existente, é invocado o operador atribuição.

Em ambos os casos, o compilador gera código para que, recursivamente, copia as variáveis membro existentes usando construtores-cópia/operadores atribuição.

Em geral, quando os objectos são criados dinamicamente dentro de uma classe, é necessário redefinir o construtor cópia e o operador de atribuição.

SEMPRE QUE SE REDEFINE O OPERADOR CÓPIA DEVE REDEFINIR-SE O OPERADOR ATRIBUIÇÃO E VICE-VERSA Não o fazer provavelmente indica que se passa algo de muito

errado!

55

O resultado da execução...

56

Excepções

Tal como na maioria das linguagens modernas, o C++ encoraja a utilização de excepções para gestão de erros

Ao contrário da maioria das outras linguagens, uma excepção pode ser qualquer coisa (um inteiro, um objecto, uma referência, um ponteiro, etc.)

Embora seja possível declarar as excepções que um método pode lançar, não há obrigatoriedade de as apanhar.

Da mesma forma, um método pode lançar excepções que não declara.

57

MonoConjunto revisitado

Classe base recomendadapara erros nas aplicações,devido ao programador!

Aqui acontece a excepçãoe o programa é abortado!

58

O resultado (espectável ou não...)

59

Salto para ohandler correspondente

60

Qual o aspecto do código?

61

Definir uma nova excepção (parametrizada )

62

As novas definições

63

Ainda resultando em...

As excepções são um tópico complexo e abrangente em C++. É importante consultar uma referênciaantes das começar a utilizar! Isto constitui apenasuma brevíssima introdução ao tópico!

64

Antes de terminarmos...

12

2

3

1. Objecto criado no stack

2. É criada uma nova cópiano stack (passagem porcópia)

3. O objecto é copiado parao seu destino final

4

4. A cópia do stack local dostack é destruída

Se os objectos forem grandes, isto é terrivelmente ineficiente!

65

A solução

Passar os objectos sempre por const&, caso os mesmos não sejam modificados e seja necessário algo que valha pelo objecto inicial!

66

» C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do, it blows away your whole leg. « Bjarne Stroustrup

» If you think C++ is not overly complicated, just what is a protected abstract virtual base pure virtual private destructor and when was the last time you needed one? « Tom Cargill

Próxima Sessão:STL = Standard Template Library

67

Para saber mais...

C++ How to Program, 4th Editionby Harvey M. Deitel, Paul J. DeitelPrentice Hall, Aug. 2002

Uma introdução “leve” ao C++

C++ Primer, 4th Editionby Stanley B. Lippman et. al.Addison-Wesley Professional, Feb. 2005

Para aprender “ao pormenor” tudo o que há a saber sobre C++

68

YOU ARE FREE TO USE THIS MATERIAL FOR YOUR PERSONAL LERNING OR REFERENCE, DISTRIBUTE IT AMONG COLLEGUES OR EVEN USE IT FOR TEACHING CLASSES. YOU MAY EVEN MODIFY IT, INCLUDING MORE INFORMATION OR CORRECTING STANDING ERRORS.

THIS RIGHT IS GIVEN TO YOU AS LONG AS YOU KEEP THIS NOTICE AND GIVE PROPER CREDIT TO THE AUTHOR. YOU CANNOT REMOVE THE REFERENCES TO THE AUTHOR OR TO THE INFORMATICS ENGINEERING DEPARTMENT OF THE UNIVERSITY OF COIMBRA.

(c) 2005 – Paulo Marques, pmarques@dei.uc.pt