Sei sulla pagina 1di 131

Introdução a Programação com

Orientação por Objetos e C++

Rodrigo Canellas

rodrigo.canellas@gmail.com

agosto / 2008
-

Índice

1 Por que usar Orientação por Objetos?.........................................................9


1.1 Efeitos na Definição.............................................................................10
1.2 Efeitos no Desenho..............................................................................11
1.3 Efeitos na Programação.......................................................................13
1.4 Efeitos no Teste de Unidade e Integrado.............................................13
1.5 Efeitos na Manutenção........................................................................15

2 Apresentação dos conceitos básicos..........................................................18


2.1 Classe...................................................................................................18
2.2 Objeto...................................................................................................18
2.3 Método.................................................................................................19
2.4 Atributo................................................................................................19
2.5 Visibilidade...........................................................................................24
2.6 Herança................................................................................................26
2.7 Construção e destruição de um Objeto................................................28
2.8 Polimorfismo........................................................................................28

3 Estudo de Caso – Definição........................................................................33


3.1 Identificação das classes de objetos....................................................33
3.2 Identificação das relações entre as classes.........................................33
3.3 Casos de Uso........................................................................................35
3.3.1 Exercício: Determinar todas as interações com um CD................36
3.3.2 Exercício: Determinar todas as interações com o CD Player.......36

4 Introdução a Unified Modeling Language.................................................37


4.1 Um pequeno histórico da UML............................................................37
4.2 Diagrama de Classes............................................................................37
4.3 Diagrama de Sequencia.......................................................................40
4.4 Diagrama de Estados...........................................................................41

5 Desenhando o CD Player............................................................................42
5.1 Exercício: Diagrama de Classes...........................................................42
5.2 Exercício: Diagrama de Estados..........................................................42
5.3 Exercício: Diagrama de Sequencia......................................................42
5.4 Uma pausa: buscando generalizações.................................................42
5.5 Morre um CD Player, nasce um Media Player.....................................42
5.6 Exercício: Rever os diagramas para o Media Player...........................42

-2-
-

6 (Re) Desenho: Orientação por Objetos e Arquitetura em Camadas..........43


6.1 Classes separando Apresentação, Processamento e Armazenamento 44
6.2 Exercício: Finalizar o Desenho do Media Player.................................52

7 Programação básica em C++.....................................................................53


7.1 Delimitadores.......................................................................................53
7.1.1 De comandos.................................................................................53
7.1.2 De bloco de comandos...................................................................53
7.2 Comentários.........................................................................................53
7.3 Tipos básicos........................................................................................53
7.4 Variáveis...............................................................................................54
7.4.1 Vetores...........................................................................................55
7.4.1.1 Declarando..............................................................................55
7.4.1.2 Referenciando uma posição do vetor......................................56
7.5 Operadores...........................................................................................57
7.5.1 Atribuição......................................................................................57
7.5.2 Igualdade/Desigualdade................................................................58
7.5.3 Relacionais....................................................................................59
7.5.4 Lógicos...........................................................................................59
7.5.5 Incremento e decremento.............................................................59
7.6 Variáveis ponteiros...............................................................................60
7.6.1 Tipos de memória..........................................................................60
7.6.2 Declarando um ponteiro – versão não segura...............................60
7.6.3 Declarando um ponteiro – versão segura......................................61
7.6.4 Alocando memória.........................................................................62
7.6.5 Checando a memória alocada.......................................................62
7.6.6 Referenciando a memória alocada................................................63
7.6.7 Atribuição......................................................................................64
7.6.8 Igualdade/Desigualdade................................................................66
7.6.9 Operadores Relacionais.................................................................68
7.6.10 Operadores Lógicos.....................................................................68
7.6.11 Implementando vetores com ponteiros ......................................68
7.6.11.1 Ponteiros para caracteres.....................................................69
7.6.12 Aritmética de ponteiros...............................................................71
7.6.13 Acessando posições de um vetor como ponteiro.........................72
7.7 Endereço de uma variável...................................................................73
7.8 Comandos condicionais........................................................................73
7.9 Comandos para interação....................................................................74
7.10 Funções..............................................................................................75
7.10.1 Declaração...................................................................................75
7.10.2 Definição......................................................................................76
7.10.3 Execução......................................................................................77
7.10.4 Definição de argumentos e passagem de parâmetros................77
7.10.5 A função main e seus argumentos...............................................80

-3-
-

7.11 Estruturas e novos tipos de dados (não são Classes!).......................83


7.12 Ponteiros para funções......................................................................85
7.13 Pré-processamento............................................................................86
7.13.1 Macro constante..........................................................................86
7.13.2 Macro comando...........................................................................87
7.13.3 Diretivas de compilação..............................................................87

8 Orientação por Objetos e C++...................................................................88


8.1 Classes.................................................................................................88
8.2 Objetos.................................................................................................88
8.3 Atributos..............................................................................................88
8.4 Herança................................................................................................89
8.4.1 Herança Múltipla...........................................................................90
8.5 Construtores........................................................................................90
8.5.1 Construtores de cópia...................................................................91
8.6 Destrutores..........................................................................................94
8.7 Métodos................................................................................................95
8.7.1 Sobrecarga....................................................................................96
8.7.2 Métodos Virtuais...........................................................................97
8.7.3 Métodos Abstratos.........................................................................98
8.8 Polimorfismo........................................................................................99
8.9 A palavra reservada this....................................................................103
8.10 Operadores.......................................................................................104
8.11 Espaço de nomes (namespace)........................................................106
8.12 Arquivos de cabeçalho (header files)...............................................106

9 A biblioteca padrão C++..........................................................................113


9.1 Entrada/Saída (IOStream).................................................................113
9.2 O que são Templates?........................................................................114
9.3 Coleções genéricas (Containers).......................................................118
9.4 Iteradores (Iterator)...........................................................................119
9.5 String.................................................................................................120
9.6 Algoritmos genéricos.........................................................................124

10 Implementação do Estudo de Caso........................................................127


10.1 Uma sugestão de padrão de arquivos de cabeçalho ......................127
10.2 Começar pela Apresentação, Processamento ou Armazenamento?130
10.3 Quais Containers uso para implementar as coleções do Desenho? 130
10.4 Implementação “de baixo para cima”..............................................130
10.5 Só um pouco está pronto? Então testa um pouco!..........................130
10.6 Finalizando implementação.............................................................130

-4-
-

11 Literatura...............................................................................................131

-5-
-

Índice de figuras

Ilustração 1: Caso de uso: CD........................................................................35

Ilustração 2: Caso de Uso: CD Player............................................................36

Ilustração 3: Classe em UML.........................................................................37

Ilustração 4: Associação em UML..................................................................37

Ilustração 5: Associação com direção em UML.............................................38

Ilustração 6: Agregação em UML...................................................................38

Ilustração 7: Composição em UML................................................................39

Ilustração 8: Uso entre classes em UML.......................................................39

Ilustração 9: Herança em UML......................................................................40

Ilustração 10: Sequencia em UML.................................................................40

Ilustração 11: Máquina de estados de um Carro...........................................41

Ilustração 12: Modelo conceitual de classes de um Cifrador........................45

Ilustração 13: Método calcula da class digester - v0.....................................45

Ilustração 14: Método calcula da classe digester - v1...................................46

Ilustração 15: Método cifra em cifrador........................................................46

Ilustração 16: Revisão da relação entre cifrador e digester..........................47

Ilustração 17: Classe processador_cifragem..................................................47

Ilustração 18: Abstração para obtenção do valor original.............................48

Ilustração 19: Processador de cifragem quase completo..............................49

Ilustração 20: Fornecedores de objetos.........................................................50

Ilustração 21: Interface para obter cifrador e digester.................................50

Ilustração 22: Fornecedor de valor original...................................................51

-6-
-

Ilustração 23: Interface processador_criptografia.........................................51

Ilustração 24: Visualização de uma variável..................................................54

Ilustração 25: Visualização da variável pi......................................................55

Ilustração 26: Vetor de inteiros......................................................................56

Ilustração 27: Atribuição - parte 1.................................................................57

Ilustração 28: Atribuição - parte 2.................................................................57

Ilustração 29: Atribuição - parte 3.................................................................58

Ilustração 30: Atribuição - parte 3.................................................................58

Ilustração 31: Variável ponteiro não inicializada...........................................61

Ilustração 32: Visualização de ponteiro inicializado......................................61

Ilustração 33: Ponteiro para inteiro...............................................................62

Ilustração 34: Modificando uma área dinamicamente alocada.....................63

Ilustração 35: Atribuição de ponteiros - parte 1............................................64

Ilustração 36: Atribuição de ponteiros - parte 2............................................64

Ilustração 37: Atribuição de ponteiros - parte 3............................................65

Ilustração 38: Atribuição de ponteiros - parte 4............................................65

Ilustração 39: Atribuição de ponteiros - parte 5............................................66

Ilustração 40: Atribuição de ponteiros - parte 6............................................66

Ilustração 41: Comparação entre ponteiros...................................................67

Ilustração 42: Ponteiro como vetor................................................................69

Ilustração 43: Ponteiro para caracteres.........................................................70

Ilustração 44: Aritmética de ponteiros - parte 1............................................71

Ilustração 45: Aritmética de ponteiros - parte 2............................................71

Ilustração 46: Aritmética de ponteiros – parte 3...........................................72

-7-
-

Ilustração 47: Endereço de uma variável - parte 1........................................73

Ilustração 48: Endereço de uma variável - parte 2........................................73

Ilustração 49: Passagem de parâmetro – parte 1...........................................78

Ilustração 50: Passagem de parâmetro – parte 2...........................................78

Ilustração 51: Passagem de parâmetro – parte 3...........................................79

Ilustração 52: Passagem de parâmetro – parte 4...........................................79

Ilustração 53: Passagem de parâmetro – parte 5...........................................80

Ilustração 54: Passagem de parâmetro – parte 6...........................................80

Ilustração 55: Parâmetros da função main....................................................82

Ilustração 56: Declarando uma variável de um tipo......................................84

Ilustração 57: Definindo valores dos campos de uma estrutura – parte 1....84

Ilustração 58: Definição dos campos de uma estrutura - parte 2..................85

Ilustração 59: Porque usar endereço em construtor de cópia - parte 1........92

Ilustração 60: Porque usar endereço em construtor de cópia - parte 2........93

Ilustração 61: Porque usar endereço em construtor de cópia - parte 3........93

Ilustração 62: Porque usar endereço em construtor de cópia - parte 4........94

Ilustração 63: Hierarquia da biblioteca IOStream.......................................113

Ilustração 64: Instâncias de classes modelo................................................117

-8-
1 -Por que usar Orientação por Objetos?

1 Por que usar Orientação por Objetos?

A História da computação nos mostra que a tarefa de desenvolver


programas para computadores era nos primórdios bastante artesanal, e
dominada por poucos programadores.

Esta mesma História revela a necessidade de tornar a criação de


programas uma atividade industrial, como uma linha de produção fabril.
Esta necessidade pode ser traduzida por produzir programas em menos
tempo; com menos erros; exaustivamente testados; que atendam
exatamente aos requisitos definidos; que sejam fáceis de ter os erros
corrigidos; que possam ter novas funções rapidamente acrescentadas; que
outras pessoas que não os criadores possam continuar seu desenvolvimento;
e que possam ser reaproveitados.

Vimos ao longo dos mais de sessenta anos da, não tão longinquamente
reconhecida, Ciência da Computação, várias tentativas de conseguir estes
objetivos.

Vários autores entendem que a linguagem de programação Simula, em


1968, implementava, mesmo que de maneira ainda rudimentar, conceitos
que já eram reconhecidos como muito úteis, principalmente o de Tipo
Abstrato de Dados, para produzir programas com aquelas qualidades. Por
este mecanismo, os programadores podiam criar novos tipos de dados, que
funcionavam semelhantemente aos tipos nativos de Simula, ou seja, uma
área de memória com tipo, tamanho, domínio de valores possíveis, e,
principalmente, ações definidos.

Por exemplo, era possível criar um novo tipo de dados chamado Carro, e
associar a este novo Tipo as ações Parar, Ligar, Acender Faróis, etc. Assim
Carro passou a funcionar exatamente como um Tipo nativo de Simula, isto é,
somente ações formalmente definidas podiam ser realizadas sobre uma
variável do Tipo Carro. Também como um Tipo original, era desnecessário ao

-9-
1 -Por que usar Orientação por Objetos?

programador que usasse o Tipo Carro conhecer como este foi implementado.
Assim, Carro se assemelha a, por exemplo, Integer, onde somente
determinadas ações são possíveis, e não necessitamos conhecer sua
implementação para usá-lo.

Veremos nos próximos tópicos como esta capacidade, fundamental em


Orientação por Objetos, auxilia o desenvolvedor a alcançar os objetivos
acima mencionados, e como influencia as fases do desenvolvimento de
programas.

1.1 Efeitos na Definição


Classicamente, são objetivos desta fase conhecer os conceitos envolvidos
no problema, ou conhecer o negócio; identificar como estes conceitos se
comportam, interagem, para produzir o resultado esperado; definir a
fronteira do que será efetivamente implementado na forma de programas de
computador; definir quando se dará esta implementação, entre outros.

A idéia de Tipo Abstrato de Dados é bastante adequada a estes fins, pois


equivale, neste nível de abstração, ao que chamamos de Conceito.

Se estamos em um problema, ou negócio, de Recursos Humanos, são


inerentes os Conceitos de Funcionário, Departamento, Lotação de
Funcionário, Jornada de Trabalho, Férias, etc. Comportamentos, ou ações,
como Empregar, Dar Férias, Pagar Salário, também fazem parte do domínio
do problema.

Em um negócio de Controle de Acesso, os Conceitos de Usuário, Ponto de


Acesso, Senha, etc., naturalmente afloram. Liberar Acesso, Validar Senha,
Excluir Usuário são comportamentos esperados neste negócio.

Em um simulador de vôo, o Conceito, ou Objeto, Avião está obviamente


presente. Aterrissar, Levantar Vôo, Acelerar, Nivelar são, entre vários
outros, Comportamentos presentes.

- 10 -
1 -Por que usar Orientação por Objetos?

Esses Conceitos serão a base para o desenvolvimento dos programas, e,


através de sucessivos refinamentos de abstração, muitos serão
implementados sob a forma de Tipos de Dados, também chamado de
Classes de Dados, Classes de Objetos, ou, usando o jargão de Orientação por
Objetos, Classes.

1.2 Efeitos no Desenho


Entre os vários objetivos da fase de Desenho, podemos destacar a
definição da arquitetura do sistema; das interfaces dos principais módulos e/
ou bibliotecas; das restrições tecnológicas, como linguagem de programação
e sistema operacional; e aprimoramento das estimativas de tempo de
desenvolvimento.

As Classes identificadas na fase de Definição sofrerão sucessivos


refinamentos, gerando um ciclo de “nova solução/novo problema”, isto é, o
Desenho que soluciona um nível de abstração cria um novo problema, em
um nível de abstração menor. Neste ciclo, cada vez mais Classes tipicamente
computacionais são usadas para resolver os problemas. A medida que isto
ocorre, o nível de abstração diminui, tornando a solução sucessivamente
mais concreta, a ponto de ser possível implementá-la usando uma tecnologia
definida.

Por exemplo, no negócio de Recursos Humanos, podemos imaginar o


seguinte ciclo de (solução nível N) → (problema de nível N - 1):

Nível 0.0
Problema Identificar Conceitos
Solução Conceitos Departamento e Funcionário identificados

Nível 0.1
Problema Conceitos Departamento e Funcionário identificados

- 11 -
1 -Por que usar Orientação por Objetos?

Solução Um Departamento tem vários Funcionário lotados, e um Funcionário


está lotado em um Departamento

Nível 0.2
Problema Conceitos Departamento e Funcionário identificados
Solução Um Funcionário é identificado pela sua Matrícula, e um Departamento
por seu Código de Departamento

