Você está na página 1de 322

Profª Raquel Mini

raquelmini@ufmg.br
DEE – UFMG – 2018
 Unidade I – Gerenciando a Complexidade
 I.1. A complexidade inerente dos sistemas de software
 I.2. A estrutura de sistemas complexos
 I.3. Trazendo ordem ao caos

2
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A complexidade de sistemas de software
 Alguns sistemas de software não são complexos ...
 Exemplos
 Aplicativos que são especificados, construídos, criados,
mantidos e utilizados por uma mesma pessoa
 Sistemas que têm um propósito limitado e um ciclo de vida curto
 Neste caso, podemos simplesmente refazê-lo, se necessário ...
 Jogar o sistema fora e construir outro
ao invés de tentar reutilizá-lo,
consertá-lo ou estender
sua funcionalidade

3
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A complexidade de sistemas de software (cont.)
 Os sistemas de software que nos interessam são os
complexos ...
 Software “industrial”
 Possui um ciclo de vida longo para o produto
 É extremamente difícil para um único indivíduo entender todos os
detalhes de seu projeto
 A complexidade do sistema ultrapassa
a capacidade intelectual humana!
 Podemos gerenciar sua complexidade,
mas não fazer com que ela desapareça!!
 O gerenciamento deve ser feito por
“mortais” e não gênios ...

4
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Por que os sistemas de software são complexos?
 O domínio do problema é complexo
 Quem desenvolve não é quem conhece o domínio de aplicação
 Dificuldade de capturar os requisitos do sistema
 Os requisitos podem não ser estáveis
 É necessário pensar na evolução do sistema
 programar para o futuro!

5
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
6
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 É difícil gerenciar o processo de desenvolvimento
 Tamanho não é virtude em software
 Hoje é comum encontrar sistemas com milhões de linhas de
código, decompostas em centenas ou milhares de módulos
separados ...
 É necessário gerenciar uma equipe de desenvolvimento,
composta por um número razoável de pessoas para
criar o sistema!
 Muitas vezes, dispersa geograficamente
 Problemas de comunicação em uma equipe
Mais comuns do que se imagina!
 O desafio principal é manter a coesão e a integridade do sistema

7
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Tem-se uma flexibilidade muito grande quando se
desenvolve software
 Isto pode ser uma grande desvantagem, pois há a tendência de se
“reinventar a roda”
 Um empreiteiro da construção civil não planta árvores para obter a
madeira que vai usar na construção
 Mas o desenvolvedor de software muitas vezes faz isto !!!
 Nas diversas engenharias temos componentes padronizados que são
os nossos blocos primitivos
 Mas falta padronização na indústria de software !!!
 Um construtor de edifícios pensaria em adicionar um novo subsolo a
um edifício de 100 andares?
 Usuários de software geralmente pedem que este tipo de modificação seja
efetuada em um sistema existente ...
 “Isto é só uma questão de programação” !!!
8
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Quanto mais complexo um sistema, mais
provável que ele entre em colapso
 Sem gerenciar a complexidade, sistemas de software
 São entregues com atraso
 Estouram o orçamento
 Não atendem aos requisitos especificados
 Podem apresentar desempenho insatisfatório
 Podem ter vida útil curta

9
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A solução começa com ...
 Entender como os sistemas complexos são
organizados
 Trazer esta organização para os sistemas de software

10
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplos de sistemas complexos
 Um computador pessoal ...
 placa mãe, monitor, teclado, HD, etc ...
 placa mãe: microprocessador, memória, barramento, etc.
 microprocessador: unidade aritmética, unidade de controle, registradores
...
 registradores: portas lógicas (ANDs, ORs, etc)
 portas lógicas: transistores, resistores, etc ...
 ....
 Estrutura hierárquica, com diferentes níveis de abstração, cada
um deles construído sobre o outro!
 O computador pessoal somente funciona corretamente devido à
colaboração existente entre as suas diversas partes
 Podemos descrever seu funcionamento porque conseguimos decompô-lo
em partes que podem ser estudadas separadamente
 Em cada nível de abstração, os elementos cooperam entre si para
desempenhar sua funcionalidade, através de uma interface conhecida,
e oferecem serviços para os níveis mais altos
11
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A complexidade é
organizada na forma
de uma hierarquia
 Um sistema complexo é
composto por subsistemas
interrelacionados
 Por sua vez, estes possuem
seus próprios subsistemas,
e assim por diante, até
chegarmos aos componentes
elementares

12
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Os atributos de sistemas complexos
 A escolha de quais componentes de um sistema são
elementares (primitivos) é relativamente arbitrária
 Depende basicamente daquilo que o observador do sistema
pretende enxergar
 A força dos relacionamentos também muda
 Relacionamentos internos entre os subcomponentes de um
determinado nível da hierarquia são fortes
 Relacionamentos entre componentes da hierarquia
em níveis distintos são fracos

13
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Entendendo melhor a força entre os módulos
 Modularidade
 É a qualidade de um módulo que garante a sua capacidade de
extensão e reuso
 Boa modularidade  baixo acoplamento e alta coesão
 Acoplamento
 Medida da força de associação (dependência) entre os módulos
 Coesão
 Medida da força de associação dentro de um módulo
 O nível da abstração utilizado
é a chave para entender
o acoplamento e a coesão
existente entre as partes
14
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A forma canônica de sistemas complexos
 A descoberta de abstrações e mecanismos comuns facilita o
entendimento de sistemas complexos
 Ex: um motorista consegue “pilotar” um novo modelo de automóvel,
simplesmente identificando seus componentes
 Volante, freio, acelerador, embreagem, marchas, ignição, ...
 Estes são mecanismos comuns presentes em todos os carros
 Um sistema complexo geralmente contém várias hierarquias
 Hierarquia estrutural: “todo-parte”
 Ex: um carro tem sistemas de propulsão, controle de direção, frenagem, ...
 Hierarquia de generalização/ especialização: “é um tipo de”
 Um animal vertebrado é um tipo de animal
 Um mamífero é um tipo de animal vertebrado
 Um primata é um tipo de mamífero
 Um macaco é um tipo de primata
15  ...
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A forma canônica de sistemas complexos
 Adiantando alguns conceitos do curso ...
 A hierarquia “é um tipo de” será modelada, na análise
orientada a objetos, por meio do mecanismo de herança
 A hierarquia “todo parte”, por meio do mecanismo de
composição

16
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Se sabemos o raciocínio a seguir no projeto de um sistema
complexo (ex: decomposição em hierarquias), por que o
desenvolvimento de software é problemático???
 O modelo de desenvolvimento OO é relativamente novo
 Descobrir o que há em comum em um sistema, abstrair seus
mecanismos, etc ... não é uma tarefa simples
 É menos simples ainda para sistemas inexistentes para os quais
estaremos projetando a hierarquia
 Inventando os mecanismos, agrupando as partes
 Podemos lidar com um número bastante grande de possíveis escolhas
 Experiência + aprender com as boas soluções  obter padrões
 Padrões de arquitetura
 Padrões de projeto

17
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Decomposição
 É uma técnica básica de tratamento da complexidade
 Essência: decompor algo em partes menores
 “Dividir para conquistar"
 Em sistemas de software podemos ter:
 Decomposição funcional
 Análise, projeto e programação estruturados
 Enfoca o que precisa ser feito  funções e fluxo de execução
 Um problema maior é decomposto sucessivamente em problemas menores,
até que sejam simples o suficiente para serem resolvidos
 Decomposição orientada a objetos
 Análise, projeto e programação orientados a objetos
 Enfoca a construção de módulos que colaboram entre si para
18 solucionar um problema
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Decomposição Funcional

Decomposição
Orientada a Objetos

19
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Decomposição (cont.)
 Análise e projeto estruturados
 Realizam a decomposição modular
 Um módulo é dividido em módulos menores até atingir um nível de
abstração no qual o problema é facilmente resolvido
 Segue a abordagem “Top Down”
 Princípio: a divisão do sistema em subsistemas permitirá distribuir o
trabalho entre diferentes pessoas ou grupos
 Meta difícil, pois os subsistemas podem apresentar
uma série de dependências

Top Down

20
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Decomposição (cont.)
 Programação estruturada
 Decomposição funcional (algorítmica)
 Utiliza um nível de abstração alto, baseado em funções
 Módulo = agrupamento de funções correlatas
 Possui funções que atuam sobre conjuntos relacionados de dados
Ex: read(), write(), open(), close(), ...
 Existe uma separação clara entre os dados e as operações sobre eles
 Dados organizados em estruturas de dados globais, disponíveis a
qualquer parte do programa
 Funções específicas de manipulação são
empregadas para evitar o uso direto dos
dados, reduzindo o acoplamento
 Dados são passados entre módulos por meio
de parâmetros de funções e sub-rotinas
21
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Análise, projeto e programação estruturados

22
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini Fonte: Gane e Sarson, Análise Estruturada de Sistemas, 1983
 Exemplo: análise, projeto e programação estruturados

23
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini Fonte: Gane e Sarson: Análise Estruturada de Sistemas, 1983
 Decomposição (cont.)
 Programação estruturada (cont.)
 Dificuldades com a programação estruturada
 A evolução do sistema é mais difícil
 Interdependência entre as funcionalidades e na forma
como elas são implementadas
 Efeitos colaterais ocorrem entre as estruturas de dados,
podendo comprometer todo o software
 Modificações sucessivas geram uma maior degradação do sistema
 Pode significar menos tempo do sistema em produção
 Necessário buscar mecanismos que permitam construir e
manter software de forma mais simples e robusta

 A solução tem sido empregar o


24 paradigma de orientação a objetos !!!
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Decomposição (cont.)
 Análise e projeto orientados a objetos Bottom Up
 O sistema é visto como composto por uma
série de subsistemas que interagem entre si
 Deve ser hierarquizado, decomposto em camadas
 Ocorre a composição modular, favorecendo a construção de
módulos que colaboram entre si para solucionar o problema
 A composição modular extrai os módulos e suas interações do
contexto do problema, seguindo uma abordagem “Bottom up”
 Abstrações chave devem ser identificadas: classes e objetos
 Sistema = série de objetos autônomos que colaboram entre
si para desempenhar um comportamento de nível mais alto
 Objeto = entidade tangível que exibe um comportamento
bem definido
 Classe = “molde” para a criação de objetos
25
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Decomposição (cont.)
 Programação orientada a objetos
 Procura representar os conceitos do mundo real de uma
maneira mais intuitiva e realista
 OO: aplicação composta por objetos que se relacionam e interagem
 Um conjunto de entidades (objetos) estão presentes no domínio da
aplicação, possuem algum tipo de ligação entre si e são descritos por
dados e operações (funções) que atuam sobre estes dados
 Estruturada: aplicação possui um conjunto de atividades e afazeres
 Teoricamente, as alterações de requisitos são mais facilmente
implementadas
 Possível perceber o que mudou nos objetos
e suas relações

26
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo: análise, projeto e programação OO

27
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Grau de abstração + próximo de  A falta de domínio pode inverter
como o ser humano pensa todos os benefícios
 Ajuda a organizar a complexidade  Ineficiência e problema de
inerente dos sistemas de software desempenho
 Melhora a interação entre o analista  Softwares mal elaborados
e o especialista do domínio  Não traduz conceitos em algo real
 Explicita pontos comuns (herança)
 OO requer treinamento e
 Maior qualidade, favorecendo: experiência
 Reúso de mecanismos comuns
 Classes, abstrações, ...
 Envolve custos de aprendizado
 Desenvolvimento incremental  Cuidado com o uso incorreto da
 Capacidade de extensão tecnologia e seus preceitos
 Robustez e correção  Programar OO com mentalidade
 Redução dos dados globais estruturada
 Manutenção e extensão  A definição incorreta de padrões
 Mais fácil identificar a origem de pode levar a falhas estruturais e
problemas dificultar a programação

Principais Benefícios Principais Dificuldades


1. Quais as técnicas existentes de se gerenciar a
complexidade de sistemas?

2. Quais as diferenças entre a análise e projeto


orientados a objetos e a análise estruturada?

29
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Na Unidade I vimos que ...
 Um sistema de software é inerentemente complexo e
muitas vezes ultrapassa a capacidade intelectual humana
 Sistemas complexos podem ser analisados a partir de sua
decomposição em coisas (objetos), que têm estado e
comportamentos próprios
 A complexidade da decomposição pode ser organizada na
forma de uma hierarquia com duas relações básicas:
“é um tipo de” e “é parte de”
 Os sistemas complexos geralmente evoluem
a partir de formas estáveis mais simples

31
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Na Unidade II veremos ...
 Como usar a metodologia OO para organizar a
complexidade de sistemas de software
 Quais os principais mecanismos que poderão ser
utilizados para isto

32
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Unidade II - A Modelagem OO
 II.1. Paradigmas de programação
 II.2. A evolução dos modelos orientados a objetos
 II.3. A abstração de objetos
 Encapsulamento, interface e implementação
 II.4. Reutilizando a implementação
 II.5. Reutilizando a interface por meio de herança
 II.6. Objetos intercambiáveis: polimorfismo
 II.7. Introdução à análise e projeto orientados a
objetos

33
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Linguagem de Programação
 Método padronizado para expressar instruções para um computador
 Possui um conjunto de regras sintáticas e semânticas usadas para definir um
programa de computador
 Especifica precisamente sobre quais dados um computador vai atuar, como esses
dados serão armazenados ou transmitidos e quais ações devem ser tomadas sob
várias circunstâncias
 Objetivos
 Aumentar a produtividade dos programadores
 Usa linguagem simbólica ao invés de linguagem de máquina
 Aumentar o entendimento por programadores humanos
 Possui sintaxe de nível mais alto  mais facilmente entendida
 Gerar programas mais organizados e modulares
 Possui estruturas mais definidas (loops, condições,...)
 Aumentar a portabilidade entre computadores
 Traduzida para o código de máquina do computador alvo
34
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Paradigmas das linguagens de programação:
 Imperativo
 Orientado à objetos
 Concorrente
 Funcional
 Lógico
 Scripting

35
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programação imperativa
 Descreve a computação como ações, enunciados ou
comandos que mudam o estado (variáveis) de um
programa
 Baseada em comandos que atualizam variáveis
armazenadas
 Em Latim, imperare significa “comando”
 Na década de 1950, as primeiras linguagens de
programação eram imperativas

36
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programação imperativa
 Nestas linguagens, é necessário pensar em termos da
estrutura do computador, e não em termos da
estrutura do problema a ser resolvido
 Linguagens imperativas constituem abstrações da linguagem
Assembly
 O modelo do espaço da solução é o de uma “linha de
produção”
 O programador deve estabelecer uma associação entre o
modelo da máquina (no espaço da solução) e o modelo do
problema que está sendo resolvido (no espaço do problema)
 O esforço para fazer este mapeamento pode ser gigantesco
37
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programação imperativa
 Variáveis e comandos de atribuição são uma
abstração simples para o acesso e atualização do
conjunto de instruções do computador
 Essas linguagens possuem uma relação próxima com a
arquitetura do computador
 Implementação eficiente (no início)
 Paradigma dominante até a década de 1990
 A grande maioria dos softwares comerciais atualmente em uso
