Você está na página 1de 344

© 1990 by Pearson Education do Brasil

Treinamento em Linguagem C – Módulo 2

Todos os direitos reservados. Nenhuma parte desta publicação poderá ser


reproduzida ou transmitida de qualquer modo ou por qualquer outro meio,
eletrônico ou mecânico, incluindo fotocópia, gravação ou qualquer outro tipo de
sistema de armazenamento e transmissão de informação, sem prévia autorização,
por escrito, da Pearson Education do Brasil.

Capa: Douglas Lucas


Editoração Eletrônica: E.R.J Composição Editorial e Artes Gráficas Ltda.

Dados de Catalogação na Publicação

Mizrahi, Victorine Viviane


Treinamento em Linguagem C – Módulo 2
Pearson Makron Books, 2001 – São Paulo

ISBN: 85-346-1423-7

1. C (Linguagem de programação para computadores)

90-0459 CDD-001.6424

Índices para catálogo sistemático:


1. Linguagem de programação : Computadores:
Processamento de dados 001.6424
2. Linguagem C: Computadores: Processamento de
dados 001.6424

2005
Direitos exclusivos para a língua portuguesa cedidos à
Pearson Education do Brasil Ltda,
uma empresa do grupo Pearson Education
Av. Ermano Marchetti, 1435
CEP: 05038-001 – São Paulo – SP
Fone (11) 3613-1222 Fax (11) 3611-0444
e-mail: vendas@pearsoned.com
A minha querida irmã Lucie, que tem sido ao
longo de todos esses anos minha maior amiga.
A sua presença singela, nobreza de alma e
generosidade incomparável fazem o dia melhorar
cada vez que nos encontramos.
Página em branco
A caminhada é feita como quem cuida de um
doente querido, com calma e delicadeza, com
paciência e dedicação.

(Luiz Carlos Lisboa)


Página em branco
MAKRON
Books

SUMÁRIO

Sumário

Prefácio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XXVII

Uma Visão Geral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .XIX


Classes e objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XIX
Encapsular e esconder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XX
Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XX
Polimorfismo e sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XXII
C é um subconjunto de C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XXIII

Capítulo 8 Classes e Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


Uma classe simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Definindo a classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Membros privados e membros públicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Funções-membro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Funções-membro inline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Criando objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Chamada a funções-membro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Mensagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Funções-membro definidas fora da classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Construtores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

IX
X Treinamento em Linguagem C++

Inicialização automática . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Valor de retorno de um construtor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Construtores chamando funções-membro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Sobrecarga de construtores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Funções-membro versus dados públicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Objetos const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Objetos como argumentos de funções-membro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Construtores de corpo vazio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Objetos como argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Formatando números . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Funções que retornam um objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Atribuições entre objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Dados-membro static . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Destrutores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Membros static públicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Funções-membro static . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Estruturas e classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Matrizes de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Criando a matriz de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Acesso a funções-membro via um elemento da matriz . . . . . . . . . . . . . . . . . . . . . . . 34
Criando um tipo string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Objetos e a alocação de memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

Capítulo 9 Sobrecarga de Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47


Visão geral de sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Limitações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Operadores unários e binários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Sobrecarga de operadores unários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Sobrecarga do operador de incremento prefixado . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Valor de retorno da função operadora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Objetos temporários sem nome . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Sobrecarga do operador de incremento pós-fixado . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Sumário XI

Limitações dos operadores de incremento sobrecarregados . . . . . . . . . . . . . . . . . . . 57


Sobrecarga de operadores binários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Sobrecarga de operadores aritméticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Somando objetos da classe ponto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Criando operadores para manipular strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Comparando cadeias de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Outras comparações com cadeias de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Sobrecarga do operador [] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Conversões de tipos com classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Conversões entre tipos básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Conversões de objetos a tipos básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Conversões de tipos básicos a objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Conversões entre objetos de classes diferentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Primeiro método de conversão: função conversora . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Segundo método de conversão: função construtora . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Conversões dos dois lados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Conversões: quando usar o quê . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Ambigüidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

Capítulo 10 Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Derivando classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Definindo a classe derivada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
A relação entre classe-base e classe derivada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
O especificador de acesso a membros protected . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Construtores da classe derivada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Usando as funções-membro da classe-base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Herança pública e privada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Reescrevendo funções-membro da classe-base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Qual função é usada? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
O operador de resolução de escopo com funções . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Construtores da classe Agente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Construtor sem argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
XII Treinamento em Linguagem C++

Construtor de múltiplos argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109


Nenhum código é reescrito . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Quando usar o quê . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Hierarquia de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Classe-base “abstrata” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Conversões de tipos entre classe-base e classe derivada . . . . . . . . . . . . . . . . . . . . . 116
Níveis de herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Herança múltipla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Funções-membro em herança múltipla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Derivação private no programa IMOVEL.CPP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Objetos de uma classe como membros de outra classe . . . . . . . . . . . . . . . . . . . . . . 129
Ambigüidade em herança múltipla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Construtores em herança múltipla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Construtores sem argumentos com herança múltipla . . . . . . . . . . . . . . . . . . . . . . . 136
Construtores de múltiplos argumentos com herança múltipla . . . . . . . . . . . . . . . 137
Revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

Capítulo 11 Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144


O que são ponteiros? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
Por que os ponteiros são usados? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Ponteiros variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Endereços de memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
O operador de endereços & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Revisão: passando argumentos por referência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Passando argumentos por referência com ponteiros . . . . . . . . . . . . . . . . . . . . . . . . 149
Variáveis que armazenam endereços . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
O operador indireto (*) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Passando endereços para a função . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Ponteiros sem funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Ponteiros e variáveis apontadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Operações com ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Atribuição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Operação indireta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Sumário XIII

Trazendo o endereço do ponteiro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157


Incrementando um ponteiro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Diferença . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Comparações entre ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
A unidade adotada em operações com ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Ponteiros no lugar de matrizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Ponteiros constantes e ponteiros variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Passando matrizes como argumento para funções . . . . . . . . . . . . . . . . . . . . . . . . . 162
Precedência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
Ponteiros e strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Funções de biblioteca para manipulação de strings . . . . . . . . . . . . . . . . . . . . . . . . . 166
Ponteiros para uma cadeia de caracteres constante . . . . . . . . . . . . . . . . . . . . . . . . . 167
Matrizes de ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
Matriz de strings e a memória alocada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Matriz de ponteiros e a memória alocada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
Área de alocação dinâmica: heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
new e delete: alocando e desalocando memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
O operador delete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
Observações para os programadores C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
Ponteiros void . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
Redimensionando strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Sobrecarga do operador de atribuição com strings . . . . . . . . . . . . . . . . . . . . . . . . . . 179
O ponteiro this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
Retornando o ponteiro this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
this é um ponteiro constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
Alocando tipos básicos usando memória dinâmica . . . . . . . . . . . . . . . . . . . . . . . . . 184
Dimensionando matrizes em tempo de execução . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Dimensionando matrizes de duas dimensões com new . . . . . . . . . . . . . . . . . . . . . 186
Ponteiros para objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Acessando membros por meio de ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Usando referências . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
Uma matriz de ponteiros para objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Criando uma lista ligada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
Esquema de uma lista ligada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
XIV Treinamento em Linguagem C++

A classe ListaLigada e o programa LISTA.CPP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194


Adicionando um livro à lista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
Imprimindo os dados da lista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
Implementações adicionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
Ponteiros para ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
Ordenando ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Notação ponteiro para matrizes de ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Argumentos da linha de comando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Ponteiros para funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
Declarando o ponteiro para função . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Endereços de funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Executando a função por meio do ponteiro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Operações ilegais com ponteiros para funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Ponteiros para funções como argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Matrizes de ponteiros para funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Inicializando uma matriz de ponteiros para funções . . . . . . . . . . . . . . . . . . . . . . . . 211
Revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214

Capítulo 12 Funções Virtuais e Amigas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224


Funções virtuais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Funções não-virtuais acessadas por meio de ponteiros . . . . . . . . . . . . . . . . . . . . . . 226
Implementando funções virtuais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
Resolução dinâmica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
Funções virtuais puras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
Classes abstratas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Funções virtuais e registros variantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
As classes Assegurado e NaoAssegurado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
O programa main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Classe-base virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Construtores em classe-base virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Destrutores de classe-base e de classe derivada . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Funções amigas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
Sumário XV

Funções amigas como interface entre classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245


Classes amigas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
Integridade da classe e funções amigas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
Funções amigas e sobrecarga de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
Sobrecarga de operadores unários e funções amigas . . . . . . . . . . . . . . . . . . . . . . . . 254
Restrições ao uso de amigas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
Revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257

Capítulo 13 Operações com Arquivos: Iostream . . . . . . . . . . . . . . . . . . . . . . . . . . . 264


Objetos stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
A hierarquia de classes iostream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Arquivos IOSTREAM.H, FSTREAM.H, STRSTREA.H E STDIOSTR.H . . . . . . . . 267
As classes iostream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Gravando linha a linha em arquivos em discos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
Lendo uma linha por vez em arquivos em disco . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Detectando o fim-de-arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
Lendo e gravando um caractere por vez no arquivo . . . . . . . . . . . . . . . . . . . . . . . . 271
A função open() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
Modo texto e modo binário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
Gravando objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
Lendo objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
Gravando e lendo objetos de um mesmo arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
As funções seekg(), tellg(), seekp() e tellp() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
Calculando o número de registros do arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
Condições de erro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
Lendo e gravando de e para a memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Sobrecarga dos operadores << e >> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
A classe data e a sobrecarga de << e >> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
Imprimindo na impressora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
Manipuladores e flags da classe ios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
Lendo com cin e gravando em disco . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
Criando os nossos próprios manipuladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
Operadores bit-a-bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
XVI Treinamento em Linguagem C++

O operador and: & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303


O operador or: | . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
O operador xor: ^ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
O operador shift direito: >> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
O operador shift esquerdo: << . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
Revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307

Apêndice — Tabela ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310


MAKRON
Books

PREFÁCIO

Prefácio
O curso completo está divido em dois volumes. O Módulo 1 abrange os
Capítulos 1 a 7 e o Módulo 2, os Capítulos 8 a 13. Este livro foi planejado para
um curso completo de programação em linguagem C++. O estudante deve ter
conhecimentos básicos de sistema operacional nos equipamentos da família
IBM-PC, XT, AT ou PS/2.
O objetivo principal é o de apresentar a linguagem C++ de maneira
simples e clara e mostrar como C++ pode ser usada para a criação de programas
sérios. Os exemplos expostos não estão relacionados a nenhum assunto especí-
fico e podem ser facilmente adaptados a qualquer área de aplicação.
O livro apresenta uma organização didática dos temas abordados e
pode ser adotado em cursos de computação de escolas técnicas ou universida-
des. Ao aluno interessado é dada a oportunidade de criar novos programas e
desenvolver novas idéias a partir das apresentadas.
É certamente possível aprender C++ sem conhecer a linguagem C. Esse
livro foi planejado para o ensino da linguagem C++ como linguagem indepen-
dente. Você pode aprender C++ mesmo como primeira linguagem de progra-
mação. Entretanto seus conceitos e sua sintaxe não são tão fáceis como os de
uma linguagem como BASIC ou PASCAL, e os programadores iniciantes podem
sentir alguma dificuldade.
Os exemplos deste livro foram processados utilizando o compilador
TURBO C++ da Borland ou o compilador C++ 7.0 da Microsoft para computa-
dores IBM-PC e sistema operacional MS-DOS versão 6.0.

XVII
Página em branco
MAKRON
Books

UMA VISÃO GERAL

Uma Visão
visão geral
Geral

CLASSES E OBJETOS

A idéia fundamental de linguagens orientadas ao objeto é a possibilidade de


combinar num único registro campos que conterão dados e campos que são
funções para operar os campos de dados do registro. Uma unidade assim
definida é chamada classe.
Uma classe é considerada um tipo de dado como os tipos que existem
predefinidos em compiladores de diversas linguagens de programação.
Como exemplo, considere o tipo int que é predefinido em C e C++.
Podemos declarar quantas variáveis do tipo int forem necessárias ao programa.
De modo similar, podemos declarar quantas variáveis quisermos de uma classe
já definida.
Uma variável de uma classe é chamada objeto e conterá campos de
dados e funções.
Definir uma classe não cria nenhum objeto, do mesmo modo que a
existência do tipo int não cria nenhuma variável.

XIX
XX Treinamento em Linguagem C++

As funções de um objeto são chamadas funções-membro ou métodos


e, de modo geral, são o único meio de acesso aos campos de dados também
chamados variáveis de instância.

ENCAPSULAR E ESCONDER

Se o programa necessita atribuir um valor a alguma variável de instância, deve


chamar uma função-membro que recebe o valor como argumento e faz a
alteração. Não podemos acessar variáveis de instância diretamente.
Desta forma, os campos de dados estarão escondidos para nós, o que
previne alterações acidentais. Dizemos então que os campos de dados e suas
funções estão encapsulados (de cápsula) numa única entidade.
As palavras encapsular e esconder são termos técnicos da definição de
linguagens orientadas ao objeto.
Se alguma modificação ocorrer em variáveis de instância de um certo
objeto, sabemos exatamente quais funções interagiram com elas: são as funções-
membro do objeto. Nenhuma outra função pode acessar esses dados. Isso
simplifica a escrita, manutenção e alteração de programas.
Um programa em C++ consiste em um conjunto de objetos que se
comunicam por meio de chamadas às funções-membro.
A frase “chamar uma função-membro de um objeto” pode ser dita como
“enviar uma mensagem a um objeto”.

HERANÇA

A programação orientada ao objeto oferece uma maneira de relacionar classes


umas com as outras por meio de hierarquias.
No nosso dia-a-dia, esse processo está presente quando dividimos
classes em subclasses, mantendo-se o princípio de que cada subclasse herda as
características da classe da qual foi derivada. Por exemplo: a classe de animais
Uma visão geral XXI

é dividida nas subclasses mamíferos, aves, peixes etc. Uma das características da
classe animais é a reprodução. Todas as suas subclasses têm essa característica.
Além das características herdadas, cada subclasse tem suas caracterís-
ticas particulares.

CLASSE-BASE

características
A B

características características características


A A B A B C
B E F
C D F G

CLASSES DERIVADAS

Em programação orientada ao objeto, o conceito de subclasse ou


processo de classes derivadas é chamado HERANÇA.
Em C++, a classe de origem é chamada classe-base e as classes que
compartilham as características de uma classe-base e têm outras características
adicionais são chamadas classes derivadas.
Uma classe-base representa os elementos comuns a um grupo de
classes derivadas.
Você poderia pensar em herança como algo semelhante ao uso de
funções para simplificar tarefas tradicionais. Você escreve uma função quando
identifica que várias seções diferentes de um programa, em parte, executam a
mesma coisa.
Em C++, você define uma classe-base quando identifica características
comuns em um grupo de classes derivadas.
XXII Treinamento em Linguagem C++

Da mesma forma que você pode criar uma biblioteca de funções úteis
a diversos programas, pode formar uma biblioteca de classes que poderão vir
a ser o núcleo de muitos programas.
O uso de uma biblioteca de classes oferece uma grande vantagem sobre
o uso de uma biblioteca de funções: o programador pode criar classes derivadas
de classes-base de biblioteca. Isto significa que, sem alterar a classe-base, é
possível adicionar a ela características diferentes que a tornarão capaz de
executar exatamente o que desejarmos.
Uma classe que é derivada de uma classe-base pode, por sua vez, ser
a classe-base de outra classe.
O uso de classes derivadas aumenta a eficiência da programação pela
não-necessidade da criação de códigos repetitivos. A uma função de biblioteca
não podemos adicionar outras implementações a não ser que ela seja reescrita
ou que tenhamos seu código-fonte para alterá-la e recompilá-la.
A facilidade com que classes existentes podem ser reutilizadas sem ser
alteradas é um dos maiores benefícios oferecidos por linguagens orientadas ao
objeto.

POLIMORFISMO E SOBRECARGA

O sentido da palavra polimorfismo é o do uso de um único nome para definir


várias formas distintas.
Em C++, chamamos de polimorfismo a criação de uma família de
funções que compartilham do mesmo nome, mas cada uma tem código inde-
pendente.
O resultado da ação de cada uma das funções da família é o mesmo. A
maneira de atingir esse resultado é distinta. Como exemplo, suponhamos que
desejemos calcular o salário líquido de todos os funcionários de uma empresa.
Nessa empresa há horistas, mensalistas, os que ganham por comissão etc.
Criamos um conjunto de funções em que cada uma tem um método
diferente de calcular o salário. Quando a função é chamada, C++ saberá
identificar qual é a função com o código adequado ao contexto.
Uma visão geral XXIII

A sobrecarga é um tipo particular de polimorfismo. Como exemplo,


tomemos o operador aritmético +. Em C++, usamos esse operador para somar
números inteiros ou para somar números reais:
5 + 2
3.4 + 5.8

O computador executa operações completamente diferentes para somar


números inteiros e números reais. A utilização de um mesmo símbolo para a
execução de operações distintas é chamada sobrecarga. Em C++, além das
sobrecargas embutidas na linguagem, podemos definir outras com capacidades
novas.
Por exemplo, podemos definir a seguinte operação nova para o símbolo +:
"ABC" + "DEF" = "ABCDEF"

O polimorfismo e a sobrecarga são habilidades únicas em linguagens


orientadas ao objeto.

C É UM SUBCONJUNTO DE C++

C++ é uma linguagem derivada da linguagem C. O conjunto de instruções que


faz parte da linguagem C também é parte de C++.
Os elementos principais que foram adicionados à linguagem C para dar
origem a C++ consistem nas classes, nos objetos e na idéia de programação
orientada ao objeto.
Se você já sabe programar em C, conhece a maior parte da sintaxe de
C++ e tem pouco a aprender.
C++ é rica em recursos que atendem às limitações impostas pelas
linguagens procedurais.
Página em branco
MAKRON
Books
Capítulo 8

CLASSES E OBJETOS

8.Classese eobjetos
Classes Objetos
O fundamento central da programação C++ são as classes, um tipo de dado
que caracteriza a programação orientada ao objeto. As classes qualificam C++
como um progresso sobre C.
As classes são muito mais poderosas que as estruturas e uniões. Elas
são formadas por dados-membro e funções-membro que operam esses dados.
Por causa deste formato, surgiu a afirmação “uma classe é completa”.

UMA CLASSE SIMPLES

O nosso primeiro exemplo cria uma classe para conter os dados de um


retângulo, calcular e imprimir a sua área.
#include <iostream.h>

class Retangulo //Define a classe


{
private:
int bas, alt; //Dados-membro

1
2 Treinamento em Linguagem C++ Cap. 8

public:
//Função-membro para inicializar os dados
void init(int b,int h) { bas=b;alt=h;}
void printdata()//Função-membro, imprime a área
{
cout << "\nBase = "<<bas<<" Altura = "<<alt;
cout << "\nÁrea = " << (bas*alt);
}
};

void main()
{
Retangulo x,y; //Declara dois objetos

x.init(5,3); //Chama função-membro que inicializa


y.init(10,6); // os dados dos dois objetos

x.printdata(); //Chama função-membro que imprime a


// área do retângulo x

y.printdata(); //Chama função-membro que imprime a


// área do retângulo y
}

A classe Retangulo é composta por dois itens de dados e duas funções.


Estas funções são o único meio de acesso aos itens de dados. A primeira atribui
valores aos itens de dados, a segunda calcula e imprime a área do retângulo.
Agrupar dados e funções numa mesma entidade é o fundamento da
programação orientada ao objeto.
Cap. 8 Classes e objetos 3

OBJETOS

Enquanto uma instância de uma estrutura ou de uma união é chamada variável,


uma instância de uma classe é chamada objeto. Em outras palavras, uma
variável de um tipo classe é dita um objeto.

DEFININDO A CLASSE

Antes de criar um objeto de uma certa classe, é necessário que ela esteja definida.
Este conceito é o mesmo que usamos com estruturas e uniões: antes de criar
uma variável de um tipo estrutura, é necessário definir a estrutura. As instruções
que definem a classe Retangulo são as seguintes:
class Retangulo //Define a classe
{
private:
int bas, alt; //Dados-membro

public:
//Função-membro para inicializar os dados
void init(int b,int h) { bas=b;alt=h;}
void printdata()//Função-membro, imprime a área
{
cout << "\nBase = "<<bas<<" Altura = "<<alt;
cout << "\nÁrea = " << (bas*alt);
}
};

Uma definição de classe sempre começa pela palavra-chave class


seguida do nome da classe (Retangulo, neste exemplo), de seu corpo delimitado
por chaves e finalizado com um ponto-e-vírgula.
4 Treinamento em Linguagem C++ Cap. 8

MEMBROS PRIVADOS E MEMBROS PÚBLICOS

O corpo da definição da nossa classe contém as palavras private e public


seguidas de dois-pontos. Elas especificam a visibilidade dos membros.
Os membros definidos após private: são chamados de parte privada da
classe e podem ser acessados pela classe inteira, mas não fora dela. Um membro
privado não pode ser acessado por meio de um objeto. Este conceito é equiva-
lente à relação existente entre uma função e as suas variáveis automáticas; elas
não estarão visíveis fora da função.
Geralmente, na parte privada da classe são colocados os membros que
conterão dados. Dizemos então que esses dados estarão “escondidos”.
O conceito de “esconder” dados significa que eles estarão confinados
dentro da classe e não poderão ser alterados, por descuido, por funções que
não pertençam à classe.
Se uma função-membro é declarada private, somente outras funções-
membro da mesma classe poderão acessá-la.
A seção pública da classe é formada pelos membros definidos após
public: e pode ser acessada por qualquer função-membro e por qualquer outra
função do programa onde um objeto foi declarado.
Os membros públicos fazem a interface entre o programador e a classe.
Geralmente, na parte pública de uma classe são colocadas as funções
que irão operar os dados.

FUNÇÕES-MEMBRO

Funções-membro, também chamadas métodos, são as que estão dentro da classe.


Na classe Retangulo, definimos duas funções-membro: init() e printdata().
Estas funções executam operações comuns em classes: atribuem valores
aos dados e imprimem certos resultados.
Cap. 8 Classes e objetos 5

A função init() aceita dois parâmetros e atribui tais valores aos dados-
membro. A função printdata() imprime os dados do retângulo e calcula e
imprime a sua área.

FUNÇÕES-MEMBRO inline

Observe que ambas as funções têm seu código definido dentro da definição da
classe. Funções-membro de código definido dentro da classe são criadas como
inline por default. Devido ao fato de nossas funções serem muito pequenas, é
melhor declará-las assim para evitar o trabalho extra de chamar uma função.
Você verificará logo que é possível definir uma função membro fora da
classe, contanto que o seu protótipo seja escrito dentro da definição da classe.
Uma função-membro inline pode ser definida fora da classe por meio
da palavra inline, ou dentro dela, como em nosso exemplo. No segundo caso,
não é necessário usar o especificador inline.

CRIANDO OBJETOS

Após a definição da classe Retangulo, podemos declarar uma ou mais instâncias


desse tipo. Uma instância de uma classe é chamada objeto, como já mencionado
anteriormente.
A primeira instrução de main(),
Retangulo x,y;

declara dois objetos, x e y, da classe Retangulo.


Observe que a definição da classe não cria nenhum objeto, somente
descreve como ela será. Este conceito é idêntico ao das estruturas que, quando
definidas, não criam nenhuma variável.
Declarar um objeto é similar a declarar uma variável de qualquer tipo;
o espaço de memória é reservado.
6 Treinamento em Linguagem C++ Cap. 8

CHAMADA A FUNÇÕES-MEMBRO

Os membros públicos de um objeto são acessados por meio do operador ponto.


A sintaxe é similar à de acesso aos membros de uma estrutura.
As funções-membro só podem ser chamadas quando associadas ao
objeto específico pelo operador ponto. Uma função-membro age sobre um
objeto particular e não sobre a classe como um todo.
A instrução
x.init(5,3);

executa a função-membro init() do objeto x. Esta função atribui o valor 5 à


variável bas e 3 à variável alt do objeto x.
A instrução
y.init(10,6);

executa a mesma tarefa sobre o objeto y.


De forma similar, as instruções
x.printdata();

y.printdata();

imprimem os dados de cada retângulo e as suas áreas.

MENSAGEM

A instrução de chamada a uma função-membro é conhecida pelo nome mensa-


gem. Dizemos que a instrução
x.printdata();

envia uma mensagem ao objeto x solicitando que ele imprima os dados e a área
do retângulo.
Cap. 8 Classes e objetos 7

FUNÇÕES-MEMBRO DEFINIDAS FORA DA CLASSE

Funções-membro podem ser escritas em algum lugar do programa fora da


definição da classe. Entretanto, os protótipos destas funções devem estar
presentes no corpo da definição da classe, o que informa ao compilador que
elas fazem parte da classe, mas foram definidas fora dela.
Observe um exemplo:
//DATA.CPP
//Mostra classe com funções definidas fora dela
#include <iostream.h>

class data //Definição da classe


{
private:
int dia, mes, ano;
public:
int bissexto() //Função inline
{ return (ano%4==0 && ano%100 || ano%400==0);}
void initdata(int d,int m,int a); //Int dados
void printdata(); //Imprime por extenso
void printsigno(); //Imprime signo
void printbissexto();//Imprime bissexto
};

void data::initdata(int d,int m,int a)


{
int dmes[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
ano = a > 0 ? a:1;
dmes[2]=dmes[2]+bissexto();
mes = m>=1 && m<=12 ? m:1;
dia = d>=1 && d<=dmes[mes] ? d:1;
}
8 Treinamento em Linguagem C++ Cap. 8

void data::printdata()
{
char nome[13][10] =
{"zero","Janeiro","Fevereiro","Março","Abril",
"Maio","Junho","Julho","Agosto","Setembro",
"Outubro","Novembro","Dezembro"};

cout <<"\n\n"<<dia<<" de "<<nome[mes]<< " de " << ano;


}

void data::printsigno()
{
char nsigno[14][12] =
{"zero","Capricórnio","Aquário","Peixes","Áries",
"Touro","Gêmeos","Câncer","Leão","Virgem",
"Libra","Escorpião","Sagitário","Capricórnio"};

int sig[] = {0,20,19,20,20,20,20,21,22,22,22,21,21};

if(dia < sig[mes])


cout << "\nSigno: " << nsigno[mes];
else
cout << "\nSigno: " << nsigno[mes+1];
}

void data::printbissexto()
{
if(bissexto()) cout << "\nAno bissexto.";
else cout << "\nAno não bissexto.";
}

void main()
{
data x,y,z;
Cap. 8 Classes e objetos 9

x.initdata(14,6,1992);
y.initdata(12,1,1976);
z.initdata(30,7,1978);

x.printdata();x.printsigno(); x.printbissexto();
y.printdata();y.printsigno(); y.printbissexto();
z.printdata();z.printsigno(); z.printbissexto();

Eis a saída:
14 de junho de 1992
Signo: Gêmeos
Ano bissexto.

12 de janeiro de 1976
Signo: Capricórnio
Ano bissexto.

30 de julho de 1978
Signo: Leão
Ano não bissexto.

O programa cria uma classe para imprimir uma data por extenso, o
signo do horóscopo e se o ano é ou não bissexto.
A classe data mostra funções-membro definidas fora dela. As declara-
ções destas funções devem estar presentes na definição da classe. A única
função-membro da classe data definida dentro dela é bissexto(), criada como
inline.
As declarações
void initdata(int d,int m,int a); //Int dados
void printdata(); //Imprime por extenso
void printsigno(); //Imprime signo
void printbissexto();//Imprime bissexto
10 Treinamento em Linguagem C++ Cap. 8

informam ao compilador que estas funções são membros da classe, mas serão
definidas fora dela, em algum lugar do programa. No nosso exemplo, as funções
foram escritas imediatamente após a definição da classe.
As definições destas funções têm uma sintaxe um pouco diferente das
que já conhecemos. Vamos analisar uma delas.
void data::initdata(int d,int m,int a)
{
int dmes[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
ano = a > 0 ? a:1;
dmes[2]=dmes[2]+bissexto();
mes = m>=1 && m<=12 ? m:1;
dia = d>=1 && d<=dmes[mes] ? d:1;
}

Observe que o nome da função é precedido pelo nome da classe, data,


e por um símbolo formado por dois caracteres de dois-pontos (::). Este símbolo
é chamado operador de resolução de escopo. Este operador é o meio pelo qual
informamos ao compilador que a função sendo definida está associada àquela
classe particular. No nosso exemplo, o operador de resolução de escopo informa
que a função initdata() é membro da classe data.

nome da função-membro

tipo de retorno

void data::initdata(int d, int m, int a)

argumentos
nome da classe da qual a
função é membro

CONSTRUTORES

A atribuição de valores aos dados-membro de um objeto da classe data é feita


por meio da chamada a função-membro initdata(). Muitas vezes, entretanto, é
Cap. 8 Classes e objetos 11

conveniente inicializar um objeto quando ele é criado, sem necessidade de


efetuar uma chamada a uma função-membro em separado.
A inicialização automática de um objeto é conseguida por meio da
chamada a uma função-membro especial quando o objeto é criado. Esta função-
membro é conhecida como construtor.
Um construtor é uma função-membro que tem o mesmo nome da classe
e é executada automaticamente toda vez que um objeto é criado.
Como exemplo, modificaremos o programa data.cpp de modo a incluir
um construtor.
//DATA.CPP
//Mostra classe com uma função construtora

#include <iostream.h>

class data //Definição da classe


{
private:
int dia, mes, ano;
public:
int bissexto() //Função inline
{ return (ano%4==0 && ano%100 || ano%400==0);}

data(int d,int m,int a); //Construtor

void initdata(int d,int m,int a);//Verifica data


void printdata(); //Imprime por extenso
void printsigno(); //Imprime signo
void printbissexto();//Imprime bissexto
};

data::data(int d,int m,int a) //Construtor sem tipo


{
initdata(d,m,a);
}
12 Treinamento em Linguagem C++ Cap. 8

void data::initdata(int d,int m,int a)


{
int dmes[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
ano = a > 0 ? a:1;
dmes[2]=dmes[2]+bissexto();
mes = m>=1 && m<=12 ? m:1;
dia = d>=1 && d<=dmes[mes] ? d:1;
}
void data::printdata()
{
char nome[13][10] =
{"zero","Janeiro","Fevereiro","Março","Abril",
"Maio","Junho","Julho","Agosto","Setembro",
"Outubro","Novembro","Dezembro"};

cout <<"\n\n"<<dia<<" de "<<nome[mes]<< " de " << ano;


}

void data::printsigno()
{
char nsigno[14][12] =
{"zero","Capricórnio","Aquário","Peixes","Áries",
"Touro","Gêmeos","Câncer","Leão","Virgem",
"Libra","Escorpião","Sagitário","Capricórnio"};

int sig[] = {0,20,19,20,20,20,20,21,22,22,22,21,21};

if(dia < sig[mes])


cout << "\nSigno: " << nsigno[mes];
else
cout << "\nSigno: " << nsigno[mes+1];
}

void data::printbissexto()
{
Cap. 8 Classes e objetos 13

if(bissexto()) cout << "\nAno bissexto.";


else cout << "\nAno não bissexto.";
}

void main()
{
data x(14,6,1992),
y(12,1,1976),
z(30,7,1978);

x.printdata();x.printsigno(); x.printbissexto();
y.printdata();y.printsigno(); y.printbissexto();
z.printdata();z.printsigno(); z.printbissexto();

Eis a saída:
14 de junho de 1992
Signo: Gêmeos
Ano bissexto.

12 de janeiro de 1976
Signo: Capricórnio
Ano bissexto.

30 de julho de 1978
Signo: Leão
Ano não bissexto.

INICIALIZAÇÃO AUTOMÁTICA

Quando um objeto da classe data é criado, seus dados-membro são inicializados


simultaneamente com os dados colocados entre parênteses e separados por
vírgulas logo após o nome do objeto, na instrução de sua declaração.
14 Treinamento em Linguagem C++ Cap. 8

data x(14,6,1992),
y(12,1,1976),
z(30,7,1978);

O construtor data() é executado uma vez para cada objeto criado; os


dados da inicialização são passados como argumentos.

VALOR DE RETORNO DE UM CONSTRUTOR

Observe que um construtor não deve retornar nenhum valor. O motivo é ele
ser chamado diretamente pelo sistema e portanto não há como recuperar um
valor de retorno. Um valor de retorno não faria sentido. Declaramos o constru-
tor, sem nenhum tipo, na instrução
data(int d,int m,int a); //Construtor

Se qualquer nome de tipo for escrito precedendo o nome da função


construtora, o compilador apresentará um erro.

CONSTRUTORES CHAMANDO FUNÇÕES-MEMBRO

Um construtor pode perfeitamente chamar uma função-membro. O seu código


é igual ao de qualquer outra função, exceto pela falta de tipo.
data::data(int d,int m,int a)
{
initdata(d,m,a);
}
Cap. 8 Classes e objetos 15

SOBRECARGA DE CONSTRUTORES

Suponhamos que você queira criar um novo construtor para a nossa classe data
que solicita a data ao usuário toda vez que um objeto é criado, mas gostaria de
manter o construtor anterior para objetos inicializados automaticamente.
Para solucionar este problema, podemos criar funções construtoras
sobrecarregadas, como mostra o programa a seguir.
//DATA.CPP
//Mostra classe com uma função construtora

#include <iostream.h>

class data //Definição da classe


{
private:
int dia, mes, ano;
public:
int bissexto() //Função inline
{ return (ano%4==0 && ano%100 || ano%400==0);}

data(int d,int m,int a); //Construtor


data(); //Construtor sobrecarregado
void initdata(int d,int m,int a);//Verifica data
void printdata(); //Imprime por extenso
void printsigno(); //Imprime signo
void printbissexto();//Imprime bissexto
};

data::data(int d,int m,int a)


{
initdata(d,m,a);
}
16 Treinamento em Linguagem C++ Cap. 8

data::data()
{
int d,m,a;
cout << "\nDia: "; cin >> d;
cout << "\nMês: "; cin >> m;
cout << "\nAno: "; cin >> a;
initdata(d,m,a);
}

void data::initdata(int d,int m,int a)


{
int dmes[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
ano = a > 0 ? a:1;
dmes[2]=dmes[2]+bissexto();
mes = m>=1 && m<=12 ? m:1;
dia = d>=1 && d<=dmes[mes] ? d:1;
}
void data::printdata()
{
char nome[13][10] =
{"zero","Janeiro","Fevereiro","Março","Abril",
"Maio","Junho","Julho","Agosto","Setembro",
"Outubro","Novembro","Dezembro"};

cout <<"\n\n"<<dia<<" de "<<nome[mes]<< " de " << ano;


}

void data::printsigno()
{
char nsigno[14][12] =
{"zero","Capricórnio","Aquário","Peixes",""Áries",
"Touro","Gêmeos","Câncer","Leão","Virgem",
Cap. 8 Classes e objetos 17

"Libra","Escorpião","Sagitário","Capricórnio"};

int sig[] = {0,20,19,20,20,20,20,21,22,22,22,21,21};

if(dia < sig[mes])


cout << "\nSigno: " << nsigno[mes];
else
cout << "\nSigno: " << nsigno[mes+1];
}

void data::printbissexto()
{
if(bissexto()) cout << "\nAno bissexto.";
else cout << "\nAno não bissexto.";
}

void main()
{
data x(14,6,1992),y(12,1,1976), z;

x.printdata();x.printsigno(); x.printbissexto();
y.printdata();y.printsigno(); y.printbissexto();
z.printdata();z.printsigno(); z.printbissexto();

Eis uma execução:


Dia: 7
Mês: 4
Ano: 1949

14 de junho de 1992
Signo: Gêmeos
Ano bissexto.

12 de janeiro de 1976
18 Treinamento em Linguagem C++ Cap. 8

Signo: Capricórnio
Ano bissexto.

7 de abril de 1949
Signo: Áries
Ano não bissexto.

FUNÇÕES-MEMBRO VERSUS DADOS PÚBLICOS

Você viu, nos exemplos anteriores, que os dados de uma classe são sempre
acessados por meio de funções-membro. Por que motivo não os declaramos
como membros públicos, o que permitiria o acesso direto?
Acessar dados por meio de funções-membro permite a validação dos
valores, o que garante que seu objeto nunca conterá valores inválidos. Por
exemplo, ao imprimir o conteúdo de um objeto data, estamos seguros de não
imprimir valores sem sentido.
O mais importante é que quando o acesso aos dados é feito por
funções-membro, podemos facilmente alterar a implementação da classe sem
que a mudança afete qualquer programa que faz seu uso. Por exemplo,
poderíamos alterar a classe data para que o dia e o mês fossem do tipo char em
vez de int para poupar memória.

OBJETOS const

O qualificador const na declaração de um objeto indica que o objeto é uma


constante e nenhum de seus membros de dados podem ser alterados. Por
exemplo,
const data natal(25,12,1994);

Quando um objeto constante é declarado, o compilador proíbe a ele o


acesso a qualquer função-membro, pois não consegue identificar quais funções-
Cap. 8 Classes e objetos 19

membro alteram os seus dados. Entretanto, há um modo de informar ao


compilador que uma função-membro não altera nenhum dado do objeto e que
poderemos chamá-la por meio de um objeto constante.
Ao colocar a palavra const após os parênteses que envolvem a lista de
parâmetros da função, estaremos indicando que a função não modifica o objeto.
Desta forma, é possível acessá-la.
class data //Definição da classe
{
private:
int dia, mes, ano;
public:
int bissexto() const
{ return (ano%4==0 && ano%100 || ano%400==0);}

data(int d,int m,int a);


data();
void initdata(int d,int m,int a);

void printdata() const; //Função constante


void printsigno() const; //Função constante
void printbissexto() const;//Função constante
};

Não basta somente colocar o qualificador const na declaração da


função, é necessário que ele apareça na definição da função. Por exemplo, a
função printdata() deve ser definida do seguinte modo:
void data::printdata() const
{
char nome[13][10] =
{"zero","Janeiro","Fevereiro","Março","Abril",
"Maio","Junho","Julho","Agosto","Setembro",
"Outubro","Novembro","Dezembro"};

cout <<"\n\n"<<dia<<" de "<<nome[mes]<< " de " << ano;


}
20 Treinamento em Linguagem C++ Cap. 8

As funções de impressão e a função bissexto() não modificam o objeto,


portanto podem ser declaradas como constantes. O acesso à função initdata()
não será permitido a objetos constantes, pois ela não pode ser declarada
como const.
Não podemos declarar uma função const se ela altera qualquer dado
do objeto. Se esta declaração ocorrer, o compilador apresentará um erro.
É importante declarar nossas funções-membro const sempre que pos-
sível. Isto permitirá ao programador utilizar a classe para declarar objetos
constantes.

OBJETOS COMO ARGUMENTOS DE FUNÇÕES-MEMBRO

Nosso próximo exemplo mostra como uma função-membro pode receber


objetos como argumentos.
//ADD_OBJ.CPP
//Mostra objetos como argumento de funções-membro
//Mostra construtor vazio
#include <iostream.h>
#include <iomanip.h>

class venda
{
private:
int npecas;
float preco;
public:
venda() {} //Construtor sem args
venda(int np, float p) //Construtor com args
{ npecas=np; preco=p;}

void getvenda()
{
Cap. 8 Classes e objetos 21

cout << "Insira No.Peças: "; cin >> npecas;


cout << "Insira Preço : "; cin >> preco;
}

void printvenda() const;

void add_venda(venda v1, venda v2)


{
npecas= v1.npecas + v2.npecas;
preco = v1.preco + v2.preco;
}
};

void venda::printvenda() const


{
cout << setiosflags(ios::fixed) //não notação científica
<< setiosflags(ios::showpoint) //ponto decimal
<< setprecision(2) //duas casas
<< setw(10) << npecas; //tamanho 10
cout << setw(10) << preco << "\n";
}

void main()
{
venda A(58,12734.53),B,Total;

B.getvenda();
Total.add_venda(A,B);

cout << "Venda A........."; A.printvenda();


cout << "Venda B........."; B.printvenda();
cout << "Totais..........";Total.printvenda();
}

Eis a saída:
Insira No.Peças: 10
22 Treinamento em Linguagem C++ Cap. 8

Insira Preço : 987.45

Venda A......... 58 12734.53


Venda B......... 10 987.45
Totais.......... 68 13721.98

CONSTRUTORES DE CORPO VAZIO

Muitas vezes, você pode querer ter um construtor para inicializar os dados e
também definir algum objeto sem que este seja inicializado. Para tanto, você
deve criar um construtor de corpo vazio. Fizemos isto na definição:
venda() {} //Construtor sem args

O corpo vazio indica que a função não faz nada e pertence à classe
somente para que o outro construtor não seja executado automaticamente.

OBJETOS COMO ARGUMENTOS

Para obter o preço total e o total de peças, criamos a função-membro add_ven-


da() que recebe os dois objetos a serem somados como argumento. A sintaxe de
objetos como argumentos é a mesma da usada com argumentos de tipos simples.
Observe que, para a função add_venda() acessar os membros dos
objetos passados como argumento, é necessário o uso do operador ponto:
v1.pecas, v1.preco.
Examinando melhor, encontramos um fato importante sobre as fun-
ções-membro — “Quando uma função-membro é chamada, ela só tem acesso
aos dados de um único objeto: o objeto do qual a função é membro”.
Na instrução
Total.add_venda(A,B);
Cap. 8 Classes e objetos 23

a função add_venda() tem acesso direto ao objeto Total por ser uma função-
membro dele. Poderá acessar também A e B, pelo fato de eles terem sido
enviados como argumentos. Quando as variáveis npecas e preco são referen-
ciadas, o acesso indica Total.npecas e Total.preco.
Observe que o resultado da soma não é retornado pela função add_ven-
da(). Esta função é void. A soma é armazenada no objeto Total.

FORMATANDO NÚMEROS

O programa add_obj.cpp imprime uma tabela de números bem organizados e


formatados apropriadamente.
Desejamos imprimir o valor do preço com um ponto e duas casas
decimais. Já estudamos o manipulador setw(), usado para definir o tamanho
do campo.
Para formatar números com casas decimais, precisamos usar vários
outros manipuladores. A função printvenda() imprime o preço num campo do
tamanho de 10 caracteres e duas casas decimais após o ponto.
void venda::printvenda() const
{
cout << setiosflags(ios::fixed) //não notação científica
<< setiosflags(ios::showpoint) //ponto decimal
<< setprecision(2) //duas casas
<< setw(10) << npecas; //tamanho 10
cout << setw(10) << preco << "\n";
}

O conjunto de bits de um long int da classe ios determina o formato


de impressão de um número. Utilizamos os flags fixed e showpoint da classe
ios. Para selecionar um deles, usamos o manipulador setiosflags seguido do
nome do flag como argumento. O nome do flag deve ser precedido do nome
da classe, ios, e do operador de resolução de escopo (::). Neste momento, não
é necessário que você compreenda detalhadamente esta sintaxe para utilizar
esses manipuladores.
24 Treinamento em Linguagem C++ Cap. 8

FUNÇÕES QUE RETORNAM UM OBJETO

Modificaremos o programa ADD_OBJ.CPP para produzir uma classe com uma


função-membro que retorna um objeto.
//ADD_OBJR.CPP
//Mostra funções-membro que retornam um objeto
#include <iostream.h>
#include <iomanip.h>
class venda
{
private:
int npecas;
float preco;
public:
venda() {} //Construtor sem args
venda(int np, float p) //Construtor com args
{ npecas=np; preco=p;}

void getvenda()
{
cout << "\nInsira No.Peças: "; cin >> npecas;
cout << "\nInsira Preço : "; cin >> preco;
}

void printvenda() const;

venda add_venda(venda v) const;


};

venda venda::add_venda(venda v) const


{
venda temp; //Variável temporária
temp.npecas = npecas + v.npecas;
temp.preco = preco + v.preco;
Cap. 8 Classes e objetos 25

return temp;
}

void venda::printvenda() const


{
cout << setiosflags(ios::fixed) //não notação científica
<< setiosflags(ios::showpoint) //ponto decimal
<< setprecision(2) //duas casas
<< setw(10) << npecas; //tamanho 10

cout << setw(10) << preco << "\n";


}

void main()
{
venda A(58,1234.5),B, Total;

B.getvenda();

Total = A.add_venda(B); //Total = A + B

cout << "Venda A........."; A.printvenda();


cout << "Venda B........."; B.printvenda();
cout << "Totais..........";Total.printvenda();
}

O programa ADD_OBJR.CPP produz o mesmo efeito de execução que


o anterior. A diferença está na forma como a função add_venda() trabalha com
objetos. No programa original, dois objetos são passados como argumentos e o
resultado é armazenado no objeto de que add_venda() é membro. Na nova
versão, o objeto B é passado como argumento para a função-membro de A,
add_venda(). Seus valores são totalizados num objeto temporário que é retor-
nado via comando return. Na função main(), o resultado é atribuído a Total.
Observe que agora podemos declarar a função add_venda() como
constante.
26 Treinamento em Linguagem C++ Cap. 8

ATRIBUIÇÕES ENTRE OBJETOS

Um objeto pode ser atribuído a outro de mesma classe por meio de uma
atribuição simples:
Total = A.add_venda(B); //Total = A + B

No próximo capítulo, veremos como usar o operador aritmético de


soma (+) para escrever uma expressão mais natural como:
Total = A + B;

DADOS-MEMBRO static

Já sabemos que cada objeto contém seus próprios dados em separado. Muitas
vezes, é necessário que todos os objetos de uma mesma classe acessem um
mesmo item de dado. Este item faria o mesmo papel de uma variável externa,
visível a todos os objetos daquela classe.
Quando um dado membro é declarado como static, é criado um único
item para a classe como um todo, não importando o número de objetos
declarados. A informação de um membro static é compartilhada por todos os
objetos da mesma classe.
Um membro static tem características similares às variáveis estáticas
normais. É visível somente dentro da classe e é destruído somente quando o
programa terminar.
Como exemplo, suponha que um objeto necessite saber quantos objetos
de sua classe estão presentes.
//STATDATA.CPP
//Mostra dados static em classes
#include <iostream.h>

class REC
{
Cap. 8 Classes e objetos 27

private:
static int n; //dado static
public:
REC() { n++; } //Construtor
int getrec() const { return n;}
};

void main()
{
REC r1,r2,r3;

cout << "\nNúmero de objetos: " << r1.getrec();


cout << "\nNúmero de objetos: " << r2.getrec();
cout << "\nNúmero de objetos: " << r3.getrec();
}

Eis a saída:
Número de objetos: 3
Número de objetos: 3
Número de objetos: 3

A classe REC tem um dado-membro do tipo static int. O construtor


incrementa n toda vez que um objeto é declarado. Em main(), criamos três
objetos da classe REC, o construtor é chamado três vezes e n é incrementada
três vezes.
Se tivéssemos usado uma variável automática no lugar de static, cada
construtor incrementaria sua própria variável n uma vez e a saída seria 1 nas
três impressões.

DESTRUTORES

Nós já sabemos que o construtor é chamado automaticamente toda vez que um


objeto é declarado. Há outra função-membro especial que é chamada automa-
28 Treinamento em Linguagem C++ Cap. 8

ticamente toda vez que um objeto é destruído (liberado da memória). Esta função
é chamada destrutor e tem o mesmo nome da classe precedido de um til (~).
Da mesma forma que um construtor, os destrutores não podem ter valor
de retorno. O destrutor também não pode receber argumentos nem pode ser
chamado explicitamente pelo programador.
Como exemplo, acrescentaremos um destrutor à classe REC que decre-
menta o contador de objetos sempre que um objeto é liberado da memória.
//DESTRUTOR.CPP
//Mostra uma função destrutora
#include <iostream.h>

class REC
{
private:
static int n; //dado static
public:
REC() { n++; } //Construtor
~REC() { n--; } //Destrutor
int getrec() const { return n;}
};

void main()
{
REC r1,r2,r3;

cout << "\nNúmero de objetos: " << r1.getrec();


{
REC r4,r5;
cout << "\nNúmero de objetos: " << r1.getrec();
}
cout << "\nNúmero de objetos: " << r1.getrec();
}
Cap. 8 Classes e objetos 29

Eis a saída:
Número de objetos: 3
Número de objetos: 5
Número de objetos: 3

MEMBROS static PÚBLICOS

Você pode declarar um membro static como public. Por exemplo:


class moeda
{
private:
float CR;
public:
static float US;
moeda(float x) { CR = x;}
moeda()
{ cout << "\nDigite CR$: "; cin >> CR;}
float usdolar() { return CR/US; }
};

O membro static pode ser acessado do seguinte modo:


void main()
{
moeda a(5345.43), b(358.2);

a.US = 920.3;

cout << "\nValor em US$ de a: " << a.usdolar();


cout << "\nValor em US$ de b: " << a.usdolar();
}

Entretanto, esta sintaxe é enganosa, pois sugere que somente o valor


do dólar do objeto a estaria sendo alterado, quando na realidade o valor do
dólar de todos os objetos da classe moeda está sendo alterado. A sintaxe mais
usada para se referenciar a um membro static público é a seguinte:
30 Treinamento em Linguagem C++ Cap. 8

void main()
{
moeda::US = 920.3;
.
.
.
}

Esta sintaxe é formada pelo nome da classe seguido do operador :: e


pode ser usada mesmo se nenhum objeto da classe moeda tiver sido declarado.
Um membro static é criado na definição da classe e existe independen-
temente de qualquer objeto.
Observe que um membro static não pode ser inicializado por um
construtor da classe, visto que o construtor é chamado toda vez que um objeto
é criado.

FUNÇÕES-MEMBRO static

Funções-membro static têm caraterísticas similares aos membros de dados


static. São utilizadas para implementar recursos comuns a todos os objetos da
classe.
Funções-membro static agem independentemente de qualquer objeto
declarado. Conseqüentemente, estas funções não podem acessar nenhum mem-
bro não-static da classe.

Funções-membro static acessam somente membros static da classe.

Funções-membro static podem ser chamadas usando a mesma sintaxe


utilizada para acessar dados-membro static.
class moeda
{
private:
Cap. 8 Classes e objetos 31

static float US;


public:
static void usvalor()
{ cout << "\nDigite o valor do dólar: ";
cin >> US;
}

.
.
.
};

void main()
{
moeda x, y, z;

moeda::usvalor();
.
.
.
}

ESTRUTURAS E CLASSES

Neste livro, decidimos utilizar estruturas para agrupar dados e classes para
agrupar dados e funções. Na verdade, você pode usar estruturas exatamente
como usa classes. Há somente uma diferença entre estruturas e classes: os
membros de uma classe são privados por default e os de uma estrutura são
públicos por default.
O formato que usamos para classes é o seguinte:
class facil
{
32 Treinamento em Linguagem C++ Cap. 8

private:
int n;
public:
void func();
};

Visto que dados privados são o default, a palavra private é desneces-


sária. Poderíamos ter escrito:
class facil
{
int n;
public:
void func();
};

Você poderia utilizar uma estrutura para executar a mesma tarefa desta
classe, dispensando a palavra public e colocando os membros públicos antes
dos privados.
struct facil
{
void func();
private:
int n;
};

Entretanto, a maioria dos programadores C++ utiliza estruturas somen-


te para dados, e classes para dados e funções.

MATRIZES DE OBJETOS

Um objeto pode conter membros que sejam matrizes; exemplos anteriores já


mostraram este uso. Podemos também criar uma matriz em que cada elemento
seja um objeto. Como exemplo, vamos modificar a função main() do programa
ADD_OBJR.CPP para criar uma matriz de objetos venda.
Cap. 8 Classes e objetos 33

const MAX = 100; //Número máximo de objetos

void main()
{
venda A[MAX];
venda Total(0,0);
int i=0;
char resp;

do
{
A[i].getvenda();
Total = A[i++].add_venda(Total);
cout << "\nAdicionar mais um objeto (s/n)? ";
cin >> resp;
} while ( resp != ’n’ && i < MAX);

for(int j=0; j<i;j++)


{
cout << "Venda .........."; A[j].printvenda();
}
cout << "Totais.........."; Total.printvenda();
}

Neste programa, podemos usar o número de objetos desejados. Após


cada entrada, o programa pergunta se o usuário deseja adicionar outro objeto.
Se não, ele imprime os valores inseridos e o total e encerra.

CRIANDO A MATRIZ DE OBJETOS

A sintaxe da declaração de uma matriz de objetos é igual à sintaxe da declaração


de qualquer outro tipo de matriz:
venda A[MAX];
34 Treinamento em Linguagem C++ Cap. 8

Quando uma matriz de objetos é declarada, o construtor é chamado para


cada elemento da matriz. No nosso exemplo, a matriz não é inicializada e o
construtor sem argumentos é chamado.
Poderíamos querer inicializar cada elemento da matriz por meio de
uma chamada explícita ao construtor com argumentos.
venda A[MAX] = { venda(50,123.5),
venda(35,86.8) ,
venda(15,38.4)
};

Se não forem inicializados todos os elementos da matriz, o construtor


sem argumentos é chamado para os elementos não-inicializados.
Neste exemplo, o construtor de dois argumentos é chamado para os
três primeiros elementos da matriz, e o construtor sem argumentos é chamado
para o restante.
É possível também misturar inicializações com diferentes construtores
da classe:
venda A[MAX] = { venda(50,123.5),
venda() ,
venda(15,38.4)
};

Aqui o construtor sem argumentos é chamado explicitamente para o


segundo elemento da matriz e implicitamente para o quarto elemento em diante.
Se a classe contiver mais construtores, eles podem também ser utiliza-
dos na inicialização da matriz.

ACESSO A FUNÇÕES-MEMBRO VIA UM ELEMENTO DA MATRIZ

Uma função-membro da classe é acessada de modo similar ao de um membro


de uma estrutura que é elemento de uma matriz.
A[i].getvenda();
Cap. 8 Classes e objetos 35

CRIANDO UM TIPO STRING

Strings em C++ têm uma sintaxe complicada e criam vários problemas para o
programador. Por exemplo, você não pode usar uma instrução perfeitamente
razoável como:
str1 = str2;

para atribuir uma cadeia de caracteres a outra.


Estes problemas podem ser resolvidos definindo o nosso próprio tipo
string, por meio das classes de C++.
//STRCLASS.CPP
//Mostra uma classe string
#include <iostream.h>
#include <string.h>

const MAX=80;

class string
{
private:
char str[MAX];
public:
string() {str[0]=’\0’;}
string(char s[]) {strcpy(str,s);}
string(char ch,int n);
int len() const { return strlen(str); }
void print() const { cout << str; }
};

string::string(char ch,int n)
{
for(int i=0;i<n;i++) str[i]=ch;
str[i]=’\0’;
}
36 Treinamento em Linguagem C++ Cap. 8

void main()
{
string s1("Feliz Aniversário! "),
s2(’=’,18),
s3 = "Estamos na primavera.",
s4,s5;

cout << "\ns1 = "; s1.print();


cout << "\ns2 = "; s2.print();
cout << "\ns3 = "; s3.print();

s4 = "O mundo gira!";

cout << "\ns4 = "; s4.print();

s5 = s4;

cout << "\ns5 = "; s5.print();

cout << "\nTamanho de s1 = " << s1.len();


cout << "\nTamanho de s2 = " << s2.len();
cout << "\nTamanho de s3 = " << s3.len();
cout << "\nTamanho de s4 = " << s4.len();
cout << "\nTamanho de s5 = " << s5.len();
}

Eis a saída:
s1 = Feliz Aniversário!
s2 = ==================
s3 = Estamos na primavera.
s4 = O mundo gira!
s5 = O mundo gira!
Tamanho de s1 = 19
Tamanho de s2 = 18
Tamanho de s3 = 21
Cap. 8 Classes e objetos 37

Tamanho de s4 = 13
Tamanho de s5 = 13

A classe string contém um único item de dado: uma matriz do tipo


char, criada da mesma forma que definimos uma string. A vantagem de montar
uma classe string em relação ao uso da string simples é a possibilidade de
acrescentar várias facilidades e obter um benefício maior no seu uso.
Visto que um objeto pode ser atribuído a outro objeto de mesma classe,
podemos usar instruções simples como:
s5 = s4;

A nossa classe tem três construtores. O primeiro atribui ao primeiro


caractere da cadeia o valor null (’\0’); isto faz com que o tamanho da cadeia
seja zero. Este construtor é chamado por meio de uma instrução como:
string s4;

O segundo construtor atribui uma cadeia de caracteres constante ao


objeto. Ele usa a função de biblioteca strcpy() para copiar a cadeia constante no
item de dado do objeto. Este construtor é chamado por meio de uma instrução
como:
string s1("Feliz Aniversário! ");

Uma forma alternativa para chamar esse construtor é:


string s1 = "Feliz Aniversário! ";

Em qualquer uma das formas, este construtor converte a cadeia cons-


tante num objeto da classe string.
O terceiro construtor preenche a cadeia com um certo número de um
mesmo caractere. Este construtor não pode ser criado como inline, pois tem um
laço for. É chamado por meio de uma instrução como:
string s2(’=’,18);

Outra função-membro, len(), retorna o tamanho da cadeia por meio da


função de biblioteca strlen(). A forma de uso é:
int x = s1.len();

A função-membro print() imprime a cadeia.


s1.print();
38 Treinamento em Linguagem C++ Cap. 8

OBJETOS E A ALOCAÇÃO DE MEMÓRIA

Para cada objeto declarado, é reservado um espaço de memória em separado


para armazenamento de seus dados-membro. Entretanto, todos os objetos da
classe utilizam as mesmas funções. Em outras palavras, é reservado espaço de
memória para armazenar somente uma cópia de cada função-membro de uma
classe particular, independentemente de quantos objetos tiverem sido criados.

Funções-membro são criadas e colocadas na memória somente


uma vez para a classe toda.

Não faria sentido duplicar todas as funções-membro da classe, cada


vez que um novo objeto desta classe fosse criado, pois as funções de cada objeto
são idênticas.
As funções-membro são compartilhadas por todos os objetos da classe.
Os itens de dado, por sua vez, armazenam diferentes valores para cada
objeto, então é necessário que sejam criados novos membros de dados sempre
que um objeto for declarado.

REVISÃO

1. Classes são tipos de dados definidos pelo usuário. Elas podem conter itens
de dados e funções para operar esses itens.
2. Um objeto é uma variável de um tipo classe.
3. As palavras-chave public: e private: especificam a visibilidade dos mem-
bros de uma classe.
4. Membros privados de uma classe são definidos após a palavra private: e
podem ser acessados somente pelos membros da própria classe.
Cap. 8 Classes e objetos 39

5. Membros públicos de uma classe são definidos após a palavra public: e


podem ser acessados tanto por outros membros da classe como por qual-
quer objeto dela.
6. Funções-membro ou métodos são as que pertencem à definição de uma
classe.
7. Funções-membro definidas dentro da definição da classe são criadas como
inline.
8. Os membros públicos de um objeto são acessados por meio do operador
ponto. Este operador liga um objeto a um membro dele.
9. Uma função-membro escrita fora do corpo da classe deve ser declarada
dentro da classe para indicar que pertence àquela classe.
10. Na definição de uma função-membro escrita fora de sua classe, o seu nome
deve ser precedido pelo nome da classe seguido do operador de resolução
de escopo (::).
11. Um construtor é uma função que tem o mesmo nome da classe e é executada
automaticamente sempre que um objeto é declarado.
12. Todo construtor não pode ter um valor de retorno, mas pode receber
qualquer número de argumentos.
13. O uso de funções para acessar dados de um objeto em vez da criação de
dados públicos permite alterar a classe sem a reescrita de códigos de
programas que a utilizam.
14. Declarar um objeto const indica que o objeto é constante e nenhum de seus
membros pode ser alterado.
15. Funções-membro const não podem alterar nenhum dado do objeto.
16. Um objeto const somente pode acessar funções-membro const.
17. Os objetos podem ser atribuídos a outros de mesma classe por meio de uma
instrução de atribuição simples.
18. Os membros static são criados uma única vez para a classe como um todo,
não importando o número de objetos declarados. Estes membros são
visíveis a todos os objetos de sua classe.
40 Treinamento em Linguagem C++ Cap. 8

19. Um destrutor é uma função que tem o mesmo nome da classe precedido
de um til (~) e é executada automaticamente toda vez que um objeto é
destruído (liberado da memória).
20. Todo destrutor não pode ter um valor de retorno, não pode receber nenhum
argumento nem pode ser chamado pelo programador.
21. Um membro static público é acessado quando precedido do nome da classe
seguido do operador (::).
22. Quando uma matriz de objetos é declarada, o construtor é chamado para
cada elemento dela.
23. Para cada objeto declarado, é reservado um espaço de memória suficiente
para armazenar todos os seus itens de dados. Funções-membro são coloca-
das na memória somente uma vez para a classe toda.

EXERCÍCIOS

1. Indique:
(1) Estrutura, (2) Classes
a) agrupam variáveis de mesmo tipo;
b) um item é chamado membro;
c) por default, seus membros são públicos;
d) por default, seus membros são privados;
e) agrupam variáveis de tipos diferentes;
f) agrupam variáveis e funções.
2. Verdadeiro ou falso: A definição de uma classe reserva espaço de memória
para conter todos os seus membros.
3. A finalidade de definir classes é:
a) reservar uma quantidade de memória;
Cap. 8 Classes e objetos 41

b) indicar que o programa é orientado ao objeto;


c) agrupar dados e funções protegendo-os do compilador;
d) descrever o formato de novos tipos de dados do compilador antes
desconhecidos.
4. A relação entre classes e objetos é a mesma existente entre:
a) tipos básicos e variáveis desses tipos;
b) variáveis e funções;
c) uniões e estruturas;
d) estruturas e funções.
5. Qual é a diferença entre o uso de estruturas e o de classes?
6. Na definição de uma classe, os membros designados como privados
a) são acessados por qualquer função do programa;
b) requerem o conhecimento de uma senha;
c) são protegidos de pessoas não-autorizadas;
d) são acessados por qualquer função-membro da classe;
e) são acessados pelos membros públicos da classe.
7. Verdadeiro ou falso: Membros privados definem a atividade interna da
classe e membros públicos, a interface com a classe.
8. Para acessar um membro de um objeto, o operador ponto conecta:
a) a palavra-chave class e o nome do membro;
b) o nome do membro e o nome do objeto;
c) o nome do objeto e o nome do membro;
d) o nome da classe e o nome do objeto.
42 Treinamento em Linguagem C++ Cap. 8

9. Uma função de código definido dentro de uma classe é sempre:


a) float;
b) inline;
c) recursiva;
d) sobrecarregada.
10. Métodos são:
a) classes;
b) dados-membro;
c) funções-membro;
d) chamadas a funções-membro.
11. Mensagens são:
a) classes;
b) dados-membro;
c) funções-membro;
d) chamadas a funções-membro.
12. Construtores são funções
a) que constroem classes;
b) executadas automaticamente quando um objeto é declarado;
c) do tipo int;
d) executadas automaticamente quando um objeto é destruído.
13. O nome de um construtor é sempre o mesmo _____________.
14. Verdadeiro ou falso: Numa classe, pode haver mais de um construtor de
mesmo nome.
15. Verdadeiro ou falso: Um construtor é do tipo retornado por meio do
comando return.
Cap. 8 Classes e objetos 43

16. Verdadeiro ou falso: Um construtor não pode receber argumentos.


17. Destrutores são funções:
a) que destroem classes;
b) executadas automaticamente quando um objeto é declarado;
c) do tipo int;
d) executadas automaticamente quando um objeto é destruído.
18. O nome de um destrutor é sempre o mesmo _____________ precedido por
___________.
19. Verdadeiro ou falso: Numa classe, pode haver mais de um destrutor de
mesmo nome.
20. Verdadeiro ou falso: Um destrutor é do tipo retornado por meio do
comando return.
21. Verdadeiro ou falso: Um destrutor não pode receber argumentos.
22. Assuma a seguinte definição:
class facil
{
private:
int x, y, z;
public:
static int p;
facil() {}
~facil() {}
};

Como podemos atribuir 5 ao membro p?


a) p = 5;
b) facil.p = 5;
c) facil a; a.p = 5;
d) facil::p=5;
44 Treinamento em Linguagem C++ Cap. 8

23. Defina uma classe de nome ALUNO com dados privados para armazenar
o nome do aluno, a série e o grau. Inclua duas funções públicas: uma para
solicitar os dados para o usuário e outra para imprimir os dados.
a) Escreva uma instrução que declare um objeto chamado alu1 da classe
ALUNO.
b) Escreva uma instrução para executar a função que solicite os dados de
entrada para o usuário.
c) Escreva uma instrução para executar a função que imprima os dados
digitados.
d) Inclua um membro static privado para contar o número de alunos
cadastrados.
e) Inclua um construtor que incremente o contador de alunos cadastrados.
f) Inclua um destrutor que decremente o contador de alunos cadastrados.
g) Inclua uma função pública que imprima o número de alunos cadastrados.
h) Escreva a instrução necessária para declarar uma matriz de 10 objetos
da classe ALUNO.
i) Escreva a instrução necessária para preencher o primeiro elemento da
matriz anterior com os dados digitados pelo usuário.
24. Assuma que cls1, cls2 e cls3 sejam objetos de uma mesma classe. Quais das
seguintes instruções são válidas:
a) cls1 = cls2;
b) cls1 = cls2 + cls3;
c) cls1 = cls2 = cls3;
d) cls1 = cls2 + 5;
25. Uma função-membro pode sempre acessar os dados
a) do objeto do qual é membro;
b) da classe da qual é membro;
c) de qualquer objeto da classe da qual é membro;
Cap. 8 Classes e objetos 45

d) da parte pública de sua classe.


26. Se cinco objetos da mesma classe forem declarados, quantas cópias dos itens
de dados da classe serão armazenadas na memória? Quantas cópias de suas
funções-membro?
27. Escreva uma classe para conter três membros do tipo int chamados hora,
mins e segs e chame-a de tempo.
a) Crie um construtor que inicialize os dados com zero e outro construtor
que inicialize os dados com um valor fixo.
b) Crie uma função-membro para solicitar a hora, os minutos e os segundos
para o usuário.
c) Crie uma função-membro para imprimir a hora no formato hh:mm:ss.
d) Crie uma função-membro para adicionar dois objetos da classe tempo
passados como argumentos.
e) Crie uma função-membro que subtraia duas horas e retorne o número
de segundos entre elas. A função recebe dois objetos da classe tempo
passados como argumentos.
28. Escreva uma classe para armazenar dados de um estacionamento. Ela deve
ser capaz de armazenar o número da chapa do carro, a marca, a hora de
entrada e a hora de saída do estacionamento. Utilize dois membros da classe
tempo, definida no exercício anterior, para as horas de entrada e saída.
a) Crie uma função-membro para solicitar os dados de um carro para o
usuário (utilize as funções da classe tempo para pedir a hora de entrada
e saída).
b) Crie uma função-membro para imprimir os dados de um carro.
c) Admita que o estacionamento cobre US$ 1.00 a hora. Escreva uma
função-membro que imprima o valor cobrado. Utilize a função que
subtrai duas horas da classe tempo.
d) Escreva um programa que crie uma matriz de cinco objetos da classe
anterior, solicite os dados dos carros para o usuário e imprima um
relatório dos dados e do valor cobrado.
46 Treinamento em Linguagem C++ Cap. 8

29. Escreva uma classe para descrever um mês do ano. A classe deve ser capaz
de armazenar o nome do mês, a abreviação em três letras, o número de dias
e o número do mês.
a) Crie um construtor para inicializar os dados com zero e outro para
inicializar os dados com um valor fixo.
b) Escreva uma função-membro que receba o número do mês como argu-
mento e retorne o total de dias do ano até aquele mês.
c) Escreva uma função-membro sobrecarregada que receba como argumen-
to o nome do mês em vez do número dele e retorne o mesmo total de dias.
30. Crie uma classe para descrever restaurantes. Os membros devem armazenar
o nome, o endereço, o preço médio e o tipo de comida.
a) Crie um construtor que inicialize os dados com zero e outro construtor
que inicialize os dados com um valor fixo.
b) Crie uma função-membro para solicitar os dados para o usuário.
c) Crie uma função-membro para imprimir os dados de um restaurante.
d) Escreva um programa que crie uma matriz de objetos restaurante e
solicite a entrada dos dados pelo usuário. Em seguida, o programa
pergunta o tipo de comida ao usuário e lista todos os restaurantes que
o oferecem.
MAKRON
Books
Capítulo 9

SOBRECARGA DE OPERADORES

11.Sobrecarga
Sobrecarga de de
operadores
Operadores
A sobrecarga de operadores é uma das mais excitantes propriedades de C++.
A idéia básica que há por trás deste conceito é a de poder transformar expressões
obscuras e complexas em outras mais óbvias e intuitivas. Por exemplo, instru-
ções como
Total.add_venda(A,B);

podem ser trocadas por outras mais legíveis como:


Total = A + B;

Uma das deficiências das linguagens de programação tradicionais


ocorre quando elas precisam lidar com espécies de dados criadas pelo progra-
mador. Esses tipos de dados não podem ser manipulados com os mesmos
operadores que funcionam com os tipos básicos. Por exemplo, você utiliza a
adição (+) e a subtração (-) com tipos internos como int e float, mas não pode
somar automaticamente estruturas e classes.
Sobrecarregar um operador significa redefinir o seu símbolo, de ma-
neira que ele se aplique também a tipos de dados definidos pelo usuário como
classes e estruturas.
No instante em que você encontrar uma limitação pelo modo como os
operadores C++ trabalham, pode mudá-los conforme as suas necessidades, por
meio de sobrecargas.

47
48 Treinamento em Linguagem C++ Cap. 9

Na verdade, o uso de sobrecarga de operadores para criar novas


definições de operadores e o uso de classes para criar novos tipos de variáveis
dão a você a possibilidade de estender C++ e criar uma nova linguagem que
atenda aos seus próprios anseios.

VISÃO GERAL DE SOBRECARGA

Antes de discutir a sobrecarga de operadores, vamos rever o mecanismo de


sobrecarga. Já sabemos que sobrecarregar significa utilizar um mesmo nome
para tarefas parecidas, mas de códigos de programa diferentes. Várias funções
com o mesmo nome podem apresentar diferentes trechos de programa.
A implementação de sobrecargas de operadores é definida por meio de
funções chamadas operadoras. Estas funções podem ser criadas como membros
de classes ou como funções globais, acessíveis a todo o programa.

LIMITAÇÕES

A sobrecarga de operadores permite uma nova implementação da maneira como


um operador funciona. Entretanto, é necessário respeitar a definição original
do operador. Por exemplo, não é possível mudar um operador binário (que
trabalhe com dois operandos) para criar um operador unário (que trabalhe com
um único operando).
Não é permitido também estender a linguagem inventando novos
operadores representados com símbolos novos. Por exemplo, não se pode
inventar uma operação usando o símbolo ##. Este símbolo não é um operador
válido em C++ e você não poderá torná-lo um operador. Devemos limitar-nos
aos operadores existentes.
A definição de operadores deve obedecer à precedência original do
operador. Por exemplo, o operador de multiplicação (*) tem precedência sobre
o de adição (+). Não é possível modificar a precedência de operadores por meio
de sobrecargas.
Cap. 9 Sobrecarga de operadores 49

Nem todos os operadores podem ser sobrecarregados. Os seguintes


operadores não podem ser sobrecarregados: o operador ponto de acesso a
membros (.), o operador de resolução de escopo (::) e o operador condicional
ternário (?:).

OPERADORES UNÁRIOS E BINÁRIOS

Os operadores unários operam sobre um único operando. Exemplos de opera-


dores unários são o de incremento (++), decremento (--) e menos unário (-). Por
outro lado, os operadores binários operam sobre dois operandos (+, -, *, /, >,
+= etc.). Por causa desta diferença, uma atenção especial deve ser dada ao uso
de cada tipo.
A regra básica é a seguinte: Operadores unários, definidos como
funções-membro de classes, não recebem nenhum argumento, enquanto opera-
dores binários recebem um único argumento.

SOBRECARGA DE OPERADORES UNÁRIOS

Dentre os operadores unários existentes em C++, a sobrecarga dos operadores


de incremento e decremento apresenta uma dificuldade maior pelo fato de eles
operarem de forma diferente quando prefixados ou pós-fixados.
Vamos começar com um exemplo de uma classe que define um ponto
de duas coordenadas inteiras e contém uma sobrecarga do operador de incre-
mento prefixado. Eis a listagem:
//PONTO1.CPP
//Mostra sobrecarga do operador ++ prefixado
//Função operadora do tipo void
#include <iostream.h>

class ponto
{
50 Treinamento em Linguagem C++ Cap. 9

private:
int x,y;
public:
ponto(int x1=0,int y1=0) { x=x1;y=y1;}//Construtor

void operator ++() //Fção operadora PREFIXADA


{ ++x;++y;}

void printpt() const //Imprime ponto


{ cout << ’(’ << x << ’,’ << y << ’)’;}
};

void main()
{
ponto p1,p2(2,3),p3; //Declara e inicializa

cout << "\n p1 = "; p1.printpt();//Imprime


cout << "\n p2 = "; p2.printpt();

++p1; //Incrementa p1
++p2; //Incrementa p2

cout << "\n++p1 = "; p1.printpt();//Imprime novamente


cout << "\n++p2 = "; p2.printpt();

p3=p1;

cout << "\n p3 = "; p3.printpt();


}

Eis a saída:
p1 = (0,0)
p2 = (2,3)
++p1 = (1,1)
++p2 = (3,4)
p3 = (1,1)
Cap. 9 Sobrecarga de operadores 51

Neste programa, criamos três objetos da classe ponto. A função cons-


trutora inicializa as coordenadas x e y de um objeto da classe com zeros por
default.

SOBRECARGA DO OPERADOR DE INCREMENTO PREFIXADO

Para sobrecarregar um operador, simplesmente definimos uma função que o


compilador chama quando esse operador é usado. A sintaxe de definição desta
função é um pouco diferente daquela para funções normais. O nome da função
é sempre igual ao símbolo do operador precedido da palavra chave operator.
Definimos esta função da seguinte forma:
void operator ++() { ++x; ++y ;}

O tipo de retorno (void, em nosso caso) vem antes da palavra operator.


A nossa função não retorna nada nem recebe nenhum argumento.
Esta declaração informa ao compilador que esta função-membro deve
ser executada toda vez que o operador ++ prefixado for utilizado com um objeto
da classe ponto como operando.
Se p1 é um objeto da classe ponto, a instrução
++p1;

é equivalente a
p1.operator++();

VALOR DE RETORNO DA FUNÇÃO OPERADORA

A função operator++() da classe ponto tem um defeito sutil. Você descobre o


problema se escrever a seguinte instrução:
p3 = ++p1;
52 Treinamento em Linguagem C++ Cap. 9

O compilador não compilará esta instrução, pois definimos a função


operadora sem valor de retorno. Desta forma, seu valor não pode ser atribuído
a outro objeto. Para resolver o problema, precisamos reescrever a função
operadora para que ela retorne um objeto da classe ponto. A próxima versão
deste programa mostra a modificação.
//PONTO2.CPP
//Mostra sobrecarga do operador ++ prefixado
//Função operadora retorna um objeto
#include <iostream.h>

class ponto
{
private:
int x,y;
public:
ponto(int x1=0,int y1=0){ x=x1;y=y1;}//Construtor

ponto operator ++() //Fção operadora PREFIXADA


{
++x;++y;
ponto temp; //Cria objeto temporário
temp.x = x; temp.y = y;
return temp;
}

void printpt()const //Imprime ponto


{ cout << ’(’ << x << ’,’ << y << ’)’;}
};

void main()
{
ponto p1,p2(2,3),p3; //Declara e inicializa

cout << "\n p1 = "; p1.printpt();//Imprime


cout << "\n p2 = "; p2.printpt();
Cap. 9 Sobrecarga de operadores 53

cout << "\n++p1 = ";

(++p1).printpt();//Incrementa e imprime novamente

cout << "\n++p2 = ";

(++p2).printpt();

p3=++p1; //Incrementa e atribui

cout << "\n p3 = "; p3.printpt();


}

Eis a saída:
p1 = (0,0)
p2 = (2,3)
++p1 = (1,1)
++p2 = (3,4)
p3 = (2,2)

Nesta versão, a função operator++() cria um objeto temporário da classe


ponto para ser usado como valor de retorno.
Com essa mudança, a nossa função operadora satisfaz a sintaxe do
operador ++. Podemos escrever instruções como:
p3=++p1;

ou
(++p1).printpt();
54 Treinamento em Linguagem C++ Cap. 9

OBJETOS TEMPORÁRIOS SEM NOME

No programa PONTO2.CPP criamos um objeto temporário da classe ponto,


chamado temp, com o único propósito de fornecer um valor de retorno à função
operator++().
//Cria um objeto temporário a ser retornado
ponto operator ++() //Fção operadora PREFIXADA
{
++x;++y;
ponto temp; //Cria objeto temporário
temp.x = x; temp.y = y;
return temp;
}

Há um meio mais conveniente de retornar objetos de funções:


//Retorna um objeto temporário sem nome
ponto operator ++() //Fção operadora PREFIXADA
{
++x;++y;
return ponto(x,y);
}

A instrução
return ponto(x,y);

cria um objeto da classe ponto. Este objeto não tem nome e é inicializado pelo
construtor com os valores fornecidos como argumentos (é necessário um
construtor que recebe argumentos). Ele só existe no instante em que for
retornado.
Cap. 9 Sobrecarga de operadores 55

SOBRECARGA DO OPERADOR DE INCREMENTO PÓS-FIXADO

Uma atenção especial deve ser dada para a implementação dos operadores de
incremento (++) e decremento (--) pós-fixados. Como você já sabe, quando estes
operadores aparecem após o operando, o valor do operando é incrementado ou
decrementado após ser usado.
Quando sobrecarregamos os operadores ++ ou --, devemos indicar ao
compilador se a sobrecarga é associada a sua forma prefixada ou pós-fixada.
Para distinguir entre as duas formas, o compilador utiliza o número de
argumentos da função operadora. Se a função operator++() for definida sem nenhum
parâmetro, ela será chamada sempre que a operação prefixada for encontrada.
A segunda e última possibilidade é definir a função operator++(int)
com um único parâmetro do tipo int que a função ignora e que serve somente
para que o compilador possa diferenciar as duas formas de uso. No segundo
caso, a função será chamada sempre que a operação pós-fixada for encontrada.
//PONTO3.CPP
//Mostra sobrecarga do operador ++ PRÉ e PÓS FIXADOS
#include <iostream.h>

class ponto
{
private:
int x,y;
public:
ponto(int x1=0,int y1=0) { x=x1;y=y1;}//Construtor

ponto operator ++() //Fção operadora PREFIXADA


{
++x;++y;
return ponto(x,y);
}

ponto operator ++(int)//Fção operadora PÓS-FIXADA


{
56 Treinamento em Linguagem C++ Cap. 9

++x;++y;
return ponto(x-1,y-1);
}

void printpt()const //Imprime ponto


{ cout << ’(’ << x << ’,’ << y << ’)’;}
};

void main()
{
ponto p1,p2(2,3),p3; //Declara e inicializa

cout << "\n p1 = "; p1.printpt();


cout << "\n p2 = "; p2.printpt();

cout << "\n++p1 = ";


(++p1).printpt(); //Incrementa e depois usa
cout << "\np2++ = ";
(p2++).printpt(); //Usa e depois incrementa
cout << "\n p2 = "; p2.printpt();

p3=p1++; //Atribui e depois incrementa

cout << "\n p3 = "; p3.printpt();


cout << "\n p1 = "; p1.printpt();
}

Eis a saída:
p1 = (0,0)
p2 = (2,3)
++p1 = (1,1)
p2++ = (2,3)
p2 = (3,4)
p3 = (1,1)
p1 = (2,2)
Cap. 9 Sobrecarga de operadores 57

LIMITAÇÕES DOS OPERADORES DE INCREMENTO


SOBRECARREGADOS

A operação pré ou pós-fixada deve ser implementada pelo programador. O


compilador somente executa uma ou outra função operadora, dependendo do
argumento. A nossa função pós-fixada
ponto operator ++(int)//Fção operadora PÓS-FIXADA
{
++x;++y;
return ponto(x-1,y-1);
}

inicializa o objeto retornado com x-1 e y-1 para obter o efeito da situação
pós-fixada em instruções do tipo:
p3=p1++;

Observe que o compilador não reconhece automaticamente a operação


PRÉ ou PÓS-fixada.

SOBRECARGA DE OPERADORES BINÁRIOS

Basicamente, quando sobrecarregamos operadores unários, a função operadora


não tem argumentos (exceto no caso do operador de incremento ou decremento
pós-fixado) e, quando sobrecarregamos operadores binários, a função operado-
ra tem um argumento.
Genericamente, a função operadora (se declarada como membro de
classe) sempre necessita de um argumento a menos do que o número de
operandos daquele operador.
58 Treinamento em Linguagem C++ Cap. 9

SOBRECARGA DE OPERADORES ARITMÉTICOS

O nosso primeiro exemplo modifica o programa ADD_OBJ.CPP, visto no


Capítulo 8, adicionando a ele uma função operadora que sobrecarrega o
operador + binário.
//ADD_OBJ1.CPP
//Mostra a sobrecarga do operador + aritmético
#include <iostream.h>
#include <iomanip.h>

class venda
{
private:
int npecas;
float preco;
public:
venda() {} //Construtor sem args
venda(int np, float p) //Construtor com args
{ npecas=np; preco=p;}

venda operator + (venda v) const;//Fção operadora

void getvenda()
{
cout << "Insira No.Peças: "; cin >> npecas;
cout << "Insira Preço : "; cin >> preco;
}

void printvenda() const;

};
Cap. 9 Sobrecarga de operadores 59

venda venda::operator + (venda v) const //soma duas vendas


{
int pec = npecas + v.npecas;
float pre= preco + v.preco;
return venda(pec,pre);
}

void venda::printvenda() const


{
cout << setiosflags(ios::fixed) //não notação científica
<< setiosflags(ios::showpoint) //ponto decimal
<< setprecision(2) //duas casas
<< setw(10) << npecas; //tamanho 10
cout << setw(10) << preco << "\n";
}

void main()
{
venda A(58,12734.53),B,C(30,6000.3),T,Total;

B.getvenda();

T = A + B;
Total = A + B + C; //Somas múltiplas

cout << "Venda:A........."; A.printvenda();


cout << "Venda:B........."; B.printvenda();
cout << "Venda:A + B....."; T.printvenda();
cout << "Totais:A + B + C";Total.printvenda();
}

Eis a saída:
Insira No.Peças: 45
Insira Preço : 10987.85
Venda:A......... 58 12734.53
Venda:B......... 45 10987.85
60 Treinamento em Linguagem C++ Cap. 9

Venda:A + B..... 103 23722.38


Totais:A + B + C 133 29722.68

A declaração do operador de adição da classe venda é a seguinte:


venda operator + (venda v) const;//Fção operadora

Definimos esta função fora da classe:


venda venda::operator + (venda v) const
{
int pec = npecas + v.npecas;
float pre= preco + v.preco;
return venda(pec,pre);
}

Esta função retorna um objeto venda e recebe um único argumento da


classe venda. A chamada à função operadora é executada na instrução:
T = A + B;

O objeto à esquerda do operador (A) é o objeto do qual a função


operadora é membro. O objeto à direita do operador (B) é fornecido como
argumento para a função operadora. Na função, o objeto A é acessado direta-
mente, visto que este é o objeto do qual a função operadora é membro. O
operando da direita (B) é acessado como argumento da função, por meio do
operador ponto (v.npecas,v.preco). O valor retornado por esta função é o valor
da expressão (A + B) e será atribuído a T. A instrução anterior é equivalente a:
T = A.operator+(B);

SOMANDO OBJETOS DA CLASSE PONTO

Vamos modificar a classe ponto adicionando a ela uma função operadora que
sobrecarrega o operador + binário. Neste exemplo, mostraremos alguns proble-
mas com a sobrecarga de operadores.
//PONTO4.CPP
//Mostra sobrecarga do operador + aritmético
#include <iostream.h>
Cap. 9 Sobrecarga de operadores 61

class ponto
{
private:
int x,y;
public:
ponto(int x1=0,int y1=0) { x=x1;y=y1;}//Construtor

ponto operator +(ponto p) const //Soma dois objetos


{ return ponto(x+p.x,y+p.y); }

ponto operator +(int n) const //Soma um número


{ return ponto(x+n,y+n); } // a um objeto ponto

void printpt() const //Imprime ponto


{ cout << ’(’ << x << ’,’ << y << ’)’;}
};

void main()
{
ponto p1(5,1),p2(2,3),p3; //Declara e inicializa

cout << "\n p1 = "; p1.printpt();


cout << "\n p2 = "; p2.printpt();

p3 = p1 + p2;
cout << "\n p3 = "; p3.printpt();

p3 = p1+5;
cout << "\n p3 = "; p3.printpt();
}

Eis a saída:
p1 = (5,1)
p2 = (2,3)
p3 = (7,4)
p3 = (10,6)
62 Treinamento em Linguagem C++ Cap. 9

A novidade deste programa é a soma de um objeto com um número


do tipo int.Uma função operadora não deve obrigatoriamente ter objetos dos
dois lados do operador. Podemos adicionar um objeto ponto e um inteiro
escrevendo uma função que descreve como a soma deve ser efetuada.Esta soma
é definida pela função operadora sobreposta:
ponto operator +(int n) const
{ return ponto(x+n,y+n); }

Utilizamos esta função na instrução:


p3 = p1+5; //Correto

Observe que esta instrução é equivalente a:


p3 = p1.operator+(5);

e por isso não podemos escrever instruções como:


p3 = 5 + p1; //ERRADO

que seriam interpretadas da seguinte forma:


p3 = 5.operator+(p1); //ERRADO

Obviamente, um inteiro não tem funções-membro que podem ser


chamadas para executar uma adição.
Para que expressões nas quais uma variável de um tipo básico seja o
primeiro operando, devemos usar funções amigas descritas mais adiante, no
Capítulo 12.

CRIANDO OPERADORES PARA MANIPULAR STRINGS

Em C++, o operador + e o operador += não podem ser usados para a operação


de acrescentar novos caracteres a uma cadeia de caracteres. Vamos expandir o
significado destes símbolos para abranger o novo uso.
A sobrecarga do operador + e += do próximo exemplo permite escrever
instruções como:
str1 = str2 + str3;
Cap. 9 Sobrecarga de operadores 63

str1 += str2;

onde str1, str2 e str3 são objetos da classe string.


//STRSOMA.CPP
//Mostra a sobrecarga do operador + com strings
#include <iostream.h>
#include <string.h>

const MAX=80;

class string
{
private:
char str[MAX];
public:
string() {str[0]=’\0’;} //Construtor 1
string(char s[]) {strcpy(str,s);}//Construtor 2

string operator +=(string s) const //Concatena


{
string temp;

if(strlen(str)+strlen(s.str) < MAX)


{
strcpy(temp.str,str);
strcat(temp.str,s.str);
}
else
cout << "\nMemória lotada!!";

return temp;
}

string operator +(string s) const //Concatena


{
string temp(str);
64 Treinamento em Linguagem C++ Cap. 9

temp+=s.str;
return temp;
}
void print() const { cout << str; } //Imprime
};

void main()
{
string s1("Feliz Aniversário! "),
s2("Denise."),
s3("Bom Dia! "),s4,s5;

cout << "\ns1 = "; s1.print();


cout << "\ns2 = "; s2.print();

s3 += s2;
s4 = s1 + s2;
s5 = s1 + s1;

cout << "\ns3 = "; s3.print();


cout << "\ns4 = "; s4.print();
cout << "\ns5 = "; s5.print();
}

Eis a saída:
Bom Dia! Denise.
Feliz Aniversário! Denise.
Feliz Aniversário! Feliz Aniversário!

A classe string contém duas simples operações de concatenação. A


definição destes dois novos operadores seguem as regras de sintaxe que você
usou para criar a classe ponto.
A função main() utiliza estes operadores nas instruções:
s3 += s2;
s4 = s1 + s2;
s5 = s1 + s1;
Cap. 9 Sobrecarga de operadores 65

A primeira destas instruções acrescenta a cadeia contida em s2 ao final


da cadeia contida em s3 e atribui a cadeia resultante a s3. A segunda concatena
as cadeias s1 e s2 e atribui a cadeia resultante a s4. A terceira duplica a cadeia
s1 e atribui o resultado a s5.
As funções operadoras recebem um argumento da classe string e
retornam um objeto da mesma classe. O processo de concatenação envolve a
criação de um objeto temporário e o uso das funções de biblioteca strcpy() e
strcat(). Os objetos envolvidos não são alterados; toda alteração é feita no objeto
temporário que é retornado pelo comando return.
Para prevenir o estouro do limite de tamanho da cadeia, a função
operator +=() verifica se a soma dos tamanhos das cadeias a serem concatenadas
não excede o tamanho máximo fixado.

COMPARANDO CADEIAS DE CARACTERES

Os nossos próximos exemplos mostram a sobrecarga de alguns operadores


relacionais. Usaremos estes operadores para comparar cadeias de caracteres.
Começaremos com um programa que ordena uma lista de nomes por
meio de uma função que utiliza o operador < sobrecarregado para a comparação
das cadeias de caracteres.
//STRSORT.CPP
//Mostra sobrecarga do operador relacional <
#include <iostream.h>
#include <string.h>

const MAX=80;
enum Boolean { False, True};

class string
{
private:
char str[MAX];
public:
66 Treinamento em Linguagem C++ Cap. 9

string() {str[0]=’\0’;}
string(char s[]) {strcpy(str,s);}

Boolean operator < (string s) const


{return (strcmp(str,s.str)<0) ? True:False;}

void print() const { cout << str; }


};

void ordena(string s[],int n);

void main()
{
string s[4] = { "José Carlos",
"Denise",
"André",
"Hélio" };

cout << "\n\nLista original:";

for(int i=0;i<4;i++)
{
cout << "\ns[" << i << "] = ";
s[i].print();
}

ordena(s,4);

cout << "\n\nLista ordenada:";

for(i=0;i<4;i++)
{
cout << "\ns[" << i << "] = ";
s[i].print();
}
}
Cap. 9 Sobrecarga de operadores 67

void ordena(string s[],int n)


{
string temp;

for(int i=0;i<n;i++)
{
for(int j=i+1; j<n;j++)
if(s[i] > s[j])
{
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
}

Eis a saída:
Lista original:
s[0] = José Carlos
s[1] = Denise
s[2] = André
s[3] = Hélio

Lista ordenada:
s[0] = André
s[1] = Denise
s[2] = Hélio
s[3] = José Carlos

Este programa usa uma função para ordenar cadeias de caracteres. A


comparação utiliza o operador < sobreposto.
A função operator<() retorna um tipo Boolean definido na instrução
enum do começo do programa. O valor retornado é False ou True dependendo
da comparação. A comparação é realizada por meio da função de biblioteca
68 Treinamento em Linguagem C++ Cap. 9

strcmp() que retorna um valor menor que zero se a primeira cadeia for menor
que a segunda. Esta função reconhece que uma cadeia é menor que outra se
esta vier antes na ordem de dicionário.

OUTRAS COMPARAÇÕES COM CADEIAS DE CARACTERES

A implementação de outros operadores relacionais pode ser acrescentada à


nossa classe. Vamos modificar o programa STRSORT.CPP para que aceite um
número qualquer de nomes. A modificação inclui uma função para ler um nome
do teclado e a sobrecarga do operador ==. Eis a listagem:
//STRSORT.CPP
//Mostra sobrecarga dos operadores relacionais
#include <iostream.h>
#include <string.h>
#include <stdio.h>

const MAX=80;
enum Boolean { False, True};

class string
{
private:
char str[MAX];
public:
string() {str[0]=’\0’;}
string(char s[]) {strcpy(str,s);}

string getstr()
{ gets(str);
return string(str);
}
Cap. 9 Sobrecarga de operadores 69

Boolean operator < (string s) const


{return (strcmp(str,s.str)<0) ? True:False;}

Boolean operator ==(string s) const


{return (strcmp(str,s.str)==0) ? True:False;}

void print() const { cout << str; }


};

void ordena(string s[],int n);

void main()
{
string s[MAX];

for(int n=0;n<=80;n++)
{
cout << "\nDigite nome ou [ENTER] para terminar:";
if(s[n].getstr()=="") break;
}

cout << "\n\nLista original:";


for(int i=0;i<n;i++)
{
cout << "\ns[" << i << "] = ";
s[i].print();
}

ordena(s,n);

cout << "\n\nLista ordenada:";

for(i=0;i<n;i++)
{
cout << "\ns[" << i << "] = ";
70 Treinamento em Linguagem C++ Cap. 9

s[i].print();
}
}

void ordena(string s[],int n)


{
string temp;

for(int i=0;i<n;i++)
{
for(int j=i+1; j<n;j++)
if(s[i] > s[j])
{
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
}

Modificamos a classe string incluindo uma função que solicita a entrada


de uma cadeia de caracteres pelo teclado e a coloca no objeto correspondente.
A função retorna um objeto da classe string inicializado com a cadeia fornecida
pelo teclado. Incluímos ainda a função operator==() que utiliza a função de
biblioteca strcmp() para comparar duas cadeias de caracteres. Esta função é
semelhante a operator<(), descrita após a versão anterior do programa.
Em main(), a instrução
if(s[n].getstr()=="") break;

compara a entrada do usuário com uma cadeia nula. Se o usuário pressionar a


tecla [ENTER] sem escrever qualquer nome, o programa entende que a entrada
terminou e passa para a segunda etapa que imprime a lista de nomes e em
seguida imprime a mesma lista ordenada.
Cap. 9 Sobrecarga de operadores 71

SOBRECARGA DO OPERADOR []

C++ não faz a checagem de limites de matrizes. Por causa disto, a manipulação
de matrizes em C++ apresenta várias falhas e permite erros acidentais de
sobreposição de dados na memória se forem usados índices que ultapassam o
limite de dimensão da matriz.
O nosso próximo exemplo modifica o programa NOTAS.CPP, define a
classe matriz e sobrecarrega o operador [].
//NOTAS.CPP
//Calcula a média das notas de qualquer número de alunos
#include <iostream.h>
#include <iomanip.h>

const MAX=50;

class matriz
{
private:
float n[MAX];
public:
matriz(); //Construtor

float& operator [] (int indice); //Sobrecarga []


float media(int i);
};

matriz::matriz()
{
for(int i=0;i<MAX;i++) n[i]=0.0;
}

float& matriz::operator [](int indice)


{
static float x=-1;
72 Treinamento em Linguagem C++ Cap. 9

if(indice >=0 && indice < MAX)


return n[indice];
else
{ cout << "\nFora de limite";
return x;
}
}

float matriz::media(int i)
{
float m=0.0;
for(int j=0; j<i; j++) m+=n[j];
return m/float(i);
}

void main()
{
matriz notas;

cout << setprecision(2);

int i=0;
do
{
cout << "Digite a nota do aluno " <<(i+1)<<": ";
cin >> notas[i];
} while (notas[i++] >= 0);
i--;
cout << "\n\nMédia das notas: " << notas.media(i);
}

A nova versão do programa age exatamente da mesma forma que a


versão original. Cria um objeto matriz para armazenar até 50 valores float,
solicita a entrada das notas e imprime a média.
Cap. 9 Sobrecarga de operadores 73

A novidade é a função operator[] que checa se o índice especificado


está dentro do limite de dimensionamento da matriz. Se estiver, a função retorna
uma referência ao elemento correspondente da matriz privada. Se não estiver,
a função imprime uma mensagem de erro e retorna uma referência a uma
variável static float com valor -1.
Isto previne a sobreposição de outras regiões da memória caso o limite
da matriz seja ultrapassado.
Observe que a função operator[] retorna uma referência a um elemento
da matriz n. Dessa forma, a expressão notas[i] age como o próprio elemento da
matriz privada, podendo ser usado em instruções como:
cin >> notas[i];

ou mesmo atribuições em que notas[i] é o operando da esquerda:


notas[i] = 67.5;

Retornar uma referência não é simplesmente eficiente, mas sim neces-


sário.
float& matriz::operator [](int indice)
{
static float x=-1;
if(indice >=0 && indice < MAX)
return n[indice];
else
{ cout << "\nFora de limite";
return x;
}
}

A função operator[] cria uma variável static, pois não é possível


retornar uma referência a uma variável automática.
74 Treinamento em Linguagem C++ Cap. 9

CONVERSÕES DE TIPOS COM CLASSES

Os exemplos mostrados até agora contêm várias sobrecargas de operadores.


Entretanto, não discutimos nada a respeito do operador de atribuição (=). Este
é um operador especial, com propriedades complexas.
Você já sabe que o operador = pode atribuir o valor de uma variável
simples a outra por meio de instruções como:
var1 = var2;

onde var1 e var2 são do tipo int. Você também sabe que este operador pode ser
usado para atribuir um objeto a outro de mesma classe em instruções como:
obj2 = obj1;

Geralmente, quando o valor de um objeto é atribuído a outro de mesma


classe, o valor de todos os seus membros de dados são simplesmente copiados
no novo objeto. O compilador não executa nenhuma instrução especial no uso
de atribuições entre tipos definidos pelo usuário.
A complexidade surge quando a atribuição é entre variáveis de tipos
diferentes. Primeiramente, discutiremos o modo como o compilador age na
conversão de tipos básicos, executada automaticamente. Em seguida, explora-
remos diversas situações em que o compilador não converte tipos automatica-
mente e é necessário que façamos a definição de como a conversão deve ser
executada.

CONVERSÕES ENTRE TIPOS BÁSICOS

Quando escrevemos uma instrução como:


double dvar = ’A’;

assumimos que o compilador chama uma rotina especial para converter o valor
ASCII da direita para um double e depois atribui o valor resultante à variável
dvar. Esta conversão de tipo é dita implícita, pois não aparece na listagem do
programa.
Cap. 9 Sobrecarga de operadores 75

Obviamente, o compilador é provido de várias rotinas para conversões


implícitas, uma para cada tipo de conversão.
O operador de molde ou de conversão de tipo pode ser usado para forçar
o compilador a converter um tipo num outro. Esta conversão é dita explícita.
double dvar = double(’A’);

CONVERSÕES DE OBJETOS A TIPOS BÁSICOS

Quando definimos uma classe em C++, devemos especificar a conversão a ser


aplicada pelo compilador com objetos desta classe. Podemos definir conversões
de tipos entre classes ou entre classes e tipos básicos.
Nosso próximo exemplo mostra como converter um objeto string num
tipo int ou num tipo double.
//STRCLASS.CPP
//Mostra os conversores de tipo int e double para string
#include <iostream.h>
#include <string.h>
#include <stdlib.h>

const MAX=80;

class string
{
private:
char str[MAX];
public:
string() {str[0]=’\0’;}
string(char s[]) {strcpy(str,s);}

operator int() const //Função conversora


{ return atoi(str);}
76 Treinamento em Linguagem C++ Cap. 9

operator double() const //Função conversora


{ return atof(str);}

void print() const { cout << str; }


};

void main()
{
string s1("1234"),s2("1234.567");

int i = s1;
double d = s2;

cout << "\ni = " << i;


cout << "\nd = " << d;
}

Eis a saída:
i = 1234
d = 1234.567

Para converter um tipo definido pelo usuário, como classes, num tipo
básico é necessário sobrecarregar o operador de molde, criando uma função
chamada função conversora.
No nosso exemplo, definimos duas funções conversoras:
operator int() const //Função conversora
{ return atoi(str);}

operator double() const //Função conversora


{ return atof(str);}

O primeiro operador toma o conteúdo da cadeia de caracteres, membro


do objeto operando, e o converte a um valor int por meio da função de biblioteca
Cap. 9 Sobrecarga de operadores 77

atoi(). A função atoi() recebe como argumento o endereço de uma cadeia de


caracteres e retorna um número inteiro resultante da conversão. Este operador
foi chamado pela instrução:
int i = s1;

que poderia ser escrita na forma explícita:


int i = int(s1);

As duas formas têm exatamente o mesmo efeito. Quando o compilador


encontra uma instrução onde é necessária uma conversão de um tipo definido
pelo usuário para um tipo básico, procura pela função conversora apropriada.
Aqui o compilador começa procurando a função sobrecarregada ope-
rator=() e, como não a encontra, procura pela função conversora de tipo e a
utiliza.
A segunda função conversora, de modo similar, toma o conteúdo da
cadeia de caracteres, membro do objeto operando, e o converte a um valor
double por meio da função de biblioteca atof().
A função atof() recebe como argumento o endereço de uma cadeia de
caracteres e retorna um número double resultante da conversão. Este operador
foi chamado pela instrução:
double d = s2;

CONVERSÕES DE TIPOS BÁSICOS A OBJETOS

Para converter um tipo básico num objeto, é necessário sobrecarregar o operador


de atribuição. Implementaremos este operador no programa anterior. O progra-
ma resultante incluirá a conversão de um int a um objeto string.
//STRCLASS.CPP
//Mostra a conversão de um int a um objeto string
#include <iostream.h>
#include <string.h>
#include <stdlib.h>
78 Treinamento em Linguagem C++ Cap. 9

const MAX=80;

class string
{
private:
char str[MAX];
public:
string() {str[0]=’\0’;}
string(char s[]) {strcpy(str,s);}

void operator = (int n) //Atribuição


{ itoa(n,str,10);}

operator int() const //Função conversora


{ return atoi(str);}

operator double() const //Função conversora


{ return atof(str);}

void print() const { cout << str; }


};

void main()
{
string s;
int i;

s = 5432; //Chama operator=()


i = s; //Chama int()

cout << "\ns = "; s.print();


cout << "\ni = " << i;
}

Eis a saída:
s = 5432
i = 5432
Cap. 9 Sobrecarga de operadores 79

Incluimos a função operator=() na classe string. Esta função recebe um


número inteiro como argumento e o converte a uma cadeia de caracteres por
meio da função de biblioteca itoa().
A função itoa() recebe como argumentos o número a ser convertido, o
endereço da cadeia onde serão colocados os bytes da conversão e a base
numérica a ser usada. Neste exemplo usamos a base decimal (10).
Este operador foi chamado pela instrução:
s = 5432;

CONVERSÕES ENTRE OBJETOS DE CLASSES DIFERENTES

O mesmo método usado para conversões entre tipos básicos e tipos definidos
pelo usuário pode ser aplicado para converter um tipo definido pelo usuário
em outro tipo definido pelo usuário. Outra forma de conversão é criar um
construtor que recebe como argumento um objeto.
Por exemplo, para utilizar instruções como
objA = objB;

onde objA é um objeto da classe A e objB é um objeto da classe B, podemos


utilizar dois métodos diferentes: criar uma função-membro conversora da classe
B ou criar um construtor membro da classe A que recebe como argumento um
objeto da classe B.
A escolha entre os dois métodos dependerá de onde queremos colocar
a função de conversão, se na classe A ou na classe B.

PRIMEIRO MÉTODO DE CONVERSÃO: FUNÇÃO CONVERSORA

O próximo exemplo mostra o uso do primeiro método de conversão. As duas


classes usadas neste programa são graus e radianos; o objetivo é converter
objetos da classe radianos em objetos da classe graus. Quando a função
80 Treinamento em Linguagem C++ Cap. 9

conversora é membro da classe a ser convertida (radianos no nosso exemplo),


é implementada como uma função conversora e não como um construtor.
Eis a listagem:
//GRAUS.CPP
//Mostra conversão da classe radianos para graus
#include <iostream.h>
#include <iomanip.h>
#define PI 3.141592653

class graus
{
private:
double g;
public:
graus(double x){ g = x;}
graus(){g=0.0;}

void print()
{ cout << setiosflags(ios::fixed)
<< setiosflags(ios::showpoint)
<< setprecision(2) << g << "\xf8\n";
}
};

class radianos
{
private:
double rad;
public:
radianos(double r){ rad = r;}
radianos() {rad=0.0;}

operator graus() //Função conversora de


{ //radianos para graus
return graus(180.0*rad/PI);
}
Cap. 9 Sobrecarga de operadores 81

void print()
{ cout << setiosflags(ios::fixed)
<< setiosflags(ios::showpoint)
<< setprecision(2)<< rad << " rad\n";
}
};

void main()
{
graus gr;
radianos rad(PI);
gr = rad; //Converte radianos para graus
gr.print();
rad.print();
}

Eis a saída:
180.000
3.14 rad

Na função main(), declaramos um objeto da classe graus, chamado gr,


que não é inicializado. Declaramos também um objeto da classe radianos,
chamado rad e inicializado com o valor PI.
Observe a função-membro graus() da classe radianos:
operator graus() //Função conversora de
{ //radianos para graus
return graus(180.0*rad/PI);
}

Esta função transforma o objeto do qual é membro em um objeto graus,


e retorna esse objeto. A função é chamada pela instrução:
gr = rad;
82 Treinamento em Linguagem C++ Cap. 9

SEGUNDO MÉTODO DE CONVERSÃO: FUNÇÃO CONSTRUTORA

Vamos reescrever o programa anterior de forma que a função conversora seja


implementada na classe graus. Para isto, é necessário criar uma função constru-
tora que recebe um argumento da classe radianos.
Uma complicação surge pelo fato de a função construtora necessitar
acessar o dado privado da classe radianos para poder fazer a conversão. Por
causa disto, é necessário incluir uma função na classe radianos que retorna o
valor do dado privado.
Eis a listagem:
//GRAUS1.CPP
//Mostra conversão da classe radianos para graus
#include <iostream.h>
#include <iomanip.h>

#define PI 3.141592653

class radianos
{
private:
double rad;
public:
radianos(double r){ rad = r;}
radianos() {rad=0.0;}

double getrad() {return rad;}

void print()
{ cout << setiosflags(ios::fixed)
<< setiosflags(ios::showpoint)
<< setprecision(2) << rad << " rad\n";
}
};
Cap. 9 Sobrecarga de operadores 83

class graus
{
private:
double g;
public:
graus(double x){ g = x;}
graus(){g=0.0;}

graus(radianos rad) //Construtor para conversão


{ g=(180.0*rad.getrad()/PI); }

void print()
{ cout << setiosflags(ios::fixed)
<< setiosflags(ios::showpoint)
<< setprecision(2) << g << "\xf8\n";
}
};

void main()
{
graus gr;
radianos rad(PI);
gr = rad; //Converte radianos para graus
gr.print();
rad.print();
}

A função construtora, implementada como um construtor de um único


argumento, é a seguinte:
graus(radianos rad)
{ g=(180.0*rad.getrad()/PI); }

Esta função transforma o objeto do qual é membro para o correspon-


dente em graus do objeto recebido como argumento.
84 Treinamento em Linguagem C++ Cap. 9

Para implementar esta conversão, este construtor deve acessar o valor


do objeto recebido como argumento. A classe radianos contém a seguinte função
para fornecer este valor:
double getrad() {return rad;}

O programa main() de GRAUS1.CPP é o mesmo de GRAUS.CPP. O


construtor implementado executa a mesma tarefa da função construtora do
primeiro exemplo.

CONVERSÕES DOS DOIS LADOS

Você poderia querer converter radianos em graus, como já fizemos, e também


graus em radianos. Para implementar a conversão dos dois lados, as duas
funções conversoras devem estar presentes na mesma classe.
Eis a mudança:
//GRAUS2.CPP
//Mostra conversão da classe radianos para graus
//e da classe graus para radianos
#include <iostream.h>
#include <iomanip.h>

#define PI 3.141592653

class radianos
{
private:
double rad;
public:
radianos(double r){ rad = r;}
radianos() {rad=0.0;}

double getrad() {return rad;}


Cap. 9 Sobrecarga de operadores 85

void print()
{ cout << setiosflags(ios::fixed)
<< setiosflags(ios::showpoint)
<< setprecision(2) << rad << " rad\n";
}
};

class graus
{
private:
double g;
public:
graus(double x){ g = x;}
graus(){g=0.0;}

graus(radianos rad) //Construtor para conversão


{ g=(180.0*rad.getrad()/PI); }

operator radianos()
{ return radianos(g*PI/180.0); }

void print()
{ cout << setiosflags(ios::fixed)
<< setiosflags(ios::showpoint)
<< setprecision(2) << g << "\xf8\n";
}
};

void main()
{
graus gr,gr1(180.0);
radianos rad(PI),rad1;
gr = rad; //Converte radianos para graus
rad1= gr1; //Converte graus para radianos
86 Treinamento em Linguagem C++ Cap. 9

gr.print();
rad.print();
rad1.print();
}

As funções conversoras são implementadas na classe graus. Uma delas


é um construtor e a outra, uma função conversora de tipo.
graus(radianos rad) //Construtor para conversão
{ g=(180.0*rad.getrad()/PI); }

operator radianos()
{ return radianos(g*PI/180.0); }

A segunda função converte o objeto do qual é membro a um objeto


radianos, e retorna esse objeto. A função é chamada pela instrução:
rad1= gr1;

CONVERSÕES: QUANDO USAR O QUÊ

Muitas vezes, a escolha do uso de uma função conversora de tipo ou de um


construtor de um argumento fica ao seu critério, mas algumas vezes você não
terá escolha. Por exemplo: se você quiser fazer um conversor para uma classe
de uma biblioteca, da qual você não tem acesso ao fonte, obrigatoriamente a
função conversora deverá estar na classe que você criar. Ou seja, se você usar
uma instrução como:
meu_obj = lib_obj;

a função conversora da sua classe deverá ser implementada como uma função
construtora de um argumento. Por outro lado, em instruções como:
lib_obj = meu_obj;

a função conversora da sua classe deverá ser implementada como uma função
conversora de tipo.
Cap. 9 Sobrecarga de operadores 87

AMBIGÜIDADES

Suponhamos que, para converter radianos em graus, você decida usar um


construtor de um argumento na classe graus e uma função conversora de tipo
na classe radianos. Como o compilador reconhecerá qual deles deve ser usado?
O compilador não reconhecerá e apresentará um erro.

REVISÃO

1. As sobrecargas de operadores permitem que expressões confusas e obscu-


ras sejam escritas de maneira clara e intuitiva.
2. Sobrecarregar um operador significa redefini-lo de modo que ele se aplique
também a tipos de dados definidos pelo programador.
3. Implementamos sobrecargas de operadores escrevendo funções chamadas
operadoras. Estas funções podem ser membros de classes ou funções
globais do programa.
4. O nome de uma função operadora é sempre igual ao símbolo do operador
precedido da palavra-chave operator.
5. A função operator pode ser de qualquer tipo de dado, mas não deve receber
nenhum argumento quando sobrecarrega um operador unário e um único
argumento quando sobrecarrega um operador binário.
6. Os operadores de incremento (++) e decremento (--) pós-fixados são
exceções à regra anterior. A função operadora destes operadores recebe um
argumento do tipo int com o único propósito de indicar ao compilador a
operação pós-fixada.
7. Em C++, não é permitido inventar novos operadores usando sobrecargas.
Devemos limitar-nos aos já existentes. É necessário também respeitar a
definição original do operador quanto ao número de operandos e a prece-
dência.
88 Treinamento em Linguagem C++ Cap. 9

8. Um objeto temporário, sem nome, é criado usando-se o nome de sua classe


seguido de parênteses contendo os argumentos do construtor (inicializado-
res) separados por vírgulas.
9. A sobrecarga dos operadores de incremento (++) e decremento (--) merece
uma atenção especial pelo fato de o compilador não reconhecer automati-
camente a operação pré ou pós-fixada.
10. A função operadora de um operador binário é membro do objeto escrito à
esquerda do operador (operando da esquerda) e recebe como argumento o
objeto escrito à direita do operador (operando da direita).
11. A instrução C = A _op B, onde _op é um operador binário, é equivalente a
C = A.operator_op(B);, onde operator_op é o nome da função-membro
operadora do objeto A, e B é um objeto ou uma variável de um tipo básico.
Desta forma, instruções do tipo C = 5 + A; não são permitidas.
12. A sobrecarga do operador binário [] é usada para evitar que limites de
matrizes sejam ultrapassados.
13. Conversões de tipos são implementadas por meio da sobrecarga do ope-
rador de molde (conversor de tipos), sobrecarga do operador de atribuição
(=) ou funções construtoras.
14. A sobrecarga do operador de atribuição (=) é usada para converter um tipo
básico num tipo definido pelo programador.
15. A sobrecarga do operador de molde () é usada para converter um tipo
definido pelo programador num tipo básico.
16. As funções construtoras ou funções de sobrecarga do operador de molde
são usadas para converter um tipo definido pelo programador em outro
tipo definido pelo programador. A escolha entre os dois métodos depende
de onde queremos colocar a função conversora, se em uma das classes ou
na outra.
17. Se quisermos converter objetos da classe A em objetos da classe B e
vice-versa, devemos criar duas funções conversoras em uma das duas
classes: uma função construtora e uma função de sobrecarga do operador
de molde.
Cap. 9 Sobrecarga de operadores 89

EXERCÍCIOS

1. Sobrecarga de operadores é:
a) tornar operadores compatíveis com C++;
b) criar novos operadores;
c) criar novas operações para os operadores C++;
d) transformar operadores em objetos.
2. A implementação de sobrecargas de operadores é definida por meio de:
a) programas pequenos;
b) funções membros de classes;
c) dados membros de classes;
d) operações com classes.
3. A sobrecarga de operadores:
a) obedece à precedência do operador original;
b) define novas precedências para o operador original;
c) redefine o número de operandos aceitos pelo operador original;
d) define um novo símbolo para o operador original.
4. Quantos argumentos são necessários a uma função de sobrecarga de um
operador unário?
5. Se obj1, obj2 e obj3 são objetos de uma mesma classe e se a classe contém
uma função-membro de nome operator+(), para que a instrução
obj3 = obj1 + obj2;

trabalhe corretamente, a função operator+() deve:


a) receber dois argumentos;
b) retornar um valor;
90 Treinamento em Linguagem C++ Cap. 9

c) criar um objeto temporário sem nome;


d) usar o objeto da qual é membro como operando;
e) receber um argumento.
6. Verdadeiro ou falso: É possível somar objetos de uma mesma classe mesmo
sem utilizar o mecanismo de sobrecarga.
7. Assuma que x, y, z, w, v e t sejam objetos da classe A e que esta classe não
contenha nenhuma sobrecarga de operadores. Como escreveríamos a ins-
trução:
x = produto(produto(soma(y,z),soma(w,v)),t);

se incluirmos funções que sobrecarregam os operadores aritméticos * e +


como membros da classe A?
8. Verdadeiro ou falso: Utilizando sobrecargas de operadores, podemos criar
a operação de exponenciação usando como símbolo os caracteres **.
9. Qual é a diferença entre as funções operadoras do operador ++ quando
prefixado ou pós-fixado?
10. Quais das seguintes instruções criam um objeto temporário sem nome da
classe A?
a) B = A(x,y);
b) B = temp.x;
c) return temp;
d) A(x,y) = B;
e) func(A(x,y));
11. A função que sobrecarrega o operador -- pós-fixado:
a) retorna um valor do tipo int;
b) recebe um argumento do tipo int;
c) não recebe argumento;
d) não retorna nada.
Cap. 9 Sobrecarga de operadores 91

12. A função que sobrecarrega o operador -- prefixado:


a) retorna um valor do tipo int;
b) recebe um argumento do tipo int;
c) não recebe argumento;
d) não retorna nada.
13. A função que sobrecarrega um operador aritmético binário (+ - * /):
a) não recebe argumentos;
b) não tem valor de retorno;
c) o argumento é um objeto do qual a função é membro;
d) nenhuma das anteriores.
14. Considere que a função de protótipo
ClassA operator+(int n);

seja membro da classe ClassA e que sejam criados os objetos x e y dessa


classe. Quais das seguintes instruções são incorretas?
a) x = y + 25;
b) x = 25 + y;
c) x = x + y;
d) x = y + 25 + 5;
15. Quais das seguintes situações são válidas para o uso de conversões de tipos:
a) atribuições;
b) operações aritméticas;
c) passagem de argumentos para uma função;
d) retornando um valor de uma função.
16. Para converter um objeto da classe X a um tipo básico devemos utilizar:
a) uma função interna do compilador;
92 Treinamento em Linguagem C++ Cap. 9

b) um construtor de um argumento;
c) sobrecarga do operador =;
d) uma função conversora membro da classe X.
17. Para converter um tipo básico em um objeto da classe X, devemos utilizar:
a) uma função interna do compilador;
b) um construtor de um argumento;
c) sobrecarga do operador =;
d) uma função conversora membro da classe X.
18. Para converter um objeto da classe X a um objeto da classe Y, devemos
utilizar:
a) uma função interna do compilador;
b) um construtor de um argumento;
c) sobrecarga do operador =;
d) uma função conversora membro da classe X.
19. A instrução
objA = objB;

onde objA é um objeto da classe A e objB é um objeto da classe B, será


executada corretamente se existir:
a) uma função conversora membro da classe B;
b) uma função conversora membro da classe A;
c) um construtor membro da classe B;
d) nenhuma das anteriores.
20. Assuma que objA é um objeto da classe A e objB é um objeto da classe B.
Para utilizar instruções do tipo:
objA = objB;

há vários meios:
Cap. 9 Sobrecarga de operadores 93

a) Podemos incluir um conversor na classe A. Qual seria o conversor?


b) Podemos incluir um conversor na classe B. Qual seria o conversor?
21. Assuma que objA é um objeto da classe A e objB é um objeto da classe B.
Para utilizar instruções do tipo:
objA = objB;

e também instruções do tipo:


objB = objA;

o que deve ser feito?


22. Acrescente ao objeto ponto os operadores: menos unário (-), menos binário (-),
multiplicação (*), divisão (/), módulo (%), as operações aritméticas de
atribuição (+=, -=, *=, /= e %=), as operações relacionais ( >, >=, <, <=, ==
e !=) e as operações lógicas (&&, ||, !).
23. Crie uma classe que defina um número complexo (x + yi, onde x e y são do
tipo float e i é raiz quadrada de -1). Defina as operações aritméticas entre
números complexos, nesta classe.
24. Defina uma classe chamada fração. Esta classe deve armazenar o numerador
e o denominador de uma fração em duas variáveis inteiras. Inclua:
a) dois construtores: o primeiro sem argumentos e o outro que recebe
numerador e denominador da fração;
b) uma função-membro que retorna o numerador e outra que retorna o
denominador;
c) as operações de adição, subtração, multiplicação e divisão;
d) as operações de incremento e decremento pré e pós-fixadas;
e) um conversor de tipo que converte um objeto fração para um tipo float.
25. Inclua uma sobrecarga do operador de chamada à função () na classe string.
O seu protótipo é o seguinte:
void operator()(int n, char ch);

Esta sobrecarga atribui o caractere ch ao enésimo caractere da cadeia.


Escreva um programa de teste.
MAKRON
Books
Capítulo 10

HERANÇA

Herança
10.Herança
No decorrer deste livro você já aprendeu que as classes são a principal
característica da programação orientada ao objeto. No entanto, somente as
classes não são suficientes para apoiar totalmente a programação orientada ao
objeto. É preciso que haja um modo de relacionar uma classe a outra, de tal
forma que uma das classes torne-se uma subclasse da outra.
Herança é o processo pelo qual criamos novas classes, chamadas classes
derivadas, baseadas em classes existentes ou classes-base.
A classe derivada herda todas as características da classe-base, além
de incluir características próprias adicionais.
Uma das maiores vantagens do processo de herança é a reutilização
do código. Após definir uma classe, você não precisa alterá-la para que ela se
adapte a novas situações. Uma família inteira de classes derivadas pode ser
criada utilizando a classe inicial como base.
A reutilização do código permite o uso de bibliotecas de classes criadas
por outras pessoas. Sem modificá-las, é possível derivar outras classes que
atendam às suas necessidades particulares.
Para usar uma classe ou mesmo para derivar uma classe, você só precisa
ter o arquivo de código-objeto ou uma biblioteca que contenha seu código-ob-
jeto; você não precisa do código-fonte. Isto permite que bibliotecas de classes
sejam compradas ou distribuídas e sejam facilmente adaptadas a situações
não-previstas ao serem elaboradas.

94
Cap. 10 Herança 95

O processo de herança vai além da derivação simples. Uma classe


derivada pode herdar características de mais de uma classe-base. Classes
derivadas não estão limitadas a herança única.

DERIVANDO CLASSES

Vamos examinar uma situação que ocorre com freqüência em programação —


desenhar janelas no vídeo. Suponhamos que você tenha escrito a classe janela
como segue:
inline int max( int x,int y)
{ return (x > y ? x:y); }

inline int min( int x,int y)


{ return (x < y ? x:y); }

class janela
{
protected:
int linicio,cinicio,lfim,cfim;

public:
janela() {linicio=cinicio=0;lfim=23;cfim=78;}

janela(int li,int ci, int lf, int cf)


{
linicio=max(0,li); linicio=min(24,li);
lfim =max(0,lf); lfim =min(24,lf);
linicio=min(li,lf);

cinicio=max(0,ci); cinicio=min(79,ci);
cfim =max(0,cf); cfim =min(79,cf);
cinicio=min(ci,cf);
}
96 Treinamento em Linguagem C++ Cap. 10

void box(char modo[]="I;:<MH:");


};

void janela::box(char modo[])


{
poscur(linicio,cinicio); cout << modo[0];
for(int i=cinicio+1; i<cfim;i++) cout << modo[1];
cout << modo[2];
for(i=linicio+1; i<lfim;i++)
{ poscur(i,cinicio); cout << modo[7];
poscur(i,cfim); cout << modo[3];
}
poscur(lfim,cinicio); cout << modo[6];
for(i=cinicio+1; i<cfim;i++) cout << modo[5];
cout << modo[4];
}

A tarefa principal da classe janela é a de desenhar uma moldura no


vídeo. A função poscur() posiciona o cursor no vídeo e está listada mais adiante.

Imagine que você tenha usado a classe janela em seus programas e


esteja satisfeito com os resultados. Entretanto, um dia você percebe que gostaria
que esta classe limpasse a área interna da moldura e processasse o controle do
cursor internamente.
Você poderia implementar as novas funções diretamente no código-
fonte da classe janela. Há várias razões pelas quais você não deve fazer isto.
Primeiro, a classe janela já foi testada e trabalha corretamente. Imagine se fosse
uma classe grande na qual você tivesse gastado muitas horas de programação
e teste. Se você alterar a classe original, o processo de teste deverá ser repetido
mesmo para o código que já trabalhava corretamente antes da modificação.
Segundo, em muitas situações você não dispõe do código-fonte para
novas implementações. Por exemplo, classes confinadas em bibliotecas.
O processo de herança existe em C++ para solucionar estes problemas.
Por meio de herança você pode criar uma nova classe com implementações novas
e herdar da classe janela as suas características sem necessidade de alterá-la.
Cap. 10 Herança 97

//JANELA.CPP
//Mostra o uso de uma classe derivada

#include <iostream.h>
#include <dos.h> //Para int86()
#include <string.h>

const int VIDEO = 0x10;


void poscur(char lin,char col);
void limpabox(char li,char ci,char lf,char cf,char atr=7);

inline int max( int x,int y){ return x > y ? x:y;}


inline int min( int x,int y){ return x < y ? x:y;}

class janela
{
protected:
int linicio,cinicio,lfim,cfim;

public:
janela() {linicio=cinicio=1;lfim=23;cfim=78;}

janela(int li,int ci, int lf, int cf)


{
linicio=max(0,li); linicio=min(24,li);
lfim =max(0,lf); lfim =min(24,lf);
linicio=min(li,lf);

cinicio=max(0,ci); cinicio=min(79,ci);
cfim =max(0,cf); cfim =min(79,cf);
cinicio=min(ci,cf);
}

void box(char modo[]="I;:<MH:");


};
98 Treinamento em Linguagem C++ Cap. 10

void janela::box(char modo[])


{
poscur(linicio,cinicio); cout << modo[0];
for(int i=cinicio+1; i<cfim;i++) cout << modo[1];
cout << modo[2];
for(i=linicio+1; i<lfim;i++)
{
poscur(i,cinicio); cout << modo[7];
poscur(i,cfim); cout << modo[3];
}
poscur(lfim,cinicio); cout << modo[6];
for(i=cinicio+1; i<cfim;i++) cout << modo[5];
cout << modo[4];
}

class window : public janela


{
public:
void cls();
void cursor(char lin,char col);
void centra(char lin, char s[])
{
int tam=strlen(s);
if(tam > cfim-cinicio-1) return;
cursor( lin, (cfim-cinicio-tam)/2);
cout << s;
}
};

void window::cursor(char lin,char col)


{
if(lin>=lfim || col >=cfim) lin=col=0;
poscur(linicio+lin+1,cinicio+col+1);
}
Cap. 10 Herança 99

void window::cls()
{
limpabox(linicio+1,cinicio+1,lfim-1,cfim-1);
}

void main()
{
window tela; //Construtor sem args da classe-base

tela.box(); //Acessa membro da classe-base


tela.cls();
tela.centra(2,"Demonstração da classe window");
tela.cursor(0,0);
}

void poscur(char lin,char col)


{
union REGS regs;
regs.h.ah=2;
regs.h.bh=0;
regs.h.dh=lin;
regs.h.dl=col;
int86(VIDEO,&regs,&regs);
}

void limpabox(char li,char ci, char lf,char cf, char atr)


{
union REGS regs;
regs.h.ah=7;
regs.h.al=lf-li+1;
regs.h.bh=atr;
regs.h.ch=li;
regs.h.cl=ci;
regs.h.dh=lf;
100 Treinamento em Linguagem C++ Cap. 10

regs.h.dl=cf;
int86(VIDEO,&regs,&regs);
}

DEFININDO A CLASSE DERIVADA

Para derivar uma nova classe de uma classe já existente, basta indicar o nome
da classe-base após o nome da nova classe, separado por dois-pontos (:) — não
os dois-pontos duplos (::) usados para o operador de resolução de escopo. O
nome da classe-base pode ser precedido da palavra public ou da palavra
private. Analisaremos seus efeitos mais adiante.
class window : public janela

Esta declaração indica que a classe window é derivada da classe janela.


A classe window incorpora todos os membros da classe janela, além de seus
próprios membros. O restante da definição da classe window é de entendimento
imediato.

A RELAÇÃO ENTRE CLASSE-BASE E CLASSE DERIVADA

Um tópico importante de herança é a relação entre a classe-base e uma classe


derivada.
Observe que a parte pública da classe-base está disponível à classe
derivada e a qualquer objeto dessa classe. Qualquer membro público da
classe-base é automaticamente um membro da classe derivada. Entretanto,
nenhum membro privado da classe-base pode ser acessado pela classe derivada.
Isto representa uma grande limitação à relação entre as classes. Para resolver
este problema, C++ permite o uso de uma seção chamada protegida.
Cap. 10 Herança 101

O ESPECIFICADOR DE ACESSO A MEMBROS protected

Além das seções public e private de uma classe, C++ permite uma terceira
possibilidade de controle da visibilidade dos membros de uma classe: a seção
protected.
Os membros de uma classe sempre podem ser acessados por outros
membros da mesma classe, independentemente de serem public ou private.
Entretanto, objetos desta classe, definidos fora dela, podem acessar somente os
membros public da classe.
Pelo fato de membros privados de uma classe serem realmente priva-
dos, classes derivadas não têm acesso aos membros privados da classe-base.
No nosso exemplo, não queremos que as coordenadas da janela sejam
públicas, pois poderão ser alteradas por qualquer objeto da classe. Por outro
lado, precisamos acessar as coordenadas da janela na classe window.
Membros protected são semelhantes a membros private, exceto pelo
fato de que são visíveis a todos os membros de uma classe derivada. Observe
que membros protected não podem ser acessados por nenhum objeto declarado
fora dessas classes.
Sempre que você escrever uma classe que pode vir a ser uma classe-base
de outras classes, declare como protected os membros privados necessários às
classes derivadas.

CONSTRUTORES DA CLASSE DERIVADA

A classe window não tem nenhum construtor. O nosso programa cria um objeto
da classe window na função main() por meio da instrução:
window tela;

Se nenhum construtor for especificado na classe derivada, o construtor


da classe-base será usado. Portanto, neste caso, o construtor sem argumentos
da classe janela será executado.
102 Treinamento em Linguagem C++ Cap. 10

Um problema aparecerá quando você tentar inicializar o objeto tela com


valores. O construtor com argumentos da classe-base janela não será usado.
Para que declarações deste tipo trabalhem corretamente é necessário escrever
um novo conjuto de construtores para a classe derivada. Eis a alteração:
class window : public janela
{
public:

window() : janela() {}
window(int li,int ci, int lf, int cf) :
janela(li,ci,lf,cf){}

void cls();
void cursor(char lin,char col);
void centra(char lin, char s[])
{
int tam=strlen(s);
if(tam > cfim-cinicio-1) return;
cursor( lin, (cfim-cinicio-tam)/2);
cout << s;
}
};

Observe os dois novos construtores para a classe window:


window() : janela() {}
window(int li,int ci, int lf, int cf) :
janela(li,ci,lf,cf){}

Estes construtores têm uma sintaxe não-familiar: os dois-pontos segui-


dos do nome da função construtora da classe-base. Isto causa a chamada ao
construtor sem argumentos da classe janela, quando um objeto é criado sem
valores de inicialização, e a chamada ao construtor com argumentos da classe
janela, quando as coordenadas da janela são passados na criação do objeto.
Eis um programa main() para testar o construtor com argumentos:
void main()
{
Cap. 10 Herança 103

window tela(5,5,15,60); //Construtor com args

tela.box(); //Acessa membro da classe-base


tela.cls();
tela.centra(2,"Demonstração da classe window");
tela.cursor(0,0);
}

A instrução:
window tela(5,5,15,60);

usa o construtor com argumentos da classe window. Este construtor chama o


construtor correspondente da classe-base janela.
window(int li,int ci, int lf, int cf) :
janela(li,ci,lf,cf){}

Este construtor envia os argumentos recebidos para o construtor da


classe-base janela que, por sua vez, inicializa o objeto.

USANDO AS FUNÇÕES-MEMBRO DA CLASSE-BASE

O objeto tela da classe window usa a função box() membro da classe janela, na
instrução:
tela.box();

O compilador procura a função box() na classe window da qual tela é


objeto. Não encontrando, usa a função-membro da classe-base.

HERANÇA PÚBLICA E PRIVADA

Declaramos a classe derivada window especificando a palavra public:


class window : public janela
104 Treinamento em Linguagem C++ Cap. 10

A declaração public indica que os membros públicos da classe-base


serão membros públicos da classe derivada e os membros protected da classe-
base serão membros protected da classe derivada. Os membros públicos da
classe-base podem ser acessados por um objeto da classe derivada.
A declaração private pode ser usada no lugar de public e indica que
tanto os membros públicos quanto os protegidos da classe-base serão membros
privados da classe derivada. Estes membros são acessíveis aos membros da
classe derivada, mas não aos seus objetos. Portanto, um objeto da classe
derivada não terá acesso a nenhum membro da classe-base.
Observe o programa seguinte:
//PUBPRIV.CPP
//Mostra acesso público, privado e protegido à classe-base
#include <iostream.h>

class BASE
{
protected: int secreto;
private: int ultra-secreto;
public: int publico;
};

class DERIV1 : public BASE


{
public:
int a = secreto; //OK
int b = ultra-secreto; //ERRO: não-acessível
int c = publico; //OK
};

class DERIV2 : private BASE


{
public:
int a = secreto; //OK
int b = ultra-secreto; //ERRO: não-acessível
int c = publico; //OK
};
Cap. 10 Herança 105

void main()
{
int x;

DERIV1 obj1; //DERIV1 é public

x = obj1.a; //ERRO: não-acessível


x = obj1.b; //ERRO: não-acessível
x = obj1.c; //OK

DERIV2 obj2; //DERIV2 é private

x = obj2.a; //ERRO: não-acessível


x = obj2.b; //ERRO: não-acessível
x = obj2.c; //ERRO: não-acessível

REESCREVENDO FUNÇÕES-MEMBRO DA CLASSE-BASE

Quando você usa uma classe predefinida e pré-compilada como classe-base,


nem sempre as funções membros dessa classe atendem completamente ao que
você precisa. Muitas vezes, você gostaria de modificar uma das funções para
acrescentar alguma nova característica e não pode fazê-lo, ou porque não tem
acesso ao seu código-fonte ou porque não quer alterar a classe-base pois a nova
implementação é dependente de um problema particular de seu programa.
Você pode criar funções-membro de uma classe derivada que tenham
o mesmo nome de funções-membro da classe-base. Isto faz com que a sintaxe
da chamada a elas, por meio de um objeto, seja a mesma, independentemente
de tratar-se de um objeto da classe-base ou da classe derivada. A nova função
pode chamar a função da classe-base por meio do operador de resolução de
escopo (::).
106 Treinamento em Linguagem C++ Cap. 10

//AGENTE.CPP
//Mostra funções-membro da classe derivada com o mesmo
//nome de funções-membro da classe-base
#include <iostream.h>
#include <string.h>
const TAM=80;

class BasAg
{
protected:
char nome[TAM];
char numag[4];
public:
BasAg(){nome[0]=’\0’;numag[0]=’\0’;}
BasAg(char n[],char ng[])
{ if(strlen(n) < TAM) strcpy(nome,n);
if(strlen(ng) < 4 ) strcpy(numag,ng);
}

void print()
{
cout << "\nDADOS DO AGENTE:";
cout << "\n----------------";
cout << "\nNome : " << nome;
cout << "\nNúmero: " << numag;
}
};

class Agente : public BasAg


{
protected:
float altura;
int idade;
public:
Agente() : BasAg()
Cap. 10 Herança 107

{ altura=idade=0; }

Agente(char n[],char ng[],float a,int i):BasAg(n,ng)


{ altura=a; idade=i; }

void print()
{
BasAg::print();
cout << "\nAltura: " << altura;
cout << "\nIdade : " << idade;
}
};

void main()
{
Agente agente("James Bond","007",1.90,35);
agente.print();
}

Eis a saída:
DADOS DO AGENTE:
----------------
Nome : James Bond
Número: 007
Altura: 1.90
Idade : 35

Neste programa, a classe Agente foi criada para acrescentar outras duas
características de um agente secreto: altura e idade.
108 Treinamento em Linguagem C++ Cap. 10

QUAL FUNÇÃO É USADA?

A classe Agente contém três funções, das quais print() tem o mesmo nome, tipo
e argumentos da função de BasAg. Quando esta função é chamada por meio
de um objeto de main(), na instrução
agente.print();

de que maneira o compilador reconhece qual das duas executar?


A regra é a seguinte: Se duas funções de mesmo nome existem, uma na
classe-base e outra na classe derivada, a função da classe derivada será
executada se for chamada por meio de um objeto da classe derivada.
Se um objeto da classe-base é criado, usará sempre funções da própria
classe-base pois não conhece nada da classe derivada.

O OPERADOR DE RESOLUÇÃO DE ESCOPO COM FUNÇÕES

Para que uma função da classe derivada, com mesmo nome de uma função da
classe-base, possa acessar a que está na classe-base, é necessário o uso do
operador de resolução de escopo (::).
BasAg::print();

Esta instrução executa a função print() da classe-base. Sem o uso do


operador de resolução de escopo, o compilador executará a função print() da
classe-derivada e o resultado será uma seqüência infinita de chamadas recursi-
vas. O operador de resolução de escopo permite que se especifique exatamente
qual é a classe da função que queremos executar.
Cap. 10 Herança 109

CONSTRUTORES DA CLASSE Agente

A classe Agente tem dois construtores: o primeiro não recebe argumentos, o


segundo recebe quatro argumentos. Estes dois construtores chamam os cons-
trutores correspondentes da classe-base BasAg.

CONSTRUTOR SEM ARGUMENTOS

O construtor sem argumentos da classe Agente é o seguinte:


Agente() : BasAg()
{ altura=idade=0; }

Este construtor chama o construtor sem argumentos da classe-base e


depois inicializa as variáveis altura e idade com zeros.

CONSTRUTOR DE MÚLTIPLOS ARGUMENTOS

O uso de construtores de vários argumentos é um pouco mais complexo. O


construtor de quatro argumentos da classe Agente é o seguinte:
Agente(char n[],char ng[],float a,int i) : BasAg(n,ng)
{ altura=a; idade=i; }

Este construtor chama o construtor de dois argumentos da classe


BasAg. Para que esta chamada possa ser feita, é necessário que sejam informa-
dos os argumentos do construtor da classe-base. Estes dados são recebidos e
passados de imediato para o construtor-base. Adicionalmente, este construtor
recebe mais dois argumentos necessários ao seu processamento interno.
Observe que, além de chamar os construtores da classe-base, estes
construtores inicializam os dados que não foram implementados na classe-base.
110 Treinamento em Linguagem C++ Cap. 10

NENHUM CÓDIGO É REESCRITO

C++ foi desenhada para que se possam criar classes derivadas eficientes. Por
meio de heranças podemos utilizar facilmente qualquer parte da classe-base,
seja ela dados, construtores ou funções-membro. Para adicionar novas proprie-
dades às existentes, necessitamos somente desenvolver estas novas implemen-
tações numa classe derivada. Observe que a classe Agente não duplicou
nenhum código da classe BasAg. Foram usadas as próprias funções de BasAg.

QUANDO USAR O QUÊ

Em que momentos devemos usar derivação private no lugar de public?


Geralmente, a derivação privada é raramente usada. Mas há momentos
em que a derivação privada é desejável. Por exemplo, imagine que você tenha
uma função da classe-base que trabalha perfeitamente com objetos da classe-
base, mas gera resultados errados quando usada com objetos da classe derivada.

Para estar seguro de que a função só será chamada por objetos da


classe-base, você poderia definir uma função de mesmo nome na classe derivada
que imprimisse uma mensagem de erro toda vez que fosse chamada. Entretanto,
a derivação privada neste caso é a solução mais elegante.

HIERARQUIA DE CLASSES

Nos exemplos deste capítulo, a herança foi usada para adicionar novas carac-
terísticas a classes existentes. Agora vamos analisar um exemplo no qual a
herança é usada para um propósito diferente: como parte do desenho original
de um programa.
Cap. 10 Herança 111

Nosso exemplo modela uma base de dados de contas bancárias. Esco-


lhemos somente três representações diferentes de contas bancárias: conta sim-
ples, conta especial e conta-poupança.
A base de dados armazena o nome do cliente, a identificação numérica
da conta e o saldo, independentemente de sua categoria. Entretanto, contas
especiais necessitam de um dado adicional: o limite; e contas-poupança neces-
sitam da taxa de reajuste.
Nosso exemplo começa criando uma classe-base conta. Esta classe é
usada para criar as classes derivadas: contaSimples, contaEspecial e Poupanca.
Eis a listagem:
//Conta.cpp
//Modela uma base de dados de contas bancárias
#include <iostream.h>
#include <stdio.h> //Para gets()
#include <iomanip.h>

const TAM = 80; //Tamanho máximo do nome

class conta
{
private:
char nome[TAM];//Nome do cliente
int Nconta; //Número da conta
float saldo; //Saldo
public:
void getdata()
{ cout << "\n Nome: "; gets(nome);
cout << " No.Conta: "; cin >> Nconta;
cout << " Saldo: "; cin >> saldo;
}
void putdata()
{ cout << "\n Nome: " << nome;
cout << "\n No.Conta: " << Nconta;
cout << "\n Saldo: "
112 Treinamento em Linguagem C++ Cap. 10

<< setiosflags(ios::fixed)
<< setprecision(2) << saldo;
}
float Saldo() { return saldo; }
};

class contaSimples : public conta


{};

class contaEspecial : public conta


{
private:
float limite;
public:
void getdata()
{ conta::getdata();
cout << " Limite: "; cin >> limite;
}
void putdata()
{ conta::putdata();
cout << "\n Limite: " << limite;
cout << "\n Saldo Total: "
<< setiosflags(ios::fixed)
<< setprecision(2)<< (Saldo()+limite);
}
};

class Poupanca : public conta


{
private:
float taxa;
public:
void getdata()
{ conta::getdata();
cout << " Taxa: "; cin >> taxa;
}
Cap. 10 Herança 113

void putdata()
{ conta::putdata();
cout << "\n Taxa: " << taxa;
cout << "\n Saldo Total: "
<< setiosflags(ios::fixed)
<< setprecision(2) << (Saldo()*taxa);
}
};

void main()
{
contaSimples c1,c2;
contaEspecial c3;
Poupanca c4;

cout << "\n* Digite os dados da conta simples 1. ";


c1.getdata();

cout << "\n* Digite os dados da conta simples 2. ";


c2.getdata();

cout << "\n* Digite os dados da conta especial. ";


c3.getdata();

cout << "\n* Digite os dados da conta poupança. ";


c4.getdata();

cout << "\n\n* Conta Simples 1. "; c1.putdata();


cout << "\n\n* Conta Simples 2. ";c2.putdata();
cout << "\n\n* Conta Especial. "; c3.putdata();
cout << "\n\n* Conta Poupança. "; c4.putdata();
}

A função main() declara 4 objetos das diferentes classes: dois de conta


simples, um de conta especial e um de poupança. É certo que várias outras
114 Treinamento em Linguagem C++ Cap. 10

contas de cada categoria podem ser criadas. Em seguida, a função getdata() de


cada objeto é chamada para obter as informações sobre cada conta, e a função
putdata() de cada objeto é chamada para imprimir estas informações.
Eis a saída:
* Digite os dados da conta simples 1.
Nome: Antonio da Silva
No.Conta: 234
Saldo: 3456.67

* Digite os dados da conta simples 2.


Nome: Denise Pereira
No.Conta: 861
Saldo: 123.34

* Digite os dados da conta especial.


Nome: Ricardo Kitto
No.Conta: 523
Saldo: 9845.32
Limite: 100

* Digite os dados da conta-poupança.


Nome: Lucie Bousso
No.Conta: 148
Saldo: 5645.32
Taxa: 1.1

* Conta Simples 1.
Nome: Antonio da Silva
No.Conta: 234
Saldo: 3456.67

* Conta Simples 2.
Nome: Denise Pereira
No.Conta: 861
Saldo: 123.34
Cap. 10 Herança 115

* Conta Especial.
Nome: Ricardo Kitto
No.Conta: 523
Saldo: 9845.32
Limite: 100
Saldo Total: 9945.32

* Conta-Poupança.
Nome: Lucie Bousso
No.Conta: 148
Saldo: 5645.32
Taxa: 1.1
Saldo Total: 6209.85

Um programa mais sofisticado poderia usar uma matriz ou outros


modos mais complexos para manipular os dados, o que poderia acomodar um
grande número de contas.

CLASSE-BASE “ABSTRATA”

Observe que não declaramos nenhum objeto da classe conta. Ela foi usada como
uma classe genérica, com o único propósito de agir como base para as outras
classes.
A classe contaSimples opera de forma idêntica à classe conta, pois ela
não contém nenhum código adicional. Você poderia pensar que esta classe é
desnecessária, mas, estando ela separada, enfatizamos que a classe é descen-
dente da mesma fonte conta. Além disso, se no futuro decidirmos modificar a
classe contaSimples, não necessitaremos alterar a especificação de conta.
Classes usadas somente para derivar outras classes são geralmente
chamadas classes abstratas, o que indica que nenhuma instância (objeto) delas
é criada. Entretanto, o termo abstrato tem uma definição mais precisa quando
usado com funções virtuais, vistas no Capítulo 12.
116 Treinamento em Linguagem C++ Cap. 10

CONVERSÕES DE TIPOS ENTRE CLASSE-BASE E CLASSE DERIVADA

Visto que contaEspecial é um tipo de conta, faz sentido pensar em converter


um objeto da classe contaEspecial num objeto da classe conta.
C++ permite a conversão implícita de um objeto da classe derivada
num objeto da classe-base. Por exemplo:
conta C;
contaEspecial CE;

CE.getdata();

C = CE; //Converte contaEspecial em conta

Todos os membros do objeto C da classe-base recebem os valores dos


membros correspondentes do objeto CE da classe derivada. Entretanto, a
atribuição inversa não é permitida:
C = CE; //Erro. Não pode ser convertido

Como CE tem membros que C não tem, seus valores ficariam indefinidos.

NÍVEIS DE HERANÇA

Uma classe pode ser derivada de outra classe, que, por sua vez, é também uma
classe derivada.
class X
{ };
class Y : public X
{ };
class Z : public Y
{ };
Cap. 10 Herança 117

Neste exemplo, Y é derivada de X, e Z é derivada de Y. Este processo


pode ser estendido para qualquer número de níveis, K pode ser derivada de Z,
e assim por diante.
Como exemplo mais concreto, imagine que tenhamos decidido adicio-
nar uma característica especial às contas-poupança: conta-poupança de três
meses de aplicação. Toda conta deste tipo receberá um prêmio. Criaremos um
novo programa que incorpora objetos da classe premio.
Visto que premio é um tipo de poupanca, a classe premio é derivada
da classe poupanca.
A conta premio tem a seguinte característica adicional: a descrição do
prêmio combinado.
Eis a listagem:
//Cpremio.cpp
//Mostra herança múltipla
#include <iostream.h>
#include <stdio.h> //Para gets()
#include <iomanip.h>

const TAM = 80; //Tamanho máximo do nome

class conta
{
private:
char nome[TAM];//Nome do cliente
int Nconta; //Número da conta
float saldo; //Saldo
public:

void getdata()
{
cout << "\n Nome: "; gets(nome);
cout << " No.Conta: "; cin >> Nconta;
cout << " Saldo: "; cin >> saldo;
}
118 Treinamento em Linguagem C++ Cap. 10

void putdata()
{
cout << "\n Nome: " << nome;
cout << "\n No.Conta: " << Nconta;
cout << "\n Saldo: "
<< setiosflags(ios::fixed)
<< setprecision(2) << saldo;
}

float Saldo() { return saldo; }


};

class contaSimples : public conta


{};

class contaEspecial : public conta


{
private:
float limite;
public:
void getdata()
{ conta::getdata();
cout << " Limite: "; cin >> limite;
}
void putdata()
{ conta::putdata();
cout << "\n Limite: " << limite;
cout << "\n Saldo Total: "
<< setiosflags(ios::fixed)
<< setprecision(2)<< (Saldo()+limite);
}
};
Cap. 10 Herança 119

class Poupanca : public conta


{
private:
float taxa;
public:
void getdata()
{
conta::getdata();
cout << " Taxa: "; cin >> taxa;
}

void putdata()
{
conta::putdata();
cout << "\n Taxa: " << taxa;
cout << "\n Saldo Total: "
<< setiosflags(ios::fixed)
<< setprecision(2) << (Saldo()*taxa);
}
};

class contaPremio: public poupanca


{
private:
char prem[TAM];
public:
void getdata()
{
poupanca::getdata();
cout << " Prêmio: "; gets(prem);
}
void putdata()
{
poupanca::putdata();
cout << "\n Prêmio: " << prem;
}
};
120 Treinamento em Linguagem C++ Cap. 10

void main()
{
poupanca c1;
contaPremio c2;

cout << "\n* Digite os dados da conta poupança. ";


c1.getdata();

cout << "\n* Digite os dados da conta prêmio. ";


c2.getdata();

cout << "\n\n* Conta Poupança. ";


c1.putdata();

cout << "\n\n* Conta Prêmio. ";


c2.putdata();
}

Eis a saída:
* Digite os dados da conta poupança.
Nome: Lucie Bousso
No.Conta: 148
Saldo: 5645.32
Taxa: 1.1

* Digite os dados da conta prêmio.


Nome: Antonio da Silva
No.Conta: 234
Saldo: 3456.67
Taxa: 1.1
Prêmio: Seguro de vida
Cap. 10 Herança 121

* Conta Poupança.
Nome: Lucie Bousso
No.Conta: 148
Saldo: 5645.32
Taxa: 1.1
Saldo Total: 6209.85

* Conta Prêmio.
Nome: Antonio da Silva
No.Conta: 234
Saldo: 3456.67
Taxa: 1.1
Saldo Total: 3802.34
Prêmio: Seguro de vida

Observe que a hierarquia de classes pode ser descrita usando-se uma


estrutura de árvore:

CONTA

SIMPLES SIMPLES

ESPECIAL

PRÊMIO
122 Treinamento em Linguagem C++ Cap. 10

Cada nó representa uma subclasse que pode, por sua vez, ter qualquer
número de subclasses adicionais.

HERANÇA MÚLTIPLA

Uma classe pode herdar as características de mais de uma classe-base. Este


processo é chamado herança múltipla.
A construção de hierarquias de herança múltipla envolve mais comple-
xidade do que as hierarquias de herança simples. Esta complexidade diz
respeito ao desenho da construção das classes e não a sintaxe de uso. A sintaxe
de múltiplas heranças é similar à de uma única herança. Eis um exemplo:
class X
{ };
class Y
{ };
class Z : public X, public Y //Z é derivada de X e de Y
{ };

As classes-base das quais Z é derivada devem ser escritas após os


dois-pontos e separadas por vírgulas.

FUNÇÕES-MEMBRO EM HERANÇA MÚLTIPLA

Como exemplo de herança múltipla, suponha que você precise registrar os


imóveis de uma imobiliária. A imobiliária necessita de dois armazenamentos
diferentes: imóveis para a venda e imóveis para aluguel.
Suponha que você já tenha desenvolvido uma classe Cadastro para
outro propósito, e que serviria aqui, e uma classe Imovel que armazena os dados
de um imóvel.
Cap. 10 Herança 123

Você decide então, em vez de modificar estas classes, criar as classes


Venda e Aluguel e incorporar o imóvel e o cadastro do proprietário e do
inquilino usando herança múltipla.
Há mais um dado importante a ser armazenado: o tipo do imóvel. Um
imóvel pode ser residencial ou comercial (loja, conjunto comercial ou galpão).
Para implementar esta característica, você cria uma nova classe Tipo.
As informações sobre inquilino não são relevantes para a classe Venda.
Eis a listagem:
//IMOVEL.CPP
//Mostra herança múltipla
#include <iostream.h>
#include <stdio.h>
#include <iomanip.h>

class Cadastro
{
private:
char nome[30],fone[20];
public:
void getdata()
{
cout << "\n\tNome: "; gets(nome);
cout << "\tFone: "; gets(fone);
}

void putdata()
{
cout << "\n\tNome : " << nome;
cout << "\n\tFone : " << fone;
}
};

class Imovel
{
124 Treinamento em Linguagem C++ Cap. 10

private:
char end[30],bairro[20];
float AreaUtil, AreaTotal;
int quartos;
public:
void getdata()
{
cout << "\n\tEnd: "; gets(end);
cout << "\tBairro: "; gets(bairro);
cout << "\tÁrea Útil: "; cin >> AreaUtil;
cout << "\tÁrea Total: "; cin >> AreaTotal;
cout << "\tNo.Quartos: "; cin >> quartos;
}
void putdata()
{
cout << "\n\tEnd: " << end;
cout << "\n\tBairro: " << bairro;
cout << "\n\tÁrea Útil: "
<< setiosflags(ios::fixed)
<< setprecision(2) << AreaUtil;
cout << "\n\tÁrea Total: "
<< setiosflags(ios::fixed)
<< setprecision(2) << AreaTotal;
cout << "\n\tQuartos: " << quartos;
}
};

class Tipo
{
private:
char tipo[20]; //Residencial, Loja, Galpão etc.
public:
void getdata()
{
cout << "\tTipo: "; gets(tipo);
}
Cap. 10 Herança 125

void putdata()
{
cout << "\n\tTipo: " << tipo;
}
};

class Venda : private Cadastro,Imovel,Tipo


{
private:
float valor;
public:
void getdata()
{
cout << "\n ...Proprietário: ";
Cadastro::getdata();
cout << "\n ...Imóvel: ";
Imovel::getdata();
Tipo::getdata();
cout << "\tValor US$: "; cin >> valor;
}
void putdata()
{
cout << "\n ...Proprietário: ";
Cadastro::putdata();
cout << "\n ...Imóvel: ";
Imovel::putdata();
Tipo::putdata();
cout << "\n\tValor US$: " << valor;
}
};
class Aluguel : private Cadastro,Imovel,Tipo
{
private:
float aluguel;
126 Treinamento em Linguagem C++ Cap. 10

int prazo;
Cadastro proprietario;
public:
void getdata()
{
cout << "\n ...Proprietário: ";
proprietario.getdata();
cout << "\n ...Inquilino: ";
Cadastro::getdata();
cout << "\n ...Imóvel: ";
Imovel::getdata();
Tipo::getdata();
cout << "\tAluguel: "; cin >> aluguel;
cout << "\tPrazo do contrato: ";cin >> prazo;
}
void putdata()
{
cout << "\n ...Proprietário: ";
proprietario.putdata();
cout << "\n ...Inquilino: ";
Cadastro::putdata();
cout << "\n ...Imóvel: ";
Imovel::putdata();
Tipo::putdata();
cout << "\n\tAluguel: " << aluguel;
cout << "\n\tPrazo do contrato: " << prazo;
}
};

void main()
{
Venda v1;
Aluguel a1;

cout << "\n* Digite os dados do imóvel: Venda. ";


Cap. 10 Herança 127

v1.getdata();

cout << "\n* Digite os dados do imóvel: Aluguel. ";


a1.getdata();

cout << "\n\n* IMÓVEL PARA VENDA:";


v1.putdata();

cout << "\n\n* IMÓVEL PARA ALUGUEL:";


a1.putdata();
}

As funções getdata() e putdata() das classes Venda e Aluguel incorpo-


ram a chamada às funções correspondentes das classes Cadastro, Imovel e Tipo.
Eis a saída:
* Digite os dados do imóvel: Venda.
...Proprietário:
Nome: Rogerio Santana
Fone: 77-7777

...Imóvel:
End: Av. Rio Branco, 777
Bairro: Centro
Área Útil: 200
Área Total: 220
No.Quartos: 0
Tipo: Loja
Valor US$: 200000

* Digite os dados do imóvel: Aluguel.


...Proprietário:
Nome: Ana Maria Ferreira
Fone: 88-8888

...Inquilino:
128 Treinamento em Linguagem C++ Cap. 10

Nome: Silvia da Silva


Fone: 99-9999

...Imóvel:
End: Av. Angélica 999
Bairro: Sta. Cecília
Área Útil: 50
Área Total: 70
No.Quartos: 2
Tipo: Residencial
Aluguel: 80000
Prazo do contrato: 2

* IMÓVEL PARA VENDA:


...Proprietário:
Nome: Rogerio Santana
Fone: 77-7777

...Imóvel:
End: Av. Rio Branco, 777
Bairro: Centro
Área Útil: 200
Área Total: 220
No.Quartos: 0
Tipo: Loja
Valor US$: 200000

* IMÓVEL PARA ALUGUEL:


...Proprietário:
Nome: Ana Maria Ferreira
Fone: 88-8888

...Inquilino:
Nome: Silvia da Silva
Fone: 99-9999
Cap. 10 Herança 129

...Imóvel:
End: Av. Angélica 999
Bairro: Sta. Cecília
Área Útil: 50
Área Total: 70
No.Quartos: 2
Tipo: Residencial
Aluguel: 80000
Prazo do contrato: 2

DERIVAÇÃO private NO PROGRAMA IMOVEL.CPP

As classes Venda e Aluguel do programa IMOVEL.CPP são derivadas privadas


das classes Cadastro, Imovel e Tipo. Não há necessidade de usar derivação
pública, pois os objetos destas classes nunca chamam rotinas das classes-base.

OBJETOS DE UMA CLASSE COMO MEMBROS DE OUTRA CLASSE

Observe a classe Aluguel do programa IMOVEL.CPP. Esta classe deve arma-


zenar o cadastro do inquilino e também o cadastro do proprietário. Usando
herança, a classe Aluguel adquire todas as características da classe Cadastro.
Entretanto, só haverá espaço suficiente para os dados de um único cadastro.
Não é possível, por meio de herança múltipla, herdar duas vezes da
mesma classe.
Outra maneira de uma classe adquirir as características de outra é
declarar um de seus membros como um objeto da outra classe.
Na classe Aluguel declaramos um objeto proprietario da classe Cadastro.
130 Treinamento em Linguagem C++ Cap. 10

Os membros deste objeto são acessados internamente por meio do


operador ponto.
class Aluguel : private Cadastro,Imovel,Tipo
{
private:
float aluguel;
int prazo;
Cadastro proprietario;
public:
void getdata()
{
cout << "\n ...Proprietário: ";
proprietario.getdata();
cout << "\n ...Inquilino: ";
Cadastro::getdata();
cout << "\n ...Imóvel: ";
Imovel::getdata();
Tipo::getdata();
cout << "\tAluguel: "; cin >> aluguel;
cout << "\tPrazo do contrato: ";cin >> prazo;
}
void putdata()
{
cout << "\n ...Proprietário: ";
proprietario.putdata();
cout << "\n ...Inquilino: ";
Cadastro::putdata();
cout << "\n ...Imóvel: ";
Imovel::putdata();
Tipo::putdata();
cout << "\n\tAluguel: " << aluguel;
cout << "\n\tPrazo do contrato: " << prazo;
}
};
Cap. 10 Herança 131

Em algumas situações, herança e objetos membros podem servir para


o mesmo propósito. Por exemplo, poderíamos ter escolhido declarar dois
objetos da classe Cadastro em vez de declarar a classe Aluguel derivada
desta classe.

AMBIGÜIDADE EM HERANÇA MÚLTIPLA

Alguns tipos de problemas podem aparecer em certas situações envolvendo


herança múltipla. O mais comum é quando duas classes-base têm, cada uma
delas, uma função de mesmo nome, enquanto a classe derivada destas duas não
tem nenhuma função com este nome.
Quando a função é acessada por meio de um objeto da classe derivada,
o compilador não reconhecerá qual das duas estará sendo chamada.
Para informar ao compilador qual das duas funções está sendo solici-
tada, devemos utilizar o operador de resolução de escopo. Eis um exemplo:
//AMBIGUO.CPP
//Mostra ambigüidade em herança múltipla
#include <iostream.h>

class X
{
public:
void print() { cout << "\nClasse X"; }
};

class Y
{
public:
void print() { cout << "\nClasse Y"; }
};

class Z : public X, Y
{};
132 Treinamento em Linguagem C++ Cap. 10

void main()
{
Z obj;
obj.print(); //Ambigüidade - Erro do compilador
obj.X::print();//OK
obj.Y::print();//OK
}

A instrução
obj.X::print();

solicita a execução da função membro print() da classe X, e a instrução


obj.Y::print();

solicita a execução da função membro print() da classe Y.


O operador de resolução de escopo resolve o problema da ambigüidade.

CONSTRUTORES EM HERANÇA MÚLTIPLA

IMOVEL.CPP não tem nenhum construtor. Vamos modificar as classes deste


programa para exemplificar o uso de construtores e ver como eles são manipu-
lados com herança múltipla.
Eis a listagem:
//IMOVEL1.CPP
//Mostra o uso de construtores com herança múltipla
#include <iostream.h>
#include <stdio.h>
#include <iomanip.h>
#include <string.h>

class Cadastro
{
Cap. 10 Herança 133

private:
char nome[30],fone[20];
public:
Cadastro() { nome[0]=fone[0]=’\0’;}

Cadastro(char n[], char f[])


{ strcpy(nome,n);
strcpy(fone,f);
}

void getdata()
{ cout << "\n\tNome: "; gets(nome);
cout << "\tFone: "; gets(fone);
}

void putdata()
{ cout << "\n\tNome : " << nome;
cout << "\n\tFone : " << fone;
}
};
class Imovel
{
private:
char end[30],bairro[20];
float AreaUtil, AreaTotal;
int quartos;
public:
Imovel()
{ end[0]=bairro[0]=’\0’;
AreaUtil=AreaTotal=0.0;
quartos=0;
}

Imovel(char e[],char b[],float au,float at, int q)


{ strcpy(end,e);
134 Treinamento em Linguagem C++ Cap. 10

strcpy(bairro,b);
AreaUtil=au;
AreaTotal=at;
quartos=q;
}
void getdata()
{
cout << "\n\tEnd: "; gets(end);
cout << "\tBairro: "; gets(bairro);
cout << "\tÁrea Útil: "; cin >> AreaUtil;
cout << "\tÁrea Total: "; cin >> AreaTotal;
cout << "\tNo.Quartos: "; cin >> quartos;
}
void putdata()
{
cout << "\n\tEnd: " << end;
cout << "\n\tBairro: " << bairro;
cout << "\n\tÁrea Útil: "
<< setiosflags(ios::fixed)
<< setprecision(2) << AreaUtil;
cout << "\n\tÁrea Total: "
<< setiosflags(ios::fixed)
<< setprecision(2) << AreaTotal;
cout << "\n\tQuartos: " << quartos;
}
};

class Tipo
{
private:
char tipo[20]; //Residencial, Loja, Galpão etc.
public:
Tipo() { tipo[0]=’\0’;}
Tipo(char t[])
{ strcpy(tipo,t); }
Cap. 10 Herança 135

void getdata()
{
cout << "\tTipo: "; gets(tipo);
}
void putdata()
{
cout << "\n\tTipo: " << tipo;
}

};

class Venda : private Cadastro,Imovel,Tipo


{
private:
float valor;
public:
Venda() : Cadastro(), Imovel(), Tipo()
{ valor=0.0; }

Venda(char n[],char f[],char e[],char b[],


float au,float at,int q,char t[],float v)
: Cadastro(n, f),Imovel(e,b,au,at,q),Tipo(t)
{ valor=v; }

void getdata()
{
cout << "\n ...Proprietário: ";
Cadastro::getdata();
cout << "\n ...Imóvel: ";
Imovel::getdata();
Tipo::getdata();
cout << "\tValor US$: "; cin >> valor;
}
136 Treinamento em Linguagem C++ Cap. 10

void putdata()
{
cout << "\n ...Proprietário: ";
Cadastro::putdata();
cout << "\n ...Imóvel: ";
Imovel::putdata();
Tipo::putdata();
cout << "\n\tValor US$: " << valor;
}
};

void main()
{
Venda v("André Serafin","33-3333",
"Rua Rocha 33","Bela Vista",50.0,75.0,2,
"Residencial",80000.0);

cout << "\n\n* IMÓVEL PARA VENDA:";


v.putdata();
}

Decidimos remover a classe Aluguel para diminuir a listagem.


A novidade deste programa é o uso de construtores da classe derivada
Venda. Estes construtores chamam os construtores apropriados das classes
Cadastro, Imovel e Tipo.

CONSTRUTORES SEM ARGUMENTOS COM HERANÇA MÚLTIPLA

O construtor sem argumentos da classe Venda é o seguinte:


Venda() : Cadastro(), Imovel(), Tipo()
{ valor=0.0; }

Este construtor chama os três construtores das classes-base. Os nomes


dos construtores chamados são colocados após os dois-pontos e separados por
vírgulas. A ordem de chamada é a mesma da ordem em que eles aparecem escritos.
Cap. 10 Herança 137

CONSTRUTORES DE MÚLTIPLOS ARGUMENTOS COM HERANÇA


MÚLTIPLA

O construtor com argumentos da classe Venda é o seguinte:


Venda(char n[],char f[], //Args da classe Cadastro
char e[],char b[],float au, //Args da classe Imovel
float at,int q,
char t[], //Args da classe Tipo
float v)
: Cadastro(n, f), //Chamada ao construtor Cadastro()
Imovel(e,b,au,at,q), //Chamada ao construtor Imovel()
Tipo(t) //Chamada ao construtor Tipo()
{ valor=v; } //Inicialização dos valores internos

Este construtor deve receber os argumentos necessários a todos os


construtores das classes-base chamados por ele. Adicionalmente, ele recebe um
argumento para suas inicializações internas. Da mesma forma que o anterior, a
chamada aos construtores ocorre na ordem em que eles estão colocados.

REVISÃO

1. O processo de herança permite a uma classe herdar todas as características


de outra classe.
2. Uma classe é dita derivada quando recebe as características de outra classe.
A classe que transmitiu é chamada base.
3. A classe derivada pode incluir características próprias, além das adquiridas
por herança. Por meio de heranças é possível ampliar as propriedades de
classes existentes e adaptá-las a novas situações.
138 Treinamento em Linguagem C++ Cap. 10

4. Para utilizar uma classe como base para a derivação de outras, é necessário
ter somente o seu código-objeto. O código-fonte é dispensável.
5. Dados ou funções de uma classe-base precedidos da palavra protected
podem ser acessados por uma classe derivada, mas não por objetos da classe
derivada.
6. Uma classe pode ter derivação pública ou privada de uma classe-base. Os
objetos de uma classe de derivação pública podem acessar os membros
públicos da classe-base, enquanto os objetos de uma classe de derivação
privada não podem.
7. O construtor de uma classe-base pode ser chamado pelo construtor da classe
derivada utilizando dois-pontos (:) após a lista de argumentos do construtor
da classe derivada, seguidos do nome e dos argumentos do construtor da
classe-base.
8. Para que uma função da classe derivada possa executar uma função da
classe-base de mesmo nome, deve-se utilizar o operador de resolução de
escopo (::) precedido do nome da classe-base.
9. O operador de resolução de escopo (::) permite que especifiquemos exata-
mente qual é a classe da função que queremos executar.
10. Qualquer classe, até mesmo outra classe derivada, pode servir de base para
uma classe derivada.
11. Você pode criar hierarquias de classes onde a classe derivada de uma
torna-se a classe-base de outra. Não há limite lógico para a extensão dessa
cadeia de classes inter-relacionadas.
12. Uma classe pode ser derivada de mais de uma classe-base. Este processo é
chamado herança múltipla.
13. Uma classe pode conter um membro que seja um objeto de outra classe.
Cap. 10 Herança 139

EXERCÍCIOS

1. Herança é um processo que permite:


a) a inclusão de um objeto dentro de outro;
b) transformar classes genéricas em classes mais específicas;
c) adicionar propriedades a uma classe existente sem reescrevê-la;
d) relacionar objetos por meio de seus argumentos.
2. As vantagens do uso de herança incluem:
a) aumento da funcionalidade de códigos existentes;
b) distribuição e uso de bibliotecas;
c) concepção de programas dentro de uma hierarquia que mostra quais
conceitos compartilham características comuns;
d) não-reescrita de código.
3. Para que membros de uma classe-base possam ser acessados por membros
da classe derivada, eles devem ser:
a) public;
b) protected;
c) private;
d) todas as anteriores.
4. Qual é a diferença entre a derivação pública e a derivação privada?
5. Assuma que a classe A é derivada pública da classe B. Um objeto da classe
A pode acessar:
a) membros públicos de A;
b) membros protegidos de A;
c) membros privados de A;
d) membros públicos de B;
140 Treinamento em Linguagem C++ Cap. 10

e) membros protegidos de B;
f) membros privados de B.
6. Assuma que a classe A é derivada privada da classe B. Um objeto da classe
A pode acessar:
a) membros públicos de A;
b) membros protegidos de A;
c) membros privados de A;
d) membros públicos de B;
e) membros protegidos de B;
f) membros privados de B.
7. Os membros de uma classe-base podem acessar:
a) membros públicos da classe derivada;
b) membros protegidos da classe derivada;
c) membros privados da classe derivada;
d) nenhuma das anteriores.
8. Os membros de uma classe derivada podem acessar:
a) membros públicos da classe-base;
b) membros protegidos da classe-base;
c) membros privados da classe-base;
d) nenhuma das anteriores.
9. Escreva a primeira linha da definição da classe Jose como derivada pública
da classe Homem.
10. Para derivar uma classe de outra já existente, deve-se:
a) alterar a classe existente;
b) usar o código-objeto da classe existente;
Cap. 10 Herança 141

c) reescrever a classe existente;


d) nenhuma das anteriores.
11. Se uma classe-base contém uma função chamada basef() e a classe derivada
não possui nenhuma função com este nome, responda:
a) Em que situação um objeto da classe derivada pode acessar basef()?
b) Em que situação um objeto da classe derivada não pode acessar basef()?
12. Se uma classe-base contém a função basef() e a classe derivada também
contém uma função com este nome, um objeto da classe derivada:
a) pode acessar a função-membro basef() da classe-base;
b) não pode acessar a função-membro basef() da classe-base;
c) pode acessar a função-membro basef() da classe derivada;
d) não pode acessar a função-membro basef() da classe derivada.
13. Qual das seguintes instruções executa a função-membro basef(), da classe-
base BAS, a partir de uma função-membro da classe derivada DERIV?
a) basef();
b) BAS::basef();
c) DERIV::basef();
d) BAS:basef();
14. Qual das seguintes instruções executa a função-membro basef(), da classe
derivada DERIV, a partir de uma função-membro da classe derivada
DERIV?
a) basef();
b) BAS::basef();
c) DERIV::basef();
d) BAS:basef();
142 Treinamento em Linguagem C++ Cap. 10

15. Um objeto de uma classe derivada contém:


a) todos os membros da classe-base;
b) somente os membros públicos da classe-base;
c) somente os membros protegidos da classe-base;
d) os itens b e c são verdadeiros.
16. Escreva a primeira linha da definição de um construtor sem argumentos da
classe A derivada da classe B.
17. Verdadeiro ou falso: Se nenhum construtor existir na classe derivada,
objetos desta classe usarão o construtor sem argumentos da classe-base.
18. Verdadeiro ou falso: Se nenhum construtor existir na classe derivada,
objetos desta classe poderão inicializar os dados da classe-base usando o
construtor com argumentos da classe-base.
19. Uma classe é dita “abstrata” quando:
a) nenhum objeto dela é declarado;
b) é representada apenas mentalmente;
c) é usada somente como base para outras classes;
d) é definida de forma obscura.
20. A conversão de tipos implícita é usada para:
a) converter objetos da classe derivada em objetos da classe-base;
b) converter objetos da classe-base em objetos da classe derivada;
c) converter objetos da classe derivada em objetos da classe-base e vice-versa;
d) não pode ser usada para conversão de objetos.
21. Verdadeiro ou falso: Uma classe derivada não pode servir de base para
outra classe.
22. O que é herança múltipla?
23. Escreva a primeira linha que define a classe A derivada da classe B e da
classe C.
Cap. 10 Herança 143

24. Verdadeiro ou falso: Se A é derivada de B, então C não pode ser derivada


de A e de B.
25. Verdadeiro ou falso: Um objeto de uma classe pode ser membro de outra classe.
26. Crie uma classe Empresa capaz de armazenar os dados de uma empresa
(Nome, End, Cidade, Estado, CEP e Fone). Inclua um construtor sem
argumentos e um que receba os dados como argumentos e os inicialize.
Escreva duas funções, uma para fazer a interface com o usuário da entrada
de dados, getdata(), e outra para imprimir os dados, putdata().
27. Use a classe Empresa como base para criar a classe Restaurante. Inclua o
tipo de comida, o preço médio de um prato, duas funções construtoras, a
interface de entrada de dados, getdata(), e a função que imprima os dados,
putdata(). Construa um programa para testar a classe Restaurante.
28. Imagine que você deva escrever um programa para armazenar veículos.
Primeiramente, crie a classe Motor que contém NumCilindro (int) e Poten-
cia (int). Inclua um construtor sem argumentos que inicialize os dados com
zeros e um construtor que inicialize os dados com os valores recebidos como
argumento. Acrescente uma função para a entrada de dados, getdata(), e
uma função que imprima os dados, putdata().
29. Escreva a classe Veiculo contendo Peso em quilos (int), VelocMax em km/h
(int) e Preco em US$ (float). Inclua um construtor sem argumentos que
inicialize os dados com zeros e um construtor que inicialize os dados com
os valores recebidos como argumento. Acrescente uma função para a
entrada de dados, getdata(), e uma função que imprima os dados, putdata().
30. Crie a classe CarroPasseio usando as classes Motor e Veiculo como base.
Inclua Cor (string) e Modelo (string). Inclua um construtor sem argumentos
que inicialize os dados com zeros e um construtor que inicialize os dados
com os valores recebidos como argumento. Acrescente uma função para a
entrada de dados, getdata(), e uma função que imprima os dados, putdata().
31. Crie a classe Caminhao derivada das classes Motor e Veiculo. Inclua
Toneladas (carga máxima), AlturaMax (int) e Comprimento (int). Inclua
um construtor sem argumentos que inicialize os dados com zeros e um
construtor que inicialize os dados com os valores recebidos como argumen-
to. Acrescente uma função para a entrada de dados, getdata(), e uma função
que imprima os dados, putdata().
32. Crie um programa para testar as classes dos exercícios 28, 29, 30 e 31.
MAKRON
Books
Capítulo 11

PONTEIROS

Ponteiros
11.Ponteiros
Os ponteiros são vistos pela maioria das pessoas como um dos tópicos mais
difíceis de C++. Existem várias razões para isto. Primeiro, os conceitos embuti-
dos em ponteiros podem ser novos para muitos programadores, visto que não
são comuns em linguagens de alto nível como BASIC. Segundo, os símbolos
usados para notação de ponteiros em C++ não são tão claros quanto poderiam.
Por exemplo, o mesmo símbolo é usado para duas finalidades diferentes.
Conceitualmente, os ponteiros podem ser difíceis, mas não muito.
A manipulação de ponteiros em C++ é similar à manipulação de
ponteiros C. O nosso objetivo neste capítulo é o de expor tão claramente quanto
possível como trabalhar com ponteiros.

O QUE SÃO PONTEIROS?

Um ponteiro é um endereço de memória. Seu valor indica onde uma variável


está armazenada, não o que está armazenado. Um ponteiro proporciona um
modo de acesso a uma variável sem referenciá-la diretamente.

144
Cap. 11 Ponteiros 145

POR QUE OS PONTEIROS SÃO USADOS?

Para dominar a linguagem C++, é essencial dominar ponteiros. Os ponteiros


são usados em situações em que o uso de uma variável é difícil ou indesejável.
Algumas razões para o uso de ponteiros são:
• manipular elementos de matrizes;
• receber argumentos em funções que necessitem modificar o argu-
mento original;
• passar strings de uma função para outra; usá-los no lugar de
matrizes;
• criar estruturas de dados complexas, como listas encadeadas e
árvores binárias, onde um item deve conter referências a outro;
• alocar e desalocar memória do sistema.

PONTEIROS VARIÁVEIS

Em C++ há um tipo especial de variável que foi concebida para conter o


endereço de outra variável. É chamada ponteiro variável.
Um ponteiro variável armazena um endereço de memória. Este ende-
reço é a localização de uma outra variável. Dizemos que uma variável aponta
para outra variável quando a primeira contém o endereço da segunda.

ENDEREÇOS DE MEMÓRIA

A memória de seu computador é dividida em bytes, e estes bytes são numerados


de 0 até o limite de memória de sua máquina. Estes números são chamados
endereços de bytes. Um endereço é a referência que o computador usa para
localizar variáveis.
146 Treinamento em Linguagem C++ Cap. 11

Toda variável ocupa uma certa localização na memória, e seu endereço


é o do primeiro byte ocupado por ela.
Nossos programas, quando carregados para a memória, ocupam uma
certa parte dela. Desta forma, toda variável e toda função de nosso programa
começam em um endereço particular, e este endereço é chamado endereço da
variável ou função.

O OPERADOR DE ENDEREÇOS &

Para conhecer o endereço ocupado por uma variável usamos o operador de


endereços (&).
Este operador é um operador unário e seu operando deve ser o nome
de uma variável, e o resultado é o seu endereço. Eis um pequeno programa que
mostra o seu uso:
//Mostra o uso do operador de endereços
#include <iostream.h>
void main()
{
int i,j,k;
cout << "\n" << &i;
cout << "\n" << &j;
cout << "\n" << &k;
}

Eis a saída:
0xfff4 <--- Endereço de i
0xfff2 <--- Endereço de j
0xfff0 <--- Endereço de k

Este programa declara três variáveis inteiras e imprime o endereço de


cada uma delas. O endereço ocupado por essas variáveis dependerá de vários
fatores como o tamanho do sistema operacional, se há ou não outros programas
residentes na memória etc. Por estas razões você pode encontrar números
diferentes quando executar o programa.
Cap. 11 Ponteiros 147

O operador << imprime endereços em hexadecimal, precedidos pelo


prefixo 0x. Observe que cada endereço difere do próximo por exatamente dois
bytes. Isto ocorre porque inteiros ocupam dois bytes de memória. Esta diferença
depende de cada tipo de variável. Por exemplo, se você usar variáveis do tipo
float, os endereços teriam uma diferença de 4 bytes de um para o outro.
Os endereços aparecem em ordem descendente, pois variáveis automá-
ticas são guardadas na pilha. Se as variáveis fossem externas teriam endereços
ascendentes, pois essas variáveis são armazenadas no heap (resto da memória
livre).
Não confunda o operador de endereços, o qual precede o nome de uma
variável com o operador de referências (&) que segue o nome do tipo de uma
variável, usado para criar referências. A falta de símbolos, ocasionada pela
grande quantidade de operadores em C++, faz com que vários operadores
diferentes tenham o mesmo símbolo.
O operador de endereço (&) só pode ser usado com nomes de variáveis.
Construções tais como:
&(i+1) //ERRADO
&5 //ERRADO

são incorretas.

REVISÃO: PASSANDO ARGUMENTOS POR REFERÊNCIA

Antes de começar, vamos rever o uso do operador de referência e a passagem


de argumentos para funções por referência, já vistos no Capítulo 5, no Módulo 1.
O operador de referência cria outro nome para uma variável já existen-
te. Toda operação em qualquer dos nomes tem o mesmo resultado.
Uma referência não é uma cópia da variável a que se refere. É a mesma
variável sob diferentes nomes.
Quando argumentos são passados por valor, a função chamada cria
novas variáveis do mesmo tipo dos argumentos e copia nelas o valor dos
argumentos passados. Desta forma, a função não tem acesso às variáveis
originais da função que chamou, portanto não as pode modificar.
148 Treinamento em Linguagem C++ Cap. 11

A principal vantagem da passagem por referência é a de que a função


pode acessar as variáveis da função que chamou. Além desse benefício, este
mecanismo possibilita que uma função retorne mais de um valor para a função
que chama. Os valores a serem retornados são colocados em referências de
variáveis da função chamadora.
Verifique novamente o exemplo do uso simples de referência como
argumento de uma função.
//REFER.CPP
//Mostra passagem de argumentos por referência
#include <iostream.h>

void reajusta20( float& p, float& r);

void main()
{
float preco,val_reaj;

do
{
cout << "\n\nInsira o preço atual: ";
cin >> preco;
reajusta20(preco,val_reaj);
cout << "Preço novo = " << preco
<< "\nAumento = " << val_reaj;
} while( preco != 0.0);
}

//reajusta20()
//Reajusta o preço em 20%
void reajusta20(float& p, float& r)
{
r = p * 0.2;
p *= 1.2;
}
Cap. 11 Ponteiros 149

O programa solicita ao usuário que insira o preço atual de uma


mercadoria, modificando o preço para um valor reajustado em 20% e calculando
o valor do aumento.
Funções que recebem argumentos por referência utilizam o operador
& somente na definição do tipo do argumento.
Observe que a chamada a uma função que recebe uma referência é
idêntica à chamada às funções em que os argumentos são passados por valor.
A declaração
float& p, float& r

indica que p e r são outros nomes para as variáveis passadas como argumento
pela função que chama. Em outras palavras, quando usamos p e r, estamos
realmente usando preco e val_reaj de main().

PASSANDO ARGUMENTOS POR REFERÊNCIA COM PONTEIROS

Há três maneiras de passar argumentos para uma função: por valor, por
referência e por ponteiro. Se uma função deseja alterar variáveis da função
chamadora, estas variáveis não podem ser passadas por valor.
Em situações como esta, podem-se usar referências ou ponteiros. Para
usar ponteiros como argumentos devemos seguir dois passos:
Primeiro, a função chamadora, em vez de passar valores para a função
chamada, passa endereços usando o operador de endereços. Estes endereços
são de variáveis da função chamadora onde queremos que a função coloque
novos valores.
Segundo lugar, a função chamada deve criar variáveis para armazenar
os endereços enviados pela função chamadora. Estas variáveis são ponteiros.
O próximo exemplo mostra uma situação equivalente a do programa
anterior, onde ponteiros são usados no lugar de referências.
150 Treinamento em Linguagem C++ Cap. 11

Eis a alteração:
//PONTEIRO.CPP
//Mostra passagem de argumentos por referência com ponteiros
#include <iostream.h>

void reajusta20( float *p, float *r);

void main()
{
float preco,val_reaj;

do
{
cout << "\n\nInsira o preço atual: ";
cin >> preco;
reajusta20(&preco,&val_reaj);
cout << "Preço novo = " << preco
<< "\nAumento = " << val_reaj;
} while( preco != 0.0);
}

//reajusta20()
//Reajusta o preço em 20%
void reajusta20(float *p, float *r)
{
*r = *p * 0.2;
*p *= 1.2;
}

O programa faz exatamente o mesmo que a versão original.


A função reajusta20() recebe como argumento dois ponteiros para float.
O seu protótipo é o seguinte:
void reajusta20( float *p, float *r);
Cap. 11 Ponteiros 151

VARIÁVEIS QUE ARMAZENAM ENDEREÇOS

Você já usou variáveis para armazenar números inteiros, caracteres, números


em ponto flutuante etc. Os endereços são armazenados de modo semelhante.
Uma variável que armazena um endereço é chamada ponteiro. Observe que um
ponteiro para um float não é do tipo float, mas sim do tipo float *. O asterisco
faz parte do nome do tipo e indica Ponteiro para.
A declaração dos argumentos da função reajusta20()
float *p, float *r

indica que *p e *r são do tipo float e que p e r são ponteiros para variáveis float.
Na verdade, usar *p é uma maneira indireta de usar a variável preco
de main(). Toda alteração feita em *p afetará diretamente preco. Como p contém
o endereço da variável preco, dizemos que p aponta para preco.

O OPERADOR INDIRETO (*)

O operador indireto (*) é um operador unário que tem como operando um


endereço ou ponteiro e resulta no conteúdo da variável localizada no endereço
(ponteiro) operando. Em outras palavras, resulta o valor da variável apontada.

PASSANDO ENDEREÇOS PARA A FUNÇÃO

A nossa função main() chama a função reajusta20() por meio da seguinte


instrução:
reajusta20(&preco,&val_reaj);

Os endereços das variáveis preco e val_reaj são enviados como argu-


mentos. O operador de endereços (&) é usado para obter estes endereços.
152 Treinamento em Linguagem C++ Cap. 11

A função reajusta20() recebe estes valores em ponteiros. Para que esta


função possa acessar as variáveis apontadas, deve não somente conhecer o
endereço da variável como também o seu tipo, o qual indica o tamanho em
bytes e a forma de armazenamento. O tipo da variável apontada é dado na
declaração do ponteiro:
float *p, float *r

Desta forma a função pode usar instruções como:


*r = *p * 0.2;
*p *= 1.2;

que modificam indiretamente as variáveis preco e val_reaj. Isto é indireto


porque a função não tem acesso aos nomes destas variáveis, e sim aos seus
endereços. A conclusão é que main() acessa as variáveis preco e val_reaj de um
certo modo, enquanto reajusta20() as acessa de outro. A função main() chama
as variáveis por preco e val_reaj, enquanto reajusta20() as chama por *p e *r.

Na declaração, o símbolo (*) indica o tipo apontado, enquanto em


outras instruções indica a variável apontada por.

Uma vez conhecidos os endereços e os tipos das variáveis do programa


chamador, a função pode não somente colocar valores nestas variáveis como
também tomar o valor já armazenado nelas. Ponteiros podem ser usados não
somente para que a função passe valores para o programa chamador, mas também
para que o programa chamador passe valores para a função.
Na instrução:
*r = *p * 0.2;

estamos usando o valor original da variável val_reaj, por meio de *p, multipli-
cando este valor por 0.2 e atribuindo o resultado a variável preco indiretamente
por meio de *r.
Cap. 11 Ponteiros 153

PONTEIROS SEM FUNÇÕES

O exemplo que apresentamos usou ponteiros como argumentos de funções. O


programa seguinte cria ponteiros como variáveis automáticas dentro de funções.
//PTRVAR.CPP
//Mostra o uso de ponteiros automáticos
#include <iostream.h>
void main()
{
int x=4, y=7;
cout << "\n&x = " << &x << "\t x = " << x;
cout << "\n&y = " << &y << "\t y = " << y;

int *px,*py;
px = &x;
py = &y;
cout << "\n\npx = " << px << "\t*px = " << *px;
cout << "\npy = " << py << "\t*py = " << *py;
}

Eis a saída:
&x = 0xfff4 x = 4
&y = 0xfff2 y = 7

px = 0xfff4 *px = 4
py = 0xfff2 *py = 7

A instrução:
int *px,*py;

declara px e py ponteiros para variáveis int. Quando um ponteiro não é


inicializado na instrução de sua declaração, o compilador inicializa-o com o
endereço zero (NULL). C++ garante que NULL não é um endereço válido, então
antes de usá-los devemos atribuir a eles algum endereço válido; isto é feito pelas
instruções:
154 Treinamento em Linguagem C++ Cap. 11

px = &x;
py = &y;

Um ponteiro pode ser inicializado na mesma instrução de sua declaração:


int *px=&x, *py=&y;

Observe que estamos atribuindo &x e &y a px e py, respectivamente,


e não a *px e *py. O asterisco, na declaração, faz parte do nome do tipo.
C++ permite ponteiros de qualquer tipo e as sintaxes de declaração
possíveis são as seguintes:
• Operador indireto junto ao nome da variável.
int *p; //Ponteiro int
char *p; //Ponteiro char
String *p; //Ponteiro para um tipo
//definido pelo usuário

• Operador indireto junto ao nome do tipo.


int* p; //Ponteiro int
char* p; //Ponteiro char
String* p; //Ponteiro para um tipo
//definido pelo usuário

• Se vários ponteiros são declarados numa mesma instrução, o tipo


deve ser inserido somente uma única vez.
int* p, * p1, * p2;
int *p, *p1, *p2;

• Inicializando ponteiros.
int i;
int *pi=&i;
int *pj, *pi=&i, *px;

• Ponteiros e variáveis simples declarados numa única instrução.


int *p, i, j, *q;
int *px=&x, i, j=5, *q;
Cap. 11 Ponteiros 155

PONTEIROS E VARIÁVEIS APONTADAS

Você pode usar ponteiros para executar qualquer operação na variável apontada.
//PTRVAR1.CPP
//Mostra o uso de ponteiros automáticos
#include <iostream.h>
void main()
{
int x,y;
int *px=&x; //Inicializa p com o endereço de x

*p=14; //O mesmo que x=14


y = *p; //O mesmo que y=x

cout << "\ny = " y;


}

Neste programa, usamos um ponteiro para atribuir um valor à variável


x e depois atribuímos este valor a y por meio do ponteiro. O operador indireto,
precedendo o nome do ponteiro em instruções como:
y = *p;

indica o valor da variável apontada.

OPERAÇÕES COM PONTEIROS

C++ permite várias operações básicas com ponteiros. O nosso próximo exemplo
mostra essas possibilidades. O programa imprime os resultados de cada opera-
ção, o valor do ponteiro, o valor da variável apontada e o endereço do próprio
ponteiro.
//OPERAPTR.CPP
//Mostra as operações possíveis com ponteiros
156 Treinamento em Linguagem C++ Cap. 11

#include <iostream.h>
void main()
{
int x=5, y=6;
int *px, *py;

px = &x; //Atribuições
py = &y;

if( px < py) //Comparações


cout << "\npy-px= " << (py-px);
else
cout << "\npx-py= " << (px-py);

cout << "\npx = " << px


<<", *px = " << *px //Op.Indireto
<<", &px = " << &px; //Op.Endereços

cout << "\npy = " << py


<<", *py = " << *py
<<", &py = " << &py;

py++; //Incremento

cout << "\npy = " << py


<<", *py = " << *py
<<", &py = " << &py;

px = py + 3;

cout << "\npx = " << px


<<", *px = " << *px //Op.Indireto
<<", &px = " << &px; //Op.Endereços
Cap. 11 Ponteiros 157

cout << "\npx-py= " << (px-py);


}

A saída é:
px-py= 1
px = 0xfff4, *px = 5, &px = 0xfff0
py = 0xfff2, *py = 6, &py = 0xffee
py = 0xfff4, *py = 5, &py = 0xffee
px = 0xfffa, *px = 0, &px = 0xfff0
px-py= 3

ATRIBUIÇÃO

Um endereço pode ser atribuído a um ponteiro. Geralmente fazemos isto


usando o operador de endereços (&) junto ao nome de uma variável. No nosso
exemplo, atribuímos a px o endereço de x e a py o endereço de y.
px = &x; //Atribuições
py = &y;

OPERAÇÃO INDIRETA

O operador indireto (*) precedendo o nome do ponteiro resulta no valor da


variável apontada.

TRAZENDO O ENDEREÇO DO PONTEIRO

Como todas as variáveis, os ponteiros têm um endereço e um valor. O operador


(&) precedendo o nome do ponteiro resulta na posição de memória onde o
ponteiro está localizado.
158 Treinamento em Linguagem C++ Cap. 11

O nome do ponteiro indica o valor contido nele, isto é, o endereço para


o qual ele aponta (o endereço da variável apontada).

INCREMENTANDO UM PONTEIRO

Podemos incrementar um ponteiro por meio de adição regular ou do operador


de incremento. Incrementar um ponteiro acarreta sua movimentação para o
próximo tipo apontado; isto é, se px é um ponteiro para uma variável int, depois
de executar a instrução:
px++;

o valor de px será incrementado de um int (dois bytes). Cada vez que px é


incrementado, apontará para o próximo int da memória. A mesma idéia é
verdadeira para decremento. Toda vez que px é decrementado de uma unidade,
será decrementado de um tipo apontado, neste caso int.
Você pode subtrair ou adicionar números de e para ponteiros. A
instrução:
px = py + 3;

fará com que px caminhe três inteiros adiante de py.

DIFERENÇA

Você pode encontrar a diferença entre dois ponteiros. Esta diferença será
expressa em número de tipo apontado entre eles. Então, se py tem um valor
4000 e px o valor 3998, a expressão:
py-px

resulta em 1 quando px e py são ponteiros int.


Cap. 11 Ponteiros 159

COMPARAÇÕES ENTRE PONTEIROS

Testes relacionais com >, <, >=, <=, == ou != são aceitos somente entre ponteiros
do mesmo tipo. Cuidado, se você comparar ponteiros que apontam para
variáveis de tipos diferentes, obterá resultados sem sentido.
Testes com NULL ou zero também podem ser feitos, contanto que você
use o modificador de tipo:
if(px == (int *)0)

A UNIDADE ADOTADA EM OPERAÇÕES COM PONTEIROS

Quando declaramos um ponteiro, o compilador necessita conhecer o tipo da


variável apontada para poder executar corretamente operações aritméticas.
int *pi;
double *pd;
float *pf;

O tipo declarado é entendido como o tipo da variável apontada. Assim,


se somarmos 1 a pi, estaremos somando dois bytes (um int); se somarmos 1 a
pd, estaremos somando oito bytes (um double) e assim por diante.

A unidade com ponteiros é o número de bytes do tipo apontado.

PONTEIROS NO LUGAR DE MATRIZES

Em C++, o relacionamento entre ponteiros e matrizes é tão estreito que ponteiros


e matrizes deveriam ser realmente tratados juntos.
O compilador transforma matrizes em ponteiros na compilação, pois a
arquitetura do microcomputador compreende ponteiros, e não matrizes. Qualquer
160 Treinamento em Linguagem C++ Cap. 11

operação que possa ser feita com índices de uma matriz pode ser feita com
ponteiros.
No capítulo sobre matrizes aprendemos que o nome de uma matriz
representa seu endereço de memória. Este endereço é o endereço do primeiro
elemento da matriz. Em outras palavras, o nome de uma matriz é um ponteiro
que aponta para o primeiro elemento da matriz.
Para esclarecer a relação entre ponteiros e matrizes, vamos examinar
um simples programa escrito primeiramente com matrizes e depois com pon-
teiros.
//MATRIZ.CPP
//Imprime os valores dos elementos de uma matriz
#include <iostream.h>
void main()
{
int M[5]={92,81,70,69,58};

for(int i=0; i<5; i++)


cout << "\n" << M[i];
}

O próximo exemplo usa a notação ponteiro:


//PMATRIZ.CPP
//Imprime os valores dos elementos de uma matriz
#include <iostream.h>
void main()
{
int M[5]={92,81,70,69,58};

for(int i=0; i<5; i++)


cout << "\n" << *(M+i);
}

A expressão *(M+i) tem exatamente o mesmo valor de M[i]. Você já


sabe que M é um ponteiro int e aponta para M[0], conhecendo também a
aritmética com ponteiros. Assim, se somarmos 1 a M, obteremos o endereço de
M[1], M+2 é o endereço de M[2] e assim por diante. Em regra geral, temos que:
Cap. 11 Ponteiros 161

M + i é equivalente a &M[i], portanto


*(M + i) é equivalente a M[i]

PONTEIROS CONSTANTES E PONTEIROS VARIÁVEIS

Analisando o exemplo anterior, você poderia perguntar: Será que a instrução


cout << "\n" << *(M+i);

não poderia ser simplificada e substituída por


cout << "\n" << *(M++); //ERRADO

A resposta é não. A razão disto é que não podemos incrementar uma


constante. Da mesma forma que existem inteiros constantes e inteiros variáveis,
existem ponteiros constantes e ponteiros variáveis.

O nome de uma matriz é um ponteiro constante.

Isto vale para qualquer constante. Você não poderia escrever:


x = 3++; //ERRADO

O compilador apresentaria um erro.


O nome de uma matriz é um ponteiro constante e não pode ser alterado.
Um ponteiro variável é um lugar na memória que armazena um endereço. Um
ponteiro constante é um endereço, uma simples referência.
Vamos reescrever PMATRIZ.CPP usando um ponteiro variável em vez
do nome da matriz.
Eis a listagem:
//PMATRIZ.CPP
//Imprime os valores dos elementos de uma matriz
#include <iostream.h>
void main()
{
162 Treinamento em Linguagem C++ Cap. 11

int M[5]={92,81,70,69,58};
int *p=M;

for(int i=0; i<5; i++)


cout << "\n" << *(p++);
}

Nesta versão, definimos um ponteiro para um int e inicializamos este


ponteiro com o nome da matriz:
int *p=M;

Agora podemos usar p em todo lugar do programa que usa M e, como


p é um ponteiro variável e não uma constante, podemos usar expressões como:
*(p++)

O ponteiro p contém inicialmente o endereço do primeiro elemento da


matriz M[0]. Para acessar o próximo elemento, basta incrementar p de um. Após
o incremento, p aponta para o próximo elemento, M[1], e a expressão *(p++)
representa o conteúdo do segundo elemento. O laço for faz com que a expressão
acesse cada um dos elementos em ordem.

PASSANDO MATRIZES COMO ARGUMENTO PARA FUNÇÕES

No capítulo sobre matrizes, apresentamos numerosos exemplos de como ma-


trizes são passadas como argumentos para funções e como as funções podem
acessar seus elementos.
Quando uma função recebe o endereço de uma matriz como argumento,
ela o declara usando o nome do tipo e colchetes ([]). Esta notação declara
ponteiros constantes e não ponteiros variáveis. Entretanto, é mais conveniente
usar a notação ponteiro no lugar da notação matriz. A notação ponteiro declara
um ponteiro variável.
O próximo exemplo modifica o programa MEDIA.CPP para que use
ponteiros no lugar de matriz.
Cap. 11 Ponteiros 163

//PMEDIA.CPP
//Mostra passagem de matrizes para funções como argumento
#include <iostream.h>

int media(int *lista, int tamanho);

void main()
{
const MAXI=20;
int notas[MAXI];

for(int i=0; i<MAXI ; i++)


{
cout << "Digite a nota do aluno " <<(i+1)<<": ";
cin >> *(notas+i);

if(*(notas+i) < 0 ) break;


}

int m = media(notas,i);

cout << "\n\nMédia das notas: " << m;


}

int media(int *lista, int tamanho)


{
int m=0;

for(int i=0; i < tamanho ; i++) m += *lista++;

return ( m/tamanho);
}

Observe primeiramente o protótipo da função media(). A declaração:


int *lista
164 Treinamento em Linguagem C++ Cap. 11

é equivalente à original
int lista[]

A primeira declara um ponteiro variável, enquanto a segunda declara


um ponteiro constante.
Visto que o nome da matriz é um endereço, aqui não há necessidade
de usar o operador de endereços (&) na instrução em que a função é chamada:
int m = media(notas,i);

PRECEDÊNCIA

Observe a expressão
*lista++

Como saber se é o ponteiro ou se é o conteúdo da variável apontada


que estamos incrementando? Em outras palavras, será que o compilador
interpreta a expressão como
*(lista++)

ou como
(*lista)++

O operador indireto e o de incremento têm a mesma precedência.


Operadores de mesma precedência são resolvidos por associação. Esta forma
de distinção diz respeito à ordem de execução dos operadores: pode ser da
direita para a esquerda ou da esquerda para a direita.
Os operadores unários são resolvidos da direita para a esquerda, o que
vier primeiro na expressão; assim a nossa expressão é interpretada como
*(lista++) e o ponteiro é incrementado.
Cap. 11 Ponteiros 165

PONTEIROS E STRINGS

Strings são simplesmente matrizes do tipo char. Desta forma, a notação ponteiro
pode ser aplicada a strings do mesmo modo que é aplicada a matrizes.
Como primeiro exemplo, vamos escrever uma função que procura um
caractere numa cadeia de caracteres. Esta função retorna o endereço da primeira
ocorrência do caractere, se este existir, ou o endereço zero caso o caractere não
seja encontrado.
//PROCURA.CPP
//Procura um caractere numa cadeia de caracteres
#include <iostream.h>
#include <stdio.h>

char * procura(char *s, char ch);

void main()
{
char ch, str[81], *ptr;

cout << "\nDigite uma frase: ";


gets(str);

ptr = procura(str, ’h’);

cout << "\nA frase começa no endereço "


<< unsigned(str);

if(ptr)
{
cout << "\nPrimeira ocorrência do caractere ’h’: "
<< unsigned(ptr);
cout << "\nA sua posição é: "
<< unsigned(ptr-str);
} else
166 Treinamento em Linguagem C++ Cap. 11

cout <<"\nO caractere ’h’ não existe nesta frase";


}

char *procura(char *s, char ch)


{
while( *s != ch && *s != ’\0’) s++;
if(*s != ’\0’) return s;
return (char *)0;
}

Observe o protótipo da função procura(). Esta função retorna um


ponteiro char e deve ser declarada de acordo.
O endereço da matriz str é usado como argumento na chamada à função
procura(). Este endereço é uma constante, mas, quando passado por valor, uma
variável é criada para armazenar uma cópia dele. A variável é um ponteiro char
de nome s. A expressão s++ avança o ponteiro para os sucessivos endereços
dos caracteres da cadeia.

FUNÇÕES DE BIBLIOTECA PARA MANIPULAÇÃO DE STRINGS

Você já usou várias funções de biblioteca para manipular strings. Estas funções
usam ponteiros. Apesar de existirem prontas na biblioteca C++, escreveremos
algumas delas para que você aprenda e crie outras conforme suas necessidades.
Os protótipos das nossas funções são os seguintes:
int strlen(char *);
void strcpy(char *dest, char *orig);
int strcmp(char *s, char *t);

Eis a listagem das funções:


//Retorna o tamanho da cadeia
int strlen(char *s)
{
int i=0;
while(*s){ i++; s++; }
Cap. 11 Ponteiros 167

return i;
}

//Copia a cadeia origem na cadeia destino


void strcpy(char *dest, char *orig)
{
while(*dest++ = *orig++);
}

//Compara a cadeia s com a cadeia t


//Retorna a diferença ASCII:
// um número positivo se s > t
// um número negativo se s < t
// zero se s == t
int strcmp(char *s, char *t)
{
while(*s==*t && *s && *t) {s++; t++;}
return *s - *t;
}

PONTEIROS PARA UMA CADEIA DE CARACTERES CONSTANTE

Vamos observar duas maneiras de inicializar cadeias de caracteres constantes:


uma usando um ponteiro constante e outra usando um ponteiro variável.
Observe o exemplo:
//DUAS_STR.cpp
//Mostra duas diferentes inicializações de strings
#include <iostream.h>

void main()
{
168 Treinamento em Linguagem C++ Cap. 11

char s1[] = "Saudações!";


char *s2 = "Saudações!";

cout << "\n" << s1;


cout << "\n" << s2;

// s1++; Não podemos incrementar uma constante


s2++; //OK

cout << "\n" << s2; //Imprime: audações


}

Eis a saída:
Saudações!
Saudações!
audações!

As duas formas de inicialização de string são equivalentes para vários


usos, como imprimir a cadeia, enviar como argumentos de funções etc. Para
outros usos elas são diferentes: s1 é um ponteiro constante e s2, um ponteiro
variável. Desta forma, s1 não pode ser alterado, enquanto s2 pode. Quando s2
é incrementado, não apontará mais para o primeiro caractere da cadeia e sim
para o próximo caractere.

MATRIZES DE PONTEIROS

O uso mais comum de matrizes de ponteiros é na construção de matrizes de


ponteiros para strings. O programa DSEMAN.CPP descrito no Capítulo 6, no
Módulo 1, que trata de matrizes e strings, mostra o uso de uma matriz de strings.
Uma matriz de strings é uma matriz de duas dimensões e tem a desvantagem
da obrigatoriedade do dimensionamento de todas as strings com o mesmo
tamanho.
Vamos alterar este programa para que use uma matriz de ponteiros no
lugar da matriz de strings.
Cap. 11 Ponteiros 169

Eis a listagem:
// PDSEMAN.CPP
// Imprime o dia da semana a partir de uma data
// Mostra o uso de uma matriz de ponteiros
#include <iostream.h>
#include <conio.h>

int dsemana(int dia, int mes, int ano);


const char ESC=27;

void main()
{
char *diasemana[7]= { "Domingo",
"Segunda-feira",
"Terça-feira",
"Quarta-feira",
"Quinta-feira",
"Sexta-feira",
"Sábado"
};
int dia, mes, ano;
do
{
cout << "\nDigite a data na forma dd mm aaaa: ";
cin >> dia >> mes >> ano;
int i=dsemana(dia,mes,ano);

cout << ’\n’ << diasemana[i];

} while(getch()!=ESC);
}

//Encontra o dia da semana a partir de uma data


//Retorna 0 para domingo, 1 para segunda-feira etc.
170 Treinamento em Linguagem C++ Cap. 11

int dsemana(int dia, int mes, int ano)


{
int f = ano + dia + 3 * (mes - 1) - 1;
if(mes < 3) ano--;
else f -= int(0.4*mes+2.3);
f += int(ano/4) - int((ano/100 + 1)*0.75);
f %= 7;
return(f);
}

O que significa a expressão char *diasemana[7]?


char *diasemana[7]

uma matriz

chamada diasemana

de ponteiros

para caracteres

Na versão matriz, as strings são guardadas na memória em 7 posições


de 14 bytes cada uma, ou seja, ocupando 98 bytes de memória. Na nova versão,
as strings são guardadas de forma a ocupar somente o número de bytes
necessários para o seu armazenamento, e uma matriz de ponteiros é criada.

MATRIZ DE STRINGS E A MEMÓRIA ALOCADA

A versão matriz aloca 98 bytes de memória da seguinte forma:


Cap. 11 Ponteiros 171

0 1 2 3 4 5 6 7 8 9 10 11 12 13

1 D o m i n g o \0
2 S e g u n d a - f e i r a \0
3 T e r ç a - f e i r a \0
4 Q u a r t a - f e i r a \0
5 Q u i n t a - f e i r a \0
6 S e x t a - f e i r a \0
7 S á b a d o \0

MATRIZ DE PONTEIROS E A MEMÓRIA ALOCADA

A versão ponteiros aloca 79 bytes de memória da seguinte forma:


0 1 2 3 4 5 6 7 8 9 10 11 12 13

0 D o m i n g o \0 S e g u n d
1 a - f e i r a \0 T e r ç a -
2 f e i r a \0 Q u a r t a - f
3 e i r a \0 Q u i n t a - f e
4 i r a \0 S e x t a - f e i r
5 a \0 S á b a d o \0

A versão ponteiros aloca uma matriz de 7 ponteiros e inicializa cada


um deles com o endereço de cada uma das cadeias de caracteres constantes.

Inicializar uma matriz de strings usando ponteiros aloca menos memória


que a inicialização por meio de matriz.
172 Treinamento em Linguagem C++ Cap. 11

ÁREA DE ALOCAÇÃO DINÂMICA: HEAP

A área de alocação dinâmica — também chamada heap — consiste em toda


memória disponível que não foi usada para outro propósito. Em outras pala-
vras, o heap é simplesmente o resto da memória.
C++ oference dois operadores que permitem a alocação ou a liberação
dinâmica de memória de heap, new e delete.

new E delete: ALOCANDO E DESALOCANDO MEMÓRIA

Suponhamos, por exemplo, que você vá escrever um programa interativo e não


conheça de antemão quantas entradas de dados serão fornecidas. A classe string
é um bom exemplo. Você pode criar uma matriz para armazenar a cadeia de
caracteres da classe e reservar uma quantidade de memória que pensa ser
razoável, como, por exemplo, para armazenar 80 caracteres. Neste caso, o
compilador alocará a memória necessária para armazenar 80 caracteres toda vez
que um objeto da classe string é criado.
Isto se torna ineficiente: se não ocuparmos todo o espaço reservado,
muita memória poderia ser desperdiçada. Por outro lado, poderia ser necessário
armazenar mais caracteres que os 80 reservados, e a classe não atenderá a esse
requisito.
A solução para esse tipo de problema é a de solicitar memória toda vez
que se fizer necessário. O mecanismo para aquisição de memória em tempo de
execução é o operador new.
Antes de exemplificar o uso de new, vamos primeiro ver como ele
opera.
O operador new obtém memória do sistema operacional e retorna um
ponteiro para o primeiro byte do novo bloco de memória que foi alocado.
Uma vez alocada, esta memória continua ocupada até que seja desalo-
cada explicitamente pelo operador delete. Em outras palavras, uma variável
Cap. 11 Ponteiros 173

criada pelo operador new existirá e poderá ser acessada por qualquer parte do
programa enquanto não for destruída por meio do operador delete e seu espaço
de memória for retornado ao banco de memória disponível.
O próximo exemplo mostra a classe string modificada.
Eis a listagem:
//NEWSTR.CPP
//Mostra o operador new na classe string
#include <iostream.h>
#include <string.h>

class string
{
private:
char *str;
public:
string()
{
str = new char;
*str=’\0’;
}
string(char *s)
{
str = new char[strlen(s) + 1];
strcpy(str,s);
}

~string()
{
if(str) delete str;
}
void print() { cout << str; }
};
174 Treinamento em Linguagem C++ Cap. 11

void main()
{
string s="A vida é uma longa estrada na qual "
"corremos contra o tempo.";
string s1;
cout << "\n"; s.print();
cout << "\n"; s1.print();
}

Eis a saída:
A vida é uma longa estrada na qual corremos contra o tempo.

A instrução:
str = new char[strlen(s) + 1];

retorna um ponteiro para um bloco de memória do tamanho exato para


armazenar a cadeia s mais o ’\0’. O construtor utiliza strcpy() para copiar a
cadeia s para a nova memória reservada, apontada por str.
A instrução:
str = new char;

do construtor sem argumentos reserva um único byte de memória.


Se não houver memória suficiente para satisfazer a exigência da instru-
ção, new devolverá um ponteiro com o valor zero (NULL).

O OPERADOR delete

Quando a memória reservada pelo operador new não for mais necessária, deve
ser liberada pelo operador delete.
Nossa classe devolve a memória alocada ao sistema operacional toda
vez que um objeto é destruído. Para isto, criamos o seguinte destrutor:
Cap. 11 Ponteiros 175

~string()
{
if(str) delete str;
}

Observe que, antes de liberar a memória alocada por new, devemos


verificar se essa memória foi realmente alocada. Se str aponta para o endereço
zero, nada é liberado.
Liberar a memória não libera o ponteiro que aponta para ela. Não é
mudado o endereço contido no ponteiro; simplesmente esse endereço não será
mais válido. Desta forma, você não deverá usar um ponteiro para uma memória
que foi liberada.

OBSERVAÇÕES PARA OS PROGRAMADORES C

Se você é um programador C, reconhecerá, no modo de trabalhar dos operado-


res new e delete, uma similaridade com as funções de biblioteca malloc() e
free(). Entretanto, os operadores new e delete de C++ são superiores em seu
modo de operar.
A função malloc() retorna um ponteiro genérico (void). O tipo deste
ponteiro deve ser modificado, por meio do operador de modificação de tipo,
para acessar a variável apontada. O operador new retorna um ponteiro já
apropriado para o tipo de dado solicitado.
Em C++, malloc() não deve ser usada para alocação dinâmica de um
objeto, pois supõe-se que um construtor da classe seja chamado na sua criação.
Se malloc() for usada para criar um novo objeto, obteremos um ponteiro para
um bloco não-inicializado de memória e perderemos o benefício proporcionado
por construtores. A função malloc() não conhece nada sobre o tipo da variável
sendo alocada. Ela toma o tamanho em bytes como argumento e retorna um
ponteiro void.
Por outro lado, o operador new conhece a classe do objeto sendo
alocado e, automaticamente, chama o construtor para inicializar a memória
alocada.
176 Treinamento em Linguagem C++ Cap. 11

PONTEIROS void

Antes de prosseguir a explanação de ponteiros, vamos mostrar um ponteiro


peculiar. Quando queremos atribuir um endereço a um ponteiro, este deve ser
do mesmo tipo do ponteiro. Por exemplo, não podemos atribuir um endereço
de uma variável int a um ponteiro float. Entretanto, há uma exceção. Há um
tipo de ponteiro de propósito geral que pode apontar para qualquer tipo de
dado. Este ponteiro deve ser do tipo void e é declarado por meio da seguinte
instrução:
void *p; //p aponta para qualquer tipo de dado

Ponteiros do tipo void são usados em situações em que seja necessário


que uma função retorne um ponteiro genérico e opere independentemente do
tipo de dado apontado.
Observe que o conceito de ponteiros void não tem absolutamente nada
a ver com o tipo void para funções.
Qualquer endereço pode ser atribuído a um ponteiro void:
int i=5;
float f=3.2;

void *pv; //Ponteiro genérico


pv = &i; //Endereço de um int

cout << *pv; //ERRO


pv = &f; //Endereço de um float

cout << *pv; //ERRO

O conteúdo da variável apontada por um ponteiro void não pode ser


acessada por meio deste ponteiro. É necessário criar outro ponteiro e fazer a
conversão de tipo na atribuição:
int i=5,*pi;
void *pv; //Ponteiro genérico

pv = &i; //Endereço de um int


Cap. 11 Ponteiros 177

pi = (int *) pv;

cout << *pi; //OK

REDIMENSIONANDO STRINGS

Você pode escrever uma função membro para a classe string que muda o
tamanho da cadeia de caracteres. Como exemplo, escreveremos uma função
operadora +=.
//REDIMSTR.CPP
//Mostra o redimensionamento de string
#include <iostream.h>
#include <string.h>

class string
{
private:
char *str;
public:
string()
{ str = new char;
*str=’\0’;
}
string(char *s)
{ str = new char[strlen(s) + 1];
strcpy(str,s);
}
void operator +=(const char *s)
{ char *temp;
int tamanho=strlen(str)+strlen(s);

temp = new char[tamanho+1];


178 Treinamento em Linguagem C++ Cap. 11

strcpy(temp,str);
strcat(temp,s);
delete str;
str = temp;
}

~string()
{
if(str) delete str;
}
void print() { cout << str; }
};

void main()
{
string s1("Feliz Aniversário! ");

s1 += "Denise";
cout << "\n"; s1.print();
}

Eis a saída:
Feliz Aniversário! Denise.

A função operator+= acrescenta o conteúdo de uma cadeia de caracte-


res, recebida como argumento, a um objeto string já existente.
O objeto string s1 é redimensionado dinamicamente. Todos os detalhes
do redimensionamento estão embutidos na função-membro.
Cap. 11 Ponteiros 179

SOBRECARGA DO OPERADOR DE ATRIBUIÇÃO COM STRINGS

Suponhamos que você use a classe string anterior e escreva o seguinte trecho
de programa:
void main()
{
string s1("Feliz Aniversário! "),s2;

s2 = s1;
.
.
.
.
}

O programa cria o objeto s2 e então atribui a ele o conteúdo do objeto


s1. O seguinte problema acontecerá: quando você atribui um objeto a outro, o
compilador copia, byte a byte, todo o conteúdo de memória ocupado por um
byte no outro. Em outras palavras, ao membro str de s2 será atribuído o
conteúdo do membro str de s1. Entretanto, o membro str é um ponteiro e, como
resultado, s2.str e s1.str apontarão para a mesma localização de memória.
Desta forma, qualquer modificação na cadeia de caracteres de um dos
objetos afetará diretamente o outro. Certamente, não é isto que desejamos.
Um problema mais sério ocorre quando os objetos envolvidos têm escopos
diferentes. Quando o destrutor de s1 é chamado, destruirá a memória alocada e
apontada por s1, destruindo portanto a mesma memória apontada por s2.
Este problema ocorre com qualquer classe que contém um ponteiro
como membro e aloca memória dinamicamente.
A solução é sobrecarregar o operador de atribuição escrevendo uma
função especial para executar a operação.
//ATRIBSTR.CPP
//Mostra a sobrecarga do operador de atribuição
#include <iostream.h>
#include <string.h>
180 Treinamento em Linguagem C++ Cap. 11

class string
{
private:
char *str;
public:
string()
{ str = new char;
*str=’\0’;
}
string(char *s)
{ str = new char[strlen(s) + 1];
strcpy(str,s);
}
void operator +=(const char *s)
{
char *temp;
int tamanho=strlen(str)+strlen(s);

temp = new char[tamanho+1];


strcpy(temp,str);
strcat(temp,s);
delete str;
str = temp;
}

void operator=(const string &s)


{
int tamanho=strlen(s.str)
delete str;
str = new char[tamanho+1];
strcpy(str,s.str);
}
Cap. 11 Ponteiros 181

~string()
{
if(str) delete str;
}

void print() { cout << str; }


};

void main()
{
string s1("Feliz Aniversário! "),s2;

s1 += "Denise";

s2=s1;

cout << "\n"; s2.print();


}

A função operator=() recebe uma referência a um objeto como argu-


mento. Observe que usamos uma referência a uma constante, indicando que a
função não poderá modificar o objeto argumento.
void operator=(const string &s)

Para executar a atribuição, primeiramente a função usa strlen() para


tomar o tamanho da cadeia a ser copiada, em seguida, libera a memória
inicialmente alocada para o objeto, aloca um novo bloco de memória e final-
mente copia a cadeia do objeto s no objeto atual.

O PONTEIRO this

A nossa classe string tem ainda um pequeno problema sutil que você verifica
quando acidentalmente atribuir um objeto a si mesmo.
s1=s1;
182 Treinamento em Linguagem C++ Cap. 11

Durante uma operação de atribuição como esta, a função operator=(),


definida na última versão da classe string, primeiro libera a memória alocada
e apontada pelo ponteiro str, em seguida aloca uma nova memória e atribui o
endereço desta nova memória ao ponteiro str. Esta nova memória contém
valores chamados lixo. Finalmente, a função copia o conteúdo desta nova
memória nela mesma. Esta operação causa resultados absurdos.
Para que a função operator=() opere de maneira satisfatória para todos
os casos, ela deve verificar atribuições de um objeto a si mesmo. Isto requer o
uso de um ponteiro chamado this.
O ponteiro this é um ponteiro especial que pode ser acessado por todas
as funções-membros do objeto. Este ponteiro aponta para o próprio objeto.
Qualquer função-membro pode acessar o endereço do objeto do qual é membro
por meio do ponteiro this.
Quando uma função-membro é chamada por um objeto, o compilador
atribui o endereço do objeto ao ponteiro this.
Uma função-membro pode usar o ponteiro this para testar quando um
objeto passado como argumento é o mesmo do qual a função é membro.
A nova versão da função operator=() é a seguinte:
void operator=(const string &s)
{
if( &s == this) return;

int tamanho=strlen(s.str)
delete str;
str = new char[tamanho+1];
strcpy(str,s.str);
}

A função testa se o endereço do objeto s é o mesmo do contido no


ponteiro this. Se positivo, a função termina sem executar nenhuma atribuição.
Do contrário, a função executa normalmente a atribuição.
Cap. 11 Ponteiros 183

RETORNANDO O PONTEIRO this

Outro uso comum do ponteiro this é para retornar um valor de uma função-
membro ou de uma função que sobrecarrega um operador.
Na nossa classe string, a função operator=() não cobre todas as formas
de uso do operador =. Em C++ você pode escrever instruções que fazem
atribuições múltiplas como:
x = y = z;

A nossa função operator=() é do tipo void e toda operação executada


por ela não tem valor de retorno. Desta forma, não poderíamos escrever
instruções de atribuições múltiplas entre objetos da classe string.
Para tornar possível este uso, devemos alterar a função operator=() para
que ela retorne o resultado da atribuição. Queremos que o operador de
atribuição retorne o objeto do qual é membro. O endereço deste objeto é
acessado por meio do ponteiro this. Para retornar o próprio objeto, devemos
utilizar *this.
string& operator=(const string &s)
{
if( &s == this) return *this;

int tamanho=strlen(s.str);
delete str;
str = new char[tamanho+1];
strcpy(str,s.str);
return *this;
}

Com esta versão, a função operator=() trabalhará corretamente em


instruções de atribuições múltiplas de objetos da classe string.
string s1="Bom Dia!", s2, s3;

s2 = s3 = s1;
184 Treinamento em Linguagem C++ Cap. 11

Observe que a função retorna uma referência a um objeto string.


Usamos a declaração:
string& operator=(const string &s)

em vez de
string operator=(const string &s)

que retornaria por valor. A instrução de retorno da função é a seguinte:


return *this;

Visto que this é um ponteiro para o objeto do qual a função é membro,


*this é este objeto propriamente e a instrução o retorna por referência.

this É UM PONTEIRO CONSTANTE

O ponteiro this é um ponteiro constante; desta forma, seu conteúdo não pode
ser alterado para que aponte para outro objeto qualquer.

ALOCANDO TIPOS BÁSICOS USANDO MEMÓRIA DINÂMICA

Os operadores new e delete podem ser usados para criar variáveis de tipos
básicos como inteiros ou caracteres. Por exemplo:
#include <iostream.h>
void main()
{
int *pi;

pi = new int;

cout << "\nDigite um número: "; cin >> *pi;


cout << "\nVocê digitou o número " << *pi;

delete pi;
}
Cap. 11 Ponteiros 185

DIMENSIONANDO MATRIZES EM TEMPO DE EXECUÇÃO

A determinação do tamanho de uma matriz pode ser feita em tempo de


execução. Vamos modificar o programa PMEDIA.CPP para que o usuário
indique quantas notas serão inseridas.
//PMEDIA.CPP
//Dimensionamento de uma matriz em tempo de execução
#include <iostream.h>

int media(int *lista, int tamanho);

void main()
{
int tamanho,*notas;

cout << "\nQual é o número de notas? ";


cint >> tamanho;

notas = new int[tamanho];

for(int i=0; i<tamanho ; i++)


{
cout << "Digite a nota do aluno " <<(i+1)<<": ";
cin >> *(notas+i);
}

int m = media(notas,tamanho);

cout << "\n\nMédia das notas: " << m;

delete [] notas;
}
186 Treinamento em Linguagem C++ Cap. 11

int media(int *lista, int tamanho)


{
int m=0;
for(int i=0; i < tamanho ; i++) m += *lista++;
return ( m/tamanho);
}

Observe a sintaxe da instrução de liberação da memória alocada:


incluímos um par de colchetes vazios antes do nome do ponteiro. O compilador
ignora qualquer número colocado dentro dos colchetes.
delete [] notas;

DIMENSIONANDO MATRIZES DE DUAS DIMENSÕES COM new

Suponhamos que você queira criar um programa que calcule a média das notas
de cada aluno de uma escola. Você sabe que cada aluno tem três notas, mas
desconhece o número de alunos da escola. O seguinte trecho de programa
mostra como alocar memória em tempo de execução:
int (*notas)[3];
int tamanho;

cout << "\nQual é o número de alunos? ";


cint >> tamanho;

notas = new int[tamanho][3];


.
.
.

delete [] notas;
Cap. 11 Ponteiros 187

Você pode alocar uma matriz multidimensional com new, mas somente
a primeira dimensão pode ser definida em tempo de execução; as outras devem
ser constantes.
int (*notas)[3];

PONTEIROS PARA OBJETOS

Os ponteiros podem apontar para objetos da mesma forma que apontariam para
qualquer tipo básico.
Em muitas situações, no instante em que você está escrevendo um
programa, desconhece o número de objetos a serem criados. Nestes casos, você
usa new para criar objetos em tempo de execução. O operador new retorna um
ponteiro para um objeto sem nome. Vamos rever a classe Venda, descrita no
Capítulo 8. O objetivo é comparar dois modos de criar objetos.
//PTRVENDA.CPP
//Mostra o acesso a funções-membro por ponteiros
#include <iostream.h>
#include <iomanip.h>

class Venda
{
private:
int npecas;
float preco;
public:
void getvenda()
{
cout << "Insira No.Peças: "; cin >> npecas;
cout << "Insira Preço : "; cin >> preco;
}
188 Treinamento em Linguagem C++ Cap. 11

void printvenda() const;


};

void venda::printvenda() const


{
cout << setiosflags(ios::fixed) //não notação científica
<< setiosflags(ios::showpoint) //ponto decimal
<< setprecision(2) //duas casas
<< setw(10) << npecas; //tamanho 10
cout << setw(10) << preco << "\n";
}

void main()
{
Venda A;
A.getvenda();
A.printvenda();

Venda *B;
B = new Venda;
B->getvenda();
B->printvenda();
}

A função main() cria dois objetos da classe Venda: o objeto A e um


objeto sem nome, apontado pelo ponteiro B.
A novidade deste programa é a forma de acesso aos membros de um
objeto por meio de seu endereço e não de seu nome.

ACESSANDO MEMBROS POR MEIO DE PONTEIROS

Você já aprendeu que, se o nome de um objeto for conhecido, podemos acessar


seus membros usando o operador ponto (.).
Cap. 11 Ponteiros 189

Será que uma construção análoga, usando um ponteiro em vez do nome


do objeto, poderia ser escrita? Ou seja, seria possível escrever a construção
seguinte?
B.getvenda(); //ERRO

A resposta é não, pois B não é um objeto e sim um ponteiro para um


objeto, e o operador ponto (.) requer o nome de um objeto à sua esquerda.
C++ oferece dois métodos para resolver este problema: o primeiro,
menos elegante, é obter a variável apontada por B por meio do operador indireto (*):
(*B).getvenda(); //OK

Entretanto, esta expressão é de visualização complexa por causa dos


parênteses. Os parênteses são necessários, pois o operador (.) tem precedência
maior que a do operador (*).
O segundo método, de uso mais comum, é por meio do operador de
acesso a membros (->) que consiste no sinal de “menos” (-) seguido do sinal de
“maior que” (>).
B->getvenda(); //Mais usado

Um ponteiro para um objeto, seguido pelo operador (->) e pelo nome


de um membro, trabalha da mesma maneira que o nome de um objeto seguido
pelo operador (.) e pelo nome do membro.

O operador de acesso a membros (->) conecta um ponteiro para um objeto


a um membro dele, enquanto o operador ponto (.) conecta o
nome de um objeto a um membro dele.

USANDO REFERÊNCIAS

Você pode criar uma referência a um objeto definido pelo operador new. Eis
um exemplo:
//REFVENDA.CPP
//Mostra o uso de referência a um objeto criado por new
190 Treinamento em Linguagem C++ Cap. 11

#include <iostream.h>
#include <iomanip.h>

class Venda
{
private:
int npecas;
float preco;
public:
void getvenda()
{
cout << "Insira No.Peças: "; cin >> npecas;
cout << "Insira Preço : "; cin >> preco;
}

void printvenda() const;


};

void venda::printvenda() const


{
cout << setiosflags(ios::fixed) //não notação científica
<< setiosflags(ios::showpoint) //ponto decimal
<< setprecision(2) //duas casas
<< setw(10) << npecas; //tamanho 10
cout << setw(10) << preco << "\n";
}

void main()
{
Venda& A= *(new Venda);

A.getvenda();
A.printvenda();
}
Cap. 11 Ponteiros 191

A expressão
new Venda

retorna um ponteiro para uma área de memória, grande o suficiente para


armazenar um objeto Venda. O objeto original pode ser referenciado por meio
da expressão:
*(new Venda)

Este é o objeto apontado pelo ponteiro. Criamos uma referência a este


objeto de nome A. Desta forma, A é o próprio nome do objeto e seus membros
podem ser acessados usando o operador ponto em vez do operador ->

UMA MATRIZ DE PONTEIROS PARA OBJETOS

Geralmente, quando um programa necessita manipular um grupo de objetos, é


mais flexível a criação de uma matriz de ponteiros para objetos do que uma
matriz para agrupar os próprios objetos. Por exemplo, é mais rápido indexar
uma matriz de ponteiros para objetos do que indexar os próprios objetos.
Nosso próximo exemplo cria uma matriz de ponteiros para a classe
Nome.
//PTRNOME.CPP
//Mostra uma matriz de ponteiros para objetos
#include <iostream.h>
#include <string.h>
#include <stdio.h>

class Nome
{
private:
char *str;
public:
int getnome()
{
192 Treinamento em Linguagem C++ Cap. 11

char nome[100];
gets(nome);
str = new char[strlen(nome)+1];
strcpy(str,nome);
return strcmp(str,"");
}

void print() { cout << str; }


};

void main()
{
Nome *p[80];

for(int n=0;;n++)
{
cout << "\nDigite nome ou [ENTER] para fim: ";
p[n] = new Nome;
if(p[n]->getnome()==0) break;
}

cout << "\n\nLista dos nomes:";


for(int i=0;i<n;i++)
{ cout << "\n" ; p[i]->print(); }

A função main() cria uma matriz de 80 ponteiros do tipo Nome. O laço


for solicita a entrada de um nome ao usuário, cria um objeto Nome usando new
e atribui o endereço deste objeto a um dos elementos da matriz p. O programa
acessa cada objeto por meio da matriz de ponteiros e imprime os nomes.
Cap. 11 Ponteiros 193

Para acessar a função membro getnome(), usamos a expressão


p[n]->getnome()

que é equivalente a
(*(p+n))->getnome()

Esta função retorna zero se o usuário pressionar a tecla [ENTER],


indicando uma cadeia de caracteres vazia.

CRIANDO UMA LISTA LIGADA

Lista ligada é um algoritmo de armazenamento de dados que muitas vezes


supera o uso de uma matriz, ou matriz de ponteiros.
A lista ligada assemelha-se a uma corrente em que os registros de dados
estão pendurados seqüencialmente. O espaço de memória para cada registro é
obtido pelo operador new, conforme surge a necessidade de adicionar itens a
lista. Cada registro é conectado ao anterior por meio de um ponteiro. O primeiro
registro contém um ponteiro com o valor NULL, e cada registro sucessor contém
um ponteiro para o anterior.
O nosso exemplo monta uma lista ligada numa classe. A lista é um
objeto da classe ListaLigada. Cada registro é representado por uma estrutura
do tipo Livro. Cada estrutura contém um conjunto de membros para armazenar
os dados de um livro: título, autor, número do registro e preço. Há um membro
a mais, um ponteiro, para armazenar o endereço do registro anterior.
A lista, como um todo, armazena um ponteiro para o último registro.
194 Treinamento em Linguagem C++ Cap. 11

ESQUEMA DE UMA LISTA LIGADA

NULL

anterior

anterior

A CLASSE ListaLigada E O PROGRAMA LISTA.CPP

//LISTA.CPP
//Mostra a implementação de uma lista ligada
#include <iostream.h>
#include <stdio.h>
Cap. 11 Ponteiros 195

struct Livro
{
char titulo[30];
char autor[30];
int numreg;
double preco;
Livro *anterior;
};

class ListaLigada
{
private:
Livro *fim;
public:
ListaLigada() { fim=(Livro *)NULL;}
void novonome();
void print();
};

void ListaLigada::novonome()
{
Livro *novolivro = new Livro;
cout << "\nDigite titulo: ";
gets(novolivro->título);
cout << "Digite autor: ";
gets(novolivro->autor);
cout << "Digite o número do registro: ";
cin >> novolivro->numreg;
cout << "Digite o preço: ";
cin >> novolivro->preco;
novolivro->anterior = fim;
fim=novolivro;
}
196 Treinamento em Linguagem C++ Cap. 11

void ListaLigada:: print()


{
Livro *atual = fim;
while(atual != NULL)
{
cout << "\n\nTítulo: " << atual->titulo;
cout << "\nAutor : " << atual->autor;
cout << "\nNo.Reg: " << atual->numreg;
cout << "\nPreço : " << atual->preco;
atual = atual->anterior;
}
}

void main()
{
ListaLigada li;
char opcao;

do
{
li.novonome();
cout << "\nInserir outro livro? ";
cin >> opcao;

} while( opcao == ’s’);

cout << "\nLISTA DOS LIVROS CADASTRADOS";


cout << "\n****************************";

li.print();
}

Eis a saída:
Digite título: Helena
Digite autor: Machado de Assis
Digite o número do registro: 102
Cap. 11 Ponteiros 197

Digite o preço: 70.5

Inserir outro livro? s

Digite título: Iracema


Digite autor: José de Alencar
Digite o número do registro: 321
Digite o preço: 63.25

Inserir outro livro? s

Digite título: Macunaíma


Digite autor: Mario de Andrade
Digite o número do registro: 543
Digite o preço: 73.3

Inserir outro livro? n

LISTA DOS LIVROS CADASTRADOS


****************************

Título: Macunaíma
Autor : Mario de Andrade
No.Reg: 543
Preço : 73.3

Título: Iracema
Autor : José de Alencar
No.Reg: 321
Preço : 63.25

Título: Helena
Autor : Machado de Assis
No.Reg: 102
Preço : 70.5
198 Treinamento em Linguagem C++ Cap. 11

ADICIONANDO UM LIVRO À LISTA

A função novonome() adiciona um livro à lista. O novo registro é inserido no


final da lista.
Primeiro, uma nova estrutura do tipo Livro é criada pela instrução
Livro *novolivro = new Livro;

que reserva memória suficiente para armazenar a estrutura toda e atribui o


endereço desta memória ao ponteiro variável novolivro.
Em seguida, os dados da nova estrutura são preenchidos pelo usuário,
com exceção do membro ponteiro. Observe que, para acessar os membros da
estrutura, usamos o operador -> da mesma forma que é usado com classes. O
membro ponteiro conterá o endereço da estrutura anterior; a instrução que faz
a atribuição é a seguinte.
novolivro->anterior = fim;

Finalmente, o novo endereço é atribuído ao membro fim da classe por


meio da instrução:
fim=novolivro;

Esta instrução faz com que a classe sempre contenha o endereço da


última estrutura inserida.

IMPRIMINDO OS DADOS DA LISTA

Imprimir os dados da lista consiste em seguir a cadeia de ponteiros, de uma


estrutura para a anterior, até encontrar NULL.
A função print() primeiro atribui o valor do ponteiro fim à variável
atual:
Livro *atual = fim;

Em seguida, analisa se a lista não está vazia verificando se o conteúdo


do ponteiro atual é ou não NULL.
Cap. 11 Ponteiros 199

while(atual != NULL)

Se não for NULL, o programa entra no laço while que imprime os


elementos da estrutura apontada por atual e, em seguida, faz com que o ponteiro
atual aponte para a estrutura anterior:
atual = atual->anterior;

O laço termina quando a primeira estrutura é atingida.

IMPLEMENTAÇÕES ADICIONAIS

Observe que a nossa lista é impressa no sentido inverso ao da entrada. Você


pode reescrever a classe ListaLigada para que a lista seja manipulada no mesmo
sentido da entrada de dados. Esta alteração envolve uma programação um
pouco mais complexa.
Vários outros refinamentos podem ser implementados à classe Lista-
Ligada. Por exemplo, você pode escrever funções para adicionar ou remover
registros de qualquer posição da lista. Eliminar um item da lista é razoavelmente
fácil. Se o registro a ser eliminado é B, então o ponteiro da estrutura anterior,
A, é copiado na estrutura seguinte, C.
Outra função-membro importante é o destrutor. O destrutor pode
caminhar ao longo da lista usando delete para liberar a memória alocada para
cada um dos itens.

PONTEIROS PARA PONTEIROS

O programa STRSORT.CPP, descrito no Capítulo 9, mostra a ordenação de


uma matriz de strings. Vamos alterar este programa de forma a criar uma matriz
de ponteiros para objetos e mostrar como ordenar esses ponteiros baseados nos
dados contidos nos objetos.
200 Treinamento em Linguagem C++ Cap. 11

Esta modificação envolve o uso de ponteiros que apontam para outros


ponteiros. Ponteiros para ponteiros dão a C++ uma grande flexibilidade na
criação e ordenação de classes complexas.
O nosso programa cria uma matriz de ponteiros para objetos da classe
String e modifica a função ordena() para que receba um ponteiro para ponteiro.
Eis a listagem:
//PSTRSORT.CPP
//Mostra o uso de ponteiros para ponteiros
#include <iostream.h>
#include <string.h>
#include <stdio.h>

enum Boolean { False, True};


class String
{
private:
char *str;
public:
int getname()
{
char nome[100];
gets(nome);
str = new char[strlen(nome)+1];
strcpy(str,nome);
return strcmp(str,"");
}

Boolean operator > (String s)


{return (strcmp(str,s.str)>0) ? True:False;}

void print() { cout << str; }


};
Cap. 11 Ponteiros 201

void ordena(String **p,int n);

void main()
{
String *p[100];

for(int n=0;;n++)
{ cout << "Digite nome ou [ENTER] para fim: ";
p[n] = new String;
if(p[n]->getname()==0) break;
}

cout << "\n\nLista original:";


for(int i=0;i<n;i++)
{ cout << "\n" ; p[i]->print(); }

ordena(p,n);

cout << "\n\nLista ordenada:";


for(i=0;i<n;i++)
{ cout << "\n"; p[i]->print(); }
}

void ordena(String **p,int n)


{
String *temp;

for(int i=0;i<n;i++)
{
for(int j=i+1; j<n;j++)
if(*(*(p+i)) > *(*(p+j)))
{ temp = *(p+i);
*(p+i) = *(p+j);
*(p+j) = temp;
}
}
}
202 Treinamento em Linguagem C++ Cap. 11

Eis a saída:
Digite nome ou [ENTER] para fim: Regiane Ferreira
Digite nome ou [ENTER] para fim: Ana Maria de Paula
Digite nome ou [ENTER] para fim: Denise Silveira
Digite nome ou [ENTER] para fim: André Victor Mesquita
Digite nome ou [ENTER] para fim: Lucy Coelho
Digite nome ou [ENTER] para fim:

Lista original:

Regiane Ferreira
Ana Maria de Paula
Denise Silveira
André Victor Mesquita
Lucy Coelho

Lista ordenada:

Ana Maria de Paula


André Victor Mesquita
Denise Silveira
Lucy Coelho
Regiane Ferreira

O programa cria uma matriz de 100 ponteiros para objetos do tipo


String, em seguida cria um objeto por vez por meio do operador new, conforme
o usuário digita os nomes. Quando o usuário digitar uma string vazia, o
processo é interrompido. Toda vez que um objeto é criado, seu endereço é
atribuído a um elemento da matriz de ponteiros.
Os nomes são impressos duas vezes: a primeira, na mesma ordem em
que foram digitados, e a segunda, em ordem alfabética.
Cap. 11 Ponteiros 203

ORDENANDO PONTEIROS

A novidade deste programa está em como os objetos são ordenados. Na


realidade, a função ordena() não ordena objetos, e sim os ponteiros para os
objetos. Esta ordenação é muito mais rápida que a ordenação dos próprios
objetos, pois estaremos movimentando ponteiros pela memória, e não objetos.
Os ponteiros são variáveis que ocupam pouco lugar de memória, entretanto os
objetos podem ocupar muita memória.
Observe o primeiro argumento de ordena():
String **p

O tipo da variável p é String**. Esta notação indica que p é um ponteiro


duplamente indireto. Quando o endereço de um objeto da classe String é
passado para uma função como argumento, você já sabe que o seu tipo é String*.
Um único asterisco é usado para indicar o endereço de um objeto.
A função ordena() não recebe o endereço de um objeto, recebe o
endereço de uma matriz de ponteiros para objetos do tipo String. O nome de
uma matriz é um ponteiro para um elemento dela. No caso da nossa matriz,
um elemento é um ponteiro. Portanto, o nome da matriz é um ponteiro que
aponta para outro ponteiro.
Dois asteriscos são usados para indicar um ponteiro para ponteiro.

NOTAÇÃO PONTEIRO PARA MATRIZES DE PONTEIROS

Cada elemento da matriz p é um ponteiro para um objeto String. Assim


p[i]

é o endereço de um objeto String. Portanto,


*p[i]

é o nome de um objeto da classe String.


204 Treinamento em Linguagem C++ Cap. 11

Você já sabe que um elemento de uma matriz pode ser escrito em


notação ponteiro. Dessa forma, podemos escrever a expressão p[i] como
*(p+i)

então a expressão *p[i] que indica o nome do objeto pode ser escrita como:
*(*(p+i))

em notação ponteiro.

ARGUMENTOS DA LINHA DE COMANDO

Com certeza, você já deve estar acostumado a usar argumentos da linha de


comando com programas do DOS. Por exemplo:
C:\>FORMAT A:

Neste exemplo, A: é um argumento da linha de comando. Os itens


digitados na linha de comando do DOS são chamados argumentos da linha de
comando.
Como podemos escrever programas em C++ para acessar esses argu-
mentos?
Os argumentos digitados na linha de comando são enviados pelo DOS
como argumentos da função main(). Para que esta função possa reconhecê-los,
é necessário declará-los, da mesma forma que é feito em qualquer função C++.
Eis um exemplo:
//LINCOM.C
//Mostra argumentos da linha de comando
#include <iostream.h>

void main(int argc, char **argv)


{
cout << "\nNúmero de argumentos: " << argc;
Cap. 11 Ponteiros 205

for(int i=0; i<argc; i++)


cout<< "\nArgumento número "<< i <<": "<< argv[i];
}

Eis uma simples execução:


C:\>LINCOM JOSE JOAO MARIA

Número de argumentos: 4
Argumento número 0: C:\CPP\LINCOM.EXE
Argumento número 1: JOSE
Argumento número 2: JOAO
Argumento número 3: MARIA

A função main() recebe dois argumentos: argc e argv.


void main(int argc, char **argv)

O primeiro argumento, argc, representa o número de argumentos


digitados na linha de comando do DOS, neste caso 4.
O segundo argumento, argv, corresponde a uma matriz de ponteiros
para strings, onde cada string representa um dos argumentos da linha de
comando. As cadeias de caracteres podem ser acessadas por meio da notação
matriz, argv[0], argv[1] etc. ou por meio da notação ponteiro, *(argv+0),
*(argv+1) etc.
A primeira cadeia, argv[0], é sempre o nome do programa sendo
executado e o seu caminho de localização no disco.
Os nomes argc (ARGument Count) e argv (ARGument Values) são
tradicionalmente usados para este fim, mas qualquer outro nome poderia ser
usado por você.

PONTEIROS PARA FUNÇÕES

Terminaremos este capítulo mostrando um tipo de ponteiro especial: ponteiro


que aponta para uma função. Se você definiu um ponteiro para função e atribuiu
206 Treinamento em Linguagem C++ Cap. 11

a ele o endereço de uma função particular, dizemos que este ponteiro aponta
para a função e que a função pode ser executada por meio do ponteiro.
O nosso primeiro exemplo mostra um ponteiro para a função dois-
beep() descrita no Capítulo 5, no Módulo 1.
Eis a listagem:
//PTRFUNC.CPP
//Mostra o uso de ponteiro para função
#include <iostream.h>

void doisbeep(void);

void main()
{
void (*pf)(void);

pf = doisbeep; //sem parênteses

(*pf)(); //chama a função


}

// doisbeep()
// toca o alto-falante duas vezes
void doisbeep(void)
{
cout << ’\x07’;
for(int i=0; i<5000 ; i++);
cout << ’\x07’;
}
Cap. 11 Ponteiros 207

DECLARANDO O PONTEIRO PARA FUNÇÃO

A função main() começa declarando pf como um ponteiro para uma função


void. É claro que o tipo void é uma das possibilidades. Se a função a ser
apontada é do tipo float, por exemplo, o ponteiro para ela deve ser declarado
como tal.
void (*pf)(void);

Observe os parênteses envolvendo *pf. Estes parênteses são realmente


necessários, pois, se omitidos
void *pf(); //ERRO

estaríamos declarando pf como sendo uma função que retorna um ponteiro


void; em outras palavras, estaríamos escrevendo o protótipo da função pf.

ENDEREÇOS DE FUNÇÕES

O nome de uma função, desacompanhado de parênteses, é o seu endereço. A


instrução
pf = doisbeep; //sem parênteses

atribui o endereço da função doisbeep() a pf. Observe que não colocamos


parênteses junto ao nome da função. Se eles estivessem presentes, como em
pf = doisbeep(); //ERRO

estaríamos atribuindo a pf o valor de retorno da função, e não o seu endereço.


208 Treinamento em Linguagem C++ Cap. 11

EXECUTANDO A FUNÇÃO POR MEIO DO PONTEIRO

Da mesma forma que podemos substituir o nome de uma variável usando um


ponteiro acompanhado do operador indireto (*), podemos substituir o nome da
função usando o mesmo mecanismo. A instrução
(*pf)();

é equivalente a
doisbeep();

e indica uma chamada à função doisbeep().

OPERAÇÕES ILEGAIS COM PONTEIROS PARA FUNÇÕES

A aritmética com ponteiros para funções não é definida em C++. Por exemplo,
você não pode incrementar ou decrementar ponteiros para funções.

PONTEIROS PARA FUNÇÕES COMO ARGUMENTOS

O exemplo a seguir cria um ponteiro para armazenar o endereço da função de


biblioteca gets(). Esta função tem o seguinte protótipo:
char *gets(char *);

definido no arquivo stdio.h. A função retorna um ponteiro para a cadeia de


caracteres lida do teclado e armazenada no endereço recebido por ela como
argumento.
//PTRGETS.CPP
//Mostra o uso de ponteiros como argumentos de função
#include <iostream.h>
#include <stdio.h>
Cap. 11 Ponteiros 209

void func( char * (*p)(char *));

void main()
{
char * (*p)(char *);
p = gets;
func(p);
}

void func(char * (*p)(char *))


{
char nome[80];

cout << "\nDigite seu nome: ";

(*p)(nome); //chama a função gets()

cout << "\nSeu nome é: " << nome;


}

A declaração do ponteiro é a seguinte:


char * (*p)(char *);

Esta instrução indica que p é um ponteiro para uma função do tipo


char* e recebe um char* como argumento.
A função main() envia o ponteiro p como argumento para a função
func().

MATRIZES DE PONTEIROS PARA FUNÇÕES

Os ponteiros para funções oferecem uma maneira eficiente de executar uma


entre uma série de funções, com base em alguma escolha dependente de
parâmetros conhecidos somente em tempo de execução.
210 Treinamento em Linguagem C++ Cap. 11

Por exemplo, suponhamos que você queira escrever um programa onde


é apresentado um “menu” de opções ao usuário e, para cada escolha, o
programa deve executar uma chamada a uma função particular. Em vez de
utilizar estruturas tradicionais de programação como switch ou if-else ou
qualquer outra estrutura de controle para decidir qual função deve ser chamada,
você simplesmente cria uma matriz de ponteiros para funções e executa a função
correta por meio de seu ponteiro.
Eis um esqueleto do programa:
//PTRMAT.CPP
//Mostra uma matriz de ponteiros para função
#include <iostream.h>
#define TRUE 1

void func0(void), func1(void), func2(void);

void main()
{
void (*ptrf[3])(void);

ptrf[0] = func0;
ptrf[1] = func1;
ptrf[2] = func2;

do
{
int i;

cout << "\n0 - ABRIR";


cout << "\n1 - FECHAR";
cout << "\n2 - SALVAR";
cout << "\n\nEscolha um item: ";
cin >> i;
if(i < 0 || i > 2) break;
(*ptrf[i])();
} while(TRUE);
}
Cap. 11 Ponteiros 211

void func0()
{ cout << "\n\n*** Estou em func0() ***";}

void func1()
{ cout << "\n\n*** Estou em func1() ***";}

void func2()
{ cout << "\n\n*** Estou em func2() ***";}

A instrução
void (*ptrf[3])(void);

declara uma matriz de três ponteiros para funções void. O laço do-while em
main() chama uma das três funções dependendo da escolha do usuário. O
programa termina se um número menor que zero ou maior que dois for
digitado.
Matrizes de ponteiros para funções fornecem um mecanismo alternati-
vo de mudar o controle do programa sem utilizar estruturas de controle
convencionais.

INICIALIZANDO UMA MATRIZ DE PONTEIROS PARA FUNÇÕES

O programa anterior poderia ter inicializado a matriz ptrf na mesma instrução


de sua declaração. Eis a modificação:
//PTRMAT.CPP
//Mostra uma matriz de ponteiros para função
#include <iostream.h>
#define TRUE 1

void func0(void), func1(void), func2(void);

void main()
{
212 Treinamento em Linguagem C++ Cap. 11

void (*ptrf[3])(void) = { func0, func1, func2};

do
{
int i;

cout << "\n0 - ABRIR";


cout << "\n1 - FECHAR";
cout << "\n2 - SALVAR";
cout << "\n\nEscolha um item: ";
cin >> i;
if(i < 0 || i > 2) break;
(*ptrf[i])();
} while(TRUE);
}

void func0()
{ cout << "\n\n*** Estou em func0() ***";}

void func1()
{ cout << "\n\n*** Estou em func1() ***";}

void func2()
{ cout << "\n\n*** Estou em func2() ***";}

REVISÃO

1. Um ponteiro é um endereço de memória. Qualquer coisa armazenada na


memória do computador tem um endereço e este endereço é um ponteiro
constante.
2. Ponteiro variável é um lugar na memória que armazena o endereço de outra
variável.
Cap. 11 Ponteiros 213

3. Podemos encontrar o endereço de variáveis usando o operador de endere-


ços (&).
4. Se um ponteiro variável p contém o endereço de uma variável var, dizemos
que p aponta para var, ou que var é a variável apontada por p.
5. Os ponteiros variáveis são declarados usando um asterisco (*) que significa
ponteiro para. O tipo da variável apontada deve sempre ser especificado
na declaração do ponteiro para que o compilador possa executar operações
aritméticas corretamente.
6. O operador indireto (*) pode ser usado com ponteiros para obter o
conteúdo da variável apontada por ele ou para executar qualquer operação
na variável apontada. Em outras palavras, o operador indireto junto ao
nome do ponteiro substitui o nome da variável.
7. Em operações aritméticas com ponteiros, a unidade adotada é o número de
bytes do tipo apontado.
8. Os elementos de matrizes podem ser acessados por meio da notação matriz,
com colchetes, ou por meio da notação ponteiro, com um asterisco. O
endereço de uma matriz é um ponteiro constante.
9. Se o endereço de uma variável é passado como argumento para uma função,
a função tem acesso à variável original. A passagem por ponteiros oferece
os mesmos benefícios da passagem por referência. Em alguns casos, os
ponteiros oferecem maior flexibilidade.
10. Uma cadeia de caracteres constante pode ser definida como uma matriz ou
como um ponteiro. Usar ponteiros, neste caso, facilita a manipulação dos
caracteres da cadeia.
11. Definir uma matriz de ponteiros para cadeias de caracteres constantes
aloca menos memória que a definição por meio de uma matriz de strings.
12. O operador new obtém uma quantidade específica de memória do sistema
e retorna um ponteiro para essa memória. Este operador é usado para criar
variáveis durante a execução do programa.
13. O operador delete devolve a memória alocada por new ao sistema.
14. Os ponteiros void podem apontar para qualquer tipo de dado e são usados
em situações onde necessitamos de um ponteiro genérico, independente-
mente do tipo de dado apontado.
214 Treinamento em Linguagem C++ Cap. 11

15. O ponteiro this sempre aponta para o objeto sendo usado. Qualquer
membro da classe poderá ter acesso a ele. O ponteiro this é útil quando a
função deve retornar o objeto do qual é membro.
16. Os membros de um objeto podem ser acessados por meio de um ponteiro
que aponta para o objeto. Para isto, deve ser usado o operador de acesso a
membros (->). A mesma sintaxe é usada para acessar membros de uma
estrutura.
17. Uma classe ou uma estrutura pode conter um ponteiro que aponta para o
seu próprio tipo. Por meio deste conceito, podemos criar algoritmos com-
plexos de armazenamento e acesso a dados como listas ligadas.
18. Um ponteiro pode apontar para outro ponteiro. Este tipo de variável é
declarada usando duas vezes o asterisco (**).
19. Um programa C++ pode acessar os argumentos da linha de comando por
meio do uso de dois argumentos na definição da função main(). O primeiro,
argc, é um número inteiro cujo valor indica o número de argumentos
fornecidos na linha de comando. O segundo, argv, é uma matriz de
ponteiros para cadeias de caracteres que permite o acesso aos dados
fornecidos.
20. Um ponteiro para uma função contém o endereço da localização da função
na memória e é usado sempre que o nome da função for desconhecido, ou
como argumento de funções que chamam outras funções por meio de seu
endereço.
21. Uma matriz de ponteiros para funções é usada em situações em que o
programa deve escolher a função a ser executada dependendo de uma
condição conhecida somente durante a execução.

EXERCÍCIOS

1. Um ponteiro é:
a) o endereço de uma variável;
b) uma variável que armazena endereços;
Cap. 11 Ponteiros 215

c) o valor de uma variável;


d) um indicador da próxima variável a ser acessada.
2. Escreva uma instrução que imprima o endereço da variável var.
3. Indique: (1) operador de endereços
(2) operador de referência
a) p = &i;
b) int &i=j;
c) cout << &i;
d) int *p=&i;
e) int& func(void);
f) void func(int &i);
g) func(&i);
4. A instrução int *p
a) cria um ponteiro com valor indefinido;
b) cria um ponteiro do tipo int;
c) cria um ponteiro com valor zero;
d) cria um ponteiro que aponta para uma variável int.
5. O que significa o operador asterisco em cada um dos seguintes casos:
a) int *p;
b) cout << *p;
c) *p=x*5;
d) cout << *(p+1);
6. Quais das seguintes instruções declaram um ponteiro para uma variável
float?
a) float *p;
216 Treinamento em Linguagem C++ Cap. 11

b) *float p;
c) float* p;
d) float *p=&f;
e) *p;
f) float& p=q;
g) *float p;
7. Na expressão int *p; o que é do tipo int?
a) a variável p;
b) o endereço de p;
c) a variável apontada por p;
d) o endereço da variável apontada por p.
8. Se o endereço de var foi atribuído a um ponteiro variável pvar, quais das
seguintes expressões são verdadeiras?
a) var == &pvar;
b) var == *pvar;
c) pvar == *var;
d) pvar == &var;
9. Assuma as declarações abaixo e indique qual é o valor das seguintes
expressões:
int i=3,j=5;
int *p=&i, *q=&j;

a) p==&i;
b) *p - *q;
c) **&p;
d) 3*-*p/ *q+7;
Cap. 11 Ponteiros 217

10. Qual é a saída deste programa?


#include <iostream.h>
void main()
{
int i=5,*p;
p=&i;
cout << p << ’\t’ << (*p+2) << ’\t’ << **&p
<< ’\t’ << (3**p) << ’\t’ << (**&p+4);
}

11. Se i e j são variáveis inteiras e p e q, ponteiros para int, quais das seguintes
expressões de atribuição são incorretas?
a) p=&i;
b) *q=&j;
c) p=&*&i;
d) i=(*&)j;
e) i=*&*&j;
f) q=&p;
g) i=(*p)++ + *q;
h) if(p == i) i++;
12. Explique cada uma das seguintes declarações e identifique quais são
incorretas.
a) int *const x=&y;
b) const int &x=*y;
c) int &const x=*y;
d) int const *x=&y;
13. O seguinte programa é correto?
#include <iostream.h>
const VAL=987;
218 Treinamento em Linguagem C++ Cap. 11

void main()
{
int *p=VAL;
cout << *p;
}

14. O seguinte programa é correto?


#include <iostream.h>
const VAL=987;
void main()
{
int i=VAL;
int *p;
cout << *p;
}

15. Qual é a diferença entre: mat[3] e *(mat+3)?


16. Admitindo a declaração: int mat[8]; por que a instrução mat++; é incorreta
17. Admitindo a declaração: int mat[8]; quais das seguintes expressões referen-
ciam o valor do terceiro elemento da matriz?
a) *(mat+2);
b) *(mat+3);
c) mat+2;
d) mat+3;
18. O que faz o programa seguinte:
#include <iostream.h>
void main()
{
int mat[]={4,5,6};
for(int j=0; j<3 ; j++)
cout << "\n" << *(mat+j);
}
Cap. 11 Ponteiros 219

19. O que faz o programa seguinte:


#include <iostream.h>
void main()
{

int mat[]={4,5,6};
for(int j=0; j<3 ; j++)
cout << "\n" << (mat+j);
}

20. O que faz o programa seguinte:


#include <iostream.h>
void main()
{
int mat[]={4,5,6};
int *p=mat;
for(int j=0; j<3 ; j++)
cout << "\n" << *p++;
}

21. Qual é a diferença entre as duas instruções seguintes?


char s[]="Brasil";
char *s ="Brasil";

22. Assumindo a declaração:


char *s="Eu não vou sepultar Cesar";

O que imprimirão as instruções seguintes:


a) cout << s;
b) cout << &s[0];
c) cout << (s+11);
d) cout << s[0];
220 Treinamento em Linguagem C++ Cap. 11

23. Escreva a expressão mat[i][j] em notação ponteiro.


24. Qual é a diferença entre os seguintes protótipos de funções:
void func(char *p);
void func(char p[]);

25. Assumindo a declaração:


char *items[5] = { "Abrir",
"Fechar",
"Salvar",
"Imprimir",
"Sair"
};

Para poder escrever a instrução p=items; a variável p deve ser declarada


como:
a) char p;
b) char *p;
c) char **p;
d) char ***p;
26. O operador new:
a) cria uma variável de nome new;
b) retorna um ponteiro void;
c) aloca memória para uma nova variável;
d) informa a quantidade de memória livre.
27. O operador delete:
a) apaga um programa;
b) devolve memória ao sistema operacional;
c) diminui o tamanho do programa;
d) cria métodos de otimização.
Cap. 11 Ponteiros 221

28. Explique o significado da palavra void em cada uma das seguintes instruções:
a) void *p;
b) void p();
c) void p(void);
d) void (*p)();
29. Qual é o erro deste trecho de programa:
float x=333.33;
void *p=&x;

cout << *p;

30. O que é o ponteiro this e quando é usado?


31. Se p é um ponteiro para um objeto da classe data, então quais das seguintes
instruções executam a função-membro printdata()?
a) p.printdata();
b) *p.printdata();
c) p->printdata();
d) *p->printdata();
32. Se p é uma matriz de ponteiros para objetos da classe data, escreva uma
instrução que execute a função-membro printdata() do objeto apontado
pelo terceiro elemento da matriz p.
33. Numa lista ligada:
a) cada item contém um ponteiro para o próximo item;
b) cada item contém dados ou ponteiros para os dados;
c) cada item contém uma matriz de ponteiros;
d) os itens são armazenados numa matriz.
222 Treinamento em Linguagem C++ Cap. 11

34. Observe a classe String do programa PSTRSORT.CPP. Por que motivo o


programa não funcionará se incluirmos o seguinte destrutor na classe
String?
~String()
{ delete str;}

35. O que declara cada uma destas instruções?


a) int (*ptr)[10];
b) int *ptr[10];
c) int (*ptr)();
d) int *ptr();
e) int (*ptr[10])();
36. Escreva uma função que inverta a ordem dos caracteres de uma cadeia de
caracteres que ela recebe como argumento. Use ponteiros.
Exemplo: “Saudações” resulta “seõçaduaS”
37. Escreva uma função que lê um caractere e seu atributo do vídeo. Use a
interrupção 0x10 com os seguintes dados de entrada: AH=8 e BH=0. Esta
interrupção retorna em AH o atributo e em AL os caracteres que estiverem
na posição do cursor. Verifique o exercício 39 do Capítulo 7, no Módulo 1.
38. Escreva uma função que salva uma janela do vídeo. A função deve receber
as coordenadas da janela e retornar um ponteiro para o buffer onde a janela
foi salva. Utilize a função do exercício 37 anterior. Verifique o exercício 39
do Capítulo 7, no Módulo 1.
39. Escreva uma função que imprime um caractere e seu atributo do vídeo. Use
a interrupção 0x10 com os seguintes dados de entrada: AH=9, AL=caractere,
BL=atributo, CX=1 e BH=0. Esta interrupção não retorna nada. Verifique o
exercício 39 do Capítulo 7, no Módulo 1.
40. Escreva uma função que restaura uma janela salva pela função do exercício
38 anterior. A função recebe as coordenadas da janela onde os dados devem
ser impressos e um ponteiro para o buffer dos dados e não retorna nada.
Utilize a função do exercício 39. Verifique o exercício 39 do Capítulo 7, no
Módulo 1.
Cap. 11 Ponteiros 223

41. Adicione um destrutor à classe ListaLigada que destrói todos os itens da


lista quando um objeto é liberado da memória.
42. Crie uma estrutura para descrever restaurantes. Os membros devem arma-
zenar o nome, o endereço, o preço médio e o tipo de comida. Crie uma lista
ligada que apresente os restaurantes de um certo tipo de comida indexados
pelo preço. O menor preço deve ser o primeiro da lista. Escreva um
programa que peça o tipo de comida para o usuário e imprima os restau-
rantes que oferecem esse tipo de comida.
MAKRON
Books
Capítulo 12

FUNÇÕES VIRTUAIS E AMIGAS

Funções virtuais
12.Funções Virtuais,
e amigas
Amigas e Estáticas
Neste capítulo, discutiremos tópicos avançados sobre classes. Cobriremos a
definição e o uso de funções virtuais e funções amigas. Estas funções não são
necessárias a todos os programas C++, entretanto são essenciais em várias
situações. O aprendizado destes conceitos permite ao programador criar classes
mais claras e obter resultados altamente poderosos no sentido de facilitar a
reutilização do código e de aumentar a flexibilidade de funções operadoras.

FUNÇÕES VIRTUAIS

Muitas vezes, quando derivamos um grupo de classes a partir de uma classe-


base, certas funções-membro precisam ser redefinidas em cada uma das classes
derivadas. Por exemplo, suponhamos que você tenha uma classe que define os
elementos básicos de uma conta bancária. Esta classe contém uma função print()
que imprime seus dados. Agora, suponhamos que você deseje estender a
habilidade desta classe criando classes derivadas para manipular contas espe-
ciais, poupanças e aplicações diversas. Para imprimir os itens adicionais, a
função print() deve ser redefinida em cada subclasse.
Agora, você planeja agrupar um número de classes para definir as
contas de um cliente. Um dos meios é criar uma matriz de ponteiros para os
diferentes objetos envolvidos.

224
Cap. 12 Funções virtuais e amigas 225

Qual seria o tipo da matriz tendo em vista que ela deve armazenar
ponteiros para objetos de classes diferentes? A resposta está em como o
compilador opera ponteiros em situações deste tipo. Um ponteiro para um
objeto de uma classe derivada é de tipo compatível com um ponteiro para um
objeto da classe-base.
Desta forma, poderíamos declarar a nossa matriz por meio da seguinte
instrução:
Base *p[10];

Sendo deriv0 um objeto da classe derivada DERIV0, deriv1 objeto da


classe DERIV1 etc., podemos atribuir seus endereços a cada um dos elementos
da matriz:
p[0] = &deriv0;
p[1] = &deriv1;
p[2] = &deriv2;
.
.
.
p[9] = &deriv9;

Poderíamos imprimir os dados de cada objeto usando um laço simples:


for(int i=0; i<10; i++) p[i]->print();

Observe que funções completamente diferentes são executadas por


meio da mesma instrução de chamada. Se o ponteiro p[i] aponta para poupança,
a função que imprime os dados da conta poupança será executada; se p[i] aponta
para uma conta simples, seus dados serão impressos, e assim por diante.

POLIMORFISMO

A característica de chamar funções membros de um objeto sem especificar o


tipo exato do objeto é conhecida como polimorfismo. A palavra polimorfismo
significa “assumir várias formas”. Em C++ indica a habilidade de uma única
instrução chamar diferentes funções e portanto assumir formas diferentes.
226 Treinamento em Linguagem C++ Cap. 12

Para que o exemplo anterior funcione corretamente, duas condições de-


vem ser introduzidas. Em primeiro lugar, todas as diferentes classes de contas
devem ser derivadas da mesma classe-base. Em segundo lugar, a função print()
deve ser declarada como virtual na classe-base.

FUNÇÕES NÃO-VIRTUAIS ACESSADAS POR MEIO DE PONTEIROS

A necessidade do uso da declaração virtual aparece quando a função print() é


chamada para um objeto específico por meio de um ponteiro para a classe-base.
O nosso próximo exemplo mostra o problema quando a declaração virtual não
é usada.
//NAOVIRT.CPP
//Funções normais acessadas por ponteiros
#include <iostream.h>
class base
{
public:
void print(){cout << "\nBASE";}
};

class deriv0 : public base


{
public:
void print(){cout << "\nDERIV0";}
};

class deriv1 : public base


{
public:
void print(){cout << "\nDERIV1";}
};
Cap. 12 Funções virtuais e amigas 227

class deriv2 : public base


{
public:
void print(){cout << "\nDERIV2";}
};

void main()
{
base *p[3]; //Matriz de ponteiros para classe-base

deriv0 dv0; //Objeto da classe deriv0


deriv1 dv1; //Objeto da classe deriv1
deriv2 dv2; //Objeto da classe deriv2

p[0]=&dv0; //Preenche a matriz


p[1]=&dv1;
p[2]=&dv2;

for(int i=0;i<3;i++)
p[i]->print();
}

Eis a saída:
BASE
BASE
BASE

As classes deriv0, deriv1 e deriv2 são derivadas da classe-base. Cada


uma destas três classes contem uma função print(). Em main() criamos uma
matriz de ponteiros para objetos da classe-base e atribuímos os endereços dos
objetos das classes derivadas a cada um dos elementos da matriz.
p[0]=&dv0; //Preenche a matriz
p[1]=&dv1;
p[2]=&dv2;

Observe que atribuímos endereços de objetos das classes derivadas a


ponteiros da classe-base. Esta atribuição é permitida pois o compilador identi-
228 Treinamento em Linguagem C++ Cap. 12

fica um objeto de uma classe derivada como sendo um exemplo de objeto da


classe-base. Entretanto, quando a instrução
for(int i=0;i<3;i++)
p[i]->print();

é executada, a função print(), membro da classe-base, é sempre usada. Em outras


palavras, a função base::print() é executada nas três chamadas.
O compilador ignora o conteúdo do ponteiro p[i] e usa o seu tipo para
identificar a função-membro a ser chamada.
Para acessar objetos de diferentes classes usando a mesma instrução,
devemos declarar as funções da classe-base que serão reescritas em classes
derivadas usando a palavra-chave virtual.

IMPLEMENTANDO FUNÇÕES VIRTUAIS

Uma simples modificação no exemplo anterior fará com que os resultados


produzidos tenham exatamente o efeito desejado. Incluiremos a palavra virtual
no início da declaração da função print() da classe-base.
//VIRTFUNC.CPP
//Funções virtuais acessadas por ponteiros
#include <iostream.h>
class base
{
public:
virtual void print(){cout << "\nBASE";}
};

class deriv0 : public base


{
public:
void print(){cout << "\nDERIV0";}
};
Cap. 12 Funções virtuais e amigas 229

class deriv1 : public base


{
public:
void print(){cout << "\nDERIV1";}
};

class deriv2 : public base


{
public:
void print(){cout << "\nDERIV2";}
};

void main()
{
base *p[3]; //Matriz de ponteiros para classe-base

deriv0 dv0; //Objeto da classe deriv0


deriv1 dv1; //Objeto da classe deriv1
deriv2 dv2; //Objeto da classe deriv2

p[0]=&dv0; //Preenche a matriz


p[1]=&dv1;
p[2]=&dv2;

for(int i=0;i<3;i++)
p[i]->print();
}

Eis a saída:
DERIV0
DERIV1
DERIV2

Observe que só foi acrescentada a palavra virtual à função print() da


classe-base. Todo o resto do programa continua igual. Agora, as funções-mem-
bros das classes derivadas são executadas.
230 Treinamento em Linguagem C++ Cap. 12

A instrução
for(int i=0;i<3;i++)
p[i]->print();

executa diferentes funções, dependendo do conteúdo de p[i], e não de seu tipo.

RESOLUÇÃO DINÂMICA

As instruções de chamada a funções não-virtuais são resolvidas em tempo de


compilação e são traduzidas em chamadas a funções de endereço fixo. Isto faz
com que a instrução seja vinculada à função antes da execução.
Quando uma instrução de chamada a uma função virtual é encontrada
pelo compilador, ele não tem como identificar qual é a função associada em
tempo de compilação. Em instruções como
p[i]->print();

o compilador não conhece qual classe p[i] contém antes de o programa ser
executado. Então, a instrução é avaliada em tempo de execução, quando é
possível identificar qual tipo de objeto é apontado por p[i]. Isto é chamado
resolução dinâmica. Esta resolução é completamente diferente da que o com-
pilador executa quando funções não-virtuais são acessadas.
A resolução dinâmica permite que uma instrução seja associada a uma
função no momento de sua execução. O programador especifica que uma
determinada ação deve ser tomada em um objeto por meio de uma instrução.
O programa, na hora da execução, interpreta a ação e vincula a instrução à
função apropriada.
A resolução dinâmica é implementada por meio do uso de uma tabela
de funções virtuais chamada “v-table”. Esta tabela é uma matriz de ponteiros
para funções que o compilador monta para toda classe que usa funções virtuais.
Em cada classe que contém uma função virtual, o compilador acrescenta
um membro de dado que armazena o endereço da tabela v-table.
Eis um exemplo que permite verificar a existência deste ponteiro:
Cap. 12 Funções virtuais e amigas 231

//VTABLE.CPP
//Mostra ponteiro para a tabela v-table
#include <iostream.h>
class basvirt
{
private:
int x;
public:
virtual void print(){ cout << "\nBASE-VIRT";}
};

class basnvirt
{
private:
int x;
public:
void print(){cout << "\nNAO-VIRT";}
};

void main()
{
cout << "\nVirtual = " << sizeof(basvirt);
cout << "\nNão virtual = " << sizeof(basnvirt);
}

Eis a saída:
Virtual = 4
Não virtual = 2

Observe que a classe basvirt é maior em número de bytes, pois foi


reservado espaço para o ponteiro para v-table. A resolução dinâmica envolve
mais memória e tempo de execução, por outro lado aumenta a flexibilidade no
projeto de software e permite criar bibliotecas de classes que outros programa-
dores podem estender mesmo não tendo o arquivo-fonte.
232 Treinamento em Linguagem C++ Cap. 12

FUNÇÕES VIRTUAIS PURAS

Uma função virtual pura é uma função virtual sem bloco de código. Para criar uma
função virtual pura, usamos o operador de atribuição seguido de um zero após
o seu protótipo.
O propósito de uso de uma função virtual pura é em situações em que
a função não será nunca executada e está presente somente para que seja
redefinida em todas as classes derivadas. A função serve somente para prover
uma interface polimórfica para as classes derivadas.
Por exemplo, no programa VIRTFUNC.CPP, a função base::print()
nunca é executada e nem é necessária à classe-base. Neste caso, o corpo da
função pode ser removido e a notação =0, adicionada ao seu protótipo.
//VIRTFUNC.CPP
//Mostra funções virtuais puras
#include <iostream.h>
class base
{
public:
virtual void print() =0; //Função virtual pura
};

class deriv0 : public base


{
public:
void print(){cout << "\nDERIV0";}
};

class deriv1 : public base


{
public:
void print(){cout << "\nDERIV1";}
};
class deriv2 : public base
{
Cap. 12 Funções virtuais e amigas 233

public:
void print(){cout << "\nDERIV2";}
};

void main()
{
base *p[3]; //Matriz de ponteiros para classe-base

deriv0 dv0; //Objeto da classe deriv0


deriv1 dv1; //Objeto da classe deriv1
deriv2 dv2; //Objeto da classe deriv2

p[0]=&dv0; //Preenche a matriz


p[1]=&dv1;
p[2]=&dv2;

for(int i=0;i<3;i++)
p[i]->print();
}

Agora a função virtual é declarada pela instrução


virtual void print() =0; //Função virtual pura

O sinal de igual (=) não tem nenhum efeito aqui. O valor 0 não é
atribuído a nada. A sintaxe =0 é usada somente para indicar ao compilador que
esta é uma função virtual pura, e não tem corpo.
Algumas restrições devem ser observadas quanto ao uso de uma função
virtual pura. Não é permitido declarar nenhum objeto da classe em que a função
foi incluída. No nosso exemplo, não podemos declarar nenhum objeto da
classe-base. Esta restrição é necessária para prevenir qualquer chamada à
função virtual pura por meio de um objeto.
234 Treinamento em Linguagem C++ Cap. 12

CLASSES ABSTRATAS

Uma classe que não pode ser usada para criar objetos é dita classe abstrata e
existe somente para ser usada como base para outras classes.
A classe que define uma função virtual pura é uma classe abstrata pois
não se pode declarar nenhum objeto dela. Entretanto, um ponteiro para uma
classe abstrata pode ser declarado para manipular objetos de classes derivadas.
Toda classe que contém pelo menos uma função virtual pura ou uma
classe derivada que não redefine a função virtual pura de sua classe-base é
abstrata.

FUNÇÕES VIRTUAIS E REGISTROS VARIANTES

Agora que já aprendemos o que são funções virtuais, vamos analisar uma
situação mais real de seu uso. Algumas vezes queremos armazenar dados em
que os registros envolvem informações diferentes dependendo de certas cir-
cunstâncias. Por exemplo, o registro de um paciente hospitalar pode variar em
função de o paciente ser ou não assegurado.
Podemos escrever o seguinte programa:
//CLIENTES.CPP
//Mostra a manipulação de registros com variantes
#include <iostream.h>
#include <iomanip.h>
#include <stdio.h> //para gets()

class Cliente
{
private:
char nome[40];
public:
void getnome()
Cap. 12 Funções virtuais e amigas 235

{
cout << "Digite nome: ";
gets(nome);
}
virtual void print()
{
cout << "\n" << nome;
}
};
class Assegurado : public Cliente
{
private:
char seguradora[40];
int numseguro;
public:
void getsegur()
{
cout << "Digite Seguradora: ";
gets(seguradora);
cout << "Digite número do seguro: ";
cin >> numseguro;
}
void print()
{
Cliente::print();
cout << "\n Seguradora: " << seguradora;
cout << "\n Número do seguro: "
<< numseguro;
}
};
class NaoAssegurado : public Cliente
{
private:
float consulta;
int banco, cheque;
236 Treinamento em Linguagem C++ Cap. 12

public:
void getvalor()
{
cout << "Valor da consulta: ";
cin >> consulta;
cout << "Banco: "; cin >> banco;
cout << "Cheque: "; cin >> cheque;
}
void print()
{
Cliente::print();
cout << setprecision(2);
cout << "\n Valor da consulta: "
<< consulta;
cout << "\n Banco: " << banco;
cout << "\n Cheque: " << cheque;
}
};
void main()
{
Cliente *Paciente[100];
Assegurado *Ass;
NaoAssegurado *NaoAss;
int n=0;
char opcao;

do
{
cout << "\nAssegurado (s/n) ? ";
cin >> opcao;
if(opcao == ’s’)
{
Ass = new Assegurado;
Ass->getnome();
Ass->getsegur();
Cap. 12 Funções virtuais e amigas 237

Paciente[n++] = Ass;
}
else
{
NaoAss = new NaoAssegurado;
NaoAss->getnome();
NaoAss->getvalor();
Paciente[n++]=NaoAss;
}
cout << "\nInserir outro cliente (s/n)? ";
cin >> opcao;
} while( opcao==’s’);

for(int i=0;i<n;i++) Paciente[i]->print();


}

AS CLASSES Assegurado E NaoAssegurado

As classes Assegurado e NaoAssegurado adicionam novos itens de dados aos


da classe-base Cliente. Observe que estas classes são de tamanhos diferentes e,
utilizando objetos delas, montaremos um arquivo. Obteremos um arquivo com
registros de tamanhos desiguais.

O PROGRAMA main()

O programa main() cria uma matriz de ponteiros para objetos da classe-base


Cliente, baseado no conceito de que um ponteiro para uma classe-base pode
armazenar o endereço de qualquer classe derivada. Desta forma, podemos
arquivar registros variados.
238 Treinamento em Linguagem C++ Cap. 12

Em seguida, é solicitado ao usuário escolher o tipo de cliente, assegu-


rado ou não-assegurado. Para clientes assegurados, o programa solicita os
dados da companhia de seguros e, para clientes não-assegurados, o programa
solicita os dados do cheque a ser pago.
Quando o usuário terminar de inserir os dados dos clientes, o programa
imprime os registros por meio dos ponteiros. O relatório é semelhante ao
seguinte:
Regina Coelho
Seguradora: Unimed
Número do seguro: 1233
Airton José Pereira
Valor da consulta: 234.09
Banco: 12
Cheque: 3215
Luiz Ferreira Paula
Seguradora: Seguros Brasil S.A.
Número do seguro: 555
Valdete Correia Jorge
Valor da consulta: 333.33
Banco: 33
Cheque: 4452

CLASSE-BASE VIRTUAL

Além de declarar funções virtuais, podemos usar a palavra virtual para declarar
uma classe inteira.
A necessidade de declarar uma classe virtual é quando esta foi usada
como classe-base para mais de uma classe derivada e dessas classes derivadas
foi derivada outra por meio de herança múltipla. Considere a seguinte situação:
Cap. 12 Funções virtuais e amigas 239

BASE
pai

DERIV1 DERIV2
filho1 filho2

SDERIV
neto

Uma classe-base não pode ser usada mais de uma vez numa mesma
classe derivada:
class BASE { ... };
class DERIV : B, B { ... }; // ERRO

Entretanto, uma classe-base pode ser indiretamente usada mais de uma


vez em classes derivadas. Observe a figura anterior:
class BASE { ... };
class DERIV1 : public BASE { ... };
class DERIV2 : public BASE { ... };
class SDERIV : public DERIV1,public DERIV2{ ... };//OK
240 Treinamento em Linguagem C++ Cap. 12

Neste caso, cada objeto da classe SDERIV terá dois subobjetos da classe
BASE. Se um objeto da classe SDERIV tentar acessar dados ou funções da
classe BASE, ocorrerá um erro de ambigüidade. O trecho do programa seguinte
mostra o erro:
class BASE
{
protected:
int basint;
};

class DERIV1 : public BASE {};


class DERIV2 : public BASE {};

class SDERIV : public DERIV1, public DERIV2


{
public:
int retint()
{ return baseint; } //ERRO: ambigüidade
};

Quando a função retint() tenta acessar a variável baseint membro da


classe BASE, o compilador apresenta um erro, pois cada uma das classes
DERIV1 e DERIV2 contém, por herança, uma cópia desta variável e o compi-
lador não pode reconhecer qual das cópias está sendo referenciada em SDERIV.
Para eliminar a ambigüidade, basta acrescentar a palavra-chave virtual
na especificação da classe BASE nas classes DERIV1 e DERIV2. Isto torna a
classe BASE virtual.
class BASE
{
protected:
int basint;
};

class DERIV1 : virtual public BASE {};


class DERIV2 : virtual public BASE {};
Cap. 12 Funções virtuais e amigas 241

class SDERIV : public DERIV1, public DERIV2


{
public:
int retint()
{ return baseint; } //OK
};

A declaração virtual nas classes DERIV1 e DERIV2 faz com que estas
classes compartilhem uma única classe-base. A classe SDERIV só terá um
subobjeto da classe BASE.

CONSTRUTORES EM CLASSE-BASE VIRTUAL

Os construtores de classes-base são sempre executados antes do construtor da


classe derivada. Os construtores de classes-base virtuais são chamados antes de
qualquer construtor de classes-base não-virtuais.
Por exemplo:
class D : public B, virtual public BV { ... };

D obj;

produz a seguinte ordem de chamada a construtores:


BV(); // Inicializa classe-base virtual
B(); // Inicializa classe-base não virtual
D(); // Inicializa classe derivada

DESTRUTORES DE CLASSE-BASE E DE CLASSE DERIVADA

Se destrutores são definidos na classe-base e na classe derivada, eles são


executados na ordem reversa à qual o construtor é executado. Quando um objeto
242 Treinamento em Linguagem C++ Cap. 12

da classe derivada é destruído, o destrutor da classe derivada é chamado e em


seguida o destrutor da classe-base é chamado.
Esta ordem não é mantida quando um ponteiro para a classe-base
contém um objeto de uma classe derivada. Se o operador delete é aplicado a
um ponteiro da classe-base, o compilador chama o destrutor da classe-base,
mesmo que o ponteiro aponte para um objeto da classe derivada.
A solução é declarar o destrutor da classe-base virtual. Isto faz com que
os destrutores de todas as classes derivadas sejam virtuais, mesmo que não
compartilhem o mesmo nome do destrutor da classe-base. Então, se delete é
aplicado a um ponteiro para a classe-base, os destrutores apropriados são
chamados, independentemente de qual tipo de objeto é apontado pelo ponteiro.
Observe o exemplo:
class BASE
{
protected:
int basint;
public:
virtual void print(){ ... }
virtual ~BASE(){}
};

A classe BASE tem um destrutor virtual, mesmo sendo um destrutor


que não faz nada. Sempre que você escrever uma classe que tem uma função
virtual, ela deve ter um destrutor virtual mesmo que não necessite dele. A razão
disto é que uma classe derivada pode necessitar de um destrutor. Por exemplo,
você deriva uma classe da classe BASE chamada DERIV, e a classe derivada define
um construtor. Se um destrutor virtual está definido na classe-base, você assegura
que destrutores de classes derivadas são chamados na ordem necessária.
Observe que, enquanto os destrutores podem ser virtuais, os constru-
tores não podem.
Cap. 12 Funções virtuais e amigas 243

FUNÇÕES AMIGAS

O conceito de isolamento interno dos itens de uma classe, onde funções


não-membros não teriam o privilégio de acesso aos dados privados ou prote-
gidos desta classe, é violado por um mecanismo oferecido por C++ que permite
que funções não-membro, às quais foi dada permissão especial, tenham acesso
à parte interna da classe.
Declarando uma função como amiga, ela tem os mesmos privilégios de
uma função-membro, mas não está associada a um objeto da classe.
O próximo programa mostra a sintaxe de uma função amiga. Este
exemplo define uma classe tempo que permite à função prntm() o acesso a seu
membro privado.
//TEMPO.CPP
//Mostra o uso de uma função amiga
#include <iostream.h>

class tempo
{
private:
long segundos;
public:
tempo(long h,long m,long s)
{ segundos = h*3600+m*60+s; }

friend char *prntm(tempo); //Declaração Amiga


};

char *prntm(tempo); //Protótipo

void main()
{
tempo tm(5,8,20);
cout << "\n" << prntm(tm);
}
244 Treinamento em Linguagem C++ Cap. 12

char *prntm(tempo tm)


{
char *buff=new char[9];

int h=tm.segundos/3600;
int resto=tm.segundos%3600;
int m=resto/60;
int s=(resto%60);

buff[0]=h/10+’0’;buff[1]=h%10+’0’;buff[2]=’:’;
buff[3]=m/10+’0’;buff[4]=m%10+’0’;buff[5]=’:’;
buff[6]=s/10+’0’;buff[7]=s%10+’0’;buff[8]=’\0’;

return buff;
}

Neste exemplo, queremos que a função prntm() tenha acesso ao dado


privado da classe tempo. Desta forma, usamos a palavra-chave friend na
declaração da função:
friend char *prntm(tempo); //Declaração Amiga

Esta declaração pode ser colocada em qualquer posição da classe. Não


há diferença se for colocada na parte pública ou na parte privada da classe.
Um objeto da classe tempo é passado como argumento à função
prntm(). Este argumento é necessário pois, mesmo quando se dá a uma função
amiga o privilégio de acesso aos dados privados da classe, ela não é parte
daquela classe, devendo ser informada sobre qual objeto agirá.
A função prntm() primeiramente cria um espaço suficiente de memória
onde montará a hora no formato hh:mm:ss. O endereço deste buffer é retornado.
Cap. 12 Funções virtuais e amigas 245

FUNÇÕES AMIGAS COMO INTERFACE ENTRE CLASSES

Uma função amiga pode agir em duas ou mais classes diferentes, fazendo o
papel de elo de ligação entre as classes.
Suponhamos que você queira que a função prntm() imprima, além da
hora, a data atual. Para isto, ela deverá agir sobre um objeto tempo e sobre um
objeto data.
Eis o exemplo:
//TMDT.CPP
//Mostra função amiga como ponte entre duas classes

#include <iostream.h>

class data; //Necessário para a declaração de prntm()

class tempo
{
private:
long segundos;
public:
tempo(long h,long m,long s)
{ segundos = h*3600+m*60+s; }

friend char *prntm(tempo,data); //Amiga


};

class data
{
private:
int d,m,a;
public:
data(int dd, int mm, int aa)
{ d=dd; m=mm; a=aa%100; }
246 Treinamento em Linguagem C++ Cap. 12

friend char *prntm(tempo,data); //Amiga


};

char *prntm(tempo tm,data dt)


{
char *buff=new char[18];

int h=tm.segundos/3600;
int resto=tm.segundos%3600;
int m=resto/60;
int s=(resto%60);

buff[0]=h/10+’0’;buff[1]=h%10+’0’;buff[2]=’:’;
buff[3]=m/10+’0’;buff[4]=m%10+’0’;buff[5]=’:’;
buff[6]=s/10+’0’;buff[7]=s%10+’0’;buff[8]=’\n’;

buff[9]=dt.d/10+’0’;buff[10]=dt.d%10+’0’;buff[11]=’/’;
buff[12]=dt.m/10+’0’;buff[13]=dt.m%10+’0’;buff[14]=’/’;
buff[15]=dt.a/10+’0’;buff[16]=dt.a%10+’0’;
buff[17]=’\0’;

return buff;
}

void main()
{
tempo tm(5,8,20);
data dt(22,6,1994);
char *tmdt = prntm(tm,dt);
cout << "\n" << tmdt;
delete tmdt;
}

Nesta nova versão, definimos duas classes tempo e data. Como a função
prntm() deve ter acesso aos dados das duas classes, é declarada amiga nas duas
classes e um objeto de cada uma das classes é passado como argumento.
Cap. 12 Funções virtuais e amigas 247

Observe a declaração da classe data no início do programa:


class data;

Esta declaração é necessária pois não podemos referir-nos a uma classe


antes de sua definição, e a classe data é referida na declaração da função prntm()
da classe tempo; assim, data deve ser declarada antes de tempo.

CLASSES AMIGAS

Além de ser possível declarar funções independentes como amigas, podemos


declarar uma classe toda como amiga de outra. Neste caso, as funções-membro
da classe serão todas amigas da outra classe. A diferença é que estas funções-
membro têm também acesso à parte privada ou protegida de sua própria classe.
Eis um exemplo:
//CLASSAMI.CPP
//Mostra classes amigas
#include <iostream.h>

class tempo
{
private:
long h,m,s;
public:
tempo(int hh,int mm,int ss)
{ h=hh; m=mm; s=ss; }
friend class data; //data é uma classe amiga
};
class data
{
private:
int d,m,a;
public:
data(int dd,int mm, int aa)
248 Treinamento em Linguagem C++ Cap. 12

{ d=dd; m=mm; a=aa; }

void prndt(tempo tm)


{
cout << "\nData: "<< d << ’/’ << m
<< ’/’ << a;
cout << "\nHora: " << tm.h << ’:’ << tm.m
<< ’:’ << tm.s;
}
};

void main()
{
tempo tm(12,18,30),tm1(13,22,10);
data dt(18,12,55);

dt.prndt(tm);
dt.prndt(tm1);
}

Na classe tempo, declaramos toda a classe data como amiga. Então,


todas as funções-membro de data podem acessar os dados privados de tempo.
A declaração é a seguinte:
friend class data;

Observe a palavra class usada nesta declaração. Ela é necessária pois


não declaramos data como uma classe antes da especificação em tempo.
Poderíamos declarar
class data;

no início do programa e dentro de tempo declarar a classe amiga sem o uso da


palavra class:
friend data;
Cap. 12 Funções virtuais e amigas 249

A função main() cria dois objetos da classe tempo e um objeto da classe


data. Por meio de classe amiga, pudemos manter a mesma data para horas
diferentes.

INTEGRIDADE DA CLASSE E FUNÇÕES AMIGAS

Uma função ou classe amiga deve ser declarada como tal dentro da classe em
que os dados serão acessados. Em outras palavras, quem escreve uma classe
deve especificar quais funções ou classes irão acessar a sua parte privada ou
protegida.
Desta forma, um programador que não tem acesso ao código-fonte da
classe não poderá criar uma função amiga para esta classe. As funções amigas
devem ser declaradas pelo autor da classe.
Observe que a palavra-chave friend oferece acesso em uma só direção.
Se a classe A é amiga da classe B, o inverso não é verdadeiro.
Esta forma de uso garante a integridade da classe.

FUNÇÕES AMIGAS E SOBRECARGA DE OPERADORES

As funções amigas são especialmente convenientes quando usadas para rede-


finir operadores. O programa seguinte mostra a limitação do uso de funções
operadoras quando funções amigas não são usadas:
//PONTO.CPP
//Mostra sobrecarga do operador + sem o uso de friend
#include <iostream.h>

class ponto
{
private:
int x,y;
250 Treinamento em Linguagem C++ Cap. 12

public:
ponto() { x=0;y=0;} //Construtor s/ args

ponto(int x1,int y1) //Construtor c/ args


{ x=x1;y=y1;}

ponto operator +(ponto p) //Função operadora


{ return ponto(x+p.x,y+p.y); }

ponto operator +(int n) //Função operadora


{ return ponto(x+n,y+n); }

void printpt() //Imprime ponto


{ cout << ’(’ << x << ’,’ << y << ’)’;}
};

void main()
{
ponto p1(5,1),p2(2,3),p3;

p3 = p1 + p2; //operator+() arg. ponto


p3.printpt();

p3 = p1 + 5; //operator+() arg. int

//p3 = 5 + p1; //Erro

p3.printpt();
}

Neste programa, o operador + é sobrecarregado duas vezes: uma para


adicionar dois objetos da classe ponto e outra para adicionar um objeto da classe
ponto a um número inteiro.
Você já sabe que expressões do tipo:
Cap. 12 Funções virtuais e amigas 251

p3 = p1 + p2;
p3 = p1 + 5;

são interpretadas como:


p3 = p1.operator+(p2);
p3 = p1.operator+( 5);

O objeto do qual a função operadora é membro deve sempre ser a


variável à esquerda do operador e por este motivo expressões do tipo:
p3 = 5 + p1;

causam um erro de compilação.


Para usar expressões nas quais uma variável de um tipo básico ou uma
constante é o primeiro operando, devemos declarar a função operadora como
amiga. Eis um exemplo:
//PONTO.CPP
//Mostra sobrecarga do operador + usando friend
#include <iostream.h>

class ponto
{
private:
int x,y;
public:
ponto() { x=0;y=0;} //Construtor s/ args

ponto(int x1,int y1) //Construtor c/ args


{ x=x1;y=y1;}

ponto operator +(ponto p) //Função operadora


{ return ponto(x+p.x,y+p.y); }

ponto operator +(int n) //Função operadora


{ return ponto(x+n,y+n); }

friend ponto operator +(int n,ponto p); //Amiga


252 Treinamento em Linguagem C++ Cap. 12

void printpt() //Imprime ponto


{ cout << ’(’ << x << ’,’ << y << ’)’;}
};

ponto operator +(int n,ponto p)


{
return ponto(p.x+n,p.y+n);
}

void main()
{
ponto p1(5,1),p2(2,3),p3;

p3 = p1 + p2; //operator+() arg. ponto


p3.printpt();

p3 = p1 + 5; //operator+() arg. int

p3 = 5 + p1; //OK - Amiga

p3.printpt();
}

Nesta versão, criamos outra função operadora, desta vez amiga:


friend ponto operator +(int n,ponto p); //Amiga

Observe que a função amiga requer dois argumentos, enquanto a


função-membro requer somente um. Quando operator+() é definida como
amiga, os dois operandos são parâmetros da função. A expressão:
p3 = 5 + p1; //Chamada à função friend

é interpretada como
p3 = operator+(5,p1);
Cap. 12 Funções virtuais e amigas 253

Observe que a classe ponto contém três funções operadoras para


redefinir o mesmo operador. Esta situação pode ser alterada se escrevermos um
construtor que converte um int a um objeto da classe ponto. Quando um construtor
deste tipo existe, nas instruções:
p3 = p1 + 5;
p3 = 5 + p1;

o compilador usará o construtor para converter o valor int ao tipo ponto.


Eis a alteração:
//PONTO.CPP
//Mostra sobrecarga do operador + usando friend
#include <iostream.h>

class ponto
{
private:
int x,y;
public:
ponto() { x=0;y=0;} //Construtor s/ args

ponto(int x1,int y1) //Construtor c/ args


{ x=x1;y=y1;}

ponto(int n) //Construtor 1 arg


{ x=n;y=n;}

friend ponto operator +(ponto p,ponto p1);

void printpt() //Imprime ponto


{ cout << ’(’ << x << ’,’ << y << ’)’;}
};

ponto operator +(ponto p,ponto p1)


{ return ponto(p1.x+p.x,p1.y+p.y); }
254 Treinamento em Linguagem C++ Cap. 12

void main()
{
ponto p1(5,1),p2(2,3),p3;

p3 = p1 + p2; //Amiga

p3.printpt();

p3 = p1 + 5; //Construtor 1 arg e Amiga

p3 = 5 + p1; //Construtor 1 arg e Amiga

p3.printpt();
}

Neste exemplo, introduzimos o construtor de um argumento para


converter um int a um objeto ponto.
ponto(int n) { x=n;y=n;}

Quando o compilador encontra as instruções:


p3 = p1 + 5;
p3 = 5 + p1;

primeiramente chama o construtor de um argumento para converter o valor 5


a um objeto ponto e em seguida chama a função amiga operator+() de dois
argumentos da classe ponto.

SOBRECARGA DE OPERADORES UNÁRIOS E FUNÇÕES AMIGAS

Os operadores unários podem ser sobrecarregados usando funções-membro


sem argumentos ou funções amigas de um argumento.
Em particular, os operadores de incremento e decremento pós-fixados
deverão receber um argumento a mais do tipo int. Observe o exemplo a seguir:
Cap. 12 Funções virtuais e amigas 255

class Ponto
{ .
.
friend Ponto& operator++(Ponto&); //Incremento Pré
friend Ponto& operator++(Ponto&,int);//Incremento Pós
friend Ponto& operator--(Ponto&); //Decremento Pré
friend Ponto& operator--(Ponto&,int);//Decremento Pós
.
.
};

Quando um operador é sobrecarregado por meio de uma função amiga,


ela deve receber um argumento a mais do que se for escrito usando uma função-
membro.

RESTRIÇÕES AO USO DE AMIGAS

Uma função operadora amiga deve receber no mínimo um argumento objeto.


Em outras palavras, você não pode criar uma função operadora binária como
operator+() que receba dois inteiros como argumentos. Esta restrição previne a
redefinição de operadores internos da linguagem.
Alguns operadores não podem ser definidos como funções amigas;
devem ser definidos como funções-membro de classes. São eles:
=
->
()
Conversores de tipos
256 Treinamento em Linguagem C++ Cap. 12

REVISÃO

1. Funções virtuais são funções-membro definidas na classe-base e redefinidas


em classes derivadas.
2. Um ponteiro para uma classe-base pode conter o endereço de um objeto de
uma classe derivada. Esta atribuição é permitida pois o compilador trata
um objeto da classe derivada como sendo um exemplo de objeto da
classe-base.
3. Quando uma função virtual é chamada por meio de um ponteiro para a
classe-base, a versão da função implementada na classe derivada é execu-
tada. Se a mesma chamada for executada para uma função não-virtual, a
versão da função implementada na classe-base é executada.
4. Para declarar uma função virtual, usamos a palavra-chave virtual prece-
dendo a declaração da função na classe-base. A palavra virtual não é
necessária para as versões da função definidas em classes derivadas.
5. Polimorfismo é a característica de C++ que possibilita chamar uma função
por meio de um objeto, sem especificar qual é o tipo exato do objeto.
6. Funções virtuais possibilitam a implementação de polimorfismo. Em ou-
tras palavras, permitem que o programa decida qual é a versão da função
a ser executada, em tempo de execução. Esta decisão é tomada em tempo
de compilação para funções não-virtuais.
7. A implementação de funções virtuais torna possível a execução de um
mesmo tipo de ação em diferentes tipos de objetos.
8. As funções virtuais podem ser chamadas por meio de uma matriz de
ponteiros para a classe-base que armazena endereços de objetos de diferen-
tes classes derivadas.
9. Uma função virtual pura é uma função sem bloco de código. Usamos
funções virtuais puras em situações em que a função não será nunca
executada e está presente apenas para que seja redefinida em todas as
classes derivadas.
10. Funções virtuais puras fornecem um modo de implementar uma interface
polimórfica para as classes derivadas.
Cap. 12 Funções virtuais e amigas 257

11. Se a partir de uma classe-base derivamos outras classes que serão usadas
como base para derivar outra classe, surge a necessidade de declarar a
classe-base original como uma classe virtual.
12. A declaração de uma classe-base virtual permite que classes derivadas
compartilhem uma única versão da classe-base.
13. Um construtor não pode ser virtual.
14. Um destrutor é declarado virtual sempre que a classe contiver uma função
virtual. Esta declaração faz com que os destrutores de todas as classes
derivadas sejam virtuais.
15. Declarar o destrutor da classe-base como virtual assegura que os destrutores
das classes derivadas serão chamados na ordem necessária.
16. Funções amigas podem acessar os dados privados de uma classe mesmo
não sendo membros desta classe.
17. Funções amigas são usadas quando uma função deve ter acesso aos dados
de duas ou mais classes.
18. Uma classe amiga é declarada quando queremos que todas as suas funções-
membro sejam amigas.
19. A declaração de uma função ou classe amiga deve ser escrita dentro da
classe em que os dados serão acessados. Quem escreve uma classe é que
deve decidir quem terá permissão de acessar os seus membros privados.
20. Funções amigas são especialmente usadas na sobrecarga de operadores.
Elas permitem o uso de expressões nas quais o operador da esquerda é de
um tipo básico, uma constante ou um objeto de uma classe diferente da
qual o operador é membro.

EXERCÍCIOS

1. Quais das seguintes instruções são válidas, assumindo as declarações:


class Base { ... };
class Deriv : public Base { ... };
258 Treinamento em Linguagem C++ Cap. 12

Base bas, *pbas;


Deriv dv, *pdv;

a) pdv = &bas;
b) pbas= &pdv;
c) bas = dv;
d) dv = bas;
2. Quais das seguintes características podem ser implementadas por meio de
uma função virtual?
a) permitir que a classe-base estabeleça um protocolo com as suas classes
derivadas, de modo que estas últimas obtenham máxima funcionalidade;
b) funções sem corpo;
c) assegurar a chamada correta a funções-membro de objetos de diferentes
classes, usando uma mesma instrução de chamada à função;
d) agrupar objetos de diferentes classes de tal forma que possam ser
acessados pelo mesmo código de programa;
e) criar uma matriz de ponteiros para a classe-base que pode armazenar
ponteiros para classes derivadas;
f) redefinir funções-membro em classes derivadas.
3. Quais das seguintes afirmações estão corretas quando é executada a cha-
mada a uma função-membro usando um ponteiro para um objeto?
a) se a função é virtual, a instrução é resolvida tomando em conta o tipo
do objeto contido no ponteiro;
b) se a função é não-virtual, a instrução é resolvida tomando em conta o
tipo do ponteiro;
c) se a função é virtual, a instrução é resolvida após o início da execução
do programa;
d) se a função é não-virtual, a instrução é resolvida na compilação do
programa.
Cap. 12 Funções virtuais e amigas 259

4. Explique a diferença entre a sobrecarga de funções-membro virtuais e não


virtuais.
5. Considerando as seguintes declarações:
class Base
{ public:
void xyz(){...}
};

class Deriv : public Base


{ public:
void xyz(){...}
};

Deriv dv;
Base *pbas=&dv;

a) a instrução p->xyz(); executará a versão de xyz() membro da classe


_____________;
b) se xyz() for declarada virtual, então a instrução p->xyz(); executará a
versão de xyz() membro da classe _____________.
6. Escreva a declaração da função virtual xyz() de tipo void e que recebe um
argumento do tipo int.
7. Resolução dinâmica é o processo de
a) associar chamadas a funções a endereços fixos;
b) associar uma instrução a uma função no momento de sua execução;
c) criação de funções virtuais;
d) criar a tabela “v-table”.
260 Treinamento em Linguagem C++ Cap. 12

8. Uma função virtual pura é uma função virtual que:


a) não retorna nada;
b) é parte da classe derivada;
c) não recebe argumentos;
d) não tem corpo.
9. Escreva a declaração da função virtual pura chamada xyz() que não retorna
nada nem recebe nada como argumento.
10. As classes abstratas:
a) existem somente para derivação;
b) contêm funções-membro virtuais;
c) não têm corpo de código;
d) contêm funções virtuais puras.
11. Quais dos seguintes processos são permitidos com classes abstratas?
a) declarar objetos;
b) retornar um objeto de uma função;
c) enviar um objeto como argumento para uma função;
d) declarar ponteiros.
12. A classe-base virtual é usada quando:
a) diferentes funções nas classes-base e derivada têm o mesmo nome;
b) uma classe-base aparece mais de uma vez no processo de herança
múltipla;
c) há múltiplos caminhos de uma classe derivada para outra;
d) a identificação da função da classe-base é ambígua.
13. Uma classe-base é virtual quando:
a) a palavra virtual é colocada na sua declaração;
Cap. 12 Funções virtuais e amigas 261

b) contém uma função-membro virtual;


c) é especificada virtual na declaração da classe derivada;
d) contém uma função virtual pura.
14. Escreva as classes Animal, Vaca, Bufalo e Bezerro, sendo Vaca e Bufalo
derivadas de Animal e Bezerro derivada de Vaca e de Bufalo.
15. Verdadeiro ou falso: Toda função-membro pode ser declarada virtual
mesmo sendo um construtor ou um destrutor.
16. Verdadeiro ou falso: Uma função amiga pode acessar dados privados da
classe sem ser um membro da classe.
17. Uma função amiga pode ser usada para:
a) impedir heranças entre classes;
b) permitir o acesso a classes das quais não temos acesso ao código-fonte;
c) permitir a uma classe o acesso a classes não-documentadas;
d) aumentar a versatilidade de um operador sobrecarregado.
18. Escreva a declaração da função amiga chamada xyz() de tipo void e que
recebe um argumento da classe yvonne.
19. A palavra-chave friend é colocada:
a) na classe que permite o acesso de outra classe;
b) na classe que deseja acessar outra classe;
c) na parte privada da classe;
d) na parte pública da classe.
20. Escreva uma declaração que, na classe onde ela aparece, torna todos os
membros da classe Pedro funções amigas.
21. Escreva a classe Veiculo contendo Peso em quilos (int), VelocMax em km/h
(int) e Preco em US$ (float). Inclua um construtor sem argumentos que
inicializa os dados com zeros e um construtor que inicializa os dados com
os valores recebidos como argumento. Acrescente uma função para a
entrada de dados getdata() e uma função que imprime os dados putdata().
262 Treinamento em Linguagem C++ Cap. 12

Crie a classe CarroPasseio usando a classe Veiculo como base. Inclua Cor
(string) e Modelo (string). Inclua um construtor sem argumentos que
inicializa os dados com zeros e um construtor que inicializa os dados com
os valores recebidos como argumento. Acrescente uma função para a
entrada de dados getdata() e uma função que imprime os dados putdata().
Crie a classe Caminhao derivada da classe Veiculo. Inclua Toneladas (carga
máxima), AlturaMax (int) e Comprimento (int). Inclua um construtor sem
argumentos que inicializa os dados com zeros e um construtor que inicializa
os dados com os valores recebidos como argumento. Acrescente uma função
para a entrada de dados getdata() e uma função que imprime os dados
putdata().
Faça um programa que cria uma matriz de ponteiros para Veiculo. Inclua
um laço que pergunta ao usuário sobre o tipo de veículo e use o operador
new para criar objetos do tipo escolhido (CarroPasseio ou Caminhao).
Quando o usuário terminar a entrada dos dados de todos os veículos,
imprima os resultados usando outro laço.
22. Crie uma classe String que:
Sobrecarrega o operador + binário:
a) soma dois objetos String;
b) soma uma cadeia de caracteres a um objeto String;
c) soma um objeto String a uma cadeia de caracteres.
Sobrecarrega o operador == relacional:
a) compara dois objetos String;
b) compara um objeto String com uma cadeia de caracteres;
c) compara uma cadeia de caracteres a um objeto String.
Sobrecarrega o operador != relacional:
a) compara dois objetos String;
b) compara um objeto String com uma cadeia de caracteres;
c) compara uma cadeia de caracteres a um objeto String.
Cap. 12 Funções virtuais e amigas 263

Sobrecarrega o operador < relacional:


a) compara dois objetos String;
b) compara um objeto String com uma cadeia de caracteres;
c) compara uma cadeia de caracteres a um objeto String.
Sobrecarrega o operador > relacional:
a) compara dois objetos String;
b) compara um objeto String com uma cadeia de caracteres;
c) compara uma cadeia de caracteres a um objeto String.
Sobrecarrega o operador <= relacional:
a) compara dois objetos String;
b) compara um objeto String com uma cadeia de caracteres;
c) compara uma cadeia de caracteres a um objeto String.
Sobrecarrega o operador >= relacional:
a) compara dois objetos String;
b) compara um objeto String com uma cadeia de caracteres;
c) compara uma cadeia de caracteres a um objeto String.
23. Defina uma classe chamada fracao. Esta classe deve armazenar o numera-
dor e o denominador de uma fração em duas variáveis inteiras. Inclua:
As operações de adição, subtração, multiplicação e divisão entre objetos e
entre um número inteiro e um objeto.
MAKRON
Books
Capítulo 13
OPERAÇÕES COM ARQUIVOS
IOSTREAM

Operações com
13.Operações com
arquivos:
Arquivos
iostream
Iostreas
Neste capítulo, você aprenderá as facilidades de C++ para operações de leitura
e gravação em discos. Começaremos com uma breve descrição das classes
usadas para executar esta tarefa, conhecidas como classes iostream. Em seguida,
mostraremos como estas classes podem ser aplicadas a programas que neces-
sitam manipular arquivos, com exemplos de diferentes situações.

OBJETOS STREAM

Em C++, os objetos stream são o ponto central das classes iostream. Um objeto
stream pode ser pensado como um lugar de recebimento ou envio de bytes.
Desta forma, um arquivo em disco é um objeto stream. A palavra stream
significa “um fluxo de caracteres”.
O conceito de arquivos é ampliado no sentido de considerar arquivos
não somente os que existem em discos mas também o teclado, o vídeo, a
impressora e as portas de comunicação. Por exemplo, o objeto cout representa
o vídeo (recebimento de bytes) e o objeto cin representa o teclado (envio de
bytes). As classes iostream interagem com esses arquivos.

264
Cap. 13 Operações com arquivos: iostream 265

A biblioteca de classes do compilador Microsoft C/C++ Versão 7.0 e a


do compilador Borlandc C++ incluem classes para envio e recebimento de bytes
de e para memória com uma sintaxe semelhante à usada para arquivos.
Discutiremos o uso dessas classes.
Diferentes objetos stream são usados para representar diferentes inte-
rações com periféricos de entrada e saída. Cada stream é associado a uma classe
particular, caracterizada pelas suas funções de interface e pelos seus operadores
de inserção e extração.

A HIERARQUIA DE CLASSES IOSTREAM

O diagrama de hierarquia de classes mostra como estão relacionadas as classes


da família de I/O do compilador Microsoft C/C++ Versão 7.0. Consultá-lo
facilita a localização da classe-base e das classes que são derivadas por herança.
Para elaborar programas básicos que manipulam arquivos, não é
necessário entender todos os detalhes das relações interclasses, entretanto uma
visão geral será bastante útil.
No decorrer deste livro, fizemos uso extensivo dos objetos cout e cin.
O objeto cout é classificado como objeto de destino de bytes e representa a
saída stream padrão, enquanto o objeto cin é dito objeto de origem de bytes e
representa a entrada stream padrão.
Observe que cout é um objeto da classe ostream_withassign que é
derivada da classe ostream. Similarmente cin é um objeto da classe
istream_withassign que é derivada da classe istream.
Objetos de destino de bytes usam o operador de inserção <<, membro
da classe ostream. Os objetos de origem de bytes usam o operador de extração
>>, membro da classe istream. Estas duas classes são derivadas da classe ios.
266 Treinamento em Linguagem C++ Cap. 13

DIAGRAMA DA HIERARQUIA DAS CLASSES STREAM

ios

istream ostream

istrstream ifstream ofstream ostrstream

istream_withassign ostream_withassign

iostream

fstream strstream stdiostream

streambuf Iostream_init

filebuf strstreambuf stdiobuf


Cap. 13 Operações com arquivos: iostream 267

ARQUIVOS IOSTREAM.H, FSTREAM.H, STRSTREA.H E STDIOSTR.H

As classes usadas para impressão em vídeo e leitura do teclado estão declaradas


no arquivo IOSTREAM.H, que foi incluído em quase todos os nossos exemplos.
As classes usadas para leitura e gravação em discos são declaradas no arquivo
FSTREAM.H. Este arquivo já inclui o arquivo IOSTREAM.H; assim, se
FSTREAM.H for incluído em nossos programas, não é necessário incluir
IOSTREAM.H.
As classes usadas para manipulação de bytes na memória estão decla-
radas nos arquivos STRSEWA.H e STDIOSTR.H.
A tabela a seguir relaciona as classes do diagrama com estes arquivos:

iostream.h fstream.h strstrea.h stdiostr.h


ios filebuf istrstream stdiobuf
streambuf ifstream ostrstream stdiostream
istream fstream strstream
ostream ofstream strstreambuf
iostream
istream_withassign
Iostream_init
ostream_withassign

AS CLASSES IOSTREAM

O diagrama da hierarquia das classes stream mostra que a classe ios é a base
para todas as outras. Ela contém várias constantes e funções-membro para as
operações de leitura e impressão comuns a todas as outras classes.
As classes istream e ostream são derivadas de ios e são dedicadas a
leitura e impressão, respectivamente. Suas funções-membro implementam ope-
rações formatadas ou não-formatadas em objetos stream.
268 Treinamento em Linguagem C++ Cap. 13

A classe istream contém funções como get(), getline(), read(), além de


outras. Contém ainda a sobrecarga do operador de extração >>.
A classe ostream contém funções como put(), write(), além de outras.
Contém ainda a sobrecarga do operador de inserção <<.
Todo objeto istream ou ostream contém um objeto streambuf derivado.
As classes istream e ostream trabalham em conjunto com a classe streambuf.
A classe iostream é derivada das classes istream e ostream por meio
de herança múltipla. Desta forma, esta classe herda as capacidades tanto de
leitura como de impressão. Outras classes podem ser criadas pelo usuário tendo
como base istream e ostream.
As classes istream_withassign e ostream_withassign são usadas para
a entrada e a saída padrão. Os objetos cin e cout são objetos destas classes.
As classes istrstream, ostrstream e strstream são usadas na manipula-
ção da memória: para leitura, para gravação e para leitura e gravação respecti-
vamente.
As classes ifstream, ofstream e fstream são usadas na manipulação de
arquivos em disco para leitura, para gravação e para leitura e gravação
respectivamente.

GRAVANDO LINHA A LINHA EM ARQUIVOS EM DISCOS

O nosso primeiro exemplo cria um arquivo e grava nele uma string por vez.
//OFILSTR.CPP
//Grava strings em arquivo
#include <fstream.h> //Para as funções de arquivos
void main()
{
ofstream fout("TESTE.TXT"); //Cria arq. p/ gravação
//em modo texto
fout << "Um grande antídoto contra o egoísmo\n";
fout << "é a generosidade... Dê, mesmo que\n";
Cap. 13 Operações com arquivos: iostream 269

fout << "isso requeira de você um esforço\n";


fout << "consciente. Pelo fato de partilhar\n";
fout << "tudo o que possui, seu egoísmo se\n";
fout << "abrandará.\n";
}

Neste programa definimos um objeto chamado fout da classe ofstream.


Inicializamos este objeto com o nome do arquivo TESTE.TXT. Esta inicialização
associa o objeto fout ao arquivo em disco TESTE.TXT para gravação.
Pelo fato de o operador << ser sobrecarregado na classe ostream da
qual ofstream é derivada, ele pode ser usado para gravar textos no arquivo.
Quando o programa termina, o destrutor do objeto é chamado e fecha
o arquivo. Desta forma, não é necessário fechar o arquivo explicitamente.
Para verificar se o arquivo foi gravado corretamente, você pode utilizar
o comando TYPE do DOS.
C:\>TYPE teste.txt

Um grande antídoto contra o egoísmo


é a generosidade... Dê, mesmo que
isso requeira de você um esforço
consciente. Pelo fato de partilhar
tudo o que possui, seu egoísmo se
abrandará.

LENDO UMA LINHA POR VEZ EM ARQUIVOS EM DISCO

O próximo exemplo lê o arquivo TESTE.TXT criado pelo programa anterior,


uma string por vez.
//IFILSTR.CPP
//Lê strings de arquivo
#include <fstream.h> //Para as funções de arquivos
270 Treinamento em Linguagem C++ Cap. 13

void main()
{
const int MAX=80;
char buff[MAX];

ifstream fin("TESTE.TXT"); //Cria arquivo para


// leitura em modo texto
while(fin) //Enquanto não terminou o arquivo
{
fin.getline(buff,MAX); //Lê uma linha de texto
cout << buff << ’\n’;
}
}

Para ler o arquivo, criamos um objeto da classe ifstream de nome fin


e associamos este objeto ao arquivo TESTE.TXT.
Lemos o arquivo, uma linha por vez, usando a função getline(),
membro da classe istream da qual ifstream é derivada. A função getline() aceita
um terceiro argumento que especifica o caractere terminador de leitura. Se não
for especificado este argumento, o valor default é o caractere ’\n’.
No nosso exemplo, getline() lê caracteres enquanto não encontrar o
caractere ’\n’ e coloca os caracteres lidos no buffer especificado como argumen-
to. O tamanho máximo do buffer é informado no segundo argumento. O
conteúdo do buffer é impresso após cada leitura.
Neste exemplo, o operador >> não foi utilizado. Este operador não
trabalha satisfatoriamente na leitura de strings pois entende o espaço em branco
como término de uma leitura. Você pode fazer um teste e verificar a saída do
programa substituindo a instrução:
fin.getline(buff,MAX);

por
fin >> buff;
Cap. 13 Operações com arquivos: iostream 271

DETECTANDO O FIM-DE-ARQUIVO

Os objetos da classe ifstream têm um valor que pode ser testado para a
verificação do fim-de-arquivo. O objeto fin terá o valor zero se o sistema
operacional enviar ao programa o sinal de fim-de-arquivo e um valor não-zero
caso contrário.
O programa verifica o término do arquivo no laço while e termina após
a leitura da última string contida no arquivo.
Poderíamos verificar o fim-de-arquivo ao mesmo tempo em que lemos
uma linha. Esta é uma forma mais compacta:
//IFILSTR.CPP
//Lê strings de arquivo
#include <fstream.h> //Para as funções de arquivos
void main()
{
const int MAX=80;
char buff[MAX];

ifstream fin("TESTE.TXT"); //Cria arquivo para


// leitura em modo texto
while(fin.getline(buff,MAX)) //Enquanto não eof
cout << buff << ’\n’;
}

LENDO E GRAVANDO UM CARACTERE POR VEZ NO ARQUIVO

Para trabalhar com um único caractere por vez, usamos as funções put() e get(),
membros das classes ostream e istream respectivamente. O nosso próximo
exemplo lê um caractere por vez do teclado e grava-o num arquivo. A leitura
do teclado termina quando se pressiona CTRL-Z.
272 Treinamento em Linguagem C++ Cap. 13

//OFILCH.CPP
//Grava um caractere por vez num arquivo
#include <fstream.h> //Para as funções de arquivos
void main()
{
ofstream fout("TESTE1.TXT");
char ch;

//Enquanto não pressionado CTRL-Z


while (cin.get(ch)) //Lê um caractere do teclado
fout.put(ch); //Grava o caractere no arquivo
}

Neste programa, o objeto ofstream é criado da mesma forma que


fizemos no programa OFILSTR.CPP. O laço while verifica se o caractere que
indica fim-de-arquivo foi digitado no teclado. Este caractere tem código ’\x1A’
e é inserido pressionando-se a tecla CTRL junto com a tecla Z.
Observe o uso da função get() membro do objeto cin. Os objetos cin e
cout podem ser usados da mesma forma que usamos objetos das classes ifstream
e ofstream, pois são derivados da mesma classe-base.
A instrução:
cin.get(ch)

lê um caractere do teclado e o coloca na variável ch.


A instrução:
fout.put(ch);

grava o caractere contido em ch no arquivo associado a fout.


Cap. 13 Operações com arquivos: iostream 273

Para ler o arquivo TESTE1.TXT um caractere por vez, escreveremos o


programa IFILCH.CPP.
//IFILCH.CPP
//Lê um caractere por vez de um arquivo
#include <fstream.h> //Para as funções de arquivos
void main()
{
ifstream fin("TESTE1.TXT");
char ch;

while (fin.get(ch)) //Lê um caractere do arquivo


cout<< ch; //Imprime no vídeo
}

Este programa usa a função get() para ler os caracteres do arquivo. O


laço while continua lendo enquanto o fim-de-arquivo não for encontrado.

A FUNÇÃO open()

Quando usamos um objeto da classe ofstream ou um objeto da classe ifstream,


é necessário associá-lo a um arquivo. Esta associação pode ser feita pelo
construtor da classe ou usando a função open(), membro da classe fstream,
numa instrução após a criação do objeto.
Em todos os nossos exemplos, associamos um arquivo ao objeto stream
por meio do construtor, na mesma instrução de sua criação.
Tanto o construtor como a função open() aceitam a inclusão de um
segundo argumento indicando o modo de abertura do arquivo. Este modo é
definido por bits de um byte, onde cada um especifica um certo aspecto de
abertura do arquivo.
A lista de modos é definida na classe ios por meio do tipo enum
open_mode.
274 Treinamento em Linguagem C++ Cap. 13

Modos Descrição
ios::in Abre para leitura (default de ifstream).
ios::out Abre para gravação (default de ofstream).
ios::ate Abre e posiciona no final do arquivo.
Este modo trabalha com leitura e gravação.
ios::app Grava a partir do fim do arquivo.
ios::trunc Abre e apaga todo o conteúdo do arquivo.
ios::nocreate Erro de abertura se o arquivo não existir.
ios::noreplace Erro de abertura se o arquivo existir.
ios::binary Abre em binário (default é texto).

MODO TEXTO E MODO BINÁRIO

Numa operação de gravação em arquivos abertos em modo texto (default), o


caractere ’\n’ é expandido em dois bytes, carriage-return e linefeed (CR/LF),
antes de ser gravado. Em operações de leitura, o par de bytes CR/LF é
convertido para um único byte ’\n’.
Quando o arquivo é aberto em modo binário, não há esta conversão.
Para verificar esta diferença, vamos escrever um programa que conta
os caracteres de um arquivo.
//FILCONT.CPP
//Conta caracteres do arquivo em modo texto
#include <fstream.h> //Para as funções de arquivos
#include <stdlib.h> //Para exit()

void main(int argc,char **argv)


{
ifstream fin;
char ch;
Cap. 13 Operações com arquivos: iostream 275

int cont=0;

if(argc != 2)
{
cout << "\nForma de uso: C:\>FILCONT nomearq";
exit(1);
}

fin.open(argv[1]);

while (fin.get(ch)) cont++;

cout << "\nCONT = " << cont;


}

Começamos verificando o número de argumentos; se são dois, então


argv[1] será o nome do arquivo a ser aberto, caso contrário o programa
apresenta uma mensagem e termina.
O arquivo é aberto por meio da função open() usando o modo texto. O
laço while é executado, lê um caractere por vez e incrementa o contador de
caracteres. Finalmente, o programa imprime o número de caracteres contados.
Execute este programa e depois use o comando DIR do DOS e compare
o tamanho do arquivo apresentado pelo programa com o tamanho apresentado
pelo DOS. A razão desta diferença está no tratamento do caractere ’\n’.
Modificaremos o programa para que o arquivo seja aberto em modo
binário:
//FILCONT.CPP
//Conta caracteres do arquivo em modo binário
#include <fstream.h> //Para as funções de arquivos
#include <stdlib.h> //Para exit()
void main(int argc,char **argv)
{
ifstream fin;
char ch;
int cont=0;
276 Treinamento em Linguagem C++ Cap. 13

if(argc != 2)
{
cout << "\nForma de uso: C:\>FILCONT nomearq";
exit(1);
}

fin.open(argv[1],ios::binary);

while (fin.get(ch)) cont++;

cout << "\nCONT = " << cont;


}

Observe que desta vez o tamanho do arquivo apresentado pelo progra-


ma é o mesmo do tamanho apresentado pelo DOS.

O fim-de-linha em modo texto é representado por um único caractere, enquanto em


modo binário é representado por dois caracteres.

GRAVANDO OBJETOS

Para gravar ou ler objetos em disco, não faz sentido gravar ou ler um caractere
ou uma linha por vez. Se o arquivo conterá objetos, gostaríamos de gravar ou
ler um objeto por vez. As funções apropriadas para o processo de gravação e
leitura de objetos são as funções write() e read(). Para mostrar o seu uso,
primeiramente criaremos um programa para gravar registros de livros de uma
biblioteca. Eis a listagem:
//WFILOBJ.CPP
//Grava objetos em disco
#include <fstream.h> //Para as funções de arquivos
#include <stdio.h> //Para gets()
#include <conio.h> //Para getche()
Cap. 13 Operações com arquivos: iostream 277

class Livro
{
private:
char titulo[50];
char autor[50];
int numreg;
double preco;
public:
void novonome();
};

void Livro::novonome()
{
cout << "\n\tDigite título: ";
gets(titulo);
cout << "\tDigite autor: ";
gets(autor);
cout << "\tDigite o número do registro: ";
cin >> numreg;
cout << "\tDigite o preço: ";
cin >> preco;
}

void main()
{
ofstream fout("lista.dat");
Livro li;

do
{
li.novonome();
fout.write((char *)&li,sizeof(Livro));
cout << "\nMais um livro (s/n)? ";
} while(getche()!=’n’);
}
278 Treinamento em Linguagem C++ Cap. 13

A função-membro novonome() é chamada para solicitar ao usuário a


entrada das informações dos registros. Após a inserção de cada registro, o seu
conteúdo é gravado no arquivo lista.dat por meio da função write() e é
perguntado ao usuário se ele deseja inserir mais um registro.
A função write() é membro da classe ostream e recebe dois argumentos:
o primeiro é o endereço do objeto a ser gravado, e o segundo, o tamanho do
objeto em bytes. O endereço do objeto deve ser convertido para um ponteiro char.

LENDO OBJETOS

Para ler os objetos gravados pelo programa anterior, usaremos a função read().
//RFILOBJ.CPP
//Lê objetos do disco
#include <fstream.h> //Para as funções de arquivos
#include <stdio.h> //Para gets()

class Livro
{
private:
char titulo[50];
char autor[50];
int numreg;
double preco;
public:
void print();
};
Cap. 13 Operações com arquivos: iostream 279

void Livro:: print()


{
cout << "\n\tTítulo: " << titulo;
cout << "\n\tAutor : " << autor;
cout << "\n\tNo.Reg: " << numreg;
cout << "\n\tPreço : " << preco;
}

void main()
{
ifstream fin("lista.dat");
Livro li;

while(fin.read((char *)&li,sizeof(Livro)))
li.print();
}

A função read() é membro da classe istream e recebe dois argumentos:


o primeiro é o endereço do objeto para onde irão os dados lidos, e o segundo,
o tamanho do objeto em bytes. O endereço do objeto deve ser convertido para
um ponteiro char.
Para que os programas de leitura e gravação de objetos trabalhem
corretamente, é necessário que a seção de dados da classe dos objetos seja a
mesma tanto na leitura como na gravação.
Os objetos da classe Livro dos nossos exemplos têm exatamente 110
bytes de tamanho, sendo que os primeiros 50 são ocupados por uma cadeia de
caracteres que representam o título de um livro, os próximos 50 representam o
nome do autor, os próximos 2 o número do registro do livro e os últimos 8 o
preço do livro.
Se um dos dois programas tivesse um dos itens de dados de tamanho
diferente, não poderiam ser usados para gravar e ler o mesmo arquivo.
Observe entretanto, que, se os itens de dados devem ser iguais, as
funções-membro podem ser diferentes.
280 Treinamento em Linguagem C++ Cap. 13

GRAVANDO E LENDO OBJETOS DE UM MESMO ARQUIVO

O nosso próximo programa trabalha num mesmo arquivo tanto para gravação
como para leitura. O programa grava tantos objetos quantos o usuário desejar
e depois lê e imprime o conteúdo total do arquivo. Eis a listagem:
//WRFILOBJ.CPP
//Grava e lê objetos de disco
#include <fstream.h> //Para as funções de arquivos
#include <stdio.h> //Para gets()
#include <conio.h> //Para getche()

class Livro
{
private:
char titulo[50];
char autor[50];
int numreg;
double preco;
public:
void novonome();
void print();
};

void Livro::novonome()
{
cout << "\n\tDigite título: ";
gets(titulo);
cout << "\tDigite autor: ";
gets(autor);
cout << "\tDigite o número do registro: ";
cin >> numreg;
cout << "\tDigite o preço: ";
cin >> preco;
}
Cap. 13 Operações com arquivos: iostream 281

void Livro:: print()


{
cout << "\n\tTítulo: " << titulo;
cout << "\n\tAutor : " << autor;
cout << "\n\tNo.Reg: " << numreg;
cout << "\n\tPreço : " << preco;
}

void main()
{
fstream fio; //Cria objeto de leitura e gravação
Livro li; //Cria objeto Livro

fio.open("lista.dat",ios::ate | ios::out | ios::in);

do
{
li.novonome();
fio.write((char *)&li,sizeof(Livro));
cout << "\nMais um livro (s/n)? ";
} while(getche()!=’n’); //Fim se ’n’

fio.seekg(0); //Coloca o ponteiro no início do arquivo

cout << "\nLISTA DE LIVROS DO ARQUIVO";


cout << "\n==========================";

while(fio.read((char *)&li,sizeof(Livro)))
li.print();
}

Eis um exemplo de uso:


Digite título: Helena
Digite autor: Machado de Assis
282 Treinamento em Linguagem C++ Cap. 13

Digite o número do registro: 102


Digite o preço: 70.50

Mais um livro (s/n)? s

Digite título: Iracema


Digite autor: José de Alencar
Digite o número do registro: 321
Digite o preço: 63.25

Mais um livro (s/n)? s

Digite título: Macunaíma


Digite autor: Mário de Andrade
Digite o número do registro: 156
Digite o preço: 73.30

Mais um livro (s/n)? n

LISTA DE LIVROS DO ARQUIVO


Título: Helena
Autor : Machado de Assis
No.Reg: 102
Preço : 70.50

Título: Iracema
Autor : José de Alencar
No.Reg: 321
Preço : 63.25
Cap. 13 Operações com arquivos: iostream 283

Título: Macunaíma
Autor : Mário de Andrade
No.Reg: 156
Preço : 73.3

A primeira novidade neste programa é a classe usada para criar o objeto


fio. A instrução:
fstream fio;

cria um objeto para leitura e gravação. A classe fstream é derivada de iostream


que, por sua vez, é derivada das duas classes istream e ostream. Assim, objetos
desta classe podem ser usados tanto para leitura como para gravação.
A segunda é o modo usado na função open(). Na instrução:
fio.open("lista.dat",ios::ate | ios::out | ios::in);

usamos vários modos para especificar os aspectos desejados para o arquivo a


ser aberto. A barra vertical (|) entre os modos escolhido executa um OR dos
bits que representam cada modo, resultando um byte com seus bits setados
conforme os modos especificados. Vários modos podem ser aplicados simulta-
neamente por meio do operador OR.
Usamos ate para preservar os objetos que já existem no arquivo e
acrescentar novos objetos ao final dele. Se o arquivo não existir, será criado.
Usamos out e in pois queremos executar as duas operações: de gravação e de
leitura.
O programa grava um objeto por vez usando a função write(). Após o
término da gravação dos objetos digitados, desejamos ler o arquivo inteiro e
imprimir seus dados. Antes da leitura, devemos modificar a posição corrente
do arquivo para o seu início. Fazemos isto na instrução:
fio.seekg(0);
284 Treinamento em Linguagem C++ Cap. 13

AS FUNÇÕES seekg(), tellg(), seekp() e tellp()

Cada objeto stream está associado a dois valores inteiros chamados ponteiro
de posição corrente de leitura e ponteiro de posição corrente de gravação. Seus
valores especificam o número do byte do arquivo onde ocorrerá a próxima
leitura ou gravação. Este byte é chamado posição atual. A palavra ponteiro,
neste contexto, não deve ser confundida com os ponteiros de C++, usados para
armazenar endereços.
A função seekg() permite movimentar a posição corrente de leitura do
arquivo para uma posição escolhida, enquanto a função seekp() executa a
mesma tarefa para a posição corrente de gravação. A função seekg() tem o
seguinte protótipo:
istream& seekg(long pos,seek_dir posicao=ios::beg);

O primeiro argumento indica o deslocamento em bytes a partir da


posição escolhida. Quando a função é chamada com um único argumento, a
posição é assumida como sendo o início do arquivo (ios::beg).
O segundo argumento, quando usado, deve ser um dos modos seguintes:
ios::beg A partir do início do arquivo.
ios::cur A partir da posição corrente.
ios::end A partir do fim do arquivo.

Como a leitura do nosso arquivo deve começar no seu início, queremos


deslocar 0 byte a partir do início do arquivo.
A função tellg() tem o seguinte protótipo:
long tellg();

Esta função retorna a posição corrente de leitura (em bytes), sempre a


partir do início do arquivo.
Cap. 13 Operações com arquivos: iostream 285

CALCULANDO O NÚMERO DE REGISTROS DO ARQUIVO

Para mostrar o uso das funções seekg() e tellg(), escreveremos um programa


que calcula o número de registros de um arquivo. O programa pergunta para
o usuário qual é o número do registro que ele quer ver, procura os dados do
registro escolhido no arquivo e imprime o seu conteúdo. Eis a listagem:
//FSEEK.CPP
//Mostra o uso de seekg() e tellg()
#include <fstream.h> //Para as funções de arquivos
#include <stdio.h> //Para gets()
#include <stdlib.h> //Para exit()

class Livro
{
private:
char titulo[50];
char autor[50];
int numreg;
double preco;
public:
void novonome();
void print();
};

void Livro::novonome()
{
cout << "\nDigite título: ";
gets(titulo);
cout << "Digite autor: ";
gets(autor);
cout << "Digite o número do registro: ";
cin >> numreg;
cout << "Digite o preço: ";
cin >> preco;
286 Treinamento em Linguagem C++ Cap. 13

void Livro:: print()


{
cout << "\n\nTítulo: " << título;
cout << "\nAutor : " << autor;
cout << "\nNo.Reg: " << numreg;
cout << "\nPreço : " << preco;
}

void main()
{
ifstream fin;
Livro li;

fin.open("lista.dat");

fin.seekg(0,ios::end);

long nrec = (fin.tellg())/sizeof(Livro);

cout << "\nNúmero de registros= " << nrec;

cout << "\n\nInsira o número do registro: ";


cin >> nrec;

int posicao = (nrec-1) * sizeof(Livro);

fin.seekg(posicao);

fin.read((char *)&li,sizeof(Livro));
li.print();
}

A saída será:
Número de registros= 3
Cap. 13 Operações com arquivos: iostream 287

Insira o número do registro: 1

Título: Helena
Autor : Machado de Assis
No.Reg: 102
Preço : 70.50

Observe que o usuário escolheu o registro 1 indicando o primeiro


registro do arquivo, entretanto para o programa o primeiro registro é o de
número 0. Assim, na instrução:
int posicao = (nrec-1) * sizeof(Livro);

usamos nrec-1 para corrigir o número do registro.

CONDIÇÕES DE ERRO

Em nossos exemplos não tratamos de situações de erro. Em particular, assumi-


mos que os arquivos abertos para leitura já existiam e que os arquivos abertos
para gravação foram criados com sucesso.
Em programas profissionais é importante verificar e tomar ações apro-
priadas quando abrimos arquivos.
Situações de erro podem ser previstas verificando a palavra (int) de
status da classe ios.
A palavra de status pode ser obtida pela função rdstate(), membro da
classe ios. Os bits individuais do valor retornado podem ser testados pelo
operador AND bit-a-bit (&) e os seguintes valores enumerados:
ios::goodbit Nenhum bit setado. Sem erros.
ios::eofbit Encontrado o fim-de-arquivo.
ios::failbit Erro de leitura ou gravação.
ios::badbit Erro irrecuperável.

O trecho de programa a seguir exemplifica o uso da função rdstate().


288 Treinamento em Linguagem C++ Cap. 13

ifstream fin("LISTA.DAT");

if(fin.rdstate() & ios::eofbit)


cout << "\nFim de arquivo encontrado.";

A função clear() de protótipo


void clear(int status=0);

modifica a palavra de status. Se usada sem argumentos, todos os bits de erro


são limpos. Do contrário, os bits são setados de acordo com os valores enume-
rados escolhidos e combinados pelo operador OR (|).
clear(ios::eofbit | ios::failbit);

Várias outras funções-membro retornam o status de um bit individual.


São elas:
good() Nenhum bit setado. Sem erros.
eof() Encontrado o fim-de-arquivo.
fail() Erro de leitura ou gravação.
bad() Erro irrecuperável.

O nosso próximo exemplo mostra o uso destas funções.


//FERRO.CPP
//Verifica erro de abertura de arquivo
#include <fstream.h>

void main()
{
ifstream fin;
fin.open("xxx.vvv");

if(!fin)
cout << "\nZebra ao abrir o arquivo XXX.VVV\n";
else
cout << "\nO arquivo foi aberto com sucesso\n";

cout << "\nrdstate() = " << fin.rdstate();


Cap. 13 Operações com arquivos: iostream 289

cout << "\ngood() = " << fin.good();


cout << "\neof() = " << fin.eof();
cout << "\nfail() = " << fin.fail();
cout << "\nbad() = " << fin.bad();
}

Eis a saída:
Zebra ao abrir o arquivo XXX.VVV

rdstate() = 4
good() = 0
eof() = 0
fail() = 4
bad() = 4

O erro retornado por rdstate() é 4. Isto indica que o arquivo não existe.
As funções fail() e bad() retornam um valor diferente de zero visto que um erro
ocorreu.
A função good() retorna zero se algum erro ocorreu, caso contrário
retorna 1. A função eof() retorna zero se o fim de arquivo não foi encontrado,
caso contrário retorna 1.

LENDO E GRAVANDO DE E PARA A MEMÓRIA

As classes iostream interagem com a memória pela mesma sintaxe com a qual
interagem com arquivos em disco. Podemos ler e gravar de e para a memória
do computador.
O nosso primeiro exemplo grava um texto na memória. Observe a
similaridade com o programa OFILSTR.CPP.
//OSTR.cpp
//Grava strings na memória
290 Treinamento em Linguagem C++ Cap. 13

#include <strstrea.h> //Para as funções de strings


void main()
{
ostrstream str; //Cria buffer para gravação
char *sp;

str << "Um grande antídoto contra o egoísmo\n";


str << "é a generosidade... Dê, mesmo que\n";
str << "isso requeira de você um esforço\n";
str << "consciente. Pelo fato de partilhar\n";
str << "tudo o que possui, seu egoísmo se\n";
str << "abrandará\n" << ends;

sp=str.str(); //Retorna o ponteiro para


//o início do buffer
cout << sp; //Imprime na tela
}

O manipulador ends, usado neste exemplo, adiciona o terminador ’\0’


à cadeia de caracteres.
Objetos stream de memória podem ter seu buffer alocado pelo cons-
trutor como mostra o exemplo seguinte:
//OSTRBUFF.cpp
//Grava a pré-alocação da memória
#include <strstrea.h> //Para as funções de strings
void main()
{
char buff[200];
ostrstream str(buff, sizeof(buff));

str << "Um grande antídoto contra o egoísmo\n";


str << "é a generosidade... Dê, mesmo que\n";
str << "isso requeira de você um esforço\n";
str << "consciente. Pelo fato de partilhar\n";
str << "tudo o que possui, seu egoísmo se\n";
Cap. 13 Operações com arquivos: iostream 291

str << "abrandará\n" << ends;

cout << buff; //Imprime na tela


}

O nosso próximo exemplo cria um buffer para leitura e gravação. O


buffer é manipulado com a mesma sintaxe usada com os objetos cout e cin.
//OISTR.cpp
//Lê e grava em memória

#include <strstrea.h> //Para as funções de strings

void main()
{
strstream str; //Cria buffer para leitura e gravação

double x,y;
int i;

str << "123.45 555.55 333";


str >> x;
str >> y;
str >> i;

cout << x << "\n" << y << "\n" << i;

Eis a saída:
123.45
555.55
333
292 Treinamento em Linguagem C++ Cap. 13

SOBRECARGA DOS OPERADORES << E >>

Os objetos stream usam os operadores de inserção (<<) e de extração (>>) com


tipos básicos. Mostraremos como sobrecarregar esses operadores para que
trabalhem com tipos criados pelo usuário. Desta forma, poderemos usar as
nossas classes da mesma forma que usamos tipos básicos. Por exemplo, se
tivermos um objeto da classe string em nosso programa, poderemos imprimir
a cadeia de caracteres por meio de uma instrução simples como:
cout << s;

O nosso primeiro exemplo sobrecarrega o operador de inserção e o


operador de extração para a classe string:
//STROS.CPP
//Mostra sobrecarga dos operadores >> e <<
#include <iostream.h>
#include <string.h> //Para strcpy()

const MAX=80;

class string
{
private:
char str[MAX];
public:
string() {str[0]=’\0’;}
string(char s[]) {strcpy(str,s);}

friend ostream& operator<<(ostream& os,string& s);


friend istream& operator>>(istream& is,string& s);
};

ostream& operator<< ( ostream& os, string& s)


{ //Imprime string usando sobrecarga do operador <<
os << s.str;
return os;
}
Cap. 13 Operações com arquivos: iostream 293

istream& operator>> ( istream& is, string& s)


{ //Lê string usando sobrecarga do operador >>
is.getline(s.str,MAX);
return is;
}

void main()
{
string s;

cin >> s;
cout << s;
}

Este programa aguarda a digitação de uma frase terminada por [EN-


TER] e imprime a frase na tela. Observe o quanto é conveniente e natural tratar
objetos string como se fossem tipos básicos.
As funções operator<<() e operator>>() devem ser friend da classe
string, visto que os objetos ostream e istream aparecem à esquerda do operador.
A função operator<<() recebe um objeto ostream como primeiro argumento.
Geralmente, o argumento enviado será cout. O seu segundo argumento é um
objeto da classe string. A função retorna um objeto ostring para que o operador
possa ser concatenado e imprima vários objetos de uma única vez. A função
operator>>() é construída de maneira similar, mas usando istream em vez de
ostream.

A CLASSE DATA E A SOBRECARGA DE << E >>

Mostraremos um segundo exemplo de sobrecarga dos operadores << e >>: a


classe data modificada. Eis a listagem:
//DATA.CPP
//Mostra a sobrecarga de << e >> para a classe data
294 Treinamento em Linguagem C++ Cap. 13

#include <iostream.h>

class data
{
private:
int dia, mes, ano;
public:
data() { dia=mes=ano=1;}
data(int d,int m,int a); //Int dados

friend istream& operator>>(istream& is, data& d);


friend ostream& operator<<(ostream& os, data& d);
};

data::data(int d,int m,int a)


{
int dmes[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
ano = a > 0 ? a:1;
dmes[2]=dmes[2]+ (a%4==0 && a%100 || a%400==0);
mes = m>=1 && m<=12 ? m:1;
dia = d>=1 && d<=dmes[mes] ? d:1;
}

istream& operator >> (istream& is, data& d)


{
cout << "\nDigite o dia: "; is >> d.dia;
cout << "Digite o mês: "; is >> d.mes;
cout << "Digite o ano: "; is >> d.ano;

//Executa construtor para verificação da entrada


data d1(d.dia,d.mes,d.ano);
d=d1;

return is;
}
Cap. 13 Operações com arquivos: iostream 295

ostream& operator << (ostream& os, data& d)


{
char nome[13][10] =
{"zero","Janeiro","Fevereiro","Março","Abril",
"Maio","Junho","Julho","Agosto","Setembro",
"Outubro","Novembro","Dezembro"};

os << d.dia<<" de "<< nome[d.mes] << " de " << d.ano;


return os;
}

void main()
{
data x,y,z(25,12,1994);

cin >> x >> y;


cout << x << ’\n’ << y << ’\n’ << z;
}

Observe a concatenação dos operadores >> e <<, permitindo leitura e


impressão múltiplas numa única instrução:
cin >> x >> y;
cout << x << ’\n’ << y << ’\n’ << z;

IMPRIMINDO NA IMPRESSORA

Imprimir dados na impressora é exatamente a mesma coisa que gravar dados


num arquivo em disco. O DOS define o nome de alguns periféricos ligados ao
computador. Qualquer um deles pode ser usado em nossos programas como
nomes de arquivos. A tabela seguinte descreve estes nomes:
296 Treinamento em Linguagem C++ Cap. 13

Nome Descrição
CON Console (teclado e vídeo).
AUX ou COM1 Primeira porta serial.
COM2 Segunda porta serial.
PRN ou LPT1 Primeira impressora paralela.
LPT2 Segunda impressora paralela.
LPT3 Terceira impressora paralela.
NUL Periférico inexistente.

O programa seguinte usa o nome PRN para abrir a primeira impressora


paralela e imprime na impressora o conteúdo do arquivo especificado na linha
de comando.
//FPRINT.CPP
//Imprime arquivo na impressora
#include <fstream.h> //Para as funções de arquivos
#include <stdlib.h> //Para exit()

void main(int argc,char **argv)


{
const int MAX=80;
char buff[MAX];

if(argc != 2)
{
cout << "\nForma de uso: C:\>FPRINT nomearq";
exit(1);
}

ifstream fin(argv[1]); //Abre arquivo para leitura

if(!fin)
{
Cap. 13 Operações com arquivos: iostream 297

cout << "\nNão posso abrir " << argv[1];


exit(1);
}

ofstream oprn("PRN"); //Abre arquivo impressora

while(fin)
{
fin.getline(buff,MAX);
oprn << buff << ’\n’;//Imprime na impressora
}
}

MANIPULADORES E FLAGS DA CLASSE ios

O controle do formato da impressão e leitura pode ser executado por meio dos
manipuladores e flags da classe ios. Estes flags estão definidos no arquivo
iostream.h como segue:

Nome Descrição
skipws Pula brancos na entrada.
left Ajusta a saída à esquerda.
right Ajusta a saída à direita.
internal Adiciona o caractere de preenchimento do campo após o sinal de
menos ou de indicação de base numérica, mas antes do valor.
dec Formato decimal para números.
oct Formato octal para números.
hex Formato hexadecimal para números.
showbase Imprime a base indicada (0x para hex e 0 para octal).
298 Treinamento em Linguagem C++ Cap. 13

Nome Descrição
showpoint Força ponto decimal em valores float e preenche com zeros à direita
para completar o número de casas decimais escolhidas.
uppercase Imprime em maiúsculas de A a F para saída hexadecimal e E para
notação científica.
showpos Imprime ’+’ para inteiros positivos.
scientific Imprime notação científica para float.
fixed Imprime ponto decimal em valores float.
unitbuf Descarrega stream após cada inserção.
stdio Descarrega stdout e stderr após cada inserção.

O nosso próximo exemplo mostra o uso de alguns dos flags e das


funções para manipulação desses flags.
//IOFLAGS.CPP
//Mostra o uso dos flags
#include <iomanip.h>

void main()
{
int x=0x5fa2;
float f=123.58;

for(int i=0; i<1; i++)


{
cout << "\n\n" << setiosflags(ios::internal)
<< setw(10) << -x;
cout << "\n" << resetiosflags(ios::internal)
<< setw(10) << -x;
cout << "\n\n" << setiosflags(ios::hex)
<< setw(10) << x;
cout << "\n"<<setiosflags(ios::hex|ios::showbase)
<< setw(10) << x;
Cap. 13 Operações com arquivos: iostream 299

cout << setprecision(5);

cout << "\n\n"<< setiosflags(ios::fixed)<< f;


cout << "\n" << setiosflags(ios::showpoint) << f;

cout << "\n------------\n";

cout.fill(’0’); //Preenche com zeros à esquerda


}
}

Eis a saída:
- 24482
-24482

5fa2
0x5fa2

123.58
123.58000
------------
-000024482
0000-24482

0000005fa2
00000x5fa2

123.58
123.58000
------------

O programa inclui o arquivo iomanip.h. Esse arquivo já inclui o arquivo


iostream.h.
300 Treinamento em Linguagem C++ Cap. 13

LENDO COM cin E GRAVANDO EM DISCO

O problema de ler um caractere por vez do teclado com cin é que cin pula a
leitura de espaços em branco que incluem caracteres de tabulação, nova linha
e fim-de-arquivo. Se quisermos ler todos esses caracteres, devemos modificar o
flag skipws. O exemplo seguinte lê do teclado com cin e grava no arquivo
TESTE.TXT os caracteres lidos um a um.
//FSKIPWS.CPP
//Mostra o uso do flag skipws
#include <fstream.h>
#include <iomanip.h>

void main()
{
ofstream fout("TESTE.TXT");
char ch;

while (!cin.eof())
{
cin >> resetiosflags(ios::skipws) >> ch;
fout.put(ch);
}
}

Você pode usar o comando TYPE do DOS para verificar o conteúdo


do arquivo TESTE.TXT.

CRIANDO OS NOSSOS PRÓPRIOS MANIPULADORES

Você pode criar novos manipuladores para objetos stream. Por exemplo,
suponhamos que você queira implementar diversas características da sua
Cap. 13 Operações com arquivos: iostream 301

impressora em manipuladores para a impressora. A impressora EPSON pode,


por exemplo, imprimir caracteres em negrito ou também caracteres em letras
pequenas, condensados.
O próximo exemplo cria os manipuladores NEGRITO e CONDENSA-
DO. Para desligar estas opções, o programa cria mais dois manipuladores
SIMPLES e NCONDENSADO.
//MEUMANIP.CPP
//Mostra a criação de novos manipuladores

#include <fstream.h> //Para as funções de arquivos

ostream& NEGRITO(ostream& os)


{
return os << char(27) << ’G’;
}

ostream& SIMPLES(ostream& os)


{
return os << char(27) << ’H’;
}

ostream& CONDENSADO(ostream& os)


{
return os << char(15);
}

ostream& NCONDENSADO(ostream& os)


{
return os << char(18);
}

void main()
{
ofstream oprn("prn");
302 Treinamento em Linguagem C++ Cap. 13

oprn << NEGRITO << "Imprime negrito " <<


SIMPLES << "Desliga negrito";
oprn << CONDENSADO << "Imprime comprimido" <<
NCONDENSADO<< "Desliga comprimido";
}

Para imprimir em negrito, é necessário enviar à impressora os caracteres


ESC (27 decimal) e ’G’. Para desligar a impressão em negrito, a impressora deve
receber os caracteres ESC e ’H’.
Para comprimir os caracteres, é necessário enviar o caractere 15 e, para
desligar o tipo comprimido, devemos enviar o caractere 18.
É claro que é possível enviar esses caracteres diretamente do programa.
Mas a criação de manipuladores é bem mais elegante.
Outra situação na qual a criação de um manipulador pode ajudar é
quando o programa for imprimir vários números num único formato. Em vez
de escrever o formato em cada instrução de impressão, poderíamos criar um
manipulador como mostra o exemplo a seguir:
#include <fstream.h>
#include <iomanip.h>

ostream& form(ostream& os)


{
os.fill(’*’);

return os << setiosflags(ios::fixed)


<< setprecision(2)
<< setw(10);
}

void main()
{
double x=3456.7849678;
cout << ’\n’ << form << x << ’\n’ << x;
}
Cap. 13 Operações com arquivos: iostream 303

OPERADORES BIT-A-BIT

Neste capítulo, usamos alguns operadores que não foram muito bem explicados:
são os operadores bit-a-bit.
C++ tem seis operadores que operam em bits individuais de variáveis.
São eles:

Símbolo Operação
& E (AND)
| OU (OR)
^ OU exclusivo (XOR)
>> Deslocamento à direita (Right shift)
<< Deslocamento à esquerda (Left shift)
~ Complemento (Unário)

Os operadores bit-a-bit operam somente com operandos do tipo char


ou int.

O OPERADOR AND: &

Este é um operador binário que executa um AND com cada bit dos operandos.
Cada bit do resultado é 1 somente quando os dois bits operandos são 1; em
qualquer outra situação, o resultado é 0.
unsigned x=5; // 00000000 00000101
unsigned y=9; // 00000000 00001001
//------------------
unsigned z=x&y; // 00000000 00000001
304 Treinamento em Linguagem C++ Cap. 13

O OPERADOR OR: |

Este é um operador binário que executa um OR com cada bit dos operandos.
Cada bit do resultado é 0 somente quando os dois bits operandos são 0; em
qualquer outra situação, o resultado é 1.
unsigned x=5; // 00000000 00000101
unsigned y=9; // 00000000 00001001
//------------------
unsigned z=x|y; // 00000000 00001101

O OPERADOR XOR: ^

Este é um operador binário que executa um XOR com cada bit dos operandos.
Cada bit do resultado é 1 somente quando os dois bits operandos são diferentes;
quando são iguais, o resultado é 0.
unsigned x=5; // 00000000 00000101
unsigned y=9; // 00000000 00001001
//------------------
unsigned z=x^y; // 00000000 00001100

O OPERADOR SHIFT DIREITO: >>

Este é um operador binário mas que age somente no operando da esquerda.


Executa um deslocamento para a direita, conforme o número de posições
especificado no segundo operando.
unsigned x=5; // 00000000 00000101
//------------------
unsigned z=x>>1; // 00000000 00000010
Cap. 13 Operações com arquivos: iostream 305

O OPERADOR SHIFT ESQUERDO: <<

Este é um operador binário mas que age somente no operando da esquerda.


Executa um deslocamento para a esquerda, conforme o número de posições
especificado no segundo operando.
unsigned x=5; // 00000000 00000101
//------------------
unsigned z=x<<1; // 00000000 00001010

REVISÃO

1. As classes iostream executam as operações de recebimento e envio de bytes.


2. Um objeto stream é um lugar de recebimento ou de envio de bytes. Em
outras palavras, stream é a origem ou o destino de bytes.
3. Os objetos cout e cin são objetos stream de saída e entrada padrão. Suas
classes são ostream_withassign e istream_withassign respectivamente.
4. O operador de inserção (<<) é parte das classes de impressão de bytes, e o
operador de extração (>>) é parte das classes de leitura de bytes.
5. As classes iostream são divididas em três categorias distintas: leitura e
impressão da entrada e da saída padrão, leitura e impressão em discos, e
leitura e impressão na memória.
6. O arquivo iostream.h define as classes de entrada e saída padrão, fstream.h
define as classes de leitura e gravação em disco, e os arquivos strstrea.h e
stdiostr.h definem a leitura e gravação em memória.
7. As classes das três categorias são todas derivadas da classe ios.
8. Para gravar bytes num arquivo em disco, criamos um objeto da classe
ofstream e, para ler bytes, criamos um objeto da classe ifstream. Para
executar as duas operações (leitura e gravação), usamos um objeto da classe
fstream.
306 Treinamento em Linguagem C++ Cap. 13

9. As funções-membro das classes leitura e gravação em disco são: get(), put(),


getline(), read(), write() dentre outras.
10. As funções-membro read() e write() trabalham em modo binário e são
usadas principalmente para ler e gravar objetos.
11. Os arquivos abertos em modo texto interpretam o caractere ’\n’ de forma
diferente da interpretação feita por arquivos abertos em modo binário. Em
modo texto, ’\n’ é expandido em dois bytes (CR/LF) antes de ser grava-
do e, em operações de leitura, o par CR/LF é convertido para um único
byte ’\n’.
12. Para movimentar as posições correntes de leitura e gravação de um arquivo,
usamos as funções seekg() e seekp() respectivamente.
13. As funções tellg() e tellp() informam a posição corrente de leitura e
gravação respectivamente.
14. A verificação da ocorrência de erros deve ser feita a cada operação com o
arquivo. O objeto stream associado ao arquivo valerá zero caso algum erro
tenha ocorrido. Além do próprio objeto, várias funções podem ser usadas
para determinar o tipo de erro ocorrido.
15. Os operadores de inserção (<<) e de extração (>>) podem ser sobrecarre-
gados para que trabalhem com tipos definidos pelo usuário como classes e
estruturas.
16. Em C++ a impressora é usada da mesma forma que usamos um arquivo
em disco. Seu nome é PRN.
17. A classe ios fornece vários flags e manipuladores para controlar o formato
de leitura e impressão. Novos manipuladores podem ser criados pelo
usuário para trabalhar com objetos stream.
18. Os operadores bit-a-bit operam sobre bits e não sobre bytes. São eles: &,
|, ^, >>, << e ~.
Cap. 13 Operações com arquivos: iostream 307

EXERCÍCIOS

1. O que é stream?
a) arquivos;
b) classes;
c) um fluxo de dados de um lugar para outro;
d) um lugar de origem ou destino de dados.
2. A classe-base para todas as classes stream é:
a) fstream;
b) ios;
c) iostream;
d) ifstream.
3. Indique:
(1) disco
(2) entrada/saída padrão
(3) memória
(4) nenhuma das anteriores
a) istream_withassign, ostream_withassign;
b) istrstream, ostrstream, strstream;
c) ios, istream, ostream;
d) ifstream, ofstream, fstream.
4. Os operadores >> e << podem ser usados para ler ou gravar dados em
arquivos em disco pois:
a) trabalham com todas as classes;
b) são sobrecarregados nas classes istream e ostream;
308 Treinamento em Linguagem C++ Cap. 13

c) as classes ifstream e ofstream são streams;


d) trabalham com cin e cout.
5. Para ler um único caractere por vez de um arquivo, qual das seguintes
funções é apropriada?
a) get();
b) read();
c) getline();
d) getch().
6. Verdadeiro ou falso: É opcional fechar o arquivo após o término da gravação.
7. Escreva duas instruções diferentes que abram o arquivo ARQ.DOC para
gravação em binário.
8. Modo texto e modo binário diferem pois:
a) um guarda textos ASCII e outro guarda bits;
b) interpretam o caractere ’\n’ de maneiras diferentes;
c) guardam números na memória de maneiras diferentes;
d) um deles não reconhece o fim-de-arquivo.
9. Para gravar um objeto da classe A num objeto da classe ofstream, usamos:
a) o operador de inserção;
b) put();
c) write();
d) seekp().
10. Escreva uma instrução que leia um objeto da classe A chamado objA de
um objeto da classe ifstream chamado fin.
11. Para movimentar a posição corrente de gravação de um arquivo, usamos:
a) tellg();
b) seekg();
Cap. 13 Operações com arquivos: iostream 309

c) tellp();
d) seekp().
12. Escreva uma instrução que movimente a posição corrente do objeto fin da
classe ifstream, 20 bytes acima.
13. Escreva uma instrução que obtenha a posição corrente do objeto fin da
classe ifstream.
14. Escreva a função de sobrecarga do operador >> que tome dados de um
objeto da classe istream e os grave num objeto da classe Facil.
15. Quando usado com cin, o que o flag skipws faz?
16. Crie um manipulador para objetos ostream que imprima um número float
num campo de tamanho 10, três casas decimais e preenchendo os campos
não-ocupados com brancos.
17. Escreva um programa que leia um programa-fonte em C++ e verifique se
o número de chaves esquerdas e direitas são iguais. Use argumentos da
linha de comando para obter o nome do arquivo.
18. Escreva um programa que imprima um arquivo na tela de 20 em 20 linhas.
O arquivo de entrada deve ser fornecido na linha de comando. A cada
impressão de 20 linhas, o programa aguarda o pressionamento de uma tecla.
19. Modifique o programa anterior para que aceite mais 2 argumentos na linha
de comandos. O primeiro é um número inteiro e indica a primeira
linha a ser impressa, e o segundo é um número inteiro que indica a últi-
ma linha a ser impressa.
20. Escreva uma função-membro da classe Livro que possibilite a eliminação
de um objeto do arquivo. Execute o programa para testar a nova função.
21. Escreva um programa que imprima o tamanho de um arquivo em bytes. O
nome do arquivo deve ser fornecido na linha de comando.
22. Escreva um programa que criptografa um arquivo usando o operador de
complemento bit-a-bit (~). Quando o programa é executado para um
arquivo já criptografado, o arquivo é recomposto e volta ao original.
Apêndice
MAKRON
Books

TABELA ASCII

SIMB TECLA DEC HEXA OCTAL BINÁRIO

NULL CTRL @ 0 0 0 00000000


SOH CTRL A 1 1 1 00000001
STX CTRL B 2 2 2 00000010
ETX CTRL C 3 3 3 00000011
EOT CTRL D 4 4 4 00000100
ENQ CTRL E 5 5 5 00000101
ACK CTRL F 6 6 6 00000110
BEL CTRL G 7 7 7 00000111
BS CTRL H 8 8 10 00001000
HT CTRL I 9 9 11 00001001
LF CTRL J 10 A 12 00001010
VT CTRL K 11 B 13 00001011
FF CTRL L 12 C 14 00001100
CR CTRL M 13 D 15 00001101
SO CTRL N 14 E 16 00001110
SI CTRL O 15 F 17 00001111
DLE CTRL P 16 10 20 00010000
DC1 CTRL Q 17 11 21 00010001
Tabela ASCII » 311

SIMB TECLA DEC HEXA OCTAL BINÁRIO

DC2 CTRL R 18 12 22 00010010


DC3 CTRL S 19 13 23 00010011
DC4 CTRL T 20 14 24 00010100
NAK CTRL U 21 15 25 00010101
SYN CTRL V 22 16 26 00010110
ETB CTRL W 23 17 27 00010111
CAN CTRL X 24 18 30 00011000
EM CTRL Y 25 19 31 00011001
SUB CTRL Z 26 1A 32 00011010
ESC CTRL [ 27 1B 33 00011011
FS CTRL \ 28 1C 34 00011100
GS CTRL ] 29 1D 35 00011101
RS CTRL ^ 30 1E 36 00011110
US CTRL _ 31 1F 37 00011111
SP SPACE BAR 32 20 40 00100000
! ! 33 21 41 00100001
“ ” 34 22 42 00100010
# # 35 23 43 00100011
$ $ 36 24 44 00100100
% % 37 25 45 00100101
& & 38 26 46 00100110
’ ’ 39 27 47 00100111
( ( 40 28 50 00101000
 ) 41 29 51 00101001
* * 42 2A 52 00101010
+ + 43 2B 53 00101011
, , 44 2C 54 00101100
- - 45 2D 55 00101101
. . 46 2E 56 00101110
/ / 47 2F 57 00101111
0 0/ 48 30 60 00110000
312 Treinamento em linguagem C++

SIMB TECLA DEC HEXA OCTAL BINÁRIO

1 1 49 31 61 00110001
2 2 50 32 62 00110010
3 3 51 33 63 00110011
4 4 52 34 64 00110100
5 5 53 35 65 00110101
6 6 54 36 66 00110110
7 7 55 37 67 00110111
8 8 56 38 70 00111000
9 9 57 39 71 00111001
: : 58 3A 72 00111010
; ; 59 3B 73 00111011
< < 60 3C 74 00111100
= = 61 3D 75 00111101
> > 62 3E 76 00111110
? ? 63 3F 77 00111111
@ @ 64 40 100 01000000
A A 65 41 101 01000001
B B 66 42 102 01000010
C C 67 43 103 01000011
D D 68 44 104 01000100
E E 69 45 105 01000101
F F 70 46 106 01000110
G G 71 47 107 01000111
H H 72 48 110 01001000
I I 73 49 111 01001001
J J 74 4A 112 01001010
K K 75 4B 113 01001011
L L 76 4C 114 01001100
M M 77 4D 115 01001101
N N 78 4E 116 01001110
O O 79 4F 117 01001111
Tabela ASCII » 313

SIMB TECLA DEC HEXA OCTAL BINÁRIO

P P 80 50 120 01010000
Q Q 81 51 121 01010001
R R 82 52 122 01010010
S S 83 53 123 01010011
T T 84 54 124 01010100
U U 85 55 125 01010101
V V 86 56 126 01010110
W W 87 57 127 01010111
X X 88 58 130 01011000
Y Y 89 59 131 01011001
Z Z 90 5A 132 01011010
[ [ 91 5B 133 01011011
\ \ 92 5C 134 01011100
] ] 93 5D 135 01011101
^ ^ 94 5E 136 01011110
_ _ 95 5F 137 01011111
` ` 96 60 140 01100000
a a 97 61 141 01100001
b b 98 62 142 01100010
c c 99 63 143 01100011
d d 100 64 144 01100100
e e 101 65 145 01100101
f f 102 66 146 01100110
g g 103 67 147 01100111
h h 104 68 150 01101000
i i 105 69 151 01101001
j j 106 6A 152 01101010
k k 107 6B 153 01101011
l l 108 6C 154 01101100
m m 109 6D 155 01101101
n n 110 6E 156 01101110
314 Treinamento em linguagem C++

SIMB TECLA DEC HEXA OCTAL BINÁRIO

o o 111 6F 157 01101111


p p 112 70 160 01110000
q q 113 71 161 01110001
r r 114 72 162 01110010
s s 115 73 163 01110011
t t 116 74 164 01110100
u u 117 75 165 01110101
v v 118 76 166 01110110
w w 119 77 167 01110111
x x 120 78 170 01111000
y y 121 79 171 01111001
z z 122 7A 172 01111010
{ { 123 7B 173 01111011
| | 124 7C 174 01111100
} } 125 7D 175 01111101
~ ~ 126 7E 176 01111110
2 CTRL <— 127 7F 177 01111111
^ ALT 128 128 80 200 10000000
a ALT 129 129 81 201 10000001
b ALT 130 130 82 202 10000010
c ALT 131 131 83 203 10000011
d ALT 132 132 84 204 10000100
e ALT 133 133 85 205 10000101
f ALT 134 134 86 206 10000110
g ALT 135 135 87 207 10000111
h ALT 136 136 88 210 10001000
i ALT 137 137 89 211 10001001
j ALT 138 138 8A 212 10001010
k ALT 139 139 8B 213 10001011
l ALT 140 140 8C 214 10001100
m ALT 141 141 8D 215 10001101
Tabela ASCII » 315

SIMB TECLA DEC HEXA OCTAL BINÁRIO

n ALT 142 142 8E 216 10001110


o ALT 143 143 8F 217 10001111
p ALT 144 144 90 220 10010000
q ALT 145 145 91 221 10010001
r ALT 146 146 92 222 10010010
s ALT 147 147 93 223 10010011
t ALT 148 148 94 224 10010100
u ALT 149 149 95 225 10010101
v ALT 150 150 96 226 10010110
w ALT 151 151 97 227 10010111
x ALT 152 152 98 230 10011000
y ALT 153 153 99 231 10011001
z ALT 154 154 9A 232 10011010
{ ALT 155 155 9B 233 10011011
| ALT 156 156 9C 234 10011100
} ALT 157 157 9D 235 10011101
~ ALT 158 158 9E 236 10011110
_ ALT 159 159 9F 237 10011111
ALT 160 160 A0 240 10100000
! ALT 161 161 A1 241 10100001
ALT 162 162 A2 242 10100010
# ALT 163 163 A3 243 10100011
$ ALT 164 164 A4 244 10100100
% ALT 165 165 A5 245 10100101
& ALT 166 166 A6 246 10100110
( ALT 167 167 A7 247 10100111
( ALT 168 168 A8 250 10101000
) ALT 169 169 A9 251 10101001
* ALT 170 170 AA 252 10101010
+ ALT 171 171 AB 253 10101011
, ALT 172 172 AC 254 10101100
316 Treinamento em linguagem C++

SIMB TECLA DEC HEXA OCTAL BINÁRIO

- ALT 173 173 AD 255 10101101


. ALT 174 174 AE 256 10101110
/ ALT 175 175 AF 257 10101111
0 ALT 176 176 B0 260 10110000
1 ALT 177 177 B1 261 10110001
2 ALT 178 178 B2 262 10110010
3 ALT 179 179 B3 263 10110011
4 ALT 180 180 B4 264 10110100
5 ALT 181 181 B5 265 10110101
6 ALT 182 182 B6 266 10110110
7 ALT 183 183 B7 267 10110111
8 ALT 184 184 B8 270 10111000
9 ALT 185 185 B9 271 10111001
: ALT 186 186 BA 272 10111010
; ALT 187 187 BB 273 10111011
< ALT 188 188 BC 274 10111100
= ALT 189 189 BD 275 10111101
> ALT 190 190 BE 276 10111110
? ALT 191 191 BF 277 10111111
@ ALT 192 192 C0 300 11000000
A ALT 193 193 C1 301 11000001
B ALT 194 194 C2 302 11000010
C ALT 195 195 C3 303 11000011
D ALT 196 196 C4 304 11000100
E ALT 197 197 C5 305 11000101
F ALT 198 198 C6 306 11000110
G ALT 199 199 C7 307 11000111
H ALT 200 200 C8 310 11001000
I ALT 201 201 C9 311 11001001
J ALT 202 202 CA 312 11001010
K ALT 203 203 CB 313 11001011
Tabela ASCII » 317

SIMB TECLA DEC HEXA OCTAL BINÁRIO

L ALT 204 204 CC 314 11001100


M ALT 205 205 CD 315 11001101
N ALT 206 206 CE 316 11001110
O ALT 207 207 CF 317 11001111
P ALT 208 208 D0 320 11010000
Q ALT 209 209 D1 321 11010001
R ALT 210 210 D2 322 11010010
S ALT 211 211 D3 323 11010011
T ALT 212 212 D4 324 11010100
U ALT 213 213 D5 325 11010101
V ALT 214 214 D6 326 11010110
W ALT 215 215 D7 327 11010111
X ALT 216 216 D8 330 11011000
Y ALT 217 217 D9 331 11011001
Z ALT 218 218 DA 332 11011010
[ ALT 219 219 DB 333 11011011
\ ALT 220 220 DC 334 11011100
] ALT 221 221 DD 335 11011101
^ ALT 222 222 DE 336 11011110
_ ALT 223 223 DF 337 11011111
a ALT 224 224 E0 340 11100000
b ALT 225 225 E1 341 11100001
G ALT 226 226 E2 342 11100010
p ALT 227 227 E3 343 11100011
å ALT 228 228 E4 344 11100100
s ALT 229 229 E5 345 11100101
m ALT 230 230 E6 346 11100110
t ALT 231 231 E7 347 11100111
F ALT 232 232 E8 350 11101000
Q ALT 233 233 E9 351 11101001
W ALT 234 234 EA 352 11101010
318 Treinamento em linguagem C++

SIMB TECLA DEC HEXA OCTAL BINÁRIO

d ALT 235 235 EB 353 11101011


¥ ALT 236 236 EC 354 11101100
Æ ALT 237 237 ED 355 11101101
Î ALT 238 238 EE 356 11101110
1 ALT 239 239 EF 357 11101111
º ALT 240 240 F0 360 11110000
q ALT 241 241 F1 361 11110001
r ALT 242 242 F2 362 11110010
s ALT 243 243 F3 363 11110011
t ALT 244 244 F4 364 11110100
u ALT 245 245 F5 365 11110101
v ALT 246 246 F6 366 11110110
w ALT 247 247 F7 367 11110111
x ALT 248 248 F8 370 11111000
y ALT 249 249 F9 371 11111001
z ALT 250 250 FA 372 11111010
{ ALT 251 251 FB 373 11111011
| ALT 252 252 FC 374 11111100
ALT 253 253 FD 375 11111101
~ ALT 254 254 FE 376 11111110
ALT 255 255 FF 377 11111111
Página em branco
Página em branco

Você também pode gostar