Nível 1.0
Problema A criação e desativação de Departamentos ocorre com baixa
frequencia
Solução A coleção necessária para guardar os Departamentos terá um volume
baixo de inserções e exclusões

Nível 1.1
Problema A coleção necessária para guardar os Departamentos terá um volume
baixo de inserções e exclusões
Solução Usar um Vetor para armazenar os Departamentos

Nível 2.0
Problema Existe uma alta rotatividade de Funcionários
Solução A coleção necessária para guardar os Funcionários terá um volume
alto de inserções e exclusões

Nível 2.1
Problema A coleção necessária para guardar os Funcionários terá um volume
alto de inserções e exclusões
Solução Usar uma Lista para armazenar os Funcionários

Este exemplo bastante simplório, incompleto e descontextualizado, tem o


objetivo de apresentar não uma técnica de desenvolvimento de programas -
seja para a fase de Definição, Desenho, Implementação, ou qualquer outra -
mas uma maneira de pensar, de abordagem para resolver o problema, que
podemos resumir: identifique os Conceitos, aprenda mais e mais sobre eles,

- 12 -
1 -Por que usar Orientação por Objetos?

até poder representá-los computacionalmente, transformando-os em


Classes.

A capacidade de pensar em níveis de abstrações é imperial para produzir


bons Desenhos Orientados por Objetos, que por sua vez são fundamentais
para a Programação de bons programas Orientados por Objetos.

1.3 Efeitos na Programação


O principal objetivo desta fase é transformar as Classes desenhadas em
programas - executáveis, bibliotecas, módulos, etc. - usando uma tecnologia
específica, ou seja, linguagem de programação, sistema operacional, talvez
um banco de dados, etc.

Por termos feito um Desenho que definiu Conceitos (Classes) com


Comportamentos (Interface) bem definidos, uma importante consequencia é
que podemos paralelizar a codificação. Esta paralelização é possível mesmo
existindo, e certamente existirão, dependências entre as Classes.

Suponha que uma Classe Carro tenha uma relação com a Classe Motor. O
programador que implementará a Classe Carro não precisa esperar que a
Classe Motor esteja acabada para ao menos iniciar sua tarefa. Isto é possível
porque a Classe Motor tem uma Interface bem definida, logo a Classe Carro 
pode ter sua implementação iniciada, confiando que Motor funcionará
segundo aquela Interface. A fim de minimamente testar a Classe Carro,  o 
programador pode criar uma Classe Motor temporária, com os Métodos de
Motor implementados de maneira rudimentar (stub).

1.4 Efeitos no Teste de Unidade e Integrado


O Teste de Unidade tem como objetivo básico garantir que pequenas
porções de código implementadas estejam corretas, antes de integrá-las à
outras.

- 13 -
1 -Por que usar Orientação por Objetos?

Como vimos, o desenvolvimento Orientado por (Classes de) Objetos


produziu estruturas computacionais que representam objetos conceituais,
com interfaces de acesso, e relações com outras Classes bem definidos.

A criação de Testes de Unidade em muito se favorece destas


características, especialmente para a criação de testes Caixa-Preta. Neste
tipo de teste, colocamos à prova se os Métodos de uma Classe estão
produzindo os resultados esperados, face aos parâmetros fornecidos, sem
entrar em considerações sobre o funcionamento do método, ou seja, seu
algoritmo.

Por exemplo, suponha a seguinte Classe Data:

classe: Data 

método:   inicializa(dia:   inteiro,   mes:   inteiro,   ano   :   inteiro): 


boleano.

fim­classe;

Este método pretende inicializar uma instância (objeto) da Classe Data,


como em:

// declara um objeto do tipo Data

d0 : Data; 

// executa o método inicializa sobre o objeto d0

d0.inicializa(13,8,2008);  

Se a data for inválida, o método retorna falso, e se a data for válida,


retorna verdadeiro.

Assim, poderíamos escrever:

d0 : Data;

se d0.inicializa(13,8,2008) = verdadeiro

imprime “data valida”

senao

- 14 -
1 -Por que usar Orientação por Objetos?

imprime “data invalida”

fim­se

Tendo apenas este método, podemos facilmente especificar ao menos 9


testes Caixa-Preta:

Parâmetro dia menor que zero;

Parâmetro mes menor que zero;

Parâmetro ano menor que zero;

Parâmetro dia igual a zero;

Parâmetro mes igual a zero;

Parâmetro ano igual a zero;

Parâmetro dia inválido, independente do mes e ano;

Parâmetro dia inválido, dependente do mes e ano;

Parâmetro mes inválido;

Já o Teste Integrado objetiva garantir que as macro Definições Funcionais


e Tecnológicas foram satisfeitas. Como dissemos no tópico Efeitos na
Programação, um programador pode codificar uma Classe, mesmo que esta
dependa de outra, confiando na sua Interface. A dependência centrada na
Interface em muito facilitará a integração de todas as classes, a fim produzir
o programa, bem como o Teste Integrado.

1.5 Efeitos na Manutenção


Classicamente, faz-se uma distinção entre Manutenção Corretiva e
Manutenção Evolutiva. A primeira tem como objetivo corrigir erros,
enquanto a última visa adicionar novas funcionalidades ao sistema.

O desenvolvimento Orientado por Objetos tende a diminuir o tempo de


uma correção, pois a origem do erro é geralmente encontrada rapidamente,

- 15 -
1 -Por que usar Orientação por Objetos?

devido às Interfaces bem definidas. Ou seja, um erro tem seu “culpado” mais
facilmente identificado, na medida que sabemos exatamente o que cada
Classe de Objetos faz.

É na Manutenção Evolutiva que o paradigma de Orientação por Objetos


mais se sobressai, em relação às outras técnicas de desenvolvimento, pois ao
criarmos Classes com Interfaces bem definidas, estamos dando um grande
passo para podermos reaproveitar estas Classes, tornando a implementação
de novas funções, seja no sistema de onde elas se originaram, ou em outros
sistemas. Por exemplo, a Classe Funcionario pode ser usada não apenas no
sistema de Recursos Humanos, mas também no de Controle de Acesso.

Outra característica de Orientação por Objetos, que veremos em detalhes


em Apresentação dos conceitos básicos a seguir, é o de expandir a própria
Classe, através do mecanismo de Herança. Sucintamente, esta técnica nos
permite criar especializações de uma Classe, por exemplo a Classe
FuncionarioContratado e FuncionarioEfetivo    podem ser encaradas como
especializações da Classe Funcionario.

Entretanto, é costume encontrar pessoas que acham, erroneamente, que o


enorme reaproveitamento de código é devido aos recursos de uma
linguagem de programação. Para termos código reaproveitável, é preciso
pensar em reuso desde a Definição e, principalmente, no Desenho. Um bom
Desenho é imprescindível para termos uma estrutura de Classes que
permita reaproveitamentos. A fase de Programação se destina a
implementar de maneira eficiente, usando uma tecnologia específica, a
estrutura construída na fase de Desenho.

Portanto, é condição básica para adotarmos a Orientação por Objetos


como técnica de desenvolvimento, pensarmos em níveis de abstração, onde,
por refinamentos do conhecimento que temos dos Conceitos, terminamos
por ter Classes que refletem estes mesmos Conceitos.

Voltando ao exemplo da classe Funcionario, se o desenhista não tem em

- 16 -
1 -Por que usar Orientação por Objetos?

mente o possível reuso desta Classe, ou seja, se pensar seu uso


exclusivamente no sistema de Recursos Humanos, pode definir
comportamentos e relações com outras Classes que prejudicarão sua
utilização em outras situações.

- 17 -
2 -Apresentação dos conceitos básicos

2 Apresentação dos conceitos básicos

Neste capítulo apresentaremos formalmente os conceitos fundamentas do


desenvolvimento Orientado por Objetos.

2.1 Classe
Como vimos, uma Classe é a representação de um Conceito, ou Objeto,
identificado no problema. Estudamos este Objeto, procurando identificar as
relações com outros Objetos, e seus Comportamentos, ou seja, o conjunto de
ações que este Objeto realiza.

Novamente exemplificando, em um jogo de corrida de motocicleta,


Objetos, ou Conceitos, como Motocicleta, Pista de Corrida, Colocação são
facilmente identificados.

Através de sucessivos refinamentos, isto é, aprofundando o conhecimento


sobre o Conceito ou Objeto, detalhamos cada vez mais as relações e
comportamentos, até que se transformem em Atributos e Métodos.

2.2 Objeto
Em termos computacionais, um Objeto, também chamado de Instância é a
concretização de uma Classe.

É preciso cuidado para não confundir o termo Objeto, quando se equivale


a um Conceito, e como usaremos daqui por diante, como Instância de uma
Classe. Ao criarmos uma Instância de uma Classe, estamos, como ao
declarar uma variável de um tipo de dados, alocando memória (principal) no
computador.

No exemplo da Classe Data, tínhamos escrito:

// declara um objeto do tipo Data

- 18 -
2 -Apresentação dos conceitos básicos

d0 : Data;

Este pseudo-código está criando o Objeto d0 da Classe Data. Novamente,


não é exagero afirmar que Objetos são para Classes, como variáveis são
para tipos de dados.

2.3 Método
O conjunto de Métodos, ou Funções, associadas a uma Classe, define sua
Interface (Protocolo da Classe), isto é, o que é possível realizar com, ou
sobre, um Objeto.

Ainda na Classe Data, havíamos escrito:

// executa o método inicializa sobre o objeto d0

d0.inicializa(13,8,2008);

inicializa é um Método definido para a Classe Data, e portanto pode ser


executado com/sobre qualquer Objeto da Classe Data:

// declara um outro objeto do tipo Data

Data d1;

// aplica o método inicializa sobre d1

d1.inicializa (16,2,1966);

2.4 Atributo
Na Classe Data, quando invocamos o Método inicializa sobre a Instância
d1, fornecemos três informações, na forma de parâmetros: 16, 2 e 1966,
para o dia, mês e ano da Data, respectivamente.

Mas, de pouco vale inicializar um Objeto da Classe Data se não podemos


posteriormente recuperar o valor do ano, por exemplo. O pseudo-código
abaixo mostra uma nova versão da Classe Data com esta capacidade:

classe: Data 

- 19 -
2 -Apresentação dos conceitos básicos

método:   inicializa(dia:   inteiro,   mes:   inteiro,   ano   :   inteiro): 


boleano.

método: qual_ano() : inteiro;

fim­classe;

Podemos agora escrever:

// declara um outro objeto do tipo Data

Data d1;

// aplica o método inicializa sobre d1

d1.inicializa (16,2,1966);

// imprime o valor do ano de d1

imprime d1.qual_ano();

Mas, entre a execução de d1.inicializa   (16,2,1966); e imprime 


d1.qual_ano();, onde estava o valor do ano?

Um Atributo de uma Classe é a área de memória (principal) onde são


armazenados os valores, ou características, de um Objeto. Novamente
vamos expandir a declaração da Classe Data:

classe: Data 

método:   inicializa(dia:   inteiro,   mes:   inteiro,   ano   :   inteiro): 


boleano.

método: qual_ano() : inteiro;

atributo: ano_ : inteiro;

atributo: mes_ : inteiro;

atributo: dia_ : inteiro;

fim­classe;

Podemos propor as seguintes implementações para inicializa e qual_ano:

Data.inicializa(dia: inteiro, mes: inteiro, ano : inteiro): boleano

inicio

- 20 -
2 -Apresentação dos conceitos básicos

retorno : boolean;

retorno = verdadeiro; // esperamos o melhor

// o método data_invalida não será mostrado

se data_invalida(dia, mês, ano) entao

retorno = falso;

senao

// não serão mostradas as críticas dos valores

dia_ = dia;

mes_ = mês;

ano_ = ano;

fim­se

retorna retorno;

fim­metodo;

Data.qual_ano() : inteiro

inicio

retorna ano_;

fim

Muito importante ressaltar que os valores dos Atributos somente são


acessados através de Métodos, a fim de conseguirmos o objetivo de criarmos
estruturas computacionais que se assemelhem aos tipos nativos da
linguagem, conforme mencionamos em Por que usar Orientação por
Objetos? Com isto, atingimos o objetivo de não precisarmos saber como é a
implementação de uma Classe, mas apenas sua Interface.

Para reforçar este conceito, vamos supor que exista uma classe
Compromisso, que tenha como Atributo um Objeto da Classe Data:

classe: Compromisso

método: agendar(dia : inteiro; 

- 21 -
2 -Apresentação dos conceitos básicos

   mes : inteiro; 

                ano : inteiro ; 

                descricao : Texto) : booleano;

atributo: data : Data;

atributo: desc : Texto;

fim­classe;

Podemos escrever a implementação do Método agendar assim:

Compromisso.agendar(dia : inteiro; 

       mes : inteiro; 

                    ano : inteiro ; 

                    descricao : Texto);

inicio

retorno : booleano;

retorno = data.inicializa(dia, mes, ano)

se  retono == verdadeiro entao

// estamos supondo que Texto tenha um Método atribui

desc.atribui(descricao);

retorna retorno;

fim­metodo;

Este exemplo, embora não tenha este objetivo, mostra um dos mecanismos
de construção de Classes em um ambiente Orientado por Objetos:
composição. A Classe Compromisso é composta por uma Instância da Classe
Data. Falaremos mais detalhadamente sobre os mecanismos e técnicas de
construção de Classes adiante.

O mais importante de se notar aqui é a linha data.inicializa(dia, mes, 


ano); dentro do Método agendar, ou seja, estamos invocando o Método
inicializa sobre o Objeto data, que é um Atributo de Compromisso.

- 22 -
2 -Apresentação dos conceitos básicos

Suponhamos uma mudança na Implementação dos Atributos da Classe


Data.

classe: Data 

método:   inicializa(dia:   inteiro,   mes:   inteiro,   ano   :   inteiro): 


boleano.

método: qual_ano() : inteiro;

atributo: valores: vetor[3] de inteiro;

fim­classe;

E a consequente mudança de implementação de inicializa e qual_ano:

Data.inicializa(dia: inteiro, mes: inteiro, ano : inteiro): boleano

inicio

retorno : boolean;

retorno = verdadeiro; // esperamos o melhor

// o método data_invalida não será mostrado

se data_invalida(dia, mês, ano) entao

retorno = falso;

senao

// não serão mostradas as críticas dos valores

valores[0] = dia;

valores[1] = mês;

valores[2]  = ano;

fim­se

retorna retorno;

fim­metodo;

Data.qual_ano() : inteiro

inicio

- 23 -
2 -Apresentação dos conceitos básicos

retorna valores[2];

fim

Nesta implementação, ao invés de três Objetos da Classe inteiro, temos


agora um vetor de 3 Objetos da Classe inteiro: na primeira posição ficará o
dia, na segunda o mês, e na terceira o ano.

Perceba que os Métodos da Classe Data não se modificaram! Ou seja,


podemos mudar a implementação dos Atributos de uma Classe, mas se não
modificarmos sua Interface, outras Classes que utilizem Objetos de Data,
não serão afetados. Logo, o Método agendar da Classe Compromisso não
precisará ser modificado!

Esta importante característica de ocultar a implementação dos Atributos


de uma Classes é bastante conhecido como Encapsulamento de Dados.
Algumas vezes podemos encontrar na literatura Classes também sendo
chamadas de Cápsulas de Dados.

2.5 Visibilidade
Quando afirmamos que os Atributos somente são acessados através de
Métodos, estamos faltando com a verdade. Na prática, isto fica a cargo do
desenhista. É possível definir Atributos que sejam acessados “de fora” da
Classe, não por intermédio de um Método, apenas tendo um Objeto da
Classe instanciado. Portanto, ao definirmos um Atributo ou Método,
precisamos especificar sua visibilidade, isto é, como será possível acessar o
Atributo ou Método. As linguagens de programação, em geral, definem três
tipos de visibilidade: pública, protegida ou privativa.

Um Atributo ou Método é público quando pode ser acessado “de fora” da