foram escritos em linguagem imperativa, assim como vários
softwares que estão correntemente sendo desenvolvidos

38
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programação imperativa
 Principais conceitos:
 Variáveis
 Comandos
 Procedimentos
 Abstração de dados (mais recentemente)

39
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programação imperativa
 Variáveis e comandos são conceitos chave
 Muitos programas são escritos para modelar processos do
mundo real afetando entidades do mundo real
 Uma entidade do mundo real frequentemente possui um estado
que varia com o tempo
 Entidades do mundo real podem ser naturalmente modeladas
por variáveis
 Processos do mundo real podem ser modelados por comandos
que inspecionam e atualizam essas variáveis

40
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programação imperativa
 Exemplo: programa para controle de voos
 Uma aeronave possui um estado contendo sua posição,
altitude, velocidade, carga, quantidade de combustível, ...
 O programa para controle de voos deve modelar os estados da
aeronave
 Como o estado muda com o tempo, essas informações são
armazenadas em um grupo de variáveis

41
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programação imperativa
 Procedimentos
 São abstrações de comandos

 Abstração de dados
 Abstração é a habilidade de concentrar nos aspectos
essenciais de um contexto qualquer, ignorando características
menos importantes
 Não é essencial em linguagens imperativas e não é suportada
em algumas linguagens clássicas (C e Pascal)

42
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Arquitetura de um programa corresponde à forma na
qual ele é decomposto em unidades menores
juntamente com o relacionamento entre essas unidades
 A qualidade da arquitetura de um programa afeta
diretamente o custo de implementação e de
manutenção
 Acoplamento
 Define o quanto uma unidade depende da outra para funcionar
 Métrica para medir a qualidade de uma arquitetura

43
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Alto acoplamento
 Modificações em uma unidade possuem alta
probabilidade de forçar modificações em outras
unidades
 Baixo acoplamento
 Modificações em uma unidade possuem alta
probabilidade de gerar poucas modificações em
outras unidades
 Facilidade de manutenção

44
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Arquitetura de um
programa imperativo
com variáveis globais

45
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programa imperativo com variáveis globais
 Manutenção cara e difícil
 Necessário conhecer o papel de cada variável global
que é acessada e também o papel dos outros
procedimentos que acessam as mesmas variáveis
globais
 Qualquer modificação em um procedimento pode
disparar modificações em cascata em outros
procedimentos
 Abstração de dados se tornou um conceito chave
nas linguagens imperativas modernas!
46
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 As unidades de programas imperativos bem
projetados são procedimentos e pacotes (ou
tipos abstratos)

47
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Arquitetura de um
programa imperativo
com abstração de
dados

48
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programa imperativo com abstração de dados
 Não existe variáveis globais, apenas variáveis locais e
parâmetros
 Todas as unidades do programa são fracamente
acopladas
 O programador que dará a manutenção será capaz de
entender cada unidade individualmente, podendo
modificá-la sem gerar modificações em cascata em
outras unidade do programa
 Como a representação de cada tipo abstrato é
privada, a manutenção pode modificá-la com
segurança sem forçar modificações em outras
unidades
49
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programação orientada à objetos
 Na programação orientada a objetos, implementa-se
um conjunto de classes que definem os objetos
presentes no sistema de software
 Cada classe determina o comportamento (definido nos
métodos) e estados possíveis (atributos) de seus
objetos, assim como o relacionamento com outros
objetos
 Possui 3 pilares:
 Encapsulamento
 Herança
 Polimorfismo
50
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A análise e o projeto orientados a objetos
 É uma metodologia que nos leva a uma decomposição
orientada a objetos de um sistema
 Os modelos resultantes identificam “coisas”, “entidades”
 São os chamados objetos que compõem o sistema
 É totalmente distinta da análise estruturada, que busca
identificar procedimentos (funções)

Paradigma: conjunto de
teorias, padrões e métodos
que, juntos, representam um
modo de organizar o
conhecimento
51
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A evolução do processo de abstração
• Abstrair = retirar do domínio do problema os detalhes
relevantes e representá-los não mais na linguagem do
domínio, mas na linguagem da solução

Para a engenharia, abstrair é criar modelos a


serem usados para solucionar o problema

52
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A evolução do processo de abstração (cont.)
 A programação orientada a objetos tenta trazer o
espaço da solução para o espaço do problema
 Ambos são representados como objetos !!
 Permite uma adaptação ao problema
 Adiciona novos tipos ao espaço da solução que
mapeiam os tipos existentes no espaço do problema
 Assim, descreve-se o problema em termos do
problema e não em termos da solução!
 Cada objeto funciona como uma
pequena parte do problema
 Possui seu estado e suas operações, que podemos pedir que eles
executem - isso é semelhante ao comportamento dos objetos no
53
mundo real, que também possuem características e comportamentos!
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que são objetos?
 Um objeto é uma variável ... ele armazena dados
 Então uma struct (registro do C) é um objeto?
 Sim, uma struct é um objeto ... mas um objeto pode ser mais ...
 Podemos pedir que algumas operações ocorram sobre os objetos

 Um objeto possui, portanto:


 Atributos (dados) e comportamentos (métodos,
procedimentos, funções) que atuam sobre ele
 Exemplos de objetos
 Cachorros, carros, DVD players, edifícios, funcionários,
indústrias ...
54
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que é um programa em linguagem OO?
 Conjunto de objetos colaborando entre si para obter
um determinado resultado

55
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que é um programa em linguagem OO? (cont.)
 Conjunto de objetos dizendo uns para os outros o que
fazer por meio de envio de mensagens
 Mensagens = chamadas a funções que pertencem a um objeto
em particular
 Cada objeto tem a sua própria região de memória, que
pode ser composta por outros objetos, também
 Ex: o objeto carro pode ser composto pelos objetos lataria,
rodas, motor, etc.
 Cada objeto tem um tipo,
isto é, pertence a uma classe

56
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que são objetos e classes?
 Classe das aves e classe dos peixes
 Os objetos, apesar de serem únicos, fazem parte de uma
classe de objetos que possuem características comuns
 A criação de tipos de dados abstratos (classes) é um conceito
fundamental na programação OO
 Tipos de dados abstratos (TADs) funcionam praticamente
da mesma maneira que os tipos fundamentais
 Pode-se criar (instanciar) variáveis do tipo  objetos ou instâncias
 Pode-se manipular estas variáveis, enviando
mensagens ou requisições para elas

57
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que são objetos e classes? (cont.)
 Revendo alguns conceitos
 Tipo: conjunto de elementos com comportamento e operações similares
 Tipos primitivos (fundamentais)
 Tipos indivisíveis providos pela linguagem de programação
 Proporcionam acesso otimizado aos recursos do computador
 int, char, short, long, float, double, boolean, ...
 Tipos compostos
 Tipos divisíveis, que possuem campos (primitivos ou compostos)
 Tipos referência: possibilitam estender a linguagem e implementar a OO
 Tipos abstratos de dados (TADs): define o tipo e uma lista de operações
que podem ser realizadas e seu comportamento externo
 Descreve um tipo de forma independente de implementação
 São modelados por meio de interfaces
58  Ex: pilha (cria, empilha, desempilha, esvazia, topo, número de elementos)
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que são objetos e classes? (cont.)
 Os objetos de cada classe compartilham algumas
características em comum
 Toda conta possui um extrato
 Todo caixa pode aceitar um depósito, etc.
 Ao mesmo tempo, todo objeto tem seu próprio estado
 Cada conta tem um extrato diferente, cada caixa tem um nome
 Assim, ...
 Caixas, clientes, contas, transações, podem, cada um, ser
representado por uma única entidade no programa
 esta entidade é o objeto
 Cada objeto pertence a uma classe que define
suas características e comportamento
59
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Classe
 Conjunto de elementos com mesmas características
 É um molde para a criação de objetos e define:
 Uma estrutura de dados encapsulada
 Um conjunto de operações para manipular esta estrutura
 Uma interface de acesso bem definida
 Implementa tipos abstratos de dados
 Formaliza os princípios da OO
 Herança, encapsulamento,
polimorfismo
 Possibilitam alta coesão
e baixo acoplamento
 Toda linguagem OO
possui classes
60
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Objeto
 É uma instância (materialização) de uma classe
 Possui seu próprio conjunto independente de atributos (dados)
 Objetos de uma mesma classe podem compartilhar as mesmas
operações e em alguns casos, os mesmos atributos
 Ao conjunto de atributos e operações é dado o nome de
membros

61
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Um objeto possui um estado, exibe um certo
comportamento e tem identidade única
 Estado
 Conjunto de atributos (valores armazenados em um objeto)
 Transição de estado = alteração de valor (causada por operações)
 Comportamento
 Ações que o objeto pode realizar =
conjunto de métodos associados
 Todo objeto é passivo
 Espera por requisições (mensagens)
de outros objetos ou classes
 Identidade
 Todo objeto possui um identificador
implícito e único
62
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Objetos e classes
 Após uma classe ser criada ...
 Podemos criar quantos objetos da classe quanto for necessário
 Podemos manipular estes objetos como se fossem elementos
que existissem no problema que está sendo solucionado
 Objetos e interfaces
 Como fazemos um objeto realizar trabalho útil?
 Cada objeto possui métodos para desempenhar suas atividades
 Método = função
 Cada objeto só pode responder a determinadas requisições
 Aquelas que ele reconhece como sendo suas
 O conjunto de métodos de um objeto é conhecido como sendo a
63 sua interface
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Interfaces: exemplos
 Lâmpada - representação UML

Nome da classe: Lampada

liga();
desliga()
Interface: aumenta_luminosidade();
 Lampada luz;
diminui_luminosidade();
 luz.liga();
 luz.desliga();
 luz.aumenta_luminosidade();
 luz.diminui_luminosidade();

64
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Objetos e interfaces (cont.)
 Quais métodos (funções) podemos chamar?
 Isto é a interface
 Onde estes métodos estão codificados, como são
implementados?
 Isto é a implementação
 Geralmente não interessa a quem usa o objeto conhecer os
detalhes da implementação ...

65
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Interface, Implementação e Encapsulamento
 O cliente usa os serviços de um objeto por meio de
sua interface
 O criador da classe expõe o mínimo da interface para
o cliente, escondendo o resto. Por quê?
 Porque se está escondido, pode ser alterado de forma
controlada, sem quebrar o programa do cliente
 Se fosse dado total acesso à implementação interna, o cliente
poderia quebrar a integridade do objeto
Objeto

X, Y, Z

66
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Encapsular
 Separar o programa em partes, o mais isoladas
possível (baixo acoplamento)

 Encapsulamento
 Permite que você divida o programa em várias partes
menores e independentes

67
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Encapsulamento
 Controle de acesso
 A estratégia utilizada para garantir que determinadas partes de
uma classe não sejam acessíveis por seus clientes
• Uma interface não apresenta necessariamente todos
os métodos de um objeto
Objeto. mensagem (p)
• Métodos públicos: aqueles que podem
ser acessados pelo público em geral mensagem (p)
{
...
• Métodos privados: aqueles que // qualquer valor manipulado
aqui é x, y, z ou p
são internos aos objetos }

• Métodos protegidos: aqueles que Encapsulamento


Objeto

podem ser acessados pela classe X, Y, Z


filha (derivada), em caso de herança
68
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Uma vez que uma classe foi criada e testada,
ela pode ser utilizada por outras classes para
auxiliar sua implementação
Esta é uma das principais vantagens da OO!
• A forma mais simples de reutilização é usar um
objeto daquela classe diretamente
• Exs: janela no Windows,
matriz em um programa de
cálculo, etc ...

69
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Estratégia: Composição
• Utiliza um objeto de uma classe dentro de uma nova
classe
• Ex: classes roda, motor, lataria, ... e a classe carro

Notação UML:

70
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Estratégia: Composição (cont.)
• Implementa uma das técnicas básicas de
gerenciamento de complexidade
• Permite criar uma classe “todo” utilizando objetos de outras
classes (as partes)!

71
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Estratégia: Composição (cont.)
• Como se identifica a composição?
• Por meio dos seguintes verbos típicos: conter, possuir
• Ex: Um carro contém 4 rodas, 1 motor, 1 lataria, ..

Notação UML:

72
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Estratégia: Herança
• Permite modelar outro tipo de hierarquia entre classes
• Classes mais especializadas (filhas) herdam propriedades
da classe mais geral (classe pai)
• Pode-se compartilhar automaticamente métodos e
dados entre diferentes classes, subclasses e objetos
• Permite criar uma nova classe programando somente as
diferenças desta para a classe pai
• A classe filha herda a interface da classe pai, podendo
substituí-la quando se espera um objeto da classe pai
• Identifica-se a possibilidade de herança por meio
da seguinte expressão típica: “é um tipo de”
73
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Estratégia: Herança
 O nome já indica
exatamente o
que ocorre ...

74
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Herança: Exemplos
 Ave é um tipo de Animal
 Ave come, dorme, voa e botaOvo
 Mamífero é um tipo de Animal
 Mamífero come, dorme e mama
 Uma Ave (ou um Mamífero)
podem substituir Animal
 São tipos de Animal
(possuem a mesma interface)

75
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Herança: Exemplos
• Triangle, Square e Circle são tipos de Shape
• Possuem a mesma interface;
• Podem substituir Shape ...

76
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Herança: Exemplos
• Cada tipo de Shape deve ter o seu
próprio método draw(), pois cada
uma se desenha de modo diferente!
• Os métodos podem ser sobrescritos
nas classes derivadas!
• Estamos dizendo:
“estou usando a mesma
interface, mas quero um
comportamento um
pouco diferente para
a classe derivada”

77
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Quando trabalhamos com hierarquias de herança,
muitas vezes queremos tratar um objeto, não por
seu tipo específico, mas por seu tipo base
 Isto permite escrever código que não depende
do tipo específico
 Ex: no caso das Shapes, podemos escrever código genérico que
as manipulasse (mandando-as se desenharem, por ex.), sem se
preocupar se são triângulos, círculos ou qualquer outra coisa ...
 Este código não seria afetado pela adição de
novos tipos de Shapes ...
 Adicionar novos tipos é uma das formas mais comuns de estender
um programa orientado a objetos para tratar novas situações

78
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Isto é o polimorfismo
• “o que possui várias formas”
• Propriedade de se usar o mesmo nome
para métodos diferentes, implementados
em diferentes níveis de uma hierarquia de classes
• Para cada classe, tem-se um comportamento
específico para o método
 Não se preocupem demais com o conceito,
no momento: ficará claro depois!

79
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Análise: detalha “o que deve ser feito”
• Detalha requisitos do sistema

• Projeto: detalha “como será feito”


• Cria modelos de como o sistema será construído

• Programação: “faz”
• Constrói o sistema Anális
e OO

Projet
o OO

Programaç
ão OO
80
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Análise orientada a objetos
• Método de análise que examina os requisitos a partir
da perspectiva das classes e objetos encontrados no
vocabulário do domínio do problema
• OBS: o produto da análise são modelos que servem
como entrada para o projeto, que por sua vez produz
os modelos que são utilizados para a programação