Classe, ou seja, tendo-se um Objeto da Classe, é possível acessar o Atributo
ou Método.

Um Atributo ou Método é protegido quando pode ser acessado somente de

- 24 -
2 -Apresentação dos conceitos básicos

dentro da Classe, ou seja, apenas Métodos da Classe podem acessar o


Atributo ou Método.

Um Atributo ou Método privativo tem a mesma característica do Atributo


ou Método protegido, a diferença acontece quando temos uma Herança,
nosso próximo tópico.

Podemos agora reescrever a Data assim:

classe: Data 

públicos:

método:   inicializa(dia:   inteiro,   mes:   inteiro,   ano   :   inteiro): 


boleano.

método: qual_ano() : inteiro;

protegidos:

atributo: ano_ : inteiro;

atributo: mes_ : inteiro;

atributo: dia_ : inteiro;

fim­classe;

Este extrato de código causa um erro de compilação:

d0 : Data;

d0.inicializa(19,8,2004);

imprime d0.dia_;

A cláusula protegidos em Data impede que o Atributo dia_ seja acessado


por código externo à Data.

Podemos ter também Métodos protegidos. Na implementação do Método


inicializa nada foi dito sobre o Método data_invalida. Podemos reescrever
Data assim:

classe: Data 

públicos:

- 25 -
2 -Apresentação dos conceitos básicos

método:   inicializa(dia:   inteiro,   mes:   inteiro,   ano   :   inteiro): 


boleano.

método: qual_ano() : inteiro;

protegidos:

atributo: ano_ : inteiro;

atributo: mes_ : inteiro;

atributo: dia_ : inteiro;

data_invalida(dia:   inteiro,   mes:   inteiro,   ano   :   inteiro): 


boleano.

fim­classe;

Da mesma forma, o código abaixo causa um erro de compilação:

d0 : Data;

se d0.data_invalida(19,8,2004) entao

imprime “invalido”;

senao

imprime “valido”;

fim­se.

É boa prática definir todos os Atributos como privativos, e definir Métodos


para acessá-los, se necessário. Assim, asseguramos que as Classes que
utilizarem Objetos de uma outra Classe, não precisarão saber como os
Atributos destes são implementados, tornando-se imunes à possíveis
mudanças de implementações.

2.6 Herança
Apresentamos detalhadamente o mecanismo de Herança, mencionado
rapidamente em Efeitos na Manutenção.

Em muitos problemas podemos encontrar Conceitos que se relacionam

- 26 -
2 -Apresentação dos conceitos básicos

através de um hierarquia de tipos. Um Porsche é um tipo de Carro Esporte,


que é um tipo de Carro, que é um tipo de Veículo Motorizado, que é um tipo
de Veículo. Um Pastor Alemão é um tipo de cão, que é um tipo de Mamífero,
que é um tipo de Ser Vivo. Um Botão de OK é um tipo de Botão, que é um
tipo de Controle de Interface (widget).

Estes pequenos exemplos demonstram que em várias problemas podemos


encontrar relações de hierarquia de tipos. Portanto, se queremos adotar
uma técnica de desenvolvimento que se apóie nos Conceitos do problema, é
muito interessante que possamos construir Classes que preservem estas
relações, inclusive de hierarquia de tipos. Este mecanismo é chamado
Herança.

A técnica de refinamentos sucessivos em muito se baseia na hierarquia de


tipos entre os Conceitos. Os tipos mais “altos” na hierarquia equivalem a
níveis mais altos de abstração, tendendo a revelar Classes que poderão ser
reutilizadas em várias situações, enquanto que níveis mais baixos da
hierarquia revelam Classes que terão uso em situações mais específicas.

Um pequeno exemplo:

classe Veiculo;

classe VeiculoMotorizado herda Veiculo;

classe CarroEsporte herda VeiculoMotorizado;

classe Porsche herda CarroEsporte;

Mas, na prática o que significa dizer que uma Classe herda de uma outra,
isto é, qual o efeito desta herança nos Atributos e Métodos?

Quanto a Visibilidade, podemos classificar um Atributo ou Método como


público, protegido ou privativo. Quando uma Classe A herda de uma classe
B, os Atributos ou Métodos públicos de A, também são públicos em B; os
Atributos ou Métodos protegidos de A, são privativos em B; e os Atributos
ou Métodos privativos de A não são acessados por B.

- 27 -
2 -Apresentação dos conceitos básicos

2.7 Construção e destruição de um Objeto


Uma característica bastante importante de linguagens Orientados por
Objetos, que, novamente, tenta imitar Objetos concretos, é o de ciclo de vida
de um Objeto. Ao criarmos uma instância de um Classe, ou seja, um Objeto,
seu ciclo de vida, seu escopo, se inicia. Associado a este início, está um
método específico chamado construtor, que é automaticamente chamado
quando um Objeto é criado.

Do mesmo modo, quando o ciclo de vida de um Objeto termina, quando ele


sai de escopo, um método específico, o destrutor, é executado
implicitamente.

Estes dois Métodos são bastante úteis na criação de Objetos robustas e


íntegros, pois no construtor asseguramos que os dados mínimos necessários
para a criação de um Objeto são fornecidos, enquanto no destrutor temos
como garantir que todos os outros Objetos utilizados sejam também
destruídos ou liberados.

2.8 Polimorfismo
Nem tanto uma técnica, mais uma consequencia da hierarquia de Classes,
Polimorfismo, no âmbito de Orientação por Objetos, é a capacidade de,
embora não sabendo de que Classe um Objeto a0 é instância, mas sabendo
que esta Classe faz parte de uma hierarquia, podermos aplicar um Método
m0, que seguramente está implementado em toda a Hierarquia, a a0. Além
disso, mesmo não sabendo exatamente de que Classe a0 é Objeto, o Método
m0 executado será o da Classe de a0.

Imagine que Orlando quer comprar um cão, um que tenha um latido bem
assustador. Orlando vai ao canil e explica ao proprietário que tipo de cão
quer. Ele venda os olhos do Orlando, o leva até a porta do canil, e pede que
fale, em tom baixo e firme, a palavra “late”. Orlando obedece, e uma
enxurrada de latidos enchem seus ouvidos, que imediatamente distingue um

- 28 -
2 -Apresentação dos conceitos básicos

latido bem feroz. O proprietário retira a venda e Orlando logo reconhece que
o dono do latido é um enorme pastor-alemão, em meio a dois buldogues, três
beagles, um pequinês, dois setter-irlandeses e quatro collies.

A hierarquia de cães pode ser implementada assim:

classe Cao;

públicos:

método late;

fim­classe;

class Buldogue : herda Cao;

públicos:

método late;

fim­classe;

class Beagle : herda Cao;

públicos:

método late;

fim­classe;

class Pequines : herda Cao;

públicos:

método late;

fim­classe;

class SetterIrlandes : herda Cao;

públicos:

método late;

fim­classe;

class Collie : herda Cao;

públicos:

- 29 -
2 -Apresentação dos conceitos básicos

método late;

fim­classe;

class PastorAlemao : herda Cao;

públicos:

método late;

fim­classe;

A classe Cao tem um Método late, assim como as Classes Buldogue,


Beagle, Collie, Pequines, SetterIrlandes e Pastor Alemão, que herdam, ou
são sub-classes de, Cao, e em todas temos também a definição do Método
late.

A Classe Canil pode ser assim entendida:

classe Canil;

privativos:

caes : colecao (cao);

públicos:

late;

novo_cao(novo : cao);

fim­classe;

Canil tem um Atributo privativo caes, da Classe colecao (cao). Coleção é


uma abstração para estruturas que podem conter Objetos da mesma Classe,
como listas, vetores, etc.

O Método late teria essa implementação:

Canil.late

inicio

para_cada (cao em caes) faca

cao.late;

fim­para;

- 30 -
2 -Apresentação dos conceitos básicos

fim­metodo;

Nada mais natural, se quero que todo os cães latam, aplico o Método late 
para cada Cao da coleção caes.

A inserção de um novo Cao seria:

Canil.novo_cao(novo : Cao)

inicio

caes.inserir(novo);

fim­metodo;

E como faríamos todo os cães do Canil latir?

Canil c;

c.latam;

Por enquanto, não temos grande novidades, mas isto já muda!

Como inserir um novo cão ao Canil, se o método novo_cao espera como


parâmetro um Objeto da Classe Cao? Vamos comprar um novo Buldogue
para exemplificar:

Canil c;

b : Buldogue;

c.novo_cao(b);

Mas como? É possível passar um Objeto da Classe Buldogue para um


Método que espera como parâmetro um Objeto da Classe Cao? Sim, porque,
pela hierarquia de Classes, um Buldogue é um tipo, uma especialização de
Cao.

Mas, se temos em Canil uma coleção de objetos da Classe Cao, como


Orlando, após falar “late” na porta do Canil, conseguiu distinguir o latido do
pastor-alemão em meio ao latido dos buldogues, collies, pequineses, beagles
e setter-irlandeses.

É exatamente isso o Polimorfismo: um Método definido em um nível da

- 31 -
2 -Apresentação dos conceitos básicos

hierarquia, mas que é implementado por cada sub-classe de uma maneira


diferente. Assim, quando Canil.late é executado, para cada Cao de caes, o
Método late que é executado é o específico da Classe do Objeto que está
em uma determinada posição da coleção. Logo, se os Objetos estão assim
em caes: collie, setter-irlandes, collie, buldogue, beagle, beagle, pastor-
alemao, pequinês, setter-irlandes, collie, buldogue, beagle, quando Orlando
disse “late”, ele ouviu um collie, depois um setter-irlandes, outro collie, um
buldogue, um beagle, outro beagle, um pastor-alemao, um pequinês, outro
setter-irlandes, mais um collie, o segundo buldogue, e o último beagle.
Óbvio que quando o último beagle começou a latir, o primeiro collie ainda
estava latindo!

Mas para que isso seja possível, o Método late em Cao deve ter uma
característica especial na Classe base da Hierarquia: ele deve ser virtual.

Revisando a Classe Cao:

classe Cao;

públicos:

método late; virtual;

fim­classe;

Entretanto, somente esta característica permitiria que o Método late não


fosse implementado em alguma sub-classe. Se isso acontece, o Método late 
executado é o de Cao. Se quisermos assegurar que todas as sub-classes de
Cao implementem late, este deve ser virtual abstrato, ou simplesmente
abstrato em Cao.

classe Cao;

públicos:

método late; virtual; abstrato;

fim­classe;

- 32 -
3 -Estudo de Caso – Definição

3 Estudo de Caso – Definição

A partir de agora iremos aplicar os conhecimentos adquiridos até aqui,


definindo, desenhando e implementado um CD Player. Veremos que, ao
trabalhar em níveis de abstração, podemos ter uma solução rápida, ainda
que longe da qualidade exigida, mas que pode ser rapidamente melhorada
para atingir a solução ótima.

3.1 Identificação das classes de objetos


Em um programa que toca CDs, começamos examinando o Objeto
principal: o CD. E como fazemos isso? Este é exatamente, como já foi dito
em Por que usar Orientação por Objetos?, uma das grandes vantagens do
uso de OO no desenvolvimento de programas: simplesmente olhamos um CD
que temos em nossa estante de CDs!

São características de um CD seu título, ano de gravação, artista, estilo


musical, nome das músicas, artistas que participaram de uma música,
instrumentos tocados pelos artistas na música, imagem da capa, gravadora,
e uma grande quantidade de outras informações.

Outro fundamental Objeto é o CD Player. Como descrever um CD Player?


Mais uma vez, olhe o seu CD Player! Um Player tem um botão de tocar,
parar, pausar, avançar para a próxima música, retroceder para a música
anterior, tocar a música mais rápido, um botão para abrir a gaveta, ou
bandeja, onde colocamos o CD. Vamos definir que o CD Player tem um visor,
para mostrar informações sobre a música que está sendo tocada, um outro
para mostrar a capa do CD, e um para mostrar todas as músicas do CD.

3.2 Identificação das relações entre as classes


Detalhamos um pouco agora algumas relações do CD com os outros

- 33 -
3 -Estudo de Caso – Definição

Objetos:

Um CD tem um único Título;

Um CD tem 4 Capas: a frontal, duas internas, e a traseira;

Um CD pode ter um único Encarte;

Um CD pode ter vários Sub-Títulos;

Um CD é classificado em ao menos um Estilo Musical;

Um CD tem um ou mais Artistas Principais;

Um CD pode ter vários Artistas Convidados;

Um CD contém ao menos uma Música;

Um CD foi produzido por uma Gravadora;

Um CD pode ter sido em um mais de um Ano;

Cada Música tem ao menos um Artista Participante, que toca ao menos um


Instrumento Musical;

As relações entre os Objetos associados ao Player são:

O Player tem um Botão de Tocar;

O Player tem um Botão de Parar;

O Player tem um Botão de Pausar;

O Player tem um Botão de Avançar Música;

O Player tem um Botão de Retroceder Música;

O Player tem um Botão de Abrir Gaveta;

O Player tem um Botão de Fechar Gaveta;

O Player tem um Visor para mostrar Informações do CD;

O Player tem um Visor para mostrar todas as Músicas do CD;

O Player tem um Visor para mostrar a Capa Frontal do CD;

O Player pode estar tocando uma Música de um CD;

- 34 -
3 -Estudo de Caso – Definição

Esta tarefa pode parecer enfadonha e um tanto óbvia, mas é fundamental


para definirmos, o mais cedo possível, o que estaremos contemplando no
nosso desenvolvimento. Por exemplo, nosso CD Player não exibirá os
Artistas que participam da Música, ou outra Capa que não a frontal.

O mais importante é ressaltar que partimos dos Objetos no mundo real,


concreto, para modelar suas conta-parte virtuais. Este processo nos guia a
termos Classes que funcionam exatamente como os Objetos concretos,
produzindo programas como mais chances de atenderem aos requisitos.

3.3 Casos de Uso


Casos de Uso são uma maneira de definir o que esperamos do programa
ou Classe, através da identificação de interações do usuário final ou de
outros programas. Nos Casos de Uso temos em mente o que o programa
deve fazer, não nos preocupamos em como é feito. São úteis para definir as
futuras Interfaces das Classes, e facilitar ao usuário final perceber o que o
programa fará.

Uma maneira interessante, e não muito ortodoxa, de identificar o que é


possível fazer com um Objeto, como interagir como ele, é imaginá-lo como
uma pessoa, alguém que possa responder a pedidos que desejamos que o
Objeto realize.

Ilustração 1: Caso de uso: CD

No nosso Estudo de Caso, concretamente, não há muito que podemos


fazer como o CD em si. Digo, o CD não é um Objeto “ativo”, que realize

- 35 -
3 -Estudo de Caso – Definição

operações. Entretanto, se encaramos um CD como uma “pessoa”, ou seja,


algo com o que pudéssemos interagir, podemos imaginar algumas
requisições? Vejamos:

Ilustração 2: Caso de Uso: CD Player

3.3.1 Exercício: Determinar todas as interações com um


CD
Talvez possa nos parecer mais simples determinar interações com um CD
Player, mas podemos também imaginá-lo como um pessoa capaz de
responder a requisições:

3.3.2 Exercício: Determinar todas as interações com o CD


Player
Muito importante perceber que conseguimos obter todas estas
características apenas observando, estudando os Objetos presentes no
problema.

- 36 -
4 -Introdução a Unified Modeling Language

4 Introdução a Unified Modeling Language

4.1 Um pequeno histórico da UML


Conforme descreve Booch1, a UML nasceu da junção do esforço de três
autores, o próprio Grady Booch, Ivar Jacobson e James Rumbaugh, em
meados da década de 1990. Eles resolveram unificar seus métodos de
Análise e Desenho, e criar uma linguagem visual que facilitasse o
desenvolvimento Orientado por Objetos. A primeira versão da UML foi
lançada em 1997, e em 14 de novembro de 1997, já na versão 1.1, a UML foi
oficialmente adotada com padrão pelo Object Management Group2.

4.2 Diagrama de Classes


Uma Classe tem a seguinte notação:

Ilustração 3: Classe em UML

A seguinte notação é usada para relação entre Classes:

Ilustração 4: Associação em UML

Este diagrama informa que a Classe A tem uma relação, neste nível de
1 Booch, Grady - “The Unified Modeling Language – User Guide”, Preface, pg xviii
2 www.omg.org

- 37 -
4 -Introdução a Unified Modeling Language

abstração é chamada associação, com a Classe B, onde pode estar presente


nenhum Objeto da Classe B, ou no máximo 1. Também informa que a Classe
B pode se relacionar com vários Objetos da Classe B.

Um outro exemplo:

Ilustração 5: Associação com direção


em UML

Além de informar que a Classe C tem uma associação com vários Objetos
da Classe D, este diagrama também revela que é possível acessar os Objetos
da Classe D, a partir de um Objeto da Classe C, mas dado um Objeto da
Classe   D, relacionado a um Objeto da Classe   C, não temos como acessar
este Objeto da Classe C a partir daquele Objeto da Classe D.

Durante os sucessivos refinamentos, a relação de associação será


reinterpretada, transformando-se em uma relação de composição ou
agregação.

A relação de agregação é diagramada assim:

Ilustração 6: Agregação em
UML

Este diagrama nos diz que um Objeto da Classe G terá, como um de seus
atributos, um Objeto da Classe H, e que este será criado quando um Objeto
da Classe G for criado. Assim, quando o destrutor do Objeto da Classe G for
executado, o Objeto da Classe H será destruído.

A relação de composição é notada assim:

- 38 -
4 -Introdução a Unified Modeling Language

Ilustração 7: Composição em UML

Basicamente, este diagrama nos diz que um Objeto da Classe E terá, como
um de seus atributos, um Objeto da Classe   F, mas este não será criado
quando um Objeto da Classe   E for criado. Assim, quando o destrutor do
Objeto da Classe E for executado, o Objeto da Classe F não será destruído.
Logo, em uma relação de composição, o Objeto usado como atributo
provavelmente será passado como parâmetro no construtor da Classe.

Um outro tipo de relacionamento entre classes é o de uso, com a seguinte


notação UML:

Ilustração 8: Uso entre classes em


UML

Este diagrama nos diz que um Objeto Classe J, será usado por um Objeto
da Classe I provavelmente durante a execução de um Método. Ao longo dos
sucessivos refinamentos, esta relação, muito provavelmente, será
reinterpretada como um Objeto da Classe J sendo passado por parâmetro
para um Método da Classe I.

- 39 -
4 -Introdução a Unified Modeling Language

Uma relação entre Classes típica de um ambiente com suporte a


Orientação por Objetos é a Herança, que tem a seguinte notação UML:

Ilustração 9:
Herança em
UML
4.3 Diagrama de Sequencia
Os diagramas de Sequencia são usados para definir como um Método
funciona, mostrando a chamada a Métodos de outros Objetos.

Ilustração 10: Sequencia


em UML

Em ferramentas que auxiliam a criar diagramas UML consistentes, ao


menos um aviso que a Classe E deve ter uma relação com a Classe F seria

- 40 -
4 -Introdução a Unified Modeling Language

emitido, caso contrário, um Objeto da Classe F não seria visível pela Classe 
E.

4.4 Diagrama de Estados


Uma importante maneira de conhecer um Objeto é estudar os possíveis
estados pelo qual passa, durante a execução de um Método, ou mesmo para
todos os Métodos.

Um Carro, por exemplo, passa pelos estados desligado, ligado, em


movimento, parado, etc. As transições entre os estados é diagramada assim
usando UML:

Ilustração 11: Máquina de estados de um Carro

Um modelo de estados de uma Classe fornece uma grande quantidade de


informações sobre as suas regras de funcionamento. Através de uma
notação simples, conseguimos identificar quais os modos de operação
válidos de uma Classe. Por este diagrama, sabemos que não é possível ir do
estado em movimento para o estado desligado diretamente.

- 41 -
5 -Desenhando o CD Player

5 Desenhando o CD Player

5.1 Exercício: Diagrama de Classes

5.2 Exercício: Diagrama de Estados

5.3 Exercício: Diagrama de Sequencia

5.4 Uma pausa: buscando generalizações

5.5 Morre um CD Player, nasce um Media Player

5.6 Exercício: Rever os diagramas para o Media Player

- 42 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

6 (Re) Desenho: Orientação por Objetos e Arquitetura em


Camadas

Podemos encontrar vários exemplos de programas estruturados em


camadas (layers), desde sistemas operacionais, editores de texto, jogos, e
todo sistema que deve funcionar a partir de um navegador Internet.

A arquitetura em camadas isola as partes do sistema, e assim é possível


acelerar seu desenvolvimento (definição, desenho, programação, teste e
manutenção), pois as partes tem tamanho e complexidades menores que o
todo; trocar uma camada sem que as demais sejam afetadas; distribuir as
camadas em computadores diferentes, geograficamente dispersos, que usam
diferentes processadores e sistemas operacionais; delegar uma ou mais
partes a uma outra equipe, talvez até a uma outra empresa, entre outras
vantagens.

Mas, como separar as partes de um sistema em camadas? Existem várias


maneiras, mas estamos interessados na que separa a interação do usuário, a
qual chamaremos de camada de Apresentação; da que efetua os cálculos,
que contém as regras de negócio, que chamaremos Processamento ou
Negócio; da que acessa os dados em meio permanente, ou memória
secundária, que chamaremos Armazenamento.

Um sistema estruturado desta maneira permite que diferentes camadas de


Apresentação sejam implementadas, fazendo uso da mesma camada de
Processamento. Isto permite o seguinte cenário: um usuário, estando na
empresa, usa o sistema através de uma programa que exibe uma tradicional
interface com várias janelas, recursos gráficos avançados, som, etc.; e em
casa, possa usar o sistema a partir de um navegador internet.

Da mesma forma, podemos mudar a camada de Processamento, mantendo


a de Apresentação, de forma que para o usuário não há mudança de
aparência, ou maneira de interagir com o sistema, mas ele poderia, por

- 43 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

exemplo, ver um resultado diferente para uma operação conhecida


(mudança de regra de negócio), ou simplesmente ter o mesmo resultado,
mas obtido de maneira mais rápida (mudança no tempo de execução).

A mesma relação existe entre a camada de Processamento e


Armazenamento, de forma a tornar possível a mudança da maneira de
armazenar os dados necessários para o sistema, por exemplo, de arquivos
texto para um servidor de dados, sem que a camada de Processamento seja
afetada.

6.1 Classes separando Apresentação, Processamento e


Armazenamento
Os objetivos alcançados com uma arquitetura em camadas estão bastante
alinhados com as características descritas em Apresentação dos conceitos
básicos, de modo que é razoável imaginar ser possível um sistema
funcionando em camadas e orientado por objetos.

Como exemplo, suponha que temos uma Classe Cifrador, responsável por
criptografar arquivos. Exercitando nosso conhecimento, podemos conceber
os seguintes refinamentos, ilustrados usando UML:

- 44 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

Ilustração 12: Modelo conceitual de classes de um Cifrador

Neste nível de conhecimento, modelamos uma hierarquia de tipos de


cifradores, e uma de tipos de digesters. Este diagrama também revela que
um cifrador vai usar, de forma ainda não detalhada, um digester.

Ilustração 13: Método calcula da class


digester - v0

Analisando a classe digester:

A função que calcula um valor resumo (digest) para um valor original,


pode ser definida tendo como entrada um vetor de bytes contendo o valor
original, e retornando um valor resumo. Reparem que a ferramenta bouml3.

3 http://bouml.free.fr/

- 45 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

usada para desenhar a classe digester, colocou o método calcula em


itálico, para visualmente refletir o fato de ser um método virtual.

Aprofundando nosso conhecimento sobre cálculo de hash, descobrimos


que o valor original pode ser extremamente grande, pois podemos querer
calcular um valor resumo sobre um arquivo com dezenas de megabytes.

Este novo desenho soluciona este problema:

Ilustração 14: Método calcula


da classe digester - v1

Nesta nova versão, acrescentamos o Método acumula, que vai armazenar


blocos do valor original, antes de o Método calcula ser chamado, efetuando
o cálculo do valor resumo.

Detalhamos a Classe cifrador:

Ilustração 15: Método cifra em


cifrador

O Método cifra recebe um vetor de bytes, presumidamente o valor


resumo final calculado. Até agora temos então:

- 46 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

Ilustração 16: Revisão da


relação entre cifrador e digester

Percebemos que a relação de uso identificada em Modelo conceitual de


classes de um Cifrador entre cifrador e digester não existe mais, isto
porque o Método cifra não espera como parâmetro um Objeto da Classe
digester, mas sim o valor resumo já calculado. A eliminação desta relação é
interessante, pois acaba com a dependência entre cifrador e digester,
deixando-os dependentes de um tipo simples de dados, um vetor de bytes.

Mas como este valor resumo será calculado, para em seguida ser passado
para cifra? O desenho abaixo ilustra a cração de uma Classe que gerencia a
criação de Objetos intermediários para o cálculo da criptografia.

Ilustração 17: Classe processador_cifragem

O objetivo deste Classe é invocar o Método calcula de digester, e após o

- 47 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

valor resumo ter sido calculado, invocar o Método cifra de cifrador. Mas
como um Objeto desta Classe terá acesso ao valor original, para passar para
digester, e em pequenos blocos, se necessário?

Examinemos o desenho:

Ilustração 18: Abstração para obtenção do valor original

O objetivo desta Classe é fornecer uma interface para obtenção do valor


original para o cálculo do valor resumo, independente da forma que este
valor original esteja estruturado. Exemplificamos duas possibilidades
através da Classe em_memoria, quando o valor original estaria todo em
memória principal; e da Classe em_arquivo, onde o valor original pode ser o
conteúdo de um ou mais arquivos locais. A separação do algoritmo do meio
de obtenção da sua entrada nos permite acrescentar outros métodos de
obtenção do valor original, em nada afetando ao cálculo do valor resumo.

Revendo o processador_cifragem, temos:

- 48 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

Ilustração 19: Processador de cifragem quase completo

Podemos ver que o processador_cifragem tem como obter o valor original,


informar ao digester para que calcule o valor resumo, e em seguida
fornecer ao cifrador para efetuar a criptografia. Mas, lembremos que
podemos ter diferentes tipos de algoritmo de hash, diferentes tipos de
algoritmos de criptografia, como ilustrados em Modelo conceitual de classes
de um Cifrador, e diferentes maneiras de obter o valor original, como mostra
o diagrama da Ilustração 18. Como manter a possibilidade de escolher qual
objeto deve ser construído?

O diagrama da Ilustração 20 abaixo propõe uma solução. Este diagrama


mostra três novas Classes: fornecedor_cifrador, fornecedor_digester e
fornecedor_valor_original. Estas classes tem o objetivo de retirar do
processador_cifragem a responsabilidade de conhecer todos os algoritmos
de criptografia e hash, e também os métodos de obtenção do valor original a
ser resumido.

- 49 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

Ilustração 20: Fornecedores de objetos

Precisamos definir como processador_cifragem vai pedir ao


fornecedor_cifrador um tipo específico de algoritmo de criptografia, o
mesmo vale para o fornecedor_digester. O desenho abaixo resolve esta
questão:

Ilustração 21: Interface para obter cifrador e digester

- 50 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

Assim, processador_cifragem pede ao fornecedor_cifrador um Objeto


cifrador baseado em um texto, que identifica o algoritmo, por exemplo,
“aes”, e o mesmo vale para fornecedor_digester. A interface para
fornecedor_valor_original precisa ser um pouco diferente:

Ilustração 22: Fornecedor de valor original

Se usarmos o Método obtem_valor_original(in   val   :   byte_array),


fornecedor_valor_original fornece um Objeto da Classe em_memoria, sub-
classe de valor_original responsável por o valor original caso este esteja
todo em memória principal. Mas se usarmos o Método
obtem_valor_original(in   nomes_arquivos   :   lista_string),
fornecedor_valor_original nos devolve um Objeto em_arquivo, sub-classe de
valor_original responsável por fornecer o valor original caso este seja o
conteúdo de vários arquivos.

Precisamos prover uma interface em processador_cifragem para que


Objetos que a usem efetivamente utilizar o sistema de criptografia:

Ilustração 23: Interface processador_criptografia

- 51 -
6 -(Re) Desenho: Orientação por Objetos e Arquitetura em Camadas

Finalmente, podemos mostrar o algoritmo para a criptografia de um valor


original.

processador_cifragem.cifra(in id_cifrador : string;
                           in id_digester : string;
                           in valor : byte_array) :  byte_array
inicio
cif : cifrador;
cif = forn_cifra.obtem_cifrador(id_cifrador);
dig : digester;
dig = forn_dig.obtem_digester(id_digester);
val_orig : valor_original;
val_orig = forn_valor_orig.obtem_valor_original(valor);
bloco : byte_array;
bloco = val_orig.proximo_bloco();
enq bloco.nao_vazio() faca
dig.acumula(bloco);
bloco = val_orig.proximo_bloco();
fim­enq
valor_resumo : byte_array;
valor_resumo = dig.calcula();
valor_cifrado : byte_array;
valor_cifrado = cif.cifra(valor_resumo);
retorna valor_cifrado;
fim;

6.2 Exercício: Finalizar o Desenho do Media Player

- 52 -
7 -Programação básica em C++

7 Programação básica em C++

7.1 Delimitadores

7.1.1 De comandos
Todo comando em C++ é delimitado por ; , como estes exemplos:

using namespace std;
int c;
cout << “Olá. Mundo!” << endl;

7.1.2 De bloco de comandos


Se quisermos criar um bloco de comandos, basta circundá-los por abre
chaves { e fecha chaves }.

7.2 Comentários
Um comentário em C++ é todo texto após barras duplas //, até o fim da
linha, ou o texto após uma barra, seguida de um asterisco /*, até o próximo
asterisco seguido de uma barra */.

Exemplo:

// o texto até o fim da linha é um comentário
/* aqui começa um comentário

que só termina aqui */

7.3 Tipos básicos


Os principais tipos básicos de C++ são char, wchar_t, int, float, double,

- 53 -
7 -Programação básica em C++

bool e void.

7.4 Variáveis
Variável é um sinônimo para um endereço de memória, onde existe um
conteúdo, limitado por um tipo, que também define um tamanho. Podemos
visualizar assim uma variável:

Ilustração 24: Visualização de uma variável

O nome de uma variável em C++ não tem limite de tamanho, e pode ser
formados por letras, dígitos e sublinhado (underscore), mas não podem
começar por dígitos:

int Inteiro;
int Inteiro1;
int Inteiro_1;
int Inteiro_1_1_a;
Podemos definir o valor de uma variável no momento da sua declaração:

float f0 = ­0.82;
double pi = 3.14;
A variável pi tem essa visualização:

- 54 -
7 -Programação básica em C++

Ilustração 25: Visualização da


variável pi

É possível definir uma variável de modo que seu conteúdo não possa ser
alterado:

const char opcao_sair = 's';

7.4.1 Vetores

7.4.1.1 Declarando
Não há em C++ uma palavra reservada para declarar um vetor, para tal
usamos o operador colchetes []:

int a [8];
Esta instrução declara um vetor de 8 posições, contadas a partir de 0, de
inteiros. Sua representação gráfica seria:

- 55 -
7 -Programação básica em C++

Ilustração 26: Vetor de inteiros

e0 é o endereço inicial do vetor de inteiros, a partir dele, a cada 2 bytes, o


tamanho de um inteiro, temos uma nova entrada do vetor. Repare que o
conteúdo de cada índice é aleatório, isto porque ao declararmos um vetor o
conteúdo de cada índice não está determinado.

7.4.1.2 Referenciando uma posição do vetor