81
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Projeto orientado a objetos
• Método de projeto de software que abrange as tarefas
de decompor o sistema de maneira orientada a objetos
• Gera classes e objetos agrupados
• Uma notação própria deve ser utilizada para expressar
as idéias associadas
• Por exemplo a UML
 O fundamental no projeto é responder as questões de
decomposição lógica do sistema
 Quais são as classes e objetos?
 Quais são as interfaces destes objetos?
 Como os objetos interagem entre si?
82
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Programação orientada a objetos
• Método de implementação de software no qual:
• Os programas são organizados na forma de coleções
cooperativas de objetos
• Cada um dos objetos representa uma instância de uma classe
• As classes podem ser membros de hierarquias criadas por meio
de mecanismos como herança e composição
• Linguagem orientada a objetos
• Linguagem que suporta os mecanismos utilizados na
programação orientada a objetos
• Composição, herança, polimorfismo, encapsulamento, ...

83
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
3. Preencher a tabela abaixo buscando
responder cada item de forma objetiva e
concisa
Tópico Conceito Necessidade Aplicação
(O qué é?) (Para que serve? (Como usa? Qual o
Qual a formato de uso?)
vantagem?)
Decomposição de
sistemas complexos

Abstração

Encapsulamento

Herança

Composição
84
© Renato Mesquita,
Polimorfismo
Ana Liddy Magalhães e
Raquel Mini
4. O que é a interface de uma classe? Como o
conceito de interface de uma classe se
relaciona com o conceito de encapsulamento?

5. Qual a diferença entre um membro public e


um private em uma classe?

85
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Na unidade II vimos ...
 Como usar a metodologia OO para organizar a
complexidade de sistemas de software
 Quais os principais mecanismos que poderiam ser
utilizados para isto
 Uma introdução à análise e ao projeto orientado a
objetos

87
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Na unidade III veremos ...
 Como algumas destas idéias se transformam em
código, usando a linguagem C++.
 Especificamente, em detalhes, como criar classes e
objetos em C++

 Referência básica
 Thinking in C++, Vol. 1
 Parte 1: Capítulos 2 ao 5
 Parte 2: Capítulos 6 ao 10
 Parte 3: Capítulos 11 ao 13

88
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Unidade III - Classes e Objetos
III.1. Implementando classes e objetos em C++
Parte

III.2. Atributos e métodos: controle de acesso


1

e encapsulamento
III.3. Inicialização e destruição
III.4. Sobrecarga de funções e argumentos default
III.5. Constantes e controle de visibilidade
III.6. Ponteiros, referências, atributos dinâmicos,
gerenciamento de memória e o construtor de cópia
III.7. Sobrecarga de operadores e conversão de tipos
89
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Principais conceitos relacionados
 Declaração
 Forma de dizer ao compilador o nome de funções e variáveis
(externos) e suas características: não aloca espaço
 “Esta função ou esta variável existe em algum lugar e é desta forma
que ela deve ser”
 Não é possível usar nomes repetidos em um mesmo contexto
 Definição
 Explicita o conteúdo da função ou variável: aloca espaço
 “Coloque esta função / variável aqui”
 Variável: verifica tamanho (tipo) e solicita reserva de espaço
 Função: gera código correspondente, ocupando espaço de memória
 Declaração x definição
 São conceitos complementares
 Principal diferença é o momento de alocação de espaço
 Uma definição pode ser também uma declaração, caso não tenha
90
ocorrido a declaração antes
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Declaração x definição
 Exemplos //: C02:Declare.cpp
// Declaration & definition examples
extern int i; // Declaration without definition
extern float f(float); // Function declaration
float b; // Declaration & definition
float f(float a) { // Definition
return a + 1.0;
}
int i; // Definition
int h(int x) { // Declaration & definition
return x + 1;
}
int main() {
b = 1.0;
i = 2;
f(b);
h(i);
}

91
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uso de header files em C e C++
 Arquivos de cabeçalho
 Contém declarações externas de uma biblioteca fornecida
 Usar a diretiva #include <nomearq> ou “nomearq.h”
 Indica ao processador para abrir o arquivo e incluir ali seu conteúdo
 <nomearq>: indica para procurar no caminho especificado no ambiente
ou linha de comando do compilador
 “nomearq.h”: indica para procurar no caminho relativo à pasta atual
 Se não achar, assume o caminho default (<>)
Extensões mais usadas: “.h”, “.hxx” e “.hpp”
 Padrão atual de nome de arquivos
 Pode ter mais de 8 caracteres e sem extensão
Ex: <iostream.h>  <iostream>
 As bibliotecas herdadas do C continuam com a extensão “.h” mas
podem ser usadas também com um “c” antes
Ex: <stdio.h>  <cstdio>
92
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Principais conceitos relacionados
 Namespace
 Necessidade
 Programas grandes são divididos em partes, cada uma mantida por
grupos / pessoas diferentes
 Isso poderia levar à utilização de mesmo nome de variáveis
 Aplicação
 “Embrulha” espaços, possibilitando separar espaços distintos
 Se houver nome idêntico, mas em outro namespace, não haverá colisão
de nomes
 Formato
 using namespace nome; (o padrão do C++ é o std)
 Esta diretiva deixa o namespace disponível para todo o arquivo
 Evite colocar no header, pois o expõe de forma inadequada
Ex: # include <iostream>  #include <iostream.h>
using namespace std; (forma + antiga)
93
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Programa inicial
Programa 1: Hello World em C++
// C02:Hello.cpp - Saying Hello with C++
#include <iostream> // Stream declarations
Notas:
 cout << (console using namespace std;
output): saída padrão int main() {
<< concatena saídas
int age = 8;
 cin >> (console input):
entrada padrão cout << "Hello, World! I am "
>> concatena << age << " years old "
entradas
 endl: “\n” na saída
<< endl;
padrão }

94
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
6. Analise os programas a seguir, presentes em
nosso livro texto, e que apresentam alguns
aspectos específicos da entrada e saída em
C++
 Stream2.cpp - formatação da saída
 Concat.cpp - concatenação de arranjos de caracteres
 Numconv.cpp - leitura de entrada e formatação da saída
 CallHello.cpp - uso da função system() para executar um
programa de dentro de outro

95
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Stream2.cpp - formatação da saída

// C02:Stream2.cpp
Notas: // More streams features
#include <iostream>
 Manipuladores
using namespace std;
iostream: não int main() {
imprimem nada, mas // Specifying formats with manipulators:
cout << "a number in decimal: "
mudam o estado do
<< dec << 15 << endl;
stream de output (dec, cout << "in octal: " << oct << 15 << endl;
oct, hex) cout << "in hex: " << hex << 15 << endl;
 Impressão de ponto cout << "a floating-point number: "
<< 3.14159 << endl;
flutuante definida pelo cout << "non-printing char (escape): "
compilador << char(27) << endl;
 char(27): cast de }
char() com valor ASCII
(27) = “escape”
96
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Concat.cpp - concatenação de arranjos de caracteres

Notas: // C02:Concat.cpp
// Character array Concatenation
 C++ é uma linguagem
#include <iostream>
de formato livre e using namespace std;
permite que a int main() {
cout << "This is far too long to put on a "
sentença continue na
"single line but it can be broken up with "
linha seguinte "no ill effects\nas long as there is no "
 O “;” termina uma "punctuation separating adjacent character "
sentença "arrays.\n";
}

97
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Numconv.cpp - leitura de entrada e formatação da saída

// C02:Numconv.cpp
Notas: // Converts decimal to octal and hex
 cin e cout pertencem #include <iostream>
às classes istream e using namespace std;
int main() {
ostream do C++ e
int number;
podem ser cout << "Enter a decimal number: ";
redirecionados para cin >> number;
nova entrada e saída cout << "value in octal = 0"
padrão << oct << number << endl;
 Manipuladores cout << "value in hex = 0x"
iostream: não << hex << number << endl;
imprimem nada, mas }
mudam o estado do
stream de output (oct,
98
hex)
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 CallHello.cpp - uso da função system() para executar um programa
de dentro de outro
Notas: // C02:CallHello.cpp
 <cstdlib> define system(); // Call another program
 O argumento de system deve ser o #include <cstdlib> // Declare "system()"
using namespace std;
comando que seria colocado no int main() {
prompt do sistema operacional, system("Hello");
contendo seus próprios argumentos }
(pode ser montado em tempo
de execução)
 O comando é executado e o controle
retorna para a instrução seguinte em
main()
 Mostra também como é simples
utilizar as funções da biblioteca do C
no C++
99
(basta incluir o header e chamar a
© Renato Mesquita,
função)
Ana Liddy Magalhães e
Raquel Mini
7. Escreva um programa em C++ que leia um
conjunto de 4 valores i, a, b, c, onde i é um
valor inteiro e positivo e a, b, c, são quaisquer
valores reais e os escreva:
 Se i=1 escrever os três valores a, b, c em ordem crescente.
 Se i=2 escrever os três valores a, b, c em ordem decrescente.
 Se i=3 escrever os três valores a, b, c de forma que o maior
entre a, b, c fique dentre os dois.

100
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
III.1.1. Utilizando objetos de classes existentes
na biblioteca padrão
 Exemplo: funcionalidade básica das strings em
C++

101
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo: Lendo e escrevendo em arquivos

Nota:
102  getline () retorna true quando lê
© Renato Mesquita,
Ana Liddy Magalhães e com sucesso e false ao final do
Raquel Mini arquivo
 Lendo de um arquivo e armazenando todas as
linhas numa string

103
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Criando vetores com a biblioteca vector<T> da biblioteca padrão
• Classe template, que permite a criação de vetores de tipos diferentes.
Especificamos o tipo no momento da declaração do vetor
• Podemos adicionar novos elementos ao vetor com push_back()

104
© Renato Mesquita,
Ana Liddy Magalhães e
Nota:
Raquel Mini  push_back() acrescenta item ao final do
vetor
 Pode-se criar um vetor de qualquer tipo
 Exemplo: uso de um vector<int>

105
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Revisão do conteúdo relacionado ao capítulo 3
 Funções recursivas
 Tipos de dados pré-definidos na linguagem
 Ponteiros

106
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções recursivas
 Recursividade
 Possibilidade de chamar novamente a função na qual
se está
 É uma técnica de programação interessante e em
alguns casos útil
 É necessário ter uma situação de parada para
“desempilhar” as chamadas realizadas e não “estourar”
a memória

107
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Precedência de operadores
 Notas:
 Autoincremento e autodecremento
 ++i: o incremento é realizado e o valor resultante é aplicado
 i++: o valor atual é aplicado e depois é realizado o incremento
 Processo semelhante para o decremento
 Atribuição compacta (ex: A+=1)
 Forma mais concisa e eficiente

108
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Tipos de dados pré-definidos em C e C++
 char : para armazenamento de caracteres
 Utiliza no mínimo 8 bits (um byte) de armazenamento
 int : armazena números inteiros
 Utiliza no mínimo 2 bytes de armazenamento
 float e double : armazenam números de ponto flutuante
 Formato padrão IEEE (sinal + expoente + mantissa)
 float para precisão simples (4 bytes), double para precisão dupla (8 bytes)
 Nota 1: a especificação do padrão C define apenas valores mínimo e
máximo que cada tipo primitivo deve manipular
 Fica a cargo de cada compilador adequar valores, respeitando o padrão
 Nota 2: o padrão C++ inclui ainda o tipo bool (valores Booleanos)
 True: converte para valor inteiro 1
 Valores inteiros não zero são convertidos implicitamente para true
 False: converte para valor inteiro 0
109
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Especificadores adicionais de tipo
 Nota: os tamanhos definidos são os menores do padrão

255

110
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
8. Escreva um programa que abra um arquivo e
conte o número de espaços em branco do
arquivo.
9. O que o seguinte programa faz?

111
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
10. Dado um vetor de números inteiros positivos
aleatórios entrados via teclado (número negativo
indica fim da entrada dos dados), faça um programa
utilizando a classe template vector para comprimir o
vetor suprimindo as repetições de números vizinhos
através da contagem do número de repetições de
cada um da seguinte forma:
Vetor de entrada: 1 1 1 4 1 1 4 4 25 67 67 67 67 2 2
Vetor de saída: 3 1 1 4 2 1 2 4 1 25 4 67 2 2

11. Faça um programa que descomprima o vetor de saída


do exercício anterior, gerando o vetor de entrada
112 correspondente.
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Ponteiro
 Variável que armazena o endereço de outra variável
 Permite criar e manipular estruturas de dados dinâmicas que podem sofrer
alterações (tamanho e forma) ao longo do tempo
 Variável comum: é um nome para um endereço de memória
 Ponteiro: armazena um endereço de memória
 Um ponteiro leva de uma posição de memória a outra da mesma forma que um link leva
de um endereço (site) para outro
 Manipulando ponteiros
 int *aptr;
 Declara ponteiro para inteiro mas não inicializa (poderia atribuir NULL)
 Um ponteiro deve ser definido para um tipo específico
 Declaração múltipla: int *a; int *b; int *c; (senão apenas o primeiro é ponteiro)
 int a = 3; int *aptr; aptr = &a;
 Atribui ao ponteiro para inteiro o endereço da variável a (também inteira, = 3)
 Um ponteiro só recebe valor de endereço de variável de mesmo tipo
 *aptr = 10
 Atribui ao endereço apontado pelo ponteiro o valor 10
 “não modifique o meu valor, mas o da variável apontada por mim”
113
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo de manipulação de ponteiros

Saída
resultante:
a = 30 b = 21

114
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Ponteiros: declaração, atribuição, operador endereço e
operador de indireção ou de-referenciação (“conteúdo”)

Saída resultante:

115
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Ponteiros e vetores: aritmética de ponteiros

Saída
resultante:
10
2
33
42
51

20
30
40

116
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Passagem de parâmetros por valor
 O valor da variável passada como argumento não
altera após retorno da função que foi chamada
 A passagem de parâmetros padrão em C é feita por cópia

Saída
resultante:
x = 47
a = 47
a = 5
x = 47

117
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Passagem de parâmetros em C++ por referência
usando ponteiros

Possível saída
resultante:
x = 47
&x = 0x28feec
r = 47
&r = 0x28feec
r = 5
x = 5

118
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A passagem de parâmetros padrão em C é feita
por cópia

119
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Passagem de parâmetros por referência, usando referências
 Em C++ podemos declarar referências ...
int i;
int &j = i; // j é uma referência e precisa ser inicializada
// j referencia i  ao alterar j, i também altera
j = 10; // faz i também igual a 10

 ... e passar parâmetros por referência


void frefref(int &j) { // função que referencia uma
referência
j = 20; // altera o conteúdo da referência
}
int main() {
int i = 10;
frefref(i); // envia 10 e, ao final, i igual a 20
}
120
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Ponteiros: alocação dinâmica de memória

121
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Ponteiros: aritmética de ponteiros

saída resultante:
*ip = 0
*++ip = 1
*(ip + 5) = 6
*ip2 = 6
*(ip2 – 4) = 2
*--ip2 = 5
ip2 – ip = 4

122
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
12. Seja p uma variável do tipo ponteiro, explique a diferença entre
p++; (*p)++; *(p++); *p++; *(p+10);