Para inicializarmos as posições de a, poderíamos fazer (mais adiante
mostraremos a maneira eficiente de inicializar um vetor de qualquer
tamanho):

a[0] = 0;
a[1] = 0;
a[2] = 0;
a[3] = 0;

- 56 -
7 -Programação básica em C++

a[4] = 0;
a[5] = 0;
a[6] = 0;
a[7] = 0;
a[8] = 0;
Ou, a maneira mais eficiente e genérica:

int contador = 0;
while (contador < 8) {
a [contador] = 0;
contador = contador + 1;
}

7.5 Operadores
Neste tópico examinaremos os diversos operadores existentes em C++.

7.5.1 Atribuição
Como em qualquer linguagem de programação, uma atribuição copia o
conteúdo de uma variável para outra:

int i = ­9;

Ilustração 27: Atribuição - parte 1

int j = 17;

Ilustração 28: Atribuição - parte 2

- 57 -
7 -Programação básica em C++

j = i;

Ilustração 30: Atribuição - parte 3

7.5.2 Igualdade/Desigualdade
O operador de igualdade compara o conteúdo de duas variáveis, e o
resultado da avaliação é uma valor do tipo bool, ou seja, true ou false.

int u = 9;
int w = ­5;
if (u == w) {
cout << “'u' igual a 'w'”;

Ilustração 29: Atribuição - parte 3

}
else {
cout << “'u' diferente de 'w'”;
}
int t = ­5;
if (t != w) {
cout << “'t' diferente de 'w'”;
}
else {
cout << “'t' igual de 'w'”;
}

- 58 -
7 -Programação básica em C++

7.5.3 Relacionais
Os operadores relacionais em C++ são:

● >
● <
● >=
● <=

7.5.4 Lógicos
Os operadores lógicos em C++ são:

! (not)
||  (or)
&& (and)
Sempre que o conteúdo de uma variável for diferente de zero, o uso
apenas do nome da variável em uma expressão resulta em true:

int var = ­2;
if (var) {
cout << “true”;
}
else {
cout << “false”;
}

7.5.5 Incremento e decremento


C++ permite que façamos um incremento, ou decremento, isto é, somar
ou diminuir 1 de uma variável do tipo inteiro, usando uma notação mais
econômica:

int i = 9;
i = i + 1;
++i;
Os comandos i = i + 1 e ++i são equivalentes. Da mesma forma:

- 59 -
7 -Programação básica em C++

int p = 11;
p = p – 1;
­­p;
Os comandos p = p – 1 e –­p são equivalentes.

7.6 Variáveis ponteiros

7.6.1 Tipos de memória


Antes de falarmos sobre variáveis ponteiros, é interessante relembrarmos
os tipos de memória acessadas por um programa:

• Estática (static): onde residem as variáveis criadas durante a


compilação e ligação do programa;

• Pilha (stack): onde residem as variáveis locais de uma função;

• Dinâmica (heap): onde residem variáveis criadas durante a execução


do programa;

7.6.2 Declarando um ponteiro – versão não segura


Em C++, as áreas de memória alocadas dinamicamente, isto é, variáveis
que residem no heap, são referenciadas por variáveis ponteiros, ou
simplesmente ponteiros. Um ponteiro é uma variável cujo conteúdo é um
endereço de memória:

int * i0;
i0 é uma variável cujo conteúdo é um endereço de memória, que ainda
não foi definido, onde estará um conteúdo inteiro:

- 60 -
7 -Programação básica em C++

Ilustração 31: Variável ponteiro não


inicializada

Neste momento, o conteúdo de i0, mostrado no diagrama como ??? é


indefinido, ou seja, qualquer endereço de memória pode estar na área de
memória e0.

7.6.3 Declarando um ponteiro – versão segura


Uma maneira segura de declarar e definir uma variável cujo conteúdo é
um endereço de memória é:

int * i0 = NULL;
ou

int * i0 = 0;

Ilustração 32: Visualização de ponteiro


inicializado

Desta forma, temos a garantia que o conteúdo de i é um endereço de


memória inválido. Programadores devem testar se um ponteiro contém este
valor antes de tentar acessar a área de memória apontada, como veremos
em Checando a memória alocada.

- 61 -
7 -Programação básica em C++

7.6.4 Alocando memória


Para criarmos dinamicamente uma área de memória, devemos fazer:

int * i = 0;
.
.
.
i = new int (­3);

Ilustração 33: Ponteiro para inteiro

i0 é um ponteiro para inteiro, seu conteúdo é o endereço de memória e1;


e1 é o endereço de memória cujo conteúdo é um inteiro de valor ­3.

7.6.5 Checando a memória alocada


Após termos alocado a memória dinamicamente, é essencial testarmos se
o sistema operacional realmente conseguiu alocá-la:

int * i = 0;
.
.
.
i = new int (­3);
if (i != 0) {
cout << “Memória alocada!”;
}

- 62 -
7 -Programação básica em C++

else {
cout << “Memória NÃO alocada!”
}
A expressão (i != 0) resulta em true o conteúdo de i não for zero, ou
seja, o endereço referenciado por i for válido.

7.6.6 Referenciando a memória alocada


A memória dinamicamente alocada é referenciada assim:

int * i = 0;
.
.
.
i = new int (­3);
if (i != 0) {
cout << “Memória alocada!”;
}
else {
cout << “Memória NÃO ALOCADA!”
}
*i = 142;

Ilustração 34: Modificando uma área


dinamicamente alocada

O comando *i = 142; referencia a área de memória cujo endereço é o

- 63 -
7 -Programação básica em C++

conteúdo de i, e em seguida atribui a esta área de memória o valor inteiro


142.

7.6.7 Atribuição
E como seria uma atribuição de ponteiros?

int * p = 0;

Ilustração 35: Atribuição de ponteiros -


parte 1

p = new int (­12);

Ilustração 36: Atribuição de ponteiros -


parte 2

int * r = 0;

- 64 -
7 -Programação básica em C++

Ilustração 37: Atribuição de ponteiros -


parte 3

r = p;

Ilustração 38: Atribuição de ponteiros -


parte 4

Como podemos ver, o conteúdo de p, o endereço de memória e2, foi


copiado para r. Agora, podemos acessar o endereço e2 usando a variável p 
ou r:

*p = 5;

- 65 -
7 -Programação básica em C++

Ilustração 39: Atribuição de ponteiros -


parte 5

*r = 193;

Ilustração 40: Atribuição de ponteiros -


parte 6

7.6.8 Igualdade/Desigualdade
Ponteiros também podem ser comparados, mas devemos ter cuidado, pois
se compararmos o conteúdo de dois ponteiros, estaremos comparando dois

- 66 -
7 -Programação básica em C++

endereços de memória, e não a área de memória apontada:

int *var1 = new int (43);
int *var2 = new int (­55);

Ilustração 41: Comparação entre ponteiros

Portanto, se escrevermos:

if (var1 == var2) {
cout << “Conteúdo de 'var1' igual a 'var2'”;
}
else {
cout << “Conteúdo de 'var1' diferente de 'var2'”;
}
Estamos comparando e2 a e4, o que sempre resultará false.

Se queremos comparar o conteúdo da área apontada por var1 com o


conteúdo da área apontada por var2 temos que escrever:

if (*var1 == *var2) {
cout << “Conteúdo da área apontada por 'var1' igual ao ”;
cout << “conteúdo da área apontada por 'var2'”;
}
else {
cout << “Conteúdo da área apontada por 'var1' diferente do ”;
cout << “conteúdo da área apontada por 'var2'”;
}

- 67 -
7 -Programação básica em C++

7.6.9 Operadores Relacionais


As mesmas observações quanto a comparação de ponteiros feita em
Igualdade/Desigualdade também são válidas para os operadores relacionais.

7.6.10 Operadores Lógicos


As regras descritas em Operadores Lógicos valem para ponteiros, ou seja,
se o conteúdo de um ponteiro, isto é, um endereço de memória, é diferente
de zero, se o ponteiro for usado em uma expressão lógica, a avaliação da
expressão é true.

int * i = new int (4);
if (i) {
cout << “true”;
}
else {
cout << “false”;
}
Se a alocação for be sucedida, o conteúdo de i será um endereço de
memória diferente de zero, logo a expressão if (i) resulta em true.

7.6.11 Implementando vetores com ponteiros


Vimos em Vetores uma maneira de implementar um vetor de inteiros.
Mostraremos agora como implementar o mesmo vetor, mas utilizando
ponteiros:

- 68 -
7 -Programação básica em C++

int * a = new int[8];

Ilustração 42: Ponteiro como vetor

Como mostra o diagrama, esta instrução está declarando um ponteiro


para inteiro, aloca dinamicamente uma área de memória suficiente para 8
inteiros, e atribui o endereço desta área à variável a. 

7.6.11.1 Ponteiros para caracteres


Em C++, na verdade um legado de C, usamos ponteiros para caracteres
para definir uma string, uma palavra. Como convenção, a última posição de
uma string é o caracter '\0'. Esta convenção em muito facilita a
implementação de funções que manipulem strings, como cálculo de tamanho
do string, comparação entre dois strings, busca de um string, etc. O exemplo

- 69 -
7 -Programação básica em C++

abaixo mostra como podemos declarar um ponteiro para caracteres, e


inicializá-lo, dispensando a alocação dinâmica:

char * s = “um string”;

Ilustração 43: Ponteiro para


caracteres

Uma maneira equivalente seria:

char * s = 0;
s = new char[10];

- 70 -
7 -Programação básica em C++

strcpy(s, “um string”);
Repare que o “um string” tem nove caracteres, mas a instrução s = new 
char[10]; aloca memória para 10 caracteres. Isto porque, como mostra a
figura acima, é necessário um byte para armazenar o caracter '\0', que
marca o fim do string.

7.6.12 Aritmética de ponteiros


Existem algumas operações que podem ser feitas sobre o conteúdo de um
ponteiro, ou seja, sobre um endereço de memória, além da descrita em
Referenciando a memória alocada. É possível acessar um endereço de
memória mais alto ou mais baixo, a partir de um conhecido.

int * a = new int[8]; // ver Ponteiro Como Vetor acima
int * b = a;

Ilustração 44: Aritmética de


ponteiros - parte 1

Esta instrução declara um ponteiro b, para inteiro, e copia o conteúdo de


a, o endereço e0, para b.

++b;

Ilustração 45: Aritmética de


ponteiros - parte 2

- 71 -
7 -Programação básica em C++

Esta instrução incrementa o conteúdo de b em 1. Como o conteúdo de b é


um endereço de memória, é este endereço que será incrementado, não em
uma posição de memória, mas de um número de posições igual ao tamanho
do tipo apontado. Como b aponta para um inteiro, a nova posição de
memória será a anterior mais dois, ou seja, e0 + 2.

*b = 444;

Ilustração 46: Aritmética de ponteiros


– parte 3

Esta instrução acessa a nova área de memória apontada por b, e atribui o


valor 444. Reparem que a área em e0 não é alterada.

7.6.13 Acessando posições de um vetor como ponteiro


No exemplo anterior, seria possível alterar a segunda posição do vetor
apontado por a, usando a própria variável a:

*(a + 1) = 444;
Esta instrução soma 1 ao conteúdo de a, e como a aponta para um inteiro,
a área referenciada será duas posições de memória subsequentes à e0, ou
seja, e0   +   2, em seguida acessa esta área através do operador *, e
finalmente atribui a área de memória endereçada o valor 444.

- 72 -
7 -Programação básica em C++

7.7 Endereço de uma variável


Em C++ é possível obter o endereço de uma variável através do operador
&.

int a = 8;

Ilustração 47: Endereço de


uma variável - parte 1

int *b = &a;

Ilustração 48: Endereço de uma


variável - parte 2

7.8 Comandos condicionais


Ao longo dos exemplos usamos informalmente o comando condicional, que
tem a forma:

if (expressão verdadeira) {
bloco de comandos;
}
else {
outro bloco de comandos;
}

- 73 -
7 -Programação básica em C++

7.9 Comandos para interação


Em C++ existem várias comandos para implementar uma repetição de im
bloco de comandos::

for ( inicialização das condições; 
 enquanto condições forem verdadeiras; 
     comandos que modificam as condições) {
bloco de comandos;
}
Exemplificando:

for (int i = 0; i < 18; ++i) {
cout << “i = “ << i << endl;
}
O outro comando é o while:

while (condição verdadeira) {
bloco de comandos;
}
Novamente um exemplo:

int i = 0;
while (i < 18) {
cout << “i = “ << i << endl;
++i;
}
O do­while executa o teste ao final;

do {
bloco de comandos;
} while (condição verdadeira);
Outro exemplo:

int i = 0;
do {
cout << “i = “  << i << endl;
++i;
} while (i < 17);

- 74 -
7 -Programação básica em C++

7.10 Funções
Todo algoritmo em C++ é implementado em forma de uma função, mesmo
que não haja valor de retorno.

7.10.1 Declaração
Na declaração de uma função identifica que tipo de dados a função
devolverá ao fim da sua execução, qual o seu nome, e quais os argumentos
que devem ser fornecidos quando a função for executada:

tipo­de­retorno nome­da­função ( argumentos ) ;
Onde:

tipo­de­retorno é o tipo de dados que será retornado à função chamadora


quando a função chamada terminar sua execução. Qualquer tipo de dados
nativo de C++, ou qualquer Classe criada poder ser usada como tipo
retornado por uma função;

nome­da­função é identificador da função, e segue as mesmas regras de


nomenclatura de uma variável;

argumentos é uma lista de :

tipo­do­argumento nome­do­argumento = valor­padrao­opcional,

Exemplos:

// uma função que retorna um inteiro, mas não tem argumentos
int funcao1(); 
// uma função que retorna um float, e tem como argumento um inteiro
float funcao_2(int i);
// uma função que retorna um char, que tem como argumentos um char, e
// um double, com valor default ­0.0321
char _FunCao3(char c, double d = ­0.0321);

- 75 -
7 -Programação básica em C++

7.10.2 Definição
A definição da função define a implementação da função:

int funcao1() {
return ­19;
}
float funcao_2(int i) {
float f = 3.14 * i;
return f;
}
char _FunCao3(char c, double d) {
char retorno = 'f';
if (d > 4.75) {
if (c == 'A) {
retorno = 'F';
}
else {
retorno = 'Z';
}
}
else {
if (c == 'R') {
retorno = 'K';
}
else {
retorno = 'W';
}
}
return retorno;
}
Reparem que na definição da função _FuncCao3, o valor padrão do
argumento d não é repetido.

- 76 -
7 -Programação básica em C++

7.10.3 Execução
Como todo algoritmo é implementado em C++ como uma função, é
razoável concluir que uma função sempre terá sua execução iniciada e
finalizada dentro do escopo de uma outra função, criando uma relação de
função-chamadora e função-chamada.

Como exemplo, suponha as seguintes declarações:

int funcaoF0 (int i = ­8);
int funcaoF1();
Seguidas das definições:

int funcaoF1() {
return ­18;
}
int funcaoF0(int i) {
int rc = 0;
if (i > 10) {
rc = funcaoF1();
}
else {
rc = 1;
}
return rc;
}
Reparem que a instrução rc   =   funcaoF1(); efetua uma chamada a
funcaoF1, iniciando sua execução. Quando funcaoF1 terminar, o valor ­18 
será retornado, e em seguida atribuído a variável rc.

7.10.4 Definição de argumentos e passagem de parâmetros


Classicamente, nas linguagens de programação estão definidos dois tipos
de passagem de parâmetros: por cópia e por referência.

Na passagem por cópia, é feita uma cópia do parâmetro para a função.

- 77 -
7 -Programação básica em C++

Suponha a seguinte função:

// declaração
float m (int i);
// definição
float m (int i) {
return i * 4.09;
}
// execução
int k = 8;

Ilustração 49: Passagem de


parâmetro – parte 1

float f = m (k);

Ilustração 50: Passagem de