13. Seja a[] um vetor qualquer, independente de tipo e tamanho, e


pa um ponteiro para o mesmo tipo de a[]. Responda V ou F,
justificando:
a) Após a atribuição pa=&a[0]; pa e a possuem valores idênticos,
isto é, apontam para o mesmo endereço.
b) A atribuição pa=&a[0]; pode ser escrita como pa=a;
c) a[i] pode ser escrito como *(a+i)
d) &a[i] e a+i são idênticos
e) a+i é o endereço do i-ésimo elemento após a
f) pa[i] é idêntico a *(pa+i)
g) pa=a é uma operação válida
h) pa++ é uma operação válida
i) a=pa é uma operação válida
j) a++ é uma operação válida

123
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
14. Após a execução do código abaixo, informe,
para cada letra, se a sequência de código está
correta e o que será impresso.

a) b) c)

d) e) f)

124
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Outros pontos importantes
 Regras de escopo
 Criação de tipos: typedef, structs, ponteiros e
structs, enums
 Arrays e Matrizes
 Diretivas de compilação
 Dicas para debug de programas
 Uso de asserts
 Ponteiros para função
 Uso de Makefiles para estruturar projetos

125
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
// How variables are scoped
 Regras de escopo int main() {
int scp1;
 Definem onde uma // scp1 visible here
variável é válida, onde {
// scp1 still visible here
foi criada e como ela sai //.....
do escopo (é destruída) int scp2;
// scp2 visible here
 O escopo de uma //.....
variável vai do par “{}” {
// scp1 & scp2 still visible here
mais próximo que a //..
contém int scp3;
// scp1, scp2 & scp3 visible here
 Vai do ponto no qual ela // ...
foi definida até o “}” } // <-- scp3 destroyed here
referente ao bloco ao // scp3 not available here
// scp1 & scp2 still visible here
qual pertence // ...
} // <-- scp2 destroyed here
// scp3 & scp2 not available here
126 // scp1 still visible here
© Renato Mesquita, //..
Ana Liddy Magalhães e } // <-- scp1 destroyed here
Raquel Mini
 Outros pontos importantes
 Criação de tipos: typedef e structs
 Typedef: possibilita uma descrição mais acurada de um tipo
 Seu uso é importante junto a structs
 Forma: typedef existing-type-description alias-name
 Ex1: typedef unsigned long ulong;
 Ex2: int *x, y; //x é ponteiro e y é int (* só vale para o 1°)
typedef int *IntPtr; //define o tipo ponteiro para inteiro
IntPtr x, y; //agora x e y são ponteiros
 Struct: forma de organizar um grupo de variáveis
 Possui um conjunto de dados, denominados membros
 Torna a operação de um conjunto de dados muito simples
 Uma vez criada, possibilita gerar várias instâncias
 Sua declaração não aloca espaço (apenas ao instanciar variável do tipo)
 Formas: struct rotulo {tipo d1; … tipo dn;}; // só declara, pode instanciar várias
127 struct {tipo d1; … tipo dn;} nome; // instancia 1 (nome) e não tem rótulo
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo: struct

128
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Outros pontos
importantes
 Criação de tipos:
ponteiros e structs
 Às vezes é necessário
manipular um objeto do tipo
estrutura a partir de seu
endereço, utilizando um
ponteiro
 Para obter seus elementos
específicos a partir de um
ponteiro, deve-se utilizar o
operador ‘->’
 Um ponteiro pode ser
dinamicamente
redirecionado para apontar
para outro objeto de mesmo
129 tipo
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Outros pontos importantes
 Criação de tipos: enum
 Tipo enumerado: é uma forma de atribuir nomes a números, propiciando
maior legibilidade ao código
 Forma: enum rotulo {nome1=v1, nome2=v2, ... Nomen=vn};
 Se não houver atribuição de valor, nome1=0, nome2=1, ...
 Podem ser atribuídos valores de referência, basta fazer a atribuição
 Ex: enum ShapeType { circle = 10, square = 20, rectangle = 50};
 Se para algum valor não houve atribuição, pega o inteiro seguinte ao valor
anterior
 Ex: enum exemplo { primeiro = 25, segundo };
neste caso, segundo recebe valor 26
 Nota: em C pode-se fazer operações com enum (ex: exemplo++)
Em C++ já não pode (envolve 2 conversões de tipo enum  int  enum)

130
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Outros pontos importantes
 Vetores e Matrizes
 Vetor é uma estrutura unidimensional contendo elementos de um mesmo tipo
 Forma: tipo nome[tam];  possui elementos de [0] a [tam-1]
 Ex: int a[10] = {10,20,30,40,50,60,70,80,90} (a[9] recebe 0)
 Notas:
 Ao passar um vetor como parâmetro para uma função utilizando apenas o seu
nome, na realidade está sendo passado seu endereço inicial de memória
 Um vetor possui tamanho fixo, especificado na declaração
 Em C++, é possível criar um vetor dinâmico utilizando a classe Vector
 É possível ter um vetor de qualquer tipo, incluindo estruturas

 Matriz é um vetor de vetores, ou seja,


uma estrutura multidimensional contendo
elementos de mesmo tipo
 Sua manipulação é semelhante ao vetor,