parâmetro – parte 2

Antes da função m ser executada, a variável k está no endereço p90, e


conteúdo 8. Quando a função m é executada, k é passado como parâmetro, e
o argumento i tem o mesmo valor que k, mas um endereço diferente, ou
seja, é uma outra área de memória. Logo k foi copiado para i.

Na passagem por referência, i seria apenas um sinônimo para k, ou seja, o


mesmo endereço de memória seria usado no corpo de m. Não há em C++
passagem de parâmetros por referência, mas é possível, graças a
capacidade de manipular endereços de memória, fazer com que a área
referenciada dentro de uma função seja igual a área da variável passada por

- 78 -
7 -Programação básica em C++

parâmetro. Existem duas maneiras de conseguir isso:

// declaração
float w (int * i);
// definição
float w (int * i) }
return (*i) * 4.09;
}
// execução
int r0 = 8;

Ilustração 51: Passagem de


parâmetro – parte 3

Neste ponto da execução, a variável r0 é declarada e definida com valor 8;

float f = w( &r0 );

Ilustração 52: Passagem de


parâmetro – parte 4

A variável f é declarada, e a função w é executada, recebendo como


parâmetro o endereço da variável r0. Assim, o valor do argumento i, que é
um ponteiro para um inteiro, será o endereço de r0, como mostra a figura
acima.

int r1 = new int (8);

- 79 -
7 -Programação básica em C++

Ilustração 53: Passagem de


parâmetro – parte 5

Aqui é declarada a variável r1, que é um ponteiro para um inteiro, alocada


uma área de memória que conterá um inteiro de valor 8, e o endereço da
área alocada é armazenado em r1, como mostra o diagrama acima.

f = w (r1);

Ilustração 54: Passagem de


parâmetro – parte 6

A função w é chamada recebendo como parâmetro a variável r1, cujo valor


é copiado para o argumento i. Como i é também um ponteiro para inteiro,
seu conteúdo será o mesmo que r1, o endereço de memória onde está o
valor 8, como mostra a ilustração 54.

7.10.5 A função main e seus argumentos


Existe uma função especial em C++, que identifica a primeira função a
ser executada em um programa, também chamada de ponto de entrada do

- 80 -
7 -Programação básica em C++

programa. Esta função chama-se obrigatoriamente de main, cujo nome


reservado, embora não seja uma palavra reservada da linguagem.

A função main dever retornar um tipo inteiro, e pode opcionalmente


receber dois parâmetros que representam os parâmetros passados ao
programa: um inteiro, que identifica o número de parâmetro passados; e um
vetor de strings (ponteiros para caracteres), com os valores dos parâmetros.
O exemplo abaixo mostra uma função main que não recebe parâmetros:

int main () {
int rc = 0;
// código que será executado quando o programa for executado
return rc;
}
Este exemplo mostra uma função main que permite ao programa receber
parâmetros:

int main (int argc, char *argv[]) {
int rc = 0;
// código que será executado quando o programa for executado
return rc;
}
Suponhamos que o programa pgm00 possa receber três parâmetros: um
inteiro, um real, e uma palavra, e a seguinte chamada ao programa:

pgm00 86 3.019 doce

- 81 -
7 -Programação básica em C++

Como base no que vimos em Vetores e Variáveis ponteiros, podemos


visualizar o parâmetros passados a pgm00 assim:

Ilustração 55: Parâmetros da função main

char *argv[] é um vetor de ponteiros para caracteres. argc é um inteiro


que define o número de argumentos passados para pgm00, ou seja, o número
de entradas do vetor argv[], que estão nos endereços e4, e8 e e12. Em cada

- 82 -
7 -Programação básica em C++

posição desta temos um endereço do início da sequencia de caracteres


correspondentes ao parâmetro passado. Assim, em e100 se inicia uma string
que contém “86”, em e103 a string “3.019”, e em e109 a string “doce”.

Na função main poderíamos escrever o seguinte código:

int main (int argc, char *argv[]) {
int rc = 0;
if (argc != 4) { // o nome do programa é sempre 
                      // o primeiro parâmetro
cout << “Uso: pgm00 <inteiro> <real> <string>”;
rc = 1;
}
else {
cout << “O primeiro parâmetro é “ << argv[1] << endl;
cout << “O segundo parâmetro é “ << argv[2] << endl;
cout << “O terceiro parâmetro é “ << argv[3] << endl;
}
return rc;
}

7.11 Estruturas e novos tipos de dados (não são Classes!)


C++, na verdade um legado de C, permite agrupar variáveis em uma
estrutura, e atribuir um nome a esta nova estrutura, fazendo-a semelhante a
um tipo de dados nativo. Entretanto, este mecanismo não implementa os
mesmos mecanismos de Classes, como o acesso aos atributos apenas através
de métodos (encapsulamento), herança e outros.

Vejamos o exemplo:

typedef struct estrutura_a {
int campo1;
float campo2;
char *campo3;
};
Podemos declarar variáveis do “tipo” estrutura_a:

- 83 -
7 -Programação básica em C++

estrutura_a var_a;

Ilustração 56: Declarando uma


variável de um tipo

var_a.campo1 = ­4;
var_a.campo2 = 0.13;
var_a.campo3 = new char [4];

Ilustração 57: Definindo valores dos


campos de uma estrutura – parte 1

- 84 -
7 -Programação básica em C++

strcpy(var_a.campo3, “ola”);

Ilustração 58: Definição dos campos


de uma estrutura - parte 2

7.12 Ponteiros para funções


Como uma função é executada? Existe um registrador na CPU chamado
Instruction Pointer (IP) que contém o endereço da próxima instrução a ser
executada. Quando uma função é chamada, o endereço onde está a primeira
instrução da função é carregado no IP, e a CPU inicia a sua execução.

Em C++ o endereço do início de uma função é referenciado pelo seu


nome, semelhante a uma variável ponteiro que contém um endereço de
memória. Como consequencia, é possível declarar um ponteiro que
armazene o endereço de início de uma função:

// declarando um tipo de função, que tem um argumento inteiro e 
retorna um float
typedef float (*t_f) ( int) ;
// definindo uma função que tem um argumento inteiro e retorna um 
float

- 85 -
7 -Programação básica em C++

float g ( int i) 
{
    return ( i * 9.2) ;
}
// a função main
int main( ) 
{
// declarando uma variável que de um tipo de função
// esta variável é na verdade um ponteiro para uma 
// função do tipo t_f
t_f f ;

// aqui acontece uma simples atribuição de ponteiros, 
// como visto em Atribuição de ponteiros
f = g;

// aqui usamos o ponteiro para função para executar a função
cout << ( *f) ( 4) << endl;
return 0;
}

7.13 Pré-processamento
É possível tomar algumas decisões antes do código começar a ser
compilado. Neste momento são avaliadas macro definições – constantes e
comados – e as diretivas de compilação, que permitem mudar
completamente o código que será compilado.

7.13.1 Macro constante


É um símbolo definido para uma constante:

#define MAX 10
#define SAUDACAO “Ola!”
#define PI 3.14

- 86 -
7 -Programação básica em C++

Este comando não aloca memória de qualquer espécie (estática, pilha ou


dinâmica), apenas cria um sinônimo para o número 10. Assim, ao invés de
usarmos o número 10 ao longo do programa, usamos o símbolo MAX. A
vantagem do uso de (macro) constantes, é que se houver necessidade de
modificar o valor de 10 para 15, basta mudar a linha acima para #define MAX 
15, e recompilar o programa.

7.13.2 Macro comando


Semelhante às macro constantes, um macro comando define, não um
símbolo que será sinônimo de um valor, mas de um conjunto de instruções:

#define area_circulo( x ) (PI *  ( x ) * ( x )  )
O compilador irá substituir todas os símbolos area_circulo(4) pelos
símbolos PI * 4 * 4.

7.13.3 Diretivas de compilação


É possível mudar completamente o código a ser compilado usando a
diretiva #ifdef.

float m (int i) {
float f = 0.0;
#ifdef A
f = i * 3.91;
#else
f = i * 4.01;
#endif
return f;
}
Assim, se o símbolo A estiver definido, o código compilado será f = i * 
3.91;, caso contrário será compilada a instrução f = i * 4.01;.

- 87 -
8 -Orientação por Objetos e C++

8 Orientação por Objetos e C++

8.1 Classes
A declaração de Classes em C++ tem esta sintaxe:

class nome_da_classe {
public:
atributos_públicos
métodos_públicos
protected:
atributos_protegidos
métodos_protegidos
private:
atributos_privativos
métodos_privativos
};

8.2 Objetos
Como dissemos em Objeto, um Objeto está para uma variável, assim como
uma Classe está para um tipo. E isso ainda é mais patente porque a sintaxe
de declaração de um Objeto é a mesma de uma variável:

classe_a objeto_da_classe_a;

8.3 Atributos
Atributos são essencialmente Objetos, ou ponteiros para Objetos,
declarados dentro de uma Classe:

// uma classe classe_b
class classe_b {
.

- 88 -
8 -Orientação por Objetos e C++

.
.
};

// uma classe classe_a
class classe_a {
private:
// um Objeto da classe_b declarado dentro da class_a
classe_b b_;
};

8.4 Herança
Como foi dito em Herança, quando uma Classe herda de outra, recebe os
métodos e atributos públicos, que se mantém públicos; os atributos e
métodos protegidos, que se tornam privativos; e não recebe os atributos ou
métodos privativos.

Isto é verdade se o tipo de herança for pública:

class classe_a {
};
class classe_b : public classe_a {
};
Entretanto, podemos definir a herança como protegida ou privativa. Na
protegida, os métodos e atributos públicos se tornam protegidos; os
atributos e métodos protegidos, e os atributos ou métodos privativos, não
são herdados.

class classe_a {
};
class classe_b : protected classe_a {
};
Na herança privativa, os os métodos e atributos públicos se tornam
privativos, e os demais não são herdados.

class classe_a {

- 89 -
8 -Orientação por Objetos e C++

};
class classe_b : private classe_a {
};

8.4.1 Herança Múltipla


Em C++ é possível termos uma Classe que seja sub-Classe de duas outras
Classes.

class classe_a {
};
class classe_b {
};
class classe_a : public classe_a, public classe_b {
};
Os tipos de herança, e suas consequencias na visibilidade dos atributos e
métodos herdados, descritas no tópico acima, continuam valendo para a
herança múltipla.

8.5 Construtores
Construtores são métodos que são executados quando um Objeto é criado.
É possível declarar mais de um construtor para uma Classe.

Class classe_a {
public:
// construtor sem argumentos
classe_a();

// construtor com um argumento
classe_a(int i);
private:
// um atributo de um tipo básico
int i_;
};

- 90 -
8 -Orientação por Objetos e C++

// definição do primeiro construtor
classe_a::classe_a() :
i_(­1) {
}
// definição do segundo construtor
classe_a::classe_a(int i) :
i_(i) {
}
Reparem que os métodos construtores admitem uma sintaxe especial para
inicializar os atributos:

nome_da_classe::construtor(lista_argumentos) :
atributo_1(valor_1),
atributo_2(valor_2) {
}
Esta inicialização dos atributos garante que os construtores das Classes
dos atributo serão chamados, e assim teremos também os atributos
construídos correta e confiavelmente.

// declaração de um Objeto da classe_a usando o primeiro construtor
classe_a a0;
class_a a1(9);

8.5.1 Construtores de cópia


Podemos declarar um construtor que efetue uma cópia de um outro Objeto
da mesma Classe:

class classe_a {
public:
// um construtor com argumentos
classe_a (int i);

// construtor que tem como argumento um outro Objeto
// também da classe_a

// como não iremos modificar este outro Objeto, 

- 91 -
8 -Orientação por Objetos e C++

// qualificamos o argumento como const, e para evitar que ao
// passarmos o outro Objeto, uma cópia seja feita do Objeto
// passado para o parâmetro, passamos o endereço, assim, o
// parâmetro vai conter o endereço do Objeto passado
classe_a(const classe_a &outro_obj);
private:
int i_;
};
// Definindo o construtor com um argumento
classe_a::classe_a(int i) :
i_(i) {
}

// Definindo o construtor de cópia
classe_a::classe_a (const classe_a &outro_obj) :
i_(outro_obj.i_) {
}
Reparem que embora i_ seja atributo privativo de classe_a, eu consigo
acessar o i_ pertencente ao parâmetro outro_obj. Isto é possível porque o
código está dentro do escopo da classe_a.

Usando o construtor de cópia:

// usando o construtor com um parâmetro
classe_a a0(­7);
// usando o construtor por cópia
classe_a a1(a0);
Vamos ilustrar o que aconteceria se declarássemos o construtor de cópia
assim classe_a(classe_a   outro_obj);. Após a linha classe_a   a0(­7) 
teríamos:

Ilustração 59: Porque usar


endereço em construtor
de cópia - parte 1

- 92 -
8 -Orientação por Objetos e C++

Mudamos um pouco nossa ilustração de uma variável para adaptá-la a


Objeto, visto que, na grande maioria das vezes, uma Classe terá atributos.

No linha classe_a a1(a0) do exemplo acima teríamos, no momento que a0 


é passado como parâmetro:

Ilustração 60: Porque usar


endereço em construtor de cópia -
parte 2

Ou seja, o construtor de cópia da classe_a foi chamado automaticamente


para que a0 fosse copiado para o parâmetro outro_obj. Por fim, a cópia de
outro_obj para a1, e neste momento tínhamos três Objetos de classe_a em
memória: a0, outro_obj e a1:

Ilustração 61: Porque usar


endereço em construtor de
cópia - parte 3

- 93 -
8 -Orientação por Objetos e C++

Então, o que acontece quando usamos o construtor na forma classe_a 


(const   classe_a   &outro_obj)? Inicialmente temos o mesmo cenário da
Ilustração 55 , mas quando o construtor de cópia é invocado:

Ilustração 62: Porque usar


endereço em construtor de
cópia - parte 4

Reparem que outro_obj tem o mesmo endereço de a0, ou seja, é como se


outro_obj fosse a0, mas apenas identificado por outro nome. Desta forma,
não temos a chamada automática do construtor de cópia de classe_a para
copiar a0 para outro_obj.

8.6 Destrutores
Destrutor é um método que é chamado automaticamente quando um
Objeto termina seu ciclo de vida, ou seja, perde o escopo.

classe_a {
public:
~classe_a();
};
// declarando um Objeto da classe_a em um bloco de comando:
{
classe_a a0;
// outro comandos..
} // exatamente neste fecha­chaves, o destrutor da 

- 94 -
8 -Orientação por Objetos e C++

  // classe_a é chamado para a0
No destrutor é colocado código para liberar memória, fechar arquivos,
enfim, liberar recursos que foram alocados durante o ciclo de vida de um
Objeto.

8.7 Métodos
Métodos são funções declaradas dentro de uma Classe.

class classe_a {
public:
// construtor
classe_a(const int &i);

// destrutor
~classe_a(const int &i);

// a declaração do método
float m1(int k = 0);

// a declaração de um método que garante que nenhum atributo de 
classe_a será modificado
const & i get_i() const;
private:
int i_;
};

// as definições dos métodos
classe_a::classe_a(const int & i)
: i_(i) {
}

classe_a::~classe_a() {
}

- 95 -
8 -Orientação por Objetos e C++

float class_a::m1(int k) {
return 7.5 * (i_ + k);
}

const int &class_a::get_i() const {
return i_;
}

8.7.1 Sobrecarga
É possível ter dois métodos com o mesmo nome, mas tendo diferentes
argumentos.

classe_a {
public:
// construtor com um argumento
classe_a(int i);
// método m com um argumento
void m(int i);
// método m com nenhum argumento
void m();
private:
int i_;
};

// definição do construtor
classe_a::classe_a(int i) :
i_(i) {}

// definição do método m com um parâmetro
void classe_a::m(int i) {
i_ += i;
}