131 estendendo-o a várias dimensões
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Diretivas de compilação
 #include: inclui, na hora da compilação, um arquivo especificado
 #include "nomeArquivo" : caminho relativo ao diretório de trabalho
 #include <nomeArquivo> : caminho pré-especificado do compilador
 #define nomeMacro sequenciaCaracteres
 Diz ao compilador para substituir nomeMacro por sequenciaCaracteres
 Deixa o programa mais geral (basta alterar no #define)
 Exemplos de uso
 #define PI 3.1416 ou #define VERSAO "2.02"
 #define nomeMacro: macro pode ser usada como uma espécie de flag
 Simulando função no código, mas substituindo em tempo de compilação
 #define SQR(X) (X)*(X) ou #define ABS(a) ((a<0) ? (-a) : (a))
 #define max(A,B) ((A>B) ? (A):(B)) ou
#define min(A,B) ((A<B) ? (A):(B))
 #undef : retira a definição (apagada da tabela interna que guarda as macros)
132
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Diretivas de compilação (cont.)
 Formas de realizar includes e definições condicionais
 #ifdef nomeMacro
sequenciaDeclaracoes
#endif
 sequenciaDeclaracoes será compilada apenas se nomeMacro estiver definido
 #ifndef nomeMacro
sequenciaDeclaracoes
#endif
 sequenciaDeclaracoes será compilada apenas se nomeMacro não estiver definido
 #if expConstante sequenciaDeclaracoes1
#else sequenciaDeclaracoes2
#endif #define SISTEMA DOS
...
 sequenciaDeclaracoes1 ou 2 será compilada #if SISTEMA == DOS
em função de expConstante #define CABECALHO "dos_io.h"
 Pode ainda utilizar #elif (else if) #else
133 #define CABECALHO "unix_io.h"
© Renato Mesquita, #endif
Ana Liddy Magalhães e #include CABECALHO
Raquel Mini
 Outros pontos importantes
 Dicas para debug de programas
 Uso da macro #define PR(x) cout << #x " = " << x << "\n";
 Ao imprimir uma variável a usando a macro PR(a), resultará em
cout << "a = " << a << "\n";
 Flags para depuração com o uso do pré-preprocessador
 Forma mais simples para habilitar / desabilitar recursos de depuração
#define DEBUG // colocar de preferência em um header file
//...
#ifdef DEBUG // verifica se a flag foi definida
/* colocar código para debugging aqui */
#endif // DEBUG
 Flags para depuração em tempo de execução
 Forma mais complexa, porém útil em programas grandes
 Possibilita habilitar e desabilitar por meio da linha de comando
134
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Nota em relação a main():
 int argc indica o número
de elementos a serem
passados como
parâmetro
 char* argv[] é o array de
parâmetros passados
para main() – cada item
da linha de comando é
separado por um
espaço e será colocado
em uma posição de
argv[]
 argv[0] contém o
caminho e o próprio
nome do programa
 Neste programa
exemplo deve-se
colocar "--debug=on” na
135
linha Mesquita,
© Renato de comando
Ana Liddy Magalhães e
Raquel Mini
 Outros pontos importantes
 Dicas para debug de programas: uso da macro assert
 assert( ) é usado com um argumento que deve ser uma expressão que
você quer ter certeza que é verdadeira
 O pré processador gera código para testá-la e, caso ela seja falsa, o
programa será interrompido após apresentar uma mensagem acusando o
erro

 Ao terminar de depurar, o código gerado pela macro pode ser removido com
#define NDEBUG
 Deve ser colocado no programa antes da inclusão de <cassert>
 Opcionalmente pode ser definindo NDEBUG na linha de comando do
compilador
 NDEBUG é um flag usado em <cassert> para alterar a forma como o código é
gerado pela macro
136
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Outros pontos importantes
 Ponteiros para função
 Da mesma forma que se faz com vetor, na qual o uso de seu nome
funciona como um ponteiro para ele (ex: a para a[tam]), o endereço de
uma função pode ser obtido a partir de seu nome
 Pode também ser usada a sintaxe explícita &func()
 Para chamar a função, basta usar uma das formas apresentadas abaixo

Nota
 Após o ponteiro para a função
ser definido, ele é atribuído ao
endereço da função func()
usando fp = func (sem
argumentos)
 O segundo caso apresenta
definição e inicialização
simultâneas

137
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
15. Defina uma função que receba um double e
retorne um int. Crie e inicialize um ponteiro
para essa função e chame essa função
através do ponteiro.

138
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O Capítulo 4 do nosso livro mostra como as bibliotecas
na linguagem C são estruturadas (de forma procedural)
 Existem várias funções independentes que são agrupadas em
um arquivo, por sua “afinidade”
 Muitas vezes se cria uma estrutura de dados (uma struct) que
agrupa os dados sobre os quais estas funções atuam
 Esta é uma abordagem clássica, usada por várias
bibliotecas de sucesso e que usam a linguagem C

139
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
// Uma janela : Implementação clássica em C ...
typedef struct tag_Window {
int x, y; // Posição na tela
int cx, cy ; // Largura e altura
Canvas* my_canvas; // Estrutura que contém atributos
// a serem utilizados para desenho
} CWindow;

int initialize(CWindow* w, int xp, int yp, int cx, cy);


int show(CWindow* w); // mostra na tela
int cleanup(CWindow* s); // libera
int move(CWindow* s ,int newx, int newy); //Move
int resize(CWindow* s , int newcx, int newcy);
int setTextColor(CWindow* s , COLORREF cor); // Define cor
int textOut(CWindow* s , int x, int y, char* text);

140
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
// outros includes não mostrados
#include "CWindow .h”
int main() {
CWindow janela1, janela2; // Duas janelas
initialize(&janela1, 1, 1, 100, 200);
initialize(&janela2, 110, 220, 120, 100);
...
show(&janela1);
show(&janela2);
...
COLORREF c1(255, 0 , 0 ); // RGB ==> vermelho
setTextColor(&janela1, c1);
textOut(&janela1, 3, 5, “Alo Mundo com Janelas”);
...
cleanup(&janela1);
cleanup(&janela2);
}
141
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Quais os problemas com esta abordagem?
 Toda vez que trabalhamos com a estrutura, temos que
passar o endereço dela para a função
 Apesar de as funções terem sido criadas para
manipular a estrutura, elas são separadas dela
 Este é o velho problema da separação entre dados e
funções, que já havíamos discutido!
 Outro problema que pode ocorrer é o de “briga de
nomes” entre bibliotecas: initialize e cleanup, por
exemplo, são muito comuns ...

142
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Outra abordagem semelhante é a que utiliza
“handles” para estruturas de dados
 Um handle é um tipo de identificador, um jeito de se
diferenciar um objeto de outro
 Quando você cria uma janela a função retorna um handle, sempre
que você precisar manipular essa janela, passe esse hwnd
 Exemplo - programação para MS-Windows:

HWND hjanela;
hjanela = CreateWindow(.... um monte de dados ...);
ShowWindow(hjanela, ....);
UpdateWindow(hjanela, ....);
...
SetWindowText(hjanela, ...);
MoveWindow(hjanela, ...);
etc ...

143
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A abordagem é bem parecida, mas esconde um
pouco mais a estrutura de dados
 Várias APIs usam “handles” e manipulam os dados
escondidos atrás destes handles por meio de funções
 Um "handle" é um identificador que o sistema operacional usa
para indexar uma tabela de objetos que ele possui
 Ex: um handle de arquivo é um índice para a tabela de arquivos
abertos do processo

144
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A programação orientada a objetos nos permite uma
solução bem mais elegante: criar classes!
class CWindow {
int x, y; // Posição na tela
int cx, cy ; // Largura e altura
Canvas* my_canvas; // Estrutura que contém atributos
// a serem utilizados para desenho
public:
int initialize(int xp, int yp, int cx, cy);
// +- (ver construtor, III.3)
int show(); // mostra na tela
int cleanup(); // +- (ver destrutor, item III.3)
int move(int newx, int newy); //Move
int resize(int newcx, int newcy); // Redimensiona
int setTextColor(COLORREF cor); // Define cor do texto
int textOut(int x, int y, char* text); // Texto em x,y
};
145
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Veja que o livro cria esta classe usando a palavra
chave struct
 Isto porque em C++ uma struct pode conter funções e
funcionar como uma classe
 A diferença entre structs e classes em C++ está apenas na
visibilidade “default” de seus membros
 Ambas “possuem” funções
 Implementam o conceito de “comportamento”
 Implementação das funções membro
int CWindow::move(int newx, int newy) {
x = newx;
y = newy;
show();
return 0;
}
146
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uma vez que a classe seja implementada, podemos
instanciar objetos e chamar seus métodos:
CWindow janela1;
janela1.initialize(1,1,100,200);
janela1.move(6,7);
 O tamanho do objeto janela1 em memória é equivalente
ao da struct correspondente!

147
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
// Declaração da classe com includes necessários
#include "CWindow.h”
int main() {
CWindow janela1, janela2; // Duas janelas
janela1.initialize(1, 1, 100, 200);
janela2.initialize( 110, 220, 120, 100);
...
janela1.show();
janela2.show();
...
COLORREF c1(255, 0 , 0 ); // RGB ==> vermelho
janela1.setTextColor( c1);
janela1.textOut(3, 5, “Alo Mundo com Janelas Orientadas a
Objetos”);
...
janela1.cleanup();
janela2.cleanup();
}
148
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 OBS: como organizamos o código em C++?
 Definição das classes, no “.h” e das funções no
“.cpp”

Código defensivo para prevenir a redefinição em inclusões múltiplas


#ifndef SIMPLE_H
#define SIMPLE_H
struct Simple {
int i,j,k;
initialize() { i = j = k = 0; }
};
#endif // SIMPLE_H

149
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Como tínhamos visto na unidade II ...
 O usuário de uma classe segue “um contrato” de uso
 A classe pode tornar disponível uma série de serviços,
através de sua interface pública
 A interface pública é o conjunto de membros da classe
declarados como públicos
 Em C++, se não for especificado o controle de
acesso, os atributos e métodos são ...
 Em uma classe: privados por default
 Em uma struct: públicos por default

150
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 C++ possui 3 especificadores de acesso:
 public: pode ser acessadas fora do objeto, onde este
estiver definido
 private: só podem ser acessados por membros da
mesma classe
 protected: podem ser acessados por membros da
mesma classe e de classes derivadas

 Esses especificadores são usados apenas na


declaração da classe e eles alteram o acesso
de todas as declarações que se seguem
151
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
class B {
private:
char j; float f;

public: int main() {


int i; B b;
void func(); b.i = 1; // OK, public
}; b.func(); // OK, public
b.j = '1'; // Illegal, private
void B::func() { b.f = 1.0; // Illegal, private
i = 0; // OK! }
j = '0'; // OK!
f = 0.0; // OK!
};

152
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Classes aninhadas: ainda a janela ...
#ifndef CWINDOW_H
#define CWINDOW_H
class CWindow {
class Canvas {
COLORREF textColor;
COLORREF lineColor;
// etc ...
public:
void setTextColor (COLORREF newColor);
};
int x, y; // Posição na tela
int cx, cy ; // Largura e altura
Canvas* my_canvas;
public:
...
int setTextColor(COLORREF cor); // Define cor do texto
int textOut(int x, int y, char* text); // Escreve texto em x,y
};
#endif // CWINDOW_H
153
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Classes e structs aninhadas: Implementação
#include ”CWindow.h”

int CWindow::setTextColor (COLORREF cor) {


my_canvas->setTextColor(cor);
}

void CWindow::Canvas::setTextColor (COLORREF newColor) {


textColor = newColor;
}

154
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Classes ou funções Friend
 São classes ou funções que podem acessar os
membros privados de uma outra classe!
 Usado quando precisamos assegurar acesso a um
grupo de funções de determinada classe
 A função friend tem acesso a dados privados ou protegidos de
uma classe, permitindo implementar operações mais flexíveis
do que as oferecidas pelas funções membro

155
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Friends: Exemplo
class X {
private:
int i;
public:
void initialize();
friend void g(X*, int); // Função friend global!
};
void X::initialize() { i = 0; }
void g(X *x, int i) {
x->i = i; // Atribui valor para atributo privado da classe
}

156
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
class X; // Declaração da classe X
class Y {
public:
void f(X*);
};
class X { // Definição de X
private:
int i;
public:
void initialize();
friend void Y::f(X*); // Função friend pertencente à classe Y
friend class Z; // A classe Z inteira é friend!!!
};
void Y::f(X *x) {
x->i = 47;
}

157
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Problemas com o uso de friends
• Este tipo de declaração diminui as características de
encapsulamento da POO
• Uma classe que usa friends em demasia muito
provavelmente é passível de uma análise mais
eficiente
• Na maior parte das vezes, a utilização de friends pode
ser evitada por meio de uma melhor especificação da
interface da classe que está se “expondo” com o uso
de friends

158
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
16. Crie uma classe que contém um int e um
ponteiro para outra instância da mesma classe.
Escreva uma função dentro desta classe que
receba um int indicando o comprimento da lista
que deve ser criada. Essa função deverá criar
uma lista encadeada começando pela célula
cabeça e inserir um contador de posição no
inteiro de cada célula (célula cabeça não tem
contador). O ponteiro da última célula deverá
apontar para NULL. Escreva também outra
função para imprimir o conteúdo da lista
encadeada.

159
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Na Unidade III já vimos ...
 III.1. Implementando classes e objetos em C++
 III.2. Atributos e métodos: controle de acesso e
encapsulamento

161
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Agora veremos ...
 III.3. Inicialização e destruição
 III.4. Sobrecarga de funções e argumentos default
 III.5. Constantes, funções inline e controle de
visibilidade

 Referência básica
 Thinking in C++, Vol. 1
 Parte 2: Capítulos 6 ao 10

162
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Unidade III - Classes e Objetos
III.1. Implementando classes e objetos em C++
III.2. Atributos e métodos: controle de acesso
e encapsulamento
III.3. Inicialização e destruição
Parte2

III.4. Sobrecarga de funções e argumentos default


III.5. Constantes e controle de visibilidade
III.6. Ponteiros, referências, atributos dinâmicos,
gerenciamento de memória e o construtor de cópia
III.7. Sobrecarga de operadores e conversão de tipos
163
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Grande parte dos erros em um programa C
ocorre quando o programador se esquece de
inicializar ou de “limpar” uma variável
 Isto é particularmente verdadeiro com bibliotecas,
quando um “programador cliente” não sabe como
inicializar as estruturas de dados, ou mesmo não sabe
que ele deveria fazer isto
 A “limpeza final” é um problema sério, porque muitas
vezes os programadores simplesmente usam suas
estruturas de dados e se esquecem que ao final de
seu uso devem fazer uma “faxina”
164
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Em C++, os processos de inicialização e de “faxina final”
podem ser automatizados nos objetos que criamos
 Funções especialmente concebidas para isto: os construtores e
os destrutores!
 Iremos revisitar a classe CWindow:
 Nela o processo de inicialização era feito pela função inicialize();
 O usuário da classe poderia se esquecer de chamar inicialize()
 desastre, pois a janela não estaria inicializada corretamente quando
a usássemos
 Modificaremos isto para que seja feito automaticamente
por um construtor

165
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que são os construtores?
 São funções que têm o propósito explícito de
inicializar objetos
 Têm o mesmo nome da classe, não retornam nada, têm
chamada automática na declaração dos objetos
class X {
int i;
public:
X(); // Construtor
};

void f() {
X a; // A função X() é executada automaticamente
}
166
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que são os destrutores?
 Função complementar às funções construtoras de
uma classe
 Sempre que o escopo de um objeto encerra-se,
esta função é chamada
 Cada classe pode ter somente um destrutor, que jamais recebe
parâmetros
 O destrutor também não tem nenhum tipo de retorno

class Y {
public:
~Y();
};
167
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Qual é a saída deste programa?

168
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Reimplementando CWindow com construtores e destrutores
#ifndef CWINDOW_H
#define CWINDOW_H
class CWindow {
int x, y; // Posição na tela
int cx, cy ; // Largura e altura
Canvas* my_canvas;
public:
CWindow (int xp, int yp, int cx, int cy); // Construtor
~CWindow() ; // Destrutor
int show(); // Mostra na tela
int move(int newx, int newy); // Move
int resize(int newcx, int newcy); // Redimensiona
int setTextColor(COLORREF cor); //
int textOut(int x, int y, char* text); // Texto em x,y
};
#endif // CWINDOW_H ///
169
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
#include ”CWindow.h”

CWindow::CWindow (int xp, int yp, int cxp, int cyp) {


// Construtora
x = xp;
y = yp;
cx = cxp;
cy = cyp;
my_canvas = new Canvas; // retorna um ponteiro do tipo Canvas
}

CWindow::~CWindow() {
delete my_canvas; // desaloca memória – é complementar a new
}

170
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Em C, as variáveis só podem ser declaradas no início
de blocos
 Em C++, elas podem ser declaradas em qualquer parte
 Isto é feito para que se possa conseguir informação suficiente
para inicializar os objetos por meio de seus construtores
 Um objeto não pode ser criado se ele não for também inicializado
 Você pode esperar ter as informações necessárias para então definir
e inicializar um objeto ao mesmo tempo
 Um construtor tem como obrigações
 Inicializar todas as variáveis do objeto
 Garantir, dentro do possível, a validade dos dados

171
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Boa prática: definir variáveis o mais perto possível do seu
local de uso e sempre inicializá-las quando são definidas
(é uma questão de segurança)
 Ao reduzir a duração da disponibilidade da variável dentro do
escopo, reduz-se a chance dela ser mal utilizada em alguma outra
parte do escopo
 Além disso, melhora a legibilidade pois o leitor não tem que desviar
sua atenção para outro local para obter o tipo da variável

172
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Inicialização agregada à definição

int a[5] = { 1, 2, 3, 4, 5 }; // inicializa todas as 5 posições


int b[6] = {9}; // inicializa o primeiro com 9 e atribui 0 aos demais
int c[10] = {0}; // forma abreviada de inicializar sem usar o for
// nota: a inicialização não ocorre se não incluir a lista de valores
int c[] = { 1, 2, 3, 4 }; // define o tamanho do vetor após
// conhecer a lista de valores
// nota: para saber o tamanho, usar (sizeof c / sizeof *c)
struct X { int i; float f; char c; };
X x1 = { 1, 2.2, 'c' }; // inicialização da estrutura
//nota: seus dados são públicos => ok)
X x2[3] = { {1, 1.1, 'a'}, {2, 2.2, 'b'} }; // inicialização de
// vetor da estrutura
173
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Construtor default
 É um construtor que pode ser chamado sem argumentos
 O compilador cria automaticamente um construtor default se,
e somente se, a classe não tem nenhum construtor
 A criação de qualquer construtor pelo programador faz com que este
construtor default não seja mais criado automaticamente!
class Y {
int i;
};
class X {
int i;
X(int k);
};
Y a, b, c[4]; // OK, existe construtor default na classe Y
X d(3); // OK
X e, f[3] ; // Erro! não existe construtor default para X
174
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A criação de qualquer construtor pelo programador
faz com que este construtor default não seja mais
criado automaticamente!
 Exemplo: construtor definido (inicialização agregada à
definição)
struct Y {
float f;
int i;
Y(int a);
};
Y y1[] = { Y(1), Y(2), Y(3) }; // OK, chama o construtor
// definido

175
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O construtor default é tão importante que, se não há
nenhum construtor para uma estrutura (struct ou
classe), é criado automaticamente um
// Automatically-generated default constructor
class V {
int i;
}; // No constructor

int main() {
V v, v2[10];
}

Nota: o construtor gerado pelo compilador


não realiza inicialização de valores. Se
você quiser inicializar, crie explicitamente
176 um construtor default. De uma maneira
© Renato Mesquita, geral, você deve criar seus construtores
Ana Liddy Magalhães e
Raquel Mini evitando deixar o compilador assumir isso
para você.
 Importante: problemas pela falta do construtor default
 Situações nas quais não existem detalhes suficientes para realizar
a inicialização (e não se tem construtor default)
 Ex: construtor definido e uso de inicialização agregada à definição

struct Y {
float f;
int i;
Y(int a);
};
Y y2[2] = { Y(1) }; // erro: não se sabe como inicializar
// o 2º elemento
Y y3[7]; // erro: não se sabe como inicializar os elementos do vetor
Y y4; // erro: não se tem construtor default

177
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que será impresso pelo programa?

178
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
17. Modifique os programas abaixo para que os
mesmos utilizem construtores e destrutores.

179
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Em português, uma palavra pode ter diferentes
significados
 Tudo depende do contexto em que ela foi empregada

 A palavra está sendo sobrecarregada ...


 Isto é particularmente útil em situações corriqueiras
 Lavar uma camisa e lavar um carro ...
 Imagine se fossemos forçados a usar um verbo diferente para cada
uma destas ações
 Em C somos forçados a usar “verbos” diferentes, para funções
muito semelhantes

180
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Em C teríamos que criar funções com nomes diferentes,
quando a única coisa que as diferencia conceitualmente
é o tipo de dados recebido
int escalar_int(int [], int [], int);
float escalar_float(float [], float [], int);

 Em C++ poderíamos usar um único nome para a


função: estaríamos efetuando uma sobrecarga
int escalar(int [], int [], int);
float escalar(float [], float [], int);

181
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Em C++, outro fator nos força a ter sobrecarga
de funções
 A possibilidade de criarmos vários construtores
diferentes
 Poder criar objetos de diferentes maneiras!

 Também é possível omitir alguns parâmetros da lista de


argumentos, quando fizermos a chamada a certas
funções
 Neste caso os argumentos receberão valores “default”

182
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 A idéia da sobrecarga de funções é muito simples
 Usa-se um mesmo nome para a função, mas uma lista de
argumentos diferentes
 O compilador utiliza o escopo, o nome da função e a lista de
argumentos para reconhecer cada função sobrecarregada
 Apesar das funções terem o mesmo nome, o compilador
conseguirá distinguir que função chamar por meio dos
parâmetros utilizados
void print(char);
void print(float);
...
print(´a´);
print(3.1415);
183
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uma pergunta que muitas vezes se faz é:
pode-se fazer a sobrecarga pelo tipo de retorno?
void f();
int f();

 Não! Como o compilador iria saber qual função


chamar quando se escrevesse
f(); ??

184
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Os dois construtores
executam basicamente o
mesmo código
 Um deles apenas inicializa x e y
com zero quando não recebe
argumentos

185
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Não poderíamos usar um único construtor?
 Sim, desde que usássemos argumentos default!
 Valor fornecido na declaração que o compilador insere
automaticamente se não for fornecido um valor na chamada
 Ex: f(int a, int b=0) pode ser chamado por:
f(150, 2); f(200, 0); f(100);
 O segundo argumento é automaticamente substituído pelo compilador
caso não seja fornecido e o primeiro seja int

186
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Argumentos default
 Se os dois construtores
executassem ações completamente
diferentes, não faria sentido usar
argumentos default,
mas sim continuar a ter sobrecarga
das funções

187
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Argumentos default
 As seguintes regras devem ser seguidas:
 Somente os últimos argumentos da lista podem ter valores default
 Não se pode ter argumento default seguido por um não default
 Uma vez que começou a usar valor default, todos os demais devem ser
default
 Não se pode omitir argumentos no meio da lista
 Os valores default somente são colocados na declaração da função
 O compilador precisa conhecer os valores default antes de usá-los
 Algumas vezes pode-se colocar estes valores comentados na definição,
apenas para fins de documentação
void fn (int x /* = 0 */ ) { // ...
188
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Argumentos default
 Exemplos:
f(int a=5, int b, float c); // NÃO PODE
g(int a, float b=0., float c=3.); // OK!
chamadas:
g(1), g(2, 7.), g(4, 5., 6.); // OK!
g(1, , 7); // NÃO PODE !
 Argumentos default devem ser utilizados para tornar as
chamadas a funções mais simples, quando houver uma grande
lista de argumentos que podem receber valores típicos
 O valor default deve ser o valor que mais provavelmente vai ocorrer
para aquele argumento, de forma que os programadores clientes
poderão muitas vezes ignorá-lo ou utilizá-lo somente quando for
necessário modificar este valor padrão
189
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Argumentos default
 Cuidado para não confundir o compilador usando a
sobrecarga e argumentos default simultaneamente
onde não poderia ...

void impressao(int,float);
void impressao(int, float, char=’a’);

quem o compilador chamaria ?


impressao(1, 5.) ;

190
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
18. Modifique a classe abaixo para usar argumentos
default no construtor. Teste o construtor criando dois
objetos diferentes.

191
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
19. Uma definição da classe Time correta pode
incluir os dois construtores a seguir? Se não,
explique o por quê.

Time (int h=0, int m=0, int s=0);


Time ();

192
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Capítulos 8, 9 e 10 do livro
 III.5.1. Constantes
 III.5.2. Funções inline
 III.5.3. Controle de visibilidade de nomes

193
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Objetivo na linguagem C++
 Permitir que o programador especifique o que pode ser
modificado ou não em seu programa
 Fornece maior controle e segurança a um programa em C++
 Compilador acusa erro se você utilizar tipos incompatíveis em uma operação
envolvendo constante ou alterar uma constante de forma acidental
 Especificação na linguagem C++
 Utiliza a palavra reservada const, que pode ter diferentes
significados, dependendo de onde ela aparece
 Para criar uma constante no programa, evitando “números mágicos”:
const int bufsize = 100; // substitui #define BUFSIZE 100
// macro na qual o pré-processador
// substitui valor, não ocupa memória

194
char buffer[bufsize];
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Vantagem do uso de constantes
 Sobre o #define
 Além de atuar de forma semelhante, uma constante tem um tipo, que
pode ser verificado pelo compilador: uma macro não tem
 Outras vantagens adicionais
 Constantes também podem ser usadas para garantir que uma
variável, depois de inicializada, não vai ter seu valor modificado ao
longo do programa
 Em geral não ocupa memória (isso depende da complexidade do tipo de dado
e do compilador – sempre aloca com uso em ponteiros e com extern)
 Possibilita realizar cálculos envolvendo os valores constantes em
tempo de compilação (importante na definição de arrays)
 Pode ser usado com todos os tipos (predefinidos ou definidos pelo usuário)
 Facilita a manutenção de programas
 Boa prática: usar const ao invés de #define
195
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Outras características
 Uma constante tem que ser inicializada na declaração
 Pois não pode haver uma atribuição para uma constante
 É visível somente dentro do arquivo onde está definida
 Se necessário, usar extern para obter valor externo e ser reconhecida
fora do arquivo
 extern const int bufsize;
 É possível inicializar constante com valor produzido em tempo de
execução
 const char c = cin.get(); const char c2 = c + ‘a’;
 Possibilita gerar código mais eficiente por eliminar (em vários
casos) a alocação de memória e a leitura de variáveis constantes
 Pode ser empregado em vários contextos
 Objetos, dados e função membros, argumentos de função, retornos de função,
196 …
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Constantes tratadas em
tempo de compilação

Constantes tratadas em tempo


de execução

197
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uso de constantes com ponteiros
 Ponteiro para constante
 Formas: const tipo *var; ou tipo const *var;
 Ex: const int *u; // u é um ponteiro que aponta para constante inteira
 u pode apontar para qualquer coisa (não é constante), mas o que ele aponta não
pode ser alterado
 Ponteiro constante
 Forma: tipo var=valor; tipo* const ponteiro = &var;
 Ex: int d=1; int *const p = &d;
 p é um ponteiro constante que aponta para um inteiro
 É necessário existir um endereço inicial, imutável para o ponteiro
 O conteúdo pode alterar – ex: *p = 2 // legal
 Ponteiro constante para objeto constante
 Engloba as duas formas acima: neste caso, nem o ponteiro nem o conteúdo
do objeto podem ser alterados
 Ex: int a=1; const int* const pa1 = &a; ou
int const* const pa2=&a
198
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Ponteiros e constantes: exemplo
 const pode se aplicar ao endereço para o qual o ponteiro aponta ou se aplicar ao
conteúdo apontado

const int *v = &e; Não se pode atribuir um


De toda forma, é possível utilizar um cast endereço de um objeto
para forçar a atribuição, mas não é uma constante para um ponteiro,
boa prática pois senão o objeto poderia ser
alterado por este ponteiro
199
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Importante!
 Apesar da linguagem C++ possuir diversos mecanismos para
ajudar a prevenir erros, ela não consegue te proteger de você
mesmo, caso você queira quebrar os mecanismos de segurança
disponibilizados
 Reconheça a razão destes mecanismos existirem e tenha cuidado
com a sua correta utilização
 Cuidados adicionais no uso de arrays
 char *cp = “qualquercoisa“
 “qualquercoisa” é criado pelo compilador como um array de caracteres constantes,
atribuindo ao ponteiro cp seu endereço inicial de memória
 A modificação de qualquer char deste array resulta em um erro de execução
 Se você quiser ser capaz de alterar uma string, aloque um array específico
 char cp[] = “qualquercoisa”
 Em geral os compiladores não reforçam esta diferença, mas é bom saber
200
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
20. Considerando as seguintes definições, indique
quais comandos são ilegais e justifique.
int i = 0;
const int j =1;
int *p1;
const int *p2;
int * const p3 = &i;
a) p1 = &i;
b) p1 = &j;
c) p2 = &i;
d) p2 = &j;
e) p2 = &i; (*p2)++;
f) p2 = &i; i++;
g) (*p3)++;
201
h) p3 = &j;
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Passagem de parâmetros em C++
 A passagem de parâmetros default é feita por cópia