// definição do método m com nenhum parâmetro
void classe_a::m() {

- 96 -
8 -Orientação por Objetos e C++

i_ += 8;
}
Quando declaramos um Objeto de classe_a, e usarmos os dois método, o
compilador sabe qual deles deve associar o endereço a ser colocado no
Instruction Pointer.

classe_a a0(18);
a0.m(3);
a0.m();
Ao final, a0::i_ terá o valor 29.

8.7.2 Métodos Virtuais


Como vimos em Polimorfismo, um método virtual é aquele que é declarado
e definido em uma Classe, mas que poderá ser redefinido em uma sub-
Classe.

classe_a {
public:
classe_a();
// para ser virtual, o método deve ter na sua declaração a 
// palavra reservada 'virtual' antes da especificação da Classe
// do Objeto que irá retornar 
virtual float m(int i);
private:
int i_;
};
// uma sub­classe
classe_b : public classe_b {
public:
classe_b();
float m(int i);
};
// definindo classe_a
classe_a::classe_a() :
i_(1) {

- 97 -
8 -Orientação por Objetos e C++

}
float classe_a::m(int i) {
return 6.2 + (i_ * i);
}
// definindo classe_b
classe_b::classe_b() :
classe_a() {
}
float classe_b::m(int i) {
return ­0.23 * (i_ / i);
}
// usando...
classe_b b;
b.m(3);
O método que será executado em b.m(3) será o método classe_b::m(int 
i);.

8.7.3 Métodos Abstratos


São métodos declarados, mas não definidos, com o objetivo de garantir
que as sub-Classes obrigatoriamente implementem este método. Classes que
têm métodos abstratos são ditas abstratas, e não podem ser instanciadas.

classe_a {
public:
classe_a();
// para ser abstrato, o método deve ter na sua declaração a 
// palavra reservada 'virtual' antes da especificação da Classe
// do Objeto que irá retornar, e antes do ';' deve ter 
// os símbolos '= 0'
virtual float m(int i) = 0;
private:
int i_;
};
// uma sub­classe

- 98 -
8 -Orientação por Objetos e C++

classe_b : public classe_b {
public:
classe_b();
float m(int i);
};
// definindo classe_a
classe_a::classe_a() :
i_(1) {
}

// definindo classe_b
classe_b::classe_b() :
classe_a() {
}
float classe_b::m(int i) {
return ­0.23 * (i_ / i);
}

8.8 Polimorfismo
Vamos mostrar uma implementação do exemplo do Canil de Polimorfismo.

// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
// classe base da hierarquia
class cao {
public:
// construtor default
cao();

// destrutor virtual, necessário pois a classe haverá sub­classes
~cao();

// método virtual abstrato, ou virtual puro, que garante, na 
// verdade, obriga, que toda sub­classe implemente
virtual void late() = 0;
};

- 99 -
8 -Orientação por Objetos e C++

// implementação dos métodos de cao
cao::cao() {
}

cao::~cao() {
}

// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
class pastor_alemao : public cao {
public:
pastor_alemao();

~pastor_alemao();

void late();
};

// implementação dos métodos da classe pastor_alemao
pastor_alemao::pastor_alemao() {
}

pastor_alemao::~pastor_alemao() {
}

void pastor_alemao::late() {
cout << “eu sou um pastor alemao” << endl;
}

// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
class buldogue : public cao {
public:
buldogue();

~buldogue();

- 100 -
8 -Orientação por Objetos e C++

void late();
};

// implementação dos métodos da classe buldogue
buldogue::buldogue() {
}

buldogue::~buldogue() {
}

void pastor_alemo::late() {
cout << “eu sou um buldogue” << endl;
}

// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
// declaração da classe canil, que conterá os vários objetos da classe
// cao
// vamos limitar o número de cães em 10
#define MAX_CAES 10
class canil {
public:
// construtor
canil();

// destruttor
~canil();

// método que fará todos os cães latirem
void late();

// método que insere um novo cão
// retorna true se foi possível inserir, ou false se o canil 
// estiver cheio
bool novo_cao(cao *c);
private:
// vamos implementar o canil como um array criado dinamicamente

- 101 -
8 -Orientação por Objetos e C++

cao *caes_;

// guarda a quantidade de cães realmente existentes
int qtde_caes;
};

// implementação dos métodos da classe canil
canil::canil() :
caes_(new cao[MAX_CAES]),
qtde_caes(0) {}

canil::~canil() {
if (caes_)
delete caes_[];
}

void late() {
if (qtde_caes == 0) {
cout << “canil vazio!” << endl;
}
else {
for (int i = 0; i < qtde_caes; ++i) {
caes_[i]­>late();
}
}
}

bool novo_cao (cao *c) {
bool rc = false;
if (qtde_caes < MAX_CAES) {
caes_[qtde_caes] = c;
++qtde_caes;
rc = true;
}
return rc;
}

- 102 -
8 -Orientação por Objetos e C++

// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
// um extrato do programa que usaria a classe canil