void f(int i) { i = 50; }
 Porém, pode-se passar por referência e altera o valor
void g(int &i) { i = 20; }
 Outra forma de modificar uma variável é usar ponteiros
void h(int *pti) { *pti = 30; }
 O retorno default também é por cópia
int f1() { int i = 4; return i; }
int main() {
int j = 10;
f(j); // j continua com o valor 10
g(j); // j assume o valor 20
h(&j); // j assume o valor 30
j = f1(); // atribui-se a j o valor 4
}
202
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uso de constantes na passagem de parâmetros e retorno
 Na passagem de parâmetros constantes por cópia
 O uso de const não causa alteração significativa para os usuários da
função
 É apenas uma promessa que os valores originais não serão alterados pela
função

203
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uso de constantes na passagem de parâmetros e retorno
 Todos os objetos temporários são constantes
 Retorno de valor constante
 Ao retornar valor, também ocorre cópia  também não se faz
necessário para os tipos primitivos
 Para outros tipos, não podem ser atribuídos a outro objeto ou
modificados

204
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uso de constantes na passagem de parâmetros e
retorno
 Na passagem e retorno de endereços (ponteiro ou referências)
 Sempre que possível, usar const ao passar um endereço para uma
função
 O uso de const previne a ocorrência de alteração inadequada
 Além disso, possibilita usá-la junto a outros objetos que forem const
 A sintaxe por referência, além de mais simples, é mais eficiente

205
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Passando referências constantes em funções
 Esta é a forma mais adequada de passar parâmetros para funções,
onde se quer evitar a cópia e ainda garantir que não haja
modificação de valores internos
class X
{
// Um monte de atributos ==> é mais eficiente
// evitar a cópia!
};
void g (X x) {} // Passagem por cópia (ineficiente)
void g1(X& x) {} // Passagem por referência (OK, pode
// modificar)
void g2(const X& x) {} // Passagem por referência
// constante (OK, não pode
// modificar)

206
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Constantes em classes
 Podemos ter atributos e funções membro constantes
class X {
const int i; // atributo constante
public:
X(int ii);
void f() const; // função membro constante
};

 Também podemos ter objetos constantes


const X a; // objeto constante

207
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uso de atributo constante dentro de uma classe
 Aloca espaço específico em cada objeto
 Representa um valor que é inicializado uma vez e não pode mais
ser alterado
 “Esta constante é para toda a vida do objeto”
 Cada objeto pode ter um valor diferente para esta constante
 A inicialização da constante ocorre no construtor de cada objeto,
em um local específico
 Lista de inicialização do construtor
 Apresentada apenas na definição do construtor
 Ocorre após a lista de argumentos, precedida do “:” e vem antes do “{“
 Indica que deve ser executada antes de qualquer código do construtor
 Criada para uso pela herança (a ser visto posteriormente)
 Nota: a idéia desta lista foi estendida também para os tipos primitivos,
existindo construtores default para eles em substituição à atribuição
 Ex: float pi (3.14159) é o mesmo que float pi = 3.14159
208
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo de atributo constante em uma classe inicializado com a
lista de inicialização do construtor
class X {
const int i; // atributo constante
public:
X(int ii);
// etc ...
};
X::X(int sz) { i = sz; } // Não pode inicializar assim,
// pois i é constante
X::X(int sz) : i(sz) {} // OK!

 Nota: se quiser utilizar constante em tempo de compilação para


uma classe, utilizar static const tipo nomeVar = valor;
 Neste caso, só existirá uma instância para a classe, independentemente do
número de objetos criados para a classe
209
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo de uso geral da lista de inicialização de construtores

class Parte {
int t;
public:
Parte(int tt) ;
};
Parte::Parte(int tt) : t(tt) {}
Esta lista de
class Todo { inicialização pode ser
const int size; usada antes do corpo
Parte b; da função mesmo sem
public: ter atributo const.
Todo(int sz, int bt); No caso específico de
}; const, ela é obrigatória
Todo::Todo(int sz, int bt) : size(sz), b(bt) { }
210
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Objetos constantes e funções membro constantes
const int i = 1;
const X b(2); // objeto constante
 Como o compilador pode garantir que um objeto do tipo
especificado vai permanecer constante?
 Permitindo que somente funções membro constantes (funções
que o compilador garante que não modificam o estado do objeto)
sejam chamadas sobre os objetos constantes!
class X { void h(const X& rx) {
int i; rx.f(); // OK, f() é
public: constante
X(int ii); rx.g(); // ERRO!!!
int f() const; }
int g(); int main() {
}; X x1(10);
X::X(int ii) : i(ii) {} const X x2(20);
int X::f() const { return i; } x1.f(); //OK
211 int X::g() { i = 20; } x2.f(); //OK
© Renato Mesquita, x1.g(); //OK
Ana Liddy Magalhães e x2.g(); // ERRO!!! }
Raquel Mini
 Objetos constantes e funções constante
 Portanto, funções membro constante são aquelas funções
para as quais se tem a garantia de se manter constante o
objeto sobre o qual as funções são chamadas
 Elas não modificam os atributos dos objetos!
 São as únicas que podem ser chamadas por objetos constantes
 São seguras de serem chamadas por todos os objetos
 O const deve aparecer na declaração e na definição
 Caso contrário, o compilador considera 2 funções distintas
 Acusam erro caso a função tente alterar qualquer membro da função
ou chamar outra função membro não constante
 Construtores e destrutores não podem ser funções const, pois
sempre realizam alteração no objeto

212
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Regra importante: faça com que todas as funções membro que
não precisem modificar atributos do objeto sejam constantes
 Desta forma, elas poderão ser chamadas sobre objetos ou referências
constantes!
// contexto do exemplo anterior
void h(const X &refcons, X &ref) {
refcons.f(); // OK, f() é const
ref.f(); //OK, f() pode ser chamada por qualquer objeto
ref.g(); // OK, nem g() nem ref são const
refcons.g();// ERRO - g() não é const!
}

213
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
21. Localize os erros na seguinte classe e
explique como corrigi-los:

214
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
22. Faz sentido um construtor ser const? E um
destrutor? Por quê?

23. Por quê é interessante declarar como const


todas as funções membro que não modificam
atributos de objetos de uma classe?

215
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: necessidade
 Uso de macros
 Uma das maneiras que a linguagem C (e C++) possui para
permitir uma maior eficiência do código
 Vantagens do uso
 Permite que ocorra uma simples substituição de código pelo pré-
processador, evitando:
 Ter uma chamada a função, com todo o custo associado
 Criar os argumentos e copiar seus valores
 Fazer um CALL em Assembly, retornando o valor e efetuando um
RETURN em Assembly
 Possui a conveniência e a legibilidade de uma chamada de função,
porém sem o custo adicional associado

216
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: necessidade
 Macros: exemplos de uso que geram problemas
 #define F(x) (x + 1)
 Uso da macro  Expandido pelo pré-processador
F(1)  (1+1)
 Problema 1
 Desdobramento incorreto em função de erro ao defini-la
#define F (x) (x + 1) // note espaço ente F e (x)
F(1)  (x) (x + 1)(1)
 Problema 2
 Precedência incorreta de operadores
#define CUBO(x) x*x*x
CUBO(a+b)  a+b*a+b*a+b = a + 2.ba + b

217 Solução Problema 2: uso de parênteses


© Renato Mesquita, #define CUBO(x) (x)*(x)*(x)
Ana Liddy Magalhães e
Raquel Mini
CUBO(a+b)  (a+b)*(a+b)*(a+b)
 Funções inline: necessidade
 Macros: exemplos de uso que geram problemas
 Problema 3
 Uso incorreto de parâmetros

218
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: necessidade
 Macros: exemplos de uso que geram problemas
 Problema 4: não se pode implementar uma função membro de
uma classe como macro!
class X {
int i;
public:
#define VAL(X::i) // Error
 Por questões de eficiência, haveria a tendência de deixar todos os
atributos da classe como public!
 Problemas adicionais: macros não permitem variáveis locais,
blocos, verificação de tipos, chamadas recursivas, etc.
 Solução para todos os problemas das macros:
funções inline!
219
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: conceito
inline int par (int num) {return((num%2==0)? num : num+1);}
inline float cubo (float x) {return x*x*x;}
inline float dobro (float x) {return 2*x;}
 Objetivo
 O especificador inline dá uma dica para o compilador de que ele
deve tentar expandir o código para a função "inline” (como a macro
fazia), evitando o “overhead” associado à chamada à função
 É apenas uma sugestão, não obriga o compilador a realizar desta forma
 Todo o processo estará sob o controle do compilador
 Não se pode garantir que toda chamada a uma função inline vai ser
realmente gerada "inline"
 Para tornar a geração de código inline possível, você deve incluir o
corpo da função dentro da declaração
220
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: características
 Toda função definida no corpo de uma classe é inline
class Circulo{
private:
int raio;
Ponto centro;
public:
Circulo(Ponto ce, int ra): centro(ce), raio(ra){ }
float area( ) const { return PI*raio*raio; }
};
Circulo::Circulo() e Circulo::area() são inline!

 Uma função não pertencente à classe também pode se tornar inline


 Basta colocar o especificador inline no início de sua declaração e incluir
também o corpo da função
221
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: funcionamento
 Ao se deparar com uma função inline, o compilador coloca na tabela de
símbolos (assinatura + retorno + corpo da função)
 Ao localizar seu uso, ele verifica se a chamada está correta, se o retorno é
compatível, faz ajustes entre tipos (se necessário) e substitui o corpo da
função pela sua chamada, eliminando o overhead da chamada de função
 O comportamento lógico é o mesmo, o que melhora é o desempenho
 O código inline ocupa espaço, mas se a função for pequena, ocupa menos
espaço que o código gerado para sua chamada
 Se a função for grande, causará duplicação de código sempre que for chamada,
reduzindo o benefício de se ter maior eficiência na chamada
 Situações nas quais o compilador não consegue aplicar inline
 Caso a função seja muito complicada (ex: com laços)
 Caso seja necessário usar o endereço da função
 Neste caso, ele precisa armazenar para obter o endereço
222
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: aplicação
 Se justifica para funções pequenas
 Nas quais o overhead da chamada às funções seja maior que o
causado pela duplicação de código
 Seu objetivo é fornecer oportunidade de otimização ao
compilador
 Um uso típico de funções inline em classes são as
chamadas funções de acesso a atributos (get / set)
 Mas podem ser utilizadas também em situações mais sofisticadas

class Access {
int i;
public:
int get() const { return i; }
void set(int ii) { i = ii; }
};
223
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: aplicação
 Muitas vezes, se usam funções sobrecarregadas para acessar /
modificar os atributos por meio de accessors e mutators
 Accessors: funções que lêem informações do estado do objeto
 Mutators: funções que alteram o estado do objeto
class Rectangle {
int wide, high;
public:
Rectangle(int w = 0, int h = 0) : wide(w), high(h) {}
int width() const { return wide; } // width for read / get
void width(int w) { wide = w; } // width for set
int height() const { return high; } // height for read / get
void height(int h) { high = h; } // height for set
};
int main() {
Rectangle r(19, 47);
// Change width & height
r.height(2 * r.width()); // height for set, width for read
r.width(2 * r.height()); // width for set, height for read
}
224
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções inline: aplicação
 O uso de funções inline na definição da classe faz com
que sua interface fique “poluída”
 Apresenta detalhes de implementação que deveriam estar
escondidos do usuário da classe
 Para evitar isto e ainda poder utilizar funções inline, pode-se definir
funções membro inline fora da definição da classe

 Nota: nenhuma função inline de uma classe é avaliada até que


se atinja o “}” da classe
 Assim, pode ocorrer referência de uma função para outra que ainda
não foi declarada dentro da própria classe

225
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
class Rectangle {
int wide, heigh;
public:
Rectangle(int w = 0, int h = 0);
int width() const;
void width(int w);
int height() const;
void height(int h);
};

inline Rectangle::Rectangle(int w, int h) : width(w), height(h) {}


inline int Rectangle::width() const { return width;}
inline void Rectangle::width(int w) { width = w;}
inline int Rectangle::height() const { return height;}
inline void Rectangle::height(int h) { height = h;}

 Para que a substituição de código possa ser efetuada pelo


compilador, a definição das funções também deve ser feita no
arquivo .h !
226
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Importante
 Deve-se procurar usar o mínimo de macros, explorando ao
máximo as funções inline
 Existem alguns casos em que o uso das macros do pré
processador é adequado
 Exemplos
 Uso de #x e da concatenação dos strings
#define DEBUG(x) cout << #x “=” << x << endl
 Uso de ## para geração de novo identificador (operador merging)
#define FIELD(a) char* a##_string; int a##_size
class Record {
FIELD(one);
FIELD(two);
FIELD(three);
// ...
};
 Orientação geral:
227  Primeiro faça o programa funcionar, depois otimize-o!
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
24. Modifique o programa abaixo de forma que a
macro BAND() funcione de forma correta.

228
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
25. No programa abaixo, troque todas as funções membro
para funções inline. Troque também a função
initialize() para o construtor.

229
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Manipular nomes é uma atividade fundamental na
programação
 Quando o projeto fica grande, o volume de nomes a
manipular acaba ficando muito grande
 Necessário ter estratégias para gerenciá-los
 static: “algo que mantém sua posição” - visto sob 2 aspectos:
 Localização física na memória (memória estática)
 Alocado uma única vez em um endereço físico
 Área especial para dados estáticos (não usa a pilha de execução)
 Visibilidade proporcionada ao nome
 Controlada pelo escopo (visibilidade local)
 Vantagem sobre nomes globais: acesso + restrito, melhor controle
 Facilita localizar erros, não alterada fora do escopo definido
e sem sobreposição de nomes
230
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Variáveis locais static
int count () {
static int num = 0; // inicializada uma única vez
int x = 0; // inicializada várias vezes
num++;
...
return 0;
}
 A inicialização de num é feita somente na primeira vez
que a função é chamada
 Na realidade, na primeira vez que o programa passa pela
declaração da variável
 num mantém o seu valor de uma chamada para a outra
 Isto significa que a variável static não é criada na pilha, mas sim na
área de variáveis estáticas do programa

231
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Variáveis locais static

232
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Objetos static
 A construção do objeto static é feita no momento
que a thread de execução executa pela primeira
vez o código da função
 Se a função não for executada nenhuma vez, não é
construído
 O destrutor de um objeto static somente é
executado ao final do programa
 Objeto static x global
 O construtor de um objeto global é executado antes do main()
começar sua execução (é diferente do que ocorre com o static)
 O destrutor de um objeto global é executado somente ao final
da execução do programa (é semelhante ao que ocorre com o
static, caso tenha sido construído)
233
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Obj a é global: construtor chamado
antes de main() e o destrutor chamado
ao final do programa

Obj b é static: construtor chamado ao


chamar a função pela primeira vez e o
destrutor ao final do programa

234
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Obj a é global: construtor chamado
antes de main() e o destrutor chamado
ao final do programa

Obj b é static: construtor chamado ao


chamar a função pela primeira vez e o
destrutor ao final do programa

235
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
26. Informe o que será impresso pelos códigos
abaixo:
a) b)

236
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Atributos de classe: atributos static
 Atributos estáticos servem para implementar o
conceito de “Atributo de uma Classe”
 Usado quando deseja-se que todos os objetos de uma
determinada classe compartilhem um certo dado
 Único para toda a classe, independente do número de objetos
 Todos os objetos compartilham e podem se comunicar por ele

class Ponto{
int x, y; Ponto_2
Ponto_1
static int cont;
// .... x x
y y
};
cont cont
Ponto Ponto_1, Ponto_2;
237
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Atributos de classe: atributos static
 Pode ter escopo público, privado ou protegido
 Pode ser acessado por funções membro ou por outras partes
do programa, quando público
 Pode ser acessado antes da existência de qualquer objeto da
classe
 Seu espaço de memória é reservado antes da criação de qualquer
objeto
 Sua definição tem que ocorrer fora da classe (sem inline) e só uma
vez

class Ponto{
int x, y;
static int cont;
// ....
};
238 int Ponto::cont = 7;
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Atributos static constantes
 Todo membro estático deve ser redeclarado fora da classe,
porém dentro do escopo do arquivo da classe
 Neste ponto ele pode ser inicializado. Não se pode inicializar um
membro estático não constante dentro da definição da classe!

class Ponto{  Se o atributo static for também


int x, y; constante, pode ser inicializado na
static int cont; própria classe
// ....
}; class Buffer{
int Ponto::cont = 7; static const int buffsize = 256;
char buff[buffsize];
// ....
};
239
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções membro static
 Tipo de função membro especial cujas principais
características são:
 Trabalha para a classe como um todo e não para cada objeto
 Evita ter que criar uma função global (vista e acessível fora da classe)
 Pode ser chamada sem estar associada a um objeto da classe
 Basta utilizar a qualificação de escopo da classe - Classe::f()
 Não pode manipular membros não estáticos da classe
 Foram criadas para manipular os membros estáticos da classe
(dados e outras funções)
 Não possui o ponteiro this (por isso não acessam membros comuns)
 Nota: funções membro não estáticas podem acessar dados
e funções estáticas

240
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções membro static: exemplo

241
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Funções membro static: exemplo

Como os métodos static não


possuem o ponteiro this, eles não
podem acessar atributos e nem
métodos que não são static

242
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 É possível inserir um objeto static da mesma
classe dentro da própria classe (padrão
singleton)
 Objetivo: criar uma classe que pode possuir apenas uma
instância, disponível globalmente no código
 Motivação
 Há várias situações nas quais é útil criar uma classe que pode
possuir apenas uma instância – exemplos:
 Deve existir apenas um sistema de arquivos, apesar de poderem
existir várias impressoras
 Deve existir apenas um spool de impressão, apenas um conversor A/D
para um filtro digital, etc.

243
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Construção do singleton:
 O construtor da classe é privado, de modo que a
criação da instância única é feita no método
Instance()
 Instance é um método de classe (static) e,
portanto, pode ser chamado antes mesmo da
existência do objeto
 Uma vez que a instância exista, Instance()
simplesmente retorna um ponteiro para ela

244
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Singleton: exemplo

245
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Singleton: consequências
 Como só existe uma instância da classe, o programador
tem controle sobre como os clientes a acessam
 Reduz a poluição no namespace global
 Apenas o nome da classe entra nele

246
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: necessidade
 Em projetos grandes, a falta de controle sobre o
espaço (escopo) de nomes pode ser um problema
 Os nomes mais comuns já foram usados, sendo necessário
colocar nomes grandes e/ou complicados para garantir que
sejam diferentes
 Podemos usar typedef para tentar simplificar, mas não seria
uma solução elegante e suportada em todos os ambientes
 Solução em C++: usar namespaces
 Subdividir o espaço de nomes globais em partes gerenciáveis,
 Coloca o nome dos membros do namespace em um espaço
específico e separado (semelhante a um encapsulamento)

247
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: aplicação
 Mecanismo do C++ para agrupar logicamente nomes
 Se quaisquer declarações (variáveis, funções, classes, ...)
forem relacionadas entre si, elas poderão ser colocadas
em um mesmo espaço de nomes para expressar este fato
 Existe um outro mecanismo de agrupamento físico
que é o uso de arquivos
 Os namespaces vêm adicionar a este mecanismo o
agrupamento lógico

248
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: sintaxe
 A sintaxe de criação de um namespace parece com a da classe
namespace MyLib {
// Declarations
}
int main() {}

 Isto produz um novo namespace contendo as declarações que


forem feitas no espaço apropriado
 Existem algumas diferenças em relação às definições de classes:
 A definição de um namespace pode aparecer apenas no
escopo global (ou aninhado a outro namespace)
 Não é necessário usar um ; após a definição do namespace
 A definição de um namespace pode ser continuada em
múltiplos arquivos de cabeçalho (vai sendo complementada)
249
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespace: exemplo

250
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: características
 Um namespace é um escopo com nome
 Quanto maior o programa, mais úteis são os namespaces para
expressar a separação lógica de suas partes
 Idealmente toda entidade em um programa deve estar em algum
namespace, para indicar o seu papel lógico no programa
 A exceção é main(), que deve ser global
 Namespaces: usos
 Seu uso pode ocorrer basicamente de 3 formas:
 Pela qualificação explícita (resolução de escopo)
 Pela diretiva using
 Pela declaração using
 Estas formas serão detalhadas a seguir
251
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: uso pela
qualificação explícita
 Explicita o escopo dos objetos
que estão em um namespace

252
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: uso com a diretiva using
 Torna todos os nomes de um namespace disponíveis em um
determinado contexto (“importa” todo o namespace de uma vez)

253  Já utilizamos isto: using namespace std;


© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: uso com a declaração using
 Possibilita não deixar disponíveis todos os nomes presentes em um
namespace, mas apenas alguns
 Introduz um sinônimo local para o nome

254
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: cuidados
 O objetivo dos namespaces é expressar uma estrutura lógica
 A forma mais simples de estrutura para a qual ele pode ser usado é a
distinção entre o código escrito por uma pessoa e o escrito por outra
 Quando usamos um único espaço de nomes global, torna-se
desnecessariamente difícil compor um programa composto por partes
separadas
 O problema é que cada parte pode definir os mesmos nomes
 Quando combinados em um mesmo programa, estes nomes colidem
... veja exemplo a seguir

255
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: cuidados

 Fica impossível para alguém usar juntos meu.h e


seu.h sem modificações
 f(): possuem mesmo nome e assinatura
 Ambos possuem a classe String
256
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: cuidados
 Solução: separar os espaços de nomes

 Utiliza-se Meu e Seu por qualificação explícita,


declaração ou diretiva using

257
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces podem ser compostos, para gerar novos
Namespaces

258
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Namespaces: regras importantes
 Não utilize diretivas using em arquivos .h
 Utilize apenas nos .cpp, pois o seu alcance fica mais limitado
 Uso livre de um namespace dentro de um arquivo, sem afetar outros
 Cada .cpp pode ter o seu namespace, distinto dos demais
 Se houver problemas de nomes duplicados quando estiver usando
mais de uma diretiva using nos .cpp, efetue a seleção deles
atribuindo declarações using
 Nos .h, ou utilize as qualificações explícitas para acessar
determinados nomes encapsulados em namespaces, ou utilize
declarações using, para introduzir apenas os nomes
selecionados no escopo

259
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
27. Crie uma função que retorna o próximo valor
em um sequência de Fibonacci toda vez que
ela é chamada. Insira um parâmetro que é um
bool com o valor default igual a false tal que
quando você passa o argumento com valor
true ele começa do início da sequência de
Fibonacci. Crie a função main para utilizar
essa função.

260
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Na Unidade III já vimos ...
 III.1. Implementando classes e objetos em C++
 III.2. Atributos e métodos: controle de acesso e
encapsulamento
 III.3. Inicialização e destruição
 III.4. Sobrecarga de funções e argumentos default
 III.5. Constantes, funções inline e controle de
visibilidade

262
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Agora veremos ...
 III.6. Ponteiros, referências, atributos dinâmicos,
gerenciamento de memória e o construtor
de cópia
 III.7. Sobrecarga de operadores e conversão de tipos

 Referência básica
 Thinking in C++, Vol. 1
 Parte 3: Capítulos 11 ao 13

263
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Unidade III - Classes e Objetos
III.1. Implementando classes e objetos em C++
III.2. Atributos e métodos: controle de acesso e
encapsulamento
III.3. Inicialização e destruição
III.4. Sobrecarga de funções e argumentos default
III.5. Constantes e controle de visibilidade
III.6. Ponteiros, referências, atributos dinâmicos,
gerenciamento de memória e construtor de cópia
Parte
3

III.7. Sobrecarga de operadores e conversão de tipos


264
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que já vimos sobre o assunto?
 Como criar e utilizar ponteiros e referências
int *pt1; // Ponteiro para inteiro
int i = 4; // Inteiro
pt1 = &i; // pt1 “aponta” para i (armazena o endereço de i)
*pt1 = 5; // valor de i modificado, usando pt1
int &j = i; // j é uma referência que referencia i
j = 7; // i é modificado através de j

265
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que já vimos sobre o assunto? (cont.)
 Como utilizar ponteiros e referências para passar
parâmetros por referência para funções

266
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O que já vimos sobre o assunto? (cont.)
 Uso de referências constantes ao passar parâmetros
por referência para funções
 Estes parâmetros não poderão ser modificados dentro da função

267
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Usando ponteiros, podemos fazer alocação dinâmica de
memória

268
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Destrutor e alocação dinâmica
 Se a classe tem destrutor, ele é chamado ao
desalocar o objeto

269
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Referências: conceito
 Referências são como ponteiros constantes, porém:
 São automaticamente desreferenciados pelo compilador
 Deixam a sintaxe mais simples e “enxuta” do que usando ponteiros
 Ficam “amarradas” à memória à qual foram inicializadas
 São adequadas para controlar a forma como os argumentos são
passados para dentro e para fora das funções
 Muito usadas em lista de argumentos e retorno de valores em funções
 São essenciais para dar suporte à sintaxe de sobrecarga de
operadores (a ser vista mais à frente)

270
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Referências: regras
 Uma referência deve ser inicializada quando é definida
 Um ponteiro pode ser inicializado a qualquer momento
 Uma vez inicializada para um objeto, não pode mais ser
alterada para referenciar outro objeto
 Um ponteiro pode apontar para outro objeto a qualquer momento
 Não pode ter conteúdo nulo, ou seja, precisa estar