canil c;
if (!c.novo_cao(new buldogue()) {
cout << “canil cheio!” << endl;
}
else if (!c.novo_cao(new pastor_alemao()) {
cout << “sem vaga para o pastor alemão...” << endl;
}
c.late();

// a saída desse programa seria:
eu sou um buldogue
eu sou um pastor alemao

8.9 A palavra reservada this

Existe uma palavra reservada em C++ para referenciar o Objeto


instanciado de dentro da Classe.

class classe_a {
public:
classe_a (int i);
void m(int i);
private:
int i_;
};
classe_a::classe_a(int i) : 
i_(i) {
}
classe_a::m(int i) {
this­>i_ = i;
}

- 103 -
8 -Orientação por Objetos e C++

Como vemos na definição do método classe_a::m(int   i), this é um


ponteiro para o Objeto corrente.

8.10 Operadores
Com o objetivo de mais fielmente possível construir objetos virtuais que
sejam semelhantes aos objetos concretos que representam, C++ permite
definir operadores em uma Classe.

class complexo {
public:
// um número complexo é composto de uma parte real, 
// e outra imaginária
// este construtor define valores padrões para os argumentos
complexo (float r = 0, float i = 0);

// como um número complexo concreto, como definido na matemática
// é possível comparar dois números complexos
// a palavra reservada 'const' antes do ';' indica que este 
método não irá
// modificar atributos da classe complexo
bool operator == (const complexo &c) const;

// novamente, imitando a matemática, queremos saber se um número 
// complexo é diferente de outro
bool operator != (const complexo &c) const;

// como um número complexo matemático, queremos atribuir um 
// número complexo a outro
complexo &operator = (const complexo &c); 
private:
// a parte real
float r_;
// a parte imaginária
float i_;
};

- 104 -
8 -Orientação por Objetos e C++

// definindo a classe complexo
complexo::complexo(float r, float i) :
r_(r), i_(i) {
}
bool complexo::operator == (const complexo &c) {
return ( (r_ == c.r_) && (i_ == c.i_) );
}
bool complexo::operator != (const complexo &c) {
return ( !(*this == c));
}
const complexo &complexo::operator = (const complexo &c) {
if (this != &c) {
this­>i_ = c.i_;
}
return *this;
}
Reparem que usamos o ponteiro this no operador == e no operador =,
neste usamos primeiramente para checar se o parâmetro c não é o próprio
Objeto, ou seja, se estou atribuindo um número complexo a ele mesmo.

Usando...

complexo c0(­4.5, 0.2);
complexo c1 (0.2, ­4.5);
if (c1 == c0) {
cout << “c1 == c0”;
}
else {
cout << “c1 != c0”;
}
// aqui usamos o construtor comm os valores padrão padrões 
// para os parâmetros
complexo c2; 
c2 = c1;
if (c1 == c2) {
cout << “c1 == c2”;

- 105 -
8 -Orientação por Objetos e C++

}
else {
cout << “c1 != c2”;
}

8.11 Espaço de nomes (namespace)


Com o intuito de mais bem organizar as várias bibliotecas existentes e as
que podem ser criadas, foi implementado em C++ o conceito de espaço de
nomes – namespace. Um namespace agrupa um conjunto de nomes, ou
identificadores, de modo que o risco de colisão de nomes de identificadores
entre bibliotecas inexiste.

namespace n0 {
class classe_a {...}
}

namespace n1 {
class classe_a{...}
}
Para identificarmos completamente um símbolo devemos explicitamente
identificar o namespace onde está incluído.

n0::classe_a a0;
n1::classe_a a1;
a0 e a1 são Objetos de Classes completamente distintas, pois a0 é instância
de classe_a, definida em n0; enquanto que a1 é instância de classe_a 
definida em n1.

8.12 Arquivos de cabeçalho (header files)


Como vimos durante todo o capítulo Programação básica em C++, existe
um momento onde declaramos, e o momento em que definimos uma Classe
ou Método.

- 106 -
8 -Orientação por Objetos e C++

Uma boa prática de programação, na verdade exigência em projetos com


vários arquivos, é colocar a declaração dos símbolos e suas definições em
arquivos diferentes. Classicamente, colocamos as declarações em arquivos
com extensão .h, ou .hpp, chamados header files, e as definições em
arquivos como extensão .cpp, mas também são encontradas bibliotecas que
usam extensões .cxx.

Arquivo cao.h:

#ifndef __cao_h__
#define __cao_h__
// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
// classe base da hierarquia
class cao {
public:
// construtor default
cao();

// destrutor virtual, necessário pois a classe haverá sub­classes
~cao();

// método virtual abstrato, ou virtual puro, que garante, na 
// verdade, obriga, que toda sub­classe implemente
virtual void late() = 0;
};

#endif

Arquivo cao.cpp:

#include “cao.h”
// implementação dos métodos de cao
cao::cao() {
}

cao::~cao() {

- 107 -
8 -Orientação por Objetos e C++

Arquivo pastor_alemao.h:

#ifndef __pastor_alemao_h__
#define __pastor_alemao_h__
// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
class pastor_alemao : public cao {
public:
pastor_alemao();

~pastor_alemao();

void late();
};
#endif

Arquivo pastor_alemao.cpp:

#include “pastor_alemao.h”

// em iosteam.h estão declarados os símbolos cout e endl
// os arquivos de cabeçalho da biblioteca padrão C++ não tem 
// (infelizmente) a extensão .h
#include <iostream>

// implementação dos métodos da classe pastor_alemao
pastor_alemao::pastor_alemao() {
}

pastor_alemao::~pastor_alemao() {
}

void pastor_alemao::late() {
std::cout << “eu sou um pastor alemao” << std::endl;
}

- 108 -
8 -Orientação por Objetos e C++

Arquivo buldogue.h:

#ifndef __buldogue_h__
#define __buldogue_h__
// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
class buldogue : public cao {
public:
buldogue();
~buldogue();
void late();
};
#endif

Arquivo buldogue.cpp:

#include “buldogue.h”

// em iosteam.h estão declarados os símbolos cout e endl
#include <iostream>

// implementação dos métodos da classe buldogue
buldogue::buldogue() {
}

buldogue::~buldogue() {
}

void buldogue::late() {
std::cout << “eu sou um buldogue” << std::endl;
}
Arquivo canil.h:

#ifndef __canil_h__
#define __canil_h__
// ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
// declaração da classe canil, que conterá os vários objetos da classe
// cao

- 109 -
8 -Orientação por Objetos e C++

// vamos limitar o número de cães em 10
#define MAX_CAES 10
class canil {
public:
// construtor
canil();

// destruttor
~canil();

// método que fará todos os cães latirem
void late();

// método que insere um novo cão
// retorna true se foi possível inserir, ou false se o canil 
// estiver cheio
bool novo_cao(cao *c);
private:
// vamos implementar o canil como um array criado dinamicamente
cao *caes_;

// guarda a quantidade de cães realmente existentes
int qtde_caes;
};
#endif

Arquivo canil.cpp:

#include “canil.h”
#include <iostream>

// implementação dos métodos da classe canil
canil::canil() :
caes_(new cao[MAX_CAES]),
qtde_caes(0) {}

- 110 -
8 -Orientação por Objetos e C++

canil::~canil() {
if (caes_)
delete caes_[];
}

void late() {
if (qtde_caes == 0) {
std::cout << “canil vazio!” << std::endl;
}
else {
for (int i = 0; i < qtde_caes; ++i) {
caes_[i]­>late();
}
}
}

bool novo_cao (cao *c) {
bool rc = false;
if (qtde_caes < MAX_CAES) {
caes_[qtde_caes] = c;
++qtde_caes;
rc = true;
}
return rc;
}

O arquivo com o programa, que chamaremos de main.cpp:

#include “canil.h”
#include “cao.h”
#include “pastor_alemao.h”
#include “buldogue.h”
#include <iostream>

int main() {
canil c;

- 111 -
8 -Orientação por Objetos e C++

if (!c.novo_cao(new buldogue()) {
std::cout << “canil cheio!” << std::endl;
}
else if (!c.novo_cao(new pastor_alemao()) {
std::cout << “sem vaga para o pastor alemão!” << std::endl;
}
c.late();
return 0;
}
Importante observar as instruções:

#ifndef __cao_h__
#define __cao_h__
.
.
#endif

Dentro do arquivo cao.h, e em cada arquivo de cabeçalho uma instrução


parecida, mudando apenas o nome identificador, de __cao_h__ para um
baseado no nome do arquivo. O objetivo desta instrução é evitar que o
conjunto de símbolos declarados entre o #ifndef e #define sejam
reprocessados pelo compilador, caso este arquivo de cabeçalho seja
utilizado, incluído, por algum outro arquivo. Se o compilador analisar o
mesmo arquivo de cabeçalho duas vezes, os símbolos nele declarados serão
considerados redeclarações, causando um erro de compilação. Percebam
que em vários arquivos incluímos o header file iostream, e se neste arquivo
não existirem instruções #ifndef ... #endif, o compilador acusaria que os
símbolos cout e endl, por exemplo, seriam duplicate identifier.

- 112 -
9 -A biblioteca padrão C++

9 A biblioteca padrão C++

A biblioteca padrão C++ é extremamente vasta, portanto apresentaremos


algumas classes que mais se destacam.

9.1 Entrada/Saída (IOStream)


C++ implementa uma biblioteca baseada no conceito de fluxo de dados
(stream) para acesso a arquivo. O diagrama abaixo mostra a hierarquia de
Classe.

Ilustração 63: Hierarquia da biblioteca IOStream

No capítulo Referências existem indicações de livros e sítios4 (sites)


Internet com tutorias, manuais e referências sobre a biblioteca IOStream.

Sucintamente, podemos dizer:


4 http://www.cplusplus.com/reference/iostream/

- 113 -
9 -A biblioteca padrão C++

ios_base Class base com tipos de dados para as outras Classe


ios Class base com tipos de dados para as outras Classe
istream Stream para entrada de dados
ostream Stream para saída de dados
iostream Stream para entrada de dados e saída de dados
ifstream Stream para entrada de dados em um arquivo
ofstream Stream para saída de dados em um arquivo
fstream Stream para entrada de dados e saída de dados em um arquivo
istringstream Stream para entrada de dados em uma string
ostringstream Stream para saída de dados em uma string
stringstream Stream para entrada de dados e saída de dados em uma string
streambuf Classe base para buferização de streams
filebuf Classe base para buferização de streams em arquivo
stringbuf Classe base para buferização de streams em string

Existem alguns Objetos globais que merecem ser mencionados:

cout: Em vários exemplos usamos cout, como no método late de buldogue.


A instrução cout << “eu sou um buldogue” exibe na saída padrão o texto eu 
sou um buldogue.

cerr: Semelhante a cout, mas exibe o texto na saída de erro padrão.

cin: Recebe dados da entrada padrão.

9.2 O que são Templates?


Em C++ é possível criar Classes e Métodos que dependam de uma outra
Classe, sem identificá-la nas suas declarações, de modo que criamos um
modelo (template) de Classe ou Método, que gera uma Classe ou Método
diferente, conforme a Classe usada para preencher o modelo.

template <typename T>
classe_a {
public:
a (T & t0);
~a();

- 114 -
9 -A biblioteca padrão C++

const T & get_t() const;

string to_str() const;
private:
T t_;
};

// definições 
template <typename T>
classe_a<T>::a(T &t0) :
t_(t0) {
}

template <typename T>
classe_a<T>::~a(T &t0) {
}
template <typename T>
const T & classe_a<T>::get_t() const {
return t_;
}
template <typename T>
string & classe_a<T>::to_str() const {
return t_.to_str();
}

// declaração da classe_b
classe_b {
public:
classe_b(const int & i) ;
~classe_b();
classe_b(const classe_b &r);
string to_str() const;
private:
int i_;
};

- 115 -
9 -A biblioteca padrão C++

// definições da classe_b
classe_b::classe_b(const int & i) :
i_(i) }
}
classe_b::~classe_b() {
}
classe_b::classe_b(const classe_b &r) :
i_(r.i) {
}
string classe_b::to_str() const {
stringstream s;
s << i_;
return s.str();
}

// declaração da classe_c
classe_c {
public:
classe_c(const float & f) ;
~classe_c();
classe_c(const classe_c &r);
string to_str() const;
private:
float f_;
};

// definições da classe_c
classe_c::classe_c(const float & i) :
f_(f) }
}
classe_c::~classe_c() {
}
classe_c::classe_c(const classe_c &r) :
f_(r.f) {
}
string classe_c::to_str() const {

- 116 -
9 -A biblioteca padrão C++

stringstream s;
s << f_;
return s.str();
}

// uso
int main () {
classe_a<classe_b> a0(classe_b(­9));
cout << a0.to_str() << endl;

classe_a<classe_c> a1(classe_c(0.32));
cout << a1.to_str() << endl;

return 0;
}

Quando a instrução classe_a<class_b> é compilada, é criada uma Classe


baseada em classe_a, mas específica para classe_b, ou seja, o compilador
gera código para uma “classe_a_classe_b”. O mesmo acontece na instrução
classe_a<classe_c>, ou seja, é criada uma “classe_a_classe_c”.

Podemos visualizar a memória assim:

Ilustração 64: Instâncias de


classes modelo

- 117 -
9 -A biblioteca padrão C++

Muito importante observar que em classe_a<T>::to_str()   const, é


executada a instrução t_.to_str();, mas neste momento não sabemos ainda
qual a Classe de t_. Logo, como o código acima pode compilar? Ao
aplicarmos o método to_str no atributo t_ obrigamos que a Classe usada no
lugar do modelo T implemente este método, caso contrário a compilação
acusará que o método to_str não está definido. É necessário, portanto, que
Classes que usem modelos documentem quais os Métodos que a Classe que
será usada no lugar do modelo deve implementar.

9.3 Coleções genéricas (Containers)


Uma das principais consequencias do uso de Templates é a criação de
Classes, chamada genericamente de Coleções (Containers), que armazenam
em memória principal Objetos de outra Classe, sem conhecimento prévio de
qual Classe exatamente é o Objeto. Os Containers tem um modelo de como
um Objeto da Classe armazenada deve se comportar.

C++ provê vários Containers unidimensionais:

● Vectors

● Lists
● Double-Ended Queues

● Stacks

● Queues
● Priority Queues

● Bitsets

● Maps
● Multimaps
● Sets
● Multisets

- 118 -
9 -A biblioteca padrão C++

Usando a mesma sintaxe mostrada no tópico anterior, para criar um vetor


que armazene inteiros, simplesmente fazemos vector<int>   v0;, e se
quisermos armazenar Objetos da classe_c, também declarada no tópico
anterior escrevemos vector<classe_c> v1;.

9.4 Iteradores (Iterator)


Iterators são Classes que nos permitem escrever código para percorrer
um Container, e acessar os Objetos nele armazenados, independente da
Classe de Objeto que o Container armazena. Isto é possível porque quando
declaramos vector<int> v0;, um tipo (typedef) de Iterator declarado dentro
da Classe vector<T> é criado para possibilitar o acesso e percurso para um
vector<int>.

#include <vector>

#include <iostream>

#include <cstdlib>

using namespace std;

int main () {

// cria um vetor de inteiros de 10 posições, e inicializa 

// cada uma com ­4

vector <int> v(10, ­4);

// vamos percorrer o vetor, atualizando o valor do 

// inteiro armazenado em cada posição

vector<int>::iterator i = v.begin();

while (i != v.end()) {

*i = *i * rand();

// agora vamos percorrer novamente o vetor, e imprimir o valor 

- 119 -
9 -A biblioteca padrão C++

// do inteiro em cada posição

// primeiro reposicionamos o iterator no início do vetor

i = v.begin();

// aqui declaramos um inteiro compatível com o tipo de tamanho do 

// vetor

vector<int>::size_type k = 0;

// agora percorremos o vetor

while (i != v.end()) {

cout << “v[“ << k++ << “] = ” << *i << endl;

9.5 String
Vimos em Ponteiros para caracteres uma maneira de trabalharmos com
uma sequencia de caracteres – string – usando ponteiros para caracteres. É
desta forma que a linguagem C manipula strings.

Em C++ existe uma classe específica para usarmos uma string, a classe
string, cuja interface é:

● string &append(const string &str); 
● string &append(const string &str,  size_type indx, size_type 
len);
● string &append(const CharType *str);
● string &append(const CharType *str, size_type num);            
● string &append(size_type len, CharType ch);
● template<class InIter>   string &append(InIter start, InIter 
end);
● string &assign(const string &str);         
● string &assign(const string &str, size_type indx, size_type len); 
● string &assign(const CharType *str);      
● string &assign(const CharType *str, size_type len);             

- 120 -
9 -A biblioteca padrão C++

● string &assign(size_type len, CharType ch);  
● template<class InIter>  string &assign(InIter start, InIter end); 
● reference at(size_type indx);               
● const_reference at(size_type indx) const;   
● iterator begin( );                         
● const_iterator begin( ) const;              
● const CharType *c_str( ) const;            
● size_type capacity( ) const;               
● int compare(const string &str) const; 
● int compare(size_type indx, size_type len,   const string &str) 
const;      
● int compare(size_type indx, size_type len, const string &str, 
size_type indx2,size_type len2) const;         
● int compare(const CharType *str) const;   
● int compare(size_type indx, size_type len, const CharType *str, 
size_type len2 = npos) const; 
● size_type copy(CharType *str, size_type len, size_type indx = 0) 
const;                                         
● const CharType *data( ) const;            
● bool empty( ) const;                      
● iterator end( );                             
● const_iterator end( ) const;                 
● iterator erase(iterator i); 
● iterator erase(iterator start, iterator end); 
● string &erase(size_type indx = 0, size_type len = npos);        
● size_type find(const string &str, size_type indx = 0) const; 
● size_type find(const CharType *str, size_type indx = 0) const;  
● size_type find(const CharType *str,  size_type indx, size_type 
len) const;       
● size_type find(CharType ch, size_type indx = 0) const; 
● size_type find_first_of(const string &str,  size_type indx = 0) 
const;   
● size_type find_first_of(const CharType *str, size_type indx = 0) 
const;    
● size_type find_first_of(const CharType *str, size_type indx, 
size_type len) const;      
● size_type find_first_of(CharType ch, size_type indx = 0) const; 
● size_type find_first_not_of(const string &str, size_type indx = 

- 121 -
9 -A biblioteca padrão C++

0) const;  
● size_type find_first_not_of(const CharType *str, size_type indx = 
0) const;                                           
● size_type find_first_not_of(const CharType *str, size_type indx, 
size_type len) const;         
● size_type find_first_not_of(CharType ch, size_type indx = 0) 
const;   
● size_type find_last_of(const string &str, size_type indx = npos) 
const; 
● size_type find_last_of(const CharType *str, size_type indx = 
npos) const;
● size_type find_last_of(const CharType *str, size_type indx, 
size_type len) const; 
● size_type find_last_of(CharType ch, size_type indx = npos) const; 
● size_type find_last_not_of(const string &str, size_type indx = 
npos) const; 
● size_type find_last_not_of(const CharType *str, size_type indx = 
npos) const;                                               
● size_type find_last_not_of(const CharType *str, size_type indx, 
size_type len) const;       
● size_type find_last_not_of(CharType ch, size_type indx = npos) 
const; 
● allocator_type get_allocator( ) const;         
● iterator insert(iterator i, const CharType &ch );          
● string &insert(size_type indx, const string &str); 
● string &insert(size_type indx1,  const string &str, size_type 
indx2, size_type len); 
● string &insert(size_type indx, const CharType *str);        
● string &insert(size_type indx, const CharType *str, size_type 
len);                                           
● string &insert(size_type indx, size_type len, CharType ch); 
● void insert(iterator i, size_type len, const CharType &ch) 
● template <class InIter> void insert(iterator i, InIter start, 
InIter end);
● size_type length( ) const; 
● size_type max_size( ) const; 
● reference operator[ ](size_type indx) const;
● const_reference operator[ ](size_type indx) const;
● string &operator=(const string &str); 
● string &operator=(const CharType *str); 

- 122 -
9 -A biblioteca padrão C++

● string &operator=(CharType ch); 
● string &operator+=(const string &str); 
● string &operator+=(const CharType *str); 
● string &operator+=(CharType ch); 
● reverse_iterator rbegin( );
● const_reverse_iterator rbegin( ) const;     
● reverse_iterator rend( );                  
● const_reverse_iterator rend( ) const;   
● string &replace(size_type indx, size_type len, const string 
&str);
● string &replace(size_type indx1, size_type len1, const string 
&str, size_type indx2, size_type len2); 
● string &replace(size_type indx, size_type len, const CharType 
*str);       
● string &replace(size_type indx1, size_type len1, const CharType 
*str, size_type len2); 
● string &replace(size_type indx, size_type len1, size_type len2, 
CharType ch); 
● string &replace(iterator start, iterator start,  const string 
&str);                                              
● string &replace(iterator start, iterator start,  const CharType 
*str);                                             
● string &replace(iterator start, iterator end, const CharType 
*str, size_type len);
● string &replace(iterator start, interator end, size_type len, 
CharType ch); 
● template <class InIter> string &replace(iterator start1, 
interator end1, InIter start2, InIter end2);
● void reserve(size_type num = 0); 
● void resize(size_type num)
● void resize(size_type num, CharType ch); 
● size_type rfind(const string &str, size_type indx = npos) const; 
● size_type rfind(const CharType *str, size_type indx = npos) 
const;
● size_type rfind(const CharType *str,  size_type indx, size_type 
len) const;                                            
● size_type rfind(CharType ch, size_type indx = npos) const; 
● size_type size( ) const; 
● string substr(size_type indx = 0,  size_type len = npos) const; 

- 123 -
9 -A biblioteca padrão C++

● void swap(string &str);                                          

9.6 Algoritmos genéricos


Ainda graças a implementação de Classes que usam modelos (templates),
e aos Iterators, C++ oferece vários algoritmos que agem sobre os
Containers, também independente da Classe que for usada no lugar do
modelo:

● accumulate
● adjacent_difference
● adjacent_find
● binary_search
● copy
● copy_backward
● count_if
● equal
● equal_range
● fill
● find
● find_end
● find_first_of
● find_if
● for_each
● generate
● generate_n
● includes
● inner_product
● inplace_merge
● is_heap
● is_sorted
● iter_swap
● lexicographical_compare
● lower_bound
● make_heap
● max

- 124 -
9 -A biblioteca padrão C++

● max_element
● merge
● min
● min_element
● mismatch
● next_permutation
● nth_element
● partial_sort
● partial_sort_copy
● partial_sum
● partition
● pop_heap
● prev_permutation
● push_heap
● random_shuffle
● remove
● remove_copy
● remove_copy_if
● remove_if
● replace
● replace_copy
● replace_copy_if
● replace_if
● reverse
● reverse_copy
● rotate
● rotate_copy
● search
● search_n
● set_difference
● set_intersection
● set_symmetric_difference
● set_union
● sort
● sort_heap
● stable_partition

- 125 -
9 -A biblioteca padrão C++

● stable_sort
● swap
● swap_ranges
● transform
● unique
● unique_copy
● upper_bounds

- 126 -
10 -Implementação do Estudo de Caso

10 Implementação do Estudo de Caso

10.1 Uma sugestão de padrão de arquivos de cabeçalho


#ifndef __INTERSIX__<namespace>__<header>_H__
#define __INTERSIX__<namespace>__<header>_H__

//                 C++ headers

//                 3rds headers

//                 our headers

//                 namespaces
using namespace std;

//                 macro constants

//                 macro commands

namespace tenacitas { namespace <NAMESPACE> {

    //             pre­declarations

    /// brief documentation
    ///
    /// detailed documentation

    class <CLASS_NAME> {

      //           <CLASS_NAME>  FRIENDS

      //                                    classes

- 127 -
10 -Implementação do Estudo de Caso

      //                                    methods

      //                                    operators

    public:

      //           <CLASS_NAME> PUBLIC   internal classes

      //           <CLASS_NAME> PUBLIC   constructors

      //           <CLASS_NAME> PUBLIC   gets

      //           <CLASS_NAME> PUBLIC   sets

      //           <CLASS_NAME> PUBLIC   helpers

      //           <CLASS_NAME> PUBLIC   processors

      //           <CLASS_NAME> PUBLIC   operators

      //           <CLASS_NAME> PUBLIC   attributes

    protected:
      //           <CLASS_NAME> PROTECTED   internal classes

      //           <CLASS_NAME> PROTECTED   constructors

      //           <CLASS_NAME> PROTECTED   gets

      //           <CLASS_NAME> PROTECTED   sets

      //           <CLASS_NAME> PROTECTED   helpers

      //           <CLASS_NAME> PROTECTED   processors

- 128 -
10 -Implementação do Estudo de Caso

      //           <CLASS_NAME> PROTECTED   operators

      //           <CLASS_NAME> PROTECTED   attributes

    private:
      //           <CLASS_NAME> PRIVATE   internal classes

      //           <CLASS_NAME> PRIVATE   constructors

      //           <CLASS_NAME> PRIVATE   gets

      //           <CLASS_NAME> PRIVATE   sets

      //           <CLASS_NAME> PRIVATE   helpers

      //           <CLASS_NAME> PRIVATE   processors

      //           <CLASS_NAME> PRIVATE   operators

      //           <CLASS_NAME> PRIVATE   attributes

    };
  } }

#endif

- 129 -
10 -Implementação do Estudo de Caso

10.2 Começar pela Apresentação, Processamento ou


Armazenamento?

10.3 Quais Containers uso para implementar as coleções do


Desenho?

10.4 Implementação “de baixo para cima”

10.5 Só um pouco está pronto? Então testa um pouco!

10.6 Finalizando implementação

- 130 -
11 -Literatura

11 Literatura

● C++ Programming Language; Bjarne Strostrup; Addison Wesley

● Design Patterns: Elements of Reusable Object-Oriented Software;


Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides; Addison
Wesley

● C++: The Complete Reference; Herb Schildt, Osborne McGraw-Hill

● C++ Templates: The Complete Guide; David Vandevoorde, Nicolai


Josuttis

● Object-Oriented Modeling and Design with UML; Michael Blaha,


James Rumbaugh; Prentice Hall

● UML User Guide; Grady Booch, James Rumbaugh, Ivar Jacobson;


Addison Wesley

● Effective C++ CD: 85 Specific Ways to Improve Your Programs and


Designs; Scott Meyers; Addison-Wesley

● C++ Cookbook; Jeff Cogswell, Christopher Diggins, Ryan Stephens,


Jonathan Turkanis; Publisher: O'Reilly

● How Not to Program in C++; Steve Oualline; No Starch Press

● C++ from the Ground Up; Herbert Schildt; McGraw-Hill/Osborne

● The C++ Standard Library: A Tutorial and Reference; Nicolai M.


Josuttis; Addison Wesley Longman, Inc

- 131 -

Potrebbero piacerti anche