relacionada a uma região de memória legítima
 Um ponteiro pode apontar para NULL

271
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo

272
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Referências: uso em funções
 Como argumento
 Qualquer alteração na referência dentro da função altera o
argumento também fora da função
 Funciona igual ao ponteiro, porém possui uma sintaxe mais limpa
 O ponteiro deixa explícito seu uso, a referência mantém implícito seu uso

 Como valor de retorno


 Uma função pode retornar uma referência, evitando a cópia no
retorno da função
 Os cuidados para retornar referência são os mesmos que com ponteiros
 Deve-se tomar o cuidado de retornar uma referência válida quando efetuarmos
um retorno por referência
 O que está sendo apontado não pode ser destruído quando a função
retorna, senão apontará para memória desconhecida
273
© Renato Mesquita,
 É necessário retornar uma variável que continue a existir fora do
Ana Liddy Magalhães e escopo da função!
Raquel Mini
 Exemplo de referências: uso em funções

274
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo de referências: uso em funções

275
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo de referências: uso em funções

276
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Referências constantes
 Não permitem alterar o objeto referenciado
 Se você sabe que a função irá respeitar o objeto (não alterará seu valor),
o uso de const permitirá que a função seja usada em todas as situações
 Para tipos primitivos, a função não alterará os argumentos
 Para tipos definidos pelo usuário, a função chamará apenas membros
constantes e não modificará dados públicos
 Seu uso em argumentos de funções é especialmente importante,
porque a função pode receber um objeto temporário
 Este pode ter sido criado como o retorno de uma outra função,
ou explicitamente, pelo usuário da função
 Objetos temporários são sempre constantes,
portanto se não utilizarmos uma referência
constante o argumento não vai ser aceito
pelo compilador
277
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Orientações para passagem de argumentos
 Ter por hábito, sempre que possível, passar
referências constantes
 Garante maior eficiência
 Evita ter que copiar valores, bastando empilhar seu endereço
 Passagem por valor requer construtor de cópia e destrutor
 Podem não estar sempre disponíveis
 Se não for alterar, estes são desnecessários

278
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
28. Escreva uma função que receba um ponteiro
como parâmetro, modifique o conteúdo apontado
pelo ponteiro e retorne o conteúdo do ponteiro
como referência.
29. Crie uma classe com algumas funções membro e
passe o objeto desta classe como parâmetro para
a função do exercício anterior. Utilize const
<nomeclasse> * no argumento da função e
faça alguns membros da classe const.
a) Prove que você somente pode chamar funções membro
const dentro da função.
b) Troque o parâmetro para referência ao invés de ponteiro
e explique as diferenças.
279
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Construtor essencial para controlar a passagem de
parâmetros e o retorno de função por cópia
 Devido à sua importância, o compilador sempre gera um construtor
automaticamente se este não for fornecido
 Ao passar um objeto por valor, é criado um novo objeto a ser utilizado
pela função a partir de um fornecido (o original passado para a função)
 Comportamento default: atributos do objeto são copiados, um a um
 Pode ser reescrito pelo usuário sempre que for necessário
 Para tipos primitivos, poderia ser realizada uma cópia por bits
 Para tipos mais sofisticados, é necessário definir um construtor
específico

280
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 É executado toda vez que:
 Um objeto da classe é passado e, em alguns casos, retornado,
por valor, para uma função
 Não chama construtor de cópia no retorno de objeto local
 Um objeto é declarado e inicializado com outro objeto da própria
classe
 Forma geral: Objeto (Objeto &) (“X of Xref”)
 O único argumento é o objeto a ser copiado

 Nota: só é necessário se for passado objeto por valor

281
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo:

282
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo:

283
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo:

284
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo:

285
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
286
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Passagem por valor:
 Sempre chama o construtor de cópia

 Retorno por valor:


 Chama o construtor de cópia se o objeto retornado for
um dos argumentos recebidos na passagem de
parâmetros
 Não chama o construtor de cópia se o objeto retornado for
algum objeto declarado localmente
 Chama o destrutor do objeto temporário construído no
momento do retorno caso o objeto que recebe o
retorno já tenha sido definido antes da chamada da
função
287
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Como evitar a cópia?
 Fazendo a passagem de parâmetros por referência constante!
 Por que o construtor de cópia tem que receber uma
referência?
 Se fosse cópia, estaríamos em uma recursão infinita!
 Como criar classes em que o usuário da classe nunca
possa passar objetos da classe por valor para funções?
 Criando um construtor de cópia privado, como abaixo

288
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• A necessidade de atributos dinâmicos
• Atributos dinâmicos são atributos que precisam ser
alocados e desalocados dinamicamente
• A alocação de atributos dinâmicos ocorre quando algum
membro da classe é um ponteiro
• Quando um objeto é criado, alocamos espaço para ele
• Isto é normalmente feito pelos construtores da classe
• Quando termina o escopo dos objetos, deve-se desalocar a
memória alocada para os atributos dinâmicos
• Para isso usamos o destrutor da classe
• É necessário criar o construtor de cópia para objetos com
atributos dinâmicos
• Caso contrário, teremos problemas graves na passagem de
289 parâmetros por cópia para funções
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Atributos dinâmicos

Solução:
1) ou passamos por referência;
290 2) ou criamos o construtor de
© Renato Mesquita,
Ana Liddy Magalhães e
cópia!
Raquel Mini
 Atributos dinâmicos e o construtor de cópia

291
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
• Ponteiro this: necessidade
• Cada um dos objetos de uma determinada classe tem sua cópia
dos atributos de maneira automática
• As funções membro têm o código compartilhado por todos os
objetos da classe
• Como o programa sabe, dentro do código da função, qual é o
objeto que está sendo acessado?
• Para efetivar a ligação entre função membro e objeto, o C++ possui
um ponteiro implicitamente criado para cada função da classe,
denominado this
• Sua finalidade é apontar para o objeto que chamou a função
• Podemos usar este ponteiro quando queremos referenciar o
objeto como um todo (e não a seus atributos individuais)

292
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
293
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
30. Crie uma classe contendo um double *. O
construtor inicializa o double * chamando
new double e associando ao conteúdo
apontado pelo ponteiro o valor recebido como
parâmetro. O destrutor imprime o conteúdo
apontado pelo ponteiro e chama o delete
para o ponteiro. Agora crie uma função que
receba um objeto desta classe por valor e
chame esta função no main(). O que
acontece? Conserte o código criando o
294 construtor de cópia.
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
31. Crie uma classe com um construtor que
parece com um construtor de cópia, mas que
recebe um parâmetro extra com um valor
default. Mostre que esse construtor é utilizado
como construtor de cópia.

295
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Sobrecarga: é apenas uma outra forma de se fazer
uma chamada de função
 Sobrecarga de funções: funções com o mesmo nome,
mas com parâmetros diferentes (e códigos diferentes)
 Sobrecarga de operadores: operadores com o mesmo
nome, mas com operandos diferentes (e códigos
diferentes)
 Diferenças:
 A sintaxe é diferente: os argumentos para esta função não
aparecem dentro de parênteses, mas próximo aos
caracteres que aparecem em torno dos operadores
 O compilador define como construir (que porção / tipo de
código chamar)
 Tratar como função inline, realizar conversão entre tipos, aplicar
instrução assembly
296
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplo:
class Complex{
double re;
double im;
}
Complex A,B,C;

 Seria interessante poder redefinir o operador + de


modo que C = A+B; pudesse ser escrito para a
classe Complex  sobrecarga de operador

297
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Uso de operadores sobrecarregados em C++
 Apenas aqueles já existentes na linguagem C
+ - * / % ^ &
| ~ ! = < > +=
-= *= /= %= ^= &= |=
<< >> >>= <<= == != <=
>= && || ++ -- ->* ,
-> [ ] ( ) new new[] delete delete[]
 Os seguintes operadores não podem ser sobrecarregados
** Exponenciação em outras linguagens
:: Resolução de escopo
. Seleção de membro
.* Seleção de membro através de um ponteiro para função
298
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Outras regras
 O operador sobrecarregado não pode alterar as
regras de precedência e de associatividade do C e
C++
 Não alterar o número de argumentos requeridos pelo
operador
 Ao menos um dos parâmetros do operador deverá ser
membro de uma classe
 Somente expressões contendo tipos definidos pelo
usuário podem ter operador com sobrecarga

299
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Sintaxe para definição de operadores
sobrecarregados
 Usar “operator” seguido do símbolo do operador
 Se torna uma função como qualquer outra

300
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Sintaxe para definição de operadores sobrecarregados
 Sobrecarga como função membro
class Nome_classe{
tipo_retorno operatorop (lista_de_parâmetros);
};
tipo_retorno Nome_classe::operatorop (lista_de_parâmetros) {
... // código da função
}

 Como função friend não membro


class Nome_classe {
friend tipo_retorno operatorop (lista_de_parâmetros);
};
tipo_retorno operatorop (lista_de_parâmetros) {
... // código da função
}
301
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Sintaxe para definição de operadores sobrecarregados (cont.)
 Como função global não friend
class Nome_classe {
...
};
tipo_retorno operator op (lista_de_parâmetros);

tipo_retorno operator op (lista_de_parâmetros) {


... // código da função
}
 O número de parâmetros do operador sobrecarregado depende do
tipo do operador e da função:
 Operador unário, função membro  nenhum parâmetro
 Operador unário, função não membro  um parâmetro
 Operador binário, função membro  um parâmetro
 Operador binário, função não membro  dois parâmetros
302
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplos

303
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Exemplos

304
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Passagem e retorno de valor: orientações gerais
 Sempre utilizar passagem por referência
 Deixa o código mais limpo e em geral é mais eficiente
 Da mesma forma que com qualquer função, se não for necessário alterar um
argumento (ex: +, -), usar referência constante
 Se a função for membro de uma classe  usar uma função membro constante
 Com operadores envolvendo atribuição (ex: +=, =), que alteram o valor à
esquerda, deve-se usar referência (sem const, pois o valor será alterado)
 O tipo de valor de retorno a ser selecionado depende do significado
esperado do operador
 Se é produzido um novo valor (ex: +), será necessário gerar um novo objeto
como valor de retorno
 Todos os operadores de atribuição alteram o lvalue
 Deve-se retornar o mesmo tipo do que está sendo modificado, de forma a permitir
atribuição múltipla (ex: a = b = c)
 O valor de retorno de todos eles deve ser uma referência não constante a lvalue
 Para operadores lógicos, retornar no mínimo um inteiro (o ideal é bool)
305
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Pontos importantes: Operadores ++ e –– (pré e pós incremento)
 São criadas assinaturas diferentes para não gerar conflito (o
int não é usado)
 Quando não é função membro
 ++a ou --a  operator++ (a) ou operator-- (a)
 a++ ou a--  operator++ (a,int) ou operator-- (a,int)
 Quando é função membro
 ++b ou --b  operator++ () ou operator-- ()
 b++ ou b--  operator++ (int) ou operator– (int)

306
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Pré-fixados: retorna o valor incrementado, por
referência
 O próprio item está sendo alterado e o operador pode
estar envolvido em outros contextos para os quais
pode ser necessário obter o valor de retorno
 Para funções membro, retornar *this
 Para funções não membro, retornar a referência para o
argumento incrementado

307
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Pós-fixados: retorna o valor antes de incrementar,
por valor (cópia)
 Tem que criar objeto para armazenar o valor de
retorno (fazendo cópia), pois o valor inicialmente
recebido deverá ser incrementado
 Desta forma, preserva-se o significado original destes operadores

308
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Pontos importantes: operadores + e -
 Necessário retorno por valor, pois deve-se sempre pensar que um
operador poderá fazer parte de uma expressão complicada,
envolvendo vários operadores
 Exemplo: a+b+c-d
 Outros pontos importantes
 Utilize passagem e retorno por referência sempre que for
possível!!!
 Retorno de valor como constante também é uma boa prática
 Todo objeto temporário é automaticamente constante
 Busque otimizar o retorno
 return Complexo (r, i); é mais eficiente que
Complexo tmp(r, i); return tmp;
 O compilador entende que não tem outra finalidade para o objeto a não ser
retornar e já constrói o objeto direto no endereço de retorno
 gera apenas uma chamada do construtor
309
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Operador friend de duas classes

310
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O operador = (atribuição)
 Tem significado pré-definido (copia atributo por atributo)
 Ele deve ser sobrecarregado sempre que seu comportamento
for diferente do operador de atribuição default criado pelo C++

 Por que é retornado *this?


 Porque permite escrever a=b=c=d;
 Problema:
 E se fizermos a=a?

311
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 O operador = (atribuição)
 Solução para o problema a=a (auto-atribuição)

312
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Operadores de fluxo de entrada e saída
 Podemos sobrecarregar os operadores >> e << para fazer a
leitura e impressão dos objetos das classes que definirmos

 Estes operadores não podem ser membros da classe


 Os objetos que aparecem à esquerda deles não são do tipo MeuVetor
(são de istream para >> e ostream para <<)
 Assim, estes operadores serão:
 friends se tiverem que fazer acesso direto aos atributos
privados da classe
313  funções globais, se não necessitarem deste acesso
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Por que retornar ostream& e istream& ?
 Para poder escrever std::cout << st1 << i << etc;
314
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
Valores digitados

315
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
32. Escreva uma classe chamada Bird que
contém uma string, um static int e um int. No
construtor default, use o static int para gerar
automaticamente um identificador para a
string da forma Bird#1, Bird#2, etc. Insira um
operador << da classe ostream para imprimir
os objetos Bird. Insira o construtor de cópia e
os operadores +, -, * e / que operam com o
int.

316
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Conversão de tipos: necessidade
 Se o compilador encontra uma expressão ou uma chamada a função
em que se utiliza um tipo de variável que não é aquela que se
esperaria naquela chamada...
 Em C e em C++, o compilador pode muitas vezes efetuar uma conversão
automática de tipos, transformando-o no tipo que a função ou a expressão
esperam receber (conversão implícita)
 Alternativamente, pode ocorrer a conversão explícita, com o uso de casts

 Este mesmo efeito pode ser obtido em C++ para os tipos definidos
pelo programador
 Funções para conversão de tipos podem ser definidas, o que pode ser
realizado de duas maneiras
 Utilizando um tipo particular de construtor
 Utilizando sobrecarga de um operador de conversão de tipos
317
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Conversão através de construtor: necessidade
(exemplo)

318
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Conversão utilizando o construtor: implementação
 Por default, um construtor com um único argumento também define
uma conversão implícita
 Para alguns tipos isto é o ideal, mas para outros pode causar sérios
erros

Solução: construtores explícitos


319
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Conversão utilizando construtores explícitos
 Para desligar a conversão automática de tipos, utilize
o modificador explicit antes no nome do construtor

320
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
 Conversão por sobrecarga de um operador de
conversão de tipos
 Cria-se uma função da classe utilizando a palavra chave
operator seguida pelo tipo para o qual se quer converter o
objeto

321
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini
33. Considerando o código abaixo, explique o que
irá acontecer nas linhas (1) a (6).

322
© Renato Mesquita,
Ana Liddy Magalhães e
Raquel Mini