0% acharam este documento útil (0 voto)
433 visualizações495 páginas

Agit Apostila CPP

Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
433 visualizações495 páginas

Agit Apostila CPP

Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd

AGIT INFORMÁTICA TREINAMENTO EM LINGUAGEM C++ 1

Treinamento em Linguagem C++

APOSTILA

Basilio Miranda

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2 • AGIT informática • C++ •

Treinamento em Linguagem C++


Autor: Basilio Miranda.
Copyright  AGIT Informática Ltda (todos os direitos reservados).

Esta Apostila está registrada sob o número 298.397, livro 542, folha 57, no Escritó-
rio de Direitos Autorais da Fundação Biblioteca Nacional, Ministério da Cultura.

 Nenhuma parte desta publicação poderá ser reproduzida, guardada pelo sistema
“retrieval” ou transmitida de qualquer modo ou por qualquer outro meio, seja este ele-
trônico, mecânico, de fotocópia, de gravação, ou outros, sem prévia autorização, por
escrito, da Agit Informática Ltda, sob pena de responder por perdas e danos, in-
fringindo ao disposto na Legislação pertinente, que assegura a proteção aos Direi-
tos Autorais – LEI nº 9.610, de 19 de fevereiro de 1998 do artigo nº 29.

Produção e Editoração: AGIT Informática Ltda.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Sumário • C++ • AGIT informática • 3

• Sumário

• Sumário detalhado .................................................5

• Capítulo 1 ▪ Introdução: a Linguagem C++ ...............................17


• Capítulo 2 ▪ Iniciando: o básico em C++ ...................................41
• Capítulo 3 ▪ Programação: princípios básicos .............................73
• Capítulo 4 ▪ Memória: tipos ...................................................103
• Capítulo 5 ▪ Fluxo de processamento ......................................133
• Capítulo 6 ▪ Ponteiros e referências ........................................181
• Capítulo 7 ▪ Iniciando orientação a objetos .............................199
• Capítulo 8 ▪ Programação genérica .........................................257
• Capítulo 9 ▪ A biblioteca padrão .............................................265
• Capítulo 10 ▪ Classes e objetos: Herança ..................................275
• Capítulo 11 ▪ Classes e objetos: Polimorfismo ............................283
• Capítulo 12 ▪ Tratamento de exceções .......................................303
• Capítulo 13 ▪ Referência técnica ...............................................313

• Anexos ..............................................................427

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4 • AGIT informática • C++ •

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Sumário detalhado • C++ • AGIT informática • 5

• Sumário detalhado

• Capítulo 1 ▪ Introdução: a Linguagem C++ ..................................17


1.1 • Tópicos de destaque neste capítulo .........................................18
1.2 • As linguagens C e C++ .............................................................20
1.2.1 ▪ Breve histórico ............................................................20
[Link] ▪ Linguagem C ........................................................20
[Link] ▪ Linguagem C++ ....................................................20
1.2.2 ▪ Características gerais de C++ ....................................21
1.2.3 ▪ Diferenças entre C e C++ ...........................................22
1.3 • Biblioteca de C++ e bibliotecas auxiliares ................................23
1.3.1 ▪ Biblioteca padrão de C++ ...........................................23
1.3.2 ▪ Bibliotecas adicionais .................................................23
1.3.3 ▪ Bibliotecas adicionais: indispensáveis ........................24
1.4 • Porque aprender C++ ...............................................................25
1.4.1 ▪ Base sólida em programação .....................................25
1.4.2 ▪ C++ pode ser a linguagem principal ...........................25
1.4.3 ▪ Usando outras linguagens ..........................................26
1.5 • Livros e sites sobre C++ ...........................................................26
1.5.1 ▪ Livros para o iniciante ................................................27
1.5.2 ▪ Livros para aprofundamento ......................................27
1.5.3 ▪ Livros adicionais(abordagens e técnicas) ..................28
1.5.4 ▪ Alguns sites úteis sobre C++ ......................................28
1.6 • Linguagem, Compiladores e Ambientes ...................................30
1.6.1 ▪ Compiladores .............................................................30
1.6.2 ▪ Ambientes de Desenvolvimento Integrado .................31
1.6.3 ▪ Compiladores comerciais e gratuitos .........................32
1.6.4 ▪ Exemplos de compiladores ........................................32
[Link] ▪ Gratuitos (alguns exemplos) ................................32
[Link] ▪ Comerciais (alguns exemplos) .............................32
[Link] ▪ Outros exemplos ..................................................33
1.6.5 ▪ Exemplos de ambientes de desenvolvimento ............33
1.7 • O que usamos na Agit Informática ............................................35
1.8 • O que veremos no curso ..........................................................36
1.9 • Conclusão ................................................................................36
1.9.1 ▪ Linguagem padronizada .............................................36
1.9.2 ▪ Linguagem multi-paradigma .......................................37
1.10 • Ferramentas usadas no curso...................................................38
1.11 • Questões para revisão do Capítulo 1 .......................................39

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6 • AGIT informática • C++ • • Sumário detalhado

• Capítulo 2 ▪ Iniciando: o básico em C++ ......................................41


2.1 • Tópicos de destaque neste capítulo .........................................42
2.2 • Exemplos preliminares .............................................................43
2.2.1 ▪ Um programa mínimo .................................................43
2.2.2 ▪ Um programa um pouco maior ...................................45
2.2.3 ▪ Blocos de código e chaves..........................................47
2.2.4 ▪ Gramática básica .......................................................48
2.3 • Compilando e executando ........................................................49
2.3.1 ▪ Exemplo no Windows .................................................49
2.3.2 ▪ Exemplo em Unix/Linux ..............................................54
2.4 • Analisando e corrigindo erros de compilação ...........................57
2.5 • Usando Ambientes de Desenvolvimento ..................................59
2.5.1 ▪ Qt Creator ..................................................................60
[Link] ▪ Criando um projeto ...............................................60
[Link] ▪ Adicionando arquivos ao projeto...........................64
[Link] ▪ Compilar e executar. ............................................65
2.5.2 ▪ Visual C++ ..................................................................65
[Link] ▪ Criando um projeto ...............................................65
[Link] ▪ Adicionando arquivos ao projeto...........................68
[Link] ▪ Compilar e executar. ............................................69
2.6 • Conclusões sobre o código usado ..........................................70
2.7 • Revisão do Capítulo 2 ..............................................................70
2.7.1 ▪ Exercício ....................................................................70
2.7.2 ▪ Questões para revisão ...............................................71

• Capítulo 3 ▪ Programação: princípios básicos ................................73


3.1 • Tópicos de destaque neste capítulo..........................................74
3.2 • Operações, memória e fluxo de processamento ......................75
3.2.1 ▪ Princípios ...................................................................75
[Link] ▪ Operações ............................................................75
[Link] ▪ Memória ...............................................................75
[Link] ▪ Fluxo de processamento ......................................76
[Link] ▪ Conceitos de Verdadeiro e Falso .........................77
3.2.2 ▪ Implementação em C++ .............................................79
[Link] ▪ Operações e operadores .....................................79
[Link] ▪ Memória ...............................................................79
[Link] ▪ Escrevendo código ...............................................80
[Link] ▪ Verdadeiro e Falso ...............................................81
3.3 • Fluxo de processamento: laços ................................................82
3.3.1 ▪ Princípios ...................................................................82
3.3.2 ▪ Implementação em C++ .............................................84
[Link] ▪ O controle de laço "while".....................................84
[Link] ▪ Compilando e executando ...................................88
[Link] ▪ Mais sobre laços; o laço for .................................92
3.4 • Conclusão ................................................................................95
3.5 • Revisão do Capítulo 3 ..............................................................95
3.5.1 ▪ Exercício.....................................................................95
3.5.2 ▪ Questões para revisão ...............................................99

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Sumário detalhado • C++ • AGIT informática • 7

• Capítulo 4 ▪ Memória: tipos ......................................................103


4.1 • Tópicos de destaque neste capítulo .......................................104
4.2 • Reserva e acesso de memória em C e C++ ...........................105
4.2.1 ▪ Tipos de dados .........................................................105
4.2.2 ▪ Escolhendo o tipo adequado ....................................105
4.2.3 ▪ Porque reservar memórias com um tipo ..................107
4.3 • Inicialização e atribuição ........................................................107
4.4 • Variáveis e constantes ............................................................107
4.4.1 ▪ Porque associar constantes a um nome ..................108
4.4.2 ▪ Constantes nomeadas em um único lugar ...............110
4.5 • Conceitos: valores, tipos e objetos .........................................111
4.6 • Conversões entre tipos ...........................................................112
4.7 • Vetores ...................................................................................115
4.7.1 ▪ Vetores: o que são e para que servem .....................115
4.7.2 ▪ std::string e std::vector .............................................116
4.7.3 ▪ Vetores na linguagem C ...........................................119
4.7.4 ▪ Caracteres: aspas simples e duplas .........................122
4.8 • Revisão do Capítulo 4 ............................................................125
4.8.1 ▪ Exercício ..................................................................125
4.8.2 ▪ Questões para revisão .............................................129

• Capítulo 5 ▪ Fluxo de processamento .........................................133


5.1 • Tópicos de destaque neste capítulo .......................................135
5.2 • Linhas de instrução; declarações e operações ......................137
5.3 • Operadores ............................................................................138
5.3.1 ▪ Resto de divisão .......................................................140
5.3.2 ▪ Operadores compostos ............................................141
5.3.3 ▪ Operações lógicas ....................................................143
5.3.4 ▪ Em conclusão ...........................................................145
5.4 • Precedência de operadores....................................................145
5.5 • Finalização de uma linha de instrução....................................146
5.6 • Comentários............................................................................147
5.7 • Funções...................................................................................148
5.7.1 ▪ Parâmetros e valor de retorno de uma função..........148
5.7.2 ▪ Funções sem parâmetros e/ou valor de retorno........149
5.7.3 ▪ A função main............................................................150
[Link] ▪ Retorno de main..................................................151
[Link] ▪ Parâmetros de main............................................152
5.7.4 ▪ Protótipos de funções................................................154
5.7.5 ▪ Arquivos header .......................................................155
5.8 • Escopo de uma função e escopo global..................................156
5.9 • Tomadas de decisão ...............................................................157
5.9.1 ▪ if / else ......................................................................157
5.9.2 ▪ switch .......................................................................159
5.10 • Laços.......................................................................................160
5.10.1 ▪ Porque usar laços estruturados ................................160
5.10.2 ▪ Laço do ... while........................................................162

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


8 • AGIT informática • C++ • • Sumário detalhado

5.10.3 ▪ Detalhes sobre todos os laços .................................162


5.11 • Revisão do Capítulo 5 ............................................................164
5.11.1 ▪ Sintetizando as regras básicas para escrita de código.
164
5.11.2 ▪ Exercício 1................................................................164
[Link] ▪ Iniciando o exercício ..........................................166
5.11.3 ▪ Exercício 2 ...............................................................174
5.11.4 ▪ Questões para revisão .............................................175

• Capítulo 6 ▪ Ponteiros e referências ...........................................181


6.1 • O problema do carteiro ...........................................................182
6.2 • Trabalhando com endereços ..................................................184
6.2.1 ▪ Exemplo com ponteiros ............................................185
6.2.2 ▪ Usando ponteiros como parâmetros de funções ......187
6.2.3 ▪ Evitando duplos acessos de memória ......................190
6.3 • Referências ...........................................................................192
6.3.1 ▪ Inicialização de referências.......................................193
6.3.2 ▪ Diferenciando referências de ponteiros.....................193
6.3.3 ▪ Exemplo com referências .........................................193
6.4 • Questões para revisão do Capítulo 6 .....................................196

• Capítulo 7 ▪ Iniciando orientação a objetos ................................199


7.1 • Estruturas ...............................................................................202
7.1.1 ▪ Estruturas e funções relacionadas ...........................204
7.1.2 ▪ Limitação da linguagem C ........................................204
7.1.3 ▪ Passando estruturas como argumento .....................207
7.1.4 ▪ A especificação const................................................210
7.2 • Estruturas em C++ : encapsulamento ...................................210
7.3 • Implementando as funções membras .....................................213
7.4 • As palavras reservadas struct e class.....................................214
7.4.1 ▪ Definindo o objeto apontado por “this” como const...215
7.4.2 ▪ Implementação das funções membras const ..........216
7.4.3 ▪ Testando a class Data ([Link])......................223
7.4.4 ▪ Especificando funções como inline...........................223
7.4.5 ▪ Usando um segundo parâmetro do tipo “Data”.........225
7.4.6 ▪ Acrescentando funções operadoras à classe “Data”. 228
7.4.7 ▪ Os operadores << e >> de ostream/istream .............229
7.4.8 ▪ Conclusão Parcial.....................................................230
7.5 • Encapsulamento: reforçando conceitos. .................................231
7.5.1 ▪ Funções inline...........................................................231
7.5.2 ▪ O ponteiro this...........................................................232
[Link] ▪ Definindo o objeto apontado por this como const
232
[Link] ▪ Exceção para a restrição const de this (mutable)
233
7.5.3 ▪ Função Construtora...................................................234
[Link] ▪ Parâmetros para a função construtora................235
[Link] ▪ Contrutora default...............................................235
[Link] ▪ Construtora de cópia e operador de atribuição...235

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Sumário detalhado • C++ • AGIT informática • 9

[Link] ▪ Construtoras com conversão implícita e explícita.


237
7.5.4 ▪ Função destrutora.....................................................238
7.5.5 ▪ Alterando o ponto de entrada da aplicação: objetos ex-
ternos. 239
7.5.6 ▪ Classes e funções amigas........................................240
7.5.7 ▪ Membros estáticos de uma classe............................241
[Link] ▪ Membros de dados estáticos..............................241
[Link] ▪ Funções-membro estáticas.................................243
7.5.8 ▪ Objetos como membros de classes..........................244
[Link] ▪ Membros “objeto de outra classe”.......................244
[Link] ▪ Membros “ponteiro para objeto de outra classe” 245
[Link] ▪ Membros “ponteiro para objeto da mesma classe”
246
7.5.9 ▪ Ambientes de nomes.................................................247
[Link] ▪ Evitando colisões de nomes...............................247
[Link] ▪ Usando namespace para organizar conjuntos de
software. 248
[Link] ▪ O namespace "std".............................................249
[Link] ▪ Exemplos de uso de namespace........................250
7.6 • Questões para revisão do capítulo 7 ......................................254

• Capítulo 8 ▪ Programação genérica ............................................257


8.1 • Templates ...............................................................................258
8.2 • Templates de função ..............................................................258
8.3 • Templates de classes .............................................................262

• Capítulo 9 ▪ A biblioteca padrão ................................................265


9.1 • std::string.................................................................................268
9.2 • std::vector ...............................................................................269
9.3 • std::list ....................................................................................270
9.4 • std::map ..................................................................................271
9.5 • std::vector < std::vector < > >.................................................272

• Capítulo 10 ▪ Classes e objetos: Herança .....................................275


10.1 • Regras básicas .......................................................................276
10.1.1 ▪ Restrição de acesso protected.................................276
10.1.2 ▪ Modos de derivação..................................................276
[Link] ▪ Derivação pública ( : public)................................276
[Link] ▪ Derivação privada ( : private)..............................276
[Link] ▪ Derivação protegida ( : protected).......................277
10.2 • Construtoras e destrutora na derivada e na base....................278
10.2.1 ▪ Criação de um objeto de uma classe derivada..........278
10.2.2 ▪ Destruição de um objeto de uma classe derivada.....278
10.2.3 ▪ Como a construtora derivada pode chamar uma cons-
trutora base.............................................................................278
10.3 • Herança Múltipla......................................................................280
10.3.1 ▪ Herança múltipla e herança virtual:...........................280

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


10 • AGIT informática • C++ • • Sumário detalhado

• Capítulo 11 ▪ Classes e objetos: Polimorfismo ...............................283


11.1 • Sobrecarga..............................................................................284
[Link] ▪ Sobrecarga de funções.......................................284
[Link] ▪ Sobrecarga de operadores.................................285
11.2 • Redefinição de funções nas classes derivadas.......................287
11.3 • Funções virtuais .....................................................................290
11.3.1 ▪ Polimorfismo em frameworks: ponteiros para função e
funções virtuais.......................................................................290
11.3.2 ▪ Declarar e implementar funções virtuais...................292
11.3.3 ▪ Funções virtuais podem ser usadas como respostas a
eventos. 294
[Link] ▪ Funções virtuais puras e classes abstratas........296
[Link] ▪ Exercício: a classe RelatPadrao.........................297
[Link].a ▪ Solução: declaração, implementação e
uso da classe RelatPadrao.................................297
11.3.4 ▪ Precauções ao usar funções virtuais.........................298
[Link].a ▪ 1 - Nunca preencher um objeto com
métodos de baixo nível.......................................298
[Link].b ▪ 2 - Nunca esquecer como a vtable é
preenchida..........................................................298
11.4 • Questões para revisão............................................................300

• Capítulo 12 ▪ Tratamento de exceções .........................................303


12.1 • Usando throw ... try {...} catch(...) {...}......................................304
12.2 • Usando um tratador default.....................................................306
12.3 • std::exception .........................................................................306
12.4 • Derivadas padrão de std::exception .......................................310

• Capítulo 13 ▪ Referência técnica .................................................313


13.1 • Tipos de dados........................................................................317
13.1.1 ▪ Os tipos da linguagem C++.......................................317
13.1.2 ▪ Modificadores de tipos..............................................319
[Link] ▪ Modificadores de sinal........................................319
[Link] ▪ Modificadores de tamanho..................................319
13.1.3 ▪ Conversões de tipos..................................................320
[Link] ▪ Conversões no estilo C ......................................320
[Link] ▪ Conversões no estilo C++ ..................................321
13.1.4 ▪ Criando sinônimos de tipos.......................................323
13.1.5 ▪ Criando novos tipos de dados...................................324
[Link] ▪ Criando tipos através de enum...........................324
[Link].a ▪ Em C (mas não em C++) o uso dos
símbolos de constantes é facultativo..................325
[Link].b ▪ Em C++ o uso dos símbolos de cons-
tantes é obrigatório.............................................326
[Link].c ▪ Denominação de um tipo criado com
enum...................................................................326
[Link] ▪ Criando tipos através de struct ou class.............326
[Link].a ▪ Ligação dos membros à variável estru-
turada..................................................................327
[Link].b ▪ Denominação de um tipo estruturado.
............................................................................328
13.1.6 ▪ Tipos aninhados........................................................328

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Sumário detalhado • C++ • AGIT informática • 11

[Link] ▪ Estruturas aninhadas..........................................329


[Link].a ▪ Tipos aninhados: em C eles são ape-
nas indicativos.....................................................330
[Link].b ▪ Tipos aninhados: C++ não permite o
estilo C................................................................330
[Link].c ▪ Criação de tipos diretamente no retorno
ou nos parâmetros de uma função.....................330
[Link] ▪ Criando tipos através de union...........................330
[Link].a ▪ Regras para union, específicas de C++.
............................................................................332
13.2 • Conceituando identificadores, funções, escopos ....................333
13.2.1 ▪ E o que é, exatamente, um nome identificador ?......333
13.2.2 ▪ Conceituando blocos de código ...............................334
13.2.3 ▪ Declaração de variáveis nos escopos local e global.
336
[Link] ▪ Visibilidade..........................................................336
[Link] ▪ Tempo de vida.....................................................337
[Link] ▪ Outros recursos...................................................338
13.2.4 ▪ Recursos de programação modular em C e C++......338
[Link] ▪ Recursos da aplicação, recursos de módulo e re-
cursos de bloco...................................................................338
[Link] ▪ Classificação dos recursos (memórias e funções)
quanto à visibilidade............................................................339
[Link].a ▪ Classificação de memórias..............339
[Link].b ▪ Classificação de funções.................339
[Link] ▪ Organizando uma aplicação por módulos. .........339
[Link] ▪ Sintetizando a classificação quanto à visibilidade
de variáveis e funções.........................................................340
[Link].a ▪ Escopo da Aplicação........................340
[Link].b ▪ Escopo do Módulo...........................340
[Link].c ▪ Escopo local (um bloco de código).. 340
[Link] ▪ Classificação das variáveis quanto ao tempo de
vida. 341
[Link] ▪ Determinando visibilidade e tempo de vida na de-
claração de variáveis...........................................................341
[Link] ▪ As classes de armazenamento de memórias.....342
[Link].a ▪ Classe auto......................................342
[Link].b ▪ Classe static (obsoleta em C++)......342
[Link].c ▪ Classe global ou extern....................343
13.2.5 ▪ Inicialização e atribuição de variáveis.......................344
13.3 • Operações e operadores.........................................................345
13.3.1 ▪ Precauções no uso dos símbolos de operadores......346
13.3.2 ▪ Resultados de operações..........................................347
[Link] ▪ Operações relacionais........................................347
[Link] ▪ Operações lógicas..............................................347
[Link] ▪ Operações aritméticas........................................347
13.3.3 ▪ Posição dos operadores de incremento e decremento.
348
13.3.4 ▪ Operadores bit a bit...................................................349
[Link] ▪ AND.....................................................................349
[Link] ▪ OR.......................................................................349
[Link] ▪ NOT (ou complemento de 1)...............................350
[Link] ▪ XOR ( or exclusivo).............................................350
[Link] ▪ Shift (ou deslocamento) à esquerda..................350
[Link] ▪ Shift (ou deslocamento) à direita:.......................351

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


12 • AGIT informática • C++ • • Sumário detalhado

[Link] ▪ Exemplos com operadores de bits......................351


[Link].a ▪ Exemplo com and, or, e not.............351
[Link].b ▪ Exemplo com shift (à esquerda e à di-
reita)....................................................................354
[Link].c ▪ Exemplo com xor (1)........................355
[Link].d ▪ Exemplo com xor (2)........................357
13.4 • Controles do fluxo de processamento.....................................360
13.4.1 ▪ Chamadas de função................................................360
[Link] ▪ Parâmetros para funções e retorno de funções..360
[Link] ▪ Declarando parâmetros.......................................360
[Link] ▪ Formas de declaração........................................360
[Link].a ▪ Forma de declaração válida em em C e
C++.....................................................................360
[Link].b ▪ Forma de declaração inválida em C++
............................................................................360
[Link] ▪ Funções com quantidade fixa de parâmetros.....361
[Link] ▪ Funções com quantidade variável de parâmetros.
361
[Link].a ▪ Declarando os parâmetros desconheci-
dos......................................................................362
[Link].b ▪ Exemplo de função com quantidade de
parâmetros variável.............................................362
[Link] ▪ Valor de retorno, tipo e protótipo de funções......363
[Link].a ▪ Valor de retorno...............................363
[Link].b ▪ Tipo de uma função.........................364
[Link].c ▪ Protótipo de funções........................364
[Link] ▪ Modos de passagem de parâmetros...................366
[Link].a ▪ 1 - Passando parâmetros por valor..366
[Link].b ▪ 2 - Passando parâmetros por endere-
ço:.......................................................................367
[Link].c ▪ 3 - Passando parâmetros por referên-
cia:.......................................................................370
[Link] ▪ Há dois motivos para passar parâmetros por ende-
reço ou por referência.........................................................371
[Link] ▪ Impedindo efeitos colaterais desnecessários.....372
13.4.2 ▪ Ponteiros para função...............................................373
[Link] ▪ Definindo os tipos dos ponteiros para função.....373
[Link] ▪ Ponteiros para função permitem estabelecer even-
tos. 374
[Link] ▪ Exemplo com ponteiros para função: a estrutura re-
latório. 376
13.4.3 ▪ Lógica estruturada através de objetos......................379
13.4.4 ▪ Funções recursivas...................................................380
13.4.5 ▪ Controles de laço......................................................381
[Link] ▪ O laço for.............................................................382
[Link].a ▪ Forma geral e funcionamento. ........382
[Link].b ▪ Sintaxe.............................................383
[Link].c ▪ Cuidados a tomar com o laço for.....385
[Link] ▪ O laço “while”......................................................386
[Link].a ▪ Forma geral e funcionamento..........386
[Link].b ▪ Sintaxe.............................................386
[Link].c ▪ Cuidados a tomar com o laço while. 387
[Link] ▪ Considerações gerais sobre os laços for e while
(tenha cuidado com…)........................................................389
[Link] ▪ O laço “do … while”.............................................389

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Sumário detalhado • C++ • AGIT informática • 13

[Link].a ▪ Forma geral, funcionamento e sintaxe:


............................................................................389
13.4.6 ▪ Desvios por salto incondicional.................................392
[Link] ▪ Desvio return.......................................................392
[Link] ▪ Desvio break.......................................................393
[Link] ▪ Desvio continue...................................................394
[Link] ▪ Desvio goto. .......................................................397
[Link].a ▪ Sintaxe.............................................397
[Link].b ▪  1 - goto: usos não aceitáveis......398
[Link].c ▪  2 - goto: uso aceitável.................399
13.4.7 ▪ Funções inline...........................................................400
13.4.8 ▪ Controles de decisão.................................................401
[Link] ▪ Os controles de decisão if e else........................401
[Link].a ▪ Sintaxe.............................................401
[Link].b ▪ Instruções associadas: ...................402
[Link].c ▪ Precauções no uso do controlador if.
............................................................................403
[Link].d ▪ A precaução com o operador de atribui-
ção também se aplica aos laços. ......................406
[Link] ▪ O controle de decisão switch..............................407
[Link].a ▪ Sintaxe.............................................407
[Link].b ▪ Funcionamento................................407
[Link].c ▪ Exemplo de uso do switch...............409
13.5 • Ponteiros, Matrizes e Referências...........................................409
13.5.1 ▪ Ponteiros...................................................................409
[Link] ▪ Como a memória é dividida (global, pilha e heap)
410
[Link] ▪ Capturando o endereço de uma variável já existen-
te. 410
[Link] ▪ Alocando livremente memória no heap ..............411
[Link] ▪ Acessando valores através de ponteiros. ..........411
[Link] ▪ Exercícios: demonstrando o funcionamento de pon-
teiros. 412
13.5.2 ▪ Vetores e Matrizes.....................................................415
[Link] ▪ Onde alocar vetores: memória global, pilha ou heap
? 416
[Link] ▪ Matrizes - usando múltiplas dimensões..............418
[Link] ▪ Inicialização de vetores e matrizes.....................419
[Link] ▪ Vetores e Matrizes de estruturas........................420
[Link].a ▪ Inicializando uma matriz de estruturas
............................................................................420
[Link] ▪ Vetores e Matrizes de caracteres........................421
[Link].a ▪ Inicializando uma matriz de caracteres
............................................................................422
[Link].b ▪ Entendendo matriz de caracteres como
um endereço.......................................................423
[Link].c ▪ Exemplo de matriz de caracteres.....423

• Anexos ....................................................................................427
Anexo A. Compilador e linker .....................................................................429
Anexo B. Respostas às questões para revisão ..........................................431
[Link]ões do Capítulo 1 ...........................................................432
[Link]ões do Capítulo 2 ...........................................................434

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


14 • AGIT informática • C++ • • Sumário detalhado

1. Exercício .........................................................................................434
2. Questões .........................................................................................435
[Link]ões do Capítulo 3 ...........................................................438
1. Exercício ........................................................................................438
2. Questões .........................................................................................440
[Link]ões do Capítulo 4 ...........................................................444
1. Exercício ........................................................................................444
2. Questões .........................................................................................448
[Link]ões do Capítulo 5 ...........................................................453
1. Exercício 1......................................................................................453
2. Exercício 2 .....................................................................................461
3. Questões .........................................................................................464
[Link]ões do Capítulo 6 ...........................................................477
[Link]ões do Capítulo 7 ...........................................................479
[Link]ões do Capítulo 11 ..........................................................481
Anexo C. Guia de consulta rápida ..............................................................485
[Link] de tipos primitivos .........................................................486
2. Tabela de operadores básicos por tipo de operação ...............487
[Link] completa de operadores e suas precedências .............489
[Link] de fluxo de processamento ......................................491
[Link] diretivas de compilação ...........................................493
6. Seqüências escape .................................................................494
7. Palavras reservadas ...............................................................494
8. Modificadores ..........................................................................495

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• C++ • AGIT informática • 15

"
Nunca projete (um software) sozinho se você puder evitar isso! Não
comece a escrever código antes que você tenha colocado suas idéi-
as a prova, explicando-as para alguém. Discuta o projeto, designs, e
técnicas de programação com amigos, colegas, usuários potenciais,
etc, antes de começar a usar o teclado. É incrível o quanto você pode
aprender apenas experimentando articular as idéias. No fim das con-
tas, um programa nada mais é do que a expressão (em código) de
determinadas idéias.
(...)
Do mesmo modo, quando você já estiver implementando um pro-
grama, e os erros aparecerem, tire os olhos do teclado. Pense no pro-
blema em si, ao invés de se fixar na solução - ainda incompleta- que
você desenvolveu. Converse com alguém: explique o que você quer
fazer e porque o que você já fez não está funcionando. É surpreen-
dente o quanto é comum que uma solução seja encontrada apenas
porque explicamos cuidadosamente o problema para alguém. Não
procure descobrir erros sozinho se você não for obrigado a fazer
isso!
(...)
Quando você buscar uma abordagem para a criação de software,
faça isso de uma maneira fundamentada e séria: você quer ser parte
da solução e não mais um dos problemas.
"

Bjarne Stroustrup, criador original da Linguagem C++,


no livro "Programming - Principles and Practice Using C++".

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


16 • AGIT informática • C++ •

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 1 ▪ Introdução: a Linguagem C++ • C++ • AGIT informática • 17

• Capítulo 1
▪ Introdução: a Linguagem C++

Neste capítulo teremos uma visão geral sobre:


- A linguagem C++ e suas principais características.
- A importância de bibliotecas que fornecem código já pronto para uso.
- As ferramentas necessárias para gerar programas executáveis.
- Recursos auxiliares para o aprendizado de C++, como livros e sites.

1.1 • Tópicos de destaque neste capítulo ......................................18


1.2 • As linguagens C e C++ ......................................................20
1.2.1 ▪ Breve histórico .................................................................................20
[Link] ▪ Linguagem C ................................................................20
[Link] ▪ Linguagem C++ ............................................................20
1.2.2 ▪ Características gerais de C++ ..........................................................21
1.2.3 ▪ Diferenças entre C e C++ ................................................................22
1.3 • Biblioteca de C++ e bibliotecas auxiliares .............................23
1.3.1 ▪ Biblioteca padrão de C++ .................................................................23
1.3.2 ▪ Bibliotecas adicionais .......................................................................23
1.3.3 ▪ Bibliotecas adicionais: indispensáveis .............................................24
1.4 • Porque aprender C++ ........................................................25
1.4.1 ▪ Base sólida em programação ..........................................................25
1.4.2 ▪ C++ pode ser a linguagem principal .................................................25
1.4.3 ▪ Usando outras linguagens ...............................................................26
1.5 • Livros e sites sobre C++ ....................................................26
1.5.1 ▪ Livros para o iniciante ......................................................................27
1.5.2 ▪ Livros para aprofundamento ............................................................27
1.5.3 ▪ Livros adicionais(abordagens e técnicas) ........................................28
1.5.4 ▪ Alguns sites úteis sobre C++ ...........................................................28
1.6 • Linguagem, Compiladores e Ambientes ................................30
1.6.1 ▪ Compiladores ...................................................................................30
1.6.2 ▪ Ambientes de Desenvolvimento Integrado .......................................31
1.6.3 ▪ Compiladores comerciais e gratuitos ...............................................32
1.6.4 ▪ Exemplos de compiladores ..............................................................32
[Link] ▪ Gratuitos (alguns exemplos) .........................................32
[Link] ▪ Comerciais (alguns exemplos) ......................................32

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


18 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

[Link] ▪ Outros exemplos ...........................................................33


1.6.5 ▪ Exemplos de ambientes de desenvolvimento ..................................33
1.7 • O que usamos na Agit Informática .......................................35
1.8 • O que veremos no curso ....................................................36
1.9 • Conclusão ........................................................................36
1.9.1 ▪ Linguagem padronizada ...................................................................36
1.9.2 ▪ Linguagem multi-paradigma .............................................................37
1.10 • Ferramentas usadas no curso..............................................38
1.11 • Questões para revisão do Capítulo 1 ....................................39

1.1 • Tópicos de destaque neste capítulo

 C e C++ são linguagens padronizadas (ISO/ANSI).

 São padrões abertos, disponíveis em qualquer plataforma compu-


tacional.
 Por isso são linguagens multiplataforma, exigindo apenas uma
nova compilação para cada plataforma desejada.

 Para poder utilizar C++ em diferentes plataformas necessita-


mos de:

 Compiladores que traduzam o código fonte para o código de má-


quina de cada plataforma.
 E, em praticamente todas as plataformas, encontramos compila-
dores C++, tanto comerciais como gratuitos.
 Encontramos também ambientes de desenvolvimento, comerciais
e gratuitos.

 C++ é, quase totalmente, compatível com C.

 Mas os novos recursos oferecidos por C++ conduzem a uma


nova maneira de conceber a programação de computadores.

 Entre esses novos recursos destacam-se duas vertentes princi-


pais: orientação a objetos e programação genérica.
 Isso significa também que C++ é uma linguagem híbrida, pois, ao
contrário de outras, que suportam apenas uma única técnica de
programação (como orientação a objetos, por exemplo), C++ su-
porta múltiplas técnicas de programação.
Por isso dizemos que C++ é uma linguagem multi-paradigma.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.1 • Tópicos de destaque neste capítulo • C++ • AGIT informática • 19

 C++ conta com uma biblioteca padrão. E há também diversas


outras bibliotecas que podem ser usadas.

 A biblioteca padrão faz parte do padrão da linguagem e nos ofere-


ce, prontos para uso, recursos de suporte em certas áreas de de-
senvolvimento previsíveis e comuns.
 Além disso existem diversas bibliotecas adicionais, desenvolvidas
por terceiros e consagradas pelo uso, e, neste aspecto, destaca-
mos duas delas: boost e Qt.
 Para o programador, isso representa um ganho significativo de
produtividade.

 Aprender C++ é essencial para um bom programador de com-


putadores.

 Porque essa é uma linguagem que poderá ser utilizada em uma


grande variedade de situações.
 E também porque, devido à sua amplitude, já que suporta diver-
sas técnicas de programação, o aprendizado de outras linguagens
torna-se mais simples.
E, aliás, muitas delas são descendentes de C++.

 Escolher C++ como linguagem principal não impede que ou-


tras linguagens sejam utilizadas, conjuntamente ou não, sem-
pre que um determinado projeto torne isso mais produtivo ou
eficiente.

 O aprendizado de C++ é atualmente facilitado pela existência


de inúmeros sites e livros de excelente nível.

 Apenas em plataformas muito “baixas” (microcontroladoras,


por exemplo), não encontramos compiladores C++.
Mas sempre encontraremos um compilador C.

 Desse modo é importante que o programador C++ saiba como


programar em C.
 Além disso, o programador C++, em certas situações, terá que li-
dar com código em C (por exemplo, para usar recursos nativos de
sistemas operacionais).
 Este é um curso sobre C++. Por isso o uso de C é apresentado em
segundo lugar, exceto naquilo que é básico e comum às duas lin-
guagens.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


20 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

1.2 • As linguagens C e C++


1.2.1 ▪ Breve histórico
[Link] ▪ Linguagem C
A linguagem C foi criada no início da década de 70 por Dennis Ritchie a partir da lin-
guagem B (criada por Ken Thompson) que, por sua vez, era baseada na linguagem
BCPL (criada por Martin Richards).
A popularização de C teve grande impulso com o livro “C – A linguagem de Progra-
mação” de Dennis Ritchie e Brian Kernighan.
Desenvolvida inicialmente no sistema operacional UNIX, a linguagem passou por
uma grande expansão, e, ainda na década de 70, surgiram novos compiladores, inclusi-
ve para mainframes como o IBM 370 e o Honeywell Bull 6000.
Em 1983, o ANSI (American National Standards Organization – Instituto Nacional
Americano de Padrões) criou um comitê para a padronização da linguagem, visando
garantir que os diversos compiladores, então já disponíveis e fornecidos por diferentes
empresas, não viessem a adotar caminhos próprios, o que geraria incompatibilidades
entre eles.
Assim temos o padrão C89 (referente a 1989) e o C99 (referente a 1999), embora, pro-
vavelmente o mais usado ainda seja o C89.

[Link] ▪ Linguagem C++


A linguagem C++ foi criada entre 1979 e 1983 por Bjarne Stroustrup (autor de “A lin-
guagem de programação C++”, atualmente em sua terceira edição).
Essa trabalho foi realizado nos “Bell Laboratories” que na época pertenciam à empre-
sa AT&T (American Telephone and Telegraph), onde Stroustrup trabalhava. O objetivo
era desenvolver uma linguagem que tivesse a performance e flexibilidade da lingua-
gem C, mas incorporasse recursos importantes de outras linguagens, especialmente Si-
mula (mas também foram utilizadas contribuições originárias de ADA, Algol e Clu).
A AT&T tornou esse projeto público o que viria a permitir a criação de um padrão aber-
to.
A partir dessa liberação um grande número de profissionais de diferentes empresas ini-
ciaram o trabalho pela padronização da Linguagem C++, o que levou à criação de um
comitê (o comitê X3J16) do American National Standards Institute (ANSI) que teve
seus trabalhos iniciados em dezembro de 1989.
E, em junho de 1991, esse esforço foi ampliado com o envolvimento direto do Interna-
tional Standards Organization (ISO) no projeto. A partir daí a iniciativa ANSI tor-
nou-se parte da iniciativa ISO, integrando-se ao comitê ISO WG21.
Em 1995 os comitês X3J16 e WG21 publicaram o primeiro documento oficial de pa-
dronização de C++.
Em 1997 (refletindo o trabalho efetuado em 1996) tivemos um segundo documento in-
titulado “1997 C++ Public Review Document” disponível em versão “html” gratuita
nos sites:
[Link]
[Link]

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.2 • As linguagens C e C++ • C++ • AGIT informática • 21

 E finalmente, em 1998 (refletindo o trabalho efetuado em 1997), foi publica-


do o último documento oficial de padronização da linguagem, sendo este,
até 2011, o padrão oficial de C++, o qual pode ser adquirido diretamente no
site [Link]
Desse modo, o padrão inicial de C++ é denominado “C++98”, o qual rece-
beu, em 2003, pequenas alterações (denominadas “technical corrigen-
dum”), as quais levaram ao padrão “C++03”.
Atualmente, C++ conta com novos padrões: o C++11 (2011) e o C++14
(2014), sendo esperada para 2017 uma nova atualização: o C++17.

Esta apostila está centrada no C++98, já que ainda é o mais reivindicado


pelas empresas que usam C++.
Por isso, criamos um segundo curso, de 24 horas:
Treinamento em C++11/14. Se estiver interessado, entre em contato.

Informações relevantes sobre a criação e a evolução de C++ podem ser obtidas tam-
bém no site do criador da linguagem, Bjarne Stroustrup:
[Link]

1.2.2 ▪ Características gerais de C++


A linguagem C++ é baseada principalmente em C, mas inspirou-se também em outras
linguagens, como Simula (especialmente), além de ADA , Algol e CLU, aproveitando
destas últimas idéias que, em alguma medida, relacionavam-se a conceitos de progra-
mação orientada a objetos (OOP), ou a conceitos de programação genérica.
Assim, a linguagem C++ nasceu para incorporar nativamente novas técnicas de progra-
mação, mas com o compromisso de não perder performance com relação ao C, man-
tendo seja a sua flexibilidade, seja os seus recursos de “baixo nível”, que nos permitem
resolver certos prolemas críticos.
C e C++ são duas linguagens independentes. Ou seja, é perfeitamente possível apren-
der C++ sem conhecer C.
Na prática, contudo, é interessante que o programador C++ conheça as diferenças com
relação à linguagem C (e, por consequência, acabe aprendendo como funciona a lin-
guagem C) por, pelo menos, três motivos:

a. A linguagem C, em quase sua totalidade, é um subconjunto de C++.


 Isto significa que quase tudo o que é válido em C continua sendo válido em
C++ (com poucas exceções, que veremos mais adiante).
b. O programador C++ encontrará incontáveis bibliotecas já escritas em C e que
serão úteis (ou mesmo indispensáveis) em muitas e diversas oportunidades.
 Isso se aplica especialmente ao uso das APIs (interfaces de programação de
aplicativos) dos sistemas operacionais mais importantes, quase todas elas bi-
bliotecas escritas em C, bem como das bibliotecas nativas de sistemas espe-
cíficos (como bancos de dados e outras).
c. Em plataformas muito “baixas” (microcontroladoras, por exemplo), nem sempre
contaremos com um compilador C++ e sim, apenas, com um compilador C
(além, obviamente, do assembler da plataforma).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


22 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

 Nesses casos, o programador C++ terá que atuar totalmente em C.

 Conclusão: é importante que o programador C++ saiba como lidar com có-
digos escritos em C, pois, em algum momento, talvez precise usá-los.

1.2.3 ▪ Diferenças entre C e C++


Dissemos que quase tudo o que é válido em C é válido em C++. E que é importante
conhecer C.

 Mas isso não significa que possamos subestimar as diferenças de C++ com
relação a C.

Note que os motivos que apresentamos acima para sustentar a posição de que o progra-
mador C++ deve entender o funcionamento de C, referem-se, fundamentalmente, ao
aproveitamento de código antigo (isto é, código escrito anteriormente por você ou
por terceiros).
Contudo, no que diz respeito à escrita de código novo, o melhor é que você “esqueça”
C (ainda que isto não seja realmente possível…) e “raciocine em C++”.
Exceto, conforme já foi dito, quando precisar programar para plataformas muito bai -
xas, onde só exista um compilador C.

 Se conseguirmos absorver a filosofia de C++ seremos conduzidos, na es-


crita de código novo, a um estilo de programação bem diferente do estilo
C mais comum.
C é tão flexível que até é possível escrever programas em C com um “es-
tilo C++ aproximado”, embora exigindo muito mais trabalho do progra-
mador, já que esta linguagem não suporta diretamente (nativamente) as
novas técnicas de programação presentes em C++.

Portanto, só entendendo conceitualmente as técnicas e o estilo próprios de C++ é que


poderemos aproveitar todas as suas melhorias com relação a C.
Existe uma série de “pequenas melhorias”do C++ com relação ao C.
Contudo, as principais diferenças, que devem implicar em estilos de programação di-
ferentes, são justamente as “grandes melhorias” do C++: como veremos adiante, es-
ses são os recursos voltados para orientação a objetos e para programação genérica.
E, consequentemente, esse será o assunto mais importante deste curso.
Por enquanto, vamos apenas sintetizá-las:

 C++ é maior e melhor que C principalmente porque incorpora a noção de


classes (orientação a objetos) e templates ou “gabaritos” (programação
genérica).

 Esses recursos ampliam consideravelmente a força do C e devem conduzir-nos


a um novo estilo de programação, seja no que diz respeito ao uso da memó-
ria, seja no que diz respeito à lógica de programação, como também ao rea-
proveitamento de código já escrito.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.2 • As linguagens C e C++ • C++ • AGIT informática • 23

 Esse assunto só será visto mais a frente. Por enquanto, basta saber que Orien-
tação a objetos e Programação Genérica são técnicas de programação que
permitem a escrita de um código mais seguro, confiável e fácil de manter, bem
como possibilitam que um código escrito para situações genéricas e previsíveis
possa ser reaproveitado mais facilmente na criação de camadas de código
mais específicas, sem que esse código novo interfira no código já escrito.

1.3 • Biblioteca de C++ e bibliotecas auxiliares


Bibliotecas oferecem recursos já desenvolvidos, aplicáveis a muitas situações e por
isso permitem elevar a produtividade do programador, que não precisará reinventar a
roda, criando código para uma série de áreas de desenvolvimento previsíveis e comuns.
Algumas vezes precisamos desenvolver nossas próprias (e novas) bibliotecas, quando
nos deparamos com situações para as quais não existem soluções prontas, ou então
para resolver problemas específicos que se manifestem repetidamente em várias partes
de um determinado projeto.
 Mas, antes de fazer isso, é importante analisar se já não existe alguma bi-
blioteca pronta que resolva o problema.

1.3.1 ▪ Biblioteca padrão de C++


C++ herda a biblioteca de funções de C. Isso significa que para uma grande variedade
de situações previsíveis, já contamos com funções prontas para uso.
Mais importante: C++ acrescenta novos recursos de biblioteca que, além de permitir
a substituição (com vantagem) das funções herdadas (na maioria das situações), tam-
bém amplia enormemente as funcionalidades oferecidas pela biblioteca herdada. Em
especial, a Standard Template Library, ou STL (que veremos no devido tempo).

1.3.2 ▪ Bibliotecas adicionais


Além disso, atualmente, C++ conta com o apoio de uma infinidade de bibliotecas open
source desenvolvidas por terceiros, das quais vale destacar:
 boost: é praticamente uma biblioteca oficiosa de C++; pois ela é desenvolvida
por diversos dos integrantes do comitê de padronização de C++, e é, na prática,
considerada a “porta de entrada” para que um novo recurso venha, no futuro, a
fazer parte da biblioteca padrão de C++;
 isso significa que ela oferece, hoje, recursos ainda não disponíveis na biblio-
teca oficial; ela não é inteiramente multiplataforma (como a biblioteca padrão);
contudo, abrange as principais plataformas da atualidade (Unix/Linux,
Windows e Mac).
 veja: [Link]

 Qt: contem muitos dos recursos que já encontramos na boost (e até na bibliote-
ca padrão), mas oferece recursos adicionais, como interface gráfica de usuário,
suporte a serviços, Open GL, Web, etc., além de oferecer suporte para certos
ambientes embarcados;
 do mesmo modo que a boost, não é inteiramente multiplataforma; mas, igual-
mente, abrange Unix/Linux, Windows e Mac; além disso, abrange também

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


24 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

alguns embarcados: Symbian S60, Maemo, Android, iOS, Window Phone,


Embedded Linux, e alguns sistemas real-time como QNX.
 veja: [Link]
 E há muitas outras bibliotecas, como gtkmm, wxWidgets, e, inclusive, diversas
outras voltadas para áreas específicas.

1.3.3 ▪ Bibliotecas adicionais: indispensáveis


Sabemos que a Biblioteca Padrão de C++ não cobre, até o momento, algumas áreas vi-
tais para muitas aplicações. O padrão evolui, mas lentamente - pois ele precisa manter
sua integridade e estabilidade, devendo permitir que compiladores possam ser criados
para qualquer plataforma.
Assim, precisaremos contar com bibliotecas adicionais que sejam portáveis ao menos
para as principais plataformas: Unix/Linux, Windows e Mac - e, se possível, alguns
sistemas embarcados. Entre essas áreas de aplicação, destacamos:

 processos e comunicação entre processos;


 memória compartilhada e memória mapeada;
 multithreading e sincronização entre threads;
 network / sockets;
 acesso a banco de dados;
 programação gráfica;
 interface gráfica de usuário.

Alternativas:
 Bibliotecas adicionais próprias:
 Biblioteca padrão + [ boost + ] bibliotecas próprias [ + ... ]
Supondo-se que exista o conhecimento prévio das APIs envolvidas pelas plataformas
desejadas, precisaríamos desenvolver nossas próprias bibliotecas para cobrir todas ou
algumas dessas áreas. Inventar a roda... Com isso teríamos os seguintes problemas:
 custo inicial de desenvolvimento;
 criar uma documentação aceitável;
 custo de manutenção, no qual deve estar incluído o treinamento de novos pro-
gramadores que venham a ser agregados à equipe;
 assim, o desejável é que o desenvolvimento de bibliotecas próprias esteja
restrito às áreas de de desenvolvimento, onde não contamos com a cober-
tura de bibliotecas bem conhecidas.
 Bibliotecas adicionais públicas; entre as alternativas disponíveis temos:
 gtkmm (GTK+) ;
 Biblioteca padrão + [ boost + ]  Qt ;  [ + ... ]
 wxWidgets ;
 outras.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.3 • Biblioteca de C++ e bibliotecas auxiliares • C++ • AGIT informática • 25

Escolher nem sempre é fácil. Mas podemos ter alguns critérios:

 estabilidade e vitalidade (presença no mercado e atualização de versões);


 abrangência: plataformas e áreas de atuação suportadas;
 documentação;
 simplicidade de uso;
 licenciamento

No caso das bibliotecas citadas acima, além da própria biblioteca oficial e da boost,
considerando-se aqui gtkmm, Qt e wxWidgets, temos três boas opções. E, tudo pesa-
do, escolhemos Qt. Com relação a gtkmm e wxWidgets, podemos dizer que Qt é:
 mais completa (tanto em plataformas suportadas como na amplitude das áreas
de aplicação);
 melhor documentada e mais simples de usar;
 estabilidade e vitalidade comprovadas, pois a atualização de versões (e corre-
ções) é a mais frequente e sua presença no mercado é importante e crescen-
te, pois hoje encontramos Qt em uma infinidade de aplicações, entre elas aplica-
ções muito populares e marcantes como:
 KDE, Google Earth, Skype, simuladores de voo da NASA, etc.

 veja a relação completa em: [Link]

Com relação ao licenciamento, até março de 2008 Qt perdia nesse critério, pois sua li-
cença open source era restrita à GPL, ao passo que gtkmm, e wxWidgets podiam ser
utilizadas sob a licença LGPL. Mas, a partir dessa data, com a versão 4.5 (e posterio-
res), Qt também pode ser utilizada sob a licença LGPL. Assim, não temos dúvidas
em nossa escolha: Qt.

1.4 • Porque aprender C++


1.4.1 ▪ Base sólida em programação
É importante aprender uma linguagem que suporta praticamente todas as técnicas de
programação (e, em C++11, além de orientação a objetos, programação genérica e
rudimentos de programação funcional, temos mais recursos para suporte à progra-
mação funcional). Isso propiciará uma formação mais sólida para um bom progra-
mador, pois ele perceberá que o mundo da programação não se restringe a uma úni-
ca técnica ou estilo.
Essa base será útil, independentemente da linguagem que esse programador estiver
usando em cada momento.

1.4.2 ▪ C++ pode ser a linguagem principal


O que dissemos acima é valioso, mas não podemos esquecer que C++ é útil em si
mesma: ou seja, para uma grande variedade de situações ela se apresenta como uma
das melhores soluções, particularmente quando precisamos de alta performance e por-
tabilidade entre diferentes plataformas.
Além disso, pela sua amplitude e clareza lógica, o aprendizado de C++ torna mais fácil
e seguro o aprendizado de outros linguagens, porque serve de parâmetro. Não por aca-

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


26 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

so, outras linguagens famosas são descendentes de C++, ainda que não dispondo de to-
dos os seus recursos.

1.4.3 ▪ Usando outras linguagens


O fato de consideramos C++ como a linguagem mais completa já criada, e até
mesmo a adotarmos como linguagem principal, não deve servir para conclusões
extremadas, do tipo: “só uso C++ e ponto final”.

Linguagens de Programação são ferramentas. E há casos em que determinada lingua -


gem pode ser mais simples e/ou eficiente para resolver um problema em particular.
Observe o exemplo do Google. Uma das qualidades dessa empresa é a sua versatilida-
de. O Google tem aplicações completamente feitas em C++ (como também tem aplica-
ções em Java).
Por exemplo: o Google Earth, foi totalmente desenvolvido em C++, com o apoio da
biblioteca Qt.
Já a sua aplicação mais tradicional, o search engine (a ferramenta de busca que fez a
fama do Google), foi desenvolvida com C, C++ e Python.

 Em um famoso artigo dos criadores do Google (“The Anatomy of a Large-


Scale Hypertextual Web Search Engine”, de Sergey Brin e Lawrence
Page), podemos ler que:
“A maior parte do Google está implementada em C ou C++ por efici-
ência”.
E nesse mesmo artigo é dito também que para determinadas atividades
(web-crawler) foi utilizada a linguagem Python.

Veja esse artigo (vale a pena) em:


[Link]

E é inteiramente possível aplicar essa “dobradinha” em muitas outras situações:


usando-se C++ no “motor” (o processamento pesado) onde a performance é mais críti-
ca, e Python na ponta.

 Aliás, Python combina muito bem com C++, e não por acaso a maioria da
comunidade de programadores C++ admira (quando não usa) Python.
Ou seja: linguagens de programação não são times de futebol, pelos quais torcemos.
São ferramentas. E escolher a(s) ferramenta(s) adequada(s) é um dos passos para bem
solucionar problemas.

1.5 • Livros e sites sobre C++


Há diversos livros sobre C+++ que podemos considerar como muito bons.
Mas devemos tomar um cuidado especial ao escolher um livro sobre C++: saber se ele
reflete o padrão da linguagem. Conforme comentado acima, o primeiro padrão só foi
firmado entre 1995 e 1998. E o padrão da linguagem incorporou um conjunto de me-

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.5 • Livros e sites sobre C++ • C++ • AGIT informática • 27

lhorias, dos quais os mais importantes são templates e a STL (Standard Template Li-
brary).
E ainda há em circulação livros que não incorporaram essas melhorias. Isso significa
que eles refletem o C++ anterior ao padrão, o qual é oficialmente considerado obsoleto.

Abaixo, indicamos alguns dentre aqueles que consideramos os melhores livros


sobre C++:

1.5.1 ▪ Livros para o iniciante


 O melhor livro para iniciantes atualmente é Programming: Principles and
Practice Using C++ do criador original de C++, Bjarne Stroustrup.
Este livro foi produzido como material para uma cadeira de primeiro semestre de
Ciências da Computação da Universidade do Texas. Ver:
[Link]
(
(editora Addison-Wesley; ISBN-10: 0321543726, ISBN-13: 978-0321543721).
OBS: há uma tradução para o português (pela editora Artmed, selo
Bookman).

1.5.2 ▪ Livros para aprofundamento


 O primeiro livro que você deve ler para aprofundar seus conhecimentos sobre
C++ também é de autoria do criador original de C++, Bjarne Stroustrup: The
C++ Programming Language.
OBS: deve ser adquirida, no mínimo, a terceira edição, que já cobre o padrão
C++98.
(editora Addison-Wesley; ISBN 0-201-88954-4).

Mas o ideal é adquirir a quarta edição, já cobrindo C++11/14.

 Atenção: esse é um livro fundamental, sendo considerado uma expressão


e um complemento do documento de padronização de C++.
Não está dirigido a iniciantes e sim a programadores com algum domí-
nio da linguagem.
Há uma tradução para o português: A Linguagem de Programação
C++ (editora Artmed, selo Bookman, Brasil; ISBN 85-7307-699-29).

 The Design and Evolution of C++, também de Bjarne Stroustrup;


um excelente complemento ao livro anterior, onde você poderá aprender porque
certas coisas são do jeito que são em C++, bem como as formas mais eficientes
de uso da linguagem;
(editora Addison-Wesley; ISBN 0-201-54330-3).
 The C++ Standard Library: A Tutorial and Reference de Nicolai M. Josuttis;
um livro completo sobre a biblioteca padrão de C++; permitirá um conheci-
mento profundo da biblioteca, e servirá permanentemente como um livro de re-
ferência e consulta, quando iniciamos o uso de um novo recurso da biblioteca;
(editora Addison-Wesley; ISBN-10: 0201379260, ISBN-13: 978-0201379266).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


28 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

 C++ templates: The Complete Guide de Nicolai M. Josuttis e David


Vandevoorde;
uma abordagem completa sobre um importante recurso de C++: templates;
(editora Addison-Wesley; ISBN-10: 0201734842, ISBN-13: 978-0201734843).
 Modern C++ design: Generic Programming and design patterns Applied de
Andrei Alexandrescu;
expande o conhecimento sobre templates (além do bom uso de herança múlti-
pla), explorando técnicas de programação genérica, com a aplicação de alguns
design patterns bem conhecidos.
(editora Addison-Wesley; ISBN-10: 0201704315, ISBN-13: 978-0201704310).
 C++ template Metaprogramming: Concepts, Tools, and Techniques from
boost and Beyond de David Abrahams;
expande o conhecimento sobre templates e programação genérica, avançando
no terreno da meta-programação;
(editora Addison-Wesley; ISBN-10: 0321227255, ISBN-13: 978-0321227256).

1.5.3 ▪ Livros adicionais(abordagens e técnicas)


 C++ Coding Standards: 101 Rules, Guidelines, and Best Practices de Herb
Sutter e Andrei Alexandrescu;
(editora Addison-Wesley; ISBN-10: 0321113586, ISBN-13: 978-0321113580).
 Effective C++: 55 Specific Ways to Improve Your Programs and designs
(3rd Edition) de Scott Meyers;
(editora Addison-Wesley; ISBN-10: 0321334876, ISBN-13: 978-0321334879).
 Effective STL: 50 Specific Ways to Improve Your Use of the Standard Tem-
plate Library de Scott Meyers;
(editora Addison-Wesley; ISBN-10: 0201749629, ISBN-13: 978-0201749625).
 Exceptional C++: 47 Engineering Puzzles, Programming Problems, and So-
lutions de Herb Sutter;
(editora Addison-Wesley; ISBN-10: 0201615622, ISBN-13: 978-0201615623).
 More Exceptional C++: 40 New Engineering Puzzles, Programming Prob-
lems, and Solutions de Herb Sutter
(editora Addison-Wesley; ISBN-10: 020170434X, ISBN-13: 978-0201704341).
 Exceptional C++ Style: 40 New Engineering Puzzles, Programming Prob-
lems, and Solutions de Herb Sutter;
(editora Addison-Wesley; ISBN-10: 0201760428, ISBN-13: 978-0201760422).
 More Effective C++: 35 New Ways to Improve Your Programs and designs
de Scott Meyers;
(editora Addison-Wesley; ISBN-10: 020163371X, ISBN-13: 978-0201633719).

1.5.4 ▪ Alguns sites úteis sobre C++


 [Link]:
[Link]
 Este é um excelente site para consultas sobre as bibliotecas C e C++ (na
seção Library Reference); além disso, oferece tutoriais, forums, artigos e in-
formações em geral sobre C++.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.5 • Livros e sites sobre C++ • C++ • AGIT informática • 29

E, por falar em bibliotecas C++, vale a pena consultar também estes sites:
[Link]
[Link]

 The C++ Standards Committe:


[Link]
 É o site do comitê de padronização de C++. Aí você poderá encontrar as
propostas e discussões voltadas para a evolução de C++, especialmente para
os novos padrões da linguagem: C++11 e C++14., podendo também acompa-
nhar o que está sendo proposto para o C++17.
 Bjarne Stroustrup's homepage:
[Link]
 Página do criador original de C++, Bjarne Stroustrup. Artigos, entrevistas,
FAQs, links e informações diversas sobre C++.
 C++ org:
[Link]
 Artigos, FAQs, notícias, links, informações sobre bibliotecas, etc.

 ACCU - Association of C and C++ Users:


[Link]
 Discussões, artigos, notícias, resenhas de livros, mailing lists, links, blogs.
Além disso, você pode tornar-se membro da associação.
A página de links da ACCU merece destaque (pois evita que tenhamos que
fazer aqui uma lista muito extensa..):
[Link]
 C++ Users Journal (atualmente é uma seção da mais prestigiada revista
para programadores, a Dr. Dobb's):
[Link]
 O “CUJ” é a legendária revista sobre C++. Artigos excelentes, código fonte,
etc. Visite também o site do C/C++ Users Group:
[Link]
 Internet sites and files of interest to C++ users:
[Link]
 Seção do site de Robert Davies, onde vemos uma lista enorme de sites sobre
C++. Alguns estão desatualizados, mas aí encontra-se de tudo.
 Como lembrete, repetimos aqui os links, para duas bibliotecas, já mencionadas
acima, que consideramos fundamentais para o programador C++:
 boost: [Link] -

 Qt: [Link]

E há ainda muitos outros links. Mas a relação acima é um bom ponto de partida para
começar – e depois, através desses, encontrar os demais.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


30 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

1.6 • Linguagem, Compiladores e Ambientes


Iremos iniciar agora um curso de Linguagem C++. A primeira coisa que precisamos
entender é a diferença entre a linguagem e as ferramentas necessárias para gerar
aplicações usando essa linguagem.
 A Linguagem C++ é um padrão aberto.

Assim sendo, podemos escrever um código fonte em C++ que poderá ser usado para
gerar aplicações em qualquer plataforma de hardware e Sistema Operacional - de pe-
quenos dispositivos embarcados até micro-computadores e computadores de grande
porte, e também em diferentes sistemas operacionais como, por exemplo, Windows,
Unix/Linux, Mac, Symbian, PalmOS, etc.

 Para isso, precisaremos utilizar ferramentas de programação, como com-


piladores, ferramentas de debug e ambientes de desenvolvimento.

Se a Linguagem em si é um padrão aberto, já as ferramentas são produtos – e neste


caso encontramos ferramentas gratuitas e comerciais.

1.6.1 ▪ Compiladores
Para desenvolver programas escreve-se o “código fonte” em uma determinada lingua-
gem e em seguida utiliza-se uma ferramenta denominada “compilador”:
a. o compilador (de cada linguagem) irá analisar a sintaxe empregada pelo pro-
gramador em seu código fonte, de acordo com as regras da linguagem;
b. se a sintaxe estiver correta, ele irá traduzir esse código fonte para a linguagem
da máquina, gerando instruções que possam ser “entendidas” e executadas por
essa máquina, ou seja as “instruções de máquina” ou o “código de máquina”.
 Portanto, o programa estará convertido para as “linguagens de máquina”,
específicas de cada plataforma, as quais, estando no nível da máquina, são
classificadas como linguagens de “baixo nível”.

 No caso de C++, usamos, em cada plataforma, o respectivo compilador


C++, para que seja gerado o código de máquina para aquela plataforma.

Código fonte escrito pelo programador em Linguagem C++


`

`
`

Compilador C++ Compilador C++ Compilador C++


para a plataforma “A” para a plataforma “B” outras plataformas ...
`

Código de Máquina Código de Máquina Código de Máquina


plataforma “A” plataforma “B” outras plataformas ...

Programar diretamente nas linguagens de máquina constitui um trabalho árduo, pois


cada pequeno detalhe deve ser resolvido com uma instrução específica, a qual é identi-

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.6 • Linguagem, Compiladores e Ambientes • C++ • AGIT informática • 31

ficada como um número binário, e o programador precisa lembrar os códigos numéri-


cos das instruções(representados em hexadecimal) . Além disso, sendo dependentes de
plataforma, linguagens de máquina não são portáveis.
Para simplificar o uso das linguagens de máquina, foram criadas as linguagens “as-
sembler”.Mas estas são constituídas apenas por expressões mnemônicas que represen-
tam os códigos binários das instruções de máquina, sendo assim também linguagens de
baixo nível (ou nível da máquina).
E, como estão relacionadas diretamente às instruções de máquinas específicas, tam-
bém não são portáveis entre diferentes plataformas.
Por isso mesmo as linguagens de programação mais utilizadas para a escrita de progra-
mas de computador são chamadas “linguagens de médio e alto nível”, em oposição às
linguagens de “baixo nível”, por estarem mais próximas do “nível humano” e não da
máquina, simplificando os detalhes. E, assim, muitas delas podem oferecer também
uma única codificação para diversas plataformas.

 Tais linguagens (e até mesmo assembler) não são compreendidas pela má-
quina e assim precisam ser traduzidas para a linguagem daquela máquina.
Este é justamente o papel dos programas compiladores.

Portanto, não basta conhecermos uma linguagem de programação. É necessário tam-


bém dispor de programas compiladores. E há vários tipos de compiladores: alguns só
conseguem gerar código de máquina para alguns sistemas operacionais (Windows, por
exemplo); e há aqueles que dispõem de versões para diferentes sistemas operacionais.
No caso da linguagem C++ dispomos de diversos programas compiladores. Há compi-
ladores gratuitos e há compiladores comerciais cuja utilização exige a aquisição de li-
cenças de uso.

1.6.2 ▪ Ambientes de Desenvolvimento Integrado

 Na maior parte dos casos, os compiladores:

 ou já são distribuídos acompanhados de ambientes de desenvolvimento;


 ou podem ser utilizados em ambientes de desenvolvimento criados por ter-
ceiros.
Um ambiente de desenvolvimento é uma ferramenta que visa integrar, em uma única
interface de usuário, todas as ferramentas necessárias ao desenvolvimento de progra-
mas. Como, por exemplo:
a. editores de texto voltados para a escrita de código fonte; esses editores, sendo
especializados em programação, podem até identificar erros de sintaxe à medida
em que o código é escrito, podendo ter diversos outros recursos auxiliares;
b. como seria de se esperar, um ambiente desses deve conter recursos para invocar
facilmente um compilador adequado para a linguagem em uso; alguns ambientes
permitem até trabalhar com várias linguagens e compiladores.
c. o mesmo para ferramentas de detecção de erros (debug), integradas ao ambien-
te de forma a facilitar o acompanhamento da execução, inspeção de variáveis, etc.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


32 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

d. em diversos casos, os ambientes de desenvolvimento podem também gerar auto-


maticamente alguns trechos de código para situações comuns e previsíveis.

 E, do mesmo modo que ocorre com compiladores, temos à disposição di-


versos ambientes de desenvolvimento, gratuitos e comerciais.

1.6.3 ▪ Compiladores comerciais e gratuitos


Há diversos compiladores para C++.
A escolha de um compilador pode estar baseada no fato de que ele seja comercial ou
gratuito.
Ou pelo fato de suportar totalmente (ou quase totalmente) o padrão mais atual da lin-
guagem. Isso excluiria, por exemplo, a versão do compilador Microsoft que acompanha
o Visual C++ 6.0, que está abaixo do padrão; esse problema foi resolvido nas versões
posteriores do Visual C++.
Ou ainda, no fato de que determinado compilador gera um código de máquina mais
otimizado, isto é, gera aplicações que executam mais rapidamente (melhor performan-
ce), como, por exemplo, o GCC, o compilador Microsoft e o compilador Intel.(este
último só existe para máquinas com processadores da Intel e provavelmente, nessa pla-
taforma, ainda é o que gera aplicações de melhor performance).

1.6.4 ▪ Exemplos de compiladores


[Link] ▪ Gratuitos (alguns exemplos)
 GCC – em [Link] – é um dos compilado-
res mais usados e tem versões para diferentes sistemas operacionais:
Unix/Linux, Windows, MacOS(Macintosh), Symbian, PalmOS, etc.
Há diversos ambientes de desenvolvimento gratuitos desenvolvidos para tra-
balhar em conjunto com o GCC.
 Visual C++ Express – em [Link]
– é a edição gratuita do compilador da Microsoft, já acompanhado do ambiente
de desenvolvimento “Visual C++”. A edição gratuita (“express”) dispõe de menos
recursos que as versões comerciais (mas isso afeta apenas o desenvolvimento
de aplicações específicas para Windows e para a plataforma .Net)
 Apple XCode – [Link] –
é o ambiente de desenvolvimento da Apple, acompanhado de compiladores
para C, C++ e Objective C.
 Digital Mars – [Link] – edições gratuita e comercial.

[Link] ▪ Comerciais (alguns exemplos)


 Microsoft: – [Link] – o compilador C++ da
Microsoft é vendido em conjunto com um ambiente de desenvolvimento: o
“Microsoft Visual C++” , em diferentes edições comerciais (professional, enter-
prise, etc – a cada nova versão, confira as edições disponíveis no link indicado).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.6 • Linguagem, Compiladores e Ambientes • C++ • AGIT informática • 33

 Intel: – seguir os links para compiladores constantes na seguinte página:


[Link]
obs.: o compilador da Intel dispõe de uma versão gratuita para Linux.
 Comeau: – [Link]

[Link] ▪ Outros exemplos


Diversos outros exemplos de compiladores gratuitos e comerciais (e ambientes de
desenvolvimento) podem ser encontrados nos seguintes sites:
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]

1.6.5 ▪ Exemplos de ambientes de desenvolvimento


Um ambiente de desenvolvimento, como dissemos acima, permite integrar diversas
ferramentas necessárias à criação de programas de computador.

 O mais primitivo desses ambientes é o arquivo makefile.

Em um arquivo “makefile” temos uma linguagem de script voltada para a compilação


e link-edição de programas (que até pode ser usada para outros fins).
Quando precisamos compilar apenas um arquivo fonte (pequenos programas de teste,
por exemplo), o uso do makefile não é tão importante. Mas, em outras situações, tere-
mos uma grande vantagem, pois esse script permite relacionar diversos arquivos fon-
tes além de outros recursos, como bibliotecas, que deverão ser considerados na gera-
ção dos binários. Desse modo evitaremos escrever, repetidamente, longas linhas de
chamada ao compilador para cada nova recompilação.
Além disso ele permite identificar quais fontes precisam ser recompilados (apenas
aqueles que foram alterados), reduzindo o tempo de compilação. Esse script é interpre-
tado por programas que o entendem, como é o caso do utilitário “make”.
Contudo, o “makefile” permite apenas relacionar os arquivos necessários e as regras
para gerar os binários. Não está integrado a editores de texto ou ferramentas de depu-
ração de erros.
Já ambientes de desenvolvimento mais completos (ou integrados) tendem a aumentar
a produtividade do programador, pois tornam mais rápida a execução de tarefas rotinei-
ras e puramente burocráticas.
Acima, ao relacionar exemplos de compiladores, indicamos também que alguns deles
já vêm acompanhados de um ambiente, como é o caso do “Visual C++”.
Além disso, há diversos outros ambientes, criados por terceiros - tanto comerciais
como gratuitos.
Nos links que relacionamos na seção [Link],página 33, acima, além de compiladores,
serão encontradas também ambientes de desenvolvimento.
REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional
34 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

Alguns exemplos:

 QtCreator: (ver em [Link] para Windows, Unix/Linux e


Mac. Mais voltado para aplicações utilizando a biblioteca Qt. Contudo, pode ser
usado para criar qualquer aplicação C++ (mesmo sem usar Qt).

 Microsoft Visual C++ (ver em [Link] já citado


acima, com as edições comerciais e a gratuita (apenas Windows).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.6 • Linguagem, Compiladores e Ambientes • C++ • AGIT informática • 35

 Eclipse (ver em [Link] );


gratuito, para Windows, Unix/Linux e Mac.

 KDevelop (ver em [Link] gratuito, para Unix/Linux.


 etc; veja os links , na seção [Link], página 33, acima.

1.7 • O que usamos na Agit Informática


Na Agit Informática utilizamos a Linguagem C++ seja para desenvolver programas
seja como tema dos cursos que oferecemos para o aprendizado dessa linguagem.

Quanto a compiladores e ambientes de desenvolvimento utilizamos:

 O GCC com Qt (usando o ambiente QtCreator), para o desenvolvimento de


nossas aplicações multi-plataforma (Windows, Unix/Linux etc).
Também é utilizado em nosso curso de Qt: “Desenvolvimento multi-platafor-
ma em C++ com Qt”.
 o GCC, no desenvolvimento (usando o ambiente QtCreator), dos poucos pro-
gramas que possam rodar tanto em Windows como em Unix/Linux., e não pre-
cisem dos recursos de Qt.
Além disso também é utilizado nos cursos “Linguagem C++” e “C++ para Li-
nux” que ministramos.
Como dito acima, o GCC é gratuito, sob a licença “General Public Licence”
(GPL). Ver em: [Link]
 “Microsoft Visual C++”: utilizamos o “Microsoft Visual C++” tanto para manu-
tenção de programas antigos, específicos para a plataforma Windows, como
também no curso “Visual C++” que também ministramos.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


36 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

1.8 • O que veremos no curso


Neste curso veremos a Linguagem C++ em seu padrão ISO (C++98/03).
Como já foi dito acima, para C++11/14, oferecemos um curso adicional.

 Isto significa que certos recursos dependentes de plataforma não são


abordados neste curso.
Mas serão abordados nos cursos “Desenvolvimento multi-plataforma
em C++ com Qt” (Windows, Unix/Linux, Mac, etc)., ou “Visual C++”
(Windows), ou “C++ para Linux” (Unix/Linux).

Entre esses tópicos, dependentes de plataforma ou de bibliotecas específicas (não cons-


tantes do padrão da linguagem), e que não são vistos neste curso, destacamos os se-
guintes:
 processos e comunicação entre processos locais;
 comunicação entre processos remotos (sockets, TCP/IP, portas seriais, etc).
 Memória mapeada em disco (memory map ou file mapping)
 multithreading e sincronização entre fios de processamento, inclusive pro-
cessamento concorrente (tópico coberto apenas a partir de C++11);
 acesso a banco de dados;
 programação gráfica(games, simuladores industriais ou para treinamento, etc);
 interface gráfica de usuário;
 aplicações web.

1.9 • Conclusão
Procuramos nesta introdução dar uma idéia geral dos principais pontos de interesse e
dos principais benefícios que você pode esperar em um curso de linguagem C++.
Para concluir, vamos sintetizar aqui o que consideramos mais importante no que foi
acima exposto.

1.9.1 ▪ Linguagem padronizada


Como C, C++ é também uma linguagem padronizada. Isto permitirá que muito do
seu código seja exatamente o mesmo nas suas aplicações para diferentes sistemas ope-
racionais, como Windows, Windows CE, Unix/Linux, MAC, Symbian, Palm OS, etc.
Naturalmente há diferenças relativas aos sistemas operacionais (particularmente no que
diz respeito à interface gráfica e processamento multitarefa, além dos demais tópicos
que já citamos).
Mas, como vimos, há hoje bibliotecas já prontas, open source, que permitirão escrever
um único código para todas essas plataformas, mesmo no que se refere aos tópicos es-
pecíficos de plataforma. Além disso, há uma série de coisas em que as diferenças entre
sistemas operacionais não interferem em absolutamente nada. Por exemplo, o código
escrito para validar um CPF/CNPJ ou para transformar um valor em texto (valor por
extenso) é o mesmo em DOS, Windows, Linux, etc…
E o que garante isso é justamente a padronização da linguagem.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.9 • Conclusão • C++ • AGIT informática • 37

1.9.2 ▪ Linguagem multi-paradigma


Devido ao sucesso alcançado, C++ já tem hoje seus descendentes, como é o caso de
JAVA e de C# (C Sharp) - embora estas sejam linguagens voltadas para plataformas
específicas (plataformas Java e .Net, respectivamente).
Mas C++ é mais ampla que seus descendentes... Justamente porque é híbrida e suporta
mais do que uma técnica de programação.
Boa parte do sucesso de C++ está na combinação de regras claras e expressivas com
flexibilidade. Você não encontrará em C ou C++ muitas restrições arbitrárias; pelo
contrário, na grande maioria das situações, as regras se combinam e se reaproveitam de
modo lógico.
Uma vez entendidos os princípios básicos e aplicada sua lógica, torna-se fácil entender
as implicações com reduzido espaço para as surpresas e exceções.
E é por isso que C alcançou tanto sucesso, tornando-se uma linguagem utilizada para
praticamente todo e qualquer tipo de aplicação: de sistemas operacionais a editores de
imagem; de aplicativos comerciais que exigem alta performance a sistemas de tempo
real.
De tal modo, que todos ou quase todos os programas de propósito geral consagrados
(esses mesmos que todos nós utilizamos em nosso dia a dia) foram escritos em C; e os
mais recentes em C++.
E, em coerência com sua adesão à flexibilidade, a grande vantagem de C++ é suportar
diversas técnicas de programação, como é o caso de orientação a objetos e programa-
ção genérica.
Além disso, também não retira nas mãos do programador os recursos necessários para
enfrentar ambientes mais “hostis”.
Isto é, em dispositivos com menor capacidade de processamento ou memória, o pro-
gramador poderá trabalhar mais próximo do nível da máquina (o popular “baixo
nível”) de modo a compensar os problemas de performance e/ou escassez de de memó-
ria.
 Isto significa que C++ é uma linguagem de propósito geral, voltada para
a programação de computadores e não apenas para uma determinada pla-
taforma, ou para um determinado tipo de aplicação.
Por isso, suporta múltiplos paradigmas de programação, sendo conhecida
como uma linguagem “multi-paradigma”.

C++ foi concebida, deliberadamente, para admitir qualquer estilo de programação.


Podemos utilizar exclusivamente uma determinada técnica (como orientação a obje-
tos) ou não.
Pois a linguagem não faz restrições ao programador. É ele quem decide o que vai usar,
em função das necessidades de cada projeto específico.
Portanto, enquanto JAVA, por exemplo, é uma linguagem puramente orientada a obje-
to, já C++, no que diz respeito às técnicas de programação, não é uma linguagem pura
e sim híbrida, isto é, uma linguagem que suporta múltiplos paradigmas de programa-
ção - ou “multi-paradigma”, como é costume dizer.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


38 • AGIT informática • C++ • • Capítulo 1 ▪ Introdução: a Linguagem C++

 Dito de outro modo, C++ não protege você de você mesmo... (embora
exija que você assuma explicitamente os riscos que pretende correr).

C++ oferece ao programador todos os recursos para que ele possa enfrentar qualquer
situação possível em programação de computadores.

 Inclusive, recursos para que você possa se proteger de si mesmo, se


esse for o caso...

1.10 • Ferramentas usadas no curso


Durante o curso, teremos disponíveis dois sistemas operacionais:
 Windows.
 Linux.
E dois compiladores:
 Microsoft (apenas em Windows).
 gcc (em Windows e Linux).
Inicialmente, no primeiro exercício, será mostrado como utilizar os compiladores dire-
tamente na linha de comando. Para pequenos projetos, com um único arquivo fonte,
isso é muito simples.
Mas isso muda de figura se passamos a ter mais arquivos fonte. Além disso, trabalhan-
do na linha de comando, teremos que usar um editor de textos qualquer. E sempre que
alteramos o fonte precisamos não esquecer de salvá-lo antes de uma nova compilação.
É por isso que ambientes de desenvolvimento integrados, embora, em princípio, não
sejam indispensáveis, são uma ferramenta auxiliar de enorme importância para que o
programador obtenha ganhos de produtividade.

 Assim, após o primeiro exercício, os demais poderão ser desenvolvidos em


um ambiente integrado.
Ficará a critério de cada aluno escolher o ambiente que considere
mais prático ou mais completo.
Inclusive, se preferir, poderá continuar usando a linha de comando...

Nesta apostila, mostraremos o uso de dois ambientes: QtCreator (pode ser usado em
Windows, Unix/Linux e Mac) e Visual C++ (apenas Windows).
Mas, se você preferir, poderá usar o Eclipse - pois também estará disponível.
O instrutor poderá usar qualquer um deles. E os alunos também farão suas escolhas:
para os objetivos deste curso, não faz qualquer diferença qual o ambiente que você irá
usar. E, sobretudo, você não é obrigado a usar o mesmo que o instrutor.
s

 Do mesmo modo, você também pode escolher se prefere trabalhar em


Windows, Mac ou em Linux: isso é totalmente indiferente para este curso.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


1.11 • Questões para revisão do Capítulo 1 • C++ • AGIT informática • 39

1.11 • Questões para revisão do Capítulo 1


Responda às questões abaixo, assinalando todas as respostas corretas (uma ou
mais). Em seguida, compare suas respostas com as respostas localizadas no Ane-
xo B-1, página 432. Caso não entenda, encaminhe as dúvidas ao instrutor.

Cap.1 - 1. Com relação à padronização das linguagens C e C++:


a.  C é uma linguagem padronizada.
b.  C++ é uma linguagem padronizada.
c.  C não é uma linguagem padronizada.
d.  C++ não é uma linguagem padronizada.
e.  A linguagem C já foi completamente padronizada, mas a padronização
de C++ ainda está em sua fase inicial.
f.  O padrão da linguagem C++ foi publicado em 1998, com uma atualiza-
ção mínima em 2003. Isso é definitivo. Nunca mais haverá um novo pa-
drão de C++.

Cap.1 - 2. Assinale as afirmações verdadeiras sobre C++:


a.  C++ introduziu apenas pequenas melhorias com relação ao C.
b.  C++ é baseada apenas em C.
c.  C++ é baseada em C, mas também herdou recursos decisivos de outras
linguagens, o que torna C++ muito diferente de C, em termos de técni-
cas de programação suportadas diretamente (ou nativamente).
d.  Além do C, outra linguagem que também serviu de inspiração para a cri-
ação do C++ foi JAVA.
e.  C++ suporta apenas uma única técnica de programação conhecida
como “orientação a objetos”.
f.  C++ é uma linguagem multi-paradigma, admitindo múltiplas técnicas de
programação.

Cap.1 - 3. Há uma série de áreas dependentes de plataforma, não cober-


tas pela biblioteca padrão de C++. Sendo assim, como atuar nes-
sas áreas?
a.  Para resolver esse problema, o programador deve desenvolver sempre
suas próprias bibliotecas, criando o código de infra-estrutura que cuide
dos detalhes dessas plataformas, e sirva de base para a criação de apli-
cações que possam ser compiladas em qualquer uma delas.
b.  Já existem boas bibliotecas que resolvem a maior parte desses proble-
mas, como, por exemplo, boost e Qt. Bastará escolher e usar a(s) bibli-
oteca(s) mais adequada(s) para resolver esse tipo de problema, poden-
do assim criar aplicações que rodam em diversas plataformas.
c.  Os compiladores são responsáveis por resolver esse tipo de problema.
d.  Os ambientes de desenvolvimento são responsáveis por resolver esse
tipo de problema.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


40 • AGIT informática • C++ •

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 2 ▪ Iniciando: o básico em C++ • C++ • AGIT informática • 41

• Capítulo 2
▪ Iniciando: o básico em C++

Neste capítulo, veremos o básico para a escrita de código nessas lingua-


gens.
Aqui estaremos quase sempre nos referindo a “C e C++”, pois o que será vis-
to neste capítulo aplica-se a essas duas linguagens.
Exceto no que diz respeito a recursos de biblioteca, onde sempre será dada
preferência à biblioteca C++.
Além do básico sobre C++, veremos os passos necessários para compila-
ção do código fonte, tanto em Windows como em Unix/Linux, bem como al-
gumas dicas para a análise e correção de erros de compilação.

2.1 • Tópicos de destaque neste capítulo ......................................42


2.2 • Exemplos preliminares .......................................................43
2.2.1 ▪ Um programa mínimo ......................................................................43
2.2.2 ▪ Um programa um pouco maior .........................................................45
2.2.3 ▪ Blocos de código e chaves...............................................................47
2.2.4 ▪ Gramática básica .............................................................................48
2.3 • Compilando e executando ...................................................49
2.3.1 ▪ Exemplo no Windows .......................................................................49
2.3.2 ▪ Exemplo em Unix/Linux ...................................................................54
2.4 • Analisando e corrigindo erros de compilação .........................57
2.5 • Usando Ambientes de Desenvolvimento ...............................59
2.5.1 ▪ Qt Creator ........................................................................................ 60
[Link] ▪ Criando um projeto .......................................................60
[Link] ▪ Adicionando arquivos ao projeto....................................64
[Link] ▪ Compilar e executar. .....................................................65
2.5.2 ▪ Visual C++ ........................................................................................65
[Link] ▪ Criando um projeto .......................................................65
[Link] ▪ Adicionando arquivos ao projeto....................................68
[Link] ▪ Compilar e executar. .....................................................69
2.6 • Conclusões sobre o código usado .......................................70
2.7 • Revisão do Capítulo 2 ........................................................70
2.7.1 ▪ Exercício ..........................................................................................70
2.7.2 ▪ Questões para revisão .....................................................................71

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


42 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

2.1 • Tópicos de destaque neste capítulo

 Usando alguns dos recursos básicos de programação em C e


C++.

 Funções.
 Chamadas de função.
 A função main: o ponto de entrada de uma aplicação (onde sua
execução é iniciada).
 Linhas de instrução e operações.
 Tomadas de decisão com if (...) ... else ...
 Recursos de impressão da biblioteca C++.
 Sintaxe para o uso de cadeias de caracteres.
 Gramática básica para a escrita de nomes de símbolos, funções,
linhas de instrução.

 Compilação.

 Utilizar os compiladores Microsoft e gcc para gerar os executá-


veis, tanto no Windows como no Linux.
 Executar os programas já compilados.
 Analisar e corrigir os erros de sintaxe do código fonte apontados
pelo compilador.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.2 • Exemplos preliminares • C++ • AGIT informática • 43

2.2 • Exemplos preliminares


Antes de entrar em detalhes, vejamos dois exemplos de escrita de código válida em C
e C++ (contudo, usaremos um recurso de impressão que só é valido em C++).

2.2.1 ▪ Um programa mínimo


#include <iostream>
int main()
{
std::cout << “imprime uma linha... e salta uma linha\n” ;
return 0 ;
}

Como resultado da imprime uma linha... e salta uma linha


execução, será impresso [ Linha em branco, ou nada. Depende da plataforma. ]
no monitor de vídeo: > [ próxima posição do cursor no monitor de vídeo ]

Analisando cada elemento do código acima:

 Acima temos uma função. Uma função é uma sub-rotina (que executa uma ta-
refa específica) e deve ter um nome, seguido por parênteses.
 E a função acima é denominada main. Esse é um nome especial e tem um
significado: é nessa função que o programa inicia sua execução:

o sistema operacional
(1) chama main (e a execução inicia)

executa

encerra
(2) retorna para

 Logo abaixo, no segundo exemplo, veremos o que faz o “int” antes do


nome da função [ "int main( )" ] e porque ela termina com “return 0”.

 Uma função agrupa instruções. Esse grupo de instruções deve estar entre
chaves de início e fim: { … }

int funcao_qualquer ( )
{  início da função "funcao_qualquer"
... instruções ... ;
}  fim da função "funcao_qualquer"

 Cada uma das instruções dever ser concluída com um ; (ponto e vírgula):

std::cout << "imprime" ;  ponto e vírgula

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


44 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

 Por enquanto, não se preocupe com o símbolo “std::”, que aparece acima como
um prefixo para o símbolo “cout”. Pense nesse prefixo como um nome de famí-
lia, ou seja: "cout" da família "std".
Do mesmo modo, por exemplo, poderíamos fazer: "Miranda::Basilio", ou seja:
"Basilio" da família "Miranda":

Família :: Nome Família :: Nome

Miranda :: Basilio std :: cout

 Mais a frente veremos o que significa exatamente esse prefixo (seção


7.5.9, página 247).

 O símbolo “cout” (abreviatura de “character output”) permite a impressão dos


elementos que apareçam após o operador “<<”.
 A expressão entre aspas após o "operador <<" (“imprime uma linha... e sal-
ta uma linha\n”) é uma cadeia de caracteres (ou string). Tudo o que o pro-
grama faz é justamente imprimir essa cadeia de caracteres.
 Existem dois caracteres especiais no final dessa cadeia: “\n”. Uma “\”, em
uma cadeia de caracteres, representa o início de uma seqüência escape.
O que significa que o caractere que vier após a “\” é um caractere de con-
trole, interpretado de determinado modo.
Nesse caso, um “n” após uma “\”, provoca uma quebra de linha
(“newline”). Logo, esse “\n” na verdade é convertido em um único caractere
(o caractere de quebra de linha).

saída  vai para  cadeia de caracteres  quebra de linha


std::cout << "imprime uma linha... "\n"
e salta uma linha"

 Se quiser saber mais sobre "seqüências escape" veja o "guia de con-


sulta rápida", na seção 6, página 494.

 Na primeira linha desse programa mínimo temos:


.

#include iostream

 A diretiva “#include” avisa ao compilador que, caso ele não saiba o que signi-
fica um determinado símbolo (como é o caso do “cout”), ele deve procurar
pela sua declaração em um determinado arquivo.
Neste exemplo, é o arquivo “iostream”, no qual está declarado o que é e
como pode ser usado o símbolo “cout”:

arquivo iostream - (requisitado pelo "#include iostream")

esse arquivo declara "cout" - ou seja: faz o mesmo para


diz o que é "cout" e como deve ser usado outros símbolos

 Até aqui descrevemos apenas o essencial sobre o nosso “programa mínimo”.


Mas, nos próximos dois capítulos tudo isso será melhor detalhado

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.2 • Exemplos preliminares • C++ • AGIT informática • 45

2.2.2 ▪ Um programa um pouco maior


#include <iostream>
int Maximo( int x , int y )
{
if ( x > y ) // se "x" é maior-que "y" ( 1 ) A função “main” inicia
return x ; // retorna um resultado a execução do programa.
else // do contrário ( 2 ) A função “main” chama
return y ; // retorna outro resultado a função “Maximo”, que
} executa suas instruções e,
ao final, volta ao ponto em
// 1 o programa inicia aqui: que foi chamada em “main”.
int main()
( 3 ) Finalmente, em “main”,
{
int a, b, c ;
o resultado de “Maximo” é
a = 10 ; // copia '10' para 'a' copiado para “c”.
b = 20 ; // copia '20' para 'b'
c= 3 Maximo ( a, b ) ; 2
std::cout << “O maior valor entre “ << a << “ e “ << b
<< “ = “ << c << “\n” ;
return 0 ;
}

Como resultado da O maior valor entre 10 e 20 = 20


execução, será impresso [ linha em branco ]
no monitor de vídeo: > [ próxima posição do cursor no monitor de vídeo ]

Além dos elementos que já descrevemos no exemplo anterior, neste exemplo


temos os seguintes acréscimos:

 Aparece aqui uma segunda função, denominada “Maximo” (cuja tarefa é encon-
trar o maior de dois valores) e que é chamada pela função “main”.
Isso significa que, quando ocorre a chamada, o processamento é desviado para
as instruções que estão dentro de “Maximo, e quando são concluídas, o proces-
samento retorna para o ponto em que ela foi chamada, em “main”.

 Na função main, na primeira linha vemos: int a, b , c ;


 Isso significa que pedimos uma reserva de memória para três números intei-
ros (é isso que faz o "int"), os quais serão acessados através dos nomes “a”,
“b” e “c”, respectivamente. Assim, podemos armazenar valores em cada uma
dessas posições (“a=10;” “b=20;”). E, depois, poderemos ler esses valores.
int a , b , c ;
a = 10 ; // copia '10' para 'a'
b = 20 ; // copia '20' para 'b'
c = Maximo ( a, b ) ; // copia o resultado de “Maximo” para “c”

Três áreas na memória para armazenar números inteiros (int)

"a" "b" "c"


10 20 será preenchida com o resultado da função "Maximo"

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


46 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

 Na função “Maximo” também temos pedidos de reserva de memória para


dois números inteiros: “x” e “y”.
Esses pedidos aparecem em uma posição especial: entre os parênteses
logo após o nome da função.
Isso significa que essas memórias serão usadas como parâmetros da função
(ou seja: valores de entrada, que a função necessita para executar seu traba-
lho):

Maximo ( int x , int y ) ;

"x" "y"

quando a função for chamada, 'x' receberá um valor. idem

 Quando “main” chama “Maximo” ela passa dois valores para satisfazer es-
ses parâmetros:

c = Maximo ( a , b) ;

 Isso significa que os valores de “a” e “b” serão lidos e copiados para “x” e “y”,
respectivamente.

c = Maximo ( a , b );

Maximo ( int x , int y );

 A função "Maximo" faz uma avaliação que conduz a uma tomada de decisão:

if ( x > y )

 Se “x for maior que “y” será retornada uma cópia do valor de “x”:

return x ;

 do contrário será retornada uma cópia do valor de “y”:

else
return y ;

 Isso explica porque aparece o “int” antes dos nomes das funções
“main” e “Maximo”:

int Maximo ( )

int main ( )

 esse “int”, nessa posição, indica que cada uma dessas funções retorna
um valor numérico inteiro, que representa o seu resultado.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.2 • Exemplos preliminares • C++ • AGIT informática • 47

 Após executar o "return", a função retorna para o ponto em que foi chamada em
main, entregando o seu resultado (valor de retorno). Nesse exemplo, o resulta-
do (que é um "int") é copiado para "c" (que também é um "int"):

c =  Maximo ( a, b ) ;

 Antes de "int main( )" vemos: "// o programa inicia aqui:". Isso é um comentá-
rio que é iniciado com o símbolo "//" e encerrado na quebra de linha ao final
da sua linha de texto.
Comentários são desprezados pelo compilador, mas são úteis para o programa-
dor que um dia precisará reler esse código - para corrigir erros ou melhorá-lo.

Inicia Comentários Encerra

// o programa inicia aqui [ quebra de linha do texto ]

// copia '10' para 'a' [ idem ]

// copia '20' para 'b' [ idem ]


etc...

2.2.3 ▪ Blocos de código e chaves.


Uma função contem um bloco de código (uma ou mais instruções) iniciado e encerrado
com chaves: { ... }. Conforme já foi exposto no primeiro exemplo, em uma função, as
chaves nunca podem ser omitidas.
Além disso, dentro de uma função, podemos ter blocos internos para agrupar instru-
ções que executem uma parte da sua tarefa.
Por exemplo: no caso do if podemos ter uma ou mais instruções a executar, conforme a
condição seja avaliada como verdadeira ou falsa. Nesse caso, as chaves irão agrupar as
instruções a executar para cada uma dessas duas alternativas. Mas, se houver apenas
uma instrução, as chaves poderão ser omitidas.

Várias instruções: Uma única instrução:


usar chaves. as chaves podem ser omitidas.

if (x > y) if (x > y)
{ // uma única instrução
// uma ou mais instruções
} else
else // uma única instrução
{
// uma ou mais instruções
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


48 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

2.2.4 ▪ Gramática básica


Nos exemplos de código acima, usamos nomes para identificar memórias (por exem-
plo, "x" e "y") e escrevemos linhas de instrução. Para fazer isso foi necessário seguir as
determinações da linguagem que formam sua sintaxe e sua gramática.
Em conclusão, para escrever “nomes” (ou melhor, identificadores) de variáveis,
constantes, funções e símbolos em geral e também "linhas de instrução", devemos
seguir as seguintes regras básicas (há outras que veremos depois):

 Palavras reservadas: como qualquer linguagem de programação, C e C++ ofe-


recem um idioma, isto é um conjunto de palavras que identificam instruções e
operações.
 Por exemplo: nos exemplos acima usamos estas palavras reservadas: int, if,
return. Há uma tabela completa das palavras reservadas no "guia de con-
sulta rápida" (anexo C-7, página 494).
 Uma palavra reservada só pode ser usada para a finalidade que lhe foi atri-
buída pela linguagem.
 Nomes de símbolos em geral:
 Em C somente os 32 caracteres iniciais de um nome de símbolo são conside-
rados pelo compilador; em C++ não há limite.
 Devem começar com letra ou “_” (sublinhado). Os outros caracteres podem
ser letras, números ou “_”.
 Exemplos: a_1 , a1_ ou _a1 são nomes válidos.
Mas não: 1a_ ou 1_a
 Em C++, para não incorrermos em conflitos com nomes de símbolos usa-
dos pelos fabricantes de compiladores na implementação da biblioteca pa-
drão, temos uma recomendação geral: não usar o "_" no início de um
nome e também não usar um duplo "__" em qualquer posição.
Exemplos: _a1, ou a__1 devem ser evitados. Prefira a1_ ou a_1.
 Minúsculas e maiúsculas são tratadas de forma diferente (case sensitive).
 Não podem ter o mesmo nome de uma palavra reservada.
 Não podem ter o mesmo nome de uma função definida anteriormente.
 Não podem ter o mesmo nome de uma variável já definida no mesmo esco-
po. Veremos o que é um escopo mais adiante, na seção 5.8, página 156.
Por enquanto, basta entender que não podemos ter duas variáveis com o
mesmo nome no bloco de instruções de uma função, exceto se uma das de-
clarações ocorre em um bloco interno (o bloco de instruções de um if, por ex-
emplo).
 Funções e instruções:
 O identificador (nome) para uma função, além de obedecer às regras descri-
tas acima, deve ser acompanhado de parênteses.
 Qualquer bloco (inclusive uma função) é iniciado e encerrado com chaves,
exceto em controles de fluxo (como o if), onde podemos omitir as chaves se
houver uma única instrução associada ao controle.
 Uma linha de instrução é sempre encerrada com ; (ponto e vírgula).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.3 • Compilando e executando • C++ • AGIT informática • 49

2.3 • Compilando e executando


Vimos (na seção 1.6.1, página 30, acima) que o código escrito em linguagem C++ ou
linguagem C é um código fonte que precisa ser compilado, isto é, traduzido para lin-
guagem de máquina. Podemos colocar um compilador em execução diretamente na li-
nha de comando ou usando ambientes de desenvolvimento integrados. Falaremos
sobre ambientes ainda neste capítulo, mas vamos iniciar com a compilação diretamente
na linha de comando, para melhor entender como funcionam os compiladores.

Iniciando: compilação na linha de comando.


Para seguir esse caminho, em primeiro lugar precisamos escrever o código fonte, usan-
do um editor de textos que não insira no texto do código caracteres especiais de forma -
tação. Há vários editores desse tipo, como, por exemplo, o notepad ("bloco de notas")
em Windows, ou os editores vi, vim, gedit e kedit em Unix/Linux.

2.3.1 ▪ Exemplo no Windows


a. Inicialmente vamos editar o "programa mínimo" (que está seção 2.2.1, página 43,
acima) usando o "bloco de notas" do Windows,.

b. Agora é preciso salvar o arquivo, dando-lhe um nome.


c. Para isso vamos inicialmente criar um diretório onde todos os projetos do curso
sejam armazenados, para que depois sejam localizados facilmente.
Exemplo (para Windows): C:\cursoCPP (crie o novo diretório "cursoCPP").
d. Além disso, uma boa idéia é criar um diretório específico para cada um dos exer-
cícios ou projetos, de modo a não misturar os seus arquivos fontes (pois alguns
exercícios terão mais do que um arquivo fonte).
Então, dentro desse diretório-base, C:\cursoCPP, vamos criar o sub-diretório para
gravar os arquivos deste projeto.
Por exemplo: 00_funcoes.
Assim teremos o seguinte diretório de projeto: C:\cursoCPP\00_funcoes.
e. Voltando ao "bloco de notas", já podemos salvar o primeiro arquivo fonte do
"programa mínimo" no diretório de projeto criado acima:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


50 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

f. Uma vez salvo o arquivo, resta agora invocar o compilador. Como já vimos, há
vários compiladores para Windows que podemos usar.
Neste exemplo usaremos dois compiladores: microsoft e gcc.

f.1. Microsoft.
Para usar o compila-
dor Microsoft na li-
nha de comando são
necessários os se-
guintes passos:

f.1.1. Em primeiro lugar, você precisa instalar (versão 2005 ou superior):


 O Visual Studio da Microsoft em qualquer uma das edições comerciais.

 Ou a edição Express (gratuita) do Visual C++ .

f.1.2. Em segundo lugar, para compilar na linha de comando é preciso acrescentar


algumas variáveis de ambiente nas propriedades do "Meu Computador".
Mas isso pode ser feito de modo mais simples, definindo essas variáveis dire-
tamente no console (ou terminal) onde você invocará o compilador na linha
de comando.
 Para isso, podemos chamar um arquivo de lote que (geralmente) é instala-
do juntamente com o Visual C++: é o arquivo "[Link]".
 Em uma instalação típica, esse arquivo está localizado no diretório:

C:\Arquivos de programas\Microsoft Visual Studio 10.0\Common7\Tools


Onde "Microsoft Visual Studio 10.0" refere-se à versão 2010 do Visual
Studio: mas o número poderá ser "8", "9", etc., conforme a versão em uso.
Uma boa idéia é copiar esse arquivo para o diretório C:\Windows\Sys-
tem32.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.3 • Compilando e executando • C++ • AGIT informática • 51

Desse modo, ele estará sempre imediatamente visível para execução. Pois
sempre que um novo console for aberto para compilação, esse arquivo
em lote deverá ser executado novamente.
copy [Link] C:\Windows\System32\
 Agora, basta executar esse arquivo de lote, digitando na linha de comando
(e concluindo com <enter>): vsvars32

 Se a resposta foi "Setting environment for using Microsoft Visual Studio


2010 x86 tools" ou semelhante, então deve estar tudo pronto para que o
compilador possa ser chamado na linha de comando.

f.1.3. O terceiro passo é chamar o compilador.


Posicione-se no diretório C:\cursoCPP\00_funcoes (e o arquivo [Link],
que foi salvo acima, já deve estar aí). Então execute a seguinte linha:
cl /EHsc /Fe"test" [Link]

 Não se preocupe, por enquanto, com os detalhes. Na linha de chamada


ao compilador, basta perceber que:
 "cl" é o arquivo executável do compilador microsoft.

 "[Link]" é o arquivo de código fonte a ser compilado.

 "test" é o arquivo executável a ser gerado.

 Os outros detalhes serão vistos mais a frente.

 Se o compilador exibiu alguma mensagem de erro, será preciso analisar o


código fonte. Logo abaixo, após os exemplos de uso dos compiladores,
mostraremos alguns dos erros de compilação possíveis.

f.1.4. Finalmente, o último passo é executar o programa gerado. Basta invocar o


seu nome na linha de comando:
test
 A execução fez exata-
mente o que era es-
perado: foi impresso
"imprime uma linha...
e salta uma linha"; e
em seguida houve uma quebra de linha, com uma linha em branco.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


52 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

f.2. gcc. Para usar o compilador gcc em Windows, você precisa instalar o pacote
minGW (ou então o cygwin). Então, temos os seguintes passos:

f.2.1. Em primeiro lugar, você precisa instalar:


 O minGW (abreviatura de Minimalist GNU for Windows). Esse pacote ofe-
rece um porte de ferramentas GNU para Windows, como, por exemplo:
gcc, make, ar, etc.
Oferece também os headers das bibliotecas de C e C++, bem como da API
Win32, e, naturalmente, as próprias e respectivas bibliotecas.
Se você instalou algum ambiente de desenvolvimento como o QtCreator, o
Eclipse ou o Dev-cpp, o minGW já deverá estar instalado.
Do contrário, acesse [Link] para download e instalação.
f.2.2. Em segundo lugar, do mesmo modo que fizemos com o compilador Microsoft,
para compilar na linha de comando é preciso acrescentar algumas variáveis
de ambiente nas propriedades do "Meu Computador".
Mas, do mesmo modo, podemos simplesmente definir essas variáveis direta-
mente no console (ou terminal) onde você invocará o compilador na linha de
comando.
 Caso você tenha instalado o ambiente QtCreator, já existirá um arquivo
de lote que faz isso. Em uma instalação típica ele estará localizado aqui (o
path poderá variar conforme a versão de Qt):
C:\Qt\2010.02\bin\[Link] // talvez não seja "2010.02"...
Você também pode copiar o arquivo [Link] para o diretório
C:\windows\system32, para obter acesso direto e rápido.
 Caso não tenha instalado um ambiente (ou o ambiente não forneça um ar-
quivo de lote desse tipo), podemos criar um arquivo de lote que deve
conter as linhas exibidas abaixo. Use o "bloco de notas" para escrevê-lo.
Mas atenção para o path "C:/minGW": esse é o diretório padrão de instala-
ção do pacote minGW. Modifique-o, caso tenha usado outro diretório.
@echo off
SET PATH=C:/minGW/bin;%path%
@echo pronto para usar o minGW na linha de comando
 Agora salve o arquivo, nomeando-o. Por exemplo, use o nome
[Link]. Copie o arquivo para C:\windows\system32.
 Execute esse
arquivo de
lote, digitando
na linha de
comando:
mingwvars.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.3 • Compilando e executando • C++ • AGIT informática • 53

f.2.3. O terceiro passo é chamar o compilador.


Posicione-se no diretório C:\cursoCPP\00_funcoes e execute a seguinte li-
nha:
g++ [Link] -o test

 Detalhes serão vistos mais a frente. Por enquanto, basta perceber os se-
guintes elementos na linha de chamada ao compilador:
 "g++" é um atalho que chama o compilador gcc já devidamente posici-
onado para usar a biblioteca padrão de C++ (e não a biblioteca C). Se
chamássemos o gcc diretamente, precisaríamos passar argumentos
para incluir a biblioteca C++.
 "[Link]" é o arquivo de código fonte a ser compilado.

 "test" é o arquivo executável a ser gerado. O "-o" indica que o nome


que o segue é o nome do executável a gerar. E, como estamos em Win-
dows, o arquivo gerado será "[Link]".
 Acima, o compilador não exibiu qualquer mensagem, o que significa que
não localizou erros. Se, no seu teste, apareceram mensagens, provavel-
mente existem erros (ou avisos sobre procedimentos suspeitos). Daqui a
pouco mostraremos alguns dos erros de compilação possíveis.

f.2.4. Finalmente, o último passo é executar o programa gerado, invocando o seu


nome na linha de comando.
 Mas aqui temos um passo preliminar.

 Como, neste caso, usamos o pacote minGW, o programa a executar de-


pende de que uma biblioteca dinâmica esteja visível na execução:
– é o arquivo "[Link]".
 Ele deve estar no diretório "bin" no path em que o minGW foi instalado.
Por exemplo:
– "C:\minGW\bin";
– "C:\Qt\2010.02\qt\bin"
(altere o "2010.02", caso a versão de Qt seja outra),
– etc.
 copie o arquivo "[Link]" para o diretório

– "C:\windows\system32".
 Agora, já podemos chamar o executável:

test

 E novamente ocorreu
o esperado.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


54 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

2.3.2 ▪ Exemplo em Unix/Linux


a. Em Unix/Linux para editar o "programa mínimo", conforme já dissemos, podemos
usar os editores vi, vim, gedit, kedit ou outros, dependendo da versão ou "sabor"
do sistema operacional, bem como da distribuição instalada. Para simplificar, veja-
mos um exemplo com gedit.

b. Crie um diretório de projetos ("cursoCPP", por exemplo), e, dentro dele, um diretó-


rio específico para este exemplo ("00_funcoes", por exemplo), e finalmente salve o
arquivo atribuindo-lhe um nome ("[Link]", por exemplo).
Teremos então algo como:
/home/user/cursoCPP/00_funcoes/[Link]
onde "user" é o seu nome de usuário:

c. Uma vez salvo o arquivo, resta agora invocar o compilador gcc, através do ata-
lho g++. Para isso são necessários os seguintes passos:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.3 • Compilando e executando • C++ • AGIT informática • 55

c.1. Instalação. Normalmente, em uma instalação "desktop" típica do Linux, o gcc já


deve estar instalado, mas é possível que o g++ não esteja. Usando pacotes, ou
os recursos disponíveis para instalação, é preciso garantir que os dois estejam
instalados.
Por exemplo, no caso do g++, em uma distribuição Debian, bastaria fazer o se-
guinte:
apt-get install g++

Neste exemplo, o g++ já estava instalado. Caso não estivesse, além do g++ o
"apt-get" já instalaria também suas dependências, como é o caso da bibliote-
ca C++.

c.2. Compilar. Para chamar o g++, procedemos exatamente como na compilação em


Windows mostrada acima:
g++ [Link] -o test

 onde "[Link]" é o arquivo fonte a ser compilado e "test" é o nome


completo do arquivo executável (sem o ".exe").

c.3. Finalmente, resta executar, chamando o programa na linha de comando:


./test

 A execução fez exatamente o que era esperado nessa plataforma: foi im-
presso "imprime uma linha... e salta uma linha"; e em seguida houve uma
quebra de linha, mas não houve a impressão de uma linha em branco
(como no Windows). A próxima linha que foi impressa já é a linha do
"prompt" do sistema.
 Isso não significa que o "\n" tenha perdido sua função. O que ocorre é
que após a execução de um programa, nesta plataforma, o terminal não
gera sua própria quebra de linha. Mas, se eliminarmos o "\n", veremos
que a impressão ficará confusa, juntando, em uma mesma linha, a linha
impressa e a próxima exibição do "prompt".
std::cout << “imprime uma linha... e salta uma linha” ;
// sem o "\n"...
Teremos o seguinte resultado, que não é o desejado:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


56 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

Compilando o segundo exemplo


a. No arquivo "[Link]", acima da função "main", acrescente a função "Maximo",
digitando o código que está no segundo exemplo (seção 2.2.2, página 45, acima) e
que reproduzimos abaixo.
Em seguida altere a função "main". O código completo deverá ficar assim:

Número Código (digite apenas o código - os números de linhas estão aí


da linha apenas para referenciar as linhas com erro nos exemplos seguintes).
1 #include <iostream>
2 int Maximo( int x , int y )
3 {
4 if ( x > y ) // se "x" é maior-que "y"
5 return x ; // retorna um resultado
6 else // do contrário
7 return y ; // retorna outro resultado
8 }
9 // o programa inicia aqui:
10 int main()
11 {
12 int a, b, c ;
13 a = 10 ;
14 b = 20 ;
15 c = Maximo ( a, b ) ;
16 std::cout << “O maior valor entre “ << a << “ e “ << b
17 << “ = “ << c << “\n” ;
18 return 0 ;
19 }

b. Salve o arquivo mantendo o mesmo nome.


c. Compile novamente, chamando na linha de comando:
cl /EHsc /Fe"test" [Link] // compilador microsoft (Windows)
OU:
g++ [Link] -o test // compilador gcc,
// seja em Windows ou em Unix/Linux

 O compilador emitiu mensagens de erro? Falaremos de erros de compi-


lação logo abaixo, na próxima seção.

d. Se não ocorreram erros, execute, chamando na linha de comando:


test // Windows.
OU:
./test // Unix/Linux.

 Resultado: as linhas abaixo devem ter sido impressas no monitor de vídeo:


O maior valor entre 10 e 20 = 10
[ linha em branco, ou nada - dependendo da plataforma ]
> [ próxima posição do cursor no monitor de vídeo ]

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.3 • Compilando e executando • C++ • AGIT informática • 57

2.4 • Analisando e corrigindo erros de compilação


Temos abaixo alguns erros muito comuns quanto à sintaxe e regras da linguagem.
a. Esquecemos de inserir um determinado "#include" necessário. Exemplo:
//  faltou o "#include <iostream>" (comente essa linha para testar)
int Maximo( int x , int y ) { ... }
int main() { ... std::cout ... } //  "cout" é declarada no arquivo iostream
 compilar com o compilador microsoft (em Windows):
cl /EHsc /Fe"test" [Link]
E o compilador exibe a seguinte mensagem de erro:

[Link](16) : error C2653: 'std' : is not a class or namespace


[Link](16) : error C2065: 'cout' : undeclared identifier
// e aparecem outras mensagens decorrentes das anteriores.

Em "[Link]", na linha 16, o compilador não reconheceu dois símbolos: o


"std" e o "cout" (identificador não declarado).
 compilar com o gcc (em Windows ou Unix/Linux) :
g++ [Link] -o test // usando o gcc,
E o compilador exibe a seguinte mensagem de erro:

[Link]: error: `cout' is not a member of `std'

Novamente, um erro em [Link], na linha 16. Neste caso a mensagem foi


mais sintética. O compilador parece ter reconhecido o símbolo "std", mas não
reconheceu "cout" (e o código afirma que "cout" pertence à "família std").

 Observar que, com mensagens diferentes, os dois compiladores estão dizen-


do a mesma coisa:
"Nesse código, existem símbolos que eu, compilador, desconheço".

E, quando o compilador diz isso, podemos ter duas situações:


 faltou um #include (esse é o caso deste exemplo);

 ou, em outras casos, um determinado símbolo foi escrito de modo diferente


de sua declaração inicial (é o que veremos no próximo item).

 Para localizar rapidamente qualquer erro siga os seguintes passos:

 Leia a mensagem de erro. Ela dá uma indicação sobre o tipo do erro.


 Observe o número da linha em que o compilador afirma que ocorreu o erro.
 Muitas vezes o erro está nessa própria linha.

 Em outros casos, o erro é indicado em uma linha porque faltou alguma coi-
sa anteriormente. E, analisando essa linha, veremos o que faltou acima dela.
 Esse é o caso do nosso exemplo: é indicada a linha 16, porque faltou uma
definição prévia do símbolo "cout", que é usado nessa linha.

b. Ao escrever determinado símbolo, usamos nomenclatura diferente da esperada.


REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional
58 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

Isto é: usamos caracteres diferentes para referenciar um símbolo. Inadvertidamen-


te, por exemplo, escrevemos "count" ao invés de "cout" ou então "Cout", com "C"
maiúsculo (minúsculas e maiúsculas são consideradas diferentes). Exemplo:
#include <iostream> // OK, agora não esquecemos disso...
// ....
std::count  << “O maior valor entre “ ...
OU:
std::Cout  << “O maior valor entre “ ...
Usando "count", após a compilação teremos os seguintes erros:
 compilador microsoft:

[Link](16) : error C2039: 'count' : is not a member of 'std'


[Link](16) : error C2065: 'count' : undeclared identifier

 compilador gcc:

[Link]: error: invalid operands of types <unknown type> ...

Os dois compiladores acusam um erro em [Link], na linha 16. E o erro


está exatamente nessa linha.
As mensagens são diferentes, mas o sentido é o mesmo.
 O compilador microsoft diz: "'count' : is not a member of 'std'"; e tam-
bém: "'count' : undeclared identifier".
 O gcc diz: "invalid operands of types <unknown type>".

Dizer que um identificador não foi declarado ou que há uma operação envol-
vendo um operando de tipo desconhecido ("unknown type"), no fim das con-
tas revela o mesmo: estamos usando um símbolo inesperado, que o compila-
dor simplesmente não conhece.
E, usando "Cout", após a compilação teremos os mesmos erros apontados acima.
Ou seja, a situação é a mesma: tanto "count" como "Cout" são desconhecidos
pelo compilador.

c. Esquecemos do ; (ponto e vírgula) para encerrar uma instrução:


#include <iostream>
// ....
std::cout << “O maior valor entre “ << a << “ e “ << b
<< “ = “ << c << “\n” 
// Na linha acima faltou o ponto e vírgula para encerrar a instrução...
 compilador microsoft:

[Link](18) : error C2143: syntax error : missing ';' before 'return'

 compilador gcc:

[Link]: error: expected `;' before "return"

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.4 • Analisando e corrigindo erros de compilação • C++ • AGIT informática • 59

Os dois compiladores acusam um erro em [Link], na linha 18. Mas o erro


está na linha acima (linha 17).
As mensagens são ligeiramente diferentes, mas o sentido é o mesmo:
falta um ";" (ponto e vírgula) antes (before) da linha 18 ("return 0;").
E realmente, antes de "return 0; ", ou seja, na linha 17, temos um problema:

<< “ = ” << c << “\n”

Nessa linha, falta um ponto e vírgula. Portanto, a mensagem é clara: falta algu-
ma coisa antes da linha que o compilador estava avaliando.

d. Ao abrir ou fechar uma cadeia de caracteres esquecemos das aspas duplas:

#include <iostream>
// ....
std::cout << “O maior valor entre “ << a << “ e “ << b
<< “ = “ << c << “\n  ;
// Desta vez, na linha acima não esquecemos do ponto e vírgula,
// mas esquecemos (ou eliminamos inadvertidamente)
// as aspas duplas de fechamento da cadeia de caracteres.
 compilador microsoft:

[Link](17) : error C2001: newline in constant


// e aparecem outras mensagens decorrentes da anterior.

 compilador gcc:

[Link]: error: missing terminating " character


// e aparecem outras mensagens decorrentes da anterior.

Os dois compiladores acusam um erro em [Link], na linha 17. E o erro


está exatamente nessa linha.
As mensagens, na aparência, são bem diferentes. Mas o sentido é o mesmo:
 o compilador microsoft diz que há uma "newline" (uma quebra de linha)
no meio da "constante", que no caso é a cadeia de caracteres ("\n), a qual
não está terminada. Isso significa que antes que a cadeia fosse fechada
com aspas duplas, apareceu uma quebra de linha inesperada.
 o compilador gcc diz (de modo mais claro) justamente que as aspas duplas
estão ausentes na posição de fechamento da cadeia de caracteres: miss-
ing terminating " character. Traduzindo: está faltando o caractere de
terminação " (as aspas duplas).

2.5 • Usando Ambientes de Desenvolvimento


A compilação na linha de comando, conforme vimos acima, é bastante simples. Se
tudo estiver instalado corretamente, bastará executar uma dessas duas linhas:
cl /EHsc /Fe"aplicacao" [Link] // compilador microsoft - em Windows
g++ [Link] -o aplicacao // compilador gcc - em Windows ou Unix/Linux

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


60 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

Contudo, essa simplicidade deixa de existir quando temos mais que um arquivo fonte a
compilar, ou, ainda, bibliotecas adicionais que devem ser incorporadas à geração do
executável.
Nesse caso, essas linhas tornam-se excessivamente longas, tornando improdutiva a ta-
refa de simplesmente chamar um compilador.
Além disso, mesmo com um único arquivo fonte, sempre que alteramos alguma coi-
sa no código (usando o notepad, o gedit, ou qualquer outro editor), é preciso lembrar
de salvar as alterações, ou do contrário a próxima chamada ao compilador irá compi-
lar o arquivo existente (anterior à alteração, a qual, por enquanto, está apenas na me -
mória do editor, e não no arquivo em disco).
Por isso o caminho mais conveniente e produtivo é usar ambientes de desenvolvimen-
to integrados, onde o editor de textos e as chamadas ao compilador estão em um mes-
mo lugar.
Vejamos agora exemplos de uso de dois ambientes: QtCreator(este pode ser usado em
Windows, Unix/Linux e Mac) e Visual C++ (apenas Windows).
A partir daqui, você poderá usar qualquer um deles, em qualquer SO suportado.
Ou se preferir, pode continuar usando a linha de comando...
O Qt Creator é o ambiente de nossa preferência pois pode ser usado em várias plata-
formas e não exige grandes quantidades de memória para sua utilização (é o mais
"leve"), tendo todas as funcionalidades importantes que esperamos de um ambiente in-
tegrado.
Contudo, é possível que essa não seja a melhor escolha para você. Por exemplo, se o
SO que você prefere é o Windows e se, além disso, você prefere usar o compilador mi-
crosoft (ao invés do gcc), então nesse caso o ambiente mais adequado é o Visual C++.
Observar que um ambiente não substitui o compilador. Pelo contrário, uma de suas ta-
refas é justamente chamar o compilador, o que é feito quando pressionamos um deter-
minado botão ou opção de menu destinados a fazer essa chamada.
O QtCreator e o Eclipse, tanto no Unix/Linux como no Windows, por default, chamam
o compilador gcc. O Visual C++, naturalmente, chama o compilador microsoft.

2.5.1 ▪ Qt Creator

[Link] ▪ Criando um projeto


Todos os ambientes trabalham com a idéia de "projeto". Um "projeto" é apenas um ar-
quivo que reúne informações sobre o tipo de aplicação a ser gerado (se ela será um
executável ou uma biblioteca, por exemplo), e mantém uma lista de todos os arquivos
de código fonte necessários para gerar a aplicação, de modo que eles sejam incluídos
na chamada ao compilador que é feita com recursos do próprio ambiente.
Cada ambiente, tem suas próprias regras sobre como o arquivo de projeto é escrito e
nomeado.
No Qt Creator, por exemplo, esse é um arquivo cujo nome tem a extensão ".pro" e nes-
se arquivo são usadas algumas variáveis específicas do próprio ambiente, as quais vi -
sam a identificar as características daquele projeto.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.5 • Usando Ambientes de Desenvolvimento • C++ • AGIT informática • 61

De modo geral, não é preciso ter preocupações especiais com o arquivo de projeto. O
que nos interessa são os arquivos de código fonte.
Depois, se for o caso, podemos criar projetos em outros ambientes e simplesmente ane-
xar esses arquivos. Pois criar projetos, na grande maioria dos casos, é uma tarefa muito
simples e que é levada a cabo com alguns movimentos e cliques de mouse.
Assim, o próximo passo é criar um projeto para poder trabalhar com o exemplo que vi-
mos acima. Como já dissemos na introdução, o QtCreator é especializado em trabalhar
com a biblioteca Qt, mas pode ser usado para projetos que usam apenas a linguagem
C++ e a biblioteca padrão.

a. Utilize a opção de menu File, e, em


seguida, a opção New File or Project.

b. Será aberta a janela abaixo. Nessa janela temos uma árvore onde cada nó repre-
senta um tipo de recurso que pode ser criado. O que nos interessa aqui é o nó Pro-
jects. E, abaixo dele, temos diversas opções para diferentes tipos de projeto.

c. Para criar um projeto de aplicação executável sem usar Qt, as duas alternativas
são as opções Empty Qt4 Project e Qt4Console Application.

A que mais se aplica é a primeira: Empty


Qt4 Project (um projeto vazio).
Contudo, ela exigirá que saibamos utili-
zar duas variáveis do ambiente.
Vamos começar testando esta alternativa
(mais a frente, veremos a segunda):

(1) Selecione o tipo do projeto.


(2) Prossiga para a próxima etapa.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


62 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

d. Próximos passos:
(1) Informar o nome
do projeto.
(2) O diretório em
que será criado:
No Windows pode ser:
C:\cursoCPP.
No Unix/Linux pode ser:
/home/user/cursoCPP.
(3) Prosseguir para a
próxima etapa.
Caso ainda não exista, será criado um diretório com o nome do projeto, abaixo do di-
retório base: C:\cursoCPP\00_funcoes ou /home/user/cursoCPP/00_funcoes.
E, caso já exista, será usado o diretório existente.
A mensagem "The project already exists" indica na verdade que já existe um diretório
<...>/cursoCPP/00_funcoes. Neste caso, podemos desprezá-la.
e. E atingimos a última janela.
Ela apenas informa que será cri-
ado um arquivo de projeto, com o
nome 00_funcoes.pro, localiza-
do no diretório do projeto.
Aqui, basta pressionar o botão
Finish.

f. Agora, voltamos à área de trabalho.

Para adicionar e editar arquivos, temos


um navegador de projetos.
É bem possível que ele já esteja visível.
Mas, caso não esteja, basta:
(1) Garantir que o modo de trabalho seja
Edit, na barra vertical à esquerda
da àrea de trabalho.
(2) Pressionar o botão Show Sidebar,
localizado na parte inferior da área
de trabalho.
Esse botão exibe/oculta o navegador
de projetos (a sidebar).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.5 • Usando Ambientes de Desenvolvimento • C++ • AGIT informática • 63

g. O navegador de projetos, à esquerda, após a barra vertical, exibe a lista de arqui-


vos já anexados ao projeto. Neste caso, temos apenas o próprio "arquivo de pro-
jeto": 00_funcoes.pro. Com um duplo clique sobre o nome do arquivo, será aberto
o editor de textos, para que o arquivo possa ser editado:

h. E para que este projeto não utilize Qt e sim apenas a Linguagem C++ e sua biblio-
teca padrão, basta acrescentar uma linha nesse arquivo:
QT -= core gui
A linha acima modifica a variável "QT" do ambiente, eliminando (-=) os dois módulos
de biblioteca ("core" e "gui") que seriam incluídos por default. Desse modo, nada de Qt
será usado.
Além disso, como este programa (e os demais que serão criados neste curso) utiliza o
console, é necessário alterar uma segunda variável: a variável "CONFIG":
CONFIG += console
A linha acima modifica essa variável, acrescentando (+=) a configuração console.
Isso só é realmente necessário no Windows. Mas, para que possamos usar o projeto
em qualquer ambiente, é bom que essa linha já esteja lá.

O projeto em si está pronto. Salve e feche o arquivo "00_funcoes.pro", pois a partir


daqui não haverá mais nada a fazer com ele. Simplesmente, esqueça que ele existe.
Agora, bastará adicionar arquivos de código fonte, compilar e executar.
Temos uma segunda alternativa para criar projetos no QtCreator sem usar Qt, e sim
apenas a Linguagem C++ e sua biblioteca padrão.
 A vantagem dessa segunda alternativa é que não teremos nada a fazer no ar-
quivo de projeto (".pro"), e assim poderemos ignorar completamente as duas
variáveis do ambiente que usamos acima.
 A desvantagem é que, nessa alternativa, é criado um arquivo [Link]. Como
já temos um arquivo com esse nome, ele será sobrescrito.
 Portanto, essa alternativa pode ser usada sem problemas quando começamos
um projeto do zero dentro do QtCreator. Faremos isso em outros projetos.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


64 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

[Link] ▪ Adicionando arquivos ao projeto.


Para acrescentar arquivos temos duas opções:
 Add New: acrescenta um novo arquivo.

 Add Existing Files: acrescenta arquivos já existentes.

No nosso caso queremos acrescentar um arquivo já existente: o arquivo [Link]


que está no diretório <...>/cursoCPP/00_funcoes, conforme é mostrado na figura
abaixo.
No navegador de
projetos:
(1) Acione o botão
direito do mouse
sobre o nome do
projeto.
(2) Selecione a
opção do menu
suspenso
Add Existing
Files.

Adicione o arquivo
<...>/cursoCPP/
00_funcoes/
[Link].

Após adicionar [Link] ao projeto, vemos que o navegador de projetos já apresenta


um nó para ele. Assim, o arquivo estará sempre diretamente disponível para edição:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.5 • Usando Ambientes de Desenvolvimento • C++ • AGIT informática • 65

[Link] ▪ Compilar e executar.


a. Compilar.
Para isso podemos usar a op-
ção de menu Build.
Ou então o botão de atalho
que está ao final da barra de
ferramentas vertical, à esquer-
da da área de trabalho.

b. Finalmente: executar o projeto.


O ambiente não prevê a execução de projetos console a partir do próprio ambiente.
Isso só é possível para aplicações com interface gráfica.
Mas basta manter um terminal aberto e posicionado no diretório onde o executável foi
gerado e simplesmente chamar o executável na linha de comando.
No Linux/Unix, o executável estará no próprio diretório do projeto.
Já em Windows são criados dois diretórios, abaixo do diretório do projeto: debug e re-
lease. Nos anexos, veremos para que servem essas duas opções .
Por enquanto, se você não alterou nenhuma característica do projeto, basta saber que
o executável estará no diretório debug:

Então, basta chamar na linha


de comando:
00_funcoes // Windows.
ou:
./00_funcoes // Unix /
Linux.

2.5.2 ▪ Visual C++


[Link] ▪ Criando um projeto
Os passos para a criação de um projeto no Visual C++ são semelhantes aos que usa-
mos no QtCreator.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


66 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

a. Utilize a opção de menu File, e, em seguida, as opções New e Project.

b. Na próxima janela, para criar um projeto de aplicação executável usando apenas a


Linguagem C++ (sem nenhum recurso específico das plataformas Windows e
.NET), deve ser escolhida a opção Win32 Console Application.
(1) Em templates, selecione Win32.
(2) Selecione o tipo do projeto: Win32 Console Application.
(3) Informe o nome do projeto: 00_funcoes.
(4) Informe o diretório em que será criado: C:\cursoCPP.
(5) Prosseguir para apróxima etapa.

Caso ainda não exista, será criado um diretório com o nome do projeto, abaixo do di-
retório base: C:\cursoCPP\00_funcoes. Caso exista, será usado o diretório existente.
c. Na próxima janela, pressione o botão Next, para passar para a próxima etapa:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.5 • Usando Ambientes de Desenvolvimento • C++ • AGIT informática • 67

d. Finalmente, nas configurações do projeto, faça isto:


(1) Assinale a opção Empty project (projeto vazio).
(2) Pressione o botão Finish para finalizar a criação do projeto.

e. E estamos de volta à área de trabalho.


Já deverá estar visível o navegador de projetos (aqui chamado Solution Explorer).
Caso não esteja, basta acionar o menu View, opção Solution Explorer:

f. O Solution Explorer, permitirá adicionar arquivos ao projeto e navegar entre eles


para edição.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


68 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

[Link] ▪ Adicionando arquivos ao projeto.


Para acrescentar arquivos temos duas opções:
 Add / New Item: acrescenta um novo arquivo.

 Add / Existing Item: acrescenta arquivos já existentes.

No nosso caso queremos acrescentar um arquivo já existente: o arquivo [Link]


que está no diretório C:/cursoCPP/00_funcoes, conforme é mostrado na segunda fi-
gura abaixo. Então, no Solution Explorer:
(1) Acione o botão direito do mouse sobre o nome do projeto.
(2) Selecione a opção do menu suspenso Add, e a sub-opção Existing Item.

Adicione o arquivo
C:/cursoCPP/
00_funcoes/
[Link].

Após adicionar [Link] ao projeto, vemos que o Solution Explorer já apresenta um


nó para ele. Assim, o arquivo estará sempre diretamente disponível para edição:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.5 • Usando Ambientes de Desenvolvimento • C++ • AGIT informática • 69

[Link] ▪ Compilar e executar.


a. Compilar.
Para isso podemos usar a op-
ção de menu Build.

Ou então o botão de atalho Build que está na barra de ferra-


mentas Build:

Caso essa barra não esteja habilitada, bas-


ta clicar com o botão direito do mouse em
uma área vazia do menu. Aparecerá a lista
de barras de ferramentas disponíveis. Então,
habilite a barra Build:

b. Finalmente: executar o projeto.


Podemos executar o projeto diretamente na linha de comando. O executável estará
em um dos dois diretórios criados abaixo do diretório do projeto: debug e release.
Nos anexos, veremos para que servem essas duas opções. Se as configurações de-
fault não foram alteradas, ele estará no diretório debug.

Contudo, o Visual C++ prevê a execução de projetos console a partir do próprio ambi-
ente.
Pois, após a execução, irá impor
uma pausa à janela console, para
que seja possível que o seu resulta-
do seja observado.
Basta usar a opção Start Without
Debugging do menu Debug.

Caso essa opção não esteja disponível, habilite-a usando-se o menu Tools, opção
Settings.
Ao invés de "Basic Set-
tings", habilite
Expert Settings.
Isso irá acrescentar vári-
as opções ao ambiente,
inclusive a desejada
aqui.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


70 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

2.6 • Conclusões sobre o código usado


De tudo o que foi exposto acima, no que diz respeito à escrita do código, podemos
concluir que qualquer linguagem de programação deve oferecer uma sintaxe e uma
gramática para a escrita de instruções e tomadas de decisão - (que são os elementos
de uma linguagem, designados genericamente como "statements").
 Em síntese, há três elementos básicos indispensáveis para a escrita de
programas de computador:
memória;
operações;
fluxo de processamento.

 E, frisando, todos esses elementos devem ser regulados através de uma


sintaxe e uma gramática específicas da linguagem.
Os elementos básicos relacionados acima indicam o que precisamos es-
crever para criar programas.
A sintaxe e a gramática servem para disciplinar como tudo isso deve ser
escrito em uma determinada linguagem.

Até aqui vimos um breve resumo de alguns desses elementos e de algumas das regras
de sintaxe e gramática. Agora, será preciso entender com mais profundidade os concei-
tos aí envolvidos, bem como os detalhes mais importantes de cada um.
E há também mais outros elementos, que não apareceram nos exemplos mostrados aci-
ma.

2.7 • Revisão do Capítulo 2


2.7.1 ▪ Exercício
Tente resolver o exercício abaixo. Em seguida, compare sua solução com a que
está no Anexo B-2-1, página 434. Caso não entenda, fale com o instrutor.

Enunciado: com base no exemplo da função "Maximo", escreva a função "Minimo",


no arquivo <...>/cursoCPP/00_funcoes/[Link] (acima da função "main").

Resultado que deve ser impresso:

Passos para atingir o objetivo:


a. A função "Minimo" deve:
 Selecionar o menor de dois valores inteiros e retornar esse valor.
b. Em seguida, na função "main":
 Chame a função "Minimo", passando-lhe os valores apropriados.

 Imprima o resultado (retorno) devolvido pela função "Minimo".

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


2.7 • Revisão do Capítulo 2 • C++ • AGIT informática • 71

c. Compilar, usando ambientes de desenvolvimento, ou chamando, na linha de


comando, uma das duas linhas abaixo:
cl /EHsc /Fe"00_funcoes" [Link] // compilador microsoft - Windows
g++ [Link] -o 00_funcoes // compilador gcc - Windows ou Unix/Linux
 Se ocorreram erros de compilação, verifique e corrija as linhas de erro.
 Dúvidas? Reveja as dicas sobre identificação de erros (seção 2.4, página 57,

acima). E as regras básicas de gramática (seção 2.2.4, página 48, acima).


d. Execute o programa, chamando 00_funcoes (Windows) ou ./00_funcoes (Unix/Li-
nux) na linha de comando. Observe se funciona corretamente.

 Compare o que você fez com a solução da apostila: página 434.

2.7.2 ▪ Questões para revisão


Responda às questões abaixo, assinalando todas as respostas corretas (uma ou
mais). Em seguida, compare suas respostas com as respostas localizadas no Ane-
xo B-2-2, página 435. Caso não entenda, encaminhe as dúvidas ao instrutor.

Cap.2 - 1. A respeito de funções, assinale as afirmações corretas:


a.  Uma função é o mesmo que uma linha de instrução.
b.  Uma função é um bloco contendo um conjunto de linhas de instrução
podendo ou não ter um nome.
c.  Uma função é um bloco contendo um conjunto de linhas de instrução, e
obrigatoriamente deve ter um nome que a identifique.
d.  Nomes de função são seguidos obrigatoriamente por parênteses .
e.  O bloco de instruções de uma função é iniciado pelo próprio nome da
função e encerrado com a instrução return.
f.  O bloco de instruções de uma função é iniciado e encerrado com cha-
ves: { ... }
g.  Se uma função tiver apenas uma única linha de instrução, as chaves
podem ser omitidas.
h.  A expressão [ c = Maximo ( a , b) ; ] contem uma chamada de fun-
ção.

Cap.2 - 2. Sobre linhas de instrução, assinale as afirmações corretas:


a.  Uma linha de instrução é encerrada pela quebra de linha do editor de
textos.
b.  Uma linha de instrução é encerrada com um ponto e vírgula.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


72 • AGIT informática • C++ • • Capítulo 2 ▪ Iniciando: o básico em C++

Cap.2 - 3. Considerando o código abaixo, assinale as afirmações corretas:


int x , y ;
// ...
if ( x > y )
std::cout << "x é maior que y" << "\n";
std::cout << "agora vou encerrar\n" ;

a.  Apenas se "x" for maior que "y", será impresso x é maior que y e agora
vou encerrar.
b.  Nada será impresso.
c.  Apenas se "x" for maior que "y", será impresso x é maior que y.
Em qualquer caso, sempre será impresso agora vou encerrar.
d.  Sempre será impresso x é maior que y e agora vou encerrar.
Cap.2 - 4. Uma aplicação tem seu início (ou ponto de entrada) em:
a.  Nesta linha: [ #include <iostream> ]
b.  Na primeira função que esteja escrita em um arquivo que deve ter o
nome de [Link].
c.  Em uma função que deve ter o nome especial de main, não importando
o nome do arquivo em que esteja escrita. Essa função é obrigatória e
única em qualquer aplicação diretamente executável.

Cap.2 - 5. A respeito do compilador podemos afirmar que:


a.  O compilador serve apenas para analisar se o código foi escrito correta-
mente segundo as regras da linguagem.
b.  Ele analisa se o código foi escrito corretamente, de acordo com a lingua-
gem, e, em caso positivo, irá traduzi-lo para linguagem de máquina.
c.  O compilador indica erros de escrita, mas não fornece informações so-
bre esse erro.
d.  O compilador indica erros de escrita, emitindo uma mensagem sobre o
tipo do erro e a linha em que ele ocorreu. Devemos usar essas duas
informações para corrigir os erros mais rapidamente.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 3 ▪ Programação: princípios básicos • C++ • AGIT informática • 73

• Capítulo 3
▪ Programação: princípios básicos

Neste capítulo, teremos uma visão geral sobre operações, memória e fluxo
de processamento, conceituando o que vimos no capítulo anterior.
Além disso, veremos um novo recurso de fluxo de processamento: os laços,
para a repetição da execução de determinadas instruções.
Quanto à implementação em C++, além de uma associação mais direta dos
recursos da linguagem aos princípios e conceitos, teremos também novos
exemplos.

3.1 • Tópicos de destaque neste capítulo.......................................74


3.2 • Operações, memória e fluxo de processamento .....................75
3.2.1 ▪ Princípios .........................................................................................75
[Link] ▪ Operações ....................................................................75
[Link] ▪ Memória ........................................................................75
[Link] ▪ Fluxo de processamento ...............................................76
[Link] ▪ Conceitos de Verdadeiro e Falso ..................................77
3.2.2 ▪ Implementação em C++ ...................................................................79
[Link] ▪ Operações e operadores ..............................................79
[Link] ▪ Memória ........................................................................79
[Link] ▪ Escrevendo código .......................................................80
[Link] ▪ Verdadeiro e Falso ........................................................81
3.3 • Fluxo de processamento: laços ...........................................82
3.3.1 ▪ Princípios .........................................................................................82
3.3.2 ▪ Implementação em C++ ...................................................................84
[Link] ▪ O controle de laço "while"..............................................84
[Link] ▪ Compilando e executando ............................................88
[Link] ▪ Mais sobre laços; o laço for ..........................................92
3.4 • Conclusão ........................................................................95
3.5 • Revisão do Capítulo 3 ........................................................95
3.5.1 ▪ Exercício...........................................................................................95
3.5.2 ▪ Questões para revisão .....................................................................99

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


74 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

3.1 • Tópicos de destaque neste capítulo.

 Entendendo os princípios básicos de programação

 Linhas de instrução e operações.


 Memória: reservar áreas da memória para que possam ser aces-
sadas nas operações a executar.
 Fluxo de processamento: seqüência de instruções, tomadas de
decisão e desvios.
 Tomadas de decisão: conceitos de verdadeiro e falso.
 Fluxo de processamento: laços para a repetição da execução de
um determinado bloco de instruções.

 Implementação em C e C++: associando os exemplos anterio-


res aos conceitos expostos acima.

 Os recursos das linguagens C e C++ já vistos nos exemplos do ca-


pítulo anterior (funções, instruções, operações, reserva de memó-
ria) serão aqui relacionados aos princípios e conceitos básicos de
programação.

 Implementação em C e C++: novos elementos.

 conceito de verdadeiro e falso em C ou C++;


 implementação de laços em C ou C++;
 entrada de dados fornecidos pelo usuário.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.2 • Operações, memória e fluxo de processamento • C++ • AGIT informática • 75

3.2 • Operações, memória e fluxo de processamento


3.2.1 ▪ Princípios
[Link] ▪ Operações
A maior parte das instruções de um programa de computador é constituída de opera-
ções, identificadas por uma palavra ou um símbolo de operador. Alguns exemplos:
 cópia de valor (armazena um valor): atribuição(=)
 operações aritméticas: soma(+), subtração(-), multiplicação(*), divisão(/), etc.
 operações relacionais: maior-que(>), menor-que(<), igualdade(==), etc.
Exemplo (uma operação de venda):

a) armazene o valor 100 como “valor-do-produto”;


b) armazene o valor 10 como “valor-do-imposto”;
c) armazene o valor 110 como o “valor-pago”;
d) some o “valor-do-imposto” ao “valor-do-produto”;
e) armazene o resultado da soma em “valor-da-venda”;
f) compare: “valor-pago” é menor-que (<) “valor-da-venda” ?

Para atingir a operação completa (uma venda), temos aí três diferentes operações:
 armazenamento (operação de atribuição);
 soma (operação aritmética);
 comparação (operação relacional).
A primeira questão a considerar aí diz respeito à operação de armazenamento: arma-
zenar algo pressupõe que guardamos alguma coisa em algum lugar.
 E, em um programa de computador, o local onde armazenamos valores é a
memória.

A segunda questão a considerar no exemplo acima é que as instruções seguem uma


seqüência lógica (de “a” até “f”).
 Logo, temos um fluxo de processamento.

[Link] ▪ Memória
Qualquer linguagem de computador oferece algum meio para acessar a memória, vi-
sando a armazenar e a recuperar valores. As operações trabalham com valores e es-
tes devem existir em algum lugar da memória do computador.
 Para isso, uma linguagem precisa definir meios para reservar memória, de
modo que depois ela possa ser acessada.

E isso pode ser feito de muitas maneira, algumas melhores do que outras.
Por exemplo, há linguagens que providenciam a reserva de memória sempre que é
executada uma nova operação de armazenamento.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


76 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

Ou seja, o programador não precisa se preocupar com a reserva em si. Isso parece bom,
mas tem um preço em termos de segurança e eficiência (falaremos disso mais a frente).
E, como logo veremos, em C e C++ teremos algumas maneiras de reservar memória.
O programador deverá escolher a maneira mais adequada para cada caso.

[Link] ▪ Fluxo de processamento


No exemplo acima, vimos que há uma seqüência de instruções, ou seja, um fluxo de
processamento. E,. nesse exemplo, o fluxo é puramente seqüencial ou linear, pois to-
das as instruções são executadas ordenadamente de “a” até “f”.
Em um programa real temos às vezes longos trechos com instruções que devem ser
executadas desse modo puramente seqüencial. Mas temos também, em diversos mo-
mentos, a necessidade de interromper essa seqüência linear, estabelecendo desvios de
fluxo.
Retomando o próprio exemplo acima (onde paramos na operação “f”), podemos perce-
ber que falta alguma coisa:

f) compare: “valor-pago” é menor-que (<) “valor-da-venda” ?

Bem, e daí? O que fazer após essa comparação? Após a comparação, será preciso to-
mar decisões em função do resultado dessa operação.
Então, deveríamos acrescentar o seguinte:

f.1) caso o “valor-pago” seja menor que (<) o “valor-da-venda”


 emitir um alarme ;
f.2) do contrário  completar a venda ;
f.2.1) se o caminho seguido foi este último, então compare:
“valor-pago” é maior-que (>) “valor-da-venda” ?
f.2.2) caso seja,  devolver o troco .

 Isso significa que, em conseqüência das comparações, as instruções seguin-


tes serão executadas mediante desvios no fluxo.

Após a primeira comparação (“f”), temos dois caminhos: “emitir um alarme” ou


“completar a venda”. Ou uma coisa ou a outra.
 Assim, como apenas uma dessas duas operações será executada, a seqüên-
cia linear de instruções (sempre executadas uma após a outra) será quebrada
nesse ponto: ou “emitir um alarme”, ou “completar a venda”.
 Além disso, se o caminho a seguir for “completar a venda”, será preciso avaliar
ainda se o “valor-pago” está maior que o “valor-da-venda”.
 Se estiver, será preciso “devolver o troco”, o que implica em um novo des-
vio para que seja seguido este caminho (pois se os dois valores estiverem
iguais, não haverá nada a devolver e o caminho será outro).

Essa situação pode ser melhor visualizada no seguinte fluxograma:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.2 • Operações, memória e fluxo de processamento • C++ • AGIT informática • 77

 Temos, aí, em síntese, tanto instruções que são executadas seqüencialmente,


como tomadas de decisão seguidas por desvios.
Portanto, um fluxo de processamento é constituído por seqüências de ins-
truções, tomadas de decisão e desvios.

[Link] ▪ Conceitos de Verdadeiro e Falso


O princípio a extrair do fluxo acima e de suas conclusões é que:
 Uma tomada de decisão é baseada no conceito "verdadeiro X falso".
 Assim, tomar uma decisão significa avaliar se uma determinada condição é
verdadeira.
 Se for verdadeira, um determinado caminho (instrução ou grupo de instru-
ções) será executado.
 E se não for verdadeira (condição avaliada como falsa), esse caminho não
será executado.
 No lugar dele, será executado um outro caminho (outra instrução ou grupo
de instruções).
 Ou, simplesmente, a instrução ou grupo de instruções associados à avalia-
ção da condição como verdadeira serão pulados, e o processamento se-
guirá diretamente pelo caminho posterior, comum às duas alternativas.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


78 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

Exemplos:

a. Caminhos diferentes para as avaliações "verdadeira" e "falsa".


Processamento comum em seguida.

Condição

Falsa NÃO SIM Verdadeira

Instrução ou Instruções Instrução ou Instruções


a executar para condição a executar para condição
falsa verdadeira

Instruções para
prosseguimento do fluxo
(caminho comum)

b. Caminho diferente apenas para a avaliação "verdadeira".


Processamento comum em seguida.

Condição

Falsa NÃO SIM Verdadeira

Nada a fazer Instrução ou Instruções


aqui a executar para condição
verdadeira

Instruções para
prosseguimento do fluxo
(caminho comum)

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.2 • Operações, memória e fluxo de processamento • C++ • AGIT informática • 79

3.2.2 ▪ Implementação em C++


[Link] ▪ Operações e operadores
Precisaremos de áreas de memória para executar certas operações. Portanto são estas
que realizam a computação propriamente dita, fazendo as coisas funcionarem.
Em C ou C++ operações são identificadas por seus operadores, que são representados
através de símbolos ou de palavras reservadas.
Inicialmente, precisamos apenas de um pequeno conjunto dos operadores existentes
nessas linguagens:

Alguns operadores:

Operadores aritméticos:
- Subtração [ x - y ] ; subtrai 'y' de 'x'
+ Soma [ x + y ] ; soma 'x' a 'y'
* Multiplicação [ x * y ] ; multiplica 'x' por 'y'
/ Divisão [ x / y ] ; divide 'x' por 'y

Operadores relacionais (comparações):


> Maior que [ x > y ] ; 'x' é maior-que 'y' ?
>= Maior que ou igual [ x >= y ] ; 'x' é maior-ou-igual-a 'y' ?
< Menor que [ x < y ] ; 'x' é menor-que 'y' ?
<= Menor que ou igual [ x <= y ] ; 'x' é menor-ou-igual-a 'y' ?
== Igualdade (equal) Obs.: um duplo "="
[ x == y ] ; 'x' é igual-a 'y' ?
!= Desigual (not equal) [ x != y ] ; 'x' é diferente-de 'y' ?

Outros operadores:

= Atribuição Obs.: um único "="


[ x = y ] ; copia 'y' para 'x'
<f>() Os parênteses representam aí uma chamada à função “f”

Para a implementação dos fluxos exibidos acima, iremos usar alguns desses operado-
res. Mais a frente essa tabela irá crescer, conforme as requisições das novas situações e
exemplos.

[Link] ▪ Memória
Em exemplo do capítulo anterior (seção 2.2.2, página 45) tínhamos: int a , b , c ;

o que significa que solicitamos a reserva de memória para três valores inteiros.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


80 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

Se fossemos fazer o mesmo para a "operação de venda" exposta acima, teríamos algo
como:
int valor_produto ; "int": reserva memória
para um número inteiro...
int valor_imposto ; idem
int valor_pago ; idem
int valor_venda ; idem

Isso significa que, em C ou C++, um pedido de reserva de memória deve indicar para
qual finalidade essa memória será usada.

 Veremos adiante (capítulo 4, página 103) qual a razão de ser desse requisito.

Por enquanto, basta frisar que a finalidade prevista para o uso das quatro memórias cri-
adas na tabela acima é armazenar números inteiros. E isso significa que uma reserva
de memória deve indicar um tipo para os dados a armazenar.
Pode-se objetar, com razão, que números inteiros não parecem muito apropriados para
representar valores que, provavelmente, precisarão de uma parte fracionária (centavos).
Mas, como veremos no capítulo já referido, o "int" não é o único tipo de dados supor-
tado pela linguagem; poderemos, portanto, escolher algum outro tipo para valores des-
sa natureza.

Neste momento, o foco é: a reserva de áreas de memória em C ou C++, deve


ser feita com a indicação de um determinado tipo que as qualifique.

Com base nisso, poderemos entender o que será feito no exemplo de implementação da
"operação de venda" (abaixo).
Nesse exemplo, teremos a reserva de memórias tal como foi descrito aqui, bem como
as operações a realizar com os dados nelas armazenados e, finalmente, as decisões a to -
mar em função da lógica do problema - usando-se uma implementação típica em C ou
C++.

[Link] ▪ Escrevendo código


Vamos ver agora como escrever código em C ou C++, para implementar o primeiro
exemplo de fluxo exposto acima (a "operação de venda").

// (a) Reserve um novo local de memória, que acessarei usando o apelido


// “valor_produto”. Em seguida, armazene 100 nesse local de memória:
int valor_produto = 100 ; // "int": reserva para um número inteiro...

// (b) Reserve um novo local de memória, que acessarei usando o apelido


// “valor_imposto”. Em seguida, armazene 10 nesse local de memória:
int valor_imposto = 10 ; // "int": idem...

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.2 • Operações, memória e fluxo de processamento • C++ • AGIT informática • 81

// (c) Reserve um novo local de memória, que acessarei usando o apelido


// “valor_pago”. Em seguida, armazene 110 nesse local de memória:

int valor_pago = 110 ; // "int": idem...

// (d) Some os valores armazenados nas memórias


“valor_produto” e “valor_imposto”. E, em seguida:
// (e) Reserve um novo local de memória, que acessarei usando o apelido
// “valor_venda”. Em seguida, armazene aí o resultado da soma:

int valor_venda = valor_produto + valor_imposto ; // "int": idem.

// (f) Compare: o “valor_pago” é menor que (<) o “valor_venda” ?

if ( valor_pago < valor_venda )


{
// (f.1) ... escrever aqui o código para “emitir um alarme” ...
}
else
{
// (f.2) … escrever aqui o código para “completar a venda” ...

// (f.2.1) compare: “valor_pago” é maior que (>) “valor_venda” ?


if ( valor_pago > valor_venda )
{
// (f.2.2) … escrever aqui o código para “devolver o troco” ...
}
}

[Link] ▪ Verdadeiro e Falso


No exemplo de código acima temos duas avaliações de condição:
[ if (valor_pago < valor_venda) ] e [ if (valor_pago > valor_venda) ]. A avaliação de
uma condição tem um resultado que pode ser verdadeiro ou falso. Em função desse re -
sultado, um caminho de processamento é seguido.
Generalizando, em C e C++, os conceitos de verdadeiro e falso seguem um princípio
muito simples:

 Zero é falso.
Tudo o que é diferente de falso é verdadeiro.
Logo, tudo o que é diferente de zero é verdadeiro.

Desse modo, uma expressão:


 É falsa se a sua avaliação retorna zero.
 E é verdadeira se retorna qualquer valor diferente de zero.
Exemplos:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


82 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

a. Caminhos diferentes para as avaliações "verdadeira" e "falsa":

int x , y ;
// ...
if ( x == y ) // [se x for igual a y] retorna 1 se a condição for verdadeira
std::cout << "condição verdadeira: valor de 'x' é igual a 'y' \n" ;
else
std::cout << "condição falsa: valor de 'x' é diferente de 'y' \n" ;
std::cout << "o processamento comum prossegue aqui \n" ;

b. Caminho diferente apenas para a avaliação "verdadeira":

int x , y ;
// ...
if ( x == y ) // [se x for igual a y] retorna 1 se a condição for verdadeira
std::cout << "condição verdadeira: valor de 'x' é igual a 'y' \n" ;
// neste caso, não há nada de especial a fazer se a condição for falsa.
std::cout << "o processamento comum prossegue aqui \n" ;

Seguindo o princípio "falso se zero, verdadeiro se diferente de zero", podemos sim-


plesmente testar o valor de uma determinada memória (variável):

a. Caminhos diferentes para as avaliações "verdadeira" e "falsa":

int x ;
// ...
if ( x ) // retorna 1 (verdadeiro) se "x" estiver diferente de zero
std::cout << "verdadeiro: valor de 'x' é diferente de zero \n" ;
else
std::cout << "falso: valor de 'x' é zero \n" ;
std::cout << "o processamento comum prossegue aqui \n" ;

b. Caminho diferente apenas para a avaliação "verdadeira".

if ( x ) // retorna 1 (verdadeiro) se "x" estiver diferente de zero


std::cout << "verdadeiro: valor de 'x' é diferente de zero \n" ;
// neste caso, não há nada de especial a fazer se a condição for falsa.
std::cout << "o processamento comum prossegue aqui \n" ;

3.3 • Fluxo de processamento: laços


3.3.1 ▪ Princípios
O exemplo de fluxo mostrado acima não é muito útil para um programa do "mundo
real", pois ele só permite a execução de uma única operação de venda.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.3 • Fluxo de processamento: laços • C++ • AGIT informática • 83

Em um programa real, provavelmente essas instruções deveriam ser repetidas para que
diversas vendas fossem processadas.
Para isso temos um outro elemento básico de programação: os laços, que são estruturas
de controle que permitem a repetição de um grupo de instruções.
Além disso, não faria sentido prático repetir a operação sempre com os mesmos valo-
res. Então, devemos substituir a forma como eles são estabelecidos. Ao invés de valo-
res constantes para "valor_produto", "valor_imposto" e "valor_pago", devemos ter
uma entrada de dados (leitura de um arquivo, ou informações prestadas pelo usuário).
É óbvio que ainda não é o momento de escrever programas para o "mundo real". Mas
vamos nos aproximar um pouquinho de uma situação mais realista.
Desse modo, o fluxo exposto acima deveria ser alterado da forma exibida abai-
xo.
No novo fluxo, após uma operação completa de venda, essa operação será
repetida, para que seja processada uma próxima venda.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


84 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

3.3.2 ▪ Implementação em C++


No fluxo que vemos acima, ao final de uma operação completa de venda, o processa -
mento volta ao início.
Além disso, nos três primeiros passos (de "a" até "c"), ao invés de definirmos os valo-
res de "valor_produto", "valor_imposto" e "valor_pago" com as constantes 100, 10 e
110, pedimos ao usuário que os informe. Assim, cada venda terá valores diferentes.
Para a implementação em C ou C++ , já sabemos que é necessário que:
 O código esteja dentro de uma função - e deve existir uma função main, para
que o programa tenha um ponto de início.
Além disso, neste exemplo:
 Precisaremos usar um controlador de laço.
 E, também, instruções de entrada de dados.
Há três controladores de laço que podemos usar. Em princípio, qualquer um poderia
ser usado aqui. Mas se existem três é por uma razão: cada um deles se ajusta melhor a
uma determinada situação.
 Existem situações em que há uma progressão até que um determinado limite
seja atingido, e para isso teremos o laço for (veremos exemplos mais abaixo).
 Já para a situação que precisamos implementar aqui (o fluxo exposto acima),
não temos propriamente uma progressão, mas uma solicitação contínua de
dados ao usuário, os quais são processados e avaliados. Ou seja, o programa
continuará pedindo dados ao usuário, para que uma nova venda seja processa-
da. Caso ocorra um erro nessa entrada de dados, então o programa encerra.
Para esta situação temos outros dois laços. Um deles é o while.

[Link] ▪ O controle de laço "while"


O laço while analisa uma condição inicialmente e permite executar instruções repeti-
damente, voltando sempre à análise da condição, até que a condição torne-se falsa - e
então o laço é interrompido.
Em certos casos, em que a interrupção do laço é determinada por uma condição especi-
al que só irá ocorrer durante o próprio processamento das instruções a repetir, podemos
inclusive estabelecer um laço infinito - e sua interrupção será feita por um desvio.
Exemplos:
A condição pode mudar Condição imutável
a qualquer momento (laço infinito)
while (x > 1) // a condição pode ser while ( 1 ) // "1" é sempre verdadeiro...
{ // verdadeira ou falsa {
// Só executa se // Executa sempre.
// a condição é verdadeira. // Ocorrerá, em algum momento, uma
// O valor de 'x' pode mudar // situação especial (por exemplo, erro
// influindo na próxima avaliação // em uma entrada de dados), que
// da condição. // exigirá a interrupção do laço aqui.
} }

No exemplo à direita, a avaliação é sempre verdadeira, pois o que é avaliado é a cons-


tante 1 que é imutável (e 1 é verdadeiro, já que é diferente de zero).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.3 • Fluxo de processamento: laços • C++ • AGIT informática • 85

Então, a implementação do fluxo exibido acima (um laço com múltiplas operações
de venda) poderia ser feita do seguinte modo:
1) Resultados que devem ser impressos:

2) Código fonte:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


86 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

#include <iostream>
int main()
{
int valor_produto , valor_imposto, valor_venda, valor_pago ;
while ( 1 ) Executa o bloco abaixo enquanto a condição entre parênteses
{ for verdadeira. Neste caso, executará para sempre, pois "1" é
verdadeiro. E é uma constante - que não será alterada.
// (a), (b), (c): solicita ao usuário que informe os três valores:
std::cout << "\n-Digite numeros. Para interromper, "
<< "digite algo invalido (uma letra):\n\n" ;
std::cout << "-(a) Informe o valor do produto: ";
std::cin >> valor_produto ; // "cin" pegará uma entrada no teclado
// e a copiará para "valor_produto".
if ( std::[Link] () ) // se houve uma entrada incorreta...
break ; // interrompe o laço.
std::cout << "-(b) Informe o valor do imposto: ";
std::cin >> valor_imposto; // idem
if ( std::[Link] () ) // se houve uma entrada incorreta...
break ; // interrompe o laço.
std::cout << "-(c) Informe o valor pago: ";
std::cin >> valor_pago; // idem
if ( std::[Link] () ) // se houve uma entrada incorreta...
break ; // interrompe o laço.
std::cout << "\n"; // quebra linhas...
// (d) soma "valor_produto" e "valor_imposto";
// (e) armazena o resultado em "valor_venda"
valor_venda = valor_produto + valor_imposto ;
// (f) compara: "valor_pago" é menor-que "valor_venda"?
if ( valor_pago < valor_venda )
std::cout << "*** Valor pago: menor. Faltou = "
<< valor_venda - valor_pago << "\n";
else
{
std::cout << "*** Completar venda\n";
// Falta fazer: chamar aqui a função "CompletarVenda()" ...
if ( valor_pago > valor_venda )
{
std::cout << "*** Devolver o troco = "
<< valor_pago - valor_venda << "\n";
// Falta fazer: chamar aqui a função "DevolverTroco()" ...
}
}
} // fim do laço while.
std::cout << "\nfim de programa\n" ;
return 0;
} // fim da função main.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.3 • Fluxo de processamento: laços • C++ • AGIT informática • 87

Acima introduzimos novos elementos. um laço while com um desvio break e std::cin.
 while: é um controle de laços que é seguido por uma condição entre parênte-
ses.
 Se e enquanto a condição for verdadeira, as instruções associadas ao con-
trole de laço serão executadas repetidamente.
 Para associar instruções precisamos criar um bloco de código (iniciado e
encerrado com chaves), que indica quais são as instruções que devem ser
repetidas enquanto a condição for verdadeira. Exemplo:

int x;
// ....
while ( x > 0 )
{
// uma ou mais instruções a executar enquanto "x" for maior que 0
}

 Caso exista apenas uma instrução as chaves podem ser omitidas:

while ( x > 0 )
// uma única instrução a executar enquanto "x" for maior que 0

 Um laço while é interrompido quando a condição é falsa.

while ( x > 0 ) // Esta condição pode ser verdadeira ou falsa.


{
// 2  executar aqui apenas se "x" for maior que 0
} // fim do laço: volta para avaliar condição

3 Se "x" é (ou torna-se) menor ou igual a 0 (condição falsa) o fluxo


é transferido para a próxima instrução após o laço. 
// Próxima instrução após o laço:
std::cout << "continuando o processamento após um laço\n";

 Um laço pode ser interrompido com o statement de desvio break. Um


break provoca um salto para a próxima instrução após o final do laço:

while ( x > 0 )
{
int condicao_especial ;
// .................
if ( condicao_especial )
break ; // Interrompe... 
// ...
}
// Próxima instrução após o laço:
std::cout << "continuando o processamento após um laço\n";

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


88 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

 cin: é um recurso da biblioteca padrão de C++ que permite aguardar por uma
entrada de dados na entrada padrão do computador (normalmente o teclado).
 a entrada será armazenada nas memórias (variáveis) que seguem o operador
">>".
Exemplos:

int x, y, z;
std::cin >> x ; // aguarda por uma entrada de dados
// que será armazenada em "x"
std::cin >> x >> y >> z ; // aguarda por três entradas de dados
// que serão armazenadas, respectivamente, em "x", "y" e "z".

O que pode ser sintetizado assim:

entrada vai para  memória

std::cin >> x y z

[Link] ▪ Compilando e executando


Para fixar o que vimos acima, vamos criar um projeto e testar o código da "operação de
venda com laço" que acabamos de descrever. Para isso, siga os passos abaixo:
a. Criar um arquivo (usando ambientes de desenvolvimento ou um editor de tex-
tos).
 Dentro do diretório-base de exercícios ("cursoCPP", por exemplo), crie um novo
diretório para este exercício; por exemplo, "01_laco_while".
 Nesse diretório, crie o arquivo [Link].

 Desse modo teremos algo como:


"<...>/cursoCPP/01_laco_while/[Link]"
Se for usar um ambiente de desenvolvimento, lembre que teremos uma diferença em
relação ao exemplo de uso que vimos na seção 2.5, página 59, acima.
Naquele exemplo, criávamos um projeto e adicionávamos um arquivo já existente.
Neste, o arquivo será criado no próprio ambiente, como um novo arquivo..
Vejamos como fazer isso com o QtCreator e o Visual C++.

Usando o QtCreator
 Para criar o projeto bastaria seguir os mesmos passos que já vimos na seção
[Link], página 60, acima. Mas você pode aproveitar este exercício para experi-
mentar uma outra alternativa: criar um projeto sem usar Qt, usando como tipo de
projeto uma "Qt4 Console Application", ao invés de "Empty Qt4 Project".
Isso pode ser feito do seguinte modo:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.3 • Fluxo de processamento: laços • C++ • AGIT informática • 89

 Iniciar a criação do projeto.

 Escolher o tipo de projeto:


Tipo de projeto: Qt4 Console Application e
não Empty Qt4 Project.
A vantagem desta alternativa é que não será
preciso alterar o arquivo de projeto (".pro") e
assim não precisaremos tomar conhecimen-
to das variáveis do ambiente, ignorando
completamente o arquivo de projeto.

 Definir o nome do
projeto.
(1) Informar o nome do
projeto.
(2) O diretório do proje-
to.
(3) Prosseguir para a
próxima etapa.

 Na próxima janela, temos


uma relação dos módulos da
biblioteca Qt.
Por default, o módulo QtCore é
sempre utilizado em projetos
com Qt.

Mas não queremos aqui usar nada de Qt.

Então:
(1) Desmarcar o módulo
QtCore.
(2) Prosseguir para a próxima
etapa.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


90 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

 Na próxima janela, bastará confirmar a criação do projeto, pressionando o botão


Finish. Em seguida, voltamos à área de trabalho.

 E podemos ver no navegador de projetos que foi criado um arquivo, [Link], e


que ele já contem uma função main:

 Nessa função main criada pelo QtCreator há algumas referên-


cias a Qt, além dos argumentos da função main (de que não
precisaremos neste projeto, mas que veremos mais a frente).
Basta excluir tudo isso, deixando a função main vazia, confor-
me a figura ao lado.
Neste exemplo, isso nem será necessário pois iremos copiar um código já pronto para
esse arquivo. Então, neste exemplo, simplesmente poderíamos apagar tudo.

Caso fosse necessário adicionar mais um novo arquivo ao projeto, bastaria fazer:

Com o botão direito do


mouse, acionar o menu
suspenso e ao invés de
selecionar a opção
"Add Existing Files",
escolher "Add New".

Definir o tipo de arquivo: C++ Source File - ou, conforme o


caso, C++ Header File.

Informar o nome do arquivo


e o diretório onde deve ser
gravado.
E confirmar, para concluir a
criação do arquivo.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.3 • Fluxo de processamento: laços • C++ • AGIT informática • 91

Usando o Visual C++.

 Para criar o projeto basta seguir os mesmos passos que já


vimos na seção [Link], página 65, acima, não esquecendo de
definir que será um projeto vazio.
 Para adicionar arquivos, ao invés de Add / Existing Item, use Add / New Item:

 Selecione C++
File.
Informe o nome
do arquivo:
[Link].

Certifique-se de
que o diretório
onde o arquivo
será gravado
("Location")
está correto.

 Tenha você usado um dos ambientes acima ou um editor de textos, o importante é


que você tenha criado o arquivo [Link].

 Agora, copie o código da "operação de venda", exposto acima, para o arquivo


[Link]. Neste caso, não é necessário digitar. Simplesmente copie o código que
está na pasta da apostila, no arquivo:
"<...>/cursoCPP/apostila/01_laco_while/[Link]"
 Compilar o exercício, usando ambientes de desenvolvimento ou, posicionando-se
no diretório "<...>/cursoCPP/01_laco_while/" e chamando, na linha de comando,
uma das duas linhas abaixo:
cl /EHsc /Fe"01_laco_while" [Link] // compilador microsoft -Windows
g++ [Link] -o 01_laco_while // compilador gcc -Windows ou Unix/Linux
 Propositalmente, existem erros de sintaxe nesse código. Verifique e corrija.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


92 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

 Dúvidas? Reveja as dicas sobre identificação de erros (seção 2.4, página


57, acima). E as regras básicas de gramática (seção 2.2.4, página 48, aci-
ma).
 Execute o programa. Entre com diferentes valores para testar todos os caminhos
do fluxo, conforme os passos abaixo, comparando cada momento da execução
com o respectivo trecho no código fonte:
 valor pago igual ao valor total da venda;

 valor pago maior que o valor total da venda.

 valor pago menor que valor total da venda;

[Link] ▪ Mais sobre laços; o laço for


Conforme já vimos, sempre que temos instruções que precisam ser repetidas para
atingir um objetivo usamos um laço. E, em algumas situações, precisamos de um laço
capaz de estabelecer, com clareza, uma progressão.
Imagine que quiséssemos somar todos os números entre um número inicial e um fi-
nal. Por exemplo, somar todos os números entre 1 e 4. Caso não usássemos um laço,
teríamos uma escrita absurda que precisaria ser repetida para outros intervalos, indefi-
nidamente.
Por Exemplo:

int num = 1 ; // "num" armazena 1


num = num + 2 ; // agora "num" armazena 3
num = num + 3 ; // agora "num" armazena 6
num = num + 4 ; // agora "num" armazena 10

E, se ao invés de 4 o número final fosse 100? Nesse caso teríamos 100 linhas de ins-
trução ao invés das quatro acima.
Imagine também que quiséssemos calcular o fatorial de um número, por exemplo o
número 4. Teríamos o mesmo problema, pois este também é um cálculo repetitivo: o
número é multiplicado por ele menos um até atingir 1.
int num = 4 ; // "num" armazena 4
num = num * 3 ; // agora "num" armazena 12
num = num * 2 ; // agora "num" armazena 24
// num = num * 1; // desnecessário; valor permanecerá o mesmo.

Do mesmo modo que no exemplo da soma, se ao invés de 4 o número fosse 100, te-
ríamos 99 linhas de instrução ao invés das três acima.
Laços existem para resolver situações desse tipo, onde uma instrução precisa ser repe-
tida "n" vezes. E o laço “for” permitirá estabelecer essa repetição de modo progressi-
vo, até que um limite seja atingido. Nesses casos, os outros laços também poderiam fa-
zer o mesmo. Mas não com a mesma clareza.
O laço "for" repete as instruções a ele associadas, até que uma condição torne-se fal-
sa (e então o laço é interrompido), de acordo com a seguinte regra de sintaxe:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.3 • Fluxo de processamento: laços • C++ • AGIT informática • 93

for ( <inicio> ; <condição de continuidade> ; <progressão> )


{
// uma ou mais instruções a executar se a <condição> for verdadeira
}

 Se, em um laço for, precisamos que mais do que uma instrução seja executada
(um bloco de instruções), as instruções devem estar entre chaves, como acima.

 Já se existir apenas uma instrução, as chaves podem ser omitidas:


for ( <inicio> ; <condição de continuidade> ; <progressão> )
// única instrução a executar se a <condição> for verdadeira

Exemplo 1. Somando números entre um número inicial e um final:

 OBS.: neste caso poderíamos resolver esse problema com um simples


cálculo: a soma dos termos de uma progressão aritmética:

int inicial = 1 , final = 100 ; // Intervalo. Substituir valores para teste.


int razao = 1 ; // Distância entre números. Substituir o valor para teste.
int n = (final-inicial+razao) / razao ; // Total de números no intervalo.
int a_n = inicial + ( (n-1)*razao ) ; // Último termo.
int result = ( (inicial+a_n) * n) / 2 ; // Resultado da soma.

Contudo, queremos exercitar o laço "for". Então, a soma seria feita assim:

int inicial = 1 , final = 100 , razao = 1, result ;


// ( <inicio> ; <condição continuidade> ; <progressão> )
for ( result = 0 ; inicial <= final ; inicial = inicial + razao )
result = result + inicial ; // Instrução a executar se condição verdadeira
std::cout << "resultado da soma: " << result << "\n";
// Imprime: "resultado da soma: 5050"

Exemplo 2. Cálculo de fatorial:

int num = 10 , result ;


for ( result = 1 ; num > 1 ; num = num - 1 )
result = result * num ; // Instrução a executar se condição verdadeira
std::cout << "resultado para fatorial de 10 : " << result << "\n";
// Imprime: "resultado para fatorial de 10 : 3628800"

Como funciona:
 A <condição de continuidade> (o segundo segmento, após o primeiro ponto
e vírgula) é avaliada em todas as vezes. Assim, apenas quando “num” for
maior-que 1, a instrução associada ao laço for será executada repetidamente;
se essa condição for falsa, o laço será interrompido (ou nem iniciará).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


94 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

 O <inicio> (primeiro segmento, anterior ao primeiro ponto e vírgula, é executa-


do apenas na primeira vez. Em seguida, a condição é avaliada.
 A <progressão> (terceiro segmento, após o segundo ponto e vírgula) é execu-
tado após a primeira vez (ao invés de executar <inicio>); ou seja, é executado
na primeira repetição. Em seguida, a condição é avaliada.

Esse fluxo estabelecido pelo "for" pode ser visualizado assim:

Observar também os seguintes exemplos:

a. while: condição inicialmente falsa.

int num = 1;
while ( num < 1 ) // se 'num' menor-que 1
std::cout << "imprimindo\n";

Como resultado nada será impresso, pois condição é falsa já na primeira


avaliação da condição: "num<1" (sendo que o valor inicial de 'num' é 1).

b. for: condição inicialmente falsa.

int num ;
for ( num=1 ; num < 1 ; num=num+1 ) // se 'num' menor-que 1
std::cout << num << " , ";
std::cout << "\nvalor de 'num' depois do 'for' = " << num << "\n";

Como resultado da execução, será


impresso no monitor de vídeo: valor de 'num' depois do 'for' = 1
 Nenhum número foi impresso, pois a condição é falsa já na primeira avalia-
ção da condição: "num<1" (sendo que o valor inicial de 'num' é 1).
 E a mensagem final apenas exibe o valor inicial de "num": 1.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.3 • Fluxo de processamento: laços • C++ • AGIT informática • 95

c. for: condição inicialmente verdadeira.

int num ;
for ( num=1 ; num <= 10 ; num=num+1 ) // 'num' menor ou igual a 10
std::cout << num << " , ";
std::cout << "\nvalor de 'num' depois do 'for' = " << num << "\n";

Como resultado da execução, será 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10


impresso no monitor de vídeo: valor de 'num' depois do 'for' = 11
 Foram impressos 10 números (de 1 a 10), já que a condição permite a repeti-
ção da instrução de impressão se "num<=10".
 E, após a impressão do número 10, ocorreu a última execução da progres-
são ("num = num +1"), de modo que o valor de "num" tornou-se 11.
 Em seguida, houve uma última avaliação da condição: e o resultado foi fal-
so, pois agora 'num' continha 11. Logo é maior e não menor ou igual a 10.
 Assim, após o laço é impresso 11, último valor que foi atribuído a 'num'.

3.4 • Conclusão
Observando o código de todos exemplos já expostos, no capítulo anterior e neste , po-
demos inferir algumas das regras e diretivas comuns às linguagens C e C++, isto é, as
definições dessas linguagens em relação ao uso da memória, operações e fluxo de
processamento, inclusive laços.
Nos próximos dois capítulos, veremos com mais detalhes a definição e uso de cada um
desses elementos nessas linguagens:
 memória: capítulo 4, página 103.
 fluxo de processamento: capítulo 5, página 133.

3.5 • Revisão do Capítulo 3


3.5.1 ▪ Exercício.
Tente resolver o exercício abaixo. Em seguida, compare sua solução com a que
está no Anexo B-3-1, página 438. Caso não entenda, fale com o instrutor.

Enunciado: implemente a soma dos números entre um número inicial e um final,


com a distância "razão" entre cada número. Esses 3 valores devem ser informados. E
o usuário deverá decidir também se deseja um novo cálculo ou encerrar o programa.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


96 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

Resultado que deve ser impresso:

 Para a soma, use o laço for e não o cálculo de soma dos termos da progres-
são, pois queremos exercitar o laço.
 Os números "inicial", "final" e a distância entre cada número ("razão") deverão
ser informados pelo usuário (use std::cin).
 O cálculo deverá ser repetido para outros números, até que o usuário decida in-
terromper o processamento.

O objetivo a atingir pode ser visualizado no seguinte fluxo:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.5 • Revisão do Capítulo 3 • C++ • AGIT informática • 97

Modelo para
implementação.
Esse fluxo pode
ser implementado
de diversas ma-
neiras.
Por exemplo,
de acordo com o
modelo exibido
ao lado:

Para atingir o objetivo, siga estes passos:

a. Usando um editor ou um ambiente, crie um novo diretório para este exercício; por
exemplo, "02_lacos_while_for", dentro do diretório-base "cursoCPP".
b. Crie um arquivo e o salve, por exemplo, com o nome "[Link]".
Desse modo teremos algo como:
"<...>/cursoCPP/02_lacos_while_for/[Link]"
c. Comece a escrever o código na função main. O programa deve pedir ao usuário
que informe:
 Os números inicial e final para a soma.
 A razão (distância ou valor a somar para atingir o próximo número da série).
 Neste exercício, a razão não pode ser zero (analise o que foi informado).
 Caso o inicial seja maior que o final, você poderia prosseguir se a razão fosse
negativa. Mas seria preciso também alterar a condição do laço for. Para os
fins deste exercício, você pode seguir um caminho mais simples:
 se inicial for maior que o final, troque os valores de inicial e final (uma ope-
ração de swap) de modo que a progressão continue sendo positiva (progre-
dindo do menor para o maior).
 Para trocar o valor de dois números, você pode escrever o código ne-
cessário para um swap (troca), ou, simplesmente, usar:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


98 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

if ( inicial > final )


std::swap( inicial, final ) ;
 Uma vez garantido que o inicial é menor que o final, a razão deve ser um
número positivo (analise o que foi informado). Caso seja negativo, você
pode inverter o sinal assim:
if ( razao < 0 )
razao = -razao ;

d. Implemente o laço for: se tiver dificuldades, olhe o exemplo da soma de números


(página 93, acima).
e. Após o laço, imprima o resultado da soma.
f. Agora, volte ao início, para que o usuário continue informando novos valores para
cálculo.
 Dica: isso pode ser resolvido com um laço
while. Nesse caso, o laço for estará dentro do
laço while ("aninhado").
 Estabeleça uma condição de fim para o pro-
cessamento. Por exemplo, após concluir um
cálculo e imprimir o resultado, pergunte ao
usuário "deseja continuar?".
 Tente usar o while sem usar o break para in-
terromper o laço, ao contrário do que foi feito
no exemplo da "operação de vendas". Se não
conseguir, pelo menos tente fazer o mesmo
que foi feito nesse exemplo (página 84, aci-
ma).

 Informação importante: como a entrada de dados estará dentro de um laço, e


assim ela também será repetida, caso ocorra erro (se o usuário digitar uma en-
trada inválida) a próxima operação de entrada de dados irá falhar. Então é preci-
so fazer o seguinte:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.5 • Revisão do Capítulo 3 • C++ • AGIT informática • 99

– As funções "clear" e "ignore" fazem coisas diferentes, mas o que


interessa aqui é que, com elas, o estado de "cin" é restaurado
para a situação anterior aos erros. Veremos os detalhes disso mais
a frente.
– Observe que o segundo argumento passado para "ignore" é o ca-
ractere '\n', entre aspas simples (e não duplas). Veremos na se-
ção 4.7.4, página 122, a diferença entre aspas simples e duplas
para caracteres.
– Dependendo do compilador, será necessário incluir o arquivo onde
"numeric_limits" está declarado. Então. insira no topo do arquivo
[Link]:
#include <iostream>
#include <limits> 

g. Compilar (usando ambientes de desenvolvimento ou a linha de comando).


 Se ocorreram erros de compilação, verifique e corrija as linhas de erro.
 Dúvidas? Reveja as dicas sobre identificação de erros (seção 2.4, página 57,
acima). E as regras básicas de gramática (seção 2.2.4, página 48, acima).
h. Execute o programa. Observe se funciona corretamente.

 Compare o que você fez com a solução da apostila: página 438.

3.5.2 ▪ Questões para revisão

Responda às questões abaixo, assinalando todas as respostas corretas (uma ou


mais). Em seguida, compare suas respostas com as respostas localizadas no Ane-
xo B-3-2, página 440. Caso não entenda, encaminhe as dúvidas ao instrutor.

 Nas questões abaixo, considere as seguintes declarações iniciais:


int x , y ;

Cap.3 - 1. A respeito de uso da memória, assinale as afirmações corretas


a.  Não é preciso ter preocupações especiais com a memória de um compu-
tador. Isso é resolvido pelo compilador.
b.  Uma memória deve ser reservada e qualificada com um determinado
tipo pelo programador. Por exemplo, usando int, como na expressão
[ int x ; ]
c.  A expressão [ x = 10 ; ] visa confirmar se 'x' contem o valor 10.
d.  A expressão [ x = 10 ; ] visa armazenar o valor 10 em 'x', copiando
esse valor para a área de memória à qual associamos o nome 'x'.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


100 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

Cap.3 - 2. Sobre fluxo de processamento, podemos dizer que:


a.  O processamento é sempre baseado na execução seqüencial de um
certo número de instruções.
b.  O processamento pode ser bifurcado em caminhos diferentes, através
de tomadas de decisão.
c.  A expressão [ if ( x > y ) ] contem uma operação que leva à seguinte
avaliação: "'será que x' pode ser armazenado em y" ?
d.  Em [ if ( x > y ) ] temos uma operação que implica nesta avaliação: "o
valor armazenado em 'x' é maior-que o valor armazenado em y" ?
e.  Após [ if ( x > y ) ] o processamento poderá seguir um caminho diferente
se o resultado da avaliação dessa condição for verdadeiro.

Cap.3 - 3. Sobre os conceitos de verdadeiro e falso, podemos dizer que:


a.  A avaliação da expressão contida em [ if ( x > y ) ] retornará um resulta-
do verdadeiro se 'x' for maior-que 'y'; do contrário retornará um resulta-
do falso.
b.  A avaliação da expressão contida em [ if ( x >= y ) ] retornará um resul-
tado verdadeiro se 'x' for maior-que 'y'; do contrário retornará um resul-
tado falso.
c.  A expressão contida em [ if ( x ) ] não é aceita pelas linguagens C e C+
+. Logo, ocorrerá um erro de compilação.
d.  A avaliação da expressão contida em [ if ( x ) ] retornará um resultado
falso se o valor de 'x' for igual a 0(zero); e retornará um resultado verda-
deiro se o valor de 'x' for exatamente igual a 1(um).
e.  A avaliação da expressão contida em [ if ( x ) ] retornará um resultado
falso se o valor de 'x' for igual a 0(zero); e retornará um resultado verda-
deiro se o valor de 'x' for diferente de 0(zero)

Cap.3 - 4. O que será impresso na execução do código abaixo?


x=5;
y=x+1;
x=x+2;
if ( x >= y )
std::cout << "x é maior ou igual a y\n" ;
else
std::cout << "x é menor que y\n" ;
a.  Será impresso x é maior ou igual a y
b.  Será impresso x é menor que y
c.  Nada será impresso.
d.  Será impresso x é maior ou igual a y e, em seguida, será impresso x é
menor que y

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


3.5 • Revisão do Capítulo 3 • C++ • AGIT informática • 101

Cap.3 - 5. Sobre laços, podemos dizer que:


a.  Um laço sempre repete, infinitamente, um certo número de instruções.
b.  Um laço pode conter uma avaliação de condição que, caso seja ou
torne-se falsa, resultará na interrupção da repetição das instruções a ele
associadas.
c.  O laço while contem uma avaliação de condição que permite interrompê-
lo. Já o laço for contem apenas uma possibilidade de progressão, mas
não prevê uma avaliação de condição.

Cap.3 - 6. Quantas vezes serão executadas as instruções associadas ao se-


guinte laço:
for ( x = 1 ; x < 1 ; x = x + 1 )
{
// instruções...
}
a.  Uma vez.
b.  Duas vezes.
c.  Nunca serão executadas.
d.  Serão executadas infinitamente.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


102 • AGIT informática • C++ • • Capítulo 3 ▪ Programação: princípios básicos

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 4 ▪ Memória: tipos • C++ • AGIT informática • 103

• Capítulo 4
▪ Memória: tipos

Neste capítulo iremos aprofundar alguns dos tópicos vistos nos capítulos
anteriores, acrescentando novos elementos.
Veremos detalhes sobre o uso da memória: valores, tipos de dados, obje-
tos, variáveis, constantes e suas declarações.
Teremos também uma abordagem inicial sobre o tratamento de dados ar-
mazenados em série: vetores.

4.1 • Tópicos de destaque neste capítulo ....................................104


4.2 • Reserva e acesso de memória em C e C++ .........................105
4.2.1 ▪ Tipos de dados ...............................................................................105
4.2.2 ▪ Escolhendo o tipo adequado ..........................................................105
4.2.3 ▪ Porque reservar memórias com um tipo ........................................107
4.3 • Inicialização e atribuição ..................................................107
4.4 • Variáveis e constantes ......................................................107
4.4.1 ▪ Porque associar constantes a um nome ........................................108
4.4.2 ▪ Constantes nomeadas em um único lugar .....................................110
4.5 • Conceitos: valores, tipos e objetos .....................................111
4.6 • Conversões entre tipos .....................................................112
4.7 • Vetores ..........................................................................115
4.7.1 ▪ Vetores: o que são e para que servem ...........................................115
4.7.2 ▪ std::string e std::vector ...................................................................116
4.7.3 ▪ Vetores na linguagem C .................................................................119
4.7.4 ▪ Caracteres: aspas simples e duplas ..............................................122
4.8 • Revisão do Capítulo 4 .......................................................125
4.8.1 ▪ Exercício ........................................................................................125
4.8.2 ▪ Questões para revisão ...................................................................129

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


104 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

4.1 • Tópicos de destaque neste capítulo

 Revendo os princípios básicos de programação e detalhando


sua implementação em C e C++:
memória.

 Memória: reservar áreas da memória para que possam ser aces-


sadas nas operações a executar.
 Ao reservar uma área de memória em C e C++, devemos indicar
qual é o seu tipo.
 Um tipo indica o tamanho (quantidade de bytes) da área de me-
mória reservada e a forma como os dados serão nela armazena-
dos (forma de armazenamento).
 Há tipos diferentes para números inteiros e números reais.
 Devido à forma de armazenamento, números reais têm um acesso
mais lento do que números inteiros.
 Declaração de uma memória. Diferença entre inicialização e atri-
buição.
 É possível fazer conversões entre determinados tipos: algumas
são seguras e outras não.
 Convenções para tipos que permitem expressar caracteres.

 Memórias: Variáveis e Constantes.

 Variáveis podem (ou não) ser inicializadas e, depois, sofrer suces-


sivas atribuições.
 Constantes podem apenas ser inicializadas; não podem receber
atribuições.
 Porque associar constantes a um nome.
 Declarando constantes.
 Declarando constantes enumeradas.

 Vetores.

 Armazenando séries de valores de um mesmo tipo.


 Trabalhando com vetores de caracteres: std::string.
 Trabalhando com vetores de qualquer tipo que aceite a operação
de atribuição: std::vector.
 Vetores no estilo "C": usos e problemas.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.2 • Reserva e acesso de memória em C e C++ • C++ • AGIT informática • 105

4.2 • Reserva e acesso de memória em C e C++


4.2.1 ▪ Tipos de dados
Observe que, nos exemplos acima, é utilizada a palavra “int”.
int valor_produto ;
int valor_imposto ;
int é uma palavra reservada de C e C++ que indica como será feita uma determinada
reserva de memória. Ele indica um tipo de dados. E a linha onde reservamos uma
memória (denominada declaração) deve, obrigatoriamente, indicar um tipo e um
identificador(nome) para essa memória.
Nesse caso, ao usar o int, estamos dizendo que precisamos da reserva de uma memória
cujo tipo seja capaz de armazenar números inteiros com um determinado tamanho.
Podemos também declarar memórias do mesmo tipo em uma única linha, separando
os seus identificadores (nomes) com vírgulas:
int valor_produto, valor_imposto;

E, em seguida, atribuir valores a qualquer uma dessas memórias reservadas:


valor_produto = 100 ; // o valor 100 foi copiado para “valor_produto”

No diagrama simplificado exposto abaixo, visualizamos o estado da memória após a


alteração de “valor_produto”:
Apelidos (símbolos) para identificar, no “valor_produto” “valor_imposto”
código fonte, cada posição de memória
reservada (esses nomes não existirão no
executável)
Posição na memória ou “endereço de 1000 1004
memória” (por hipótese)
Valor armazenado 100 ? (“lixo”)
Veremos ainda neste capítulo (seção 4.5, página 111) que um tipo qualifica uma área
de memória, permitindo representá-la como um objeto (um objeto é um valor variável
ou constante de determinado tipo).

4.2.2 ▪ Escolhendo o tipo adequado


Podemos observar também que, para o exemplo acima, o tipo int não parece muito
adequado. Pois com ele não podemos armazenar números que suportem uma parte
fracionária.
E, se estamos falando em “valor de um produto”, precisamos prever a ocorrência de
centavos, havendo assim a necessidade de casas decimais. Pois, ainda que no exemplo
não tenham sido usados os centavos, eles poderão aparecer a qualquer momento, devi-
do à natureza do valor em questão. Por isso, ao invés de um número inteiro, precisa-
mos de um número real, o que, em computação, significa um número com ponto flu-
tuante (que é um formato de representação digital de números reais).
Para armazenar números com ponto flutuante, temos outros tipos, diferentes do int,
como é o caso do tipo double.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


106 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

Desse modo, o código do nosso exemplo poderia ser escrito assim:

double valor_produto = 100 ; //  poderia ter centavos...


double valor_imposto = 10 ; // idem...
double valor_venda = valor_produto + valor_imposto ; // idem...
double valor_pago = 110 ; // idem...

E qual a razão pela qual algumas linguagens usam diferentes palavras reservadas
para a declaração de números inteiros e números reais?
 O motivo é performance: um número inteiro pode ser armazenado e aces-
sado mais rapidamente, ao passo que um número real (ponto flutuante)
exige uma forma de armazenamento e acesso mais complexa e lenta.

Afinal, não podemos esquecer que computadores são baseados em números de base bi-
nária, que, por natureza, são inteiros. Assim, o ponto flutuante, para armazenar a par-
te fracionária, exige uma forma de armazenamento diferenciada, dividindo o número
em uma mantissa e um expoente, o que torna o acesso mais lento.
 Se quiser mais informações sobre o assunto "ponto flutuante", eis aqui um
ponto de partida: [Link]

Ficamos sabendo acima que o uso de números reais implica em um acesso mais lento.
Mas, se o problema que estamos resolvendo exige o uso de números reais (por exem-
plo, para armazenar os centavos), então temos que usar esse tipo de número, não ha-
vendo como escapar do custo ocasionado pelo ponto flutuante.
Em conclusão, o princípio básico é:

 Reservar memória em C e C++, significa declarar uma memória com um


determinado tipo.
E um tipo indica uma forma de armazenamento e um tamanho.
Os tipos são definidos com palavras reservadas, como vemos abaixo, na
tabela reduzida de tipos (apenas os que usaremos inicialmente):

Alguns tipos:
Tipo Tamanho mínimo em bytes Forma de armazenamento
int 2, 4, 8, …  depende da plataforma inteiro
double 8 ponto flutuante
enum Entre 1 byte e o tamanho que tenha o inteiro (enumera constantes)
int, dependendo do valor usado.

Na tabela acima relacionamos alguns dos tipos referentes a valores (apenas aqueles
que usaremos logo abaixo).
 Existem outros tipos, como veremos depois. Eles são mostrados na tabela
localizada no “guia de consulta rápida”, seção 1, página 486.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.2 • Reserva e acesso de memória em C e C++ • C++ • AGIT informática • 107

4.2.3 ▪ Porque reservar memórias com um tipo


Devido à reserva de memória com tipos explicitamente definidos, temos vantagens:
 Um uso mais claro da memória: sempre poderemos saber o que deve e pode
ser feito em determinada área de memória.
 E o compilador, por sua vez, poderá analisar se estamos usando essa memória
conforme sua especificação, e assim, se tentarmos atribuir-lhe um valor indevi-
do, o compilador emitirá uma mensagem de erro ou de advertência.
 Por exemplo: se tentarmos fazer

int x ;
x = 5.3 ; // 5.3 é do tipo double

– O compilador emitirá um aviso, pois o valor 5.3 será truncado para 5 ao


ser copiado para 'x', já que esta memória é do tipo int e não suporta o
armazenamento da parte fracionária.
 Além disso, o compilador poderá resolver totalmente o problema da alocação
dessa memória, e nenhum tipo de interpretação ou análise será necessária em
tempo de execução para que a alocação seja efetuada.

 Reservar memória com um tipo (com uma forma de armazenamento e um


tamanho próprios) permite evitar erros além de ganhos de performance.

4.3 • Inicialização e atribuição


Inicialização: é quando atribuímos um valor a uma memória que está sendo reservada
na própria linha em que ela é declarada (ou seja, no momento em que é reservada):
int valor_produto = 100 , valor_imposto; // “valor_produto” foi inicializada

Já em qualquer outra linha que não seja a da declaração, uma operação de cópia de
valor é denominada atribuição:
valor_imposto = 10 ; // “valor_imposto” recebeu uma atribuição.

 Quando atribuímos um valor a uma memória na linha de sua declaração,


temos uma operação denominada inicialização (e não atribuição).
Já uma atribuição é a cópia de um valor para uma memória de valor variá-
vel em qualquer linha que não seja a de sua declaração.
No item abaixo (variáveis e constantes) veremos uma situação em que
isso fará uma grande diferença. E, mais tarde, ainda veremos outras.

4.4 • Variáveis e constantes


Quando reservamos uma memória para armazenar valores, precisamos definir também
de que modo esses valores irão ser usados.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


108 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

 Se essa memória admitir alterações no valor armazenado, então dizemos que


ela é uma variável. Poderá aceitar novas atribuições de valor.
 Mas existem casos em que um determinada memória é imutável. Ela é apenas
inicializada com um valor que não mais será alterado. Dizemos então que essa
é uma memória constante. Não poderá aceitar qualquer atribuição posterior.
Exemplos:
double valor_produto ; // pode ser inicializada aqui ou
// receber valores mais tarde; é uma variável.
const int ultimo_mes = 12 ;
 // é uma constante: deve ser inicializada aqui obrigatoriamente;
// e não poderá ser alterada após essa linha.
E porque motivos precisamos declarar constantes? Não poderíamos simplesmente
usar o número “12” diretamente sempre que precisássemos nos referir ao último mês
de um ano?
Considere:
int mes;
// ...
if ( mes == 12 ) // se "mes" for igual a 12...
// ... pagar o décimo-terceiro salário.
Neste caso, o uso direto do número constante "12", parece claro: afinal estamos com-
parando esse número com uma variável denominada "mes". Logo, esse número pare-
ce referir-se ao último mês de um ano. Mas nem sempre um valor usado diretamente
aparenta o seu significado.

4.4.1 ▪ Porque associar constantes a um nome


Exemplo:
int x;
x = 80 ; //  o que significa "80" aqui ?
Se, ao invés de usar constantes diretamente, associarmos o valor a um nome, o código
ficará muito mais claro e fácil de ler/entender.
Continuando o exemplo:
int x;
const int screen_max_x = 80 ; // o nome indica que essa é a maior
// ... // coordenada "x" da tela
x = screen_max_x ; //  OK: nenhuma dúvida sobre o que está sendo feito.
 Para que o código seja o mais claro possível, adote, como regra geral,
sempre associar valores constantes a um nome, mesmo em casos em que
um constante pareça clara em um determinado contexto (mas pode ficar
obscura em um outro). O código fonte deve ser sempre auto-explicativo.

Portanto, a comparação feita acima entre "mes" e "12" ficaria melhor assim:
const int ultimo_mes = 12 ;
int mes ; // ...
if ( mes == ultimo_mes ) // nada a explicar ou a comentar...
// ... pagar o décimo-terceiro salário.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.4 • Variáveis e constantes • C++ • AGIT informática • 109

Neste caso do "último mês", talvez fosse útil associar cada mês do ano a um nome de
constante, pois muitas vezes precisamos nos referir a um mês qualquer e não apenas ao
último mês do ano.
Exemplo:
int mes ; // ...
if ( mes == 9) // se o "mes" for setembro...
// ... marcar o dia 7 como feriado.
Quando temos um determinado conjunto de constantes que formam uma seqüência
(como é o caso dos meses), podemos usar uma outra forma mais prática e segura de de-
clarar constantes, através do tipo enum.
enum { const_0 , const_1 }; // const_0 tem o valor 0; const_1 tem o valor 1

Ele é um enumerador, que permite criar uma lista de constantes, separadas por vírgu-
las, onde:
 o valor de cada uma, por default, é sempre o valor da constante anterior mais
um, sendo que o valor da primeira delas, por default, é zero;
 exceto se forçarmos alguma delas para um valor específico - e as seguintes
continuarão seguindo a regra "anterior mais um", a não ser que também te-
nham seus valores especificados explicitamente.
Exemplo:
enum { Janeiro=1 , Fevereiro , Marco , Abril , Maio , Junho , Julho ,
Agosto , Setembro , Outubro , Novembro , Dezembro } ;
Na lista de constantes acima "Janeiro" teve seu valor forçado para 1 (do contrário se-
ria zero), e as demais seguem a regra "anterior mais um". Desse modo, "Fevereiro"
tem o valor 2 - e assim sucessivamente até "Dezembro" que terá o valor 12.
Continuando o exemplo:
int mes; // ...

if ( mes == Dezembro ) // nada a explicar ou a comentar... 


// ... pagar o décimo-terceiro salário.

if ( mes == Setembro) // nada a explicar ou a comentar... 


// ... marcar o dia 7 como feriado.
Há uma forma ainda melhor de usar o enum, pois ele permite que criemos também um
nome de tipo associado à lista de constantes.
Desse modo, poderemos declarar variáveis desse tipo, e com isso, essas variáveis se-
rão amarradas à lista de constantes, de forma que só aceitarão valores presentes nessa
lista (ou teremos um erro de compilação).
Pois quando fazemos "int mes", declaramos uma variável que está sendo usada para
armazenar um dos valores da lista enumerada. Mas, sendo " int", poderá aceitar qual-
quer outro valor inteiro, o que não é o nosso objetivo. Melhor seria então se criássemos
um tipo que impedisse qualquer atribuição aleatória.
Exemplo:
enum Meses { Janeiro=1, Fevereiro, Marco, Abril, Maio, Junho, Julho,
Agosto, Setembro, Outubro, Novembro, Dezembro } ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


110 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

 Criamos o tipo "Meses" amarrado à lista de constantes. Desse modo, va-


riáveis declaradas com esse tipo só aceitarão valores da sua lista.

Continuando o exemplo:
Meses mes; // Declarei uma variável do tipo "Meses".
// ...
if ( mes == Dezembro ) // nada a explicar ou a comentar...
// ... pagar o décimo-terceiro salário.
if ( mes == Setembro) // nada a explicar ou a comentar...
// ... marcar o dia 7 como feriado.
mes = 31 ; //  Erro de compilação. OK: isso é bom... faça a correção.
mes = 10 ; //  Erro de compilação. Idem...

 Apesar do valor 10 coincidir com o valor da constante "Outubro" da lista,


temos um erro, pois a constante 10, por default, é do tipo int; e variáveis do
tipo "Meses" só aceitam as constantes nomeadas na sua lista.
 Na Linguagem C esse erro não seria acusado pelo compilador. Se
você escreveu coisas assim em C, corrija para que possa compilar em
C++.

mes = Outubro ; //  correto.


int x = Outubro ; // também correto pois "Meses" pode ser convertido
// para int. Nesse caso, o valor de 'x' será 10.

4.4.2 ▪ Constantes nomeadas em um único lugar


Finalmente, cabe observar que há ainda uma outra vantagem em associar valores cons-
tantes a nomes. Pois pode ocorrer que, no futuro, uma determinada constante venha a
ser usada com um outro valor. E, se uma constante é nomeada em um único lugar no
escopo em que será usada, não teremos muito trabalho para usar um novo valor.
Neste caso, bastará alterar a declaração da constante (que estará em um único lugar),
ao invés de ter que alterar diversas linhas espalhadas pelo código, o que precisaria ser
feito caso estivéssemos usando o valor constante diretamente.
Exemplo clássico:
const double pi = 3.14159 ;
// ... // "pi" é usado a partir daqui ao invés de "3.14159".
Desse modo, no restante do código, usamos o nome "pi", ao invés do valor bruto.
No futuro, talvez seja necessário que essa constante seja usada de modo mais preciso.
Nesse caso, bastará mudar a sua única linha de declaração:
const double pi = 3.14159265359 ;
// ... // E nada resta a alterar daqui para baixo. Isso é muito bom... 

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.4 • Variáveis e constantes • C++ • AGIT informática • 111

 Ressaltando: constantes são inicializadas obrigatoriamente em um único lu-


gar no escopo em que são declaradas. E não poderão ser alteradas através de
atribuição (do contrário não seriam constantes...). Então:

pi = 3.14 ; //  Tentativa de atribuir valor a uma constante.


// Erro de compilação.

4.5 • Conceitos: valores, tipos e objetos


Bjarne Stroustrup, em "Programming: Principles and Practice Using C++" (seção
3.8), estabelece algumas distinções úteis para melhor caracterizar o uso da memória:
 Um tipo define um intervalo de valores possíveis e um conjunto de operações
(para um objeto).
 Um objeto é uma determinada área de memória que contem um valor de um
determinado tipo.
 Um valor é um conjunto de bits na memória, o qual é interpretado de acordo
com o seu tipo.
 Uma variável é um objeto que tem um nome.
 Uma declaração é uma instrução que dá um nome a um objeto.
 Uma definição é uma declaração que aloca memória para um objeto.
Mais acima, muitas vezes nos referimos ao uso de "memórias" de maneira genérica.
Mas dissemos também que uma memória precisa ser qualificada para que possa ser
usada.
E a questão central para sua qualificação é o seu tipo. Sem isso não sabemos quais
valores poderão ocupar legitimamente essa área de memória (e já vimos que números
reais não podem ser armazenados como inteiros).
Em segundo lugar, decidimos se essa área de memória poderá ser alterada (variável)
ou não (constante).
Quando declaramos uma variável ou uma constante, associamos um nome a uma
determinada área de memória, a qual está sendo reservada de acordo com o tipo que
rege a declaração e que define o seu tamanho (a quantidade de memória a ser alocada)
e a sua forma de armazenamento.
Os valores que serão armazenados em uma área de memória já declarada estarão assim
qualificados com o tipo empregado nessa declaração. Por isso podemos pensar nesses
valores como objetos: não são apenas um conjunto arbitrário de bits, mas um conjunto
qualificado, isto é, com características próprias.

 Adiante, veremos mais sobre tipos de dados, objetos, variáveis e constan-


tes (conceitos, detalhes e exemplos adicionais) no capítulo "referência téc-
nica", nas seções "tipos de dados" (13.1, página 317) e "conceituando
identificadores, funções, blocos e escopos" (13.2, página 333).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


112 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

4.6 • Conversões entre tipos


Por enquanto, vamos apenas introduzir o assunto, chamando a atenção para alguns pro-
blemas potenciais que podem ocorrer quando trabalhamos com tipos diferentes que às
vezes precisam estar presentes em uma mesma operação ou em operações encadeadas.
Para isso, vamos iniciar ampliando a nossa tabela de tipos (a tabela completa está no
“guia de consulta rápida”, seção 1, página 486):
Mais alguns tipos, com seus tamanhos e intervalo de valores:
Tamanho Forma de arma-
mínimo zenamento
Tipo em bytes Intervalo de valores / natureza
unsigned char 1 0 a 255 inteiro
char 1 -128 a 127 inteiro
unsigned short 2 0 a 65535 inteiro
short 2 -32768 a 32767 inteiro
unsigned long 4 0 a +4294967295 inteiro
long 4 -2147483648 a + 2147483647 inteiro
unsigned int depende da plataforma inteiro
int depende da plataforma inteiro
enum De 1 byte até, no máximo, o tamanho que te- inteiro (enume-
nha o int, dependendo dos valores usados. ra constantes)
bool 1 true / false (1 ou zero) inteiro(só C++)
float 4 3.4E-38 a 3.4E+38 ponto flutuante
double 8 1.7E-308 a 1.7E+308 ponto flutuante

Como podemos observar na tabela acima, temos:


 números inteiros de diferentes tamanhos mínimos: char (1 byte), short (2
bytes), long (4 bytes) e o int (cujo tamanho depende da plataforma: 2 bytes em
máquinas de 16 bits, 4 em máquinas de 32 bits, etc).
 o tipo bool (1 byte), que, na memória irá armazenar os valores 1 e 0 - mas no
código fonte devem ser expressos com as palavras reservadas true e false.
 números reais de diferentes tamanhos: o float (4 bytes) e o double (8 bytes).
Além dos diferentes tamanhos para cada forma de armazenamento, temos ainda um ou-
tro diferencial: observe que para os tipos inteiros (exceto o bool e o enum) temos uma
repetição de cada um deles precedida por um modificador: unsigned.
Portanto, tome nota:
 Os tipos inteiros (exceto o bool e o enum), admitem duas alternativas, indi-
cadas por dois modificadores de tipo:

 signed: com sinal, admite números negativos e positivos; este é o default,


logo pode ser omitido; ou seja: basta declarar “int” ao invés de “signed int”.
 unsigned: sem sinal, apenas positivos.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.6 • Conversões entre tipos • C++ • AGIT informática • 113

 Já os números de ponto flutuante são sempre signed – com sinal.

Cada tipo tem sua aplicação de acordo com a situação em que são usados. Mas é preci-
so cuidado quanto é preciso relacionar tipos diferentes em determinadas operações.

Exemplos:

bool flag = true ; imprime: 1 { OK: true é convertido para 1, para que a im-
std::cout << flag ; pressão seja possível (conversão natural). }
short i = 2000 ; imprime: 2000 { OK: imprime exatamente o esperado. }
std::cout << i ;
flag = i ; imprime: 1 { Ao converter short para bool [ flag = i ], o in-
std::cout << flag ; teiro 2000 foi truncado para true, já que 2000 é diferente de
zero, logo é verdadeiro. A conversão é natural, mas será
( ) que era isso que esperávamos? }
i = flag ; imprime: 1 { OK: [ i = flag ], true é convertido para 1. }
std::cout << i ;
char c = 65 ; imprime: A { OK: 'A' é a representação gráfica do número
std::cout << c ; 65 na tabela ASCII. E cout, ao receber um char, entende
que deve imprimir sua representação gráfica (isto é, o carac-
tere correspondente na tabela de caracteres em uso). }
i=c; imprime: 65 { Nada a ressaltar: o valor continua o mesmo.
std::cout << i ; Apenas o cout, recebendo um short, imprime o próprio nú-
mero ao invés de imprimir sua representação gráfica. }
i = 323; imprime: C { Ao converter short para char [ c = i ], o nú-
c= i; mero 323 foi truncado para 67, já que 323 não pode ser ar-
std::cout << c ; mazenado em um único byte (tamanho do char). E cout, ao
receber um char, imprimiu a representação gráfica de 67: 'C'.
() A conversão é natural, mas era isso que esperávamos? }

i=c; imprime: 67 { Nada a ressaltar: o valor continua o mesmo.


std::cout << i ; }

() Nesses dois casos, conversão de short para bool [ flag = i ] e conversão de
short para char [ c = i ], temos um conversão natural entre tipos inteiros, mas com
um problema: o maior (short, 2 bytes) está sendo convertido para o menor (bool,
ou char, ambos com 1 byte, sendo que os valores admitidos pelo bool são 0 e 1).
Em conversões assim (maior para menor) os números poderão ser truncados
quando (como ocorre nesses dois exemplos) os valores atuais dos objetos de tipo
maior não estejam dentro dos intervalos de valores dos objetos de tipo menor.
Todos (ou quase todos) os compiladores admitem que os chamemos passando argu-
mentos que elevam o seu "nível de warning" (ou advertência), de modo a obter uma
análise mais rigorosa do código. E, em casos assim, alguns deles emitirão mensagens
de advertência, indicando uma possível perda de dados. Como, por exemplo:
warning :conversion from 'short' to 'char', possible loss of data

 Mas, infelizmente, isso não é exigido pelo padrão da linguagem para os ti-
pos primitivos e, desse modo, muitos compiladores ficarão em silêncio em
casos desse tipo.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


114 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

Por isso o programador deve ser sempre explícito quando estiver realizando conversões
de tipos maiores para menores, deixando clara a sua intenção. Por exemplo:
flag = bool( i ) ; Conversão explícita.
c = char( i ); Idem.
Isso não altera a natureza do problema, mas deixa claro que não houve uma distração
do programador e sim uma ação deliberada (intencional). Quando alguém precisar re-
ler o código isso fará diferença.
Uma outra situação, já descrita acima, int x ;
seria esta: x = 5.3 ; // 5.3 é do tipo double

Uma conversão de double para int é ainda mais suspeita, por envolver diferentes for-
mas de armazenamento. Neste caso, felizmente, todos (ou quase todos) os compilado-
res emitirão um aviso semelhante a este:
warning: converting to 'int' from 'double'

Outra situação int x ; unsigned int y ;


semelhante: if ( x > y ) // comparando signed com unsigned

Uma comparação entre inteiros com sinal e inteiros sem sinal pode levar a resultados
inesperados, já que os seus intervalos de valores são diferentes. Aqui também teremos
um aviso: warning: comparison between signed and unsigned integer.
Além disso, nas seguintes operações: double d = double ( 4 ) * 1024 * 1024 * 1024 ;
 Considere que os números constantes 4 e 1024 por default são do tipo int. Já, por
exemplo, 4.0 (ou apenas 4.) é, também por default, do tipo double.
 Quando fazemos: double( 4 ), estamos convertendo uma constante int para dou-
ble, e com isso o resultado das operações de multiplicação também será do tipo
double, pois ele é maior (ou mais preciso) que o int.
 Se não fizéssemos essa conversão, o resultado seria int. Em plataformas de 32
bits, onde esse tipo tem quatro bytes (ou seja, o mesmo tamanho e intervalo de va-
lores do long), o resultado final seria truncado de [Link] para
-[Link], pois estaria estourando o valor máximo suportado. Em seguida,
ocorre a operação de atribuição para um double ("d"), que suportaria armazenar
[Link]. Mas, se o resultado da multiplicação estivesse truncado, seria ar-
mazenado o valor truncado.
Por enquanto, o nosso objetivo é apenas advertir sobre problemas desse tipo. Contudo,
esse assunto merece uma atenção maior, e será abordado em detalhes mais a frente. Em
especial, conversões como [ c = char( i ) ] deveriam ser implementadas de modo mais
claro. Como veremos, C++ oferece recursos mais explícitos para conversões.

 Veremos conversões de tipos no capítulo "referência técnica", seção


13.1.3, página 320.

 E, em especial, as conversões específicas de C++ (static_cast<>, dyna-


mic_cast<>, reinterpret_cast<> e const_cast<>), na seção [Link], pá-
gina 321.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.6 • Conversões entre tipos • C++ • AGIT informática • 115

O tipo bool e o resultado de avaliações.


O tipo bool não existe em C, apenas em C++. Acima, quando examinamos os concei-
tos de verdadeiro e falso, dissemos que se a avaliação de uma condição tem um resulta -
do falso, teremos como valor de retorno o inteiro zero. Se verdadeira, o retorno será o
inteiro um. Em C esse já é, implicitamente, um resultado lógico (ou seja booleano).
Em C++ isso é materialmente válido, mas formalmente o retorno de uma avaliação
não é do tipo int e sim do tipo bool: true ou false.
E estas são duas palavras reservadas de C++, que também não existem em C. Ainda
que true, na memória, seja armazenado como um, e false como zero, essas duas pala-
vras visam expressar, com maior clareza, os conceitos booleanos de verdadeiro e falso.

4.7 • Vetores
Além dos tipos que já vimos, temos outros tipos. Dizemos que tipos suportados direta-
mente pela CPU são tipos primitivos. Assim, teremos uma variedade de tipos inteiros
de diferentes tamanhos, e o mesmo para os números reais.
A linguagem também permite que o programador crie novos tipos a partir de tipos já
existentes. Veremos como criar novos tipos mais a frente. Por enquanto, vamos usar
alguns dos tipos suportados pela biblioteca padrão de C++ (não existem em C).

4.7.1 ▪ Vetores: o que são e para que servem


Em muitas situações precisamos não apenas de um único int ou double, mas sim de
uma série deles.
Exemplo:
Queremos armazenar os valores referentes aos totais das vendas de cada mês. Se alo-
carmos esses valores individualmente, teremos uma sobrecarga de código ao acessá-
los:
double vendas_mes_1 = 1000; No futuro, teremos que acessar sempre cada
double vendas_mes_2 = 1200; elemento individualmente, mesmo que o
//... acesso seja repetitivo. Isso multiplicará a
double vendas_mes_12 = 1800; quantidade de instruções de acesso.

Se precisarmos imprimir esses 12 valores, teremos 12 instruções de impressão indivi-


duais. Isso só irá piorar se a quantidade de valores aumentar.
Por exemplo:
 Se ao invés dos 12 meses, tivéssemos que imprimir os totais de vendas de cada
semana de um ano, teríamos então mais de 50 variáveis individuais - e, para
acessá-las, precisaríamos dessa quantidade de instruções, seja para impressão
ou outras operações que poderiam ser executadas repetitivamente em um laço.

Um vetor permite que valores como esses formem uma série, de tal modo que pode-
mos percorrer essa série em um laço, acessando cada um de seus elementos a cada

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


116 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

nova iteração do laço com uma única instrução (uma única impressão, por exemplo) ou
um único bloco de instruções.
Podemos resolver isso com dois tipos oferecidos pela biblioteca C++: string e vector.

4.7.2 ▪ std::string e std::vector


 std::string - armazena e permite o tratamento de um vetor de caracteres (uma
cadeia de caracteres). Embora no fundo seja um vetor, esse tipo diferenciado
permite que tenhamos funções especializadas em tratar cadeias de caracteres,
que são muito comuns em programação (textos, principalmente).
 Por exemplo, se fizermos:

std::string nome = "MARIA"; Teremos na memória algo como:

valor armazenado: M A R I A <TC>


posição: 0 1 2 3 4 5

 Onde <TC> é um caractere especial de terminação da string (e, por


convenção, esse terminador é o zero binário), de modo que:
Irá imprimir "MARIA". Ou seja: imprimirá to-
std::cout << nome ; dos os caracteres até encontrar <TC> (o ter-
minador zero), interrompendo aí a impressão.

 A posição é um índice seqüencial (iniciado em zero) que permite aces-


sar cada elemento individualmente, de modo que:

std::cout << nome[ 2 ] ; Irá imprimir o caractere 'R'.

 std::vector - armazena e permite o tratamento de um vetor de qualquer tipo já


existente, desde que esse tipo admita a operação de atribuição, pois cada ele-
mento do vetor pode receber uma cópia de um determinado valor desse tipo.
 Por exemplo, para armazenar os 5 primeiros números primos:

std::vector< int > primos; Vetor de inteiros: tipo indicado com <int>

unsigned int pr_max= 5; unsigned: a dimensão não pode ser negativa.


[Link]( pr_max ); Dimensiona: aloca memória para 5 inteiros.
Observar que [ [Link]( pr_max ); ] in-
dica que std::vector (ao contrário dos tipos
primitivos) nos fornece funções especializa-
das em trabalhar com esse tipo. Veremos isso
no capítulo 7, página 199.
primos [ 0 ] = 2; Acessa cada elemento, usando o índice:
primos [ 1 ] = 3; primos [ <indice> ]  usa o "operador [ ]"
primos [ 2 ] = 5;
primos [ 3 ] = 7;
O índice é baseado em zero. Se o vetor tem 5
primos [ 4 ] = 11 ; elementos, podemos usar índices de 0 até 4.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.7 • Vetores • C++ • AGIT informática • 117

 Em conseqüência, teremos na memória algo como:


valor armazenado: 2 3 5 7 11
posição: 0 1 2 3 4

 A convenção usada para strings, onde temos um terminador, é útil ape-


nas para textos (que agrupam caracteres), e por isso não se aplica a ve-
tores em geral.
– Desse modo, em princípio, um vetor deve ser controlado pelo seu
tamanho, e não por um terminador especial.
– Pois, exceto em textos, o zero binário pode ser um elemento
qualquer do vetor (e não seu terminador).
– Então, se quisermos imprimir todos os elementos do vetor, uma
das alternativas seria a seguinte:

unsigned int indice ; // unsigned: o índice não pode ser negativo...


for ( indice = 0 ; indice < [Link]() ; indice = indice + 1 )
std::cout << primos [ indice ] << " , " ;
E serão impressos todos os elementos do vetor [de 0 até p_max-1] :
2 , 3 , 5 , 7 , 11 ,

 std::vector fornece-nos a função size, que retorna a dimensão do vetor.


 Tal como em string, a posição é um índice seqüencial (iniciado em
zero) que permite acessar cada elemento individualmente, coisa que,
aliás, já fizemos no laço acima. Assim, também poderíamos fazer:

std::cout << primos [ 2 ] ; Irá imprimir o número 5.

Exemplo.
a. Criar um arquivo e escrever o código.
 Em um editor ou ambiente, dentro do diretório-base de exercícios ("cursoCPP"),
crie um novo diretório para este exercício; por exemplo, "03_string_vector".
 Nesse diretório, grave o arquivo "[Link]". Assim, teremos algo como:

"cursoCPP/03_string_vector/[Link]"
Objetivo a atingir:

O código abaixo inicialmente usa um objeto std::string, com alguns exemplos


muito simples de manipulação de texto.
Em seguida, cria um vetor (um objeto std::vector) para armazenar o total de
vendas de cada trimestre de um ano. Esse tipo de valor exige centavos; logo
será necessário o ponto flutuante e por isso usaremos o tipo double.
Assim teremos: std::vector < double > vendas_trimestre. Esse vetor terá seus
elementos preenchidos por entradas de dados a partir do teclado, em um laço.
Depois esses valores serão impressos e acumulados para gerar um valor anual
- também em um laço.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


118 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

Exemplo de execução:

Código fonte
para "[Link]":

#include
<iostream>
#include <string>
#include <vector>
int main()
{
// === a. string :
std::cout << "=== a. testando string\n";
std::string nome ;
nome = "Maria" ;
std::cout << nome << "\n" ; // imprime [Maria]
nome = nome + " da Silva" ;
std::cout << nome << "\n" ; // imprime [Maria da Silva]
// === b. vector :
std::cout << "\n=== b. testando vector\n";
// um vetor para armazenar o total de vendas em um trimestre:
const unsigned int trimestres_ano = 4 ; // total de trimestres no ano...
// declara o vetor, que conterá uma série de objetos do tipo double:
std::vector < double > vendas_trimestres ; // tipo em uso: double
// define a quantidade de elementos do vetor
// (usando uma variável ou uma constante):
vendas_trimestres.resize( trimestres_ano ); // vetor para 4 elementos
// pede ao usuário que informe o total de vendas de cada trimestre:
std::cout << "informe total de vendas de cada trimestre:\n";
unsigned int index ;
for ( index = 0 ; index < trimestres_ano ; index = index + 1 )
{
std::cout << "informe o trimestre : " << index + 1 << "\n";

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.7 • Vetores • C++ • AGIT informática • 119

std::cin >> vendas_trimestres [ index ] ; // entrada para cada um


}
// imprime cada trimestre e totaliza os valores para o ano inteiro:
std::cout << "\nimprimindo os valores informados:\n";
double total_ano = 0;
for ( index = 0 ; index < trimestres_ano ; index = index + 1 )
{
std::cout << vendas_trimestres [ index ] << "\n" ;
total_ano = total_ano + vendas_trimestres [ index ] ;
}
// imprime o total do ano:
std::cout << "\ntotal vendas no ano : " << total_ano << "\n" ;
return 0;
}
b. Compilar o exercício: use ambientes de desenvolvimento ou a linha de comando.
c. Executar o programa. Informe os valores que serão solicitados. Reveja o exemplo
de uso exibido acima, para comparar com o que você obteve.
Depois modifique o código com base nos exemplos de string e vector. Por ex-
emplo, para imprimir um único caractere da string. Ou para testar o acesso a ele-
mentos não alocados.

4.7.3 ▪ Vetores na linguagem C


Acima vimos um uso simples de strings e vetores, o qual é possível devido à bibliote-
ca de C++.
Em algumas situações poderíamos usar, em C ou em C++, vetores "em baixo nível"
(praticamente no nível da máquina). Aliás, essa é a única alternativa que temos no caso
da linguagem C.
Em C++ só devemos usá-los se:
 Tiverem quantidade fixa de elementos (pois os vetores de C com quantidade
variável de elementos apresentam alguns riscos adicionais, como veremos mais
a frente).
 Além disso, só devem ser usados em situações onde, garantidamente, não exis-
tam riscos de acesso a elementos não alocados (fora da dimensão do vetor).
Considere os seguintes exemplos:
a. strings.
Em C++ fazemos:
std::string nome;
nome = "Maria";
nome = nome + " da Silva";
Usando o "estilo C", faríamos:
char nome[ 15 ] ; // um vetor de 15 caracteres
strcpy ( nome , "Maria" ) ;
strcat ( nome , " da Silva" ) ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


120 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

 Dimensionamos um vetor de 15 caracteres (char).


 Em seguida, copiamos (função strcpy) a string "Maria" (constante)
para esse vetor: [ strcpy( nome, "Maria" ); ].
 Isso significa que todos os caracteres da constante são copiados, um a
um, para os seis primeiros elementos do vetor (os cinco de "Maria", mais
o caractere de terminação zero (<TC>) que, por convenção, está embuti-
do no final dessa constante):

valor: M a r i a <TC> (sem uso no momento)

posição: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

 Finalmente, usamos uma outra função (strcat) para concatenar uma se-
gunda string ao vetor: [ strcat( nome, " da Silva" ); ].
 Isso significa que todos os caracteres da string " da Silva" também se-
rão copiados, um a um, para o vetor, a partir da posição onde está o ter-
minador zero (após o último 'a' de "Maria").

valor: M a r i a d a S i l v a <TC>

posição: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

Aparentemente nenhum problema. Todos os elementos do vetor estão dentro da dimen-


são máxima definida inicialmente: 15.
Mas, se ao invés de:
strcat ( nome , " da Silva" ) ;
tivéssemos:
strcat ( nome, " Aparecida" ) ;
Estaríamos ultrapassando os 15 caracteres alocados para o vetor "nome" (e o
mesmo poderia ocorrer com a função strcpy):

valor: M a r i a A p a r e c i d a <TC>

posição: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Isso não aconteceria com std::string, pois se fizermos:

std::string nome = "Maria"; std::string cuidará dos detalhes para que


a memória seja redimensionada conforme
nome = nome + " Aparecida";
as necessidades determinadas pelo uso.

 Isso significa que o uso do estilo "C" para vetores (inclusive vetores de ca-
racteres) exige cuidados especiais.

b. vetores de qualquer tipo.


O mesmo problema observado com strings pode ocorrer com vetores em geral. Em um
contexto controlado, podemos garantir um acesso seguro. Mas nem sempre isso é pos-
sível.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.7 • Vetores • C++ • AGIT informática • 121

No exemplo abaixo, não temos qualquer problema:


const unsigned int primos_max = 5 ; // a constante nomeada servirá
// para permitir que a dimensão do vetor seja usada de modo coerente
// em todas as partes do código. Mas, isso dependerá do programador.
// Um vetor no estilo "C" pode ser inicializado com uma
// lista de inicialização entre chaves:
int primos [ primos_max ] = { 2 , 3 , 5 , 7 , 11 } ;

valor armazenado: 2 3 5 7 11
posição: 0 1 2 3 4

unsigned int i ;
for ( i = 0 ; i < primos_max ; i = i + 1 )
std::cout << primos [ i ] << " , " ;
Em uma situação assim, tudo está (ou parece estar) sob controle. Temos uma constante
nomeada para determinar a dimensão do vetor e, no código acima, essa constante é
sempre considerada para impedir que ocorra um acesso a um elemento não alocado.
Mas, isso não impede que, em outras partes do código, a "boa prática" seja esquecida e
ocorra um acesso arbitrário. Tudo dependerá do cuidado do programador.
Na lista de inicialização, caso usássemos um sexto número, o compilador acusaria erro.
Mas a partir daí estamos por nossa conta e risco. Temos que garantir, por todo o códi-
go, que não existirão tentativas de acesso a elementos não alocados.
Por exemplo (e aqui temos problemas):

primos [ 5 ] = 13 ; acessa memória não alocada.

Pois tudo o que temos são cinco elementos e não seis, como podemos ver nesta tabela:

valor armazenado: 2 3 5 7 11 13
posição: 0 1 2 3 4 5

O vetor foi dimensionado para 5 elementos. Logo, podemos acessá-lo com os índices
de 0 até 4. Ao usarmos o índice 5, estamos tentando acessar um sexto elemento. Mas
não temos memória reservada para acolher esse intruso.
Em conseqüência, é possível que o número 13 esteja sendo escrito em uma área de me-
mória alocada para outra finalidade.
Os resultados desse tipo de erro são imprevisíveis, pois dependem de cada execução.
 Em determinada execução, escrevemos em uma área de memória que não está
em uso (e, na aparência, não temos problemas).
 Em outra execução, escrevemos em uma área de memória reservada para ou-
tro fim, acarretando resultados inesperados.
 Há ainda uma terceira situação: sistemas multi-tarefa (como Windows ou
Unix/Linux) atribuem áreas de memória para cada aplicação que esteja em exe-
cução.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


122 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

 E, se determinada aplicação acessa uma área de memória que não lhe foi ce-
dida pelo SO, a execução é interrompida e o sistema acusa uma tentativa ile-
gal de acesso à memória.
Desse modo, se em determinada execução, o número 13 estiver sendo escri-
to em memória não cedida pelo SO, a aplicação será interrompida imediata-
mente.
Bem, isso é ruim mas é menos grave do que escrever em uma memória ce-
dida pelo SO mas que está em uso para outra coisa...
Pelo menos, ficamos sabendo rapidamente que algo está errado. Ao passo
que se escrevemos em memórias destinadas a outros fins, a aplicação pode-
rá continuar rodando e gerando danos. Apenas mais tarde é que percebere-
mos que temos problemas. E nem sempre será fácil detectar que tudo come-
çou com aquele mísero "13".

Já se estivéssemos usando std::vector, poderíamos evitar isso.

Se fizéssemos: std::vector< int > primos; também teríamos o mesmo


[Link]( 5 ); problema.
primos [ 5 ] = 13 ;

Mas std::vector oferece outro meio de acesso aos elementos do vetor além do opera-
dor [ ] : a função at e pré-verificação da dimensão com a função size. Como veremos,
será possível evitar facilmente esse tipo de engano, bastando para isso usar o recurso
adequado sempre que exista um perigo potencial de acesso à memória não alocada.

 Detalhes importantes que precisaremos examinar:

- vetores com dimensões variáveis;


- como garantir (quando possível) um uso seguro de vetores no estilo "C".
Veremos isso no capítulo "referência técnica", seção 13.5, página 409.

 Só use vetores no estilo "C" com dimensões fixas (constantes) e que usem
pouca memória, em contextos absolutamente seguros.
Na dúvida, use std::string e std::vector. Além do que já foi dito, eles ofere-
cem um conjunto de recursos que não encontraremos, nativamente, em veto-
res no estilo C.
Veremos mais detalhes sobre os seus usos no capítulo 9, página 265.

4.7.4 ▪ Caracteres: aspas simples e duplas


Por convenção, uma cadeia de caracteres constantes é representada por aspas duplas,
incluindo sempre um caractere de terminação(zero binário, ou "terminador zero") ao
seu final. Já um único caractere constante é representado por aspas simples e não inclui
qualquer caractere de terminação pois é um valor inteiro único e não um vetor.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.7 • Vetores • C++ • AGIT informática • 123

Assim, se fizemos (usando aspas duplas):

std::string nome = "Maria" ; OU char nome[ 6 ] = "Maria" ;

Teremos uma cadeia de caracteres (um vetor de elementos do tipo char) à qual é adici-
onada o "terminador zero":
valor: M a r i a 0 (TC)

posição: 0 1 2 3 4 5

Já se fizermos Temos uma única posição de memória para


(usando char c = 'M' ; um único valor inteiro do tipo char.
aspas simples): E nenhum caractere extra é alocado.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


124 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.8 • Revisão do Capítulo 4 • C++ • AGIT informática • 125

4.8 • Revisão do Capítulo 4


4.8.1 ▪ Exercício

Tente resolver o exercício abaixo. Em seguida, compare sua solução com a que
está no Anexo B-4-1, página 444. Caso não entenda, fale com o instrutor.

Enunciado:
 Crie um vetor de inteiros que deverá armazenar todos os números entre um
número inicial e um final, existindo entre eles uma distância denominada "ra-
zão".
 O "inicial", o "final" e a "razão" devem ser informados pelo usuário.
 Após o armazenamento dos números no vetor, o programa deve imprimir to-
dos os elementos do vetor.
 Esses números também devem ser inseridos em uma string, formando uma
cadeia de caracteres. Por isso, o intervalo entre 'inicial' e "final' deve ser compa-
tível com o tipo char (por exemplo, entre 'A' e 'Z').
 Ao final, imprimir: o total de números no intervalo, a soma dos elementos do
vetor e a string formada pelos números.
 O programa deve também permitir que o usuário repita a operação, informando
novos valores.

Resultado que deve ser


impresso:

Passos para atingir o objetivo:


a. Usando um editor ou um ambiente, dentro do diretório-base de exercícios ("cur-
soCPP"), crie um novo diretório para este exercício; por exemplo, "04_tipos". Em
seguida, crie e salve nesse diretório um novo arquivo (por exemplo, "[Link]"),
de modo que teremos algo como:
/cursoCPP/04_tipos/[Link]

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


126 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

b. Para começar, você pode copiar a função "main" do exercício do capítulo anterior:
"02_lacos_while_for" (e que está resolvido na página 438).
A estrutura será semelhante, com solicitações ao usuário dentro de um laço while,
e também teremos "inicial", "final" e "razao".
Mas serão necessárias algumas alterações.
 Antes de main, faça todos os includes necessários:

 iostream, limits, vector e string.


 Em main, use std::vector< int > para criar o vetor de inteiros e std::string para
criar uma string. Por exemplo:
std::vector<int> vec ;
std::string str ;
 Se for usar um flag para o laço de repetição de toda a operação (como, por ex-
emplo, o "while (continuar)" do exemplo citado) defina essa variável como bool
e não como int.
bool continuar = true;
while ( continuar ) { ... }
Mas atenção. Se fizermos:
std::cout << "Quer continuar?\n(0 p/encerrar, 1 p/continuar)\n";
cin >> continuar ;
Como a variável "continuar" agora é bool, std::cin só aceitará zero ou um.
Ao contrário do int, onde aceitávamos zero como falso ou qualquer outro
número como verdadeiro, agora, se o usuário digitar um número diferente de
zero ou um, std::cin irá considerar a entrada inválida. Ficaremos sabendo
disso assim:
if ( std::[Link]() )
// Uma entrada inválida: qualquer coisa diferente de 0 e 1...
 Como vamos incluir os números também em uma string, o "inicial" e o "final"
devem determinar um intervalo de valores compatível com o tipo char.
 Para isso, podemos definir os limites mínimo e máximo para 'inicial' e 'final',
como, por exemplo:
const char inicial_min = 'A' ;
const char final_max = 'Z' ;
Se garantirmos que os valores informados pelo usuário obedecerão a limites
adequados, eles não serão negativos e, além disso, estarão dentro do intervalo
de valores suportados pelo tipo char, e assim serão inseridos adequadamente
na string. Para isso será preciso analisar as entradas de dados:
char inicial, final;
int razao;
std::cin >> inicial >> final >> razao ;
if ( inicial < inicial_min )
// mensagem de erro - e "tente de novo".
if ( final > final_max )
// mensagem de erro - e "tente de novo".
if ( inicial > final )
// mensagem de erro - e "tente de novo".

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.8 • Revisão do Capítulo 4 • C++ • AGIT informática • 127

 A distância entre cada número ("razão") também deve ser analisada:


 Deve ser maior que zero.

if ( razao <= 0 )
// mensagem de erro - e "tente de novo"
 Não pode ser maior que [ final - inicial + 1 ].
if ( razao > (final - inicial + 1) )
// mensagem de erro - e "tente de novo"

 Dimensione o vetor e a string para a quantidade de números existentes no in-


tervalo informado, sabendo-se que essa quantidade pode ser determinada as-
sim:
// Quantidade total de números no intervalo:
unsigned int quant_num = (final-inicial+razao) / razao ;

 Obs.: a conversão do resultado das operações aritméticas acima, o qual


terá o tipo "int" (o tipo de 'razao') para a variável "quant_num", que é
"unsigned int", seria insegura caso pudesse ter um valor negativo.
Além disso o resultado não pode ser zero, pois servirá para dimensionar
o vetor e a string.
Mas, neste caso, nada disso acontecerá (e teremos uma conversão segu-
ra) se forem feitas todas as consistências indicadas acima.
Assim, se fizermos todas as análises relacionadas acima, teremos:

 A garantia de que 'inicial' e 'final' estarão dentro de um intervalo positivo


e que 'inicial' não será maior que 'final'.
 A garantia de que 'razao' não pode ser maior que ('inicial-'final'+1) e des-
se modo o resultado da operação não será zero.
 Finalmente, garantimos também que 'razão' não pode ser zero(impedin-
do a divisão por zero).
 Assim sendo, você agora pode usar "quant_num", de modo coerente e
seguro, para dimensionar o vetor e a string. Por exemplo:
[Link]( quant_num ) ; // Dimensiona o vetor (garantimos que
// a dimensão é maior que zero).
[Link]( quant_num ); // Dimensiona a string (idem).

 Em um laço for, armazene no vetor todos os números entre inicial e final (inclu-
sive estes), e insira os mesmos na string. Por exemplo:
unsigned int index ;
for ( index = 0 ; inicial <= final ; inicial = inicial +razao )
// Mas isso pode levar a uma warning do compilador. É melhor fazer:
char r = char(razao) ; 
for ( index = 0 ; inicial <= final ; inicial = inicial + r  )

 Esta conversão visa evitar que o último segmento do laço for


[ inicial = inicial + razao ] receba uma eventual warning do compilador,
pois, 'razao' é int, e a atribuição será feita em 'inicial' que é char.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


128 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

 Observe que, como "razao" é int, poderia conter um valor que, somado
a 'inicial', não coubesse em um char - e a atribuição está sendo feita na
própria 'inicial' que é char. Logo, teríamos: [ <char> (inicial) = <int> ].
 Acima, ao analisar e vetar valores incompatíveis, garantimos que isso
não poderá acontecer, mas o compilador não é obrigado a analisar
toda a lógica do programa.
Ele apenas verifica uma possível truncagem de um determinado valor.
 Por isso, ao invés de [ inicial = inicial + razao ], fazemos
[ inicial = inicial + r ], com todos os tipos compatíveis:
for ( index = 0 ; inicial <= final ; inicial = inicial + r  )
{
vec[ index ] = inicial ; // Conversão de char para int: segura.
str [ index ] = inicial ; // Alimenta a string (OK: 'inicial' é char).
index = index + 1; // Evoluir o índice.
}

 Em um novo laço for, some e imprima os elementos do vetor. Por exemplo:


for ( index = 0 ; index < [Link]() ; index = index + 1 )
{
// somar vec [ index ] a um acumulador
// imprimir vec[ index ]
}

 Ao final, antes de perguntar ao usuário se ele deseja continuar, imprima:


 A quantidade de números no intervalo: quant_num.

 O resultado da soma acumulada no último laço for.

 A string alimentada no primeiro laço for.

c. Compilar
d. Executar.

 Compare o que você fez com a solução da apostila: página 444.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.8 • Revisão do Capítulo 4 • C++ • AGIT informática • 129

4.8.2 ▪ Questões para revisão


Responda às questões abaixo, assinalando todas as respostas corretas (uma ou
mais). Em seguida, compare suas respostas com as respostas localizadas no Ane-
xo B-4-2, página 448. Caso não entenda, encaminhe as dúvidas ao instrutor.

Cap.4 - 1. Assinale as respostas corretas, considerando estas declarações:


long i ;
float f ;
a.  'i'(long) tem o mesmo tamanho de 'f'(float): 4 bytes. Logo podemos dizer
que não há qualquer diferença quanto ao modo como essas variáveis
serão representadas na memória.
b.  'i'(long) tem uma forma de armazenamento diferente de 'f'(float), já que
esta é representada na memória através de ponto flutuante. Logo, ape-
sar de terem o mesmo tamanho, são representadas de modo diferente.
Mas isso não tem nenhuma implicação prática.
c.  A forma de armazenamento de 'f'(float), através de ponto flutuante, torna
o seu acesso mais lento do que o acesso a números inteiros.

Cap.4 - 2. Assinale as respostas corretas, considerando o código abaixo:


const int constante ;
//...
constante = 5 ;
a.  As duas linhas do código acima estão corretas.
b.  A primeira linha do código acima está incorreta, pois uma declaração
const exige inicialização. A segunda linha está correta.
c.  A primeira linha do código acima está incorreta, pois uma declaração
const exige inicialização. A segunda linha também está incorreta, pois
constantes podem apenas ser inicializadas mas não podem sofrer atri-
buições.

Cap.4 - 3. Assinale as respostas corretas, considerando o código abaixo:


int variavel ;
//...
variavel = 5 ;
a.  As duas linhas do código acima estão incorretas.
b.  A primeira linha do código acima está incorreta, pois uma declaração de
variável exige inicialização obrigatoriamente. A segunda linha está cor-
reta.
c.  As duas linhas do código acima estão corretas.

Cap.4 - 4. Assinale as respostas corretas, considerando estas declarações:


double d = 10.9 ;
int i = d ;

a.  O código acima não apresenta nenhum tipo de problema.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


130 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

b.  A variável 'i'(int) não tem suporte a ponto flutuante. Logo o valor de 'd'
(10.9) será truncado para 10 ao ser copiado para 'i'. Isso pode ser um
problema, dependendo da situação em que ocorra.

Cap.4 - 5. Assinale as respostas corretas, considerando estas declarações:


double d = double (4) * 1024 * 1024 * 1024 ;
long i = d ;
// OBS.: o resultado de [ double (4) * 1024 * 1024 * 1024 ]
// é [Link].
a.  O código acima não apresenta nenhum tipo de problema.
b.  O valor de 'd' (double) será truncado ao ser copiado para 'i' (long)

Cap.4 - 6. Assinale as respostas corretas, considerando o código abaixo:


std::string nome ;
nome = "Maria" ;
nome = nome + " Aparecida";
a.  As três linhas do código acima estão corretas e não implicam em ne-
nhum tipo de problema.
b.  As três linhas do código acima estão incorretas..
c.  A terceira linha do código acima pode implicar em um problema, escre-
vendo em memória ainda não alocada.

Cap.4 - 7. Assinale as respostas corretas, considerando o código abaixo:


std::vector <int > vetor ;
vetor [ 0 ] = 10 ;
a.  As duas linhas do código acima estão corretas e não implicam em ne-
nhum tipo de problema.
b.  As duas linhas do código acima estão incorretas..
c.  A primeira linha do código acima está correta. A segunda linha apresen-
ta um problema potencial, já que "vetor" não foi dimensionada e há um
acesso ao seu primeiro elemento ([0]). Antes disso, alguma coisa deve-
ria ter sido feita, como, por exemplo: [ [Link]( <dimensão> ) ; ].
Desse modo a execução da segunda linha implica em resultados impre-
visíveis (ou indetermináveis).

Cap.4 - 8. Assinale as respostas corretas, considerando o código abaixo:


enum Meses { Janeiro=1, Fevereiro, Marco, Abril, Maio, Junho, Julho,
Agosto, Setembro, Outubro, Novembro, Dezembro } ;
std::vector < std::string > vec ;
[Link]( Dezembro ) ;
vec[ Janeiro ] = "Janeiro" ;
// faz o mesmo para Fevereiro, Marco... até vec[Dezembro]="Dezembro";
unsigned int index;
for ( index = 0 ; index < [Link]() ; index = index + 1 )
std::cout << vec[ index] << "\n" ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


4.8 • Revisão do Capítulo 4 • C++ • AGIT informática • 131

a.  Todas as linhas do código acima estão corretas e não implicam em ne-


nhum tipo de problema.
b.  Todas as linhas do código acima estão incorretas.
c.  Não é possível fazer: [ std::vector < std::string > vec ; ].
Só são possíveis os usos de std::vector para tipos básicos como:
[ std::vector < int > vi ; ] ou [ std::vector < double > vd ; ].
d.  Não é possível fazer: [ [Link]( Dezembro ) ; ].
e.  Não é possível fazer: [ vec [ Janeiro ] = "Janeiro" ; ].
f.  A expressão [ [Link]( Dezembro ) ; ] está correta pois "Meses"
pode ser convertido para int. Nesse caso, seria o mesmo que:
[ [Link]( 12 ) ; ].
g.  A expressão [ vec [ Janeiro ] = "Janeiro" ; ] está correta pois "Meses"
pode ser convertido para int. Nesse caso, seria o mesmo que:
[ vec [ 1 ] = "Janeiro" ; ].
h.  Caso não ocorra um erro de execução, o laço for do código acima irá
imprimir:
Janeiro
Fevereiro
// Marco, Abril ... Novembro
Dezembro.
i.  Caso não ocorra um erro de execução, o laço for do código acima irá
imprimir:
// <uma string vazia>
Janeiro
Fevereiro
// Marco, Abril ... Novembro
Dezembro.
j.  Caso não ocorra um erro de execução, o laço for do código acima irá
imprimir:
// <uma string vazia>
Janeiro
Fevereiro
// Marco, Abril ... Outubro
Novembro.
k.  É possível que ocorra um erro de execução na linha:
[ vec [ Dezembro ] = "Dezembro" ; ], ou em algum momento qualquer
após sua execução.
Isso porque o vetor foi dimensionado para 12 elementos e deve ser
acessado com os índices de 0 a 11.
Mas, nessa linha, está sendo acessado com o índice 12 - pois esse será
o resultado da conversão da constante Dezembro, do tipo Meses, para
int. Logo, está tentando acessar um décimo-terceiro elemento, para o
qual não há memória alocada.
Caso isso atinja uma área de memória não reservada para a aplicação
em um sistema multi-tarefa, o SO irá interromper a aplicação.
Do contrário, teremos resultados indeterminados em operações seguin-
tes, pois é possível que essa operação esteja escrevendo em memória
alocada para outra finalidade.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


132 • AGIT informática • C++ • • Capítulo 4 ▪ Memória: tipos

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 5 ▪ Fluxo de processamento • C++ • AGIT informática • 133

• Capítulo 5
▪ Fluxo de processamento

Neste capítulo iremos rever e aprofundar alguns dos tópicos vistos nos
capítulos anteriores, acrescentando também novos elementos. Veremos:
- Detalhes sobre funções: parâmetros e retornos.
- O escopo de memórias em função do local de sua declaração.
- Operações e operadores: precedência de operadores.
- Mais informações sobre tomadas de decisão.
- E novos aspectos sobre laços.

5.1 • Tópicos de destaque neste capítulo ....................................135


5.2 • Linhas de instrução; declarações e operações ......................137
5.3 • Operadores .....................................................................138
5.3.1 ▪ Resto de divisão .............................................................................140
5.3.2 ▪ Operadores compostos ..................................................................141
5.3.3 ▪ Operações lógicas .........................................................................143
5.3.4 ▪ Em conclusão .................................................................................145
5.4 • Precedência de operadores................................................145
5.5 • Finalização de uma linha de instrução.................................146
5.6 • Comentários....................................................................147
5.7 • Funções..........................................................................148
5.7.1 ▪ Parâmetros e valor de retorno de uma função................................148
5.7.2 ▪ Funções sem parâmetros e/ou valor de retorno..............................149
5.7.3 ▪ A função main.................................................................................150
[Link] ▪ Retorno de main..........................................................151
[Link] ▪ Parâmetros de main.....................................................152
5.7.4 ▪ Protótipos de funções.....................................................................154
5.7.5 ▪ Arquivos header .............................................................................155
5.8 • Escopo de uma função e escopo global................................156
5.9 • Tomadas de decisão .........................................................157
5.9.1 ▪ if / else ............................................................................................ 157
5.9.2 ▪ switch ............................................................................................. 159
5.10 • Laços..............................................................................160
5.10.1 ▪ Porque usar laços estruturados .....................................................160
5.10.2 ▪ Laço do ... while..............................................................................162

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


134 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

5.10.3 ▪ Detalhes sobre todos os laços .......................................................162


5.11 • Revisão do Capítulo 5 .......................................................164
5.11.1 ▪ Sintetizando as regras básicas para escrita de código...................164
5.11.2 ▪ Exercício 1......................................................................................164
[Link] ▪ Iniciando o exercício ...................................................166
5.11.3 ▪ Exercício 2 .....................................................................................174
5.11.4 ▪ Questões para revisão ...................................................................175

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.1 • Tópicos de destaque neste capítulo • C++ • AGIT informática • 135

5.1 • Tópicos de destaque neste capítulo

 Revendo os princípios básicos de programação e detalhando


sua implementação em C e C++.

 Linhas de instrução e operações.


 Fluxo de processamento: seqüência de instruções, tomadas de
decisão e desvios.

 Mais sobre operadores.

 Operadores compostos.
 Operadores lógicos.
 Resto de divisão.
 Bitwise and.
 Precedência de operadores.

 Fluxo de processamento: funções.

 Em C e C++, todas as linhas de instrução são escritas dentro de


funções.
 Uma função é uma unidade básica de processamento e alocação
de memória.
 Uma função pode chamar (colocando em execução) uma outra
função qualquer.
 Funções podem (ou não) receber argumentos e retornar valores.

 Memória: escopos.

 Memórias podem ser reservadas dentro ou fora de funções, o que


determina o seu escopo.
 O escopo de uma memória reservada determina quem poderá
acessá-la (sua visibilidade) e por quanto tempo ela permanecerá
reservada (seu tempo de vida).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


136 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

Tópicos de destaque, continuando...

 Fluxo de processamento: seqüência de instruções, tomadas de


decisão e desvios.

 Uma linha de instrução é composta por uma ou mais operações,


onde cada operação depende de um operador que pode ter uma
precedência superior a outros.
 Se nada de especial ocorrer, as linhas de instrução serão executa-
das linearmente, uma após a outra.
 A execução seqüencial das instruções será quebrada quando hou-
ver uma decisão a tomar (por exemplo, quando comparamos os
valores armazenados em duas diferentes áreas de memória).
 Comparações (ou, em geral, a avaliação de uma expressão) re-
presentam tomadas de decisão, que implicam em desvios no fluxo
de processamento, levando a caminhos diferentes.
 Veremos com mais detalhe as tomadas de decisão com
[ if ( <condição> ) ... else ... ].

 Fluxo de processamento: laços.

 Quando é necessário repetir a execução de uma seqüência de ins-


truções por um certo número de vezes, precisamos que o fluxo de
processamento volte ao início dessa seqüência até que uma de-
terminada condição seja satisfeita, e a repetição deva ser encer-
rada.
 Essas repetições são denominadas laços.
Em C e C++ temos algumas palavras reservadas que permitem
criar laços de modo muito simples.
Além do while e do for, analisados no capítulo anterior, veremos
todos os controles de laço e alguns de seus detalhes.

 Protótipo de funções e arquivos header.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.1 • Tópicos de destaque neste capítulo • C++ • AGIT informática • 137

Como já vimos, o fluxo de processamento é baseado em seqüência de instruções, to-


madas de decisão e desvios.

5.2 • Linhas de instrução; declarações e operações


Linhas de instrução são constituídas por declarações e operações.
 As declarações representam pedidos de reserva de memória para um deter-
minado tipo de dados.
 As operações envolvem operadores e operandos (ou argumentos da opera-
ção) e executam uma determinada computação.
 Tanto declarações como operações podem ser encadeadas de diferentes mo-
dos em uma mesma linha de instrução.

Exemplos com declarações e operações únicas ou encadeadas:

Declarando memórias do Idem. Mas inicializa 'a', Declarando memórias em


mesmo tipo em uma única ao atribuir-lhe um valor diferentes linhas de
linha de instrução: na própria linha de sua instrução:
declaração:
  
Declarações: Declarações: Declarações:
int a , b , c , d ; int a = 10 , b , c , d ; int a = 10 ; // inicializa
// inicializou 'a'... int b;
int c;
int d;
  
Operações: Operações: Operações:
a = 10 ; // uma operação
b = a ; // idem b=a; // as mesmas
c = a + b ; // duas c=a+b; // do quadro à esquerda
d = a + b – c ; // três d=a+b–c;

Todos os exemplos acima implicam em pedidos de reserva de memória, seguidos de


operações cuja natureza é revelada pelos seus operadores (os símbolos das
operações), os quais usam essas memórias como argumentos ou (operandos).
 Ou seja, a memória é constituída por um conjunto de posições, cada qual repre-
sentada por um endereço, do mesmo modo que uma rua é constituída por um
conjunto de casas, cada qual com o seu número(endereço).
 Associamos nomes a essas posições simplesmente como mnemônicos.

 E, uma vez declaradas as variáveis e constantes necessárias,


 as operações irão simplesmente ler (variáveis e constantes), recuperando o
seu valor;
 e/ou alterar (variáveis) os valores armazenados nas posições de memória já
reservadas.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


138 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

Os exemplos da tabela acima poderiam ser visualizados assim:

a = 10 ; // (0) 10 é copiado para 'a'


b=a ; // (1) conteúdo de 'a'(10) é copiado para 'b'

Endereços (hipótese) 1000 1004 1008 1012


Apelidos (símbolos) “a” "b" "c" "d”
Valor armazenado 10 10 ? ("lixo") ? ("lixo)

(0) (1)
constante 10 é copiada para 'a' copia para

c = a + b ; // (2) soma o conteúdo de 'a'(10) ao conteúdo de 'b'(10)


// (3) o resultado da soma(20) é copiado para 'c'

“a” "b" "c" "d”


10 10 20 ? ("lixo)

(2) (3)
soma: 10(a) + 10(b)  20 copia para

d = a + b - c ; // (4) soma o conteúdo de 'a'(10) ao conteúdo de 'b'(10)


// (5) subtrai o conteúdo de 'c'(20) do resultado da soma(20);
// (6) o resultado da subtração(0) é copiado para 'd'

“a” "b" "c" "d”


10 10 20 0

(5) (6)
(4)
subtrai: copia para
soma: 10(a) + 10(b)  20
20(soma) - 20(c)  0

Operações (ou expressões) são a base para a criação de linhas de instrução, e, desse
modo, para a criação de programas de computador. E operações dependem de opera-
dores.

5.3 • Operadores
Acima, já vimos algumas operações e seus operadores. Para os próximos exemplos e
exercícios precisaremos de novos operadores, conforme a tabela abaixo:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.3 • Operadores • C++ • AGIT informática • 139

Mais alguns operadores

Operadores aritméticos:
% Resto de divisão en- [ x % y ] ; retorna o resto da divisão
tre inteiros (módulo) de 'x' por 'y'

Operadores aritméticos para operações executadas bit a bit (bitwise):


& and [ x & y ] ; operação and, executada bit a bit
entre 'x' e 'y'

Operadores compostos (operações aritméticas seguidas de atribuição)


+= soma e atribui [ x += y ] ; soma 'x' a 'y' e armazena resul-
tado em 'x'; equivale a [ x = x + y ];
-= subtrai e atribui [ x -= y ] ; subtray 'y' de 'x' e armazena re-
sultado em 'x'; equivale a [ x = x - y ];
*= multiplica e atribui [ x *= y ] ; multiplica 'x' por 'y' e armazena
resultado em 'x'; equivale a [ x = x * y ];
/= divide e atribui [ x /= y ] ; divide 'x' por 'y' e armazena re-
sultado em 'x'; equivale a [ x = x / y ];
%= retorna resto da divi- [ x %= y ] ; armazena o resto da divisão de
são e atribui 'x' por 'y' em 'x'; equivale a [ x = x % y ];
&= and bit a bit e atribui [ x &= y ]; and bit a bit entre 'x' e 'y'; armaze-
na resultado em 'x'; equivale a [ x = x & y ];
++ incremento (+1) [ ++x ] ou [ x++ ] ; soma 1 a 'x' e armazena
resultado em 'x'; equivale a [ x = x + 1 ];
-- decremento (-1) [ --x ] ou [ x-- ] ; subtrai 1 de 'x' e armaze-
na resultado em 'x'; equivale a [ x = x - 1 ];

Operadores lógicos:
&& and ("e" lógico) [ x && y ] ; operação and lógica: 'x' e 'y' são
verdadeiros?
|| or ("ou" lógico) [ x || y ] ; operação or lógica: 'x' ou 'y' são
verdadeiros?
! not (negação) [ !x ] ; se 'x' é verdadeiro (diferente de zero)
retorna falso; se 'x' é falso (igual a zero), re-
torna verdadeiro.

Outros operadores:
( <o> ) Aumenta a precedência da operação entre parênteses.

 A tabela completa de operadores comuns a C e C++ está no “guia de


consulta rápida”, seção 2, página 487.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


140 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

5.3.1 ▪ Resto de divisão


O resto da divisão entre números inteiros pode ser obtido de duas maneiras:
int x, y, z ;
// ...
z=x%y; Retorna o resto da divisão de 'x' por 'y', usando o opera-
dor de módulo, e o armazena em 'z'.
Obs.: 'x' e 'y' podem conter quaisquer valores inteiros.
z = x & (y-1) ; Retorna o resto da divisão de 'x' por 'y' , usando o ope-
rador and bit a bit, e o armazena em 'z'.
Obs.: 'y' deve ser uma potência de dois. Nesse caso (e
apenas nele), o bitwise and é operado entre 'x' e a po-
tência de dois menos um, obtendo o resto da divisão.

Por exemplo, se queremos saber se um número é par, apuramos o resto da divisão


desse número por 2:
int x ;
//...
if ( x % 2 == 0 ) // se o resto da divisão de 'x' por 2 é zero:
std::cout << "o numero " << x << " é par\n" ;
else // do contrário:
std::cout << "o numero " << x << " é ímpar\n" ;

Contudo, como 2 é uma potência de 2, poderíamos obter esse resultado mais rapida-
mente usando o and bit a [Link] 'x' e 1 (ou seja, 2-1):
if ( (x & 1) == 0 ) // se o resto da divisão de 'x' por 2 é zero:
std::cout << "o numero " << x << " é par\n" ;
else // do contrário:
std::cout << "o numero " << x << " é ímpar\n" ;

Do mesmo modo, se quiséssemos saber o resto da divisão de 'x' por 4, poderíamos usar
o and bit a [Link] 'x' e 3 (ou seja, 4-1):
if ( (x & 3) == 0 ) // se o resto da divisão de 'x' por 4 é zero:
std::cout << "o numero " << x << " é divisível por 4\n" ;
else // do contrário:
std::cout << "o numero " << x << " não é divisível por 4\n" ;

Uma operação bit a bit é executada mais rapidamente (principalmente no caso de ope-
rações que envolvem divisão e, por decorrência módulo: é a mais lenta das opera-
ções aritiméticas).

 Veremos as propriedades e usos das operações bitwise no capítulo "referên-


cia técnica", seção 13.3.4, página 349.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.3 • Operadores • C++ • AGIT informática • 141

5.3.2 ▪ Operadores compostos


Para operações aritméticas cujo resultado deve ser atribuído (armazenado) em uma va-
riável, podemos, em alguns casos, usar os operadores compostos.
Por exemplo:

int x , y , z ; Declara. // ...em seguida x, y e z receberão valores...


x=x+2; Somar 'x' e 2 Depois armazenar o resultado na própria 'x'
x=x+y; Somar 'x' e 'y' Depois armazenar o resultado na própria 'x'
x=y+z; Somar 'y' e 'z' Depois armazenar o resultado em 'x'

Observe que nos dois primeiros exemplos de soma e atribuição a variável 'x' aparece
nas duas operações: um valor é somado a 'x' e o resultado é armazenado na própria 'x'.
Já no terceiro exemplo, temos uma situação diferente: o que é armazenado em 'x' é o
resultado da soma de 'y' e 'z'. Logo, 'x' está presente apenas na operação de atribuição,
não na de soma.
Em conseqüência,as duas primeiras operações poderiam ser escritas assim:

x += 2 ;  Somar primeiro (+), Ou seja:


atribuir depois (=).  some 2 a 'x'

 e depois armazene o resultado em 'x'

x += y ;  Idem, para a soma de 'y' em 'x'.

E o mesmo se aplica às demais operações aritméticas (subtração, multiplicação, divi-


são, etc.).
Observe que o código tornou-se mais compacto e imediatamente legível: fica claro que
a soma e a atribuição envolvem uma mesma variável.
Além disso temos um caso especial para operadores compostos: incremento e decre-
mento. Essas são operações muito comuns: uma variável é afetada pela soma ou sub-
tração do número constante 1.
Nesse caso poderíamos:

x=x+1; Somar 1 a 'x' Depois armazenar o resultado na própria 'x'


x += 1 ; Idem, usando o operador composto.

++x ;  Idem, usando o operador de incremento.

x=x-1; Subtrair 1 de 'x' Depois armazenar o resultado na própria 'x'


x -= 1 ; Idem, usando o operador composto.

--x ;  Idem, usando o operador de decremento.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


142 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

 Além disso os operadores de incremento e decremento podem ser usados nas


formas pré-fixada ou pós-fixada: { [ ++x ; ] ou [ x++ ; ] } e { [ --x ; ] ou [ x-- ; ]}.
Essas duas formas implicam em diferenças de resultado, podendo criar proble-
mas dependendo do caso de uso. Veremos isso no capítulo "referência técni-
ca", seção 13.3.3, página 348.

Detalhes:
Os operadores compostos indicam que o acesso é feito a uma única área de
memória. Veremos mais a frente que isso pode permitir uma otimização do
código, com ganhos de performance.
E no caso de incremento e decremento, além de indicarmos que a operação
é feita em uma única área de memória, estamos indicando também que a
soma ou a subtração envolvem sempre o número 1.
Um compilador pode selecionar instruções de máquina de incremento ou de-
cremento mais eficientes do que as instruções normais de soma e subtração,
o que, obviamente, depende de plataforma. Por exemplo, o x86, possui as
instruções inc e dec que têm justamente essa finalidade.

Exemplos:
Os exemplos com o laço for para somar números e para cálculo de fatorial foram
escritos assim (página 93, acima):

// a) SomaNumeros:
int inicial = 1 , final = 100 , razao = 1, result ;
for ( result = 0 ; inicial <= final ; inicial = inicial + razao )
result = result + inicial ; // acumula para atingir a soma

// b) Fatorial:
int num = 10 , result ;
for ( result = 1 ; num > 1 ; num = num – 1 )

result = result * num ; // acumula para atingir o fatorial

Usando os operadores compostos (ou ++ e --) poderíamos escrever assim:

// a) SomaNumeros:
int inicial = 1 , final = 100 , razao = 1, result ;

for ( result = 0 ; inicial <= final ; inicial += razao ) //  +=


result += inicial ; //  +=

// b) Fatorial:
int num = 10 , result ;

for ( result = 1 ; num > 1 ; --num ) //  -- (decremento)


result *= num ; //  *=

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.3 • Operadores • C++ • AGIT informática • 143

5.3.3 ▪ Operações lógicas


As operações lógicas tradicionais (and e or) permitem uma simplificação do código à
medida em que, com elas, podemos reduzir o uso de sucessivos testes de condição in-
dividuais. E, em certos casos, são quase insubstituíveis.

OR (ou) lógico
Considere esta situação: int funcao_qualquer()
{
int x, y, z;
// ...
if ( x > y )
return 0;
if ( x < z )
return 0;
return 1;
}

Analisando esse código, verificamos que: [ caso 'x' seja maior que 'y' ] ou [ caso 'x'
seja menor que 'z' ], o caminho de processamento é o mesmo: [ return 0; ].
Isso poderia ser expresso mais simplesmente com o uso do operador lógico or, que é
simbolizado, em C e C++, por dois pipes: ||
Então o código acima poderia ser escrito conforme o exemplo abaixo, deixando claro
que tanto uma coisa como a outra levam ao mesmo resultado:

int funcao_qualquer()
{
int x, y, z;
// ...
if ( x > y || x < z)  Se [ x > y ] OU [ x < z ]
return 0;
return 1;
}

E, consequentemente, o mesmo se aplica a múltiplas condições:


[ if ( <condição_1> || <condicão_2> || ... || <condição_n> )  <único_caminho> ].

AND (e) lógico


Em outras situações, ao contrário, é preciso que duas (ou mais) condições sejam ver-
dadeiras para que um determinado caminho seja seguido.
Ou seja: nesse caso temos uma conexão and (duas ou mais condições têm que ser ver-
dadeiras), que, em C e C++, é simbolizada, com dois "e-comerciais": &&.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


144 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

Considere esta situação: int funcao_qualquer()


{
int x, y, z; // ...
if ( x > y )
{
if ( x < z )
return 0;
}
return 1;
}

Analisando esse código, verificamos que: [ caso 'x' seja maior que 'y' ] e [ caso 'x' seja
menor que 'z' ], um determinado caminho de processamento será seguido: [ return 0; ].
Ou seja: apenas se as duas condições forem verdadeiras é que teremos um desvio do
fluxo para esse caminho. Desse modo, o código acima poderia ser escrito conforme o
exemplo abaixo, deixando claro que apenas se duas(ou mais) condições forem ver-
dadeiras é que teremos o mesmo resultado:
int funcao_qualquer()
{
int x, y, z; // ...
if ( x > y && x < z)  Se [ x > y ] E [ x < z ]
return 0;
return 1;
}

Not (negação) lógico


O operador not (simbolizado por um sinal de exclamação) transforma um valor verda-
deiro em falso e vice-versa. Exemplos com tipos inteiros e com o bool:
int i = 100 ; i = !i ; Inicialmente "i" é verdadeira (diferente de zero).
Após o not (!) torna-se falsa (valor zero).
int i = 0 ; i = !i ; Inicialmente "i" é falsa (zero). Após o not (!)
torna-se verdadeira (valor um).
bool b = true ; b = !b ; Inicialmente "b" é verdadeira (true). Após o not
(!) torna-se falsa (valor false).
bool b = false ; b= !b ; Inicialmente "b" é falsa (false). Após o not (!)
torna-se verdadeira (valor true).

Outros exemplos com operações relacionais e lógicas:

int x, y, z ; // …
bool r ; Seguem-se operações que alteram "x", "y" e "z"...
r=x>y; Se [ "x" for maior-que "y" ], "r" receberá o valor true. Do
contrário, false.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.3 • Operadores • C++ • AGIT informática • 145

r = x || y ; Se [ "x" ] ou [ "y" ] forem diferentes de zero, "r" recebe-


rá o valor true. Do contrário, false.
r = x && y ; Se [ "x" ] e [ "y" ] forem diferentes de zero, "r" receberá
o valor true. Do contrário, false.
r = x >y || x < z ; Se [ "x" for maior-que "y" ] ou [ "x" for menor-que "z" ],
"r" receberá o valor true. Do contrário, false.
r = x >y && x < z ; Se [ "x" for maior-que "y" ] e [ "x" for menor-que "z" ], "r"
receberá o valor true. Do contrário, false.
r = ! (x > y) ; Se [ "x" for maior-que "y" ], o resultado da comparação
será true. Do contrário, false.
Mas o not inverte esse resultado. Desse modo, "r" rece-
berá false se [ "x" for maior-que "y" ]. Do contrário, true.

5.3.4 ▪ Em conclusão
Uma operação é identificada por seu operador. Até agora vimos:
 O operador de atribuição.
 O operador de chamada de função.
 Os operadores aritméticos (inclusive o operador de módulo e o bitwise and).
 Os operadores relacionais.
 Os operadores compostos (inclusive incremento e decremento).
 Os operadores lógicos and, or e not.
Além disso, precisamos saber que existem regras de precedência para a execução de
operadores, as quais visam a disciplinar operações encadeadas.

5.4 • Precedência de operadores.


Em operações encadeadas, algumas operações são realizadas anteriormente a outras,
mesmo que estejam em uma posição posterior.
Exemplo:
int x = 10 , y = 20 , z =30 , r ;
r=x+y*z ; // a multiplicação é executada antes da soma...

A linha de instrução acima será executada na seguinte ordem:


a. a multiplicação (“y * z”) será executada primeiro;
b. em seguida, o resultado da multiplicação será somado a “x”
c. finalmente, o resultado da soma será atribuído a “r” (ou, dito de outro modo,
copiado para “r”)
Isso é executado assim porque a multiplicação tem uma precedência mais alta do que
a soma e esta, por sua vez, tem precedência mais alta do que a atribuição, representa-
da pelo operador “=” (também chamado operador de cópia).
Poderíamos mudar essa ordem, com o operador “(<operação>)”, que eleva a prece-
dência de uma operação colocada entre parênteses. Se a finalidade desse cálculo exi-
gisse que a soma fosse realizada em primeiro lugar, bastaria fazer:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


146 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

r= (x + y) *z; // a soma é executada antes da multiplicação...

Nessa forma, as operações serão executadas na seguinte ordem:


a. a soma (“x + y”) será executada primeiro;
b. em seguida, o resultado da soma será multiplicado por “z”
c. finalmente, o resultado da multiplicação será atribuído a “r”.

 A tabela de precedência de operadores está no “guia de consulta rápi-


da”, seção 3, página 489. Nessa tabela, além dos operadores comuns a C
e C++, veremos também operadores que só existem em C++.
Informações, conceitos e detalhes sobre operações e operadores podem
ser encontrados no capítulo “referência técnica”, seção 13.3, página 345.

 Considere também que explicitar a precedência de operações com o ope-


rador “( <operação> )”, pode melhorar a legibilidade do código em linhas
de instrução com muitas operações encadeadas.

Exemplo:
a=b+c*d /e*f+g–h;
Digamos que essas operações, em função de sua finalidade, já estejam aí corretamen-
te posicionadas, devido às regras de precedência.
Contudo, ao ler essa linha, perdemos um pouquinho de tempo, pois precisamos analisar
as precedências envolvidas para ter certeza de que está tudo realmente correto.
Essa linha seria mais rapidamente legível se a escrita fosse mais explícita:
a=b+ ( c*d / e *f) + (g–h) ;

5.5 • Finalização de uma linha de instrução.


Como já pudemos observar nos exemplos de código fonte acima (e nas regras básicas
de gramática), linhas de instrução são encerradas com um “;” (ponto e vírgula).
Essa é uma regra básica de C e C++. Isso significa que a linha de texto é uma coisa e
a linha de instrução é outra coisa.

 Linhas de instrução são escritas em editores de texto, formando linhas de


texto, mas não se confundem com estas.

Exemplo: A linha de instrução abaixo poderia ser escrita assim


a=b; a
=
b
;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.5 • Finalização de uma linha de instrução. • C++ • AGIT informática • 147

Embora a segunda forma pareça (e de fato é!) uma escrita mais confusa, ela está sinta-
ticamente correta. No caso, temos uma única linha de instrução em quatro linhas de
texto. Pois o que termina uma linha de instrução é o ponto e vírgula.

5.6 • Comentários.
Este é um assunto sintaticamente trivial. Nada tem a ver com operações e instruções.
Contudo é bom falar nisso desde o início, pois comentários são essenciais na escrita de
um código fonte legível. Já falamos parcialmente sobre isso. Vejamos o que falta.
Temos dois modos para escrever comentários, com os símbolos:
 /* <comentário> */ - válido em C e C++.
 // <comentário > <quebra de linha do texto> - válido em C++ e C99; muitos
compiladores, dependendo das opções de compilação o aceitam em C89.
Exemplos:

/*  inicia um comentário
…..............................
finaliza um comentário  */
Já o símbolo “//” inicia um comentário que é encerrado ao final da linha de texto (new-
line):
//  comentário encerrado ao final da linha de texto  [quebra de linha]

 Comentários são importantes para um melhor entendimento do código, ex-


plicando o sentido de certas operações e rotinas, o qual nem sempre
pode ser deduzido rapidamente do código em si.
Mas isso não significa que eles sirvam para tornar legível um código
confuso.

Por exemplo:
double vp = 100 ; // “vp”, quem diria, significa “valor do produto” 
Ora, por que não escrever claramente, tornando o código auto-explicativo? Assim:
double valor_produto = 100 ; // em princípio, nada a comentar aqui 
Comentários, usados nos lugares necessários, são essenciais, pois o código não é escri-
to apenas para ser compilado (e o compilador despreza comentários), mas também para
ser lido e relido: quase sempre um código precisa ser melhorado ou corrigido.
Logo, o código deve ser escrito da maneira mais legível (clareza!) que seja possível e
deve ter comentários sempre que necessário. Um programa que esteja apresentando er-
ros de execução mas que esteja bem escrito é melhor (porque é mais fácil consertá-lo)
do que um programa que esteja funcionando mas esteja mal escrito (porque um dia
será preciso alterá-lo, e perderemos muito tempo tentando entender o que ele faz).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


148 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

5.7 • Funções.
Acima vimos:
 alocação (reserva) de memória;
 linhas de instrução contendo declarações e/ou operações;
 tomadas de decisão (if ... else...).
E já vimos também onde escrever tudo isso: declarações e instruções devem estar den-
tro de uma função.
 Uma função é o elemento mais importante do controle de fluxo, porque repre-
senta a unidade básica para a escrita de código, permitindo que um determina-
do trecho de código desvie a execução para outro trecho (isto é, uma outra fun-
ção) ordenadamente.
 Assim, escrevemos funções que executam apenas uma pequena tarefa.
 E, quando uma função precisa do serviço prestado por outra, simplesmente
chama essa outra função, transferindo o fluxo de processamento para esta.
 Ao encerrar sua execução, a função retorna ao ponto onde foi chamada (retor-
na para quem a chamou).

Exemplo: função “Minimo” - recebe dois int Minimo ( int a, int b )


valores inteiros e retorna o menor deles. {
Pode ser chamada assim: if ( a < b )
return a ;
int x, y, z ;
else
//...
return b ;
z= Minimo ( x, y) ;
}

Em uma outra função qualquer, quando quisermos saber qual é o menor de dois va-
lores, simplesmente chamamos a função Minimo:

 A linha abaixo, irá transferir o processamento para a função 'Mini-


mo' que executará suas instruções e, ao final, retornará ao ponto de
sua chamada:

z = Minimo ( x, y ) ; // o resultado retornado por 'Minimo'


// será copiado para 'z'

5.7.1 ▪ Parâmetros e valor de retorno de uma função.


Observe, no próprio exemplo da função Minimo, que uma função pode receber argu-
mentos (passados por quem a chama) e armazená-los em variáveis denominadas pa-
râmetros, bem como retornar um valor, que representa o seu resultado formal e que
pode ser memorizado por quem a chamou para uso futuro.
Parâmetros.
Argumentos são objetos, passados por quem chama a função. Servem para que ela te-
nha informações, visando a atingir um resultado de acordo com sua finalidade. Eles são

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.7 • Funções. • C++ • AGIT informática • 149

recebidos pela função em memórias (variáveis) designadas como parâmetros da fun-


ção. No exemplo da função Minimo, esses parâmetros são as variáveis “a” e “b”.

Assim sendo, de acordo com o exemplo acima:


 a função Minimo tem dois parâmetros do tipo int;

 logo deve ser chamada com a passagem de dois argumentos do mesmo


tipo, ou teremos um erro de compilação, pois a função precisa desses dois
argumentos para cumprir sua finalidade.

Valor de retorno.
Além disso essa função retorna um valor o qual representa o resultado do seu traba-
lho. Esse resultado é um valor de determinado tipo.
Acima, no exemplo de chamada da função Minimo, z = Minimo ( x, y ) ;
podemos perceber que ela retorna um valor e também que esse valor pode ser recupera-
do e memorizado por quem chamou a função.

 Em conclusão, uma função é caracterizada por um valor de retorno, um


nome e uma lista formal de parâmetros, a qual é definida pelos parênte-
ses após o nome.

E o exemplo da função Minimo per- Valor de retorno Nome Parâmetros


mite-nos descrever todos os elemen-
tos que fazem parte de uma função: int Minimo ( int a , int b )

5.7.2 ▪ Funções sem parâmetros e/ou valor de retorno.

 Não necessariamente uma função precisa receber argumentos ou oferecer


retornos. Tudo isso depende, exclusivamente, da sua finalidade.

 Contudo, isso não altera a sua sintaxe. Ela continua tendo uma indicação de re-
torno (que pode ser “retorno vazio”) e uma lista formal de parâmetros entre os
parênteses. A lista poderá estar vazia ou não, mas a regra geral é a mesma.

Exemplos:

a. Função que não retorna valor.

Função Imprime; imprime um número inteiro e não retorna um valor:

 // “void” indica que a função não retorna um valor...


void Imprime( int n )
{
// … instruções necessárias para a impressão do número 'n' ...
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


150 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

 Se o retorno da função é definido como void (vazio), isso significa que


ela não retorna valor.

b. Função que não recebe argumentos.

Função IsPrinterOn: não recebe argumentos pois sua finalidade é descobrir


se há uma impressora ligada e ela não precisa de informações de entrada para
descobrir isso. Retorna verdadeiro ou falso, dependendo da situação.

bool IsPrinterOn ( )
{
bool on;
// …instruções necessárias para descobrir se a impressora está ligada…
// a variável “on” assumirá “true” em caso afirmativo - ou “false”.
return on;
}

c. Função que não retorna valor e não recebe argumentos.

Função exit_on_error: verifica se ocorreu algum erro durante a execução.


Caso tenha ocorrido, chama a função "exit" da biblioteca C para encerrar o
programa. Não recebe argumentos, pois as informações de que necessita es-
tão em um indicador de erros (variável global "errno") fornecida pela própria bi-
blioteca. Também não retorna valor, já que simplesmente ou encerra o progra-
ma ou não executa nada.

void exit_on_error()
{
// a variável "errno" da biblioteca C informa o último erro ocorrido;
// se contiver zero é porque não existiram erros.
if ( errno != 0 ) // diferente de zero? então ocorreu um erro...
{
std::cout << "Este programa fez algo errado\n" ;
std::cout << "Encerrando agora\n" ;
exit( -1 ) ; // encerra o programa acusando erro
}
}

5.7.3 ▪ A função main


Como vimos, um programa em C ou C++ tem suas linhas de instrução sempre aninha-
das em funções.
E vimos também que para que um programa seja iniciado é preciso que exista uma
função privilegiada que cumpra o papel de ponto de entrada de uma aplicação exe-
cutável. E essa é a função que deve ter um nome especial: “main” - por convenção da
linguagem.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.7 • Funções. • C++ • AGIT informática • 151

 Um programa executável deve, obrigatoriamente, conter uma e somente


uma função com esse nome especial (main). A execução inicia aí.

Escrevemos nossas funções em arquivos (formalmente denominados como unidades


de tradução ou módulos). Os nomes desses arquivos, normalmente (mas não necessa-
riamente), têm a extensão “.c” (Linguagem C) ou “.cpp” (Linguagem C++). E a fun-
ção main poderá estar em qualquer um dos arquivos envolvidos em um projeto.

 Não importa em qual arquivo ou em qual posição dentro de um arquivo


esteja escrita a função main. Independentemente disso, ela será sempre o
ponto de entrada, dando início à execução do programa.

E dentro de main, isto é, em suas linhas de instrução, outras funções podem ser chama-
das (por exemplo, para executar um menu de opções disponíveis para seleção do usuá-
rio), dando assim seguimento ao programa.
Acima, já vimos vários exemplos de implementação da função main.

 E, lembrando, quando main retorna, o programa é encerrado.

Agora é o momento de acrescentar mais algumas informações. Temos usado a fun-


ção main retornando um inteiro. Mas para que serve esse retorno? Além disso, main
também pode receber argumentos.

[Link] ▪ Retorno de main.

Em todos os exemplos onde a função main já apareceu, temos o seguinte modelo:

int main()
{
 Este modelo (main retorna int) está em conformidade
return 0; com o padrão da linguagem (veremos abaixo porque).
}

Mas talvez você encontre, em algum lugar, exemplos de main com o seguinte modelo:

void main()
{
 Este modelo (main retorna void) está em desacordo
com o padrão da linguagem.
}

No primeiro exemplo desta apostila ("programa mínimo", seção 2.2.1, página 43, aci-
ma) foi exibido o seguinte diagrama que mostra como main começa e encerra: ela é
chamada pelo sistema operacional (e, assim, o próprio programa tem início) e, ao final,
retorna para quem a chamou (o próprio SO):

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


152 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

o sistema operacional
(1) chama main (e a execução inicia)

executa

encerra
(2) retorna para

Na realidade main não é chamada diretamente pelo SO. Há um pequeno trecho de có-
digo, adicionado pelo compilador, que permite que o sistema coloque a aplicação em
execução - e é nesse código adicional que main é chamada.
Mas esse detalhe de implementação não muda o que é sintetizado no diagrama acima:
para todos os efeitos, naquilo que é visível no código fonte, o programa inicia em
main. E, como qualquer função, main retorna para quem a chamou, que, no seu
caso, é o sistema operacional.
A pergunta é: o sistema operacional espera algum valor de retorno?

Isso depende do sistema operacional que esteja em uso. Alguns sistemas operacionais
(Unix/Linux, por exemplo) consideram esse valor de retorno (zero) como uma indica-
ção de que o programa encerrou sem erros.
Dito de outro modo, se um programa é interrompido porque tentou realizar uma opera-
ção ilegal, um código de erro (diferente de zero) será atribuído como o seu valor de re-
torno.
Nesse caso, o programa não teve a chance de concluir e não atingiu o "return 0" que
está no final de main. Mas, se atingir esse ponto, estará indicando um "erro zero" isto
é, ausência de erro.
Existem outros sistemas que desconsideram o retorno de main. Mas, sendo multi-pla-
taforma, a linguagem deve satisfazer a todos - e por isso main deve ter um retorno int
e retornar zero (exceto se quiser acusar deliberadamente uma condição de erro, retor-
nando qualquer outro valor do tipo int).

[Link] ▪ Parâmetros de main.


A função main pode ser implementada com ou sem parâmetros - isso porque há pro-
gramas que esperam receber argumentos (passados na linha de comando, ou por al-
gum outro meio) e também há programas que não têm nada a fazer com argumentos
que eventualmente lhe sejam passados (provavelmente por engano do usuário, já que o
programa não os espera).

Então a função main pode ser implementada de duas formas, ambas corretas:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.7 • Funções. • C++ • AGIT informática • 153

sem parâmetros com parâmetros


int main() int main( int argc , char *argv[] )
{ {
return 0; return 0;
} }

O primeiro parâmetro, "argc", é um inteiro que informa a quantidade de argumentos


que foram passados para o programa e que serão recebidos no segundo parâmetro
("argv"). Por enquanto, ainda não sabemos identificar o tipo do segundo parâmetro
("char * argv[]"). Esse tipo será visto adiante (seção [Link], página 421). Mas, por
enquanto, podemos assumir que se trata de uma lista (vetor) de strings.
E é fácil perceber como funcionaria o tratamento desses parâmetros com um
pequeno exemplo:
a. Dentro do diretório-base de exercícios ("cursoCPP"), crie um novo diretório para
este exemplo; por exemplo, "05_main_arg". No editor de textos, digite o código
abaixo e o salve como "[Link]".
 Desse modo teremos algo como: "cursoCPP/05_main_arg/[Link]"

b. Código fonte:
#include <iostream>
int main( int argc , char *argv[] )
{
int a ;
// percorre a lista de argumentos (o total deles é "argc")
for ( a = 0 ; a < argc ; ++a )
std::cout << argv [ a ] << "\n" ; // imprime um argumento
// - ou seja, uma string da lista de strings "argv"
return 0;
}

c. Compilar.
d. Executar o programa, passando dois argumentos: "testando" e "argumentos".
Se o nome do executável for 05_main_arg, teremos:
Em Windows: 05_main_arg testando argumentos
Em Unix/Linux: ./05_main_arg testando argumentos

Como resultado da 05_main_arg [ no Windows teremos o ".exe" ]


execução, será impresso testando
no monitor de vídeo:
argumentos
> [ próxima posição do cursor no monitor de vídeo ]

Como podemos ver, o primeiro argumento sempre existe: ele é o próprio nome do exe-
cutável ("05_main_arg" ou "05_main_arg.exe", no caso do Windows).
O segundo e terceiro argumentos ("testando" e "argumentos") são aqueles que foram
passados explicitamente na linha de comando ao executarmos o programa.
Há uma série de programas que tratam os argumentos passados na linha de comando. E
também há muitos que simplesmente desprezam o que quer que seja passado.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


154 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

5.7.4 ▪ Protótipos de funções.


Quando uma função chama outra, a função chamada deve ser conhecida pelo compi-
lador. Esta tanto pode estar no mesmo arquivo da chamadora como também em outro
arquivo qualquer. Além disso, se estiver no mesmo arquivo, poderá estar escrita acima
ou abaixo da chamadora. O compilador não olha para baixo nem para os lados...
Quando ele analisa se um determinado símbolo está sendo usado de forma correta, ele
só olha para cima.
Exemplo:
#include <iostream>
// aqui está escrita a função "Fatorial":
int fatorial( int num ) { /* ... */ }
int main ( )
{
std::cout << "fatorial de 5 = " << Fatorial ( 5 ) << "\n";

 OK: o compilador encontra "Fatorial" acima.

std::cout << "soma dos números entre 1 e 10 = " <<


SomaNumeros( 1, 10 ) << "\n";

 Erro: o compilador não encontra a função "SomaNumeros",


pois ela está escrita abaixo.
}
// aqui está escrita a função "SomaNumeros":
int SomaNumeros( int inicial, int final ) { /* ... */ }
Por isso, se uma função chamada estiver escrita abaixo da chamadora (ou até mesmo
em outro arquivo), será preciso instruir o compilador sobre sua sintaxe, isto é,
como essa função deve ser chamada, obedecendo a seu valor de retorno e a seus pa-
râmetros.
Para isso escrevemos protótipos de funções, indicando os seus nomes, valores de re-
torno e suas listas de parâmetros (em outras palavras, determinamos sua sintaxe).
Exemplo:

void Func_1 () ; // protótipo da função ”Func_1”


void Func_2 (int x) ; // protótipo da função ”Func_2”
int Func_3 ( ) ; // protótipo da função ”Func_3”

 Acima, declaramos que, em algum lugar, existirão funções com esses no-
mes, esses valores de retorno e essas listas de parâmetros.
Agora o compilador poderá verificar se elas estão sendo chamadas
corretamente ou não.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.7 • Funções. • C++ • AGIT informática • 155

int main()
{
// chama a função “Func_1”:
Func_1() ; // correto: foi chamada de acordo com seu protótipo.
int a = Func_1() ; //  Erro: tentando copiar retorno “void”...
Func_1( 5 ) ; //  Erro: essa função não recebe argumentos

// chama a função “Func_2”:


Func_2( 5 ) ; // correto: foi chamada de acordo com seu protótipo.
int b = Func_2( 5 ) ; //  Erro: tentando copiar retorno “void”...
Func_1() ; //  Erro: essa função deve receber um argumento int.
// chama a função “Func_3”:
Func_3( ) ; // correto: foi chamada de acordo com seu protótipo;
// e não é obrigatório copiar o seu valor de retorno.
int c = Func_3( ) ; // correto, de acordo com o protótipo.
int c = Func_3( 5 ) ; //  Erro: essa função não recebe argumentos.

return 0 ;
}

// “Func_1” está implementada aqui:


void Func_1() { /* …. */ }
s

// “Func_2” e “Func_3” estão implementadas em outros arquivos.

 No capítulo “referência técnica” veremos em detalhe: funções, chamadas


de função, retornos, passagem de argumentos, protótipos, etc. Seção
13.4.1, página 360.

5.7.5 ▪ Arquivos header


No exemplo acima, vimos os seguintes protótipos de funções:
void Func_1 () ; // protótipo da função ”Func_1”
void Func_2 (int x) ; // protótipo da função ”Func_2”
int Func_3 ( ) ; // protótipo da função ”Func_3”
E, no final, é afirmado que "Func_1" está implementada nesse mesmo arquivo, abai-
xo da função main. Já "Func_2" e "Func_3" estão implementadas em outros arqui-
vos. Nos dois casos, os protótipos permitiram que o compilador pudesse entender que
símbolos são esses e como podem ser usados. Isto é: qual é a regra de sintaxe a ser se-
guida em seu uso (nome, argumentos e retorno cabíveis). Isso permite que possamos
implementar as funções em qualquer lugar, independentemente de onde é chamada.
Mais sobre o papel do compilador e do link-editor: anexo A, página 429.
Podem existir muitos arquivos fonte em uma mesma aplicação, e se ocorrerem chama-
das a essas funções em mais do que um arquivo, teríamos que copiar e, em seguida,
colar esses protótipos em todos os arquivos onde existissem chamadas a elas:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


156 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

arquivo_1.cpp arquivo_2.cpp
int Func_3 ( ) ; // protótipo int Func_3 ( ) ; // o mesmo protótipo
void funcao_do_arquivo_1 ( ) void funcao_do_arquivo_2 ( )
{ {
Func_3( ); // chama "Func_3" ; Func_3( ); // chama "Func_3" também
} }

Isso não seria necessário se tivéssemos colocado esses protótipos em um único lugar
(um único arquivo). Esse arquivo seria então incluído em todos os arquivos fonte onde
existissem chamadas a essas funções.
É por isso que escrevemos [ #include <iostream> ] no topo de todos os fontes que
usamos até aqui. E isso foi feito devido ao uso de cout, que não é uma palavra reserva-
da mas sim um recurso declarado no arquivo iostream. Nesse arquivo encontramos:
ostream cout ; // "cout" é um objeto do tipo "ostream"
O que permite que o compilador saiba o que é e o que não é possível fazer com cout.
Podemos usar o mesmo método. Digamos que, no exemplo acima, criássemos um ar-
quivo denominado "funcoes.h", sendo ".h" a abreviatura de header, isto é, algo que é
incluído no topo ou previamente ao uso. A extensão ".h" é usual, mas não é uma regra
da linguagem: não existe uma regra para nomes e extensões de arquivos.

funcoes. h arquivo_1.cpp arquivo_2.cpp


void Func_1 () ; // protótipo #include "funcoes.h" #include "funcoes.h"
void Func_2 (int x) ; // idem void f_a_1( ) void f_a_2( )
int Func_3 ( ) ; // idem { {
Func_1( ); Func_1( );
Func_2( ); Func_2( );
Func_3( ); Func_3( );
} }

Naturalmente, se uma função é usada em apenas um arquivo fonte não seria necessá-
rio inserir seu protótipo em um header. Bastaria que ele existisse nesse único arquivo.

5.8 • Escopo de uma função e escopo global.


Vimos que uma função é uma unidade de execução no fluxo geral de processamento
de um programa, o que significa que ela executa uma tarefa bem definida. Assim, um
programa nada mais é do que a articulação de diversas tarefas executadas através de
chamadas de função, as quais trabalham com determinados dados (memória).

 E uma função, em C e C++, além de ser uma unidade de execução de


instruções, também é uma unidade de alocação de memória.

Isto é, uma função pode ter memórias próprias, que nenhuma outra função poderá
acessar. Pois tais memórias têm um proprietário: a função que as declarou. Por isso
dizemos que o seu escopo é local.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.8 • Escopo de uma função e escopo global. • C++ • AGIT informática • 157

Além disso, podemos declarar memórias fora de qualquer função, o que significa que
essas memórias não pertencem a nenhuma função em particular (não têm um proprie-
tário). Logo, são públicas ou globais, podendo ser acessadas por qualquer uma das
funções de uma aplicação.
Exemplo:

int g_varGlobal;

 A variável “g_varGlobal” foi declarada fora de qualquer função.

Ou seja: não tem proprietário. Então é pública... Escopo global.


void Funcao_1 ( int x ) ; // protótipo...

int main()
{
int x; // Declarada dentro de main; pertence a main. Escopo local.

 A variável “x” foi declarada em “main”. Logo, pertence a “main”, não


podendo ser acessada por qualquer outra função. Escopo local.
// ...
Funcao_1 ( x ) ; // Chama a função “Funcao_1”.
g_VarGlobal = 1; // Acessa a variável global.
return 0 ;
}

void Funcao_1 ( int x )


{
int a;

 Tanto “x” (parâmetro) como “a” foram declaradas em “Funcao_1”.

Logo, pertencem a “Funcao_1”, não podendo ser acessadas por


qualquer outra função. Escopo local.
g_VarGlobal = 2; // Também aqui podemos acessar a variável global
}

 No capítulo “referência técnica” veremos em detalhe: a conceituação


completa de escopos e também visibilidade e tempo de vida de memó-
rias em função de seus escopos. Seção 13.2, página 333 e seção 13.2.4,
página 338.
.

5.9 • Tomadas de decisão


5.9.1 ▪ if / else
Já vimos, em exemplos acima, avaliações de condição para tomadas de decisão através
de { if (...) ... [ else ... ] }: nas funções "Maximo" e“Minimo”. E também assim:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


158 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

if ( valor_pago < valor_venda )


{
// …...............
}
else
{
// …...............
}

Em sua forma geral, esse controle de fluxo é definido como:

{ if ( <condição > ) ... [ else ... ] }

mas o else pode ser acompanhado de um novo if:


{ if ( <condição> ) ... [ else if ( <condição> ) ... ] [ else ... ] }

Exemplo:

if ( valor_pago < valor_venda )


{
// …...............
}
else if ( valor_pago > valor_venda )
{
// …...............
}
else // ... iguais
{
// …...............
}

Algumas questões básicas a ressaltar sobre o if:


 A condição a avaliar deve estar entre parênteses.
 Não necessariamente o “else" precisa estar presente.
 Se existir um “else" (inclusive na forma combinada "else if") ele não pode apa-
recer isoladamente, devendo sempre ser precedido pelo if.
 Existindo mais do que uma instrução a ser executada caso uma condição seja
verdadeira (“if” ou “else if”) ou falsa (“else”), essas instruções deverão estar en-
tre chaves (como nos dois exemplos acima).
 As chaves delimitam assim um bloco de instruções que é executado em
determinada condição.
 Caso exista apenas uma instrução, as chaves podem ser omitidas, como foi
feito mais acima no exemplo da função “Minimo”.
if ( a < b ) return a ;
else return b ;

 Veremos mais detalhes e exemplos adicionais sobre o if no capítulo “re-


ferência técnica”, na seção [Link], página 401

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.9 • Tomadas de decisão • C++ • AGIT informática • 159

5.9.2 ▪ switch
Para melhor entender em que situações podemos usar este controlador de fluxo, vamos
ver primeiro um exemplo com [ if … else ... ]:
int x ;
// ...
if ( x == 1 )
{
// ...
}
else if ( x == 2 )
{
// ...
}
else
{
// ...
}
O conjunto [ if ... else if ... else ... ] acima, tem as seguintes características:
a. O valor que está sendo analisado é sempre o mesmo. No exemplo acima, esse
valor é o resultado da leitura da variável “x”.
b. Poderia ser o valor resultante de qualquer expressão, desde que seja um valor in-
teiro. Como já sabemos, nessa condição temos: int, char, short, long e enum.
c. O operador empregado é sempre o operador relacional de igualdade, não sendo
usado qualquer outro operador relacional ou lógico.
d. E apenas uma comparação por igualdade é feita em cada uso do if.
e. O valor analisado é sempre comparado a um valor constante (1 e 2, no exemplo).

Nestes casos, podemos usar:


switch ( x )
{
case 1:
// ...
break;
case 2:
// ...
break;
default:
// ...
}
// próxima instrução após o switch

 Nesse exemplo, como o único valor que é avaliado é o valor de 'x', o avaliado é
colocado em um único lugar: switch ( x ).
 Em seguida, o valor de 'x' é testado para as constantes
1 [ case 1: ] e 2 [ case 2: ].

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


160 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

 Se o valor de 'x' bater com um desses valores, será executado o código asso-
ciado ao respectivo rótulo case.
 Do contrário, é executado o código associado ao rótulo default.

 O switch serve para situações simples como essa. Ele é restrito, pois não tem
pretensões de ser um if...
 Por exemplo, se um determinado rótulo case satisfizer a condição e, no código
associado, não houver um desvio break (ou um return), a execução continuará
no código associado ao próximo case ou ao default.
 Assim, se, no exemplo acima, o valor de 'x' fosse 1 e não houvesse o desvio
break, seria executado também o código associado ao rótulo case 2. Do
mesmo modo, se também aqui não houvesse um desvio, seria executado o
código associado ao rótulo default.
Considerando este exemplo:
int x = 1 ;
switch ( x )
{
case 1:
std::cout << "1 - " ;
case 2:
std::cout << "2 - " ;
default:
std::cout << "menor que 1 ou maior que 2" ;
}

Seria impresso:
1 - 2 - menor que 1 ou maior que 2

 Veremos mais detalhes e exemplos adicionais sobre o switch no capítu-


lo “referência técnica”, na seção [Link], página 407

5.10 • Laços.
5.10.1 ▪ Porque usar laços estruturados
Já vimos os laços while (seção [Link], página 84, acima) e for (seção [Link], página
92, acima).

Temos aí dois dos laços estruturados oferecidos por C e C++. Quando dizemos "es-
truturados", isso significa que o seu fluxo não é estabelecido por desvios executados
manualmente pelo programador, e sim por uma estrutura de controle de laço, com
uma sintaxe determinada.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.10 • Laços. • C++ • AGIT informática • 161

 Aviso aos programadores assembler.

Um programador assembler, ao ver construções como o while ou o for pela primei-


ra vez, provavelmente irá considerá-las estranhas. Isso porque, nas linguagens de
máquina, não existem construções assim. Existem apenas saltos.
As linguagens C e C++ também permitem que problemas desse tipo sejam resolvi-
dos por saltos (ou desvios livres).
É o que veremos no exemplo abaixo, onde criamos um laço para o cálculo de fato-
rial através de um desvio direto (goto).
Um laço estruturado, ao ser traduzido para linguagem de máquina, será resolvido
(como não poderia deixar de ser) através de saltos, de modo que, no fim das con-
tas, ficará muito parecido com o exemplo abaixo.

Contudo, considere que, no código fonte, os laços estruturados:


— reduzem a quantidade de erros lógicos potenciais, pois a probabilidade
de ocorrência desse tipo de erro é maior quando saltamos livremente
para qualquer parte do código;
— melhoram enormemente a legibilidade do código.

Exemplos com laços estruturados e laços baseados em saltos.

a. Relembrando o exemplo de laço usando o for para cálculo de fatorial:

int num = 10 , result ;


for ( result = 1 ; num > 1 ; --num )
result *= num ; // instrução a executar se condição verdadeira

b. Exemplo de laço usando o goto (desvio livre) para cálculo de fatorial:

int num = 10 , result = 1 ; // início


AVALIA : //  “AVALIA” é um rótulo que referencia a próxima instrução
// permitindo que um salto desvie o fluxo para este ponto:
if ( num > 1 ) // condição de continuidade
{
result *= num ; // processa...
--num ; // progressão

goto AVALIA ; // volta para a avaliação da condição, permitindo


// a repetição das instruções, em um laço.
}
// quando a condição (num>1) for falsa, o fluxo seguirá abaixo.

 Laços estruturados são melhores do que laços estabelecidos através


de saltos. Reduzem erros lógicos e tornam o código bem mais legível.
Veremos os detalhes sobre o uso do goto, problemas ocasionados por sal-
tos (e até um raro uso aceitável) na seção [Link], página 397.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


162 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

5.10.2 ▪ Laço do ... while


Além dos laços while e for, temos ainda o laço [ do ... while ( <condição> ); ].
Ele é parecido com o while, mas tem uma grande diferença:
int x ; // ...
while ( x > 1 ) do
{ {
// <instruções> ... // <instruções> ...
} } while ( x > 1 ) ;
No while temos uma condição a priori: do ... while : condição a posteriori: isto
isto é, ela é avalida no início do laço, e, é, a condição só é avaliada ao final do
por isso, se estiver inicialmente falsa as laço e com isso as <instruções> serão
<instruções> não serão executadas. executadas pelo menos uma vez.

5.10.3 ▪ Detalhes sobre todos os laços


Temos mais alguns detalhes envolvendo o uso dos laços estruturados. Dois deles mere-
cem atenção especial aqui, pois costumam ser causa de erros comuns em iniciantes:

Associar uma instrução vazia

 Tanto os laços como um if podem ter, como instrução associada, uma ins-
trução vazia.

Exemplos:
int x , y ; // ...

while ( x > y ) ;  if ( x > y ) ; 


x = func ( ) ; x = func ( ) ;

O ponto e vírgula encerra a instrução que o precede. E se não é precedido por nada, en-
cerra uma instrução "vazia"
Nos exemplos acima, como o ponto e vírgula está posicionado logo após os parênteses
que envolvem as expressões a avaliar, ele está encerrando uma instrução vazia que é
aquela que deve ser executada se a condição for verdadeira (é a instrução associada ao
laço ou ao if). No caso dos laços, existem situações em que isso se justificaria. Mas,
neste caso, não. Pois teremos duas possibilidades, ambas indesejadas por princípio:
a. No caso do while, se "x" for inicialmente maior-que 1, a condição será verdadei-
ra e o laço será executado para sempre (laço infinito), já que não há nada que per-
mita que o valor de "x" possa ser alterado, tornando a condição falsa.
 e se "x" for inicialmente menor-que-ou-igual-a 1, a condição será falsa e o
laço nunca será executado.
b. No caso do if se a condição for verdadeira nada será executado (instrução vazia).
Em seguida será executada a próxima linha. O mesmo ocorrerá se a condição esti -
ver falsa (neste caso a instrução vazia deixa de ser executada... dá na mesma).
Certamente, uma construção como essa é resultado de uma distração do programador:
Isto é, certamente esse código deveria ter sido escrito do seguinte modo:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.10 • Laços. • C++ • AGIT informática • 163

while ( x > y ) if ( x > y )


x = func ( ) ; x = func ( ) ;

Ou seja:
a. No caso do while, se "x" estiver inicialmente maior-que 1 e enquanto a função
"func" retornar um valor maior-que 1, o laço continuaria sendo executado.
 Mas, a qualquer momento, "func" poderá retornar um valor menor ou igual a
1, e a próxima avaliação da condição terá resultado falso, encerrando o laço.
b. No caso do if, se a condição for verdadeira teremos uma chamada à função
"func". Do contrário ela não será chamada.
c. Então, em ambos os casos, o ponto e vírgula após os parênteses só pode ter sido
fruto de uma distração.

Atribuição ao invés de comparação por igualdade


Outro erro comum em iniciantes, é usar o operador de atribuição no lugar do operador
de comparação por igualdade.
if ( a = b ) // Isso foi intencional? Copiar o valor de 'b' para 'a'
// e depois testar se o novo valor de 'a' é verdadeiro?
// ou foi uma distração e na verdade você queria fazer isso:
if ( a == b ) // comparação por igualdade: dois sinais [ == ]
// do mesmo modo:
while ( a = b ) { /*....*/ }

Veremos que, em certas situações, no caso específico dos laços, o uso da atribuição po-
deria ter uma justificativa. Mas, por princípio, tome muito cuidado com isso, princi-
palmente enquanto você não tiver adquirido experiência com a linguagem.

 Por enquanto, o importante é alertar para esses erros muito comuns em


programadores iniciantes na linguagem.

 Veremos mais detalhes e exemplos de uso dos laços estruturados no


capítulo “referência técnica”:

laço for: seção [Link], página 382.


laço while: seção [Link], página 386.
laço do ... while: seção [Link], página 389.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


164 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

5.11 • Revisão do Capítulo 5


5.11.1 ▪ Sintetizando as regras básicas para escrita de código.
Relembrando alguns dos quesitos básicos para a escrita de código em C e C++::
a. Criar um ou mais módulos(ou unidades de tradução): um módulo é um arquivo
texto com extensão “.c” (linguagem C) ou “.cpp” (linguagem C++), ainda que isso
seja apenas uma praxe e não uma regra da linguagem (e há quem use outras ex-
tensões para arquivos fonte C++).
b. Dentro de cada módulo, criar funções: uma função é um bloco onde podemos es-
crever instruções.
c. Definir qual dessas funções representará o início ou ponto de entrada da aplica-
ção. Essa função especial e única deverá receber o nome main.
d. Dentro das funções, escrever linhas de instrução.
A figura ao lado mostra o aspecto de um programa
mínimo usando regras comuns às linguagens C e
C++ (exceto <iostream> e cout, de C++).
 Há um único módulo: o arquivo [Link].
 O módulo tem uma única função: main. E, por
ter esse nome especial, ela é o ponto de entrada
da aplicação.
 A função tem apenas duas linhas de instrução:
 uma operação de chamada a uma outra função: o operador "<<" , que, jun-
tamente com o cout, devem fazer parte de alguma biblioteca externa, já que
suas declarações não estão visíveis neste fonte;
 return 0 - é o retorno da função.

5.11.2 ▪ Exercício 1
Tente resolver o exercício abaixo. Em seguida, compare sua solução com a que
está no Anexo B-5-1, página 453. Caso não entenda, fale com o instrutor.

Enunciado: criar um projeto com dois módulos e testar várias funções.

 Neste exercício serão criados duas unidades de tradução (ou módulos), isto é,
dois arquivos fontes.
 Um deles (por exemplo, [Link]) deve conter apenas a função main.

 O outro (por exemplo, [Link]) deverá conter diversas funções que re-
alizarão tarefas específicas.
 main deverá testar todas as funções implementadas no módulo [Link].
 As funções que devem ser escritas no módulo [Link] (e que serão deta-
lhadas abaixo) são:
 double Fatorial ( int num ) ;

Calcula o fatorial de um número inteiro. Retorna double porque o resulta-


do pode não caber em um int.
 double Potencia ( int base, int exp ) ;

Calcula o resultado de "base elevada a expoente". Retorno: idem.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.11 • Revisão do Capítulo 5 • C++ • AGIT informática • 165

 int PA_TotalTermos ( int inicial, int final, int razao ) ;


Calcula o total de termos de uma Progressão Aritmética (não a sua soma).
 void ImprimePares ( int inicial , int final ) ;
Imprime todos os números pares entre "inicial e final".
 double DobraValor ( int ultimo_dia ) ;
Problema básico/clássico de matemática: se você ganha 1 real no primeiro
dia do mês e dobra esse valor a cada dia (2 reais no segundo dia, 4 reais
no terceiro, etc), quanto irá ganhar no último dia (por exemplo, 31)?
 double DobraValor_for ( int ultimo_dia ) ; O mesmo usando um laço for.
 double TotalCombinacoes ( int conjunto, int escolhas ) ;
Retorna o total de combinações únicas em qualquer ordem, considerando-
se uma quantidade de escolhas em um conjunto de alternativas. Por exem-
plo, para saber as possibilidades de acerto na mega-sena: C(60,6).
Resultados que devem ser impressos nos testes da função main e na função
ImprimePares (ou outros resultados semelhantes):

Observe que os testes de Fatorial e Potencia também foram feitos com um laço for.
E os testes de "PA_TotalTermos" procuram explorar alguns casos de uso em que a
função poderia falhar.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


166 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

Resultados (continuando a exibição):

Nos testes de ImprimePares, foram usados alguns intervalos visando a demonstrar


se a função apresenta resultados corretos em todos os casos:
- inicial ímpar e final ímpar, inicial ímpar e final par, inicial par e final ímpar, inicial par e
final par - e também: inicial igual a final e ambos pares, inicial igual a final e ambos ím -
pares, inicial maior que final.

 Mesmo que você não saiba como implementar todas as funções descritas
acima, tente resolver algumas.
Abaixo, teremos algumas dicas para que você possa tentar resolver o
maior número delas.

[Link] ▪ Iniciando o exercício


Mostraremos aqui os primeiros passos, usando o QtCreator e o Visual C++.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.11 • Revisão do Capítulo 5 • C++ • AGIT informática • 167

Para começar, vamos definir um nome para o projeto (daremos uma sugestão; você po-
derá usar qualquer outro nome).
Este exercício permitirá que você explore muitos dos elementos que vimos até aqui:
 Tipos.
 Operadores em geral.
 Testes de decisão.
 Laços.
Além disso, teremos um elemento novo:
 Neste exercício, pela primeira vez, será usado mais do que um arquivo fonte.
 Com isso estaremos mais próximos de uma situação real.
 E, ainda, isso permitirá exercitar o uso de protótipos de funções, que vimos
neste capítulo.
 Para marcar esse fato novo, podemos denominar este projeto como:
"06_dois_modulos".

Usando o QtCreator

 Iniciar a criação do projeto.

 Escolher o tipo de projeto:


Tipo de projeto: Qt4 Console Application
Você pode escolher também Empty Qt4
Project - mas neste caso não esqueça de al-
terar o arquivo de projeto (".pro"), conforme
já vimos na página 63, acima.

(1) Informar o nome do


projeto.

(2) O diretório do proje-


to.
(3) Prosseguir para a
próxima etapa.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


168 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

 Na próxima janela, temos


uma relação dos módulos da
biblioteca Qt.
Por default, o módulo QtCore é
sempre utilizado em projetos
com Qt.

Mas não queremos aqui usar nada de Qt.

Então:
(1) Desmarcar o módulo QtCore.
(2) Prosseguir para a próxima
etapa.

 Na próxima janela bastará confirmar a criação do projeto, pressionando o botão


Finish. Em seguida, voltamos à área de trabalho.
 E podemos ver no navegador de projetos que foi criado um arquivo, [Link], e
que ele já contem uma função main.
Altere o código de modo
que não existam referên-
cias à Qt.
Conforme a figura ao
lado.

 Será necessário adicionar mais um arquivo: "[Link]":

Com o botão direito do


mouse, acionar o menu
suspenso e selecionar a
opção "Add New".

 Definir o tipo de arquivo: C++ Source File - ou, conforme


o caso, C++ Header File.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.11 • Revisão do Capítulo 5 • C++ • AGIT informática • 169

 Informar o nome do arqui-


vo e o diretório onde deve
ser gravado.
E confirmar, para concluir a
criação do arquivo.

Usando o Visual C++

 Para criar o projeto basta usar a opção de menu File, e, em seguida, as opções
New e Project.

 Na próxima janela, para criar um projeto de aplicação executável usando apenas a


Linguagem C++ (sem nenhum recurso específico das plataformas Windows e
.NET), deve ser escolhida a opção Win32 Console Application.
(1) Em templates, selecione Win32.
(2) Selecione o tipo do projeto: Win32 Console Application.
(3) Informe o nome do projeto: 06_dois_modulos.
(4) Informe o diretório em que será criado: C:\cursoCPP.
(5) Prosseguir para apróxima etapa.

Caso ainda não exista, será criado um diretório com o nome do projeto, abaixo do di-
retório base: C:\cursoCPP\06_dois_modulos.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


170 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

 Na próxima janela, pressione o botão Next, para passar para a próxima etapa:

 Finalmente, nas configurações do projeto, não esqueça de defi-


nir que será um projeto vazio.

 Para adicionar arquivos, use a opção Add / New Item do menu suspenso:

Selecione
C++ File.
Informe o nome
do arquivo:
[Link].

Certifique-se de
que o diretório
onde o arquivo
será gravado
("Location")
está correto.

 Será necessário adicionar mais um arquivo: "[Link]":

Do mesmo modo exibido acima, use a opção Add / New Item do menu suspenso.
Em seguida, informe o nome e a localização do novo arquivo:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.11 • Revisão do Capítulo 5 • C++ • AGIT informática • 171

 Tenha você usado um dos ambientes acima ou um editor de textos, o importante é


que você tenha criado os arquivos [Link].e [Link].

O que fazer no código fonte: detalhamento das funções propostas acima e


dicas de implementação

 Comece pelo arquivo [Link], implementando as funções:


a. int Fatorial ( int numero ) { ... }
Para isso, tome como base o código com que exemplificamos o uso do laço "for"
para cálculo de fatorial (seção [Link], página 93, acima).
Mas, preferencialmente, tome como base esse mesmo exemplo, modificado neste
capítulo, usando operadores compostos: seção 5.3.2, página 142, acima .
b. int Potencia ( int base, int expoente ) { ... }
A função deve calcular o resultado de "base" elevada ao "expoente". Observe que
a lógica dessa função será muito semelhante à da função "Fatorial".
Em "Fatorial", temos um número multiplicado por ele menos um, sucessivamen-
te, até atingir 1. Em "Potencia", teremos um número (a "base") multiplicado por
ele mesmo tantas vezes quanto indicar o "expoente". Por exemplo, se "base" for
10 e "expoente" for 2, teremos 10 * 10.
c. int PA_TotalTermos ( int inicial, int final, int razao ) { ... }
O total de termos de uma Progressão Aritmética pode ser calculado simplesmente
assim:
( final-inicial+razao) / razao ; // atenção: precedência das operações...
Mas é preciso que esses valores sejam consistentes:
 razao não pode ser zero.

 Se inicial for menor-que final, razao deve ser positiva.

 Se inicial for maior-que final, razao deve ser negativa.

 Nesses três casos, a função deve retornar zero.

Há duas maneiras de implementar essas três consistências:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


172 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

 Com sucessivos testes de condição if else.


 Mas pode ser melhor resolvido com um único if, usando os operadores
lógicos or e and.
A segunda alternativa é melhor, mas, agora, o importante é que você resolva o
problema seja de que modo for.
d. void ImprimePares ( int inicial , int final ) { ... }
 Deve imprimir uma mensagem inicial, informando o intervalo (inicial e final).
 Deve imprimir uma segunda mensagem informando o total de números que
será impresso.
 Para isso pode chamar a função PA_TotalTermos( inicial, final, 2 ), ou
seja, passando 2 como argumento para razao, já que essa é a distância
entre os números pares.
 Mas atenção: se inicial for ímpar, a função poderá não retornar o resulta-
do esperado, uma vez que o intervalo não está correto (já que esse núme-
ro não poderá ser impresso).
 Para imprimir todos os números pares entre "inicial e final", um laço for é
uma boa alternativa. Mas há duas maneiras de implementá-lo:
 Percorrer todos os números entre inicial e final, e, dentro do laço, antes
de imprimir:
 Testar se o número é par.

 Se for, imprimir. Do contrário, nada a executar.

 Em seguida o laço faz um incremento, passando a tratar o próximo nú-


mero (se ele for menor-ou-igual a final).
 Se você analisar a alternativa acima, verá que ela duplica o processamen-
to.
 Melhor seria testar inicialmente (antes do laço) se 'inicial' é impar.

 Se for, incrementar 'inicial' para que se torne par.

 Agora, no laço, o primeiro valor a ser impresso (se houver algum) será
um número par.
 E se, ao invés de incremento, somarmos 2 para imprimir o próximo nú-
mero, ele será sempre par (a distância entre números pares é 2).
 Nas duas alternativas acima, você terá que testar se um determinado nú-
mero é par (ou ímpar). Isso é conseguido descobrindo qual é o resto da di-
visão desse número por 2. Temos duas maneiras:
 Usando o operador de módulo (%).

 Como queremos saber o resto da divisão por 2 (que é uma potência de


2) há uma maneira mais eficiente.
Não lembra? Reveja este capítulo para descobrir qual é...
Se não conseguir, use a primeira alternativa. O importante é resolver.
e. double DobraValor ( int ultimo_dia ) { ... }
Problema básico/clássico de matemática: se você ganha 1 real no primeiro dia do
mês e dobra esse valor a cada dia (2 reais no segundo dia, 4 reais no terceiro,
etc), quanto irá ganhar no último dia (por exemplo, o dia 31)?
 Essa é uma Progressão Geométrica, com razão de multiplicação 2 (cada
termo é o dobro do anterior).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.11 • Revisão do Capítulo 5 • C++ • AGIT informática • 173

 A forma de cálculo do enésimo termo (neste exemplo, o dia 31, ou qualquer


outro dia que se queira usar) é a seguinte:
[ an = a1 * q ^ (n-1) ] - (modelo genérico: o símbolo ^ simboliza
exponenciação - isso não é assim em C ou C++)
Onde 'a1' é o primeiro termo, 'q' é a razão de multiplicação e 'n' é
o total de termos.
ou:
an = a1 * Potencia( q , n-1 ) ;
Neste caso (dobrar o valor a partir do dia 1), o primeiro termo é o dia 1 e
a razão só pode ser 2 (a cada dia ganho o dobro do dia anterior);
então podemos substituir:
 'a1' por 1;
 'q' por 2
 e 'n' por 'ultimo_dia' (que é o parâmetro da função).
f. double DobraValor_for ( int ultimo_dia ) {...}
Faz o mesmo usando um laço for.
 Dentro do laço, uma variável (previamente iniciada com 1), será, cumulativa-
mente, multiplicada por 2.
 É necessária uma segunda variável, que condicione a repetição do laço, de
tal modo que essa operação só seja realizada até [ ultimo_dia - 1 ].
Pois, se ultimo_dia == 1, ganho apenas 1...
g. double TotalCombinacoes ( int conjunto, int escolhas ) { ... }
Outro problema básico/clássico de matemática: retorna o total de combinações úni-
cas (independentemente da ordem dos elementos), considerando-se uma quanti-
dade de escolhas s como um subconjunto em um conjunto de n elementos:
Fatorial ( n ) / ( Fatorial ( s ) * Fatorial (n - s ) )
Por exemplo, para saber as possibilidades de acerto na mega-sena, [ C(60,6) ]:
TotalCombinacoes ( 60, 6 ) ;

 Teste todas as funções no arquivo [Link]:


a. Em "main", onde as demais funções serão chamadas, como o compilador poderá
analisar se essas chamadas estão sendo feitas corretamente?
Pois acontece que "main" está no arquivo "[Link]" e as demais funções estão
em outro arquivo ("[Link]").

 Será que protótipos de funções e arquivos header resolvem isso?

Não lembra como fazer? Reveja este capítulo para descobrir a solução...

b. Para alguns testes você pode usar um laço for (por exemplo, Fatorial de 0 até 4).
c. Sempre que se aplique, procure usar operadores compostos, operadores lógi-
cos e o bitwise and.
d. Os testes devem explorar todas as possibilidades de falha de cada função (por
exemplo, uma chamada a ImprimePares, com números ímpares).
Analise os limites de cada uma e teste todas as consistências ou tratamentos que
elas deveriam implementar.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


174 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

 Compare o que você fez com a solução da apostila: página 453.

5.11.3 ▪ Exercício 2
Tente resolver o exercício abaixo. Em seguida, compare sua solução com a que
está no Anexo B-5-2, página 461. Caso não entenda, fale com o instrutor.

Enunciado: Implemente uma calculadora que realize as 4 operações básicas (soma,


subtração, divisão e multiplicação).

 Use dois módulos. O primeiro deles ([Link]) deverá conter a função main, a
qual irá testar as funções do segundo módulo. No segundo módulo (calculado-
[Link]), escreva duas versões da função Calculadora.
 As duas irão pedir ao usuário, dentro de um laço, que informe: o primeiro
operando, o operador e o segundo operando.
 Após cada operação, perguntar se deve ser feita nova operação.

 A única diferença entre essas duas funções é que uma usará [ if else ] para
analisar o operador e a segunda usará [ switch ( operador ) ].
void Calculadora_if_else ( ) ;
void Calculadora_switch ( ) ;

Resultado que será exibido:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.11 • Revisão do Capítulo 5 • C++ • AGIT informática • 175

Exemplo de entrada de dados e de análise do operador:


std::cin >> operando_1 >> operador >> operando_2 ;
if ( operador == '+' )
std::cout << "Somar: " << operando_1 + operando_2 << '\n';

Os operadores admitidos são:


+ - * / (soma, subtração, multiplicação, divisão).
Se informado qualquer outro, deve ser exibida mensagem de errro:
std::cout << "operador incorreto\n" ;

 Usando um ambiente ou o editor de textos, crie os dois arquivos em um novo di-


retório. Por exemplo:
<...>/cursoCPP/07_if_else_switch
 Compilar e executar.

 Compare o que você fez com a solução da apostila: página 461.

5.11.4 ▪ Questões para revisão


Responda às questões abaixo, assinalando todas as respostas corretas (uma ou
mais). Em seguida, compare suas respostas com as respostas localizadas no Ane-
xo B-5-3, página 464. Caso não entenda, encaminhe as dúvidas ao instrutor.

Cap.5 - 1. Assinale as afirmações verdadeiras:


a.  O comentário iniciado por /* e finalizado por */ só é válido em C.
b.  O comentário iniciado por /* e finalizado por */ só é válido em C++.
c.  O comentário iniciado por /* e finalizado por */ é válido em C e em C++.
d.  O comentário iniciado por // e finalizado pela quebra de linha do texto
(new-line) só é válido em C++ ou em C99, embora muitos compiladores,
dependendo das opções de compilação, o aceitem para C89.

Cap.5 - 2. Em uma linha de instrução podemos:


a.  Encadear diversas operações, inclusive chamadas de função, indepen-
dentemente dos retornos dessas funções.
b.  Combinar operações, inclusive chamadas de funções, desde que os re-
tornos das funções, usadas como operandos, sejam compatíveis com os
operadores associados.
c.  Atribuir a uma variável o retorno de uma função com valor de retorno
void.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


176 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

Cap.5 - 3. Assinale as afirmações corretas, considerando o código abaixo:


char c ; for ( c = 2 ; c >= 0 ; ++c ) { std::cout << c ; }
a.  O código acima leva a um erro de compilação.
b.  O bloco de instruções associado ao laço será executado 2 vezes.
c.  O bloco de instruções associado ao laço será executado uma vez.
d.  Será executado infinitamente (laço infinito).
e.  Nunca será executado.
f.  Nenhuma das respostas acima está correta.

Cap.5 - 4. Assinale as afirmações corretas, considerando o código abaixo:


unsigned char uc ; for ( uc = 2 ; uc >= 0 ; ++uc )
{ std::cout << uc ; }
a.  O código acima leva a um erro de compilação.
b.  O bloco de instruções associado ao laço será executado 2 vezes.
c.  O bloco de instruções associado ao laço será executado uma vez.
d.  Será executado infinitamente (laço infinito).
e.  Nunca será executado.
f.  Nenhuma das respostas acima está correta.

Cap.5 - 5. Assinale as afirmações corretas, considerando o código abaixo:


char c ; for ( c = 2 ; c >= 0 || c < 0 ; ++c ) { std::cout << c ; }
a.  O código acima leva a um erro de compilação.
b.  O bloco de instruções associado ao laço será executado 2 vezes.
c.  O bloco de instruções associado ao laço será executado uma vez.
d.  Será executado infinitamente (laço infinito).
e.  Nunca será executado.
f.  Nenhuma das respostas acima está correta.

Cap.5 - 6. Assinale as afirmações corretas, considerando o código abaixo:


int x ; for ( x=10 ; x > 1 ; --x ) { std::cout << x ; }
O bloco de instruções associadas ao laço for será executado:
a.  11 vezes.
b.  10 vezes.
c.  9 vezes, de acordo com o resultado de uma chamada à função
"PA_TotalTermos", implementada no exercício "06_dois_modulos",
acima: [ PA_TotalTermos ( 10 , 2 , -1 ) ; ]
d.  Infinitamente (laço infinito).
e.  Nunca será executado.
f.  Após o encerramento do for o valor de "x" será zero.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.11 • Revisão do Capítulo 5 • C++ • AGIT informática • 177

g.  Após o encerramento do for o valor de "x" será um.


h.  Após o encerramento do for o valor de "x" será dois.

Cap.5 - 7. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; if ( x == 10) { ++x ; }
// atenção para o símbolo em ( x = = 10 ): dois sinais de igual
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  10 vezes, se 'x' for igual a 10.
b.  Sempre será incrementada uma vez.
c.  Uma vez, somente se 'x' for igual a 10.
d.  Nunca será incrementada.
e.  A operação é incorreta e ocorrerá um erro de compilação.
f.  Nenhuma das respostas acima está correta.

Cap.5 - 8. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; if ( x = 10) { ++x ; }
// atenção para o símbolo em ( x = 10 ): um sinal de igual
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  10 vezes.
b.  Uma vez, somente se 'x' for igual a 10.
c.  Nunca será incrementada.
d.  Sempre será incrementada.
e.  A operação é incorreta e ocorrerá um erro de compilação.
f.  Nenhuma das respostas acima está correta.

Cap.5 - 9. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; if ( x > 20 && x < 21 ) { ++x; }
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  Nunca será incrementada.
b.  Sempre será incrementada.
c.  Será incrementada sempre que 'x' for igual a 20.
d.  Será incrementada sempre que 'x' for igual a 21.
e.  Será incrementada se 'x' for [ maior que 20 ] OU [ menor que 21 ].
f.  A operação é incorreta e ocorrerá um erro de compilação.
g.  Nenhuma das respostas acima está correta.

Cap.5 - 10. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; if ( x > 20 || x < 21 ) { ++x; }
Quando o 'if' acima for executado, a variável 'x' será incrementada:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


178 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

a.  Nunca será incrementada.


b.  Sempre será incrementada.
c.  Será incrementada somente se 'x' for igual a 20.
d.  Será incrementada somente se 'x' for igual a 21.
e.  A operação é incorreta e ocorrerá um erro de compilação.
f.  Nenhuma das respostas acima está correta.

Cap.5 - 11. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ;
if ( ( x > 20 || x < 21 ) && ( x > 20 && x < 21 ) ) { ++x; }
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  Nunca será incrementada.
b.  Sempre será incrementada.
c.  Será incrementada sempre que 'x' for igual a 20.
d.  Será incrementada sempre que 'x' for igual a 21.
e.  A operação é incorreta e ocorrerá um erro de compilação.
f.  Nenhuma das respostas acima está correta.

Cap.5 - 12. Assinale as afirmações corretas, considerando o código abaixo:


int x , y ; std::cin >> x >> y ;
if ( ( x > 20 && x < 22 ) && ( y > 20 && y < 22 ) ) { ++x; }
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  Nunca será incrementada.
b.  Sempre será incrementada.
c.  Sempre que 'x' for igual a 21; 'y' poderá conter 20, 21 e 22.
d.  Sempre que 'y' for igual a 21; 'x' poderá conter 20, 21 e 22.
e.  Sempre que [ 'x' for igual a 21 ] E [ 'y' for igual a 21 ].
f.  A operação é incorreta e ocorrerá um erro de compilação.
g.  Nenhuma das respostas acima está correta.

Cap.5 - 13. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; while ( true ) { ++x ; }
A cada vez que o 'while' acima for executado, a variável 'x' será
incrementada:
a.  Uma vez.
b.  Nunca será incrementada.
c.  Será incrementada infinitamente (laço infinito).
d.  A operação é incorreta e ocorrerá um erro de compilação.
e.  Nenhuma das respostas acima está correta.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


5.11 • Revisão do Capítulo 5 • C++ • AGIT informática • 179

Cap.5 - 14. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; while ( false ) { ++x ; }
A cada vez que o 'while' acima for executado, a variável 'x' será
incrementada:
a.  Uma vez.
b.  Nunca será incrementada.
c.  Será incrementada infinitamente (laço infinito).
d.  A operação é incorreta e ocorrerá um erro de compilação.
e.  Nenhuma das respostas acima está correta.

Cap.5 - 15. Assinale as afirmações corretas, considerando o código abaixo:


int y ; std::cin >> y ; int x = y > 5 && y < 7 ;
a.  'x' poderá armazenar o número inteiro 5 ou o número inteiro 7.
b.  'x' sempre armazenará o valor inteiro 1.
c.  'x' sempre armazenará o valor inteiro 0.
d.  'x' armazenará o valor inteiro 1 se 'y' for igual a 6.
e.  'x' armazenará o valor inteiro 0 se 'y' for diferente de 6.
f.  A operação é incorreta e ocorrerá um erro de compilação.
g.  Nenhuma das respostas acima está correta.

Cap.5 - 16. Assinale as afirmações corretas, considerando o código abaixo:


int y ; std::cin >> y ; bool x = y > 5 && y < 7 ;
a.  'x' poderá armazenar o número inteiro 5 ou o número inteiro 7.
b.  'x' sempre armazenará o valor booleano true.
c.  'x' sempre armazenará o valor booleano false.
d.  'x' armazenará o valor booleano true se 'y' for igual a 6.
e.  'x' armazenará o valor booleano false, se 'y' for diferente de 6.
f.  A operação é incorreta e ocorrerá um erro de compilação.
g.  Nenhuma das respostas acima está correta.

Cap.5 - 17. Assinale as afirmações corretas, considerando o código abaixo:


int y ; std::cin >> y ; bool x = y <= 5 || y >= 7 ;
a.  'x' sempre armazenará o valor booleano true.
b.  'x' sempre armazenará o valor booleano false.
c.  'x' armazenará o valor booleano true se 'y' for diferente de 6.
d.  'x' armazenará o valor booleano false, se 'y' for igual a 6.
e.  A operação é incorreta e ocorrerá um erro de compilação.
f.  Nenhuma das respostas acima está correta.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


180 • AGIT informática • C++ • • Capítulo 5 ▪ Fluxo de processamento

Cap.5 - 18. Assinale as afirmações corretas, considerando o código abaixo:


// ...
int x ;
std::cin >> x ;
switch ( x )
{
case 1 :
funcao1 ( );
break;
case 2 :
funcao2 ( );
case 3 :
funcao3 ( );
return;
case 4 :
funcao4 ( );
default :
funcao5 ( );
}
++x ;

a.  se 'x' for igual a 1, a funcao1 será chamada e em seguida 'x' será


incrementada.
b.  se 'x' for igual a 2, a funcao2 será chamada e em seguida 'x' será
incrementada.
c.  se 'x' for igual a 2, a funcao2 será chamada, em seguida a funcao3
será chamada e em seguida 'x' será incrementada.
d.  se 'x' for igual a 2, a funcao2 será chamada, em seguida a funcao3
será chamada, ocorrendo retorno em seguida.
e.  se 'x' for igual a 3, a funcao3 será chamada e em seguida 'x' será
incrementada.
f.  se 'x' for igual a 3, a funcao3 será chamada, ocorrendo retorno em
seguida.
g.  se 'x' for igual a 4, a funcao4 será chamada e em seguida 'x' será
incrementada.
h.  se 'x' for igual a 4, a funcao4 será chamada, em seguida a funcao5
será chamada e em seguida 'x' será incrementada.
i.  a funcao5 será chamada apenas quando 'x' for maior que 4.
j.  para qualquer valor menor que 1 e maior que 4 a funcao5 será
chamada, ocorrendo retorno em seguida.
k.  para qualquer valor menor que 1 e maior que 4 a funcao5 será
chamada, e em seguida 'x' será incrementada.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 6 ▪ Ponteiros e referências • C++ • AGIT informática • 181

• Capítulo 6
▪ Ponteiros e referências

6.1 • O problema do carteiro ....................................................182


6.2 • Trabalhando com endereços ..............................................184
6.2.1 ▪ Exemplo com ponteiros ..................................................................185
6.2.2 ▪ Usando ponteiros como parâmetros de funções ............................187
6.2.3 ▪ Evitando duplos acessos de memória ............................................190
6.3 • Referências ....................................................................192
6.3.1 ▪ Inicialização de referências.............................................................193
6.3.2 ▪ Diferenciando referências de ponteiros...........................................193
6.3.3 ▪ Exemplo com referências ...............................................................193
6.4 • Questões para revisão do Capítulo 6 ..................................196

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


182 • AGIT informática • C++ • • Capítulo 6 ▪ Ponteiros e referências

Até agora trabalhamos com variáveis e constantes nas quais armazenamos valores.
Quando as declaramos, dizemos que estamos alocando memória. Isso significa que
essas variáveis e constantes não estão em um algum lugar misterioso ou desconhecido:
elas estão situadas em, e ocupam, determinadas regiões ou locais da memória.
E como um determinado local de memória é acessado pelo computador? Através do
endereço desse local. É o que veremos melhor aqui.

6.1 • O problema do carteiro


Imagine uma rua, com diversas casas, que só tenha um lado, cada uma delas com uma
certa quantidade de moradores. Digamos também que a numeração das casas evolua
de acordo com o seu tamanho, determinado pela quantidade de moradores:

Rua A
Número: 1 3 4 8
Moradores: Família Silva Família Gil Família Souza
...
João Pedro Maria Gil José Souza André Souza
Silva Silva
Marta Souza Ana Souza

Se enviarmos uma carta para "João Silva", do seguinte modo:


Para: João Silva
Endereço: Família Silva
São Paulo - SP - Brasil
O carteiro teria um problema. Mesmo supondo-se que a "Família Silva" fosse a única
com essa denominação na cidade de São Paulo, para localizá-la seria preciso percorrer
a cidade, batendo de porta em porta e perguntando se ali reside a "Família Silva". E
não adiantaria memorizar o resultado dessa busca: nada garante que essa família não
irá mudar de endereço... O endereço, salvo desastres, é fixo. Os ocupantes não.
Para o carteiro, portanto, esse seria um péssimo modo de trabalho. Para ele não interes-
sa o valor que ocupa um local (isto é, os moradores). Para ele interessa o endereço
desse local:
Para: João Silva
Endereço: Rua A, número 1
São Paulo - SP - Brasil
Nesta segunda forma, supondo-se que a "Rua A" seja única na cidade de São Paulo, o
carteiro dispõe de um guia para chegar rapidamente ao local. Na realidade, os endere-
ços precisam ser um pouco mais complexos, incluindo o Código de Endereçamento
Postal, o CEP. Mas isto apenas reafirma a conclusão acima: o importante é ter uma for-
ma de endereçamento de locais que melhor atenda à cada situação.
 E o problema do computador é o mesmo do carteiro. Para acessar um lo-
cal de memória, ele precisa do endereço desse local.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6.1 • O problema do carteiro • C++ • AGIT informática • 183

Podemos perfeitamente, em uma primeira abordagem, imaginar a memória de um


computador como uma longa rua, onde cada casa tem o tamanho de um byte. Por ex-
emplo, em uma máquina de 32 bits, poderíamos ter:
Endereços 0 1 2 3 ... [Link]
ou, em hexa,
FFFFFFFF
Valor armazenado ? ? ? ? ... ?

Contudo, por eficiência os bytes podem ser agrupados em palavras ou words (uma
"casa" completa). Além disso, para o programador, o modo de ver a memória que inte-
ressa está relacionado aos tipos empregados em sua alocação, pois eles indicam quan-
tos bytes são reservados em um determinado endereço para um determinado fim.
Por exemplo:
long a = 10 ; double b = 20.9 ; short c = 30 ; char d = 40 ;

Nomes identificadores “a” "b" "c" "d”


(puramente mnemônicos, interessam
apenas ao programador)
Endereços - hipótese - 1000 1004 1012 1014 1015
(é o que interessa para o computador)
Valor armazenado 10 20.9 30 40 ?

Nesse exemplo, teríamos, por hipótese, a primeira alocação de memória no endereço


1000. A partir desse endereço teríamos as demais áreas, cada qual garantindo a quanti-
dade bytes necessária para a alocação dos respectivos tipos:
identificador tipo endereço inicial tamanho área ocupada
a long 1000 4 1000 a 1003
b double 1004 8 1004 a 1011
c short 1012 2 1012 a 1013
d char 1014 1 1014

Esse modelo pode ser implementado de diversas maneiras, dependendo do compilador


e da plataforma. Mas a essência será a mesma: cada variável alocada tem um endere-
ço (que acima chamamos de "endereço inicial") e uma garantia de reserva da quantida-
de de bytes (tamanho) requerida pelo seu tipo.
Os nomes identificadores só interessam para o programador: são mnemônicos que,
na leitura do código, permitem um entendimento rápido da finalidade que cada área de
memória desempenhará no processamento.
Já para o computador esses nomes não tem significado: tudo o que ele sabe (e precisa)
fazer é acessar determinados endereços. O compilador e o linker (este no caso de sím-
bolos globais) se encarregam de gerar o código de máquina necessário para que os en-
dereços sejam resolvidos.
Desse modo, só será necessário manter um nome de símbolo no arquivo binário se uma
aplicação exportar símbolos (funções ou variáveis globais) para outras aplicações. É o

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


184 • AGIT informática • C++ • • Capítulo 6 ▪ Ponteiros e referências

que acontece, por exemplo, com as bibliotecas dinâmicas (DLLs no caso do Windows e
shared objects, no caso de Unix/Linux).
Mas, para a execução pelo computador, a única maneira de acessar determinada memó -
ria (seja uma variável seja o código de uma função) é através dos endereços.

Assumindo que seja assim, em princípio isso só deveria interessar ao próprio


computador e a programadores assembler. Então, por que os programadores
que usam C ou C++ precisam tomar conhecimento disso?

Porque, em muitas situações, trabalhar com os endereços é a melhor solução.

6.2 • Trabalhando com endereços


Como dissemos, existem muitas situações em que teremos vantagens em acessar valo-
res através de seus endereços.
Por exemplo, se você for implementar uma árvore binária que exige constante troca de
lugar entre os elementos inseridos na árvore (para que sejam mantidos sempre em or-
dem crescente ou decrescente), é muito mais eficiente trocar os endereços que ligam
um elemento ao outro, do que trocar os valores, pois isto seria bem mais dispendioso.
Uma situação mais simples e também muito comum (e teremos várias situação como
essa daqui para a frente): uma função que precisa retornar mais do que um valor.
Uma forma de fazer isso é através de parâmetros de saída, que podem ser implemen-
tados como parâmetros que recebem endereços ao invés de valores.
Para isso temos os tipos ponteiro. Um ponteiro é uma memória que, ao invés de arma-
zenar valores, armazena o endereço de um outro local de memória. E, para trabalhar
com endereços, temos estes operadores:
Operador Descrição Exemplo
<tipo> * <p> Declara um ponteiro <p>: ele int * p ; // 'p' poderá armazenar
poderá armazenar o endereço // o endereço
de memórias do tipo <tipo>. // de objetos do tipo int
& <o> Address of - retorna o int x ;
endereço de um objeto <o>. int * p = &x ; // copia o endereço
// de 'x' para 'p'
* <p> Devolve o valor apontado int y = *p ; // acessa a memória
pelo ponteiro <p> // apontada por 'p'
// e copia o seu valor para 'y'
Para usar os operadores acima, temos que seguir adequadamente sua lógica intrínseca:
 Endereços só podem ser armazenados em memórias aptas a guardar endere-
ços.
 Pois, se o operador de endereço [ & ] lê um endereço, então precisamos tam-
bém do seu oposto: um tipo que possa armazenar esse endereço. Para isso te-
mos o operador ponteiro (pointer) representado pelo símbolo [ * ].
 Assim, podemos fazer:
int v = 10 ;
int * p = &v ; // o operador pointer na declaração de um ponteiro 'p'.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6.2 • Trabalhando com endereços • C++ • AGIT informática • 185

 O endereço de 'v' é lido pelo operador de endereço [ & ].


 Em seguida é gravado em 'p'.
 Isso é possível pois 'p' é uma variável capaz de armazenar um endereço,
pois foi declarada como [ int * p ] , o que significa que ela é um ponteiro
(memória destinada a armazenar endereços).
 Agora se fizermos:
int v_2 = * ponteiro ; // o operador pointer em um
// acesso ao valor apontado
 Estaremos recuperando o valor apontado por 'p'. Isto é, através dele ire-
mos acessar o endereço de 'v', recuperando o valor inteiro 10, que, final-
mente, será atribuído a 'v_2'.

6.2.1 ▪ Exemplo com ponteiros


Usando ambientes ou editores de texto:
 Crie um novo projeto, em um novo diretório.

 Por exemplo, 08_ponteiros_1. O sufixo "_1" é aí usado, pois teremos logo a


seguir um segundo exemplo, onde veremos uma função que devolve mais do
que um valor de retorno.
 Acrescente um arquivo. Por exemplo: [Link].

 Não é necessário digitar o código. Ele pode ser encontrado, já pronto, em:

<...>/cursoCPP/apostila/08_ponteiros_1/[Link].

Código fonte:
#include <iostream>
int main()
{
int x = 5 ; // 'x' é o apelido de um endereço de memória
// onde armazenei o número inteiro 5;
int * p = &x ; // 'p' é o apelido de um outro endereço de memória
// onde armazenei o endereço de 'x'
std::cout << "conteudo de 'x' = " << x << '\n';
std::cout << "endereco de 'x' = " << &x << '\n';
std::cout << "conteudo de 'p' = " << p << '\n';
std::cout << "endereco de 'p' = " << &p << '\n';
std::cout << "conteudo APONTADO por 'p' = " << *p << '\n';
return 0;
}
/* Resultado:
Obs.: os endereços exibidos poderão variar, dependendo de plataforma e
compilador. Mas a situação é a mesma.
conteudo de 'x' = 5
endereco de 'x' = 0x22ff5c
conteudo de 'p' = 0x22ff5c
endereco de 'p' = 0x22ff58
conteudo APONTADO por 'p' = 5
*/

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


186 • AGIT informática • C++ • • Capítulo 6 ▪ Ponteiros e referências

Podemos representar graficamente esse resultado do seguinte modo:

Na tabela acima, vemos que 'x' é um mnemônico para o endereço 22ff5c, e, nessa po-
sição de memória está armazenado o número inteiro 5. E, quando é executada a linha:
int * p = &x ;

O endereço de 'x' é copiado para 'p'. Observar que 'p' ocupa seu próprio local de me-
mória (no endereço 22ff58), constituindo assim uma segunda posição de memória em
uso.
 Contudo, diferentemente de 'x', que armazena um valor inteiro, 'p' arma-
zena um endereço (justamente o endereço de 'x': 22ff5c).

Dessa forma, podemos dizer que 'p' aponta para 'x', já que através de 'p' podemos
acessar a posição de memória 'x'.
Essa situação pode ser visualizada na figura abaixo:

Assim, quando é executada a linha abaixo:


std::cout << "conteudo APONTADO por 'p' = " << *p << '\n';
A operação [ *p ] retorna o valor apontado por 'p' (neste caso, o valor inteiro 5).
 Em outras palavras, estamos ordenando:
 Acesse ‘p’ (no exemplo: acesse o endereço 22ff58) .
 Considere o que está armazenado ali como um endereço (no exemplo: o
endereço de 'x', 22ff5c).
 Agora acesse esse endereço encontrado em 'p' (no exemplo, 22ff5c). Essa
operação constitui um redirecionamento determinado pelo operador pontei-
ro [ * ].
 Finalmente, leia e retorne o valor que está armazenado nesse segundo lo-
cal (que é exatamente 'x' ou 22ff5c).
 Então, o valor inteiro 5 é retornado e impresso por std::cout.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6.2 • Trabalhando com endereços • C++ • AGIT informática • 187

6.2.2 ▪ Usando ponteiros como parâmetros de funções


Há dois motivos mais usuais para passar argumentos por endereço para uma determi-
nada função:
 Funções que retornam mais do que um valor. Uma forma de fazer isso é pas-
sar endereços, ao invés de valores.
 Assim, através do endereço, a função poderá alterar a memória de quem a
chamou.
 Com isso, na prática, estará oferecendo mais um valor de retorno além do
seu retorno formal.
 Para que isso seja possível essas funções devem estar aptas a receber en-
dereços. Ou seja, esses parâmetros "de saída" devem ser ponteiros.
 Uma segunda razão é performance, nos casos em que uma função receba um
objeto (valor) de tamanho muito grande.
 Se os parâmetros da função fossem objetos, haveria uma cópia da memória
de quem chamou para o parâmetro da função chamada.
 E, se esse objeto tem uma grande quantidade de bytes, e, pior, caso isso
seja feito dentro de um laço, teremos uma grande perda de tempo copiando
todos esses bytes.
 Nesse caso, o correto seria passar o endereço. Pois, seja qual for o objeto
envolvido, um endereço é sempre um número inteiro sem sinal (positivo).
Desse modo, copiaremos apenas essa quantidade de bytes.

 Veremos este segundo caso no próximo capítulo, quando trataremos de


estruturas - e então esse problema estará colocado.
Por enquanto, vejamos um exemplo com uma função que retorna mais
do que um valor.

Usando ambientes ou editores de texto:


 Crie um novo projeto, em um novo diretório. Por exemplo, 09_ponteiros_2.

 Acrescente um arquivo. Por exemplo: [Link].

 Não é necessário digitar o código. Ele pode ser encontrado, já pronto, em:

<...>/cursoCPP/apostila/09_ponteiros_2/[Link].

Teremos duas funções: a função main e a função validar_entrada, que devolverá dois
valores. Sua assinatura é esta:
bool validar_entrada( int * param_ptr ) ; // retorna bool;
// e seu único parâmetro é um ponteiro para int
O objetivo dessa função é pegar uma entrada de dados no teclado e verificar se essa en -
trada é válida. Deve devolver os seguintes valores de retorno:
 O retorno formal (bool) indicará se a operação foi bem sucedida.
 E o parâmetro ponteiro devolve o valor inteiro digitado pelo usuário.
A função main irá chamar validar_entrada, passando como argumento o endereço de
uma variável sua do tipo int. E, através desse endereço, a função validar_entrada
irá alterar essa variável, caso a entrada de dados seja bem sucedida.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


188 • AGIT informática • C++ • • Capítulo 6 ▪ Ponteiros e referências

Resultado que deve ser exibido (ou semelhante):

Código fonte:
#include <iostream>
#include <limits>

// Função que oferece dois retornos.


// Essa função pega uma entrada no teclado.
// - o retorno formal (bool) indica se a operação foi bem sucedida
// - e o parâmetro ponteiro devolve o valor digitado.
bool validar_entrada( int * param_ptr )
{
// Imprime linhas para exibir conteúdo e endereço do parâmetro
// (apenas para teste, já que isto não tem nada a ver
// com o objetivo da função):
std::cout << "\n-validar_entrada: testando conteudo\n"
<< "e endereco do parametro 'param_ptr'\n";
std::cout << "conteudo de 'param_ptr' = " << param_ptr << '\n';
std::cout << "endereco de 'param_ptr' = " << &param_ptr << '\n';
std::cout << "conteudo inicialmente APONTADO por 'param_ptr' = "
<< *param_ptr << '\n';
// Agora sim, faz o trabalho real desta função:
std::cout << "\n-validar_entrada: entre com um numero inteiro: ";
std::cin >> *param_ptr ; // alterando a memória APONTADA
if ( std::[Link]() )
{
std::cout << "entrada invalida\n";
// Limpa erros:
std::[Link]();
// Ignora "new-lines" pendentes no buffer:
std::[Link]( std::numeric_limits<int>::max() , '\n' );

return false ; // entrada inválida


}

return true ; // entrada válida


}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6.2 • Trabalhando com endereços • C++ • AGIT informática • 189

int main()
{
int x = 5 ; // 'x' é o apelido de um endereço de memória
// onde armazenei o NÚMERO INTEIRO 5;

std::cout << "-main: conteudo de 'x': "<< x << '\n';


std::cout << "-main: endereco de 'x': "<< &x << '\n';

// Chama a função 'validar_entrada', passando o endereco de 'x'


if ( validar_entrada ( &x ) )
std::cout << "\n-main: conteudo de 'x' apos 'validar_entrada' = "
<< x << '\n';
return 0;
}

/* RESULTADO:

Obs.: os endereços exibidos poderão variar,


dependendo de plataforma e compilador. Mas a situação é a mesma.

-main: conteudo de 'x': 5


-main: endereco de 'x': 0x22ff5c

-validar_entrada: testando conteudo


e endereco do parametro 'param_ptr'
conteudo de 'param_ptr' = 0x22ff5c
endereco de 'param_ptr' = 0x22ff40
conteudo inicialmente APONTADO por 'param_ptr' = 5

-validar_entrada: entre com um numero inteiro: 15

-main: conteudo de 'x' apos 'validar_entrada' = 15


*/

Através do parâmetro ponteiro, validar_entrada devolve o valor digitado. Foram ne-


cessários dois retornos, já que o retorno formal (bool) está sendo usado para indicar su-
cesso ou falha na operação de entrada de dados.
Quanto ao comportamento do ponteiro, exceto pelo fato de que está sendo usado como
parâmetro de função, temos uma situação idêntica ao exemplo anterior:

Na linha:
if ( validar_entrada ( &x ) )
O endereço de 'x' é copiado para o parâmetro ponteiro 'param_ptr', da função 'vali-
dar_entrada'. E, nessa função, essa memória será alterada através do ponteiro:
std::cin >> * param_ptr ; //  alterando a memória apontada

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


190 • AGIT informática • C++ • • Capítulo 6 ▪ Ponteiros e referências

Como podemos ver na figura abaixo:

 Implemente algumas variações em um dos exercícios acima (ou ambos) .


Por exemplo, tente fazer:
double x = 5 ;
int * p = &x ; // o que será que acontece aqui?
Ou:
int * p = 100 ; // e aqui?

6.2.3 ▪ Evitando duplos acessos de memória


No exemplo acima, no código necessário para atingir sua finalidade, a função
validar_entrada faz um único acesso à memória apontada por param_ptr:
std::cin >> * param_ptr ;

Contudo há situações em que é necessário fazer muitos acessos. Isso pode comprome-
ter a performance, dependendo do caso.
Pois, nos dois exemplos acima, vimos que uma operação (de leitura ou escrita) em uma
memória, feita através de um ponteiro que guarde seu endereço, exige um duplo aces-
so à memória:
 Acesse ‘ponteiro’
 primeiro acesso: acessa o endereço onde está o próprio ponteiro.

 Agora acesse o endereço encontrado em 'ponteiro'


 segundo acesso: acessa a memória apontada.

Se isso for feito muitas vezes, duplicando, a cada vez, o tempo gasto para acessos à
memória, teremos problemas de performance.
 Há uma forma de evitar isso. É o que veremos no exemplo abaixo.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6.2 • Trabalhando com endereços • C++ • AGIT informática • 191

A função abaixo, deverá somar 3 ao valor inteiro apontado por 'p', durante 'n' vezes.
Retornará true se o resultado for menor ou igual a 100. Do contrário, retornará false.

Exemplo 1 - acessa sempre via ponteiro:


bool funcao( int * p, int n )
{
for ( int contador = 0 ; contador < n ; ++contador )
*p += 3 ; // ... *p = *p + 3 ; // ... // duplos acessos repetidamente
if ( *p > 100 )
return false;
else
return true;
}
Agora essa função poderá ser chamada de muitas maneiras:
int main ( )
{
int x = 5 ;
// Na chamada abaixo, não teremos muitos duplos acessos em 'funcao':
if ( funcao ( &x , 2 ) )
std::cout << "- 'x' agora contem : " << x << '\n';
else
std::cout << "- 'x' com valor invalido apos 'funcao'\n";
// Mas agora teremos:
if ( funcao ( &x , 100 ) ) // 100 vezes!
std::cout << "- 'x' agora contem : " << x << '\n';
else
std::cout << "- 'x' com valor invalido apos 'funcao'\n";
}

Exemplo 2 - minimizando duplos acessos:


bool funcao( int * p, int n )
{
int temp = * p ; // faz uma cópia inicial do valor apontado por 'p'
// aqui tempos um primeiro duplo acesso.
for ( int contador = 0 ; contador < n ; ++contador )
temp += 3 ; // acumula o valor em 'temp', com um único acesso

if ( temp > 100 )


return false ;
else
{
*p = temp ; // Altera, de uma única vez, a memória apontada por 'p'
// com o resultado acumulado em 'temp'.
// Aqui temos um segundo duplo acesso - e não mais que isso.
return true;
}
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


192 • AGIT informática • C++ • • Capítulo 6 ▪ Ponteiros e referências

Nesta segunda versão, se a função for chamada com um valor alto para 'n', não teremos
problemas. Pois o laço já não está fazendo duplos acessos.
Então, em uma chamada como [ funcao ( &x , 100 ) ], que acarretaria 100 duplos
acessos (totalizando 200 acessos) na primeira versão, teremos agora apenas 100 aces-
sos a 'temp'. O duplo acesso [ *p ] é feito apenas duas vezes: no início e no final.

 Em C, essa seria a única alternativa para minimizar os duplos acessos.


Mas, em C++, temos uma alternativa melhor: usar referências.

6.3 • Referências
Referências (só existem em C++), resolvem, quando possível e adequado, o proble-
ma do duplo acesso, sem que o programador precise preocupar-se com isso. Além dis-
so referências não podem ser corrompidas (ao contrário de ponteiros).
 Um ponteiro é uma nova memória (ocupando um novo endereço) e que visa a
armazenar o endereço de uma outra memória.
 Uma referência é apenas um nome alternativo (um sinônimo) para um ende-
reço de memória já existente.
A criação desse sinônimo é conseguida através do operador de referência:
int a ;
int &ra = a; // 'ra' é uma referência (um sinônimo) para 'a'.

Em outras palavras, se, por exemplo, a variável a for um apelido para o endereço 1000
da memória, então a variável ra será apenas um segundo apelido para esse mesmo en-
dereço (1000).
 Isso significa que uma instrução que declara uma referência, como
int &ra = a ;
não cria uma nova memória e sim apenas um novo nome para uma memó-
ria já existente.

Podemos então ter funções com parâmetros referência, ao invés de ponteiros. Nada
será preciso fazer para minimizar duplos acessos.
Para o programador, esse parâmetro será simplesmente um sinônimo para o argu-
mento que será passado na chamada da função.
Detalhe.
Não importa aqui como a implementação de cada compilador irá resolverá o pro-
blema.
Sabemos que, na passagem de argumentos, não é possível implementar um
"sinônimo". A implementação mais provável é que o argumento seja passado
por endereço e, dentro da função, dependendo do custo-benefício, o compilador
implemente um código semelhante ao que usamos acima no "Exemplo 2 -mini-
mizando duplos acessos".

Além disso, referências trazem outros benefícios muito importantes, como veremos
mais a frente em alguns tópicos como operadores, templates e outros.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6.3 • Referências • C++ • AGIT informática • 193

E um desses benefícios é tornar o código mais limpo, legível e fácil de manter.

6.3.1 ▪ Inicialização de referências.

 Regra básica:
Variáveis-Referência devem obrigatoriamente ser inicializadas, pois só
podemos referenciar algo já existente.
Assim, a referência já deve nascer associada àquela memória que ela pre-
tende referenciar.

Exemplos:
int a ;
int &ra = a ; //  Correto! 'ra' foi inicializada.
int &rb ; //  Errado! 'rb' não foi inicializada. Erro de compilação.

6.3.2 ▪ Diferenciando referências de ponteiros.


Embora possa parecer confuso que o operador &(referência) tenha o mesmo símbolo
do operador &("address of"), na prática não há confusão pois eles sempre aparecem
em posições diferentes e assim o compilador tem como analisá-los corretamente.

 Quando se trata de &("adress of") o operador aparece sempre na posição


à direita, de captura (leitura) de um endereço:

Endereço:
int a = 5 ;
int * pa = &a ; // Aqui, o operador & captura o endereço de a (leitura).
// Em seguida, o operador de atribuição [=]
// armazena esse endereço em pa (gravação);

 Quando se trata de &("referência") o operador aparece sempre na posição


à esquerda, de recepção ou associação:

Referência:
int a = 5 ;
int &ra = a ; // Aqui o operador & faz com que ra referencie a
// (tornando-se um um sinônimo). Pois o símbolo &
// não está na posição de leitura, e sim na posição de
// recepção ou associação, à esquerda.

6.3.3 ▪ Exemplo com referências


Vamos usar, em um único exemplo, um código muito semelhante ao que vimos nos
dois exemplos com ponteiros. Teremos o uso de referências dentro de uma função
(main) e o uso de referências como parâmetros de funções.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


194 • AGIT informática • C++ • • Capítulo 6 ▪ Ponteiros e referências

Usando ambientes ou editores de texto:


 Crie um novo projeto, em um novo diretório. Por exemplo, 10_referencias.

 Acrescente um arquivo. Por exemplo: [Link].

 Não é necessário digitar o código. Ele pode ser encontrado, já pronto, em:

<...>/cursoCPP/apostila/10_referencias/[Link].

Resultado que deve ser impresso (ou semelhante):

Código Fonte:
#include <iostream>
#include <limits>
// Função que oferece dois retornos. Essa função pega uma entrada no teclado.
// - o retorno formal (bool) indica se a operação foi bem sucedida
// - e o parâmetro referência devolve o valor digitado.
bool validar_entrada( int & param_ref ) // 'param_ref' é uma referência
{
// Imprime linhas para exibir conteúdo e endereço do parâmetro
// (apenas para teste, já que isto não tem nada a ver
// com o objetivo da função):
std::cout << "\n-validar_entrada: testando conteudo\n"
<< "e endereco do parametro referencia 'param_ref'\n";
std::cout << "conteudo inicial de 'param_ref' = " << param_ref
<< '\n';
std::cout << "endereco de 'param_ref' = " << &param_ref << '\n';

// Agora sim, faz o trabalho real desta função:


std::cout << "\n-validar_entrada: entre com um numero inteiro: ";
std::cin >> param_ref ; // alterando a memória referenciada
if ( std::[Link]() )
{
std::cout << "entrada invalida\n";
// Limpa erros:
std::[Link]();
// Ignora "newlines" pendentes no buffer:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6.3 • Referências • C++ • AGIT informática • 195

std::[Link]( std::numeric_limits<int>::max() , '\n' );


return false ; // entrada inválida
}
return true ; // entrada válida
}

int main()
{
int x = 5 ; // 'x' é o apelido de um endereço de memória
// onde armazenei o número inteiro 5;
int * p = &x ; // 'p' é o apelido de um outro endereço de memória
// onde armazenei o endereço de 'x'
int & r = x ; // 'r' é uma referência para 'x': ou seja, um novo apelido
// (um sinônimo) para a mesma posição de memória
// já apelidada anteriormente como 'x'
std::cout << "conteudo de 'x' = " << x << '\n';
std::cout << "endereco de 'x' = " << &x << '\n';
std::cout << "conteudo de 'r' = " << r << '\n';
std::cout << "endereco de 'r' = " << &r << '\n';
std::cout << "conteudo de 'p' = " << p << '\n';
std::cout << "endereco de 'p' = " << &p << '\n';
std::cout << "conteudo APONTADO por 'p' = " << *p << '\n';
// Chama a função 'validar_entrada', passando uma referência para 'x'
if ( validar_entrada ( x ) )
std::cout << "\n-main: conteudo de 'x' apos 'validar_entrada' = "
<< x << '\n';
return 0;
}
/* RESULTADO:
Obs.: os endereços exibidos poderão variar,
dependendo de plataforma e compilador. Mas a situacão é a mesma.
conteudo de 'x' = 5
endereco de 'x' = 0x22ff58
conteudo de 'r' = 5
endereco de 'r' = 0x22ff58
conteudo de 'p' = 0x22ff58
endereco de 'p' = 0x22ff54
conteudo APONTADO por 'p' = 5

-validar_entrada: testando conteudo


e endereco do parametro referencia 'param_ref'
conteudo inicial de 'param_ref' = 5
endereco de 'param_ref' = 0x22ff58
-validar_entrada: entre com um numero inteiro: 20
-main: conteudo de 'x' apos 'validar_entrada' = 20
*/

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


196 • AGIT informática • C++ • • Capítulo 6 ▪ Ponteiros e referências

A figura abaixo ilustra a situação de memória obtida por essa execução:

6.4 • Questões para revisão do Capítulo 6


Responda às questões abaixo, assinalando todas as respostas corretas (uma ou
mais). Em seguida, compare suas respostas com as respostas localizadas no Ane-
xo B-6, página 477. Caso não entenda, encaminhe as dúvidas ao instrutor.

Cap.6 - 1. Assinale as afirmações corretas, considerando o código abaixo:


int x = 5;
int * px = &x;
a.  'px' é uma referência para 'x'.
b.  'px' é um ponteiro para 'x'.
c.  'px' guarda o endereço de 'x'.
d.  O valor armazenado em 'px' é 5.
e.  O valor armazenado em 'x' é 5.
f.  É correto fazer: *x = 10;
g.  É correto fazer: *px = 10;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


6.4 • Questões para revisão do Capítulo 6 • C++ • AGIT informática • 197

Cap.6 - 2. Assinale as afirmações corretas, considerando o código abaixo:


int x = 5 ;
int & rx = x ;
a.  'rx' é uma referência para 'x'.
b.  'rx' é um ponteiro para 'x'.
c.  'rx' guarda o endereço de 'x'.
d.  O valor armazenado em 'rx' é 5.
e.  O valor armazenado em 'x' é 5.
f.  'rx' é um sinônimo de 'x'.
g.  É correto fazer: *rx = 10;
h.  É correto fazer: x = 10;
i.  É correto fazer: rx = 10;

Cap.6 - 3. Assinale as afirmações corretas, considerando o código abaixo:


int x = 5 ;
int * px ;
px = &x ;
a.  'px' é uma ponteiro para 'x'.
b.  O valor apontado por 'px', a partir da terceira linha, é 5.
c.  O código acima está incorreto e ocorrerá um erro de compilação.
Cap.6 - 4. Assinale as afirmações corretas, considerando o código abaixo:
int x = 5 ;
int & rx ;
rx = x ;
a.  'rx' é uma referência para 'x'.
b.  O valor armazenado em 'rx', Io mesmo que “x”) a partir da terceira linha,
é 5.
c.  O código acima está incorreto e ocorrerá um erro de compilação.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


198 • AGIT informática • C++ •

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 7 ▪ Iniciando orientação a objetos • C++ • AGIT informática • 199

• Capítulo 7
▪ Iniciando orientação a objetos

7.1 • Estruturas ......................................................................202


7.1.1 ▪ Estruturas e funções relacionadas .................................................204
7.1.2 ▪ Limitação da linguagem C ..............................................................204
7.1.3 ▪ Passando estruturas como argumento ..........................................207
7.1.4 ▪ A especificação const......................................................................210
7.2 • Estruturas em C++ : encapsulamento ...............................210
7.3 • Implementando as funções membras .................................213
7.4 • As palavras reservadas struct e class..................................214
7.4.1 ▪ Definindo o objeto apontado por “this” como const.........................215
7.4.2 ▪ Implementação das funções membras const ................................216
7.4.3 ▪ Testando a class Data ([Link])............................................223
7.4.4 ▪ Especificando funções como inline.................................................223
7.4.5 ▪ Usando um segundo parâmetro do tipo “Data”...............................225
7.4.6 ▪ Acrescentando funções operadoras à classe “Data”.......................228
7.4.7 ▪ Os operadores << e >> de ostream/istream ..................................229
7.4.8 ▪ Conclusão Parcial...........................................................................230
7.5 • Encapsulamento: reforçando conceitos. ..............................231
7.5.1 ▪ Funções inline.................................................................................231
7.5.2 ▪ O ponteiro this.................................................................................232
[Link] ▪ Definindo o objeto apontado por this como const........232
[Link] ▪ Exceção para a restrição const de this (mutable)........233
7.5.3 ▪ Função Construtora........................................................................234
[Link] ▪ Parâmetros para a função construtora.........................235
[Link] ▪ Contrutora default........................................................235
[Link] ▪ Construtora de cópia e operador de atribuição............235
[Link] ▪ Construtoras com conversão implícita e explícita........237
7.5.4 ▪ Função destrutora...........................................................................238
7.5.5 ▪ Alterando o ponto de entrada da aplicação: objetos externos........239
7.5.6 ▪ Classes e funções amigas..............................................................240
7.5.7 ▪ Membros estáticos de uma classe..................................................241
[Link] ▪ Membros de dados estáticos.......................................241
[Link] ▪ Funções-membro estáticas..........................................243

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


200 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

7.5.8 ▪ Objetos como membros de classes................................................244


[Link] ▪ Membros “objeto de outra classe”...............................244
[Link] ▪ Membros “ponteiro para objeto de outra classe”.........245
[Link] ▪ Membros “ponteiro para objeto da mesma classe”......246
7.5.9 ▪ Ambientes de nomes......................................................................247
[Link] ▪ Evitando colisões de nomes........................................247
[Link] ▪ Usando namespace para organizar conjuntos de softwa-
re. 248
[Link] ▪ O namespace "std"......................................................249
[Link] ▪ Exemplos de uso de namespace.................................250
7.6 • Questões para revisão do capítulo 7 ...................................254

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 7 ▪ Iniciando orientação a objetos • C++ • AGIT informática • 201

Orientação a objetos é uma das técnicas de programação suportadas por C++. Em C, é


possível implementar essa técnica, mas ao custo de um trabalho extra considerável, já
que ela não é suportada diretamente (nativamente) pela linguagem.
Resumidamente, essa técnica é baseada em três princípios:
 Encapsulamento: recursos para proteger os dados com o código, de manei-
ra inviolável.
 Pois muitos dos dados que usamos em um programa de computador devem
obedecer a determinadas regras.
 Por exemplo, não existe o dia 32. Há uma regra para isso.

 Então, uma variável "dia" deveria sempre estar "escondida", isto é, não aces-
sível para o conjunto do programa.
 Só deveria ser acessada em funções que conhecem a regra e verificam se
ela foi violada.
 E, ocorrendo uma violação, uma condição de erro deve ser estabelecida.

 Herança: recursos para reaproveitamento de código.


 Uma determinada camada de código realiza determinada tarefa.
 Para certas situações, é necessário complementar (ou estender) as caracte-
rísticas e/ou as funcionalidades envolvidas por essa tarefa.
 Nesse caso podemos criar uma segunda camada de código que deverá her-
dar tudo o que já foi feito na camada superior, acrescentando o que for ne-
cessário.
 Isso deve ser feito de tal modo que o código já pronto (a primeira camada)
não sofra alterações, de tal modo que aquilo que já está pronto continue fun-
cionando sem correr o risco de que erros sejam introduzidos em uma altera-
ção.
 Não haverá também a necessidade de testar novamente o código anterior, já
que não foi alterado.
 Polimorfismo: aqui temos também recursos para reaproveitamento de códi-
go, em um nível mais genérico.
 Em vários casos, ao escrevermos uma camada de código básica, sabemos
que ela pode implementar uma série de funcionalidades necessárias, mas
não todas.
 Para que aquilo que é reaproveitável seja útil, é preciso que essa camada
não pretenda ser completa.
 Ao contrário, ela deve deixar em aberto determinadas funções que devem
ser executadas, mas podem ser executadas de diferentes formas (polimor-
fismo = múltiplas formas).
 Por exemplo: um agendador de tarefas. Podemos construir uma aplicação
que tem a capacidade de medir o tempo e saber que, em determinado tempo,
determinada tarefa deve ser executada.
 Se o agendador quisesse fazer tudo, ele executaria apenas tarefas bem co-
nhecias no momento em que foi desenvolvido. No futuro, surgindo novas tare-
fas que também precisem ser agendadas, o nosso agendador não serviria
para tratá-las.
 Assim, o agendador deve resumir-se àquilo que é genérico: medir o tempo e
ter a abertura para, no devido momento, executar qualquer tarefa.
 Então, em outras camadas de código, podemos implementar tarefas específi-
cas (como efetuar um backup ou atualizar um anti-virus, etc, etc.).
REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional
202 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

 Cada uma dessas tarefas específicas poderá utilizar os serviços do agenda-


dor de tarefas genérico, para que ela seja posta em execução no momento
adequado.

Neste capítulo vamos tratar dos aspectos mais importantes de encapsulamento. Pois
esse é o princípio mais importante, sem o qual os demais não fariam muito sentido, já
que não estaria garantida a integridade dos dados.
Encapsulamento é pois a base (o alicerce) para implementações sólidas de orientação
a objetos.
Para sua implementação em C++, o ponto de partida é um recurso que já existia em C,
embora de modo incompleto: as estruturas de dados.

7.1 • Estruturas
Quase sempre temos dados que se complementam (podendo até ser interdependentes)
e que, assim, formam um conjunto de dados. Precisamos de uma maneira de expres-
sar esse fato real no código.
Por exemplo:
 Uma data, é composta (pelo menos) de 3 informações:
 dia
 mês

 ano

 Essas informações são interdependentes. Dependendo do mês, o último dia é


30 ou 31. E, para o mês de fevereiro, dependendo do ano o último dia será 28
ou 29.
 Além disso, mesmo que não houvesse essa interdependência, sabemos que es-
ses dados atuam em conjunto (complementares) para estabelecer uma data
completa.
 E existem regras: regras para dia, regras para mês (entre 1 e 12) e regras que
podem ser estabelecidas para o ano, dependendo da finalidade de uma data.
Em C, poderíamos organizar esse conjunto de dados da seguinte forma:
struct Data
{
int dia ;
int mes ;
int ano ;
};
Uma estrutura (struct) permite agrupar informações que formam um conjunto. Ela é
apenas um modelo para um novo tipo de dados. Esse tipo é constituído de campos ou
membros (cada um dos dados agrupados).
Esse novo tipo, pode ser usado para criar variáveis, as quais serão alocadas na memória
de acordo com o modelo declarado pela struct.
Por exemplo:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.1 • Estruturas • C++ • AGIT informática • 203

// Declarando variáveis de diversos tipos:


long x ;
double d ;
struct Data data_pagamento ;
struct Data data_vencimento ;

Detalhando:

Tipo Variável Comentário


long x 'x' é uma variável do tipo long
double d 'd' é uma variável do tipo double
struct Data data_pagamento 'data_pagamento' é uma variável
do tipo struct Data
struct Data data_vencimento 'data_vencimento' é uma variável
do tipo struct Data

Após as declarações acima, podemos ilustrar esquematicamente a situação da memó-


ria com a seguinte tabela:

endereço 1000 1004 1012 1024 1036


(hipótese) (1000+4) (1004+8) (1012+12) (1024
+ 12)
mnemôni-
x d data_pagamento data_vencimento
co
composi- um um dia mes ano dia mes ano
ção único único
valor valor (4+4+4 = 12 bytes (4+4+4 = 12 bytes
(4 bytes) (8 bytes) em SO de 32 bits) em SO de 32 bits)

Atribuindo valores a essas variáveis:


x=9 ; // 'x' é um único valor (apenas um campo de informação)
d = 8.7 ; // idem para 'd'
Mas, ao contrário de 'x' e 'd', 'data_pagamento' e 'data_vencimento' não são constituí-
das por um único valor, sendo, ao contrário um conjunto com 3 campos de informa-
ção. Então deve haver um meio de acessar cada campo. Isso é feito com um ponto.
Acessando cada campo das variáveis estruturadas (o ponto acessa o campo):
data_pagamento.dia = 10 ; // acessa o campo 'dia' (pagamento)
data_pagamento.mes = 2 ; // idem para 'mes'
data_pagamento.ano = 2010 ; // e para 'ano'
data_vencimento.dia = 5 ; // acessa o campo 'dia' (vencimento)
data_vencimento.mes = 3 ; // idem para 'mes'
data_vencimento.ano = 2011 ; // e para 'ano'

E agora teremos na memória:

variável x d data_pagamento data_vencimento

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


204 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

dia mes ano dia mes ano

valor 9 8.7 10 2 2010 5 3 2011

Sintetizando:
 Uma struct funciona como um descritor de campos. A estrutura é assim um
conjunto de campos.
 É algo muito semelhante à criação de uma tabela em um banco de dados.

 Quando criamos uma nova tabela em um banco de dados, o que fazemos é


justamente definir os nomes e tipos dos diferentes campos.
 Para a linguagem, uma struct cria um novo tipo de dados. Assim, após decla-
rar uma struct poderemos criar variáveis desse tipo.
 E para acessar os dados internos da struct (isto é, seus campos ou mem-
bros):
 usamos: o operador de ligação de membro, cujo símbolo é um ponto:

data_pagamento dia = 10 ; .
// a ligação do membro (ou campo) é simbolizada por um ponto

7.1.1 ▪ Estruturas e funções relacionadas


É possível perceber, no exemplo acima, a utilidade da struct. O simples fato de agru-
par os campos, permite expressar no código uma situação real: esses dados devem an-
dar sempre juntos, em um único conjunto. Mas isso ainda não é suficiente.
Após definir conjuntos de dados (structs), passamos a precisar de funções especiali-
zadas no tratamentos desses dados. Particulamente, é preciso garantir que sejam apli-
cadas as regras que devem regular os valores possíveis para cada campo.
Assim precisamos, no mínimo, de funções que validem os campos. Além disso outras
funções quase sempre são necessárias para atuar sobre cada conjunto de dados: impri-
mir, realizar cálculos, etc.
No caso da struct data, além de funções que garantam valores válidos para dia, mes
e ano, precisamos também de outras funções que implementem o tratamento de todas
as necessidades relacionadas a uma data.
Como, por exemplo, avaliar se uma data é maior que outra; ou calcular a diferença en-
tre duas datas em número de dias, etc.

7.1.2 ▪ Limitação da linguagem C


Na linguagem C, também escrevemos funções para atuar sobre cada struct.
Contudo, em C, não há uma maneira de relacionar intimamente os dados com as fun-
ções. Para a linguagem essas são funções como outras quaisquer. Do ponto de vista do
programador determinadas funções foram feitas para trabalhar com determinada
struct. Mas, em C, não há como expressar essa necessidade real no código.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.1 • Estruturas • C++ • AGIT informática • 205

 A conseqüência disso é que, em C, não podemos garantir (exceto com um


trabalho extra considerável) que os dados só serão alterados em fun-
ções que conhecem suas regras e impedem sua violação.
Ou seja: qualquer função poderá alterar os campos de uma struct.

Exemplo:

Poderíamos implementar a seguinte função, que recebendo uma data como argumento,
altera os seus campos. Mas analisa se os dados enviados para alteração estão em con-
formidade com as regras.
struct Data
{
int dia ;
int mes ;
int ano ;
int ok ; // flag que indica se a data está correta ou não, caso
// contenha valores incompatíveis com as regras
// (o seu tipo é int, pois, em C, não existe o bool)
};

struct Data data_altera ( struct Data dt , int dia , int mes , int ano )
{
[Link] = dia ;
[Link] = mes ;
[Link] = ano ;
if ( < avaliação > ) // avalia se dia, mes e ano estão em
// conformidade com as regras.
{
[Link] = 1 ; // os valores estão corretos
}
else
{
[Link] = 0 ; // condição de erro
}
return dt ;
}

E o flag 'ok' poderá ser usado em qualquer outra função que faça a leitura dos dados de
uma data:
void data_imprime ( struct Data dt )
{
if ( [Link] )
{
// os valores estão corretos: imprime a data normalmente
}
else
{
// condição de erro: imprime mensagem de erro.
}
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


206 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

Além disso seria preciso garantir que a data não será impressa sem que tenha sido
anteriormente alterada, de modo que o campo ok contenha o resultado de uma avali-
ação, e não "lixo" (um valor arbitrário qualquer).
 Para isso deveria haver uma função inicializadora.
A inicialização garante um estado estável para uma estrutura desde o seu
nascimento. Por exemplo, estabelecendo a condição de erro [ [Link] = 0 ; ].
Ou utilizando valores default (caso existam).
Em suma: inicialização é um elemento indispensável quando falamos
em encapsulamento.

Contudo, mesmo que escrevêssemos uma função "data_inicia":


struct Data data_inicia ( struct Data dt )
{
// ...
[Link] = 0 ; // condição de erro
return dt ;
}
Não há como garantir que ela sempre será chamada imediatamente após a criação de
uma variável do tipo struct Data.
Tudo dependerá do programador. Se o programador nunca esquecer de chamar as fun-
ções adequadas, tudo correrá bem. Mas isso não é uma base segura para escrever pro-
gramas, principalmente à medida em que eles crescem, tornando mais difícil assegurar
que, em todas as partes do código, tudo está sendo feito corretamente.
Este exemplo mostra essa instabilidade potencial:
int main()
{
struct Data pagamento ;
struct Data vencimento ;
pagamento = data_inicia ( pagamento ) ;
data_imprime (pagamento ) ; // imprimirá mensagem de erro: OK
pagamento = data_altera ( pagamento , 1, 10, 2010 );
data_imprime ( pagamento ) ; // imprimirá a data ou erro: OK

data_imprime ( vencimento ); //  o que será impresso aqui?


 A variável 'vencimento' está sendo impressa sem que tenha sido ini-
ciada ou alterada.
A função 'data_imprime' irá avaliar o campo 'ok', para saber se impri-
me a data ou uma mensagem de erro.
Mas qual será o valor desse campo? Indeterminado ("lixo").
Pois o seu valor atual não é resultado de qualquer avaliação.

// além disso, podemos:


// [Link] = ... ; // atribuir livremente qualquer valor a um campo
// sem usar a função adequada.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.1 • Estruturas • C++ • AGIT informática • 207

return 0 ;
}

 Todos os problemas apontados acima são facilmente resolvidos em


C++.

7.1.3 ▪ Passando estruturas como argumento


Os problemas apontados acima só serão adequadamente resolvidos em C++.
Mas há um outro problema que, mesmo em C, poderia ser resolvido de modo adequa-
do: a maneira como a variável estruturada está sendo passada para as funções, pois essa
é uma maneira ineficiente.
Para entender porque, vamos analisar qualquer uma das funções que estão recebendo
uma cópia de uma variável do tipo “Data” como parâmetro.
Por exemplo, a função data_inicia.
int main()
{
struct Data dtHoje;
dtHoje = data_inicia ( dtHoje );
//...
}

struct Data data_inicia( struct Data dt )


{
//...
[Link] = 0 ;
return dt ;
}
Na função main criamos a variável dtHoje do tipo struct Data.
Em seguida chamamos a função data_nicia passando a variável dtHoje como parâme-
tro.
O que ocorrerá aí será uma cópia do conteúdo armazenado na memória que apelida-
mos de “dtHoje” para uma outra memória, pertencente à função data_inicia, a qual
apelidamos de “dt”.
E dt é uma variável estruturada, com vários campos (e certamente essa estrutura teria
ainda mais campos para atender a todas as funcionalidades necessárias).
Assim todos esses campos precisarão ser copiados de um lugar da memória para outro.
E isto reduz a performance.
Além disso, a função data_nicia, por sua vez, deve retornar uma cópia de dt, para
que a função que a chamou possa receber as alterações que ela fez nesse parâmetro.
Por isso tivemos que fazer:
dtHoje = data_inicia ( dtHoje );

Pois, do contrário as alterações seriam feitas em dt, apelido de uma memória que per-
tence à função data_inicia (e que é liberada, e com isso destruída, quando data_inicia
retorna).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


208 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

Assim, para que o trabalho não seja perdido, deve haver uma nova cópia, desta vez em
sentido inverso: desse modo o conteúdo de dt deve ser copiado para a área de retorno
da função (o que é feito pela diretiva return) e, finalmente, deve ser copiado dessa
área de retorno para a variável dtHoje, em main (o que é feito pelo operador de atri-
buição [ = ] ).

 Assim teremos sucessivas cópias não de um pequeno valor mas de


uma variável estruturada que representa um conjunto de diversos valores
(os seus campos), podendo ter um tamanho total de muitos bytes.

Por isso, devemos usar outra forma de passagem de parâmetros no caso de variáveis
estruturadas: elas sempre terão um certo número de campos (muitas vezes, uma quanti-
dade grande de campos). Seria bom que pudéssemos evitar a perda de tempo que ocor-
rerá com a cópia de todos os campos e também o desperdício da memória duplicada
(com muitos bytes).

 Esse objetivo pode ser atingido com ponteiros (C e C++) ou com referên-
cias (C++).

Já vimos esse assunto (na seção 6.2.2, página 187).


E vimos que há dois motivos para passar argumentos por endereço ou por refe-
rência:
 Funções que devem retornar mais do que um valor: usam parâmetros "de saída"
(ponteiros ou referências).
 Além disso, por motivos de performance: ao passar memórias com uma grande
quantidade de bytes, devemos também definir o parâmetro da função como um
ponteiro ou uma referência.

Então as funções exemplificadas acima, deveriam ser escritas assim:


void data_inicia( struct Data * pdt )
{
// ...
pdt->ok = 0 ;
}
void data_Altera ( struct Data * pdt, int dia, int mes, int ano ) ;
void data_imprime ( struct Data * pdt ) ;

Ligação de membro a ponteiro para estrutura:


Para acessar os membros de dados de uma variável estruturada a partir de um pontei-
ro (como é feito acima com [ pdt->ok = 0; ]) é preciso usar o operador de ligação de
membro corretamente.
Assim, não poderíamos fazer:
pdt.m_OK = FALSE ; //  ERRO: 'pdt' armazena um endereço,
// que é um número inteiro positivo.
// Logo, 'pdt' não possui campos...

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.1 • Estruturas • C++ • AGIT informática • 209

Pois “pdt” é um ponteiro, e assim sendo ele simplesmente não tem campos… Logo,
'ok' não é um membro (ou um campo) de 'pdt'.
A variável 'pdt' está armazenando um endereço. E é nesse endereço que estarão os
campos 'ok', 'dia', etc.
Logo não podemos ordenar: [ acesse o campo ‘ok’ de ‘pdt’ ].

 Pois a ordem correta é :


 entenda ‘pdt’ como o lugar onde está o endereço de uma variável do tipo
‘struct Data’;
 leia esse endereço;

 agora vá até esse endereço, acessando esse outro lugar da memória;

 e, agora sim, chegando lá, acesse o campo ‘ok’.

Por isso, temos que usar o operador ponteiro para indicar que esse acesso indireto
deve ser feito:
( * pdt ).ok = 0 ;
Nesses casos, como operações com ponteiros para estruturas são muito comuns, a lin-
guagem fornece um operador específico (simbolizado por [ -> ] ).
Ele serve para ligar campos a ponteiros para estruturas. Não existe qualquer diferença
de funcionamento. O operador específico é fornecido apenas para simplificar a escrita
e torná-la mais legível.
Assim, a melhor forma de uso é esta:
pdt->ok = 0 ;

Como vimos também, em C++, prefira referências a ponteiros, sempre que isso seja
possível.
Caso escrevêssemos uma função semelhante em C++, deveríamos fazer isto:
void data_inicia( Data & dt ) // referência
{
// ...
[Link] = 0 ; // Se uma referência é formalmente um sinônimo
// (e não um ponteiro), usamos nomalmente o operador ponto
}
Observe também que, em C++, não é necessário declarar a variável
estruturada usando a palavra reservada 'struct' como parte do nome
do tipo.
Em C++ 'Data', simplesmente, já é considerada como o nome do tipo.

 E, como regra geral, estruturas não devem ser passadas por valor.
E sim ou por endereço, ou, preferencialmente (em C++), por referência.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


210 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

7.1.4 ▪ A especificação const


Resta um: quando passamos um endereço ou uma referência, a função que o recebe
passa a ter acesso a uma variável declarada em outra função, podendo alterá-la
Se a função que recebe endereços ou referências tiver como objetivo justamente o for-
necimento de retornos extras, então é exatamente isso que se deseja.
Mas se o objetivo for apenas velocidade de cópia(performance), então devemos deixar
claro que essa função não irá alterar a memória cujo endereço ou referência ela rece-
beu e que usará esse endereço ou referência apenas para fins de leitura da variável
apontada.
Assim poderemos ter certeza que, caso essa memória passe a apresentar comportamen-
tos indevidos, a origem do problema poderá estar em qualquer lugar, menos na função
que recebeu o endereço ou a referência exclusivamente para leitura.
Fazemos isso especificando ponteiros e referências em situações assim como const, o
qual estabelece uma condição read-only para os dados
Nos exemplos acima, esse seria o caso da função data_imprime.
Ela não foi projetada para alterar nada, e sim apenas para ler e imprimir.
Então, ela deveria ser declarada de uma das duas maneiras abaixo:
Usando ponteiros:
void data_imprime( const Data * pdt ) ; // Os dados apontados são
// 'read-only' nesta função
Usando referências:
void data_imprime( const Data & dt ) ; // Os dados referenciados são
// 'read-only' nesta função

7.2 • Estruturas em C++ : encapsulamento


Vimos que em C não há como garantir o encapsulamento de um modo simples. Já em
C++ isso será possível.

 Em C++ podemos declarar tanto membros de dados(campos) como tam-


bém as próprias funções dentro da declaração da struct, de modo que
tanto os campos de dados como funções sejam membros da estrutura.
Além disso poderemos determinar que apenas funções declaradas dentro
da struct possam acessar os membros de dados.
E, ainda, a struct C++ permitirá uma inicialização garantida e segura
que sempre antecederá qualquer tentativa de uso das variáveis estrutura-
das.
Essas características resolverão o problemas que já apontamos na estru-
tura C.
E, além delas, teremos uma série de outros recursos – que resolverão ou-
tras insuficiências de C.

Vejamos então como ficaria a nossa estrutura em C++.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.2 • Estruturas em C++ : encapsulamento • C++ • AGIT informática • 211

#include <string> // tudo aqui em um arquivo header (data.h, [Link].)


// já que poderá ser usada em muitos projetos.
struct Data
{
private:

 Ao declarar membros com acesso private, estamos determinando que eles


só poderão ser acessados nas funções declaradas dentro desta struct
(ou, como veremos depois, em funções que esta struct declare como
"amigas", isto é, colaboradoras).

char m_dia;
char m_mes;
short m_ano;
// variáveis de controle para validação do ano:
short m_anoMin , m_anoMax;
// flag que indica se a data está correta ou não:
bool m_OK; // C++ tem o tipo “bool” ...
public:

 Membros declarados como public podem ser acessados em qualquer fun-


ção, esteja ela declarada aqui ou não (funções globais ou de outra classe).

// valores default para os anos mínimo e máximo, e constantes para meses:


enum { AnoMinDefault = 1 , AnoMaxDefault = 9999 } ;
enum { Janeiro=1, Fevereiro, Marco, Abril, Maio, Junho,
Julho, Agosto, Setembro, Outubro, Novembro, Dezembro};

 A função abaixo (“Data()”), por ter o mesmo nome da struct, cumpre um


papel especial: ela é classificada como construtora e será chamada auto-
matica e obrigatoriamente sempre que uma variável deste tipo for criada
(podendo assim ser usada para a inicialização dos membros, além da
aquisição de recursos – como abertura de arquivos e outros)
s

Data ( /* parâmetro oculto */ ) ;

 Sobrecarga: função com o mesmo nome, mas com tipos de parâmetros


diferentes. Abaixo, temos uma sobrecarga da construtora.
Podemos chamá-la de "construtora de conveniência" pois permite que
os valores dia, mes e ano sejam passados ao criar (ou instancia) uma
variável (objeto) do tipo "Data".

Data ( /* parâmetro oculto , */ char dia, char mes, short ano ) ;

// outras funções-membro da struct


void altera ( /* parâmetro oculto , */
char dia, char mes, short ano) ;
char ultimoDiaMes (); // calcula o último dia do mês (m_mes)
bool anoBissexto (); // verifica se o ano (m_ano) é bissexto
std::string toString(); // formata a data em uma string
} ; // fim da definição da struct Data (note o ponto e virgula após a chave)

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


212 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

As funções membras da estrutura serão usadas assim:


int main()
{
Data dtHoje ; /* aqui é chamada automaticamente a função
“Data()” (construtora) , que receberá como
parâmetro oculto o endereço de “dtHoje”. */
[Link]( 1, 1, 2001) ; /* esta função receberá 4 argumentos:
3 passados explicitamente e mais um,
oculto, que é o endereço de “dtHoje”. */

Data dtOutra ( 2, 1, 2010 ) ; // aqui será chamada


// a "construtora de conveniência"
// …
}

 Anote:

1) Em C++ as funções fazem parte da estrutura, como membros


2) E o acesso aos dados deverá ser feito obrigatoriamente através de funções de-
claradas dentro da struct caso eles tenham sido declarados como privativos da es-
trutura (private).
3) No momento da declaração da variável (inicialização) é chamada automaticamente
a função que tem o mesmo nome da estrutura (construtora).
4) Não precisamos (e não devemos) passar como parâmetro a variável estruturada ou
o seu endereço: isto será feito automaticamente pelo compilador (pois o compila-
dor acrescentará um parâmetro oculto contendo o endereço da variável estrutu-
rada que disparou a chamada à função).
E o nome convencionado para esse parâmetro oculto é “this”(que, em princípio,
pode ser passado por registrador e não pela pilha).

Usamos as funções-membro com a mesma sintaxe com que usamos os membros de da-
dos. Criamos uma variável usando o tipo estruturado em sua declaração. A partir daí
simplesmente acessamos os membros de dados e funções, utilizando o operador de li-
gação de membro.
Só não devemos esquecer que dentro de funções não pertencentes à struct nunca pode-
remos acessar os membros declarados como private (sejam membros de dados ou fun-
ções-membro).

 Assim, no exemplo de "main", acima, temos a ressaltar que:

 Só podemos chamar a função “altera” a partir de uma variável já criada e que


tenha sido declarada com o tipo “Data”. O endereço dessa variável é então pas-
sado para a função.
 Mas essa chamada ocasionaria um erro de compilação se, na struct, “altera”
fosse declarada como private.
 Assim, em uma função externa à struct, como é o caso de main, só podemos
acessar os membros declarados como public.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.2 • Estruturas em C++ : encapsulamento • C++ • AGIT informática • 213

7.3 • Implementando as funções membras


Agora, em um arquivo “.cpp” ([Link], [Link].), para escrever a implementação das
funções membras da struct, precisaremos indicar que elas não são funções globais ou
membras de uma outra struct qualquer, e sim, exatamente, funções membras desta
struct.
Isso é feito através de um operador, o “operador de resolução de escopo”, que é repre-
sentado pelo símbolo “::” (dois pontos duplos).

 Abaixo, a implementação da função construtora. Note que construtoras


não têm tipo de retorno (são obrigatoriamente void e não podemos de-
signar um tipo).
Além disso, o operador de resolução de escopo [ :: ], usado abaixo indi-
ca: “função Data, membra da estrutura Data”
 E, ainda, antes do corpo da função ( {…} ) devemos inicializar os mem-
bros que necessitem de inicialização obrigatória. Isso deve ser feito inici-
ando com o operador de inicialização de membros (:) e usando vírgu-
las para os próximos membros.

// os campos são acessados a partir do ponteiro “this”:*/


Data::Data( /* Data * this */ ) // parâmetro oculto cujo nome é “this”

: m_anoMin(AnoMinDefault) // Inicialização de membros

, m_anoMax(AnoMaxDefault)

, m_OK(false) // C++ tem as palavras reservadas “true” e “false” ...

{
// se necessário, adquirir recursos aqui (abrir arquivos, etc).
} // Nenhuma outra tarefa deve ser executada na construtora.

// Implementando a "construtora de conveniência":


Data::Data( /* Data * this , */ char dia, char mes, short ano)
: m_anoMin(AnoMinDefault) // Inicialização de membros
, m_anoMax(AnoMaxDefault)
// , m_OK(false) // não é preciso inicializar m_OK, pois abaixo
// será chamada a função "altera" que atribuirá um valor a m_OK
// de acordo com a correção ou não dos argumentos passados:
{
altera ( dia, mes, ano ) ; 
// se necessário, adquirir recursos aqui (abrir arquivos, etc).
} // Nenhuma outra tarefa deve ser executada na construtora.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


214 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

// função “Altera”, da estrutura Data:


void Data::altera ( /* Data * this , */
char dia , char mes , short ano )
{
m_dia = dia ;

 Relembrando:
O compilador C++ irá gerar o código necessário para a passagem do
endereço da variável que disparou a chamada a esta função - e que
será recebido como “this” (parâmetro oculto).
E o compilador C++ também liga automaticamente os campos
da struct ao ponteiro recebido.
Logo não precisamos fazer:
this->m_dia = dia;
Exceto se o nome do membro fosse “dia” (o mesmo nome
do parâmetro). Nesse caso, teríamos que fazer:
this->dia = dia; // membro = argumento. 
m_mes = mes;
m_ano = ano;
// análise e validação dos valores recebidos
// (obs: o exemplo usa 31, como último dia do mês. Mais a frente
// implementaremos o código para calcular o último dia de cada mês)
m_OK = m_ano >= m_anoMin && // and
m_ano <= m_anoMax &&
m_mes >= Janeiro && m_mes <= Dezembro &&
m_dia >= 1 && m_dia <= 31 ; // depois, trocar 31
// por uma chamada à função ultimoDiaMes
}

 As demais funções declaradas na struct serão implementadas mais abai-


xo, após estudarmos o especificador const.

7.4 • As palavras reservadas struct e class.


C++ tem duas palavras reservadas que fazem a mesma coisa, com uma pequena dife-
rença: struct e class.
Podemos usar qualquer uma das duas para declarar uma estrutura de dados e funções.
A única diferença entre elas é que, na struct o acesso “default” é public, enquanto na
class o acesso “default” é private.
 Não existe qualquer outra diferença.

Assim, teríamos:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 215

struct Qualquer
{
int x ; /* como não explicitamos o acesso, será usado o “default”;
logo, será public, já que usamos struct
*/
};
class Qualquer
{
int x ; /* como não explicitamos o acesso, será usado o “default”;
logo, será private, já que usamos class
*/
};

Mais a frente veremos porque existem as duas palavras reservadas.


Por enquanto basta estabelecer que, em geral, é melhor usar class já que esta é mais
segura (se houver omissão, vale o private, protegendo os membros contra acesso ex-
terno) .

7.4.1 ▪ Definindo o objeto apontado por “this” como const


Já vimos que se uma função recebe um ponteiro, mas precisa apenas ler a variável
apontada, deixávamos isso claro especificando o ponteiro como const:
void Imprime ( const Data * pdt );

Mas, neste último caso, como fazer isso em uma função-membro C++, já que o ende-
reço da variável é passado implicitamente através do parâmetro oculto this?
Para essa situação, a linguagem reservou uma sintaxe especial, já que o parâmetro é
oculto:
<tipo> Nome_Funcao ( <parâmetros explícitos> ) const ;

O especificador const é colocado ao final do cabeçalho da função. E isso significa en-


tão que, nessa função, o ponteiro this aponta para um objeto recebido como const
(read-only).

 Observar que o ponteiro this é, por definição da linguagem, sempre


const.
Isto significa que não podemos alterar o endereço armazenado em
this. Por exemplo:

void Classse::func( )
{
this = new Classe; //  ERRO: o this não pode ser alterado.
}

O que significa que podemos entender essa função como:


void Classe::func ( /* Classe * const this */ ) ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


216 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

 Mas se fizermos:
void Classe::func( ) const;
Neste caso além do ponteiro this ser inalterável, o objeto aponta-
do por this também não pode ser alterado.

Podemos entender assim:


void Classe::func ( /* const Classe * const this */ ) const;
 Em consequência teremos um erro aqui:
void Classe::func ( /* const Classe * const this */ ) const
{
// assumindo que “classeMembro” é um membro de “Classe”:
classeMembro = …. ; //  ERRO: o objeto apontado por this
// não pode ser alterado aqui.
}

Então, vamos aplicar o especificador const nas funções em que ele se aplique na class
Data. Analisemos as funções:
 construtora: irá alterar dados, então não podemos usar const;
e esta é uma regra geral para construtoras, já que seu papel é inicializar mem-
bros.
 “altera” : como o próprio nome está dizendo… não pode usar const.
 “ultimoDiaMes”: não deve alterar dados, apenas irá apurar o último dia de
cada mês; então deve ser especificada como const.
 “anoBissexto”: idem, pois deverá apenas avaliar se o ano é bissexto;
 “toString”: idem, pois apenas lê os campos para formatar uma string
.

7.4.2 ▪ Implementação das funções membras const


Inicialmente, devemos alterar os protótipos das 3 últimas funções declaradas em
Data ("data.h", por exemplo), e, também, trocar a palavra struct por class.
class Data
{
…………………………………………………………..
// apurar o último dia de cada mês:

 
char ultimoDiaMes( /* const Data * const this */ ) const ;
// descobrir se um ano é bissexto:

s
bool anoBissexto( /* const Data * const this */ ) const ;
// converter dia, mês e ano para uma string e retornar esse string:
std::string toString( /* const Data * const this */ ) const;
};

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 217

No arquivo [Link] iremos escrever o corpo das funções:

#include <sstream> // streams que alimentam uma string


#include <iomanip> // manipuladores de IO
#include "data.h" // reconhecer os símbolos da class Data

// …............................................................................
// As funções construtoras e a função altera,
// já foram implementadas acima.

// Converter a data para uma string:


 
std::string Data::toString ( /* const Data * const this */ ) const
{
std::ostringstream sout; // funciona como "cout" (classes de stream)
// mas sua saída é um objeto-membro do tipo std::string

if ( isValid())
{
[Link]('0'); // preenchedor à esquerda em função da largura(setw)
sout << std::setw(2) << m_dia << '/'
<< std::setw(2) << m_mes << '/'
<< std::setw(4) << m_ano;
}
else
sout << "***ERRO***";

return [Link](); // retorna o membro std::string alimentado pelo stream


}

Abaixo, as demais funções, como exercício.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


218 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

Exercícios:

1) Escreva a função “anoBissexto”, sabendo que:


- um ano é bissexto quando é divisível por 4
- mas não por 100, exceto se for divisível por 400.

Exemplos:
2000 : bissexto, pois é divisível por 400;
1800 : não-bissexto, pois não é divisível por 400, e,
embora seja divisível por 4, também é divisível por 100.
1996 : bissexto, divide por 4 mas não por 100
1997 : não-bissexto, pois não é divisível por 4

2) Escreva a função “ultimoDiaMes”, sabendo que:


- fevereiro ……… : 28 ou 29 dias, se o ano for bissexto;
- janeiro a julho … : meses pares tem 30 dias, e ímpares 31 dias;
- agosto a dezembro: meses pares tem 31 dias,
e ímpares 30 dias;

Exemplos:
mês 6(junho)……… : anterior a agosto e par -> 30 dias;
mês 7(julho) ……… : anterior a agosto e ímpar -> 31 dias;
mês 8(agosto)…….. : posterior a julho e par -> 31 dias;
mês 9(setembro)…. : posterior a julho e ímpar -> 30 dias;

Recursos que você já aprendeu e poderá usar:


 if ; else ;
 operador lógico “and” [ símbolo && ] ;
 operador de módulo (resto de divisão) [ símbolo % ]
 operador de bit’s “and” [ símbolo & ]
 operadores relacionais em geral( igual a, não-igual a, maior que, menor que …)

 Após finalizar os exercícios compare com as soluções expostas na


próxima página.

Se tiver dúvidas anote e depois apresente-as ao instrutor.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 219

Solução para os exercícios.


Serão apresentadas aqui diversas alternativas para cada um dos dois exercícios.
A idéia é que você perceba as diversas maneiras de usar alguns recursos básicos da lin -
guagem. E assim exercitaremos o uso dos operadores e das condições
verdadeiro/falso em C e C++.
Você pode escolher qualquer uma delas (ou então aquela que você desenvolveu) e usar
no seu arquivo [Link]. Contudo, há uma diferença de performance entre elas. Pri-
meiramente é apresentada uma versão possivelmente menos eficiente (execução mais
lenta) e, em seguida, uma possivelmente mais eficiente (execução mais rápida).
Assim, a última será sempre a mais eficiente:

a) anoBissexto:

// a.1) primeira versão:


bool Data::anoBissexto( ) const
{
// se não for divisível por 4 não é bissexto:
if ( m_ano % 4 != 0 ) // ou usando and de bits: if( (m_ano&3) !=0 )
return false;
// se divisível por 4 mas não por 100, exceto se divisível por 400, é bissexto:
return m_ano % 100 != 0 || m_ano % 400 == 0;
// nos dois casos acima não podemos usar o and de bits
// pois, ao contrário de 4, os números 100 e 400 não são potências de 2
.
}
// a.2) segunda versão (usa o operador lógico OR [ símbolo: || ]):
bool Data::anoBissexto( ) const
{
return m_ano % 400 == 0 || // OR
( m_ano % 4 == 0 && // AND
m_ano % 100 != 0
);
}
// a.3) terceira versão (usa o operador lógico NOT [ símbolo: ! ]):
bool Data::anoBissexto( ) const
{
// o resto zero, nas divisões por 4 e por 400, diz que o ano é
// (ou, no caso de 4, tem chances de ser) bissexto;
// então inverta o zero (not zero)
// para que o resultado seja verdadeiro (pois zero é falso)
// [ !0 -> 1(true) ];
// e na divisão por 100, o resto diferente de zero diz que o ano
// tem chances de ser bissexto (então não é preciso fazer “! = 0”) :
return !(m_ano % 400) || // OR
( !(m_ano % 4) && // AND
(m_ano % 100)
);
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


220 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

/* a.4) quarta versão (procura executar as operações da mais frequente para


a menos frequente - afinal, a comparação com 400 colocada
em primeiro lugar, está privilegiando uma situação menos frequente):
*/
bool Data::anoBissexto( ) const
{
return !(m_ano % 4) && // AND
( (m_ano % 100) || //OR
!(m_ano % 400)
);
}
/* a.5) quinta versão (usa o operador bitwise AND, para apurar
o resto da divisão por 4, pois, nesse caso, através de um AND,
bit a bit, entre o ano e a constante 3, é possível saber
se é divisível por 4, sem usar o operador de módulo, mais lento,
já que 4 é uma potência de 2.
*/
bool Data::anoBissexto( ) const
{
/* Observe que:
1 [0001] & 3 [0011] -> 0001 -> 1
2 [0010] & 3 [0011] -> 0010 -> 2
3 [0011] & 3 [0011] -> 0011 -> 3
4 [0100] & 3 [0011] -> 0000 -> 0
5 [0101] & 3 [0011] -> 0001 -> 1
6 [0110] & 3 [0011] -> 0010 -> 2
7 [0111] & 3 [0011] -> 0011 -> 3
8 [1000] & 3 [0011] -> 0000 -> 0
9 [1001] & 3 [0011] -> 0001 -> 1
10 [1010] & 3 [0011] -> 0010 -> 2
11 [1011] & 3 [0011] -> 0011 -> 3
12 [1100] & 3 [0011] -> 0000 -> 0
... etc ...
Enfim: apenas múltiplos de 4 em uma operação and, bit a bit,
contra a constante 3,apresentam resultado zero.
Então:
*/
return !(m_ano & 3) && // AND
( (m_ano % 100) || //OR
!(m_ano % 400)
);
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 221

b) ultimoDiaMes:

// b.1) primeira versão:


char Data::ultimoDiaMes( ) const
{
if ( m_mes == Fevereiro ) // ‘Fevereiro’ é uma constante da class
{
if ( anoBissexto() ) // se o ano for bissexto…
return 29 ;
return 28 ; // se chegou até aqui, é porque o ano não é bissexto.
}

// 'Janeiro' ‘Julho’, 'Agosto' e 'Dezembro são constantes da class:


// janeiro a julho, inclusive estes, exceto fevereiro
if ( m_mes >=Janeiro && m_mes <= Julho )
{
if ( m_mes & 1 ) // se o mês é ímpar
return 31 ;
return 30 ; // se chegou até aqui, é porque o mês é par.
}

// agosto a dezembro, inclusive estes:


if ( m_mes >= Agosto && m_mes <= Dezembro )
{
if ( m_mes & 1 ) // se o mês é ímpar
return 30 ;
return 31 ; // se chegou até aqui, é porque o mês é par
}.

return 0; // se chegou aqui, é um mês inválido


}

/* b.2) segunda versão:


(nesta versão usaremos o operador condicional ternário;
esse operador funciona como um “if inline”:
(condicao) ? resultado_se_condicao_verdadeira
: resultado_se_condicao_falsa ;
portanto o símbolo ”?” é uma pergunta que exige resposta verdadeira
e o símbolo “:”(dois pontos) significa “do contrário” (else).
*/

char Data::ultimoDiaMes( ) const


{
if ( m_mes == Fevereiro )
return ( anoBissexto() ) ? 29 : 28; // usa o operador condicional
if (m_mes >= Janeiro && <= Julho) // janeiro a julho exceto fevereiro
return ( m_mes & 1 ) ? 31 : 30;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


222 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

if ( m_mes >= Agosto && m_mes <= Dezembro)


return (m_mes & 1 ) ? 30 : 31;

return 0; // mês inválido


}
// b.3) terceira versão (economiza testes de condição):
char Data::ultimoDiaMes( ) const
{
if ( m_mes == Fevereiro )
return 28 + anoBissexto(); // soma 1, se for bissexto, do contrário zero
if ( m_mes >= Janeiro && m_mes <= Julho) // exceto fevereiro
return 30+( m_mes & 1 ); // soma 1, se for ímpar e zero se não for
// agosto a dezembro:
if ( m_mes >= Agosto && m_mes <= Dezembro )
return 31-(m_mes & 1); // subtrai 1, se for ímpar e zero se não for.
return 0; // mês inválido
}

// b.4) quarta versão (usa operador condicional no lugar do último ‘if’):


char Data::ultimoDiaMes( ) const
{
if ( m_mes < Janeiro || m_mes > Dezembro )
return 0; // mês inválido
if ( m_mes == Fevereiro)
return 28 + anoBissexto();
return ( m_mes <= Julho) ? 30+ ( m_mes & 1 )
: 31 - ( m_mes & 1 );
}

/* b.5) quinta versão (usa o operador xor bit a bit).

 Para entender esta versão consulte a explicação deste exemplo na se-


ção sobre o operador xor, página 357.
*/

char Data::ultimoDiaMes( ) const


{
if ( m_mes < Janeiro || m_mes > Dezembro )
return 0; // mês inválido

return ( m_mes==FEVEREIRO) ? 28 + anoBissexto( )


: 30 + ( (m_mes & 1) ^ (m_mes > Julho) );
// usando o xor bit a bit:
}
Após inserirmos, no arquivo [Link], uma das versões da “anoBissexto” e uma das
versões da “ultimoDiaMes”(escolhidas entre as expostas acima, ou então use aquelas
que você mesmo escreveu), teremos então concluído a primeira fase do nosso projeto
“Data”. Não esqueça de trocar 31 por ultimoDiaMes() na função altera.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 223

Já podemos testá-lo.

7.4.3 ▪ Testando a class Data ([Link])


O código para teste, a ser escrito no arquivo [Link], é o seguinte:
#include "Data.h"
#include <iostream>
void imprime(const Data & dt ) // função auxiliar para imprimir Data
{
std::cout << [Link]() << '\n';
}

int main()
{
Data d1;

[Link](31,1,2001); imprime(d1); // resultado: 31/01/2001

[Link](29,2,2001); imprime(d1); // resultado: ERRO

[Link](29,2,1997); imprime(d1); // resultado: ERRO

[Link](29,2,1800); imprime(d1); // resultado: ERRO

[Link](29,2,1996); imprime(d1); // resultado: 29/02/1996

[Link](29,2,2000); imprime(d1); // resultado: 29/02/2000

[Link](31,6,2001); imprime(d1); // resultado: ERRO

[Link](31,7,2001); imprime(d1); // resultado: 31/07/2001

[Link](31,8,2001); imprime(d1); // resultado: 31/08/2001

[Link](31,9,2001); imprime(d1); // resultado: ERRO

[Link](31,12,2001); imprime(d1); // resultado: 31/12/2001

return 0;
}
E o teste está correto, imprimindo as datas quando os dados estão bons e imprimindo
mensagens de erro quando os dados estão inválidos.

7.4.4 ▪ Especificando funções como inline.


Quando uma função é especificada como "inline", isso significa que o compilador po-
derá substituir a sua chamada, diretamente pelo próprio corpo (implementação) da
função.
Isto é: usará o código da função nos lugares em que ela é chamada, poupando assim
chamadas de função para funções muito pequenas ou que são pouco usadas.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


224 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

Exemplo:
// função que retorna o menor de dois valores do tipo int:
inline int Minimo( int x , y )
{
return x < y ? x : y ;
}

 Anote: a função acima será usada como uma função qualquer.


Mas poderá não será tratada pelo compilador como uma função normal,
como veremos abaixo.

int main( )
{
int a, b;
........
int c = Minimo ( a , b ); //  esta linha será substituída por:
int c = a < b ? a : b;
int c = Minimo ( ++a , --b ) ; //  será substituído por:
++a ; // (1)
--b ; // (2)

 Notar que primeiro são resolvidas as pendências, para de-


pois ser realizada a operação prevista pela inline "Minimo":

int c = a < b ? a : b ; // (3)


....................................
}

Na class “Data” podemos aplicar a especificação inline a algumas funções.


As duas candidatas são as funções “anoBissexto” e “ultimoDiaMes”.
Pois, se estivermos usando as últimas versões propostas para elas mais acima (ou ver-
sões parecidas), teremos duas funções com apenas uma a duas linhas de código.
Então poderiam ficar assim:
inline bool anoBissexto( ) const ;
inline char ultimoDiaMes( ) const ;
Lembrar que:

 A especificação inline solicita ao compilador que no lugar da chamada da


função, ele insira, diretamente, o próprio código da função.
Assim, funções inline devem ser implementadas nos arquivos header (.h),
ou em arquivos por ele incluídos, de modo que seu corpo esteja disponível
para o compilador caso ele opte por efetuar a substituição.

Em consequência, deve ser feito o seguinte (no arquivo data.h):


class Data
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 225

..................................................................
// protótipos das funções inline:
inline bool anoBissexto( ) const ;
inline char ultimoDiaMes( ) const ;
};

// implementação das funções inline que serão usadas em outros módulos,


// deve ser feita aqui, no próprio arquivo data.h,após a declaração da classe,
// ou então em um outro arquivo aqui incluído:

inline bool Data::anoBissexto( ) const


{
...........................................
}
inline char Data::ultimoDiaMes( ) const
{
...........................................
}

7.4.5 ▪ Usando um segundo parâmetro do tipo “Data”.


Na estrutura C++ as funções-membro recebem como primeiro parâmetro oculto o en-
dereço da variável a partir da qual a função foi chamada. É o ponteiro this.
Em algumas funções precisaremos também de uma segunda variável desse tipo estrutu-
rado.
Por exemplo, se precisássemos comparar duas “Datas”, para saber qual é a mais
antiga(menor) ou qual é a mais atual(maior), poderíamos ter a seguinte função mem-
bro:
int Compara( /* const Data * this , */ const Data * pdt2 ) const;
/* retorna : 0 (zero) se estiverem iguais;
< 0 se a primeira estiver menor;
> 0 se a primeira estiver maior.
*/

Que seria usada assim:


Data dtPagamento ;
Data dtVencimento ;
if ( [Link]( &dtVencimento ) == 0 )
printf ( “Datas iguais\n”);

Nesse caso, o endereço de dtPagamento é passado no primeiro parâmetro (o parâme-


tro oculto this), e o endereço de dtVencimento é passado explicitamente como segun-
do parâmetro (pdt2).
Mas já sabemos que, em C++, há uma forma mais eficiente de fazer isso. Podemos (e
devemos) passar o segundo parâmetro por referência (ao invés de endereço),
Então, Na declaração da classe (data.h), acrescente o seguinte protótipo, onde o segun-
do argumento é uma referência:
// na função prototipada abaixo,

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


226 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

// o parâmetro ‘this’ é um ponteiro, especificado como const


// e o parâmetro ‘rdt2’ é uma referência (especificada como const):
int Compara( const Data & rdt2 ) const;
/* retorna : 0 (zero) se estiverem iguais;
< 0 se a primeira estiver menor;
> 0 se a primeira estiver maior.
*/

Exercício:

1 - No arquivo [Link], escreva a função “Compara”.


2 - Depois, no arquivo [Link], acrescente algumas linhas para testar a
nova função.

Nesta versão, ainda não temos um número único, que simplificaria a compa-
ração.

Então será preciso comparar ano, mês e dia separadamente,


sabendo-se que:
- ano vale mais que mês;
- mês vale mais que dia.
– retorno da função : leia o comentário acima, logo abaixo do protótipo.

 Depois compare a sua solução com as duas soluções que são ex-
postas na próxima página.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 227

Função “Compara” (arquivo [Link]):

// a) versão 1:
int Data::Compara( /* const Data * this , */ const Data & other ) const
{

if ( this->m_ano > other.m_ano )


return 1; // primeira maior
// o this é assumido por default... Então:
if ( m_ano < other.m_ano)
return -1; // primeira menor
if ( m_mes > other.m_mes )
return 1; // primeira maior
if ( m_mes < other.m_mes)
return -1; // primeira menor
if ( m_dia > other.m_dia )
return 1; // primeira maior
if ( m_dia < other.m_dia)
return -1; // primeira menor
return 0; // iguais
}
// b) versão 2:
int Data::Compara( /* const Data * this , */ const Data & other ) const
{
if ( this->m_ano != other.m_ano)
return this->m_ano - other.m_ano ;
return ( this->m_mes != other.m_mes )
? this->m_mes - other.m_mes
: this->m_dia - other.m_dia ;
// e o retorno será:
// menor que zero se a primeira estiver menor
// maior que zero se a primeira estiver maior
// zero, se estiverem iguais.
}
No arquivo [Link], podemos testar a nova função.
No topo do arquivo, acrescente:
#include <iostream> // std::cout será usada em main

E, antes do return de main, acrescente, por exemplo:


Data d2;
[Link](1, 1, 2002 );
int comp = [Link](d2);
if ( comp > 0 )
std::cout << "d1 maior\n";
else if ( comp < 0 )
std::cout << "d1 menor\n";
else
std::cout << "iguais\n";

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


228 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

7.4.6 ▪ Acrescentando funções operadoras à classe “Data”.


A estrutura C++ permite a escrita de funções no formato operador.
Isto torna o uso de algumas funções mais legível (leitura de código mais rápida) e tam-
bém permitirá que classes diferentes se comuniquem (pois operadores são uma lin-
guagem universal).
Assim podemos escrever, dentro da declaração da class Data, no arquivo data.h:
bool operator == (const Data & other ) const;

Que poderia ser usado de duas maneiras:


Data d1, d2;
// 1) primeira forma de usar
// (evite esta forma, exceto dentro da própria “class”):
if ( [Link]==(d2) )
std::cout << “iguais\n” ;

// 2) segunda forma de usar (e esta é a forma comum de uso):


if ( d1 == d2 ) // muito mais legível (e também mais legível
// que “Compara”)
std::cout << “iguais\n”;

Note que:
 o endereço do primeiro operando(d1, no exemplo acima) será passado como
this;
 o segundo operando (d2, no exemplo acima), será passado como parâmetro
explícito (e por referência no exemplo acima).

Implementação da função (arquivo [Link]):


bool Data::operator == ( const Data & other ) const
{
return ( this->Compara( other ) == 0 );
}
// OU, simplesmente:
bool Data::operator == ( const Data & other ) const
{
return ( Compara ( other ) == 0 );
}

 A função operadora acima, apenas chama uma outra função. Assim, deve
ser declarada como inline para evitar uma dupla chamada de função,

// protótipo, dentro da declaração da classe (arquivo data.h):


class Data
{
...........................................
 inline bool operator == ( const Data & other ) const ;
...........................................

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 229

};
// implementação, no próprio arquivo “header” data.h, após a declaração da
// classe ou em outro arquivo, aqui incluído:
 inline bool Data::operator == ( const Data & other ) const
{
return ( Compara ( other ) == 0 );
}
Exercício:

1 - Declare (em data.h) e implemente(em [Link]) os demais operadores re-


lacionais (não-igual ; maior que ; menor que ; maior ou igual que ; menor ou
igual que).
2 - Depois, no arquivo [Link], acrescente algumas linhas para testar
os novos operadores.
Exemplo (operador menor que):
inline bool Data::operator < ( const Data & other ) const
{
return ( Compara ( other ) < 0 );
}

7.4.7 ▪ Os operadores << e >> de ostream/istream


Agora já podemos entender melhor porque temos feito:
std::cout << "saída\n";
int x; std::cin >> x ;
Isto é possível porque, como vimos, podemos criar operadores para um determinado
tipo de dados (uma struct ou class).
 A biblioteca padrão do C++ oferece, ao invés de funções globais (como é o caso
da biblioteca padrão do C), estruturas de dados e funções para nos fornecer
suporte em uma série de situações básicas, entre elas as operações de entrada
e saída.
 Assim, podemos usar uma classe C++ para entrada de dados (class istream)
e uma outra para saída (class ostream).
 E, ainda, a biblioteca padrão declara duas variáveis globais (que podem ser
usadas em qualquer ponto da aplicação) usando essas duas classes para tipi-
ficá-las:
ostream cout;
istream cin;
 Logo, a partir dessas variáveis já criadas, podemos usar suas funções-membro
e operadores.
 E, nessas classes de entrada e saída, estão definidos operadores de direção
de fluxo (shift ou deslocamento), que funcionam como um insertor no fluxo.
Assim:
std::cout << “Vai para a saida primeiro” << “Vai para a saida depois”
<< std::endl ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


230 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

 cout envia para a saída padrão (o monitor de vídeo) as duas cadeias de carac-
teres que ele recebeu seguindo a ordem sequencial de envio (como em um flu-
xo).
 Finalmente é enviado um terminador de linha e um “flush”: é o “endl”;
Outro exemplo:
int iImprimePrimeiro , iImprimeDepois;
std::cout << iImprimePrimeiro << iImprimeDepois << std::endl ;

 E o mesmo ocorre para entrada de dados (invertendo-se a direção do fluxo):


int iEntraPrimeiro, iEntraDepois ;
std::cin >> iEntraPrimeiro >> iEntraDepois ;
 cin receberá dois valores da entrada padrão (o teclado) e em seguida os envia-
rá para armazenamento nas variáveis iEntraPrimeiro e iEntraDepois, segundo
a ordem de entrada.

7.4.8 ▪ Conclusão Parcial


A linguagem C++ permite que criemos nossos próprios tipo de dados a partir de tipos
já existentes. Inicialmente os tipos existentes são os tipos primários oferecidos pela lin-
guagem e, como logo veremos, também os tipos e gabaritos de tipos da biblioteca pa-
drão C++.
Contudo à medida que criamos tipos novos, eles também poderão ser usados na criação
de outros tipos.
Um tipo novo é criado através das palavras reservadas enum ou struct. Há também a
palavra reservada union que permite criar tipos, mas cujo uso em C++ é menos fre-
quente do que em C.
A diretiva enum cumpre um papel específico, podendo apenas criar um tipo numérico
associado a uma lista de símbolos para constantes.
Já a diretiva struct cumpre um papel muito mais amplo.
Pois, através dela, podemos criar conjuntos de dados de quaisquer tipos existentes.
E a struct irá também unir os dados ao código, permitindo que os dados sejam protegi-
dos pelo código, principalmente através das restrições de acesso private e public –
conforme vimos na primeira parte da apostila.
Assim, é através de struct que poderemos implementar os principais conceitos de ori-
entação a objeto em C++: encapsulamento, herança e polimorfismo.
Em resumo: em C++ os conceitos da orientação a objetos são implementados princi-
palmente através de tipos de dados.
Mas, além de orientação a objetos, C++ oferece também recursos para programação
genérica
- 1 - “gabaritos” para tipos (templates), que permitem o desenvolvimento de
algoritmos genéricos sem preocupação com o tipo (quando um algoritmo se
aplica a diversos tipos), mas que, depois, só poderão ser usados a partir de ti-
pos claramente especificados.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.4 • As palavras reservadas struct e class. • C++ • AGIT informática • 231

- 2 – A STL (Standard Template Library), baseada em templates, e que já forne-


ce, prontos, recursos importantes para programação genérica, os quais devem
ser ampliados com o novo padrão (C++0x)
E programação genérica também pode ser combinada com orientação a objetos, pois
podemos ter templates de classes.

 Além disso, como veremos, operadores não são bons apenas por legibilidade.
Mas principalmente porque, dão um importante suporte à programação genéri-
ca, quando usados por templates.

 Inicialmente vamos recapitular o básico: classes em C++ fornecem novos tipos


de dados. Vejamos mais detidamente os conceitos aí envolvidos.

7.5 • Encapsulamento: reforçando conceitos.


Vamos aqui recapitular os recursos da linguagem que permitem implementar a técnica
de encapsulamento (proteção de dados e ocultamento de recursos que não devem ser
usados fora da classe).
Em seguida iremos ver alguns recursos adicionais.
Recapitulando. Os recursos de encapsulamento vistos até aqui são:
 struct e class
 restrições de acesso
 membros de dados
 funções membras
 função construtora
Vejamos agora, com mais detalhes, alguns tópicos.

7.5.1 ▪ Funções inline.

 Já vimos funções inline. Mas, falando em encapsulamento, é importante ressal-


tar o papel cumprido por elas para que o encapusulamento não leve a perda de
eficiência.

Aparentemente, quando declaramos um membro de dados com restrição de acesso pri-


vate, passamos a ter um problema de performance, pois em muitos casos teremos fun-
ções cujo único objetivo é retornar uma cópia dos membros de dados declarados
como private.
Estamos desperdiçando processamento (chamando uma função e retornando dela) para
manter o encapsulamento.
Mas isso pode ser resolvido se a função for declarada como inline.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


232 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

Nesse sentido, ela auxilia o encapsulamento, evitando a tentação de declarar membros


de dados como public para evitar perdas de processamento com chamadas de função.

7.5.2 ▪ O ponteiro this.

 Como realmente funciona a chamada a uma função-membro?

 Quando escrevemos (supondo-se uma classe “Menu”, que contenha uma fun-
ção “exibe”):
 Menu novoMenu;

 [Link]( )

 O compilador traduz (aproximadamente) para:


 Menu::exibe( & novoMenu);

 no código de máquina, isso ficaria aproximadamente assim (mas isso de-


pende de compilador e convenções adicionais):
 passa o endereço de ‘novoMenu’ , e em seguida chama a função:

call exibe@Menu

 Ou seja: o nome da class fará parte do nome completo da


função (também uma codificação dos tipos de seus parâme-
tros, mas isso não está em foco aqui).
.

 O que significa:

 "Chamada à função exibe, da Classe Menu, passando como


parâmetro o endereço do objeto que acionou a função”.

 Então, significa que o endereço do objeto é passado como um parâmetro im-


plícito e automático para a função membro.
 E, seja qual for a classe, em qualquer função-membro de classe podemos ter
acesso a esse parâmetro.
 Ele é representado pelo ponteiro this;

 É um ponteiro fixo: não podemos modificar o endereço que ele armazena;

 Mas, sempre que for necessário acessar o objeto que acionou a função (por
exemplo, para repassá-lo para uma outra função qualquer) poderemos lançar
mão do ponteiro this.

[Link] ▪ Definindo o objeto apontado por this como const


Quando escrevemos uma função que recebe um ponteiro como parâmetro, mas o va-
lor por ele apontado não será alterado pela função, deixamos claro esse fato especifi-
cando o parâmetro como const.
E como fazer isso com o parâmetro this, já que ele é oculto?
Fazemos isso, acrescentando o especificador const no fim do cabeçalho da função.
Essa é a sintaxe determinada por C++ para tal finalidade.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 233

Exemplo:
class Data
{
…………………..
std::string toString( ) const ;
…………………….
}

No exemplo acima, como o objeto apontado pelo ponteiro this foi especificado como
const, os valores por ele apontado não poderão ser modificados.
E como o this é o endereço de uma variável estruturada que será usada para chamar a
função, isso significa que os campos dessa variável não serão alterados pela função
“toString”.
………………
Data qualquer (1, 1, 2012 ) ;
std::cout << [Link]() << '\n' ;
………………
O endereço da variável qualquer será passado como primeiro parâmetro (this) para a
função toString da classe Data.
E, na função toString, o objeto apontado pelo ponteiro this foi especificado como
const; assim é garantido que os membros de dados da variável qualquer não serão al-
terados por essa função.

[Link] ▪ Exceção para a restrição const de this (mutable)

Podemos desejar que alguns membros possam ser alterados mesmo em funções mem-
bro em que objeto apontado pelo ponteiro this é especificado como const.
Estes membros devem ser declarados usando-se a palavra reservada mutable.

Exemplo:
class Qualquer
{
................
int var;
mutable int podeAlterarSempre;
..................................................

void Imprime( ) const


{
var=0; // ERRO: o objeto apontado por this é const e var não é mutable.
podeAlterarSempre = 0; // OK: este membro é mutable.
......................................
}
};

 Essa é uma restrição arbitrária. Mas que, em alguns casos pode ser muito útil
(geralmente, em situações de herança, como veremos).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


234 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

7.5.3 ▪ Função Construtora


Considerando o seguinte exemplo:
class Menu
{
......................
Menu();
....................
};
 Nesse exemplo da classe Menu, podemos observar que há uma função que tem
o mesmo nome da classe: a função Menu( ).
 Por ter o mesmo nome da classe, esta função é chamada construtura e tem
um significado especial:
 Ela só pode ser chamada no momento da criação de uma variável;
 Assim, se escrevermos:
Menu NovoMenu ;
[Link]( ) ;
 O resultado será um erro de compilação.
 Isso porque uma construtora é chamada automaticamente na declaração da
variável, isto é, no momento em que a variável é criada na memória.
 Desse modo temos as seguintes possibilidades:
Menu NovoMenu ; // a função construtora é chamada aqui
Menu * pNovoMenu = new Menu; // a função construtora
é chamada aqui
Menu( ); // é criado um objeto temporário (sem nome)
// sendo executada a construtora;
// em seguida o objeto é destruído

// Na linha abaixo a construtora será chamada também para a criação


//de um objeto temporário(anônimo) que será copiado para o
:
// parâmetro da função “Gravar”:
Gravar ( Menu() ) ;
/*
1) a função Gravar recebe um parâmetro do tipo Menu
2) quando chamamos a função Gravar, passamos como parâmetro
um objeto menu criado temporariamente (sem nome), o que
provoca uma chamada à construtora.
*/

 Observe que nos quatro exemplos acima, o princípio é o mesmo: a


construtora é chamada no momento da criação da variável e apenas
nesse momento.

 Como as construtoras são automaticamente chamadas quando um objeto (uma


variável desse tipo) é alocada na memória, a consequência é que as construto-
ras serão especialmente úteis para inicializar os membros de dados daquele ob-
jeto que está sendo criado, seja com simples valores default, seja a partir de pa-
râmetros.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 235

 Pois uma declaração de variável implica em que naquele ponto do processa-


mento será reservada a memória necessária determinada pelo tipo da variável.
No caso de uma classe, isto significa que será usado o seu molde para alocar
memória para todos os membros de dados.
 E, em seguida, imediatamente após a alocação da memória será sempre
chamada a função construtora.
 Desse modo, a construtora é chamada uma única vez para cada objeto que é
criado.
 Como é chamada na linha de declaração (criação), a chamada é visível no có-
digo fonte; assim podemos passar parâmetros para uma construtora.

[Link] ▪ Parâmetros para a função construtora.


Uma função construtora pode receber parâmetros, normalmente usados para incializar
os membros de dados.
Em C++ podemos ter diversas funções com o mesmo nome desde que sua parame-
tragem seja diferente, pois tanto o nome da class quanto os tipos dos parâmetros são
usados na composição do nome real da função em código de máquina.
Assim podemos ter diversas “versões” da construtora (na realidade diversas funções
construtoras com o mesmo nome mas com quantidade e/ou tipos de parâmetros dife-
rentes).

[Link] ▪ Contrutora default.


A construtora sem parâmetros é considerada a construtora default.
Isto significa que em determinadas situações poderemos ter uma chamada à construtora
não escrita diretamente no código fonte (será chamada a construtora default).
Entenderemos melhor a utilidade da construtora default quando estudarmos herança.
Mesmo que não tenhamos escrito nenhuma construtora, a construtora default é imple-
mentada pelo compilador(caso necessário, isto é, caso seja usada).
Contudo, escrevermos alguma construtora com parâmetros, o compilador não imple-
mentará automaticamente a construtora default.
Neste caso, precisaremos implementar também uma construtora default.

[Link] ▪ Construtora de cópia e operador de atribuição.


Além da construtora default, uma outra construtora é implementada pelo compilador
automaticamente: a construtora de cópia.
Esta construtora permite que criemos um objeto a partir do outro do mesmo tipo.
class Qualquer
{
// nenhuma construtora foi declarada
............
};
......................
Qualquer q1;
Qualquer q2 ( q1 );

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


236 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

 O objeto “q2” será construído e receberá uma cópia, campo a campo,


do objeto “q1”.

O mesmo ocorre com o operador de atribuição. Ele é implementado automaticamente


pelo compilador para permitir cópias dos campos de um objeto para os campos cor-
respondentes de um outro objeto do mesmo tipo.
Exemplo:
....................
Qualquer q1;
Qualquer q2;
q1 = q2;
...................
// os campos de q2 serão copiados para
// os campos correspondentes de q1

Na realidade, a implementação do construtor de cópia e do operador de atribuição para


cópia é muito simples. É feita uma cópia byte a byte, do endereço inicial de um objeto
para o endereço inicial do outro (geralmente usando a função memcpy), usando-se aí o
tamanho da estrutura (sizeof), para determinar quantos bytes devem ser copiados.
Caso precisemos de um operador de atribuição que receba como argumento um tipo
diferente, então deveremos declará-lo e implementá-lo explicitamente.

 Mas cuidado: se algum membro de dados for responsável pela alocação de


recursos adquiridos manualmente (arquivo, memória dinâmica, etc) a cópia
estará acessando esse recurso (tanto devido ao construtor de cópia como
ao operador de atribuição).
Pois esse recurso pode ser destruído a qualquer momento por um dos ob-
jetos (o original ou a cópia) sem que o outro objeto fique sabendo disso.

 Se existirem recursos assim em uma classe temos duas soluções:

 Ou implementamos o construtor de cópia e o operador de atribuição explicita-


mente, de um modo tal que resolva o problema e que que faça sentido,
 ou, caso isso não seja possível, devemos anular o construtor de cópia e o
operador de atribuição declarando-os explicitamente como membros private e
sem implementação (apenas o protótipo).
Exemplo:
class Qualquer()
{
//...
private
// Construtor de cópia: private e apenas prototipado:
// não terá implementação:
Qualquer( const Qualquer & );
// Idem para o operador de atribuição:
Qualquer & operator = ( const Qualquer & );
};

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 237

[Link] ▪ Construtoras com conversão implícita e explícita.


Vejamos a seguinte implementação da classe Qualquer:
class Qualquer
{
private:
int x;
public:
Qualquer ( int i ) // construtora recebendo um parâmetro
{
x = i;
}
.....................
};

Agora, temos uma função que recebe como parâmetro um objeto do tipo Qualquer:

void Funcao( Qualquer qq )


{
........
}

E chamamos essa função passando como parâmetro um valor do tipo int:


....................
Funcao ( 5 );

Será criado um objeto temporário do tipo Qualquer, usando a construtora que recebe
um int como parâmetro.
O objeto temporário será em seguida copiado para o parâmetro “qq” de “Funcao”.
Em alguns casos esse comportamento é indesejável, podendo criar situações fora de
controle. Em outros casos essa escrita estaria indicando um erro de compilação (a clas -
se não foi projetada para ser usada desse modo).
Para prevenir esse comportamento automatico de conversão implícita, podemos proibir
as conversões implícitas, declarando a construtora com a palavra reservada explicit.
class Qualquer
{
private:
int x;
public:
explicit Qualquer ( int i ) // a construtora só admite
// conversõesEXPLÍCITAS.
{
x = i;
}
.....................
};

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


238 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

Agora se tentarmos fazer:


void Funcao( Qualquer qq )
{
........
}
....................
Funcao ( 5 );

Obteremos um erro de compilação.

Para corrigir o erro teremos que ser explícitos:


Funcao ( Qualquer( 5 ) );

OU:
Qualquer qq ( 1 ); Funcao ( qq );
// OK: nos dois casos, estamos explicitamente construindo
// um objeto do tipo Qualquer
// e passando um inteiro como argumento para a construtora.

7.5.4 ▪ Função destrutora


 Uma função destrutora também tem o mesmo nome da classe procedido pelo
sinal ~(til);
 A função destrutora é chamada automaticamente quando a variável (o objeto)
vai ser destruída:
 ou porque a variável vai sair da abrangência (fim do bloco de sua declara-
ção);
 ou porque foi criada através de new e agora foi empregado um delete.

 Como ela é chamada automaticamente no momento da destruição, sua chama-


da é invisível no código fonte; logo não pode receber parâmetros (e, por isso
mesmo, não podemos ter mais do que uma destrutora).
 Devemos usar destrutoras para qualquer finalização necessária.

 Em especial para liberar recursos adquiridos manualmente, como me-


mória dinâmica, arquivos, etc.

 A destrutora também é automaticamente chamada quando usamos delete tendo


como argumento um ponteiro para um objeto alocado no heap.

int main()
{
…………………………..
Menu * pNovoMenu = new Menu;
………………………
delete pNovoMenu; // a memória apontada por pNovoMenu
// será liberada aqui e a destrutora será chamada
………………………..
return 0;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 239

 Podemos também chamar uma destrutora diretamente. Mas isso significa


que irão ocorrer duas chamadas à destrutora (pois ela sempre será chama-
da automaticamente quando a memória for liberada).

 A chamada direta à destrutora se justifica em determinados casos, geral-


mente em algoritmos complexos, onde a chamada direta simplifica a lógica
(por exemplo, alguns algoritmos da biblioteca padrão usam chamadas dire-
tas à destrutora).

7.5.5 ▪ Alterando o ponto de entrada da aplicação: objetos ex-


ternos.
Como vimos acima, construtoras e destrutora funcionam exatamente como respostas a
eventos (respectivamente, a criação e a destruição de um objeto).
O compilador insere no ponto em que um objeto é criado uma chamada à construtora.
E, no local em que será liberado, insere uma chamada a sua destrutora.
Assim, uma construtora é chamada no momento em que um objeto é criado. Vejamos
como isso funciona com variáveis externas.
 Variáveis externas (globais ou modulares) são alocadas na memória antes
de qualquer outra coisa – antes mesmo que a função main seja chamada.

 Assim sendo, quando declaramos uma variável externa de um tipo estrutu-


rado será chamada a sua construtora no momento de sua criação, o que
significa que ela será executada antes de main.

Desse modo o ponto de entrada da aplicação, embora formalmente pertença a main


(já que uma chamada a main é sempre incluída pelo compilador), na prática será trans-
ferido para a construtora do primeiro objeto externo que tenha sido declarado (o que
pode ser difícil de determinar se houver muitos arquivos fonte que declarem objetos
globais)..
E uma variável externa só é liberada após o retorno de main. Isso significa que sua
destrutora só será chamada após esse retorno.
Desse modo o último código escrito pelo programador a ser executado será a destru-
tora do primeiro objeto externo que tenha sido declarado.
Assim sendo, concluímos que um objeto tem sua própria lógica, alterando o fluxo de
processamento convencional.

 Dependendo do caso, isso pode levar a erros. Por exemplo, se "main" liberar
recursos que são requeridos pelo objeto global.
Como regra geral, devemos evitar. Caso necessário, por alguma razão, seria
melhor ter um ponteiro global para o objeto, mas que só seria instanciado no iní-
cio de main, sendo liberado ao final de main.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


240 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

7.5.6 ▪ Classes e funções amigas


Quando uma classe declara uma outra classe, ou uma função externa, como amiga, está
permitindo que o declarado tenha acesso direto a todos os membros da classe que de -
clarou, inclusive os membros private.
class X
{
friend int main( ) ; //a função main é uma função amiga
friend class Y ; // a classe Y é uma classe amiga;
};

 No exemplo acima, tanto a função main quanto as funções-membras da


classe Y podem acessar os dados e funções private da classe X.

 É preciso portanto cuidado ao usar declarações friend.


Uma má utilização irá eliminar o encapsulamento da classe que declara,
abrindo caminho para efeitos colaterais.

Uma classe ou função amiga, faz sentido quando precisamos de um recurso auxiliar a
uma determinada classe.
O recurso auxiliar é conhecido pela classe e é seu ajudante.
Um exemplo típico seria o seguinte:
class Node
{
friend class NodeList ; // declaração friend
//...
}
class NodeList // uma lista de “Nodes”
{
Node * m_node;
//...
}
Observar que nesse caso precisamos de duas classes para resolver o nosso problema.
Pois “NodeList” precisa de um conjunto de “Nodes”. Então a classe Node só existe
para auxiliar NodeList.
Faz sentido que ela declare NodeList como friend.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 241

7.5.7 ▪ Membros estáticos de uma classe


[Link] ▪ Membros de dados estáticos

 Um membro de dados estático representa uma memória compartilhada por


todos os objetos da classe. Do ponto de vista do tempo de vida tem o mesmo
comportamento de uma variável global.
Contudo, o membro estático obedece ao escopo da class (seu nome) e está
sujeito às restrições de acesso (private).
Por isso prefira membros estáticos a objetos globais.

assim se fizermos:
class Qualquer
{
..................
public:
static int MembroStat ;
...................
};

e depois:
int Qualquer::MembroStat = 0; // inicialização no escopo global
int main( )
{
// sendo estático pode ser acessado mesmo que nenhum objeto exista.
std::cout << Qualquer::MembroStat << '\n'; // imprime 0
Qualquer Coisa ;
Qualquer Outra ;
[Link] = 5;
cout << [Link] << endl; // o resultado é 5
cout << [Link] << endl; // o resultado é 5
[Link] = 10;
cout << [Link] << endl; // o resultado é 10
cout << [Link] << endl ; // o resultado é 10
}

 verificamos que tanto faz o objeto usado para acessar o membro: cada al-
teração feita nesse membro estático em qualquer objeto afeta todos os de-
mais objetos dessa classe.

 O membro estático é o único para todos os objetos de sua classe.

Justamente por isso um membro estático deve, obrigatoriamente, ser ini-


cializado fora da classe (não na construtora).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


242 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

 Se fosse inicializado em uma função construtura, estaríamos sempre, a


cada novo objeto criado, alterando seu valor e afetando todos os objetos já
criados.
Naturalmente, não é proibido atribuir um valor a um membro estático na
construtora. Mas neste caso não estamos falando de inicialização e sim
de simples atribuição.

Exemplo de inicialização do membro estático:


class Qualquer
{
..................
public:
static int MembroStat;
...................
};

agora o membro estático já pode ser inicializado:


int Qualquer::MembroStat = 1;
// o operador de resolução de escopo indicando que MembroStat é membro
// da classe “Qualquer” -e não uma variável global.

e o programa segue normalmente:


int main( )
{
cout << Qualquer::MembroStat ; // RESULTADO = 1
// não foi criado nenhum objeto
// da classe “qualquer”, mas o membro
// estático pode ser usado a partir
// do nome da classe e
// do operador de resolução de escopo
.................................;
}
 A inicialização é obrigatória e é externa ao contexto de definição da classe.
 Logo, uma variável estática pode ser acessada mesmo antes da criação de
qualquer objeto de sua classe, a partir do nome da classe e do operador de re-
solução de escopo.
 Por isso também deve ser evitado acessar um membro estático a partir de um
objeto.
 Embora seja possível acessar um membro estático a partir de um objeto já exis-
tente, o melhor é sempre acessar o membro estático a partir da classe e não
dos seus objetos.

int main( )

{
Qualquer Um ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 243

Qualquer Dois ;
[Link] = 5 ;
// POUCO CLARO: afinal, o objeto “dois” também será afetado.
Qualquer::MembroStat = 5 ; // MELHOR: fica claro que foi
// acessado um membro estático único na memória
}

[Link] ▪ Funções-membro estáticas


 Um membro de dados estático pode ser acessado por qualquer função-membro,
inclusive funções não estáticas.
 Mas uma função estática só pode acessar membros estáticos (funções estáticas
não recebem o ponteiro this como parâmetro).
 E devemos acessar a função a partir do nome da classe e não de um objeto
(pelos mesmos motivos levantados acima para acessar membros de dados es-
táticos).
 O melhor caminho para acessar membros de dados estáticos é utilizar funções
estáticas, pois como essas funções não recebem o ponteiro this como parâme-
tro, através delas poderemos acessar os membros de dados estáticos mesmo
que não tenha sido criado qualquer objeto da classe envolvida.
class Qualquer
{
private:
static int MembroStat ;
public:
int VarInt ;
static int RetornaMembroStat( )
{
VarInt = 3 ; // ERRO: Função estática tentando
// acessar membro não estático
return MEMBROSTAT; // OK: acessando membro estático.
}
static void GravaMembroStat( int Param )
{
MembroStat = Param ; // OK: acessando membro estático
}
};
int Qualquer::MembroStat = 1 ; // inicialização do membro estático
int main( )
{
Qualquer Um ;
[Link] ( 2 ) ;
// CONFUSO: tentando acessar função-membro estática a partir
// do objeto e não da classe.

Qualquer::GravaMembroStat ( 2 ) // Melhor: a partir da classe.


}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


244 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

7.5.8 ▪ Objetos como membros de classes.


[Link] ▪ Membros “objeto de outra classe”
 Já dissemos que não faria muito sentido estabelecer relações de herança que
não existam na realidade.
 Por exemplo, mesmo que a classe menu precise dos membros de data (para
imprimir uma data na tela), não há relação de herança real entre uma data e
um menu.
 Além disso é preciso considerar também em que medida (ou em que quanti-
dade) necessitamos dos membros de uma classe dentro de outra.
 Vejamos o seguinte exemplo:

class Pagar
{
public:
float Valor;
Data DiaAtual ;
Data DiaPagar;
};

class Receber
{
public:
float Valor;
Data DiaAtual ;
Data DiaReceber;
};

int main( )
{
Data dtHoje (1 , 1, 2011 );

Pagar PG;
[Link] = dtHoje;
std::cout << [Link]() << '\n';
std::cout << [Link]() << '\n';
Receber RC;
[Link] = dtHoje;
std::cout << [Link]() << '\n';
return 0;
}

 No exemplo acima, tanto a classe pagar como receber possuem, cada


uma, dois membros da classe data.
Se elas fossem herdeiras de data, além de estabelecer uma relação con-
fusa (pois nem “pagar” nem “receber” são datas), não teriam o seu proble-
ma resolvido, pois teriam apenas, cada uma, um único conjunto de mem-
bros da classe data, e no caso precisam de mais do que uma data.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 245

 Nesse caso o melhor é que elas tenham objetos da classe data como mem-
bros (e quantos forem necessários) ao invés de utilizar herança
Nessa situação, caso um membro de dados “objeto de outra classe” tenha
utilidade apenas em algumas situações menos frequentes, podemos tam-
bém considerar a alternativa de utilizar ponteiros, alocando dinamicamen-
te esses membros.
Assim só será alocada memória para os objetos caso (e quando) realmen-
te for necessário.

[Link] ▪ Membros “ponteiro para objeto de outra classe”

 Veja como ficaria a escrita das classes data, pagar e receber, usando ponteiros:
class Pagar
{
public:
float VALOR;
Data * DiaAtual ;
Data * DiaPagar;
};
class Receber
{
public:
float Valor ;
Data * DiaAtual ;
Data * DiaReceber ;
}

 Atenção:
Veremos abaixo (função main) um exemplo de uso das classes "Pagar" e
"Receber". Mas nesse exemplo existem erros de acesso à memória.
Tente localizar esses erros:

int main( )
{
// SUPONDO-SE que dia, mes e ano (de Data) fossem public:

Data * pdtHoje = new Data (1,1, 2011); // Alocação de memória no heap


pdtHoje->dia = 1 ; // USA O OPERADOR DE LIGAÇÃO A PONTEIRO
pdtHoje->mes = 1;
pdtHoje->ano = 1995;

Pagar * pPG = new Pagar ; // PG É UM PONTEIRO


Receber RC ; // RC NÃO É UM PONTEIRO
pPG-> DiaAtual= pdtHoje ; // ATENÇÃO aos operadores de
// LIGAÇÃO DE MEMBRO
[Link] = pdtHoje ; // idem

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


246 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

pPG->DiaPagar->Dia = 10;
pPG->DiaPagar->Mes = 2;
PPG->DiaPagar->Ano = 1995;
std::cout << pPG->DiaPagar->toString() << '\n';

[Link]->Dia = 10;
[Link]->Mes = 2;
[Link]->Ano = 1995;
std::cout << [Link]->toString() << '\n' ;
}

[Link] ▪ Membros “ponteiro para objeto da mesma classe”


Quando temos um membro ponteiro para objeto da mesma classe, e um objeto dessa
classe é criado o C++ reservará apenas o espaço para um ponteiro.
Seria diferente se pretendessemos ter um objeto da classe como membro dela mesma:
isto seria impossível pois o C++ precisaria alocar o objeto-membro (e para isso voltaria
a consultar o molde que, novamente e mandaria criar o objeto-membro do objeto-mem-
bro ... E assim ao infinito.
Com um ponteiro, quebramos essa ação recursiva.
Será reservado espaço para um endereço que poderá mais tarde receber ou não o ende-
reço real de um objeto criado através de new.
class Faixa
{
float Maximo;
float Minimo;

Faixa * proxima ;
};

 OBSERVE QUE: quando as classes são diferentes podemos decidir, de


acordo com a necessidade, se queremos um membro objeto da outra clas-
se ou um membro ponteiro para esse objeto.

 Contudo quando precisamos de um objeto da mesma classe como mem-


bro, é impossível usá-lo diretamente.
Neste caso, só podemos ter um ponteiro para esse objeto.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 247

7.5.9 ▪ Ambientes de nomes.


[Link] ▪ Evitando colisões de nomes.
Em C++ podemos isolar escopos através de ambientes de nomes, usando a palavra re-
servada namespace.
Um namespace irá impedir colisões de nomes de variáveis, funções ou classes escritas
em módulos ou bibliotecas diferentes.
Isto significa que namespace é um recurso que visa reforçar o encapsulamento e, as-
sim sendo, é mais um recurso disponível ao programador C++ para implementar a ori-
entação a objetos.
Desse modo, se tivermos uma classe chamada “Data” e, algum dia, usarmos uma bi-
blioteca que também tenha uma classe chamada “Data”, não haverá problemas se esti-
vermos usando ambientes de nomes.
Pois em C++ podemos fazer:
namespace agit
{
class Data;
};
e agora a nossa classe Data será declarada assim:
class agit::Data
{
private:
..........
public:
..........
};
e usaremos a classe assim
:

int main( )
{
agit::Data Hoje;
........................
return 0;
}

E, para fins de simplificação, em um determinado escopo, podemos declarar o names-


pace em uso, o que irá permitir que usemos nomes declarados dentro dele sem referên-
cia explícita ao namespace. Assim:
using namespace agit;
Data Hoje; // OK;

 Mas cuidado! Ao anular um namespace você perde o benefício da proteção


dos nomes de símbolos declarados dentro do namespace.
Isso torna possível colisões com nomes idênticos de símbolos diferentes.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


248 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

[Link] ▪ Usando namespace para organizar conjuntos de software.


Além disso, namespace's também permitem organizar um projeto ou uma biblioteca
em um conjunto de unidades lógicas, onde cada uma delas é representada por um na-
mespace.

 Ou seja: o namespace permite que evitemos colisões com bibliotecas de


terceiros e permite também que seja claramente exposta a organização ló-
gica de uma biblioteca ou conjunto de software.

Por exemplo:
namespace agit
{

namespace tipos // tipos básicos


{
class Data;
class Moeda;
};
namespace financ // financeiro
{
class Pagar;
class Receber;
class Impressao;
};
namespace admin // administrativo
{
class Controle;
class Processos;
class Impressao;
};

}; // Fim da declaração do namespace agit.


// - declarar as classes aqui...
int main()
{
agit::tipos::Data hoje;
agit::financ::Pagar pag;
agit::admin::Controle ctl;
agit::financ::Impressao fImpr;
agit::admin::Impressao aImpr;
return 0;
}

Assim, através de namespace's aninhados, temos claramente exposta a organização ló-


gica do conjunto "agit", com a especificação dos subconjuntos e a posição das classes
dentro deles.
Além disso evitamos colisão entre símbolos de diferentes conjuntos da PRÓPRIA bi-
blioteca, como é o caso das duas classes de impressão:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 249

agit::financ::Impressao
agit::admin::Impressao
E se quisermos simplificar a escrita, podemos usar(em qualquer escopo) o "using na-
mespace" ou então criar sinônimos simplificadores:
namespace agFin = agit::Financ;

[Link] ▪ O namespace "std".


Toda a biblioteca padrão C++ está declarada dento do namespace "std". Isto significa
que, na verdade, o nosso tipo "string" é:
std::string
e o nosso "cout" é:
std::cout.
Contudo, se declararmos:
using namespace std;
poderemos agora usar "string" e "cout" sem fazer referência ao seu namespace.
O que causa alguma confusão é que encontramos muito código onde a declaração
"using namespace std" não está presente e mesmo assim os recursos da biblioteca pa-
drão são usados sem qualquer referência ao "std".
Isto se deve ao seguinte: os arquivos de inclusão da biblioteca padrão não têm a exten-
são ".h".
Desse modo, usando a biblioteca padrão, fazemos:
#include <iostream> // sem .h
#include <string> // sem .h

E, aí, temos duas alternativas:


OU
std::string MinhaString;
std::cout << std::endl;
OU
using namespace std;
string MinhaString;
cout << endl;

Contudo, os compiladores fornecem também um segundo jogo de arquivos de inclusão


COM a extensão ".h".
 Esses arquivos são obsoletos. São mantidos por compatibilidade com C++
anterior ao padrão C++98.

Por isso, se, ao invés de


#include <iostream> // SEM o .h
usarmos
#include <iostream.h> // COM o .h
então usaremos diretamente
cout << endl; // não tínhamos “namespaces” no C++ antigo.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


250 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

 Use sempre os arquivos atuais, coerentes com o padrão.

[Link] ▪ Exemplos de uso de namespace.

#include <iostream> // sem .h

// OBS: não estamos usando a linha abaixo:


// using namespace std;

/* ==============================

Neste exemplo, iremos explorar alguns conselhos do criador de C++,


Bjarne Stroustrup:

1) "Use ambientes de nomes (namespace's) para expressar uma


estrutura lógica".
Exemplo:
namespace Financeiro
{
class Pagar;
class Receber;
};

2) "Coloque cada nome não-local em um namespace". Exemplo:


Variáveis globais do projeto Contas a Pagar:
namespace ConPag_Glob
{
int ParametroGeral=0;
};

3) "Projete um ambiente de nomes de modo a não acessar


acidentalmente um ambiente não relacionado"
Exemplo: O nome "financeiro" poderia existir em um outro ambiente, pois é
muito comum. Uma solução seria:
namespace agit
{
namespace financeiro
{
class Pagar;
class Receber;
};
namespace admin
{
class Patrimonio;
class ControlaProcessos;
};
};
Agora, "financeiro" tornou-se: "agit::financeiro"
E "admin" deve ser acessado como: "agit::admin"

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 251

4) "Evite nomes muito curtos para ambientes de nomes"


Nomes muito curtos facilitam colisões com outros nomes.
Contudo, se usarmos, como acima, nomes aninhados, esse problema será
minimizado, pois cada nome só faz sentido dentro do ambiente em que foi
declarado.
Assim o nome mais externo (“agit”, no exemplo) garantirá que os seus
nomes internos estarão isolados com relação a outros ambientes de
nomes.
Agora, bastará garantir que não teremos um outro ambiente de nomes
onde o nome principal seja, também, “agit”.

5) "Para simplificar, se for necessário, use sinônimos para ambientes


de nomes"
Exemplo:
namespace agfin = agit::financeiro;

6) "Use 'using namespace' somente para migração


ou em um escopo local."
E este conselho também poderia ser aplicado a:
"so crie sinônimos em um escopo local".
O "using namespace" ou os sinônimos, devem ser usados
preferencialmente apenas dentro de uma determinada função ou classe.

 Do contrário, o namespace, na prática, estará sendo anulado.


*/

// ===== Declara o namespace global “agit” que envolverá vários


conjuntos lógicos:
namespace agit
{
class Data;
class Outra;
class etc;
namespace Financ
{
class Pagar;
class Receber;
class Impressao;
};
namespace admin
{
class Controle;
class Processos;
class Impressao;
};
};

// ===== Declaração das classes:


class agit::Data
{
public:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


252 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

char m_Dia;
char m_Mes;
short m_Ano;
};
class agit::Outra
{
public:
int m_iOutro;
};
class agit::etc
{
public:
int m_iEtc;
};

class agit::Financ::Pagar
{
public:
double m_Pagar;
};
class agit::Financ::Receber
{
public:
double m_Receber;
};
class agit::Financ::Impressao
{
public:
Impressao(){}
double m_Receber;
};
class agit::admin::Impressao
{
public:
Impressao(){}
double m_Receber;
};

// namespace para variáveis globais de um determinado projeto.


// Por exemplo o projeto "Contas a Pagar":
namespace ConPag_Glob
{
int ParametroGeral=0;
};
// contudo, no exemplo acima, talvez fosse melhor criar uma classe
//"ConPag_Glob",
// onde a variável seria private e seria acessada apenas através de funções.
int main()
{
std::cout << "Testando NameSpace" << std::endl;
agit::Financ::Pagar Pag;
Pag.m_Pagar = 101.50;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.5 • Encapsulamento: reforçando conceitos. • C++ • AGIT informática • 253

agit::Data hoje;
hoje.m_Dia = 3;
// ERRO:
// Data amanha; // não indicou o namespace
using namespace agit;
Data amanha; // Agora sim !!!:
amanha.m_Dia = 10;
// ERRO:
// Receber Rec; // não indicou o namespace
using namespace agit::Financ;
Receber Rec; // Agora sim !!! :
Rec.m_Receber = 10.33;
ConPag_Glob::ParametroGeral = 10;
// ERRO:
// std::cout << ParametroGeral << std::endl;
//namespace de ParametroGeral foi omitido
// Agora sim:
std::cout << ConPag_Glob::ParametroGeral << std::endl;
// ou então:
using namespace ConPag_Glob;
std::cout << ParametroGeral << std::endl;
// OK: namespace em uso.
// criando um sinônimo local:
namespace agFin = agit::Financ;
agFin::Pagar Pag2;
Pag2.m_Pagar = 44.3;
// apenas a partir daqui, até o fim da função,
é que o std poderá ser suprimido:
using namespace std;
cout << Pag2.m_Pagar << endl;

// usando namespace evitamos o conflito


entre as duas classes de impressão:
agit::admin::Impressao aImpr;
agit::Financ::Impressao fImpr;

return 0;
}

void Funcao()
{

 Todo o código abaixo está errado:

/*
agFin::Pagar Pag2;
// o sinônimo agFin só existe em main !

Pag2.m_Pagar = 44.3;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


254 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

cout << Pag2.m_Pagar << endl;


// teria que usar std::cout e std::endl
// pois o "using namespace std;", feito acima,
// foi feito dentro de main
// (e, portanto, é um uso local pertencente a main):
*/
}

7.6 • Questões para revisão do capítulo 7


Responda às questões abaixo. Em seguida, compare suas respostas com as res-
postas localizadas no Anexo B-7, página 479. Caso não entenda, encaminhe as dú-
vidas ao instrutor.

 porque as estruturas (struct ou class) do C++ são melhores que as estruturas


(struct) do C?

 o que são as restrições de acesso private e public ?

 o que significa “this” ?

 o que são funções operadoras ?

 o que significa “const” na definição do tipo de um parâmetro de função?

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


7.6 • Questões para revisão do capítulo 7 • C++ • AGIT informática • 255

 responda à pergunta que está no comentário do código abaixo:


class X
{
X( )  // Que função é esta e para que serve?
{
........................
}
...............................
};

 responda à pergunta que está no comentário do código abaixo:


class X
{
~X( )  // Que função é esta e para que serve?
{
}
};

 responda às 4 perguntas que estão nos comentários do código abaixo:


class X
{
bool operator < ( const X & obj_2 ) const ; 
// 1- Que função é esta e para que serve?
// 2- Por que a palavra “const” aparece aí duas vezes?
// 3- O que faz aí o “e-comercial” (&) ?
// 4- O que faz aí a letra “X” ?
};

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


256 • AGIT informática • C++ • • Capítulo 7 ▪ Iniciando orientação a objetos

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 8 ▪ Programação genérica • C++ • AGIT informática • 257

• Capítulo 8
▪ Programação genérica

8.1 • Templates .......................................................................258


8.2 • Templates de função ........................................................258
8.3 • Templates de classes .......................................................262

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


258 • AGIT informática • C++ • • Capítulo 8 ▪ Programação genérica

8.1 • Templates
 Em algumas situações temos uma mesma lógica que se aplica a diversos
tipos. Nesses casos, o foco não é o tipo e sim o algoritmo.

8.2 • Templates de função


Por exemplo: se quisermos escrever uma função que calcule o menor entre dois valo-
res, teremos um algoritmo que servirá para qualquer tipo que disponha do operador
relacional menor que. Contudo, uma função exige que seus parâmetros e seu retorno
tenham tipos explicitamente declarados:
int Minimo ( int a , int b)
{
return ( ( a < b ) ? a : b );
}

Agora, se quiséssemos calcular o menor valor entre dois valores do tipo double, tería-
mos que escrever uma nova função:
double Minimo ( double a , double b)
{
return ( ( a < b ) ? a : b );
}

O que fizemos foi “copiar” a primeira função, para depois “colar” em outro lugar e fi -
nalmente “trocar” int por double. Isso é trabalho de máquina, não de gente.
Não seria possível que o compilador fizesse isso por nós? Não podemos pedir a ele que
“copie”, “cole” e “troque”?
Sim, podemos. E isto é feito através de “gabaritos”(templates). Em um template, ao
invés de indicar um tipo real, indicamos que ali, futuramente(no momento em que
formos usar a função) será informado um tipo de acordo com as necessidades:

 Ao declará-lo, precisamos indicar que vamos escrever um template de


função e não uma função:
template <class TIPO_PARAMETRIZADO>
// tipo de retorno
TIPO_PARAMETRIZADO Minimo(
TIPO_PARAMETRIZADO a // tipo do parâmetro 1
TIPO_PARAMETRIZADO b ) // tipo do parâmetro 2
{
return ( a < b ) ? a : b;
}

 Se, em algum momento, usarmos esse template para trabalhar com dois
valores do tipo int, o compilador criará uma função onde TIPO_PARAME-
TRIZADO será substituído por int.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


8.2 • Templates de função • C++ • AGIT informática • 259

E, se, em algum outro momento, usarmos esse template para trabalhar


com dois valores do tipo double, o compilador criará uma outra função
onde TIPO_PARAMETRIZADO será substituído por double.
Logicamente essa operação poderá ser feita inline, evitando uma chamada de função
desnecessária, já que o código é muito pequeno. Um template de função tanto pode ser
instanciado “inline” ou como uma função comum.
Templates são assim modelos para que o compilador escreva funções e classes basea-
das em tipos que serão passados como parâmetros de acordo com a necessidade de
cada momento. Por isso os templates também são chamados de recursos(funções ou
classes) com tipos parametrizados.
Assim, na implementação de um algoritmo genérico, ao invés de criar uma classe para
cada tipo de dados, ou então uma classe com diversas funções sobrecarregadas, uma
para cada tipo desejado, podemos criar um único esqueleto de classe capaz de operar
com diversos tipos de dados - pois os tipos serão passados como parâmetros. E o mes-
mo pode ser feito com funções globais.

 Um programador C poderia argumentar que isso tudo poderia ser feito atra-
vés da diretiva de pre-processamento #define.
É verdade. Mas teríamos aí vários inconvenientes.
Vejamos porque.

EXEMPLO:

Utilizando o exemplo acima do cálculo do menor valor (minimo), teríamos as seguin-


tes alternativas:
1) NÃO usando TEMPLATES:

1.A) usando MACROS:


#define MINIMO(x,y) ((x)<(y)?x:y)
// que seria usado assim:
a = MINIMO(2,3);
// que seria trocado para
// a = ((2)<(3)?2:3);
A vantagem deste método seria a simplicidade e uma baixa sobrecarga de código já que
ocorreria apenas a substituição da pseudo-função(ou macro) por uma simples opera-
ção.
A desvantagem é que não ocorrerá checagem dos tipos de x e y.
Se os tipos forem comparáveis, (como um int ou um double) o compilador não emitirá
mensagens de erro e nem mesmo uma "warning".
Se não forem (comparação entre duas estruturas, não providasde operador de compara-
ção compatível) o erro apresentado pelo compilador indica a linha onde a macro é usa-

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


260 • AGIT informática • C++ • • Capítulo 8 ▪ Programação genérica

da mas não fará qualquer referência à própria macro (já que o código foi expandido no
pre-processamento e o compilador não enxerga mais a macro).
A mensagem será incompleta, e muitas vezes confusa.
Já em um template, a mensagem de erro fará referência explícita ao próprio template,
ainda que atualmente os compiladores tenham dificuldade de nomear os erros em situa-
ções mais complexas.
Além disso, usando macros teremos problemas adicionais, devido ao fato de que o pre-
processador realiza uma simples troca sem qualquer consideração de qualidade.
Por isso, repare que o seguinte uso da macro MINIMO, acarretaria perda de perfor -
mance:
a = MINIMO ( sqrt( x ) * 2, sqrt( y ) / 2);
// seria traduzido para
a= ((( sqrt( x ) * 2 ) < (sqrt( y ) / 2))
? sqrt( x ) * 2 : sqrt( y ) / 2);

E assim operações lentas (raiz quadrada e multiplicação ou divisão) seriam executadas


duas vezes (uma vez na comparação e outra na decisão).
E essa forma de expansão do código pode acarretar problemas bem piores quando utili-
zamos operadores compostos ou o incremento e o decremento (que realizam uma ope-
ração aritmética seguida de uma operação de atribuição).
Ou seja, nunca deveríamos usar esses operadores ( ++, --, += , -=, etc. ) em macros.
Vejamos o que pode ocorrer:
int x=6, y=6;
cout << MINIMO( ++x , y ) << endl;

:
a linha acima seria expandida para
cout << ( ( (++x)>(y) ) ? (++x) : (y) ) << endl ;
Ou seja: quem usou a macro como se fosse uma função provavelmente espera que a
operação de incremento em “x” seja realizada uma única vez.
Contudo, com a expansão, o incremento poderá ser efetuado duas vezes (sempre que +
+x resultar maior que y).
E, se o objetivo fosse realmente esse, semelhante código seria altamente confuso.
1.B) usando FUNÇÕES SOBRECARREGADAS:
inline int Minimo( int a, int b )
{ return ( a < b ) ? a : b; }
inline long Minimo( long a, long b )
{ return ( a < b ) ? a : b; }

(e mais funções para char, double, unsigned int, unsigned long, etc...)
A operação utilizará os tipos corretamente pois os parâmetros são tipificados e tam-
bém o retorno da função, indicando assim tanto o que pode ser enviado como o que
pode ser esperado (e eventualmente armazenado) como resultado da operação.
O compilador fará a checagem normalmente e emitirá as mensagens apropriadas.
A desvantagem é que teremos muitas funções, gerando uma sobrecarga de código a ser
mantido.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


8.2 • Templates de função • C++ • AGIT informática • 261

2) USANDO TEMPLATES:
Usando templates, não haveria necessidade de toda essa repetição de código:
// para identificar um parâmetro template
// podemos usar "class" ou "typename"
// a função template pode ser inline ou não, dependendo do caso,
template <typename TIPO_PARAM>
TIPO_PARAM inline Minimo( TIPO_PARAM a, TIPO_PARAM b )
{
return ( a < b ) ? a : b;
}
que poderia ser usado assim:
int X=5 , Y=5;
double A=3.4 , B=5.6;
a = Minimo <double> ( A, X ); //TIPO_PARAM será double
a = Minimo <int>(X,Y); // TIPO_PARAM será int
a = Minimo (X , Y); // TIPO_PARAM será int
// já que os tipos de x e y estão claros
a = Minimo( A, (double)X); // TIPO_PARAM será double
// já que A é double e X foi convertido para double

cout << Minimo( X , Y ) << endl; // o TIPO Parametrizado será int


cout << Minimo( A , B ) << endl; // o TIPO Parametrizado será double
E o template será usado para substituir cada linha de operação do modo adequado.
A vantagem sobre as funções sobrecarredas é que reduzimos código sem reduzir a fle-
xibilidade que temos com a sobrecarga.
E a vantagem sobre a macro é que isso foi feito sem reduzir a checagem segura de ti-
pos oferecida pelo compilador e sem correr o risco de duplicar operações compostas,
ou outras expansões indesejadas.
 Mas como a linguagem permite a sobrecarga de operadores, várias classes
podem ter o "operator <", usado por Minimo. É o caso da nossa class Data.
Portanto "Minimo" poderá ser usado por qualquer tipo que tenha o "operator <".
Passar estruturas por cópia pode ter um custo alto, dependendo do caso.

Então, o correto seria usar referências (const ou não-const, dependendo do


caso):
template <typename TIPO>
const TIPO & inline Minimo( const TIPO & a, const TIPO & b )
{
return ( a < b ) ? a : b;
}
Então podemos fazer:
Data dt1(1,1,2000) , dt2(2,1, 2000);
const Data & dt3 = Minimo( dt1, dt2);
std::cout << [Link]() << '/n' ; // vai imprimir 01/01/2000

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


262 • AGIT informática • C++ • • Capítulo 8 ▪ Programação genérica

8.3 • Templates de classes


#include <iostream>
using namespace std; // cuidado com isso...

// TEMPLATE de CLASSE "ListaDeValores":


template <class TIPO, size_t dim> class ListaDeValores
{
private:
TIPO buffer[dim];
// membro de dados do TIPO PARAMETRIZADO
// com a dimensão constante "dim"
public:
void atribui( size_t Indice , TIPO valor );
// segundo parâmetro: do TIPO PARAMETRIZADO
TIPO retorna(size_t Indice );
// valor de retorno: do TIPO PARAMETRIZADO
};
// usar assim, por exemplo:
// ListaDeValores <int, 10> MinhaLista;

// Template de função-membro "atribui" do template de classe "ListaDeValores":


template <typename TIPO, size_t dim>
void ListaDeValores<TIPO, dim>::atribui(size_t Indice, TIPO valor)
{
buffer[Indice] = valor;
}

// Template de função-membro "retorna" do template de classe


// "ListaDeValores":
template <typename TIPO, size_t dim>
TIPO ListaDeValores<TIPO, dim>::retorna(size_t indice)
{
return buffer[indice];
}
 OBS: o template "atribui" tem retorno void e recebe como argumentos-template:
 um valor do tipo size_t
 um valor do tipo parametrizado “TIPO”

 Já o template "retorna" tem como retorno


 um valor do tipo parametrizado “TIPO”

 e recebe como argumento-template um valor do tipo size_t.

 Desse modo, o tipo parametrizado “TIPO” tanto pode servir para definir os ti-
pos de membros de dados do Template de Classe como também os tipos de re-
tornos e parâmetros das funções-membro..

template <class TRet,class TArg>


TRet SomaValores( const TArg & a, const TArg & b )
{
return ( a + b );
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


8.3 • Templates de classes • C++ • AGIT informática • 263

O template acima precisa que seu primeiro parâmetro template (TRet)


seja explicitado no uso, já que o retorno não pode ser deduzido.

int a = 5, b=6;
int c = SomaValores<int, int> (a,b );

// explicitou TRet e TArg. Mas TArg pode ser deduzido


// (pois os tipos de a e b estão claros); Então basta explicitar TRet:
c = SomaValores<int>(a,b);

int main()
{
// usa ListaDeValores para uma MATRIZ DE 10 valores do tipo char
ListaDeValores<char, 10> ob1;
cout << "=== Testa template 'ListaDeValores' com CHAR:"<< '\n';
for ( int iC = 0; iC < 10; iC++ )
{
[Link]( iC, 'A'+iC );
cout << [Link](iC) << ( (iC<9) ? " , " : "" ) ;
}
//** Resultado: A , B , C , D , E , F , G , H , I , J

// usa ListaDeValores para uma MATRIZ DE 6 valores do tipo int


ListaDeValores<int, 6> ob2;
cout << "\n\n=== Testa template 'ListaDeValores' com INT:"
<< '\n';
for ( size_t c = 0; c < 6; ++c+)
{
[Link]( c, 1+static_cast<int>(c) );
cout << [Link](c) << ", " ;
}
//** Resultado: 1 , 2 , 3 , 4 , 5 , 6,

x=2; y=5;
cout << Minimo( ++x , y ) << '\n'; // valores x e y do tipo INT
//** Resultado: 3 (int)
// MAS se Minimo fosse uma Macro o resultado seria 4 (incorreto)

cout << "\n===Usando o mesmo template para tipos DOUBLE:"


<< '\n';
double a=5.1 , b=5.2 ;
cout << Minimo( a , b ) << '\n'; // valores a e b do tipo DOUBLE
//** Resultado: 5.1 (double)

cout << "\n===Pode ser usado tambem com “


<< “definicao EXPLICITA do tipo:" << '\n';
cout << Minimo <int> ( x , y ) << '\n';
//** Resultado: 3 (int)
cout << Minimo<double>( x , y ) << '\n';
// x e y convertidos para double

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


264 • AGIT informática • C++ • • Capítulo 8 ▪ Programação genérica

\m
//** Resultado: 3 (double)

cout << "\n===Exemplo de um template que recebe dois tipos\n";


<< '\n';

cout << SomaValores <int>( x,y ) << '\n';

return 0;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 9 ▪ A biblioteca padrão • C++ • AGIT informática • 265

• Capítulo 9
▪ A biblioteca padrão

9.1 • std::string.......................................................................268
9.2 • std::vector .....................................................................269
9.3 • std::list ..........................................................................270
9.4 • std::map ........................................................................271
9.5 • std::vector < std::vector < > >........................................272

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


266 • AGIT informática • C++ • • Capítulo 9 ▪ A biblioteca padrão

Além das funções herdadas do C e das classes de stream para operações de entrada/saí-
da, a biblioteca padrão oferece a Standard Template Library(STL) que é uma bibliote-
ca de templates que nos permitem criar, diretamente, novos tipos de dados.
Os templates de maior aplicação prática são:
 basic_string,
 vector,
 list,
 map e multimap
 set e multiset
Além disso dois tipos já são fornecidos:
 string; que é um sinônimo para basic_string<char>
( typedef basic_string<char> string );
 wstring; que é um sinônimo para basic_string<wchar_t>
( typedef basic_string<wchar_t> wstring> ), sendo este último usado para UNI-
CODE.

Descrição:
string:
Esta classe permite encapsular uma matriz de caracteres (tipo char) com
tamanho variável e redimensionamento automático.
Isto porque as operações de alocação e liberação de memória são
garantidas pela classe, que realiza tais atividades de modo otimizado e
seguro.
wstring:
Esta classe permite encapsular uma matriz de caracteres sob o padrão UNICODE (tipo
wchar_t, o qual é implementado como um sinônimo para unsigned short).
As observações feitas acima (para string) aplicam-se igualmente aqui.
vector:
Este gabarito de classe permite encapsular um sequência com
quantidade variável de elementos de um tipo especificado.
A sequência de elementos é armazenada como uma array.
valarray :
De modo semelhante a vector este gabarito de classe permite encapsular um sequência
com quantidade variável de elementos de um tipo especificado.
A sequência de elementos é armazenada como uma array.
O gabarito valarray é diferente de vector por duas razões:
1) valarray implementa diversas operações aritméticas entre elementos correspondentes de
objetos valarray do mesmo tipo e tamanho;
Exemplo:
x = cos(y) + sin(z),
será valido se x, y e z forem do tipo valarray< int , _mesmo_tamanho >
2) valarray define diversos modos para acesso aos seus elementos através de índice,
sobrecarregando o operador [].
list:
Este gabarito de classe permite encapsular um asequência com
quantidade variável de elementos de um tipo especificaddo

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 9 ▪ A biblioteca padrão • C++ • AGIT informática • 267

A sequência de elementos é armazenada como uma lista ligada bidirecional.


map:
Este gabarito de classe permite encapsular um sequência com quantidade variável de
elementos do tipo pair <const Key, T>, sendo que o primeiro elemento de cada par é a
chave de ordenação e o segundo elemento é o seu valor associado.
A sequência é organizada de modo a permitir que busca, inserção e
remoção de um elemento sejam efetuadas rapidamente (o número de
operações envolvidas é proporcional ao logaritmo do número de
elementos na sequência)

 Além desses e outros containers a STL contem algorítmos genéricos e objetos


função (functors).
 Veja em [Link]

 Além disso, nos exercícios do curso vários deles serão exemplificados.

Exemplos de uso:

#ifdef WIN32
#pragma warning(disable:4786) // específico Microsoft.
#endif
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>
using namespace std;
// cria um sinônimo para um vector de int’s:
typedef vector <int> vector_int;
// cria um sinônimo para um list de int’s:
typedef list<int> list_int;
// cria um sinônimo para um map onde a chave é int e o valor associado
é string:
typedef map<int, string> map_int_string;
// cria um sinônimo para um vector onde cada elemento é um vector de
int’s:
typedef vector<vector_int> vector2_int;
void TestaString();
void TestaVector();
void TestaList();
void TestaMap();
void TestaVectorVector();
void Pausa();
class FaixaImposto // mais abaixo faremos um list desta struct
{
public:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


268 • AGIT informática • C++ • • Capítulo 9 ▪ A biblioteca padrão

float m_Min;
float m_Max;
float m_Percent;
// construtor:
FaixaImposto ( float Min, float Max, float Percent )
{
m_Min = Min ;
m_Max = Max ;
m_Percent = Percent;
}
};
// cria um sinônimo para uma lista de FaixaImposto
(list<FaixaImposto>)
typedef list<FaixaImposto> list_faiximp;
int main()
{
TestaString();
TestaVector();
TestaList();
TestaMap();
TestaVectorVector();
return 0;
}

9.1 • std::string
// testa string
void TestaString()
{
cout << "testando string" << endl << endl;
string texto;
texto = "ABC"; // operador de atribuição: realiza uma cópia
cout << texto << endl;
cout << [Link]() << endl;
// quantidade de caracteres até o terminador zero
cout << texto.max_size() << endl;
// máximo de caracteres que pode ser alocado

texto += "DEF"; // operador "+=": concatenação


cout << texto << endl;

string subtexto = [Link](2,3);


// extraindo uma substring (um segmento)
cout << subtexto << endl;

int posic = [Link]('E'); // busca o caracter 'E' na string texto


if ( posic != string::npos )
// "string::npos" = "null position" (não localizado)
cout << posic << endl;

posic = [Link]("DE"); // busca a string "DE" na string texto

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


9.1 • std::string • C++ • AGIT informática • 269

if ( posic != string::npos )
// "string::npos" = "null position" (não localizado)
cout << posic << endl;

[Link](); // " apaga" todos os caracteres


// (na verdade, termina a string no primeiro byte)
if ( [Link]() ) // agora, verifica se realmente está vazia
cout << "string vazia" << endl;

texto = "ARI";
cout << texto << endl;
texto[0]='E'; // altera o primeiro caracter
cout << texto << endl;
texto[1]='L'; // altera o segundo caracter
cout << texto << endl;
texto[2]='A'; // altera o terceiro caracter
cout << texto << endl;

Pausa();
}

9.2 • std::vector
// ======== Usando vector:
void TestaVector()
{
cout << "VECTOR: Inicia teste:" << endl << endl;

//o vetor abaixo será criado com zero elementos:


vector_int VecInt;
// Adicionar um elemento ao FINAL do vetor
// contendo o valor inteiro "10":
VecInt.push_back(10) ;
// Vejamos agora como está o vetor:
cout << "Tamanho de VecInt : " << [Link]() << endl;
cout << "Tamanho maximo de VecInt : " << VecInt.max_size()
<< endl;
// preenche CINCO elementos da lista com o número 11
[Link]( 5 , 11);
// Vejamos agora como ficou o vetor:
cout << "\nSituacao de VecInt apos preenchimento de 5 elementos"
<< endl;
cout << "Tamanho de VecInt : " << [Link]() << endl;
cout << "Tamanho maximo de VecInt : " << VecInt.max_size()
<< endl;
cout << "Impressao dos elementos ate size()" << endl;
int iC;
for ( iC= 0; iC < [Link](); iC++ )
cout << VecInt[iC] << " - ";
cout << endl;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


270 • AGIT informática • C++ • • Capítulo 9 ▪ A biblioteca padrão

VecInt[2] = 12;
cout << "Alterou o valor do TERCEIRO elemento para 12" << endl;
for ( iC= 0; iC < [Link](); iC++ )
cout << VecInt[iC] << " - ";
cout << endl;
// REDIMENSIONA espaco para 200 elementos:
[Link](200);
// Vejamos agora como ficou o vetor:
cout << endl << "Situacao de VecInt apos REDIMENSIONAMENTO “
“para 200 elementos:" << endl;
cout << "Tamanho de VecInt : " << [Link]() << endl;
cout << "Tamanho maximo de VecInt : " << VecInt.max_size()
<< endl;
Pausa();
}

9.3 • std::list
// ======== Usando list:
void TestaList()
{
cout << "LIST: Inicia teste:" << endl << endl;
list_int ListInt;
list_int::iterator ListIntIt;
// Insere no inicio;
[Link] ([Link](), 1);
[Link] ([Link](), 2);
// Insere no fim:
[Link] ([Link](), 3);
// Imprime a lista:
for ( ListIntIt = [Link](); ListIntIt != [Link](); +
+ListIntIt)
{
cout << *ListIntIt << " - ";
}
cout << endl;
// Insere 3 vezes o número 4 no fim da lista:
[Link] ([Link](), 3, 4);
// Reimprime
for (ListIntIt = [Link](); ListIntIt != [Link](); ++ListIntIt)
{
cout << *ListIntIt << " - ";
}
cout << endl;
// uma lista do tipo list_faiximp (class list_faiximp)
list_faiximp ListFaixImp;
list_faiximp::iterator ListFaixImpIt;
// Insere no fim:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


9.3 • std::list • C++ • AGIT informática • 271

[Link] ( [Link]() , FaixaImposto( 0, 100, 10) );


[Link] ( [Link]() , FaixaImposto(101, 200,
15) );
[Link] ( [Link]() , FaixaImposto(201, 0, 20) );
// Imprime Percentuais de imposto:
cout << endl << "Percentuais de imposto:" << endl;

for ( ListFaixImpIt = [Link]();


ListFaixImpIt != [Link](); ++ListFaixImpIt )
{
cout << ListFaixImpIt->m_Percent << " - ";
}
cout << endl;

// Calcula imposto para um valor:


float Imposto = 0;
float Valor = 150;
cout << endl << "Imposto para 150 reais:" << endl;
for (ListFaixImpIt = [Link]();
ListFaixImpIt != [Link](); ++ListFaixImpIt )
{
if ( Valor >= ListFaixImpIt->m_Min &&
(Valor <= ListFaixImpIt->m_Max ||
ListFaixImpIt->m_Max == 0))
{
Imposto = Valor * ListFaixImpIt->m_Percent / 100;
break;
}
}
cout << "Imposto = " << Imposto << endl;
Pausa();
}

9.4 • std::map
// ======== Usando map:
void TestaMap()
{
cout << "MAP: Inicia teste:" << endl << endl;
map_int_string MapIntStr;
map_int_string::iterator MapIntStrIt;
// Inserindo elementos no mapa:

// Abaixo: "value_type" é um par "chave/valor":


[Link](map_int_string::value_type(3,"Terceiro"));
[Link](map_int_string::value_type(2,"Segundo"));
[Link](map_int_string::value_type(1,"Primeiro"));
// Procurando elementos pela chave:
MapIntStrIt = [Link](3);
// procura pelo primeiro elemento do par(a chave)
if ( MapIntStrIt != [Link]() )

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


272 • AGIT informática • C++ • • Capítulo 9 ▪ A biblioteca padrão

cout << MapIntStrIt->second << endl;


// second é o segundo elemento do par (o valor associado);
// desse modo, será impresso “Terceiro”
MapIntStrIt = [Link](2);
if ( MapIntStrIt != [Link]() )
cout << MapIntStrIt->second << endl;
// será impresso “Segundo”
MapIntStrIt = [Link](1);
if ( MapIntStrIt != [Link]() )
cout << MapIntStrIt->second << endl << endl;
// será impresso “Primeiro”
// Percorrendo e imprimindo o map:
for ( MapIntStrIt = [Link]() ;
MapIntStrIt != [Link]() ; MapIntStrIt++ )

{
cout << MapIntStrIt->first << endl; //imprime a chave;
cout << MapIntStrIt->second << endl;
//imprime o valor associado;
}
cout << endl;

// eliminando elementos:
[Link](2);
// reimprimindo o map após a exclusão do elemento com chave 2:
MapIntStrIt = [Link]();
while ( MapIntStrIt != [Link]())
{
cout << MapIntStrIt->second << endl;
MapIntStrIt++;
}
cout << endl;
// esvaziando todo o map:
[Link]();
// reimprimindo o map (NADA será impresso agora):
for ( MapIntStrIt = [Link]() ;
MapIntStrIt != [Link]() ; MapIntStrIt++)
{
cout << MapIntStrIt->second << endl;
}
Pausa();
}

9.5 • std::vector < std::vector < > >

// ======== Usando um vetor de vetores:


void TestaVectorVector()
{
cout << "VECTOR<VECTOR>: Inicia teste:" << endl << endl;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


9.5 • std::vector < std::vector < > > • C++ • AGIT informática • 273

int Linhas = 5;
int Colunas = 3;
vector2_int vec2Int;
// Irá criar 5 vetores de int dentro do vetor de vetores;
// sendo que cada vetor de int's terá 3 elementos:
// primeiro cria um vetor com 3 elementos iniciados com zero:
vector_int item;
[Link](Colunas,0); // um vetor de 3 inteiros zerados.
// agora, dimensiona o vetor de vetores com tamanho 5,
// preenchendo esses 5 elementos com o vetor de 3 int's:
[Link](Linhas, item);
// agora vamos preencher, sabendo-se
// que o vetor é indexadoa partir de ZERO (como uma matriz):
vec2Int[0][0] = 11; // "linha" 1, "coluna" 1
vec2Int[0][1] = 12; // "linha" 1, "coluna" 2
vec2Int[0][2] = 13; // "linha" 1, "coluna" 3
vec2Int[1][0] = 21; // "linha" 2, "coluna" 1
vec2Int[1][1] = 22; // "linha" 2, "coluna" 2
vec2Int[1][2] = 23; // "linha" 2, "coluna" 3
// e etc...
// agora, imprimir:
for (int iLinha = 0; iLinha < [Link](); iLinha++)
{
for ( int iColuna=0 ; iColuna < vec2Int[iLinha].size() ; iColuna++)
{
[Link](3);
cout << vec2Int[ iLinha ][ iColuna ] << " - ";
}
cout << endl;
}

/* RESULTADO:

VECTOR<VECTOR>: Inicia teste:

11 - 12 - 13 -
21 - 22 - 23 -
0 - 0- 0-
0 - 0- 0-
0 - 0- 0-

Todas as colunas de todas as linhas foram iniciadas com zero


("assign");
Depois as duas primeiras linhas tiveram seus valores alterados
manualmente.
Assim, o resultado está correto.
*/
Pausa();
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


274 • AGIT informática • C++ • • Capítulo 9 ▪ A biblioteca padrão

void Pausa()
{
cout << endl << " tecle <enter> para continuar..." << endl;
[Link](); // espera pela entrada no teclado e depois despreza;
cout << endl << endl ;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 10 ▪ Classes e objetos: Herança • C++ • AGIT informática • 275

• Capítulo 10
▪ Classes e objetos: Herança

10.1 • Regras básicas ................................................................276


10.1.1 ▪ Restrição de acesso protected.......................................................276
10.1.2 ▪ Modos de derivação........................................................................276
[Link] ▪ Derivação pública ( : public).........................................276
[Link] ▪ Derivação privada ( : private).......................................276
[Link] ▪ Derivação protegida ( : protected)...............................277
10.2 • Construtoras e destrutora na derivada e na base..................278
10.2.1 ▪ Criação de um objeto de uma classe derivada...............................278
10.2.2 ▪ Destruição de um objeto de uma classe derivada...........................278
10.2.3 ▪ Como a construtora derivada pode chamar uma construtora base.278
10.3 • Herança Múltipla..............................................................280
10.3.1 ▪ Herança múltipla e herança virtual:.................................................280

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


276 • AGIT informática • C++ • • Capítulo 10 ▪ Classes e objetos: Herança

10.1 • Regras básicas


 Podemos derivar uma classe de outra, reaproveitando assim todo o código
escrito (membros de dados e funções-membro) e acrescentando novas caracte-
rísticas à classe derivada.

A regra de derivação em si mesma é muito simples, bastando acrescentar o operador de


derivação (:) imediatamente após uma classe ser nomeada.
Esse operador deve ser seguido de uma das restrições de acesso previstas por C++, in-
dicando um modo de derivação, o qual permite elevar o nível de privacidade dos mem-
bros herdados da base na nova classe derivada (veremos esses modos logo abaixo):
Exemplo de derivação (classe “B”, derivada de “A”):
Classe base: Classe derivada:
class A Class B : public A // “B” é uma derivada de “A”
{}; {};

10.1.1 ▪ Restrição de acesso protected.


Além das seções “public” e “private”, devido a herança, existe uma terceira restrição
de acesso a membros: a seção “protected” (protegida), de tal modo que:
 os membros “public” da classe-base serão públicos na classe derivada;
 os membros “private” da classe-base, não podem ser acessados pelas fun-
ções-membro da classe derivada (serão sempre membros “private” da base);
 já os membros declarados como “protected” em uma classe podem ser aces-
sados tanto nessa classe, como em suas amigas e, ainda, nas funções de
suas classes derivadas (e não podem ser acessados em qualquer outro esco-
po).
Contudo, em uma derivada, esses níveis de restrição de acesso podem ser forçados para
um nível maior de privacidade, como veremos agora, nos modos de derivação.

10.1.2 ▪ Modos de derivação.


Temos três modos de derivar uma classe da outra:

[Link] ▪ Derivação pública ( : public).


 neste caso os membros públicos e protegidos da classe base serão respecti-
vamente públicos e protegidos na classe derivada, mantendo assim seu status;
 os membros “private” da classe-base continuam naturalmente restritos à
classe-base.

[Link] ▪ Derivação privada ( : private).


 os membros públicos e protegidos da classe-base tornam-se “private” na
classe derivada; só podem ser acessados assim por funções-membro de am-
bas as classes;.
 os membros “private” da classe-base continuam naturalmente restritos à
classe-base.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


10.1 • Regras básicas • C++ • AGIT informática • 277

[Link] ▪ Derivação protegida ( : protected).


 os membros públicos e protegidos da classe-base tornam-se “protected” na
classe derivada; só podem ser acessados assim por funções-membro de am-
bas as classes, por suas respectivas amigas, ou por funções de uma nova clas-
se que escolha como classe-base a atual derivada.
 os membros “private” da classe-base continuam naturalmente restritos à
classe-base.

 Em C++ herança é uma forma eficaz e segura de reaproveitamento de có-


digo. Deve ser usada sempre que houver uma hereditariedade real entre os
componentes envolvidos.
Faz sentido, por exemplo, derivar a classe “BotãoComBitmap” da classe
“Botão”;

 Mas não faria muito sentido derivar a classe “Menu” da Classe “Data”,
mesmo que eventualmente “Menu” precise usar “Data” (por exemplo, para
exibir uma data na tela).
Neste caso, seria melhor mantê-las independentes.
E bastaria que um dos membros de dados de “Menu” fosse um objeto da
Classe “Data” - ou um ponteiro para um objeto “Data”.

// a classe-base “cadastro”
class Cadastro
{
private:
int iStatusInterno;
protected:
char Nome[ 30 ];
char CGC[20];
char Endereco[40];
char Cidade[32];
char CEP[8];
public:
int iQualquerCoisa; // sem validação
.......... FUNÇÕES ..........
};

// a classe “clientes” é uma classe-derivada de “cadastro”:


class Clientes : public Cadastro
{
..... ACRESCENTA SEUS PRÓPRIOS MEMBROS .....
}

// a classe “fornecedores” também é uma classe-derivada de “cadastro”:


class Fornecedores: public Cadastro
{
..... ACRESCENTA SEUS PRÓPRIOS MEMBROS .....
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


278 • AGIT informática • C++ • • Capítulo 10 ▪ Classes e objetos: Herança

 Tanto fornecedores como clientes herdam as características de cadastro


(que devem ser exatamente os dados cadastrais básicos de ambas).

10.2 • Construtoras e destrutora na derivada e na


base
10.2.1 ▪ Criação de um objeto de uma classe derivada.
Quando um objeto de uma classe derivada é criado, o seguinte fluxo de processamen-
to é seguido:
1) É chamada a construtora da classe derivada.
2) Antes que suas instruções (entre as chaves da função) sejam executadas é chamada
a construtora da classe base.
3) A construtora da classe base é então executada. Isso significa que ela é executada
antes que a construtora derivada seja executada (o que faz sentido, pois é preciso
que os membros da base sejam inicializados antes que a derivada tente utilizá-los).
4) Finalmente, após o retorno da construtora base, será executada a construtora de-
rivada.
5)

10.2.2 ▪ Destruição de um objeto de uma classe derivada.

Quando um objeto é liberado, o seguinte fluxo de processamento é seguido:


1) É chamada e executada a destrutora da classe derivada
2) Imediatamente após o seu retorno, é chamada a destrutora da classe base.
Desse modo, a lógica dos objetos (disparos automáticos de funções para criação e des-
truição) é simples e naturalmente desdobrada para adequar-se ao conceito de herança.

10.2.3 ▪ Como a construtora derivada pode chamar uma cons-


trutora base.
Vimos que a construtora derivada sempre, antes de ser efetivamente executada, chama
a construtora base.
E se uma classe-base tiver mais do que uma construtora. Será chamada, por default, a
construtora que não recebe parâmetros.
Se queremos modificar isso devemos, explicitamente, chamar uma determinada cons-
trutora, passando os parâmetros.
Fazemos isso usando o operador de inicialização de membro. Vejamos um exemplo.
class Base
{
private :
int m_iCodigo;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


10.2 • Construtoras e destrutora na derivada e na base • C++ • AGIT informática • 279

………
public:
Base ( ) ; // construtora
Base ( int Cod ) ; // construtora com parâmetros
………
};

Base::Base ( ) // construtora-base
{
m_iCodigo = 0;
}

Base::Base ( int iCod ) // construtora-base com parâmetros


{
m_iCodigo = iCod ;
}

class Derivada : public Base // derivada


{
…………….
public:
Derivada ( ) ;
Derivada ( int iCod ) ;
}

Derivada::Derivada ( ) // construtora-derivada
: Base ( ) // chama a construtora-base que não recebe parâmetros,
// usando o operador de inicialização de membros,
// antes do corpo da função.
// Neste caso isto é desnecessário, pois é exatamente
// isso que será feito por default,
// já que está chamando a construtora default da base
{
………………
}
/* Contudo, a derivada poderia usar isso para forçar um valor inicial:
Derivada::Derivada ( ) // construtora-derivada
: Base ( 1 ) // chama a construtora-base que recebe parâmetros,
// passando um valor inicial (constante já que esta
//construtora derivada não tem parâmetros
{
………………
}

*/
// Agora a construtora derivada que recebe parâmetros, poderá
// repassar os parâmetros recebidos para a construtora-base:
Derivada::Derivada ( int iCod ) // construtora-derivada com parâmetros
: Base ( iCod )
// chama a construtora-base que recebe parâmetros,
// repassando o parâmetro recebido.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


280 • AGIT informática • C++ • • Capítulo 10 ▪ Classes e objetos: Herança

{
………………
}

10.3 • Herança Múltipla


 Uma mesma classe pode ser herdeira de várias classes.

class Vencimentos
{
public:
float ValorVencs;
};
class Descontos
{
public:
float ValorDescs;
};
class SalarioFinal : public Vencimentos , public Descontos
{
public:
float ValorFinal ;
float Calcula ( )
{
ValorFinal = ValorVencs - ValorDescs;
return ValorFinal ;
}
};
int main( )
{
SalarioFinal MeuSalario;
[Link] = 10;
[Link] = 5 ;
cout << [Link]( ) << endl ; //IMPRIME 5;
}
 A Classe Salario Final é herdeira de Vencimentos e Descontos.
Isso significa que um objeto SalarioFinal comporta todos os membros de Venci-
mentos e de Descontos e também os seus próprios membros.

10.3.1 ▪ Herança múltipla e herança virtual:

 Se a regra acima é verdadeira o que aconteceria se Vencimentos e Descontos


tivessem sua própria classe-base (da qual seriam derivadas)?
SalarioBase

Vencimentos Descontos
REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional
SalarioFinal
10.3 • Herança Múltipla • C++ • AGIT informática • 281

 O diagrama aparentemente poderia ser expresso assim:


class SalarioBase
{ ..... };
class Vencimentos : public SalarioBase
{ ..... };
class Descontos : public SalarioBase
{ .....};
class SalarioFinal : public Vencimentos , public Descontos
{.....};
 Mas, no caso acima, estaríamos com a seguinte situação:
 Ao construírmos um objeto “MeuSalario” da classe SalarioFinal, é consultado o
molde dessa classe; ocorre então que:
1) Ele informa que é derivado de Vencimentos;
 o molde de Vencimentos é então consultado;

 e este informa que é derivado de SalarioBase;

 os membros de SalarioBase serão então considerados para a criação do ob-


jeto;
 os membros de Vencimentos serão agora considerados na construção do ob-
jeto.
2) E SalarioFinal informa que também é derivado de Descontos;
 o molde de Descontos é então consultado;

 e este informa que é derivado de SalarioBase;

 os membros de SalarioBase serão novamente considerados na criação do


objeto;
 os membros de Desconto serão normalmente considerados;

 3 - Finalmente, os próprios membros de SalarioFinal serão considerados.

 Desse modo teremos a duplicação dos membros do primeiro ancestral da


hierarquia (a classe SalarioBase, no exemplo).
Isso pode também ficar muito confuso: seremos obrigados a usar a resolu-
ção de escopo para acessarr os membros de “SalarioBase” herdados via
“Vencimentos” e via “Descontos”.
 A ambiguidade será resolvida se especificarmos que Vencimento e Desconto
são virtualmente herdeiros de SalarioBase (e não necessariamente
herdeiros).

É o mesmo que dizer:

 “Serão herdeiros se, e somente se, tal herança ainda não houver sido reali-
zada”.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


282 • AGIT informática • C++ • • Capítulo 10 ▪ Classes e objetos: Herança

 Para implementar a herança virtual deveríamos então reescrever nossas dire-


trizes de derivação do seguinte modo:
class SalarioBase
{ ..... };
class Vencimentos : virtual public SalarioBase
{ ..... };
class Descontos : virtual public SalarioBase
{ ..... };
class SalarioFinal : public Vencimentos , public Descontos
{ ..... };

 Desse modo SalarioBase seria herdado apenas uma vez em objetos da classe
SalarioFinal.
 E seria herdado normalmente em objetos da classe vencimentos ou objetos da
classe descontos.

 Quando temos a necessidade de herança virtual (classes sendo herdadas


por vários caminhos), é preciso voltar à análise do caso de uso.
Talvez faça sentido.
Mas em muitas ocasiões revelam um erro de análise.
O exemplo acima poderia ser resovido de outro modo.

Afinal, "Vencimentos" e "Descontos", no mundo real, precisam deri-


var de "SalarioBase"?

Para este caso, uma solução mais apropriada provavelmente seria:

SalarioBase Vencimentos Descontos

SalarioFinal

class SalarioBase { ..... };


class Vencimentos { ..... };
class Descontos { ..... };
class SalarioFinal : public SalarioBase ,
public Vencimentos , public Descontos
{ ..... };

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 11 ▪ Classes e objetos: Polimorfismo • C++ • AGIT informática • 283

• Capítulo 11
▪ Classes e objetos: Polimorfismo

11.1 • Sobrecarga......................................................................284
[Link] ▪ Sobrecarga de funções................................................284
[Link] ▪ Sobrecarga de operadores..........................................285
11.2 • Redefinição de funções nas classes derivadas.......................287
11.3 • Funções virtuais ..............................................................290
11.3.1 ▪ Polimorfismo em frameworks: ponteiros para função e funções virtu-
ais 290
11.3.2 ▪ Declarar e implementar funções virtuais.........................................292
11.3.3 ▪ Funções virtuais podem ser usadas como respostas a eventos.....294
[Link] ▪ Funções virtuais puras e classes abstratas.................296
[Link] ▪ Exercício: a classe RelatPadrao..................................297
[Link].a ▪ Solução: declaração, implementação e uso da classe Relat-
Padrao. 297
11.3.4 ▪ Precauções ao usar funções virtuais..............................................298
[Link].a ▪ 1 - Nunca preencher um objeto com métodos de baixo ní-
vel. 298
[Link].b ▪ 2 - Nunca esquecer como a vtable é preenchida.............298
11.4 • Questões para revisão.......................................................300

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


284 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

 Rigorosamente falando polimorfismo refere-se a funções virtuais.


Diz-se que uma classe é polimórfica quando contem pelo menos uma fun-
ção virtual.

Mas há quem diga que, por fidelidade à palavra em sentido amplo (polimorfismo = al-
guma coisa que assume múltiplas formas), tudo aquilo que implicar em múltiplas for-
mas de uso de um recurso deveria ser considerado no capítulo polimorfismo.
Sem entrar no mérito, iremos aqui incluir tais recursos.

11.1 • Sobrecarga.
[Link] ▪ Sobrecarga de funções.

 Se escrevermos diversas funções usando um mesmo nome, podemos dizer que


temos uma função sobrecarregada;
Isto é: a função tem mais do que uma forma (ou várias versões), caracterizando
o polimorfismo.

E como escrever funções diferentes usando um mesmo nome?


A regra básica da sobrecarga é que exista alguma diferença quanto à lista de parâme-
tros, pois do contrário o compilador não teria como distinguir qual versão está sendo
chamada.
Exemplo:
versão 1 - int SomaNumeros ( int A , int B ) ;
versão 2 - float SomaNumeros (float A, float B) ;
.................................................................
int main( )
{
int A = SomaNumeros ( 5, 4 ); /* será chamada a VERSÃO 1, pois é
esta que recebe dois parâmetros do tipo int /*

float B = SomaNumeros ( 5.3F, 4.4F ); /* será chamada a VERSÃO 2,


que recebe dois parâmetros do tipo float */
return 0;
}

 Observe que isto é válido para qualquer função, membra de classe ou não.

Observe também que a sobrecarga poderá ser especialmente útil em fun-


ções construtoras de classe, já que a sobrecarga permitirá diversas ver-
sões de modo a passar parâmetros e inicializar alguns membros de dados
com valores diferenciados.

Exemplo com construtoras:


class Data
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.1 • Sobrecarga. • C++ • AGIT informática • 285

private:
char Dia;
char Mes;
short Ano ;
public:
Data ( ) ; /* Não recebe parâmetros,
logo só poderá inicializar os membros com valores
default */
Data ( char D, char M, short A) ; /* poderá inicializar os membros
Dia (a partir do parâmetro D),
Mes (a partir do parâmetro M),
Ano (a partir do parâmetro A) */

Data ( short N ) ; /* só poderá incializar um único membro


(a partir do parâmetro N);
os demais só poderão ser incializados a partir de valores default
*/
};

[Link] ▪ Sobrecarga de operadores


Já vimos que em C++ podemos escrever nossos próprios operadores, como membros
de uma classe.
 Na realidade, um operador é uma função com algumas características especí-
ficas.
 É escrito do mesmo modo que escrevemos uma função –só que não pode-
mos escolher livremente o nome da função, devendo empregar um símbolo
ou nome de operador já existente na linguagem.
 Para isto utilizamos o especificador operator, seguido de um símbolo de
operador ( =, + , - , etc) já admitido pela linguagem.
Exemplo:
class Qualquer
{
private:
int Valor ;
public:
int operator = (int Param) ;
};
int Qualquer::operator = (int Param )
{
Valor = Param;
return Valor;
}

int main( )
{
Qualquer Var;
Var = 2 ;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


286 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

 Uma função operadora redefine ou cria, para um determinado tipo de dados, um


operador já existente na linguagem; então ela herda a regra de precedência do
operador já existente na linguagem.
 Os parâmetros da função operadora são os operandos admitidos para cada
operador; por isso existirá uma restrição quanto ao número de parâmetros (nor-
malmente apenas um parâmetro explícito; e no máximo dois).
 Uma vantagem em usar operadores é que a escrita se torna mais legível.
 Mas uma outra vantagem, ainda mais significativa, é que os operadores permi-
tem que classes diferentes se comuniquem, contribuindo fortemente para
programação genérica.
 Considere, sobre isto, o seguinte exemplo (veremos em detalhes nos capítulos
sobre templates e STL.

.....................................
list < Data > dtLst; // uma lista de objetos do tipo 'Data'
.....................................
[Link](); //classifica a lista de datas

 O uso do método “sort”, aqui, só foi possível porque a class Data dispõe da
função operator <(...) (operador “menor que”), o qual é usado pelo método
sort, para classificar a lista.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.2 • Redefinição de funções nas classes derivadas. • C++ • AGIT informática • 287

11.2 • Redefinição de funções nas classes derivadas.


 Uma classe derivada pode redefinir funções de suas ancestrais.
 Isto significa que:
 uma classe base, por exemplo a classe “Janela”, pode ter uma função chamada
“Exibe”;
 uma derivada, por exemplo a classe “JanelaComMensagem”, pode também ter
uma função chamada “Exibe”
 Objetos da classe “Janela” acessarão a sua própria função “Exibe”.
 Objetos da classe “JanelaComMensagem” acessarão a função “Exibe” desta
classe e não a função da classe-base (pois a função da derivada estará escon-
dendo a ancestral).
 Contudo, dentro das funções da classe derivada, a função-base pode ser cha-
mada diretamente através do operador de resolução de escopo, permitindo as-
sim, inclusive, o reaproveitamento do código já escrito nas funções da classe
base.

Exemplo:
class Janela // classe base
{
public:
void Exibe( )
{
DesenhaBorda ( );
ImprimeTitulo ( );
}
…………………………
};
class JanelaComMensagem : public Janela // derivada
{
public:
void Exibe( ) // redefine função da classe ancestral
{
Janela::Exibe( ) ; // chama diretamente a função ancestral
ImprimeMensagens ( ) ; // acrescenta código específico
}
...........................
};
int main( )
{
Janela objJanela ;
[Link] ( ) ; // será usada a função Exibe() da classe-base
JanelaComMensagem objJanMsg ;
[Link]( );
// será usada a função Exibe() da classe-derivada
return 0;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


288 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

Vimos então que a redefinição de funções de classes-base em uma classe derivada per-
mite ocultar as funções-base.
Este recurso irá permitir que uma função possa ser simplesmente substituída em clas-
ses derivadas.
Vamos usar mais um exemplo de redefinição de funções para depois melhor entender-
mos o uso de funções virtuais.

Um exemplo.
Vamos criar uma classe para servir como base para a construção de tabelas de referên -
cia. Depois criaremos uma derivada para implementar um tipo de tabela mais específi-
co.

1) classe base:
 Ela cuida de apenas dois campos, sempre usados em tabelas de referência: os
campos “código” e “Descrição”.
 Para código, a classe implementa a regra de validação mais comum: o código
deve estar entre um mínimo e um máximo. Então a classe base fornece uma
função para que sejam informados os valores mínimo e máximo. Contudo, ela
exige que esta função seja chamada apenas uma vez para cada objeto criado
(de tal modo que novas chamadas a essa função serão simplesmente despreza-
das). Com isso, o mínimo e o máximo definem um único comportamento para o
campo “código”, durante todo o tempo de vida de cada objeto. Já o campo “Des-
crição” será considerado válido se preenchido com uma quantidade mínima de
caracteres estipulada pela classe.
 Além disso, a classe base implementa um método para impressão de código e
descrição: se o código estiver correto e se a descrição estiver preenchida com a
quantidade mínima de caracteres, os valores serão impressos; do contrário,
será impressa uma mensagem de erro.
2) classe derivada:
 Agora, escreveremos uma classe derivada que poderá acrescentar outros cam-
pos; neste caso, acrescentaremos o campo “Tipo” (que servirá para identificar
se o par código/descrição se refere a uma receita ou a uma despesa).
 Se criarmos objetos a partir da classe derivada e em seguida usarmos o método
“Imprimir”, serão impressos apenas os campos “código” e “descrição” –pois, até
aqui, a única função de impressão existente é a função da classe-base que co-
nhece apenas esses dois campos.
 Esse método não é assim realmente útil, pois deveria ser impresso também o
campo “Tipo”. Para resolver o problema, a classe derivada deve implementar o
seu próprio método “Imprimir”.
 Ao incluir esse novo método, que terá o mesmo nome do método correspon-
dente na classe base, o que acontece é que, para objetos criados a partir da
classe-derivada, a função da classe-base é “escondida”, isto é, é anulada pela
função da derivada. Assim, para estes objetos, será sempre chamada a função
da classe derivada.
 Dentro da função-derivada, ela pode contudo (caso seja conveniente), reapro-
veitar aquilo que já é feito pela função-base, chamando explicitamente essa fun-
ção através do operador de resolução de escopo.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.2 • Redefinição de funções nas classes derivadas. • C++ • AGIT informática • 289

 Por exemplo:
// redefinição da função de mesmo protótipo na classe base:
bool derivada::Imprimir( bool bFimLinha )
{
if ( base::Imprimir( false ) ) // reaproveita função da classe base
// (sem fim de linha)
{ // a qual imprime código/descrição, ou uma mensagem de erro
// agora acrescenta a impressão do TIPO :
switch( m_iTipo )
{
case RECEITA:
cout << “ - RECEITA “; break;
case DESPESA:
cout << “ – DESPESA “; break;
default:
cout << “ – INVALIDO “; break;
}
}
if ( bFimLinha )
cout << endl;
return true;
}

A observação mais importante que podemos fazer é que a redefinição de funções de


classes-base anula tais funções, permitindo assim a sua substituição de modo eficiente
e sem que as classes-base precisem ser alteradas.
Isto é feito de modo muito simples:
 se uma variável é declarada a partir de uma classe base, e se uma função é
chamada a partir dessa variável, será sempre utilizada a função membro des-
sa classe;
 se uma variável é declarada a partir de uma classe derivada, e se uma função
é chamada a partir dessa variável, será sempre utilizada a função membro
dessa classe, anulando todas as funções da classe base que tenham esse
mesmo nome.
Quem determina portanto qual função deve ser chamada é o tipo da variável que dis-
para a chamada de função(isto é, a variável que terá seu endereço passado no parâme-
tro “this”).
As funções estão sendo chamadas diretamente por variáveis; a variável tem um tipo;
o tipo tem funções próprias; simplesmente será usada a função correspondente ao tipo.
Assim, quando fazemos:
base oB;
[Link]( );
OU
derivada oD;
[Link]( );
não há dúvidas sobre qual função deve ser usada em cada caso, já que o tipo da variá-
vel está claro, e, se cada classe possuir a sua própria função “Imprime”, é essa função
própria que deverá ser chamada.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


290 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

Mas nem sempre temos uma situação assim tão clara e direta. E é aqui que entra um
outro elemento da linguagem: as funções virtuais.

11.3 • Funções virtuais


Esse é o elemento essencial de polimorfismo. Por isso mesmo dizemos que uma classe
é “polimórfica”, quando ela dispões de funções virtuais.
 Inicialmente devemos destacar que polimorfismo é um princípio muito antigo
usado na indústria desde a segunda onda da Revolução Industrial.
Contudo, em programação, muitas vezes deixamos de tirar proveito desse
princípio tão antigo.

 Um exemplo típico:

11.3.1 ▪ Polimorfismo em frameworks: ponteiros para fun-


ção e funções virtuais
Um framework tipicamente é um “motor” que oferece um serviço incompleto. Por ex-
emplo, um “loop” com certas características: ele saber “girar” mas não sabe exatamen-
te o que fazer com esse “giro” em termos de uma aplicação final.
A utilidade de um “framework” está exatamente em implementar uma estrutura genéri-
ca de aplicação que fornece a funcionalidade previsível e comum a uma família de pro-
blemas.
Por isso, nesses casos, o melhor é isolar o “motor” da sua “aplicação”, pois assim
permitimos que ele seja reaproveitado para diversas situações, para muitas “aplica-
ções”. E esse comportamento “multi-uso” é uma das manifestações de polimorfismo.

É o caso de uma furadeira elétrica que na verdade faz bem mais do que apenas furos:

Uma ferramenta
polimórfica...

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.3 • Funções virtuais • C++ • AGIT informática • 291

Na verdade, a ferramenta acima poderia ser descrita como “um motor que só sabe gi-
rar e serve para acionar qualquer coisa que precise girar e que possua uma haste que
possa ficar presa no bico acoplado ao motor”.

A palavra “furadeira” nem é muito adequada, pois descreve apenas uma das aplicações
desse motor. No bico(o mandril), que irá girar, poderemos encaixar coisas muito dife-
rentes e que farão com que o motor seja adaptado a aplicações particulares:
• brocas de diferentes tamanhos e com diferentes finalidades (aqui temos uma
furadeira);
• lixas circulares (aqui passamos a ter uma lixadeira);
• serras circulares (aqui passamos a ter uma serra elétrica);
• escovas, polidoras, etc.

Isso é exatamente aquilo que podemos chamar de polimorfismo: uma mesma ferra-
menta básica que pode trabalhar de múltiplas formas. É flexível e a sua flexibilidade
reside justamente no fato de que essa é uma ferramenta incompleta. Assim, ela só
será útil se adquirimos também a broca, a lixa, e o que mais for necessário para os nos-
sos usos.
Mas se, por exemplo, ela tivesse uma broca soldada no bico, então ela se tornaria rígi-
da, e não polimórfica: ela estaria completa, sim, mas nos forneceria apenas uma e so-
mente uma funcionalidade...
E ficaria bem mais caro adquirir (e também manter) diversos motores todos eles “com-
pletos”, todos amarrados a uma utilidade fixa.

E não é muito diferente tratando-se de softwares.


Também para programas de computador, se há alguma família de problemas em que
um “motor” poderia servir para muitas situações, então a solução é também separar o
“motor” das suas “aplicações”.
O engate (o “mandril”) nesse caso será um ponteiro para função acoplado ao
“motor”.
Então, nas aplicações específicas, só precisaremos “encaixar” nesse “bico do motor”
o endereço de funções específicas para cada caso: funçao “Broca”, função “Serra”,
função “Lixa”, função “Escova”, etc, de acordo com as necessidades de cada situação.

E há duas maneiras de fazer isso:


• trabalhando diretamente com os endereços de funções através de ponteiros
para função;
• trabalhando indiretamente com esses endereços através de funções virtuais;

Do ponto de vista da orientação a objetos, a melhor escolha seria o uso de funções vir-
tuais, pois ele ocorre de modo estruturado, seguindo a lógica e o tempo de vida dos ob-
jetos.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


292 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

Contudo ponteiros para função tem um importante uso em bibliotecas C++, como é o
caso do mecanismo de signals/slots criado por Qt (e seguido depois por outras bibliote-
cas com a libsigC++ e a boost)

11.3.2 ▪ Declarar e implementar funções virtuais.


Frequentemente precisamos criar classes para implementar tarefas genéricas. E, em
muitos desses casos, queremos que a classe genérica contenha uma determinada lógi-
ca, comum a problemas daquela natureza.
Isto é: a classe deve garantir que um conjunto de operações seja executado sempre com
uma determinada sequência, o que pode implicar na necessidade de disparar chamadas
de funções nos momentos adequados.
Contudo, algumas dessas funções podem estar incompletas (pois a classe base, para
ser útil, deve cuidar exclusivamente da parte genérica), realizando assim apenas uma
parte do trabalho necessário.
Pior do que isso: em muitos casos a classe base não terá condições de oferecer nem
mesmo uma função incompleta. Pois ela sabe apenas que, em determinado momento,
deve ser executada uma determinada função; ela sabe qual o papel que deve ser de-
sempenhado por essa função(o que significa que ela conhece o tipo da função e o obje-
tivo geral do trabalho que deve ser executdado por ela) mas não sabe como a função
deve ser implementada (pois isto depende da situação específica).
Nessa última situação, a classe base estará chamando uma função que na realidade não
está implementada ainda(isto é: não está implementada no escopo dessa classe). Em
consequência, obrigatoriamente deverá existir uma classe derivada que implemente
a função (pois simplesmente não é possível chamar uma função que não exista em al-
gum lugar).
Observe a diferença entre esta situação e a anterior. Na situação anterior, a classe base
contem uma determinada função e uma classe derivada contem uma função com o
mesmo nome. Se criarmos um objeto a partir da classe base, para esse objeto será usa -
da a função-base. E, se um objeto for criado a partir da derivada, para esse objeto será
usada a funçã[Link] porque estamos falando de uma função que estará sendo
chamada diretamente pelo objeto:
[ [Link]( ); ou: [Link]( ); ].
Já na segunda situação, teremos uma função da classe-base (que simplesmente desco-
nhece a existência de derivadas) chamando uma outra função da própria classe-base.
Contudo, na maioria dos casos, queremos que seja executada uma função de alguma
classe derivada(para que seja realizado um trabalho específico, adequado a uma deter-
minada situação). E aqui temos as duas possibilidades assinaladas acima:
 Uma função de uma determinada classe base ( “Funcao_1” ) chama outra função
da mesma classe ( “Funcao_2” ). “Funcao_2” é implementada na classe base de
modo genérico e é incompleta para a maioria das aplicações. Então escreve-
mos uma derivada que implementa a sua própria “Funcao_2”, para que seja exe-
cutado o trabalho específico (e completo) necessário nesse caso. O código fica-
ria assim:
class base
{
public:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.3 • Funções virtuais • C++ • AGIT informática • 293

………….
void Funcao_1( )
{
………….
Funcao_2(); // exatamente neste ponto Funcao_2( )
// deve sempre ser executada.
………….
}
void Funcao_2()
{
AbrirArquivoLog(); // Funcao_2 sabe que o arquivo de ocorrências
// deve ser aberto
GravarRegistroCabecalho(); // e que um registro “header”
deve ser gravado;
// mas não sabe o que deverá ser gravado a seguir.
}
~base() // destrutora
{
FecharArquivoLog();
}
void AbrirArquivoLog();
void FecharArquivoLog();
void GravarRegistroCabecalho();
};

class derivada : public base


{
void Funcao_2()
{
base::Funcao_2( );
}
};

 Observe-se que nesse caso a função que gostaríamos de redefinir (funcao_2) é


chamada dentro de uma função da própria classe base.
 E a classe base desconhece a existência de derivadas. Como fazer então para
que essa função possa ser redefinida e substituída por uma função de uma
classe derivada?
 Se a declararmos como virtual, ela será chamada a partir de um ponteiro
para função e não rigidamente a partir do seu nome.
 Assim quando declaramos uma função como virtual, o compilador irá criar um
membros de dados oculto e obrigatório.
 Esse membro de dados é uma matriz de ponteiros para função denominada
vtable.
 E para cada função virtual será adicionado um elemento nessa matriz de pontei-
ros, de tal forma que a matriz possa armazenar os endereços de todas as fun-
ções virtuais da classe.
 Antes que a construtora-base seja executada, a matriz é preenchida com os en-
dereços das funções da classe base.
 Mas, após o retorno da construtora-base e antes que seja executada a constru-
tora-derivada, os endereços são alterados: a matriz será então preenchida com

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


294 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

os endereços das funções-derivadas (somente para aquelas que foram redefini-


das).

11.3.3 ▪ Funções virtuais podem ser usadas como respostas a


eventos.
Já vimos isto quando falamos de ponteiros para funções. E funções virtuais têm a mes-
ma flexibilidade de um ponteiro para função, só que utilizando a lógica própria aos
objetos.
Vejamos um exemplo.
Iremos implementar a classe “Despertador”. A classe “Despertador” sabe que, de tem-
pos em tempos (a cada cinco segundos, por exemplo), deve disparar uma chamada de
função para que algum trabalho seja feito. Assim, ela funciona como um “timer”.
Por exemplo: uma aplicação poderia utilizar os serviços de “Despertador” para receber
uma notificação a cada cinco minutos. A aplicação então poderia checar se existem ar-
quivos abertos e se eles estão em uso. Se, nos últimos cinco minutos, os arquivos per-
maneceram abertos mas não houve qualquer atividade por parte do operador do siste-
ma, a aplicação pode simplesmente fechar os arquivos ou tomar qualquer outra atitude.
Outro exemplo: em um sistema operacional que não dispusesse de um mecanismo efi-
ciente de sinais ou mensagens, uma aplicação poderia usar os serviços de “Desperta -
dor” para, a cada cinco segundos, ler o arquivo de configurações “[Link]” (ou
“[Link]”). Com isto, a aplicação ficaria sabendo que seus parâmetros de funcio-
namento foram alterados e, a partir daí, poderia reorientar o modo de execução de al -
guns dos seus procedimentos.
Enfim: a classe “Despertador” realiza o trabalho genérico de controlar intervalos de
tempo, garantindo assim que periodicamente seja disparada uma chamada de função.
Essa chamada de função cumpre o papel de uma mensagem que notifica a aplicação
sobre a ocorrência de um evento (e, no caso da classe “Despertador”, o evento é: “no-
vamente foi atingido o fim do período estipulado”).
“Despertador” conhece tudo sobre controle de tempo e conhece também as caracterís-
ticas de tipo da função que deve ser disparada (isto é: quais são os seus parâmetros e
qual é o seu retorno). Mas “Despertador” não conhece o trabalho específico que deve
ser feito pela função, pois, como vimos nos dois exemplos acima, isso depende da apli-
cação específica –podendo implicar em atividades completamente diferentes.
Por isso a classe base deve declarar uma função virtual para que esta seja redefinida
em derivadas.

Implementação do exemplo:

#include <iostream>
#include <cstdlib>
#include <ctime>

class Despertador
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.3 • Funções virtuais • C++ • AGIT informática • 295

public:
Despertador()
// vtable[0] = &Despertador::Despertar // na base
{
}

virtual bool Despertar( int nVez ) // função virtual


{
return false;
}

public:
void Iniciar( int Intervalo )
{
int Vez = 0;
do
{
Vez++;
Pausa(Intervalo);

} while ( Despertar( nVez) ); // chamada à função virtual:


// O EVENTO É DISPARADO
}

void Pausa ( int Intervalo )


{
clock_t Fim =((clock_t)Intervalo * CLOCKS_PER_SEC)
+ clock();

while( Fim > clock() ) ;

}
};

// DERIVADA: irá redefinir a função virtual “Despertar”


class AcordaParaImprimir : public Despertador
{
AcordaParaImprimir()
: // vtable[0] = &AcordaParaImprimir&&Despertar
{}

bool Despertar(int nVez) // redefinindo esta função,


{ // irá RESPONDER AO EVENTO
cout << "Acordei. Vou Imprimir" << endl;
return (nVez<10);
}
};

int main()
{
AcordaParaImprimir DaUmTempo; // objeto é criado a partir da derivada

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


296 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

[Link]( 1 );
return 0;
}

[Link] ▪ Funções virtuais puras e classes abstratas.


Observe que no exemplo da classe “Despertador”, a função virtual “Despertar” da
classe-base simplesmente não faz nada, tendo apenas uma linha de retorno (“return
false”), que interromperá a pausa na primeira vez em que ela for usada.
Se ela fizesse alguma coisa minimamente útil, então a função de mesmo nome da deri-
vada poderia até, se fosse conveniente, reaproveitar o seu código fazendo uma chama-
da direta à função-base.
Mas não é esse o caso.
Assim poderíamos declarar essa função como uma virtual-pura.
Uma função virtual pura simplesmente não existe na classe base. É apenas uma interfa -
ce (um protótipo). Contudo o fato de que ela tenha sido declarada faz com que a tabela
de ponteiros para funções virtuais (vtable) ganhe mais um elemento.
Só que as entradas na matriz de ponteiros para as funções virtuais puras deverão ser as-
sinalada como nulas (já que esse tipo de função não tem corpo e assim sendo não exis-
tirá qualquer endereço válido).
Por isso se uma classe tem pelo menos uma função virtual pura ela passa a ser conside-
rada como uma classe abstrata.
Isto é: não podemos declarar objetos a partir dessa classe e sim, apenas, a partir de
classes derivadas que tenham redefinido a função virtual pura criando um corpo
(código endereçável) para ela
Logo, na derivada, essa função não poderá ser também virtual pura (do contrário conti-
nuaríamos com uma chamada a um ponteiro de função cujo conteúdo é um endereço
inválido).
E, se isso acontecer, então teremos que criar uma derivada dessa primeira derivada, até
que a função adquira um corpo endereçável.
Enfim: só uma classe que implemente a função permitirá que sejam criados objetos a
partir dela.

Desse modo a função “Despertar” da classe “Despertador” poderia ter sido declarada
assim:
virtual bool Despertar ( ) = 0 ;
A especificação “igual a zero” no final do protótipo da função indica que ela é uma
virtual pura; não terá corpo nesta classe e o seu endereço é assim inválido (zero, isto é,
nulo).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.3 • Funções virtuais • C++ • AGIT informática • 297

[Link] ▪ Exercício: a classe RelatPadrao


Veja o código fonte deste exercício no diretório do instrutor:

 A solução completa está em: “Exercicios/virtuais/Relatorio”

[Link].a ▪ Solução: declaração, implementação e uso da classe RelatPa-


drao.
A classe RelatPadrao fornece a infraestrutura básica para a impressão de relatórios. Só
pode ser usada a partir de uma derivada, já que contem uma função virtual-pura, a fun-
ção "ImprDetalhe", a qual deverá ser redefinida para imprimir as linhas de detalhe.
Por exemplo:
class Relato : public RelatPadrao
{
public:

// redefine a virtual-pura para impressão de detalhes:

bool ImprDetalhe();
};

Além disso, antes de começar a impressão podemos definir linhas de


cabeçalho e/ou de rodapé, conforme o exemplo abaixo:

int main()
{
RelatPadrao::StringList Cabec; // StringList: sinônimo para list<string>
[Link]([Link](), "Titulo Geral do Relatorio - Folha =#000#");
// podemos acrescentar mais linhas com insert;
// OBS: por convenção da classe, um # finalizado com outro #
// marca a posição em que deve ser inserido o número da FOLHA.
Relato Rel;
// cabeçalho com um fio horizontal separador
// e uma linha em branco para separação:
[Link](Cabec, true, 1);
// A função abaixo ("Imprime") ficará em loop,
// imprimindo cabeçalho e rodapé no momento adequado, e chamando
// a virtual-pura "ImprDetalhe" até que esta retorne falso,
// quando então o loop será finalizado e "Imprime" retornará:
[Link]("LPT1");
return 0;
}
Além disso, a classe derivada poderia também redefinir as virtuais(não-puras) "Impr-
Cabecalho" e “ImprRodape", caso precisasse modificar o comportamento padrão des-
sas funções-base as quais já imprimem o cabeçalho e o rodapé a partir das "Strin-
gList's" informadas nas funções "Cabecalho" e Rodape".
A solução está dividida em 3 arquivos:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


298 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

a. declaração da classe "RelatPadrao" (Relat.h)


b. implementação da classe ([Link])
c. um exemplo de uso ([Link]).

11.3.4 ▪ Precauções ao usar funções virtuais.


[Link].a ▪ 1 - Nunca preencher um objeto com métodos de baixo nível.
 Ao usar uma única função virtual, uma classe já passa a ter um membro de da-
dos oculto (a vtable). Por isso nunca utilize uma prática comum entre progra-
madores C :
Data Qualquer;
memset ( &Qualquer , 0x0 , sizeof(Data) );

 Isto já é ruim pois anula o encapsulamento.


 Mas mesmo o código abaixo, também não deve ser escrito:
Data::Data ( ) // construtora.
{
memset ( this , 0x0 , sizeof( Data ) );
}
 Nos dois casos a vtable foi zerada logo após ter sido preenchida, levando o
programa a executar um endereço inválido na primeira oportunidade em que a
virtual for chamada.
[Link].b ▪ 2 - Nunca esquecer como a vtable é preenchida.
Sempre que usarmos virtuais e, especialmente, virtuais-puras, temos que estar consci-
entes do modo como a vtable é preenchida (lógica de construtoras e destrutora entre
classe base e derivada):

 Na construtora-base o endereço existente para uma virtual na vtable é o ende-


reço da função membra da classe base.
 Se chamamos uma virtual aí estaremos chamando sempre a função da classe
base.
 E, se ela for uma virtual-pura o seu endereço é inválido (zero, pois a função
não existe aqui).

 Logo, não se pode chamar uma virtual-pura dentro da construtora-base


(erro de compilação), ou, indiretamente, em qualquer outra função da
classe que seja chamada pela construtora (erro de execução).
Só após o retorno da construtora-base, e antes da execução da cons-
trutora-derivada, é que o ponteiro estará preenchido adequadamente com
o endereço da função redefinida na derivada.

 Na destrutora-base o endereço de uma função virtual armazenado na vtable,


volta a ser o endereço da função membra da classe base.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.3 • Funções virtuais • C++ • AGIT informática • 299

 Então, o último ponto em que uma virtual pura pode ser chamada é a destruto-
ra da derivada.
 Pois, após o retorno da destrutora derivada ela será completamente destruída.
Assim, após este retorno, e antes de ingressar na destrutora-base, o endereço
de uma virtual na vtable volta a ser o endereço da classe base. E, sendo pura,
o endereço é inválido.

 Então, não podemos chamar uma virtual-pura na destrutora-base ou em


função por ela chamada.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


300 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

11.3.5 ▪
11.4 • Questões para revisão
Responda às questões abaixo. Em seguida, compare suas respostas com as res-
postas localizadas no Anexo B-7, página 479. Caso não entenda, encaminhe as dú-
vidas ao instrutor.

 responda à pergunta que está no comentário do código abaixo:

class X
{
................
};
class Y : public X  // O que significa esta declaração?
{
...................
};
_______________________________________________________

_______________________________________________________

_______________________________________________________

_______________________________________________________

 responda à pergunta que está no comentário do código abaixo:

class X
{
virtual bool Funcao( )  // Que função é esta e para que serve?
{
............................
}
};
_______________________________________________________

_______________________________________________________

_______________________________________________________

 responda à pergunta que está no comentário do código abaixo:

class X
{
X( ) {}
};
class Y : public X
{
Y( ) {}
};

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


11.4 • Questões para revisão • C++ • AGIT informática • 301

int main ( )
{
Y meu_Y ;  // Que funções serão executadas aqui ?
// E em que ORDEM ?
............................
}
_______________________________________________________

_______________________________________________________

_______________________________________________________

 responda à pergunta que está no comentário do código abaixo:

class X
{
int Imprime ( ) {
............................
}
};
class Y : public X
{
int Imprime( ) {
............................
}
};

int main ( )
{
Y meu_Y ;
meu_Y.Imprime( ) ;  // Que funções (e de quais classes)
// serão chamadas aqui ?
// E em que ORDEM ?
........................
}
_______________________________________________________

_______________________________________________________

_______________________________________________________

 responda à pergunta que está no comentário do código abaixo:



class X
{
virtual bool Funcao( ) {
............................
}
int Imprime ( ) {
Funcao( ) ;
............................

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


302 • AGIT informática • C++ • • Capítulo 11 ▪ Classes e objetos: Polimorfismo

}
};
class Y : public X
{
bool Funcao( ) {
............................
}
};
int main ( )
{
Y meu_Y ;
meu_Y.Imprime( ) ;  // Que funções (e de quais classes)
// serão chamadas aqui ?
// E em que ORDEM ?
............................
}
_______________________________________________________

_______________________________________________________

_______________________________________________________

_______________________________________________________

_______________________________________________________

 responda à pergunta que está no comentário do código abaixo:

template <class T> class X


{
T Valor ;  // Qual é o tipo do membro de dados “Valor” ?
...................
};
_______________________________________________________

_______________________________________________________

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 12 ▪ Tratamento de exceções • C++ • AGIT informática • 303

• Capítulo 12
▪ Tratamento de exceções

12.1 • Usando throw ... try {...} catch(...) {...}.............................304


12.2 • Usando um tratador default...............................................306
12.3 • std::exception .................................................................306
12.4 • Derivadas padrão de std::exception ...................................310

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


304 • AGIT informática • C++ • • Capítulo 12 ▪ Tratamento de exceções

12.1 • Usando throw ... try {...} catch(...) {...}


Um desvio de fluxo especial é usado para o tratamento de exceções.
Trata-se da combinação throw ... try {...} catch {...}
Se uma determinada função é usada de modo indevido, recebendo uma ordem impos-
sível de cumprir (por exemplo, uma tentativa de acessar uma memória não alocada),
ela pode exigir que esse erro seja tratado como uma exceção por quem tenha chama-
do a função.
Assim a função utilizará throw, que dispara uma chamada a um tratador de exceções.
O trecho do código responsável pela chamada incorreta, deveria ter se precavido,
apresentando um tratador de exceções para que ele possa ser chamado em caso de vi-
olação.
Assim:
try
{
// chama a função crítica a qual chamará throw em caso de violação
}
catch(...) //em caso de violação throw desviará o processamento para cá:
{
// código para tratamento da exceção.
}

O try posiciona o bloco associado ao catch como sendo o código responsável pelo tra-
tamento de exceções que sejam provocadas por qualquer instrução contida no bloco as-
sociado ao próprio try.
Assim, se, em uma das instruções dentro do bloco associado ao try, for chamada uma
determinada função e esta função constatar uma violação e fizer uma chamada a th-
row, imediatamente será executado o bloco de código associado ao catch.

Exemplo:
#include <iostream>
#include <string>
using namespace std;

// classe com informações para tratamento de exceções


class ErrDim
{
public:
string m_Descr; // descrição da exceção
string m_Origem; // onde ela ocorreu
};
// classe onde será disparada a exceção:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


12.1 • Usando throw ... try {...} catch(...) {...} • C++ • AGIT informática • 305

class Generica
{
public:
enum {MAX_DIM=10};
private:
int Matriz[MAX_DIM];
public:
void Atribuir(int indice, int valor);
};

void Generica::Atribuir(int indice, int valor)


{
if ( indice <= MAX_DIM )
Matriz[indice] = valor;
else // Impossível continuar!!!
{
ErrDim edim;
edim.m_Descr ="indice invalido";
edim.m_Origem="Generica::Atribuir/[Link]";

throw edim; //  chama um tratador de exceção


// passando "edim" como parâmetro
}
}

int main()
{
Generica matr;
try
{
[Link](20, 50); // uso incorreto! estouro de dimensão.
}
catch( ErrDim & ed ) // exceções informadas por um objeto ErrDim
{
cout << "Descricao: "<< ed.m_Descr << endl;
cout << "Origem: " << ed.m_Origem << endl;
}
catch(...) // qualquer outra exceção
{
cout << "Erro não identificado" << endl;
}
return 0;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


306 • AGIT informática • C++ • • Capítulo 12 ▪ Tratamento de exceções

12.2 • Usando um tratador default


Caso a função main não tivesse tratado a exceção, o programa seria [Link]-
tudo, a classe Generica poderia ter previsto essa situação, definindo um tratador de-
fault que seria acionado se o disparo efetuado por throw não encontrasse nenhum trata-
dor no ponto onde ocorreu a violação de acesso.
Isto pode ser feito cadastrando-se um ponteiro para uma função tratadora, através da
função set_terminate:
void term_function()
{
cout << "excecao nao foi tratada" << endl;
exit(-1);
}
void Generica::Atribuir(int indice, int valor)
{
if ( indice <= MAX_DIM )
Matriz[indice] = valor;
else // Impossível continuar!!!
{
ErrDim edim;
edim.m_Descr ="indice invalido";
edim.m_Origem="Generica::Atribuir/[Link]";

// define abaixo um tratador default, que será usado


// se nenhum tratador responder ao throw
set_terminate(term_function); //  term_function será o
// tratador default.

// a função term_function
// será chamada se não houver resposta para o disparo abaixo:
throw edim; // chama um tratador de exceção
}
}

12.3 • std::exception
O exemplo acima funciona, mas o ideal é utilizar uma interface bem conhecida para
o tipo do argumento que será usado quando uma exceção é lançada.
Para isso temos std::exception.
Para personalizá-la podemos criar uma derivada, ou, melhor, usar uma derivada já
existente na própria biblioteca padrão.
Neste exemplo, usaremos uma derivada própria. No próximo, usaremos uma derivada
já existente na biblioteca. Basicamente, será preciso redefinir a virtual "what" da base.

#include <iostream>
#include <string>
#include <exception>

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


12.3 • std::exception • C++ • AGIT informática • 307

#include <cstdlib>

// emite uma mensagem final


void MsgFinal()
{
std::cout << "\n<enter> p/sair\n";
std::[Link]();
}

// tipo parametrizado valor constante


// \|/ \|/
template <typename T, size_t Dim> class VetorFixo
{
private:
T m_Vetor[ Dim ]; // vetor de "T" com dimensões fixas

// Classe aninhada para tratamento de exceções


// (aninhada, pois só serve para trabalhar com "VetorFixo").
// As funções "get" e "set" de VetorFixo<> usarão esta classe
// para disparar exceções:
class Exception : public std::exception // deriva da classe padrão
{ da STL
friend class VetorFixo<T, Dim> ;
// ações que podem provocar uma exceção
// (funções get/set de "VetorFixo"):
enum Action {actGet, actSet } ;
Action m_action ;

// construtora private
// (poderá ser usada apenas na sua "friend" VetorFixo<>):
Exception( Action action ) : m_action ( action ) {}

public:
// redefine virtual DA BASE:
virtual const char* what() const throw()
// o "throw()" aqui - sem especificacão de tipo -
// adverte que esta função não dispara exceptions
{
switch ( m_action )
{
case actGet : return "VetorFixo::GET : indice incorreto" ;
case actSet : return "VetorFixo::SET : indice incorreto" ;
default:
return std::exception::what();
// algo inesperado aqui ...
}
}
};

// funções estáticas que redefinem os tratadores


// "default" para exceptions:
// se ocorrer um "throw" e ele não for capturado
// em um "try/catch",
// os tratadores serão estes:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


308 • AGIT informática • C++ • • Capítulo 12 ▪ Tratamento de exceções

static void excecaoDefault_getFunc ()


{
std::cout << "\nTERMINATE: VetorFixo::GET: indice incorreto :"
<< " (sem try/catch)\n" ;
MsgFinal();
exit(-1); // encerra aplicação
}
static void excecaoDefault_setFunc ()
{
std::cout << "\nTERMINATE: VetorFixo::SET: indice incorreto :"
<< " (sem try/catch)\n" ;
MsgFinal();
exit(-1); // encerra aplicação
}

public:
VetorFixo(){}

inline size_t Size () { return Dim ; }

// verificam índice:
inline const T & Get( size_t nIndice )
{
{
if ( nIndice >= Dim )
{
// redefine o tratador default
// (caso a exceção não seja capturada em um try/catch)
std::set_terminate( excecaoDefault_getFunc );
 // objeto de erro personalizado
Exception err ( Exception::actGet );
throw err;
}
return m_Vetor [ nIndice ];
}
}
inline void Set( size_t nIndice, const T & newValue )
{
if ( nIndice >= Dim )
{
// redefine o tratador default
// (caso a exceção não seja capturada em um try/catch)
std::set_terminate( excecaoDefault_setFunc );
 // objeto de erro personalizado
Exception err ( Exception::actSet );
throw err;
}
m_Vetor [ nIndice ] = newValue;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


12.3 • std::exception • C++ • AGIT informática • 309

// não verificam índice:


inline T & operator[ ] ( size_t nIndice )
{
return m_Vetor[ nIndice] ;
}
inline const T & operator[ ] ( size_t nIndice ) const
{
return m_Vetor[ nIndice] ;
}
};

void TrataExcecaoDefault ()
{
std::cout << "impossivel continuar" << std::endl;
exit(-1); // encerra aplicação
}

int main()
{
// esse "set_terminate" aqui é genérico demais...
// nunca saberemos onde ocorreu o problema.
std::set_terminate( TrataExcecaoDefault );

const int viDim = 5 ;


VetorFixo< int , viDim > vi;

[Link] ( 2, 1); // OK: acessa elemento alocado

try // provoca exception com a função "Get":


{
std::cout << [Link]( viDim ) << "\n"; // acessa elemento
// não alocado...
}
catch( std::exception & err )
{
std::cout << "Erro: " << [Link]() << "\n";
}
catch(...) // se o tipo do erro não for "std::exception"
// então vai cair aqui
{
std::cout << "algo desconhecido, talvez terrivel, ocorreu\n";
}

try // provoca exception com a função "Set":


{
[Link]( viDim, 10 ) ; // acessa elemento não alocado...
}
catch( std::exception & err )
{
std::cout << "Erro: " << [Link]() << "\n";
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


310 • AGIT informática • C++ • • Capítulo 12 ▪ Tratamento de exceções

// Acessa elemento não alocado sem "try/catch".


// O "terminate" sera usado:
int x = [Link]( viDim ) ;
std::cout << x << "\n";

MsgFinal();
return 0;
}

12.4 • Derivadas padrão de std::exception


Para o exemplo acima, teria sido mais simples usar uma derivada já existente na biblio-
teca. As derivadas já existentes permitem cobrir erros comuns (runtime, range,etc) e
podemos personalizar a mensagem que é devolvida pela função virtual what.
Caso houvesse mais coisas a personalizar, poderíamos criar uma derivada própria,
como no exemplo acima.
Mas para esse exemplo temos uma derivada padrão que se aplica perfeitamente:
range_error.

#include <iostream>
#include <string>
#include <stdexcept>

#include <cstdlib>

// emite uma mensagem final


void MsgFinal()
{
std::cout << "\n<enter> p/sair\n";
std::[Link]();
}

// tipo parametrizado valor constante


// \|/ \|/
template <typename T, size_t Dim> class VetorFixo
{
private:
T m_Vetor[ Dim ]; // vetor de "T" com dimensões fixas

// funções estáticas que redefinem os tratadores


// "default" para exceptions:
// se ocorrer um "throw" e ele não for capturado em um "try/catch",
// os tratadores serão estes:
static void excecaoDefault_getFunc ()
{
std::cout << "\nTERMINATE: VetorFixo::GET: indice incorreto :"
<< " (sem try/catch)\n" ;
MsgFinal();
exit(-1); // encerra aplicação
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


12.4 • Derivadas padrão de std::exception • C++ • AGIT informática • 311

static void excecaoDefault_setFunc ()


{
std::cout << "\nTERMINATE: VetorFixo::SET: indice incorreto :
<<" (sem try/catch)\n" ;
MsgFinal();
exit(-1); // encerra aplicação
}

public:
VetorFixo(){}

inline size_t Size () { return Dim ; }

// verificam índice:
inline const T & Get( size_t nIndice )
{
{
if ( nIndice >= Dim )
{
// redefine o tratador default
// (caso a exceção não seja capturada em um try/catch)
std::set_terminate( excecaoDefault_getFunc );
 // objeto de erro, com mensagem para "what":
std::range_error err("VetorFixo::GET - indice incorreto");
throw err;
}
return m_Vetor [ nIndice ];
}
}
inline void Set( size_t nIndice, const T & newValue )
{
if ( nIndice >= Dim )
{
// redefine o tratador default
// (caso a exceção não seja capturada em um try/catch)
std::set_terminate( excecaoDefault_setFunc );
 // objeto de erro, com mensagem para "what":
std::range_error err ("VetorFixo::SET - indice incorreto");
throw err;
}
m_Vetor [ nIndice ] = newValue;
}

// não verificam índice:


inline T & operator[ ] ( size_t nIndice )
{
return m_Vetor[ nIndice] ;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


312 • AGIT informática • C++ • • Capítulo 12 ▪ Tratamento de exceções

inline const T & operator[ ] ( size_t nIndice ) const


{
return m_Vetor[ nIndice] ;
}
};

int main()
{
const int viDim = 5 ;
VetorFixo< int , viDim > vi;

[Link] ( 2, 1); // OK: acessa elemento alocado

try // provoca exception com a função "Get":


{
std::cout << [Link]( viDim ) << "\n"; // acessa elemento
// não alocado...
}
catch( std::exception & err )
{
std::cout << "Erro: " << [Link]() << "\n";
}
catch(...) // se o tipo do erro não for "std::exception"
// então vai cair aqui
{
std::cout << "algo desconhecido, talvez terrivel, ocorreu\n";
}

try // provoca exception com a função "Set":


{
[Link]( viDim, 10 ) ; // acessa elemento não alocado...
}
catch( std::exception & err )
{
std::cout << "Erro: " << [Link]() << "\n";
}

// Acessa elemento não alocado sem "try/catch".


// O "terminate" sera usado:
int x = [Link]( viDim ) ;
std::cout << x << "\n";

MsgFinal();
return 0;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 13 ▪ Referência técnica • C++ • AGIT informática • 313

• Capítulo 13
▪ Referência técnica

13.1 • Tipos de dados.................................................................317


13.1.1 ▪ Os tipos da linguagem C++.............................................................317
13.1.2 ▪ Modificadores de tipos....................................................................319
[Link] ▪ Modificadores de sinal.................................................319
[Link] ▪ Modificadores de tamanho...........................................319
13.1.3 ▪ Conversões de tipos.......................................................................320
[Link] ▪ Conversões no estilo C ...............................................320
[Link] ▪ Conversões no estilo C++ ...........................................321
13.1.4 ▪ Criando sinônimos de tipos.............................................................323
13.1.5 ▪ Criando novos tipos de dados.........................................................324
[Link] ▪ Criando tipos através de enum....................................324
[Link].a ▪ Em C (mas não em C++) o uso dos símbolos de constantes
é facultativo..........................................................................................325
[Link].b ▪ Em C++ o uso dos símbolos de constantes é obrigatório.
326
[Link].c ▪ Denominação de um tipo criado com enum.....................326
[Link] ▪ Criando tipos através de struct ou class......................326
[Link].a ▪ Ligação dos membros à variável estruturada..................327
[Link].b ▪ Denominação de um tipo estruturado..............................328
13.1.6 ▪ Tipos aninhados..............................................................................328
[Link] ▪ Estruturas aninhadas...................................................329
[Link].a ▪ Tipos aninhados: em C eles são apenas indicativos.......330
[Link].b ▪ Tipos aninhados: C++ não permite o estilo C..................330
[Link].c ▪ Criação de tipos diretamente no retorno ou nos parâmetros
de uma função.....................................................................................330
[Link] ▪ Criando tipos através de union....................................330
[Link].a ▪ Regras para union, específicas de C++...........................332
13.2 • Conceituando identificadores, funções, escopos ...................333
13.2.1 ▪ E o que é, exatamente, um nome identificador ?............................333
13.2.2 ▪ Conceituando blocos de código .....................................................334
13.2.3 ▪ Declaração de variáveis nos escopos local e global......................336
[Link] ▪ Visibilidade...................................................................336
[Link] ▪ Tempo de vida.............................................................337
[Link] ▪ Outros recursos...........................................................338
13.2.4 ▪ Recursos de programação modular em C e C++............................338
[Link] ▪ Recursos da aplicação, recursos de módulo e recursos
de bloco. 338

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


314 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link] ▪ Classificação dos recursos (memórias e funções) quanto


à visibilidade................................................................................339
[Link].a ▪ Classificação de memórias..............................................339
[Link].b ▪ Classificação de funções.................................................339
[Link] ▪ Organizando uma aplicação por módulos. ..................339
[Link] ▪ Sintetizando a classificação quanto à visibilidade de va-
riáveis e funções..........................................................................340
[Link].a ▪ Escopo da Aplicação........................................................340
[Link].b ▪ Escopo do Módulo...........................................................340
[Link].c ▪ Escopo local (um bloco de código)..................................340
[Link] ▪ Classificação das variáveis quanto ao tempo de vida..341
[Link] ▪ Determinando visibilidade e tempo de vida na declaração
de variáveis..................................................................................341
[Link] ▪ As classes de armazenamento de memórias..............342
[Link].a ▪ Classe auto......................................................................342
[Link].b ▪ Classe static (obsoleta em C++)......................................342
[Link].c ▪ Classe global ou extern....................................................343
13.2.5 ▪ Inicialização e atribuição de variáveis.............................................344
13.3 • Operações e operadores....................................................345
13.3.1 ▪ Precauções no uso dos símbolos de operadores...........................346
13.3.2 ▪ Resultados de operações...............................................................347
[Link] ▪ Operações relacionais.................................................347
[Link] ▪ Operações lógicas.......................................................347
[Link] ▪ Operações aritméticas.................................................347
13.3.3 ▪ Posição dos operadores de incremento e decremento...................348
13.3.4 ▪ Operadores bit a bit.........................................................................349
[Link] ▪ AND.............................................................................349
[Link] ▪ OR...............................................................................349
[Link] ▪ NOT (ou complemento de 1).......................................350
[Link] ▪ XOR ( or exclusivo)......................................................350
[Link] ▪ Shift (ou deslocamento) à esquerda...........................350
[Link] ▪ Shift (ou deslocamento) à direita:................................351
[Link] ▪ Exemplos com operadores de bits...............................351
[Link].a ▪ Exemplo com and, or, e not.............................................351
[Link].b ▪ Exemplo com shift (à esquerda e à direita)......................354
[Link].c ▪ Exemplo com xor (1)........................................................355
[Link].d ▪ Exemplo com xor (2)........................................................357
13.4 • Controles do fluxo de processamento..................................360
13.4.1 ▪ Chamadas de função......................................................................360
[Link] ▪ Parâmetros para funções e retorno de funções...........360
[Link] ▪ Declarando parâmetros...............................................360
[Link] ▪ Formas de declaração.................................................360
[Link].a ▪ Forma de declaração válida em em C e C++..................360
[Link].b ▪ Forma de declaração inválida em C++............................360
[Link] ▪ Funções com quantidade fixa de parâmetros..............361
[Link] ▪ Funções com quantidade variável de parâmetros.......361
[Link].a ▪ Declarando os parâmetros desconhecidos......................362
[Link].b ▪ Exemplo de função com quantidade de parâmetros variá-
vel. 362

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 13 ▪ Referência técnica • C++ • AGIT informática • 315

[Link] ▪ Valor de retorno, tipo e protótipo de funções...............363


[Link].a ▪ Valor de retorno................................................................363
[Link].b ▪ Tipo de uma função..........................................................364
[Link].c ▪ Protótipo de funções........................................................364
[Link] ▪ Modos de passagem de parâmetros............................366
[Link].a ▪ 1 - Passando parâmetros por valor..................................366
[Link].b ▪ 2 - Passando parâmetros por endereço:..........................367
[Link].c ▪ 3 - Passando parâmetros por referência:.........................370
[Link] ▪ Há dois motivos para passar parâmetros por endereço
ou por referência..........................................................................371
[Link] ▪ Impedindo efeitos colaterais desnecessários..............372
13.4.2 ▪ Ponteiros para função.....................................................................373
[Link] ▪ Definindo os tipos dos ponteiros para função..............373
[Link] ▪ Ponteiros para função permitem estabelecer eventos. 374
[Link] ▪ Exemplo com ponteiros para função: a estrutura relatório.
376
13.4.3 ▪ Lógica estruturada através de objetos............................................379
13.4.4 ▪ Funções recursivas.........................................................................380
13.4.5 ▪ Controles de laço............................................................................381
[Link] ▪ O laço for.....................................................................382
[Link].a ▪ Forma geral e funcionamento. ........................................382
[Link].b ▪ Sintaxe.............................................................................383
[Link].c ▪ Cuidados a tomar com o laço for.....................................385
[Link] ▪ O laço “while”...............................................................386
[Link].a ▪ Forma geral e funcionamento..........................................386
[Link].b ▪ Sintaxe.............................................................................386
[Link].c ▪ Cuidados a tomar com o laço while.................................387
[Link] ▪ Considerações gerais sobre os laços for e while (tenha
cuidado com…)............................................................................389
[Link] ▪ O laço “do … while”.....................................................389
[Link].a ▪ Forma geral, funcionamento e sintaxe:............................389
13.4.6 ▪ Desvios por salto incondicional.......................................................392
[Link] ▪ Desvio return...............................................................392
[Link] ▪ Desvio break................................................................393
[Link] ▪ Desvio continue...........................................................394
[Link] ▪ Desvio goto. ................................................................397
[Link].a ▪ Sintaxe.............................................................................397
[Link].b ▪  1 - goto: usos não aceitáveis......................................398
[Link].c ▪  2 - goto: uso aceitável.................................................399
13.4.7 ▪ Funções inline.................................................................................400
13.4.8 ▪ Controles de decisão......................................................................401
[Link] ▪ Os controles de decisão if e else.................................401
[Link].a ▪ Sintaxe.............................................................................401
[Link].b ▪ Instruções associadas: ....................................................402
[Link].c ▪ Precauções no uso do controlador if................................403
[Link].d ▪ A precaução com o operador de atribuição também se apli-
ca aos laços. ......................................................................................406
[Link] ▪ O controle de decisão switch.......................................407
[Link].a ▪ Sintaxe.............................................................................407
[Link].b ▪ Funcionamento................................................................407

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


316 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link].c ▪ Exemplo de uso do switch...............................................409


13.5 • Ponteiros, Matrizes e Referências........................................409
13.5.1 ▪ Ponteiros.........................................................................................409
[Link] ▪ Como a memória é dividida (global, pilha e heap).......410
[Link] ▪ Capturando o endereço de uma variável já existente.. 410
[Link] ▪ Alocando livremente memória no heap .......................411
[Link] ▪ Acessando valores através de ponteiros. ...................411
[Link] ▪ Exercícios: demonstrando o funcionamento de ponteiros.
412
13.5.2 ▪ Vetores e Matrizes..........................................................................415
[Link] ▪ Onde alocar vetores: memória global, pilha ou heap ?416
[Link] ▪ Matrizes - usando múltiplas dimensões.......................418
[Link] ▪ Inicialização de vetores e matrizes..............................419
[Link] ▪ Vetores e Matrizes de estruturas.................................420
[Link].a ▪ Inicializando uma matriz de estruturas............................420
[Link] ▪ Vetores e Matrizes de caracteres................................421
[Link].a ▪ Inicializando uma matriz de caracteres............................422
[Link].b ▪ Entendendo matriz de caracteres como um endereço....423
[Link].c ▪ Exemplo de matriz de caracteres.....................................423

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Capítulo 13 ▪ Referência técnica • C++ • AGIT informática • 317

13.1 • Tipos de dados.


Existem linguagens em que é possível ocupar a memória sem antes declarar para que
vamos utilizá-la.
E existem outras (como é o caso de C e C++) em que é obrigatório que, antes de aces-
sarmos uma região de memória, solicitemos explicitamente uma reserva da quantidade
de memória necessária, indicando também o uso que será dado a esse pedaço da me -
mória.
E isso é feito indicando-se um tipo para a memória que deve ser reservada. Um tipo
deve identificar duas características para a memória que está sendo reservada:
 tamanho: um tipo sempre tem um tamanho fixo e conhecido em tempo de com-
pilação;
 forma de armazenamento: a forma de armazenamento também deve ser co-
nhecida em tempo de compilação. Um número inteiro, por exemplo, é armaze-
nado de um modo diferente do modo com que é armazenado um número real
que admite uma parte fracionária.
Dissemos que a linguagem exige o uso de tipos para a alocação de memórias. E é bom
ressaltar que isto nos proporciona os seguintes benefícios:
 segurança: se os tipos são conhecidos o compilador pode analisar se estamos
usando uma memória reservada de modo compatível com o tipo declarado em
sua criação. E caso o uso esteja incorreto o compilador emitirá mensagens de
erro, evitando assim que ocorram erros já em tempo de execução.
 eficiência (performance): se os tamanhos e modos de armazenamento são co-
nhecidos em tempo de compilação, o compilador pode gerar todo o código ne-
cessário para a alocação de memória.
 Pois, se essas características fossem conhecidas apenas em tempo de execu-
ção, só então o código necessário poderia ser gerado(o que na prática significa-
ria que o compilador deveria incluir, no aplicativo, um interpretador capaz de agir
nessas situações). E isto tornaria os programas mais lentos.

 Por isso mesmo, como veremos mais a frente, na linguagem C++, quando
é preciso alocar quantidades de memória que só serão conhecidas em tem-
po de execução, é o próprio programador quem deve criar o código neces-
sário para a alocação e liberação.
E assim será mantida a eficiência, pois não haverá necessidade de qual-
quer código de interpretação.

13.1.1 ▪ Os tipos da linguagem C++

Em primeiro lugar temos os tipos primitivos, que correspondem direta ou aproxima-


damente aos tipos suportados nativamente pela própria máquina. São os tipos numéri-
cos que podemos dividir em dois grandes grupos: números inteiros e números reais
(com ponto flutuante).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


318 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Em segundo lugar temos os tipos complexos oferecidos pela biblioteca padrão C++.
Não sendo suportados diretamente pela máquina, os tipos complexos são na verdade
rotinas que implementam um determinado algoritmo.
E, ao contrário de muitas linguagens onde os tipos complexos são fechados, em C++
eles são abertos (não são palavras reservadas), permitindo que o programador possa
criar extensões sempre que isso for necessário.
E, a partir dos tipos existentes, sempre poderemos criar novos tipos.
Vejamos primeiramente os tipos primitivos. Mais a frente, quando falarmos da bibliote-
ca padrão, veremos os demais.
 Os tipos básicos ou primitivos, alguns admitindo sinal são:
 inteiros:
 int - com e sem sinal;
 short - com e sem sinal;
 long - com e sem sinal;
 char - com e sem sinal;
 bool (admite os valores true e false) – não admite sinal.
 ponto flutuante (sempre com sinal):
 float ;
 double;
 long double.
 ausência de valor (o sinal não se aplica):
 void (vazio).
 endereçadores (números inteiros sem sinal, que representam um endereço de
memória):
 ponteiros;
 matrizes.

Anote:

 Cada tipo tem sua própria definição quanto ao tamanho máximo de memó-
ria que ele reservará para a variável [Link] isso é definido também
o intervalo de valores (o valor mínimo e o máximo) que poderá ser com-
portado na região reservada.
O tipo char, por exemplo, reserva um único byte.
Desse modo só poderá armazenar valores entre 0 (zero) e 255, ou entre –
128 e +127.
E anote também que a diferenciação entre tipos afeta não apenas as
quantidades de memórias ocupadas (tamanhos das variáveis) mas tam-
bém as formas de armazenamento, as quais podem implicar em diferen-
ças de performance.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.1 • Tipos de dados. • C++ • AGIT informática • 319

 Por exemplo: uma variável cujo tipo tem como objetivo armazenar números
inteiros pode ter o mesmo tamanho de uma outra cujo tipo admita ponto flu-
tuante.
Mas, mesmo que o tamanho seja idêntico, a variável inteira será acessada
mais rapidamente pois não necessitará de tratamento ao ponto flutuante.
Assim, só use double (ou float) quando for necessário armazenar a parte
fracionária de um número (as “casas decimais”).
Ou, em casos específicos, quando precisamos de um valor inteiro muito
grande (e que ultrapassa a capacidade de armazenamento dos tipos intei-
ros): neste caso só nos restaria usar um tipo com suporte a ponto flutuan-
te que admita valores maiores.

13.1.2 ▪ Modificadores de tipos.


Alguns dos tipos primitivos são formados por uma modificação de sinal ou de tama-
nho. Vejamos então os modificadores:
[Link] ▪ Modificadores de sinal.
Os tipos inteiros (exceto o bool) podem ser usados com ou sem sinal.
Os tipos com suporte a ponto flutuante são sempre sinalizados, não sendo assim pos-
sível utilizá-los sem o sinal.
Um inteiro com sinal e um inteiro sem sinal são tratados como tipos diferentes, pois
comportam intervalos de valores diferentes.
Para diferenciar o tipo com sinal do tipo sem sinal, usamos os modificadores signed e
unsigned:
 signed: o tipo terá sinal, abrangendo valores negativos e positivos dentro do
seu intervalo;
 unsigned: o tipo não terá sinal;

 O default para o modificador de sinal é signed


(ou seja: se não utilizamos explicitamente o modificador unsigned, nossos
dados serão sinalizados por default, comportando tanto valores positivos
como negativos).
Obs: o default para o tipo char depende da implementação (fabricante do
compilador); em geral, é usado signed como default.

[Link] ▪ Modificadores de tamanho.


O tipo int pode ser usado com os modificadores short (um int com tamanho menor) ou
long (um int com tamanho maior).
Dependendo da plataforma, contudo, como veremos, o int pode ter o mesmo tamanho
do short ou o mesmo tamanho do long.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


320 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Podemos declarar uma memória como short int ou simplesmente como short (inteira,
por default). E igualmente podemos declarar uma memória como long int ou simples-
mente como long (inteira, por default).
Um inteiro, um inteiro especificado como short, e um inteiro especificado como long
são tratados como tipos diferentes já que os intervalos de valores são diferentes.
Além disso, o long pode ser usado como um modificador para double, para obter o
tipo long double (um double com tamanho maior).
Contudo algumas plataformas não suportam o long double, e assim se declararmos
uma variável como long double na realidade será criado um double.
Na prática, os modificadores de tamanho implicam em um novo tipo, já que um tipo é
definido pela sua forma de armazenamento, mas também por seu tamanho.

 Veja a tabela de tipos localizada no “guia de consulta rápida”, página 486.

13.1.3 ▪ Conversões de tipos


[Link] ▪ Conversões no estilo C
Quando somamos um dado do tipo int e um dado do tipo char, ou um dado do tipo int e
um dado do tipo float, C++ converte o tipo menos preciso para o tipo mais preciso pre-
sente naquela operação.
 É uma conversão temporária, isto é, feita apenas para fins da operação.
 Desse modo na soma de um int com um float, o int (menos preciso) seria provi-
soriamente convertido pra float (mais preciso) de modo a viabilizar a operação
sem perda de precisão.

Contudo, pode ser necessária uma conversão explícita para fins de operação.
 Considere a seguinte situação:
 uma variável float deverá receber (atribuição) o resultado da soma de outro
float mais o resultado da divisão entre dois int’s. [ f1 = f2 + ( i1 / i2 ) ]
 neste caso, se a divisão entre as duas variáveis do tipo int resultar em um
número real (isto é contendo parte fracionária ou casas decimais), a precisão
será perdida, pois a parte fracionária é eliminada numa operação entre tipos
que não a comportam.
Exemplos:
 4/2 - o resultado é 2; não há perda de precisão;
 5/2 - o resultado é 2.5; há perda de precisão,

 como a parte fracionária é eliminada, o resultado será truncado para 2.

 O problema não teria ocorrido, logicamente, se todos os dados envolvidos na


operação fossem do tipo float.
 Contudo, é possível em C++ superar o problema de outro modo:

 através da conversão explícita para fins da operação.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.1 • Tipos de dados. • C++ • AGIT informática • 321

 Para isso utilizamos os moldes de tipo:


 um molde de tipo é o nome do tipo entre parênteses.
 Desse modo, na operação:
int i1, i2;
...............
float f2 = f + ( (float)i1 / i2 ) // admitido em C e C++
OU
f2 = f + ( float( i1 ) / i2 ) // apenas em C++

 Não há perda de precisão, pois o tipo do resultado da operação


assume o tipo do operando mais preciso (então, bastou elevar a
precisão do primeiro operando).

Exemplo:
int main( )
{
int A = 5 ;
char C = 2 ;
float F = 10.0 ;
int A2 ;
float F2 ;
A2 = A + C ;
cout << “int + char = “ << A2 << endl ; // resultado : 7
F2 = A + C + F ;
cout << “int + char + float = “ << F2 << endl ; // resultado: 17
F2 = F + ( A / C ); //  não usa conversão explícita
cout << “float + (int / char ) = “ << F2 << endl ;
// resultado (10 + 2.5) = 12;  houve perda de precisão
F2 = F + ( float( A ) / C ); //  aqui, usa conversão
cout << “float + (int / char ) = “ << F2 << endl ;
// resultado (10 + 2.5) = 12.5;  não houve
// perda de precisão
return 0;
}

Além disso, C++ oferece palavras reservadas especiais para casts mais
explícitos.

[Link] ▪ Conversões no estilo C++


Como já vimos acima, o C++ admite o estilo "C" de conversão entre tipos.
Por exemplo:
float x;
............
int a = (int)(x / 3);
Uma variação do cast tradicional do C, exemplificado acima, seria o cast no formato
função, admitido por C++:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


322 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

int a = int( x / 3);


Em muitos casos um cast pode representar um problema potencial.
Por exemplo:
int * pa = (int *) 1000; // "1000" é um endereço VÁLIDO?
Ou então:
const int * pi = ....... ;
....................
int * p = (int *)pi; // transformou um ponteiro const em não-const

Para dar mais segurança e legibilidade aos cast's o C++ introduziu alternativas especí-
ficas para cast, cada qual expressando o problema potencial daquele tipo de cast.
São elas:
 static_cast;
 dynamic_cast;
 reinterpret_cast;
 const_cast;
a. static_cast
O static_cast é usado para converter um ponteiro para uma classe-base para
um ponteiro para um classe derivada, ou vice-versa
Exemplo:
class Base {};
class Deriv : public Base {};
void Funcao ( Base * pB, Deriv * pD )
{
// 1) convertendo ponteiro a objeto-base para derivado:
Deriv * pDTemp = static_cast< Deriv * > (pB);
// 2) convertendo ponteiro ao objeto-derivado para base:
Base * pBTemp = static_cast < Base * > (pD);
}
Observar que no primeiro caso convertemos um ponteiro para a classe base sendo con -
vertido para um objeto derivado.
Se, contudo, isso não for verdadeiro (isto é, se o objeto for na verdade um objeto-base)
e tentarmos acessar membros derivados ocorrerá erro já que esses membros não esta-
rão alocados na memória.
Além disso o static_cast pode ser usado para as conversões de tipos básicos:
int i;
..............
char c = static_cast<char> (i);

b. dynamic cast

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.1 • Tipos de dados. • C++ • AGIT informática • 323

O static_cast feito no primeiro caso acima (conversão de ponteiro a derivada para base)
não é seguro.
Se ele fosse realmente necessário, poderíamos então usar dynamic_cast que permitiria
uma checagem em tempo real.
Deriv * pDTemp = dynamic_cast< Deriv * > (pB);
Caso pB não aponte para um objeto do tipo Deriv, teríamos um retorno nulo.
Lembrar contudo que este recurso tem um custo mais alto, pois a verificação estará
sendo feita durante a própria execução.
Assim, se quisermos usá-lo deveremos habilitar a opção do compilador para suporte a
"RTTI" (Run-Time Type Information).

c. reinterpret_cast
As conversões mais suspeitas devem ser feitas com reinterpret_cast, que pode ser usa-
do para converter um ponteiro de um tipo para qualquer outro e inclusive um inteiro
para um ponteiro:

char * pC;
...............
int * pI = reinterpret_cast < int * > (pC);
pI = reinterpret_cast < int *> (100);

d. const_cast

Geralmente, conversões de const para não-const são altamente suspeitas e devem ser
documentadas e facilmente localizáveis com o uso deste conversor.
const char * pC;
.......................
char * pC2 = const_cast < char *>(pC);

13.1.4 ▪ Criando sinônimos de tipos.

 O programador pode criar sinônimos para tipos de dados já existentes, através


da palavra reservada typedef.

Sintaxe.
typedef <tipo existente> <sinônimo para esse tipo> ;
Exemplos
.

typedef unsigned int UINT ;


Neste exemplo criamos o sinônimo “UINT” para o tipo “unsigned int”.
typedef unsigned short USHORT;
Neste exemplo criamos o sinônimo “USHORT” para o tipo “unsigned short”.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


324 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

13.1.5 ▪ Criando novos tipos de dados.


Criamos novos tipos através das seguintges palavras chaves:
 enum;
 struct, class ou union.

[Link] ▪ Criando tipos através de enum.


Tipos criados através de enum servem para predefinir uma lista de valores numéricos
inteiros e constantes, admitidos para atribuição em variáveis desse tipo.
Dessa forma uma variável declarada a partir de um tipo criado por enum só poderá re-
ceber um dos valores da lista.
Os valores não serão usados de forma bruta (o próprio número) mas sim através de
apelidos (símbolos para constantes), garantindo com isso também a boa legibilidade do
código.
A diretiva enum, portanto, tem um objetivo bem específico, limitando-se a associar um
tipo com uma lista de valores inteiros.
Sintaxe
enum [ <tipo> ] { <constante simbólica> [ =n ]
[ , <constante simbólica> [ =n ] , … ]
};
 Por default, a primeira constante vale zero e as demais assumem o valor da an-
terior mais um.
 Contudo podemos indicar explicitamente um valor inteiro qualquer, usando o si-
nal de igual.
Exemplo.
enum semana { segunda , terca , quarta , quinta, sexta, sabado,
domingo };
*/ Acima: foi criado o tipo semana, associado a uma lista de símbolos
constantes que obrigatoriamente deverá ser usada para atribuir
valores a variáveis desse tipo.
*/
// Por default, “segunda” é um apelido para zero,
// “terca” é um apelido para o número um, e assim sucessivamente
// (sempre o anterior mais um)

enum semanaAlterada { segunda = 1 ,


terca , quarta , quinta, sexta,
sabado = 7 , domingo
};
/* Já no exemplo acima, o símbolo constante “segunda” ao invés de zero
representa o número um. Em consequência:
terça vale 2 (o anterior mais um); quarta vale 3 (idem); quinta vale
4(idem) e sexta vale 5(sempre o anterior mais um). E o símbolo constante
“sabado” é explicitamente associado ao número 7,
quebrando a sequencia “default”. Em seguida, o símbolo “domingo”
não é associado explicitamente a um valor. Logo será o apelido do
número 8 (o anterior mais um).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.1 • Tipos de dados. • C++ • AGIT informática • 325

*/

enum { Falso , Verdadeiro } ;


/* Acima: foi criada apenas uma lista de constantes, sem um tipo. Ela poderá
ser usada para atribuir valores a uma variável declarada a partir de um
tipo inteiro,
*/
// Como aqui também foi usado o default, “Falso” é um apelido para o
// número zero, e “Verdadeiro” é um apelido para o número um.
int main ( )
{
semana Passada ; // usando o tipo semana
// criado acima através de enum;
Passada = quarta ; // atribuindo um dos valores possíveis,
// definidos pela lista de constantes associada ao tipo.
int Var = quinta ; // Correto: também é possível
// que um tipo inteiro
// primitivo utilize uma das constantes
// da lista associada ao tipo semana
int Flag = Falso; // Já neste caso usamos apenas
// a constante “Falso”,
// para atribuir um valor a um tipo inteiro primitivo.
…………………….
return 0;
}

Nos exemplos acima, vemos dois modos de utilizar o enum:


1) criando um tipo (no exemplo, é o tipo semana), e em seguida definindo uma lista
de constantes que deverão ser usadas para atribuir um valor a uma variável desse
tipo.
2) não definindo um tipo e sim apenas uma lista de constantes.
Os dois modos de uso são admitidos, permitindo uma alternativa melhor que o “#defi-
ne” para criar constantes simbólicas (ou apelidos para constantes).
Em sessões de debug, as constantes criadas através de enum são visíveis. Ao passo que
aquelas criadas através do “#define” já foram trocadas pelo número que representam, e
com isso não poderemos mais visualizá-las.
Além disso constantes criadas com enum podem ser membras de estruturas (o que não
acontece com aquelas criadas pela diretiva de preprocessamento “#define”).

[Link].a ▪ Em C (mas não em C++) o uso dos símbolos de constantes é


facultativo.
Em C podemos fazer:
…………..
semana Proxima ;
Proxima = 10;
…………..

Isto é: em C, simplesmente podemos desrespeitar a lista de constantes, até mesmo


usando um valor que não corresponde a nenhum dos valores da lista (o número 10,
REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional
326 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

neste caso, é maior que “domingo” que representa o número 6, e é o maior valor da lis-
ta).

[Link].b ▪ Em C++ o uso dos símbolos de constantes é obrigatório.


Felizmente, C++ não manteve a compatibilidade com C neste aspecto, e assim não
podemos fazer:
semana Proxima ;
Proxima = 1 ; // Erro de compilação, mesmo considerando
// que o número 1 é um dos valores da lista.
// Devemos assim usar o símbolo “terca”.
Desse modo, em C++ devemos usar os símbolos, sempre que uma variável for declara-
da pelo tipo criado com enum:
semana Proxima ;
Proxima = terca ; // Uso correto.

[Link].c ▪ Denominação de um tipo criado com enum.


Em C++, o nome de um tipo criado através de enum é diretamente o nome designado
pelo programador.
Assim em
enum semana { … } ;
O nome do tipo é semana. Desse modo declaramos variáveis como abaixo:
semana Variavel ;
Além disso, como C considera a própria palavra reservada enum como parte do nome
do tipo, em C++ é admitido também (por compatibilidade) que em declarações de va-
riáveis seja usado o nome designado pelo programador, precedido da palavra reservada
“enum”, conforme exemplificado abaixo:
enum semana Variavel ; // em C, mas não em C++,
// isto seria obrigatório.
Por isso, em C, é muito comum usar sinônimos:
typedef enum semana Semana; // desnecessário em C++
Semana Variavel;

[Link] ▪ Criando tipos através de struct ou class.


As diretivas struct e class cumprem um papel muito mais amplo do que o enum já
que, através delas, podemos criar conjuntos de dados (de quaisquer tipos já existentes)
e funções.
Como já vimos, os dados podem ser protegidos através de uma restrição de acesso que
os torna privativos da estrutura (isto é, só poderão ser acessados nas funções declaradas
no interior da própria estrutura. Para isso, usa-se a palavra reservada private.
Em contrapartida, podemos declarar membros (dados ou funções) sem restrição de
acesso, classificando-os como públicos. Para isso usa-se a palavra reservada public.
Apenas as funções membro de uma struct/class (e como veremos, as suas “amigas”)
podem acessar os membros private.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.1 • Tipos de dados. • C++ • AGIT informática • 327

Aqui, o que é necessário registrar é que, além dos tipos primitivos oferecidos pela lin-
guagem, e dos tipos simples que o programador pode criar com enum, a linguagem
permite também a criação de tipos complexos, isto é que permitem reaproveitar e re-
combinar todos os tipos criados anteriormente.
Por exemplo, mais acima criamos o tipo “Data”, reaproveitando, em uma combinação
específica, os tipos primitivos char, short e bool.

 E, agora, podemos reaproveitar o próprio tipo “Data”, em um novo tipo,


ainda mais complexo:

class Contas
{
public:
enum TipoConta { Receita , Despesa };
private:
double m_dblValor ;
TipoConta m_TipoConta ;
Data m_dtEntrada ;
Data m_dtVencimento ;
……………………………….
public:
……………………………….
};

O tipo “Contas”, reaproveita os tipos primitivos double e char.


Também cria e agrega, através de enum, um novo tipo simples (“TipoConta”), para
melhor expressar a natureza de um valor informado.
E reaproveita o tipo estruturado “Data”, agregando-o duas vezes ao conjunto de da-
dos (data de entrada e data de vencimento).
[Link].a ▪ Ligação dos membros à variável estruturada.
A declaração de tipo “struct data”, feita acima, criou um novo tipo de dados; mas
atenção: nenhuma variável desse tipo foi criada ainda.
 A declaração struct tem o mesmo significado da operação de criar a estru-
tura de um arquivo: já criamos a estrutura mas o arquivo ainda está vazio.
Quando, depois, viermos a incluir um registro, teremos então espaços de
memória (ou seja, campos) compatíveis com a estrutura, que então pode-
rão receber valores.
Para criarmos uma variável do tipo Data (que então poderá receber valores em seus
respectivos campos) basta declará-la normalmente a partir do seu tipo. E em seguida
podemos acessar os seus campos através do operador de ligação de membro, simbo-
lizado por um ponto (.).
Data DiaDeHoje ; /* DiaDeHoje é uma variável do tipo Data ;
como a variável já foi criada, podemos agora preencher os
seus campos com valores: */

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


328 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link] = 5; // usa o operador de ligação de membro


[Link] = 2;
[Link] = 1997;

 O operador de ligação de membro (.) permite que acessemos cada um dos


campos de um tipo de dados estruturado.
Podemos ler a expressão “[Link]” como:
“Ano” que pertence a DiaDeHoje; ou:
“Ano” que é membro de DiaDeHoje; ou:
o campo “Ano” da estrutura DiaDeHoje; ou:
o atributo ”Ano” do objeto DiaDeHoje.

[Link].b ▪ Denominação de um tipo estruturado.


Em C++, o nome de um tipo criado através de struct ou class é diretamente o nome
designado pelo programador.
Assim em
struct Data { … } ;
class Contas { … } ;
O nome do primeiro tipo é Data. E o nome do segundo é Contas. Desse modo decla-
ramos variáveis como abaixo:
Data Hoje ; // criando a variável Hoje do tipo Data
Contas PagarJa ; // criando a variável PagarJa do tipo Contas

Mas temos aqui também a situação já vista acima a respeito de enum: como C consi-
dera a própria palavra reservada struct como parte do nome do tipo, em C++ é admiti-
do também (por compatibilidade) que em declarações de variáveis seja usado o nome
designado pelo programador, precedido das palavras reservadas “struct” (se o tipo foi
criado com struct) ou “class” (se foi criado com class), conforme exemplificado abai-
xo:
struct Data Hoje ; // criando a variável Hoje do tipo Data
class Contas PagarJa ; // criando a variável PagarJa do tipo Contas
// admitido mas desnecessário em C++

13.1.6 ▪ Tipos aninhados.


Quando escrevemos uma estrutura como “Data”, sabemos que ela será reaproveitada
em um grande número de outras estruturas.
Ou seja: esse tipo não foi criado para servir apenas à estrutura “Contas”, mas também
a muitas outras que serão oportunamente criadas.
Mas às vezes podemos precisar de um tipo auxiliar que só será usado em uma única
estrutura.
Observe que isso já foi feito acima , na estrutura “Contas”, quando foi criado o tipo
“TipoConta” através de enum.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.1 • Tipos de dados. • C++ • AGIT informática • 329

Caso esse tipo de dado fosse usado também em outras estruturas, então ele deveria ter
sido criado fora da estrutura “Contas”, para ser aproveitado nas diversas estruturas que
dele necessitassem.
Como não era esse o caso (já que só seria usado na própria estrutura “Contas”), então
ele foi criado aninhado na estrutura “Contas”.
Assim, ele é um tipo interno à própria estrutura. Isto significa que não só a variável
m_TipoConta é um membro de “Contas”. O próprio tipo (“TipoConta”) pertence à
estrutura.
Assim, se fossemos usar esse tipo em qualquer outro lugar que não a estrutura “Con-
tas”, teríamos que respeitar essa realidade, da seguinte maneira:
Contas::TipoConta Variavel ; // usando fora da classe
Variavel = Contas::Receita ; // idem

[Link] ▪ Estruturas aninhadas.


Conforme exposto acima, através de enum, podemos criar um tipo aninhado em uma
estrutura. E, do mesmo modo, também podemos criar um tipo aninhado utilizando
struct ou class. Istoé: podemos criar uma estrutura aninhada em outra.
Fazemos isso sempre que precisamos de um tipo estruturado auxiliar para uma estrutu-
ra maior. E esse tipo não será reaproveitado em outras estruturas.
Exemplo:
struct Contas
{
………………….
private:
struct ImpostoSimples // tipo criado com restrição de acesso private
{
public:
double m_dblPercentual ;
Data m_dtPagamento ;
……………………………….
};
public:
struct ImpostoPorFaixas // tipo criado sem restrições de acesso
{
public:
double m_dblPercentual ;
double m_dblValorMinimo ;
double m_dblValorMaximo ;
……………………………….
};

// cria dois membros de dados a partir do tipo interno


ImpostoSimples m_isISS ;
ImpostoSimples m_isINSS ;
……………………………………..
};

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


330 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Um dos tipos auxiliares internos (ImpostoSimples) foi criado com restrição de acesso
private. Então não poderá ser usado fora da classe.
Já o tipo ImpostoPorFaixas é público (public) e poderia ser usado da seguinte forma:
Contas::ImpostoPorFaixas Variavel ; // usando fora da classe
Isto é: o nome do tipo é Contas::ImpostoPorFaixas e não ImpostoPorFaixas, justa-
mente porque é um tipo aninhado, logo pertence ao ambiente de nomes da estrutura
mais externa.

[Link].a ▪ Tipos aninhados: em C eles são apenas indicativos.


Em C, a declaração de uma variável de um tipo aninhado seria:
ImpostoPorFaixas Variavel ;
Desse modo, o tipo ImpostoPorFaixas, embora declarado dentro do tipo Contas, não
pertence a Contas.

[Link].b ▪ Tipos aninhados: C++ não permite o estilo C.


Um outro ponto em que C++ não é compatível com C é esse.
Em C++, como já vimos acima, isso não é permitido, e o programador terá que alterar
esse código, caso escrito anteriormente para compiladores C, para que seja compatível
com a única escrita admitida em C++:
Contas::ImpostoPorFaixas Variavel ;

 Em C++, portanto só é possível a utilização da forma indicada acima, a qual


implementa a lógica do encapsulamento: ImpostoPorFaixa é um tipo que
pertence ao tipo Contas.

[Link].c ▪ Criação de tipos diretamente no retorno ou nos parâmetros


de uma função.
Em C podemos criar um tipo diretamente no retorno ou na declaração de parâmetros de
uma função:
struct Area { int Largura ; int Altura; } Calcula();
/* A função Calcula retorna um valor do tipo “Area” declarado
diretamente na especificação do tipo de retorno da função */
Calcula( struct Area { int Largura ; int Altura; } AreaACalcular );
/* A função Calcula recebe o parâmetro “AreaACalcular” cujo tipo
(“Area”) é declarado diretamente na especificação do tipo do parâmetro
da função */

Em C++ isso não é permitido. Assim, qualquer código herdado do C que contenha
esse tipo de escrita deverá ser reescrito. Observar que alguns compiladores ainda per -
mitem essa escrita, mas ela não é padrão.

[Link] ▪ Criando tipos através de union.


Uniões funcionam como estruturas polimórficas.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.1 • Tipos de dados. • C++ • AGIT informática • 331

Isto significa que uma união pode abrigar diferentes tipos mas apenas um apenas um
poderá ser usado a cada vez.
Isto é, o tamanho de uma union é o tamanho do maior tipo declarado em seu interior.
Os campos de tamanho menor, quando usados, simplesmente não preenchem todo o es-
paço reservado, deixando uma área de memória vazia, sem uso.
Precisamos sempre saber qual é o campo da union que está em uso no momento para
não recuperar informações incorretas.
Exemplo:
#include <iostream>
using namespace std;
#define VT_DOUBLE 1
#define VT_BOOL 2
#define VT_LONG 3
#define VT_CHAR 4
struct VariosTipos
{
int TipoEmUso;
union
{
double dblVal;
bool boolVal;
long longVal;
char charVal;
};
void ImprimeVariosTipos( );
};
void VariosTipos::ImprimeVariosTipos( )
{
switch ( TipoEmUso )
{
case VT_DOUBLE:
cout << dblVal << endl; return;
case VT_BOOL:
cout << boolVal << endl; return;
case VT_LONG:
cout << longVal << endl; return;
case VT_CHAR:
cout << charVal << endl; return;
default:
cout << "ERRO" << endl;
}
}
int main()
{
VariosTipos vrt1, vrt2;
[Link] = VT_DOUBLE;
[Link] = 20.5;
[Link] ();

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


332 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link] = VT_LONG;
[Link] = 1050L;
[Link] ();
[Link] = 3150L;
[Link] ();
return 0;
}
/*
RESULTADO:
20.5
1050
ERRO
*/

A melhor maneira de utilizar uniões é definindo, fora da union, uma variável indicado-
ra para descobrir qual é o tipo que está sendo efetivamente usado.
No exemplo acima, isso foi feito aninhando a union em uma struct e incluindo um
membro de dados na struct para indicar qual é o tipo atualmente em uso na union.
Em C, nas situações onde é preciso trabalhar com uma variedade alternativa de tipos, a
union é quase sempre a primeira possibilidade a ser considerada (uma outra saída para
o problema seriam os ponteiros void).
Já em C++, antes de lançar mão de uma union, devemos primeiro analisar se não po-
deria ser empregado um template(que é justamente o assunto do próximo capítulo).
E apenas em caso negativo é que devemos usar a union.

[Link].a ▪ Regras para union, específicas de C++.


Em uma union C++, aplicam-se as seguintes permissões:
 a union pode especificar diferentes acessos (public, private, ...), sendo que o
acesso default é public;
 pode conter tanto membros de dados como funções-membro;
 a afirmação acima aplica-se, inclusive, a construtoras e destrutoras: uma union
C++ pode conter construtoras e destrutora;
E, em uma union C++, aplicam-se também as seguintes restrições:
 a union não pode conter funções virtuais ou membros estáticos
(veremos mais a frente o que são membros estáticos e funções virtuais);
 uma union não pode ser usada para fins de herança, pois não pode ser usada
como base ou como derivada de uma base
(veremos mais a frente o que é herança, o que é uma classe base e o que é
uma classe derivada).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.2 • Conceituando identificadores, funções, escopos • C++ • AGIT informática • 333

13.2 • Conceituando identificadores, funções, esco-


pos
Já trabalhamos com variáveis e funções. Vimos que uma variável ou uma função de-
vem em primeiro lugar ter um nome (isto é, um identificador).

Agora vamos detalhar o assunto.

13.2.1 ▪ E o que é, exatamente, um nome identificador ?


Quando precisamos de uma memória, declaramos uma variável. A declaração deve
conter sempre o tipo e um identificador (o nome), o qual nada mais é do que um ape-
lido (um mnemônico) para um determinado endereço de memória.
No caso de um identificador de função, temos uma situação [Link] um
programa é chamado na linha de comando, o sistema operacional toma três providênci-
as básicas:
a. lê o programa;
 um programa deve estar armazenado, em disco ou outro dispositivo, como um
arquivo;
b. escreve o programa na memória RAM;
 portanto as instruções do programa serão copiadas de disco para memória e,
portanto, ocuparão posições na memória;
 assim sendo, essas instruções podem ser acessadas a partir do seu respectivo
endereço de memória;
c. coloca o programa em execução;
 para isso a aplicação deve ter um segmento de instruções que o sistema opera-
cional entenda como o “início do programa”;
 é esse segmento que será posto em marcha pelo sistema operacional;
 a partir daí caberá ao programa estabelecer um fluxo de processamento (ou
aguardar decisões do usuário).
Assim, usamos um nome para poder acessar uma determinada função e executá-la..

 O nome na verdade é uma representação mnemônica (um apelido fácil de


lembrar) do endereço da região da memória onde foi armazenado o código
da função no momento em que ele foi carregado.
Isto é: o código da função está armazenado em algum lugar da memória e
o nome é um apelido para o endereço inicial dessa região ocupada pela
função.

 Isso pode parecer óbvio. Mas é importante registrar, porque em C++ pode-
mos manipular de modo direto esses endereços.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


334 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Assim, quando chamamos uma função (invocando o seu nome) estaremos determinan-
do um desvio no fluxo de processamento para aquela posição de memória.
Isso significa que agora serão processadas as instruções contidas naquela função.
E, uma vez finalizado o processamento desse bloco de código endereçado (início) e de -
limitado (fim) pela função, o processamento deverá retornar ao ponto imediatamente
seguinte à chamada da função, chamada essa que provocou o desvio.
Esse fluxo de processamento pode ser visualizado na figura abaixo:

funcao_1 ( )
{
instrução … ;
funcao_2 ( ) ;
/* chama função_2, desviando o fluxo de processamento
instrução … ; para o bloco de instruções cuja posição inicial recebe
} esse nome; quando concluir, retorna ao ponto seguinte a
funcao_2 ( ) sua chamada – para então executar a próxima instrução
{ ou operação pendente. */
instrução … ;
}

Já sabemos que o ponto de entrada de nossa aplicação é a função main.


E isto significa que a função main é chamada pelo sistema operacional e, quando en-
cerrada sua execução retorna para quem a chamou (o próprio S.O.), encerrando a apli-
cação.
Enfim: funções são portanto blocos de código que permitem conduzir o fluxo de pro-
cessamento de modo organizado.
O sistema operacional chama main; e ela deve retornar para o sistema operacional;
dentro de main, outras funções podem ser chamadas; cada função chamada deve retor-
nar ao ponto imediatamente seguinte à própria chamada (o que significa a próxima li-
nha de instrução ou a próxima operação pendente).

13.2.2 ▪ Conceituando blocos de código


 Uma função é assim um bloco de código endereçável.
 E, dentro de uma função, podemos ter blocos de códigos menores.
 Isto é: não necessariamente qualquer bloco de código é uma função. Um bloco
de código representa um local (um escopo) para alocação de memórias e es-
crita de instruções.
 Blocos de código internos a uma função não podem ser endereçados através de
um nome (isto é: eles não podem ser funções; isso significa que não podemos
escrever uma função dentro de outra).
E como podemos escrever blocos de código dentro de uma função? Simplesmente
abrindo e fechando chaves:
i

int main( )

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.2 • Conceituando identificadores, funções, escopos • C++ • AGIT informática • 335

{
…………… // instruções…
{ // bloco interno; este bloco não pode ter um nome
// (ou seja: não pode ser uma função)
…………. // instruções do bloco interno;
}
…………… // instruções…
return 0;
}

E porque motivos precisaríamos de um bloco interno? Um bloco interno pode ser usa -
do basicamente em duas situações:
a. Quando queremos associar mais do que uma instrução a um controle de
fluxo de processamento. Exemplo:
int x ;
if ( x > 5 ) // se “x” for maior que 5 será executada
// apenas a próxima instrução
cout << “x maior que 5” << endl;

 No exemplo acima, poderíamos usar as chaves delimitadoras de bloco para


envolver a instrução iniciada por cout (que será executada se “x” for maior que
5). Contudo, como se trata de uma única instrução, isso não é necessário.

 Já no exemplo abaixo, como teremos mais do que uma instrução associadas à


solução verdadeira do teste de condição “(x > 5)”, então o bloco é obrigatório.

if ( x > 5 )
{
cout << “x maior que 5” << endl;
……………………………………………….. // outras instruções
}
 E o mesmo se aplica a outros controles de fluxo de processamento, como o laço
for, por exemplo.

b. Quando queremos que o acesso a uma determinada variável seja feito por apenas
algumas instruções (e não por toda uma função) também podemos usar um bloco
interno. Vejamos abaixo um exemplo de um bloco com variáveis de abrangência lo-
calizada e privativa:
main( )
{
int NUMERO_1 = 1 ;
// a única variável que existe neste ponto
// é “NUMERO_1” declarada acima
{
int NUMERO_1 ; /* ATENÇÃO: privativa deste bloco
apesar de ter o mesmo nome da variável
“NUMERO_1” do bloco principal de main,
esta variável declarada no bloco interno
representa outro endereço de memória. */
int NUMERO_2 ; /* também privativa deste bloco */

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


336 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

…………………….
} /* fim do bloco: aqui são liberadas
// as memórias declaradas dentro do
bloco interno e apelidadas de”NUMERO_1” e “NUMERO_2”
a única variável que existe neste ponto é “NUMERO_1” declarada
no topo do bloco principal de main
*/
……………………………..
} // fim de main() : aqui é liberada a variável “NUMERO_1”, declarada
// no topo do bloco principal de main

No caso da variável “Numero_1”, declarada no bloco interno ocorrem duas coisas:


 ela só pode ser acessada nas instruções escritas dentro desse bloco;
 essa variável deixa de existir (a memória reservada para ela é liberada) no
fim do bloco em que foi declarada (e não no fim de main).
Assim, dentro do bloco interno, se fizermos:
NUMERO_1= 10;

 Estaremos acessando a variável declarada no bloco interno e não


aquela de mesmo nome declarada no topo do bloco principal de main.

 Isto significa que pelo fato de ter o mesmo nome, a variável do bloco interno es-
condeu a variável declarada no topo de main, impedindo assim que ela possa
ser acessada dentro do bloco interno.
 Se não fosse por isso, ela poderia ser acessada normalmente, pois um bloco in-
terno tem acesso garantido às variáveis de blocos externos, exceto nos casos
em que ocorre uma coincidência de nomes.
Mais a frente veremos também como definir o tempo de vida de uma variável.

Regra básica quanto ao momento da declaração de uma variável declarada dentro de


um bloco:
 Em C, a declaração de variáveis deve iniciar a escrita de um bloco de
código, não podendo estar após qualquer outra linha de instrução
desse bloco.
 Em C++, a declaração de variáveis pode estar após linhas de instru-
ção; contudo deve sempre preceder a primeira linha de instrução que
utilizar aquela variável.

13.2.3 ▪ Declaração de variáveis nos escopos local e global.


[Link] ▪ Visibilidade.
Vimos acima que quando declaramos uma variável dentro de um bloco ela pertence ao
escopo desse bloco, o que significa que só existe para esse bloco e só pode ser acessa-
da dentro dele.
Dizemos então que variáveis declaradas dentro de um bloco têm visibilidade local (já
que pertencem ao escopo local).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.2 • Conceituando identificadores, funções, escopos • C++ • AGIT informática • 337

E se declararmos uma variável fora de qualquer bloco?


Neste caso, como não pertence a qualquer escopo local, a variável passa a pertencer ao
escopo global.
Isto significa que a variável poderá ser acessada por qualquer função ou bloco de uma
aplicação, já que sua visibilidade é global.
Exemplo:
int VariavelGlobal = 0; // declara uma variável no escopo global
// (fora de qualquer bloco).
void funcao_1( )
{
int VariavelLocal = 0; // escopo local: só existe para este bloco.
...............................
VariavelGlobal = 1; // acessa a variável do escopo global
...............................
}
void funcao_2( )
{
int VariavelLocal = 0; // escopo local: só existe para este bloco.
...............................
VariavelGlobal = 2; // acessa a variável do escopo global
...............................
}

No exemplo acima temos duas variáveis declaradas em escopos locais. Cada uma de-
las, embora usando o mesmo nome (“VariavelLocal”), é o apelido de um endereço de
memória próprio.
Ou seja:
 “VariavelLocal” pertencente à “Funcao_1” ocupa uma determinada posição de
memória;
 e “VariavelLocal” pertencente à “Funcao_2” ocupa uma outra posição de me-
mória;
Já “VariavelGlobal” pertence ao escopo global. Ela é “enxergada” tanto por
“Funcao_1” como por “Funcao_2”, sendo uma memória compartilhada por toda a
aplicação. Sua visibilidade é global.

[Link] ▪ Tempo de vida.


 Uma variável declarada dentro de um bloco tem, por default, um tempo de vida
local.
 Uma variável declarada fora de blocos tem, por default, um tempo de vida glo-
bal.
Isso significa que uma variável declarada em um escopo local, nasce quando sua de-
claração é executada (o que significa que aquela função está sendo executada) e é libe-
rada quando a função retorna, ou quando o bloco é encerrado.
Já uma variável do escopo global, como poderá ser acessada por qualquer função da
aplicação, precisa existir (pela lógica) antes que qualquer função esteja sendo execu-
tada. E só poderá ser liberada depois que todo o processamento for encerrado.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


338 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Como o ponto de entrada da aplicação é a função main (responsável em primeira ins-


tância por iniciar e finalizar o processamento), isto significa que as variáveis do escopo
global precisam nascer antes que main tenha início e só poderão ser liberadas após o
retorno de main.
Logo, seu tempo de vida é global já que ela existirá enquando a aplicação estiver em
execução.

[Link] ▪ Outros recursos.


Podemos restringir a visibilidade de uma variável declarada no escopo global ao mó-
dulo em que foi declarada.
E podemos fazer com que uma variável declarada no escopo local tenha tempo de vida
global (não sendo liberada quando a função retorna ou o bloco é encerrado).
Contudo, estes dois recursos são obsoletos em C++. Nós os veremos no último capítu-
lo desta apostila.

13.2.4 ▪ Recursos de programação modular em C e C++


Neste capítulo, aprofundaremos o tema “escopos”: escopo global, escopo do módulo e
escopo local. Com destaque para o “escopo de módulo”, que ainda não foi tratado.
Esse é um recurso de programação modular, em sua implementação na linguagem C.
 Os recursos de programação modular, contudo, são oficialmente obsole-
tos em C++, pois dispomoos do encapsulamento através de tipos (estrutu-
ras ou classes).
Fazem parte da linguagem por compatibilidade, já que, em C, esse era um
importante recurso, e muito utilizado.

Mas, aproveitaremos este capítulo para reforçar a conceituação geral de escopos.

[Link] ▪ Recursos da aplicação, recursos de módulo e recursos


de bloco.
Vamos revisitar o conceito de módulo. Dissemos que um módulo (em C ou C++) é um
conjunto de funções escritas em um mesmo arquivo texto.
Além disso dissemos também que nas duas linguagens um módulo pode ser mais do
que simplesmente um repositório para funções.
 Isto porque podemos ter memórias (variáveis e constantes) do módulo e
funções internas do módulo.
 C e C++ usam os conceitos “modular” e “estruturado em blocos” para definir a
visibilidade de variáveis. Dizemos que uma variável ou constante é “visível”,
quando ela pode ser acessada para leitura(variáveis e constantes) ou
gravação(constantes) em um determinado local (ou seja, um bloco).
 Em C++, conforme já vimos, temos também restrições de acesso impostas pe-
las regras de estrutura, e este é seu principal recurso.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.2 • Conceituando identificadores, funções, escopos • C++ • AGIT informática • 339

E é justamente em função da divisão da aplicação em módulos e blocos que regiões


de memória serão classificadas quanto à visibilidade. E também podemos ocultar fun-
ções dentro de módulos.

[Link] ▪ Classificação dos recursos (memórias e funções)


quanto à visibilidade.
[Link].a ▪ Classificação de memórias.
Variáveis e constantes serão classificadas como:
 privativas de um bloco (variáveis declaradas dentro de um bloco, só podem
ser acessadas nele);
 privativas de um módulo (compartilhadas pelo conjunto de blocos do módulo);
 públicas ou globais (regiões de memória compartilhadas por todos os blocos
de uma aplicação, sem restrição de módulo).

 E essa é a maneira básica, em C, para “esconder” informações:


se forem privativas de apenas um ou de apenas alguns blocos, só poderão
ser acessadas (tanto para leitura como para gravação) nos blocos capazes
de enxergá-las.

[Link].b ▪ Classificação de funções.


As funções serão classificadas como:
 privativas de um módulo (só podem ser chamadas dentro de outras funções
do mesmo módulo);
 públicas ou globais (podem ser chamadas dentro de qualquer função de uma
aplicação, sem restrição de módulo).

[Link] ▪ Organizando uma aplicação por módulos.


Se não pudéssemos organizar a aplicação em classes, e sim apenas em módulos
( linguagem C), deveríamos fazer o seguinte:
 Em primeiro lugar dividimos nossa aplicação em módulos.
 Agora escrevemos funções (blocos de código com um nome);
 Dentro de qualquer bloco podemos reservar memória, através da declaração
de variáveis e constantes.

 Se declararmos uma variável ou constante(reservando memória) dentro de


um bloco, essa memória só poderá ser acessada dentro desse bloco e
somente nele.
Contudo, se quisermos que uma posição de memória seja acessada por
várias funções deveremos então declará-la fora de qualquer função.
Podemos então ter variáveis e constantes do módulo ou variáveis da
aplicação, caso a memória seja alocada fora de blocos.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


340 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link] ▪ Sintetizando a classificação quanto à visibilidade de va-


riáveis e funções.
[Link].a ▪ Escopo da Aplicação.
 Se quisermos que uma variável seja global (ou pública), podendo ser compar-
tilhada por toda a aplicação devemos simplesmente declará-la fora de qual-
quer função:
int variavel_1;
 variavel_1, declarada fora de qualquer função, poderá ser acessada por to-
das as funções da aplicação.
 E se quisermos que uma função seja global, podendo ser chamada em qual-
quer ponto da aplicação, basta declará-la, não sendo necessário tomar qualquer
providência especial:
void funcao_1( );
 funcao_1 pode ser acessada por qualquer função da aplicação.

[Link].b ▪ Escopo do Módulo.


 Se quisermos que uma variável seja privativa de um módulo, devemos de-
clará-la, fora de qualquer função, classificando-a como static:
static int variavel_2;
 (variavel_2, declarada fora de qualquer função, poderá ser acessada por
todas as funções do módulo escritas abaixo de sua declaração e somente
por elas, devido à restrição modular imposta pela classificação static).
 E se quisermos que uma funçao seja privativa de um módulo, devemos de-
clará-la classificando-a como static:
static void funcao_2( );
 (funcao_2 só pode ser acessada por outras funções do seu módulo, devido
à restrição modular imposta pela classificação static).

[Link].c ▪ Escopo local (um bloco de código).


 Se quisermos que uma variável seja privativa de um bloco, devemos de-
clará-la dentro desse bloco:
{
int variavel_3;
}

 “variavel_3”, só poderá ser acessada no interior do bloco em que foi


declarada.

 E, quanto a funções, não podemos ter uma função “privativa de um


bloco” pelo simples motivo de que não podemos implementar uma fun-
ção dentro de outra.

 Lembre que:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.2 • Conceituando identificadores, funções, escopos • C++ • AGIT informática • 341

Se uma função não é declarada como static (não pertence a um


módulo em particular) então ela é global (da aplicação).
Do mesmo modo, se uma variável é declarada fora de um bloco
mas não como static, entao ela é global (da aplicação).

[Link] ▪ Classificação das variáveis quanto ao tempo de vida.


Em relação a variáveis, o que dissemos acima define uma das implicações da classifi-
cação de variáveis: a sua visibilidade com relação à aplicação, ao módulo e ao bloco.
Mas há uma segunda implicação: o tempo de vida.
Uma variável representa um pedaço de memória reservado para armazenar valores de
um determinado tipo.
Enquanto essa reserva for válida esse lugar da memória não será utilizado para qual-
quer outra finalidade.
Em um determinado momento, contudo essa reserva pode ser encerrada, liberando
essa local da memória para outro uso.
O que determina o encerramento da reserva são os seguintes fatores:
 variáveis declaradas fora de blocos, sejam elas globais (escopo da aplicação)
ou modulares (escopo do módulo) têm um tempo de vida global, ou seja, a sua
reserva é garantida durante todo o tempo de vida da aplicação.
 Isto significa que essas memórias são reservadas antes mesmo que a função
main seja iniciada.
 E a reserva só é encerrada após o retorno de main.

 Logo, essas memórias estarão sempre disponíveis para qualquer função des-
de que respeitada a sua visibilidade (global ou modular).
 variáveis declaradas dentro de blocos podem ter um tempo de vida local ou
global dependendo da classificação.
 se classificadas como static o seu tempo de vida é global (sua reserva não
será encerrada no fim do bloco ou retorno de função).
 do contrário, o seu tempo de vida é local (sua reserva será encerrada no fim
do bloco ou retorno de função).

[Link] ▪ Determinando visibilidade e tempo de vida na declaração


de variáveis.
Quanto à declaração de variáveis, já vimos anteriormente que:
 quando declaramos uma variável, precisamos estabelecer um nome que a
identifique (o identificador); e precisamos também definir qual o uso que será
dado a essa memória (o seu tipo);
 e definimos, finalmente, o escopo (local, modular ou global) que determinará a
visibilidade e o tempo de vida da variável.
Agora estamos vendo que:
 poderemos restringir a visibilidade de uma variável do escopo global ao módu-
lo em que foi declarada.
 e veremos ainda que poderemos fazer com que uma memória no escopo local
tenha tempo de vida global.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


342 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Já dissemos que os dados podem ser tratados de modo privativo a um bloco (visibilida-
de local), privativo a alguns blocos (visibilidade modular ou parcial), ou públicos (vi-
sibilidade global: compartilhados entre todos os blocos).
 Este é o primeiro aspecto quanto às classes de armazenamento de variáveis
e constantes:
 a classe deve determinar a abrangência ou visibilidade da variável, isto é,
quem poderá enxergá-la e portanto acessá-la.
 Ou seja: poderá ser acessada por um único bloco (aquele dentro do qual foi
declarada), por alguns blocos, ou até mesmo por todos.
 Mas há um segundo aspecto: quanto tempo deverá durar essa variável?
 Temos duas possibilidades:

 a variável deve existir apenas enquanto o bloco que a declarou estiver em


execução; uma vez finalizado, a variável deixa de existir;
 deve existir durante todo o tempo de execução da aplicação.

 Assim a definição da classe de armazenamento da variável ou constante só


estará completa quando indicarmos:
 o seu tempo de vida.

[Link] ▪ As classes de armazenamento de memórias.


Para satisfazer os diferentes escopos analisados acima, as linguagens C/C++ oferecem
as seguintes classes de armazenamento para variáveis e constantes:
 auto;
 static (interna a uma função ou bloco);
 static (externa a qualquer bloco);
 global ou extern;
 register (reserva registrador para alocar a variável).
Vejamos cada uma delas:

[Link].a ▪ Classe auto.

 Deve sempre ser declarada dentro de um bloco de código.

 Tempo de vida:
 sempre local: uma vez finalizado o bloco que a declarou, a variável “morre”,
isto é, a memória para ela reservada é imediatamente liberada.
 Abrangência:
 sempre local: ou seja, só pode ser acessada no bloco de código em que
foi declarada.

[Link].b ▪ Classe static (obsoleta em C++)

 Pode ser declarada dentro ou fora de um bloco de código.

 Tempo de vida:
 sempre global:
 o tempo de vida de uma variável ou constante static é sempre global, a
partir do momento em que é inicializada;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.2 • Conceituando identificadores, funções, escopos • C++ • AGIT informática • 343

 se foi declarada fora de qualquer função (externa), será inicializada antes


que main inicie sua execução e, com isso, ela terá o mesmo tempo de vida
da própria aplicação;
 se foi declarada dentro de uma função (interna a um bloco), será iniciliaza-
da na primeira vez que essa função for chamada (ou não existirá, se essa
função nunca for chamada); a partir daí, seu tempo de vida passa a ser o
da aplicação (só será liberada quando a aplicação encerra);
 desse modo, a variável da classe de armazenamento static, seja ela inter-
na ou externa a um bloco de código, conservará sempre o último valor
nela armazenado.
 Abrangência:
 local:
 se for declarada dentro de um bloco de código, sua abrangência é local:
só poderá ser acessada no bloco que a declarou;
 modular ou parcial:

 se for declarada fora de qualquer bloco, sua abrangência é modular ou


parcial: poderá ser acessada por todos os blocos de códigos do mesmo
módulo, escritos abaixo de sua declaração.

[Link].c ▪ Classe global ou extern.

 Deve sempre ser declarada fora de qualquer bloco de código.

 Tempo de vida:
 Sempre global: reserva posições de memória e conserva o valor aí armaze-
nado por todo o tempo em que a aplicação estiver rodando.
 Abrangência:
 Sempre global (aplicação): poderá ser acessada por qualquer bloco da apli-
cação, independentemente do módulo em que a variável for declarada.

 Atenção: observe a diferença entre uma memória da classe auto e outra


da classe static que tenham sido declaradas dentro do mesmo bloco de
código:
Quanto à abrangência elas têm comportamento idêntico, só podendo ser
acessadas no bloco que as declarou.
Mas elas diferem quando ao tempo de vida:
 a variável ou constante da classe auto “morre” quando finalizada a execução
do bloco de código em que foi declarada; a memória ocupada é então libera-
da e a informação aí armazenada é assim destruída:
 a variável da classe static conserva a informação; como o seu tempo de
vida, a partir de sua iniciliazação, é o mesmo da aplicação; assim, se iniciali-
zada em algum momento, ela só irá “morrer” quando a própria aplicação for fi-
nalizada.
Exemplo:
// As duas veriáveis abaixo estão sendo declaradas fora de qualquer bloco
int ValorDaAplicacao = 5; // variável global:
// pode ser acessada em qualquer função de qualquer módulo

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


344 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

static int ValorDesteModulo = 10; // variável static externa:


// pode ser acessada por qualquer função deste módulo,
// já que foi escrita antes de todas as funções
void Funcao_1 ( )
{
int ValorLocalQueMorreAqui = 3; // variável autol; só pode ser acessada
// nesta função e é liberada no fim (retorno) da função.
static int ValorLocalQueNaoMorre = 4; // variável static interna:
// só pode ser acessada nesta função;
// conserva seu valor, não sendo liberada no retorno da função.
// Abaixo, serão acessadas as variáveis
// “ValorDaAplicacao” e “ValorDesteModulo”.
// Isso pode ser feito, pois ValorDaAplicacao é uma variável global e
// ValorDesteModulo é da classe static externa podendo ser acessada por
// qualquer função deste módulo escrita abaixo de sua declaração:
ValorDaAplicacao = 9;
ValorDesteModulo = 10;
} // Fim de Funcao_1: a variável “ValorLocalQueMorreAqui” é destruída.

13.2.5 ▪ Inicialização e atribuição de variáveis.


 Os dois modos de escrever um valor na posição de memória endereçada por
uma variável são os seguintes:
 atribuição: em qualquer linha de instrução, exceto a de sua declaração, utili-
zamos o operador de atribuição para gravar um valor na variável.
Exemplo:
numero = 5 ; // grava (atribui) o inteiro 5 em “numero”
 inicialização: o valor é atribuído na própria linha de declaração da variável
(em qualquer outra linha trata-se de simples atribuição e não inicialização);
exemplo:
int numero = 5 ; // “numero” é inicializada com 5

 Atenção: repare que a diferença entre atribuição e inicialização terá um


efeito decisivo no comportamento das variáveis da classe static:

 a inicialização de uma variável é feita apenas uma vez por tempo de vida;
 assim, se a variável “morre” (classe auto), a consequência é que ela terá que
ser inicializada da próxima vez em que for utilizada (“nova vida”);
 mas, se ela não “morre” (classe static) a inicialização ocorre apenas uma única
vez durante todo o tempo de vida de uma aplicação;
 desse modo, uma vez que as variáveis da classe static conservam o valor ar-
mazenado, e, em consequência, a inicialização é executada apenas na primeira
vez, o resultado é que essas variáveis poderão conservar sempre o último
valor atribuído. Exemplo:
FuncaoTesta( )
{
int Numero_1 = 5 ;
static int Numero_2 = 5 ;
// ...

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.2 • Conceituando identificadores, funções, escopos • C++ • AGIT informática • 345

Numero_1 = 10 ;
Numero_2 = 10 ;
}
 a variável numero_1 (classe auto) tem seu valor destruído quando o proces-
samento retorna da função;
 assim, se a função for chamada novamente ela voltará a ser inicializada
(voltando a receber o valor 5);
 ao contrário, a variável numero_2 (classe static) conserva seu valor;

 não mais será inicializada e, assim, em uma próxima chamada a função


seu valor inicial será 10 (último valor recebido);
 se, contudo, não houvesse sido empregada a inicialização e sim uma linha co-
mum de atribuição ocorreria o seguinte:
FuncaoTesta( )
{
static int Numero_2; // não inicializou...
Numero_2 = 5 ; // atribuição...

 Simples atribuição; sempre será executada, e desse modo o


último valor armazenado na execução anterior não será pre-
servado.
//...
Numero_2 = 10 ; // não será preservado para uma próxima execução...
// ...
}
 no primeiro exemplo, em uma segunda chamada à função, a variável nume-
ro_2 estaria com o valor 10 (e não 5), pois a inicialização não se repetiria;
 já neste segundo exemplo, numero_2 terá sempre o seu valor forçado para 5
no início da função pois a atribuição é sempre executada ao contrário da ini-
cialização.

 Finalmente, observe que a diferença entre inicialização e atribuição,


refere-se apenas a variáveis e não a constantes.
Pois, como já vimos, uma constante pode apenas ser inicializada,
não podendo sofrer atribuições.
Logo, independentemente da classe de armazenamento, uma cons-
tante precisa ser obrigatoriamente inicializada – e depois disso pode
apenas ser lida, não alterada.

13.3 • Operações e operadores


Em todos os exemplos que já vimos até aqui é fácil notar que linhas de instrução são
normalmente compostas por declarações e operações (inclusive chamadas de fun-
ção). Vejamos alguns detalhes adicionais sobre operadores.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


346 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

13.3.1 ▪ Precauções no uso dos símbolos de operadores.


A maioria dos operadores utiliza os símbolos universais a eles associados (sinais de
“mais”, “menos”,etc). São poucos os operadores que utilizam nomes ao invés de sím-
bolos de operação.
O único problema no uso de símbolos de operação é que, como a quantidade deles é li-
mitada, a linguagem precisou utilizar o mesmo símbolo para operadores diferentes.
Mas não teremos problemas se prestarmos atenção no posicionamento do símbolo na
operação que está sendo realizada e, também, os tipos das memórias envolvidas. Veja
os exemplos abaixo.
Usando o símbolo “*”.

Em uma simples operação de multiplicação:


int a, b, c ;
a = b * c ; // Correto: como “c” não é um ponteiro o * simboliza a
// multiplicação entre os valores armazenados em “b” e em “c”.

E em outra situação, envolvendo multiplicação e ponteiros:


int a , b , *d ; // “d” é um ponteiro para inteiro
a = b * d ; // ERRO: “d” é um ponteiro !
A correção é simples:
a = b * *d ; // Agora sim:
// o primeiro asterístico simboliza o operador de multiplicação // eo
segundo asterístico simboliza o operador ponteiro.
// Assim, a linha acima está ordenando corretamente:
// “multiplique o conteúdo da variável ‘b’ pelo conteúdo
// da posição de memória cujo endereço está em ‘d’ “

Ou se quiséssemos maior clareza de escrita:


a = b * ( *d ) ; // Leitura mais rápida, principalmente se a linha
// contivesse muitas operações encadeadas
Além de operadores que usam o mesmo símbolo, temos também operadores cujos sím-
bolos guardam alguma semelhança.
É o caso dos operadores de atribuição e comparação por igualdade.
 O operador de atribuição tem como símbolo um único sinal de igual.
 Já o operador de comparação por igualdade usa dois sinais de igual seguidos.
Então:
int a
a = 1 ; // isto é uma atribuição
……………..

if ( a ==1 ) // isto é uma comparação por igualdade


…………….

Basta portanto tomar cuidado usando o operador corretamente de acordo com a situa-
ção, considerando tanto a lógica envolvida quanto os tipos das variáveis empregadas.
Mais a frente, quando analisarmos os detalhes do controle de fluxo “if”, veremos em
que condições poderíamos fazer “if ( a = Var )”, e para que serviria isso. Observe que
o operador aí envolvido é o de atribuição (um único sinal de igual).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.3 • Operações e operadores • C++ • AGIT informática • 347

13.3.2 ▪ Resultados de operações.


Toda operação deve retornar um determinado resultado. Vejamos como isso ocorre
com os diferentes tipos de operadores.

[Link] ▪ Operações relacionais.


Os operadores relacionais permitem estabelecer uma relação comparativa entre dois
operandos.
Como ocorre em toda e qualquer operação, uma operação relacional deverá retornar
um resultado.
E o resultado de uma operação relacional é sempre zero(falso) ou um(verdadeiro). Isto
significa que esse tipo de operação retorna sempre um resultado lógico.
Assim, se fizermos:
int a ;
………………
int z = ( a > 5 ) ; // O valor de “a” está maior do que 5 ?

O resultado será:
 Se “a” estiver maior do que 5, o resultado da operação será 1 (um, verdadeiro);
e, em seguida, esse valor será armazenado em “z”.
 Se “a” não estiver maior do que 5 (ou seja, se estiver menor ou igual), o resulta-
do da operação será 0(zero, falso);
e, em seguida, esse valor será armazenado em “z”.

[Link] ▪ Operações lógicas.


Também aqui o resultado só poderá ser lógico.
Assim, se fizermos:
int a , b ;
………………
int z = ( a && b ) ; // “a” está verdadeira (diferente de zero) E(&&)
// “b” também está verdadeira (diferente de zero)?
O resultado será:
 Se “a” estiver diferente de zero e “b” também estiver diferente de zero, o re-
sultado da operação será 1 (um, verdadeiro);
e, em seguida, esse valor será armazenado em “z”.
 Se um dos dois (“a” ou “b”) estiver igual a zero, o resultado da operação será
0 (zero, falso);
e, em seguida, esse valor será armazenado em “z”.

[Link] ▪ Operações aritméticas.


Neste caso o resultado será, naturalmente, aritmético.
int a , b = 10 , c = 20 ;

a = b + c ; // o resultado da soma de “b” e “c” é o número inteiro 30;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


348 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

// esse resultado (aritmético) é então armazenado em “a”.

13.3.3 ▪ Posição dos operadores de incremento e decremento.


Os operadores de incremento(++) e decremento(--) admitem duas situações:
a. Posicionamento pósfixado: o operador aparece depois (à direita) do operando.
Exemplos:
1 ) a++ ;
2 ) b = a++;
Esse tipo de posicionamento apresenta o seguinte comportamento: se tentarmos
ler o valor da variável a, ele será devolvido antes que a operação de incremento
seja executada. Assim:
a=7;
b = a++ ;
Após a operação de incremento, o valor de b será 7. E o valor de a será 8.
 Isto porque primeiro a variável a foi lida, retornando 7, para só depois ser
incrementada para 8.

b. Posicionamento préfixado: o operador aparece antes (à esquerda) do operando.


Exemplos:
1 ) ++a ;
2 ) b = ++a ;

Esse tipo de posicionamento apresenta o seguinte comportamento: se tentramos


ler o valor da variável a, ele será devolvido após a execução da operação de in-
cremento. Assim:
a=7;
b= ++a ;
O valor de b será 8. E o valor de a será 8.
Isto porque primeiro a variável a foi incrementada, assumindo o valor 8, e
só depois foi lida, retornando o valor 8.

 Em resumo:
 Quando escrevemos x++ (variável antes, incremento depois) estamos ordenan-
do:
 Primeiro leia o que está armazenado na variável 'x' e depois incremente
'x'.
 E quando escrevemos ++x (incremento antes, variável depois) estamos orde-
nando:
 Primeiro incremente a variável 'x' e depois leia o que está armazenado
lá.
 E o mesmo é válido para o operador de decremento (--).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.3 • Operações e operadores • C++ • AGIT informática • 349

13.3.4 ▪ Operadores bit a bit.


Regra básica:
 operadores de bit’s só podem ser usados com tipos inteiros, não sendo
suportados em tipos com ponto flutuante.

Já vimos, na primeira parte da apostila, o operador and em operações bit a bit. Vejamos
agora todos os operadores bit a bit:
 and, or, not, xor, shift à esquerda e shift à direita.

[Link] ▪ AND
Considerando:
unsigned char A = 8 ;
unsigned char B = 9 ;
Podemos fazer:
A = A & B ; // and de bits
A &= B ; // Melhor, pois, como “A” aparece nos dois lados da operação,
// podemos utilizar o operador composto.

Variável Decimal Binário


A 8 0000 1000
B 9 0000 1001
A (Resultado) 8 0000 1000
Uma operação and exige que os dois operandos (neste caso, cada bit) estejam verda-
deiros.
Assim, apenas se ambos os bits estiverem com um (verdadeiros) o resultado será ver-
dadeiro (um).
Do contrário o resultado será falso (zero).

[Link] ▪ OR
Considerando:
unsigned char A = 8 ;
unsigned char B = 9 ;
Podemos fazer:
A = A | B ; // or de bit’s.
A |= B ; // Melhor, pois, como “A” aparece nos dois lados da operação,
// podemos utilizar o operador composto

Variável Decimal Binário


A 8 0000 1000
B 9 0000 1001
A (Resultado) 9 0000 1001
Em uma operação or basta que um dos dois operandos (neste caso, cada bit) esteja ver-
dadeiro.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


350 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Assim, se qualquer um dos dois bits envolvidos na operação bit a bit estiver com um
(verdadeiro) o resultado será verdadeiro (um). Do contrário o resultado será falso
(zero).

[Link] ▪ NOT (ou complemento de 1)


Considerando:
unsigned char A = 8;
Podemos fazer:
A = ~A ; // not de bit’s.

Variável Decimal Binário


A 8 0000 1000
A (Resultado) 247 1111 0111
Uma operação not bit a bit, inverte todos os bits. (zero é trocado para um, e um é tro-
cado para zero).
Observe que o not aplicado sobre o número 8 (exemplo acima) resulta no número 247
E o mesmo resultado poderia ser obtido com 255 – 8 (o que será válido para qualquer
operação not de bits em cada byte).
Sabendo-se que 255 tem todos os seus bits com um (1111 1111), podemos dizer que
uma operação not bit a bit é o mesmo que o complemento de um.

[Link] ▪ XOR ( or exclusivo)


Considerando:
unsigned char A = 8 ;
unsigned char B = 9 ;
Podemos fazer:
A = A ^ B ; // xor de bit’s.
A ^= B ; // Melhor, pois, como “A” aparece nos dois lados da operação,
// podemos utilizar o operador composto

Variável Decimal Binário


A 8 0000 1000
B 9 0000 1001
A (Resultado) 1 0000 0001
Em uma operação xor o resultado será verdadeiro apenas se os dois operandos (os
dois bits) estiverem diferentes (“exclusivamente OU”).
Isto significa que um deles deve estar verdadeiro e o outro deve estar falso.

[Link] ▪ Shift (ou deslocamento) à esquerda


Considerando:
unsigned char A = 8;
Podemos fazer:
A = A << 1 ; // shift de bit’s, com deslocamento de um bit
// para a esquerda;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.3 • Operações e operadores • C++ • AGIT informática • 351

A <<= 1; // Melhor, pois, como “A” aparece nos dois lados da operação,
// podemos utilizar o operador composto

Variável Decimal Binário


A 8 0000
1000
A (Resultado) 16 0001
0000
O segundo operando (1, no exemplo), indica a quantidade de bits que deve ser deslo-
cada à esquerda no primeiro operando. Os bit’s vagos, à direita, são preenchidos com
zeros (o primeiro bit, no exemplo).

[Link] ▪ Shift (ou deslocamento) à direita:


Considerando:
unsigned char A = 8;
Podemos fazer:
A = A >> 1 ; // shift de bits, com deslocamento de um bit
// para a direita;
A >>= 1; // Melhor, pois, como “A” aparece nos dois lados da operação,
// podemos utilizar o operador composto

Variável Decimal Binário


A 8 0000
1000
A (Resultado) 4 0000
0100
O segundo operando (1, no exemplo), indica a quantidade de bits que deve ser deslo-
cada à direita no primeiro operando. Os bits vagos, à esquerda, são preenchidos com
zeros (o último bit, no exemplo).

[Link] ▪ Exemplos com operadores de bits.


[Link].a ▪ Exemplo com and, or, e not.
Um bom exemplo para uso de bits é quando precisamos de um conjunto de variáveis
para indicar estados verdadeiro/falso.
Ao invés de criar diversas variáveis separadas podemos usar os bits de uma única va-
riável (pois para indicar se uma situação está verdadeira ou falsa basta usar um único
bit, que poderá estar com um(verdadeiro) ou com zero(falso).
Assim poderíamos ter uma situação de entrada de dados onde diversos campos preci-
sam ser preenchidos. E usaríamos os bits de uma variável para indicar a situação de
cada um dos campos: se preenchido corretamente ou não.
Vamos então criar uma classe chamada Dados.
 A classe Dados tem três atributos: Código, Valor e Tipo.
 Além disso terá uma variável interna chamada “Erros” que servirá para assinalar
qualquer erro.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


352 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Regras:
 Código deverá estar entre as constantes CODIGO_MIN e CODIGO_MAX.
 Valor deverá ser maior que zero.
 E Tipo deverá assumir um destes dois valores constantes: TIPO_RECEITAS ou
TIPO_DESPESAS
Funcionamento:
 Cada um dos três campos acima, usará um bit da variável Erros, para indicar se
foi preenchido corretamente.
 Se qualquer um dos três campos estiver incorreto, um bit da variável Erros,
correspondente ao campo, deverá ser ligado.
 E, se estiver correto o bit deverá ser desligado.
 Para que isso seja possível, é preciso que cada campo use um único bit. Para
isso é preciso que o indicador de erro de cada campo seja uma potência de
2 ( 1, 2, 4, 8, 16, etc).
Implementação:
#include <iostream>
class Dados
{
private:
int m_Codigo;
double m_Valor;
int m_Tipo;
unsigned int m_Erros;
public:
// Constantes indicadoras de erro em um campo:
// cada uma é uma potência de 2
enum { ERRO_CODIGO = 1 , // 2 elevado a zero
ERRO_VALOR = 2 , // 2 elevado a um
ERRO_TIPO = 4 }; // 2 elevado a dois
/* E COMO usar ?
1) Se quisermos assinalar o bit correspondente ao campo Codigo como estando
ERRADO:
m_Erros |= ERRO_CODIGO; // coloca este bit com 1 usando or de bits
2) E se quisermos zerar esse bit, assinalando-o como correto, devemos inverter
(not) todos os bits do indicador ERRO_CODIGO e, então, aplicar um and de
bits:
m_Erros &= ~ERRO_CODIGO; // coloca este bit com zero
3) E se quisermos saber se o bit de Codigo está ligado:
if ( m_Erros & ERRO_CODIGO ) // basta usar o and de bits
………………..
*/
// Outras constantes
enum { CODIGO_MIN = 100 , CODIGO_MAX = 999 } ;
enum { TIPO_RECEITAS = 1 , TIPO_DESPESAS = 2 } ;
Dados() // construtora
void AlterarCodigo(int Cod);
void AlterarTipo(int Tipo);

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.3 • Operações e operadores • C++ • AGIT informática • 353

void AlterarValor(double Valor);


int LerCodigo() const { return m_Codigo; }
double LerValor() const { return m_Valor; }
int LerTipo() const { return m_Tipo ; }
int LerErros() const { return m_Erros ; }
};
Dados::Dados() // construtora
{
m_Erros = 0;
AlterarCodigo(0);
AlterarValor(0);
AlterarTipo (0);
}
void Dados::AlterarCodigo(int Cod)
{
m_Codigo = Cod;
if ( Cod >= CODIGO_MIN && Cod <= CODIGO_MAX )
m_Erros &= ~ERRO_CODIGO; // Código CORRETO:
// então coloca este bit com zero
else
m_Erros |= ERRO_CODIGO; // Código ERRADO: então coloca este bit com 1
}
void Dados::AlterarValor(double Valor)
{
m_Valor = Valor;
if ( Valor > 0 )
m_Erros &= ~ERRO_VALOR ; // Valor CORRETO: então coloca este bit com zero
else
m_Erros |= ERRO_VALOR ; // Valor ERRADO: então coloca este bit com 1
}
void Dados::AlterarTipo(int Tipo)
{
m_Tipo = Tipo;
if ( Tipo == TIPO_RECEITAS || Tipo == TIPO_DESPESAS )
m_Erros &= ~ERRO_TIPO ; // O tipo está CORRETO:
// então coloca este bit com zero
else
m_Erros |= ERRO_TIPO ; // O tipo está ERRADO: coloca este bit com 1
}

int main()
{
Dados MeusDados;
[Link](0); // preenchimento incorreto...
[Link] (1); // preenchimento incorreto...
[Link](TIPO_RECEITAS); // preenchimento
// CORRETO.
int Erros = [Link]();
if ( Erros == 0 )
cout << "Nenhum erro !!!" << endl;
else

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


354 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

{
if ( Erros & ERRO_CODIGO ) // and de bits
cout << "Codigo esta errado" << endl;
if ( Erros & ERRO_VALOR ) // and de bits
cout << "Valor esta errado" << endl;
if ( Erros & ERRO_TIPO ) // and de bits
cout << "Tipo esta errado" << endl;
}
return 0;
}
/* RESULTADO
Codigo esta errado
Valor esta errado
*/

[Link].b ▪ Exemplo com shift (à esquerda e à direita).


Vamos agora ver um segundo exemplo, usando o shift para a esquerda e para a direita.
O objetivo é “Imprimir uma lista crescente de números, começando em “2 elevado a
zero” (1) até atingir “2 elevado a 5” (32). Em seguida imprima em ordem inversa (de-
crescente), até atingir o número zero.
Devem ser usados os operadores shift”.
main()
{
int Var ;
// shift para a esquerda:

for ( Var = 1 ; Var <= 16 ; Var <<= 1 ) // shift à esquerda:


// lista crescente
cout << Var << “ “;

cout << "\nValor de Var depois do primeiro for = " << Var
<< endl << endl;
// shift para a direita:

for ( ; Var >= 1 ; Var >>= 1 ) // shift à direita:


// lista decrescente
cout << Var << “ “;

cout << endl;


cout << "Valor de Var depois do segundo for = " << Var << endl ;
return 0;
}
/* RESULTADO
1 2 4 8 16
Valor de Var depois do primeiro for = 32
32 16 8 4 2 1
Valor de Var depois do segundo for = 0
*/

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.3 • Operações e operadores • C++ • AGIT informática • 355

[Link].c ▪ Exemplo com xor (1).


Uma aplicação muito conhecida do xor é em criptografia. O xor, isoladamente, é usado
no mais elementar dos algorítmos de criptografia simétrica. Também é usado, com-
binado com outros recursos, em outros algorítmos.
Sabendo-se que um xor entre dois bits retorna falso se eles são iguais e verdadeiro se
eles são diferentes, podemos facilmente concluir que:
 se eu tenho um determinado texto e uma determina chave, e opero um xor bit a
bit entre os dois, eu obtenho um resultado (criptografado) onde:
 os bits iguais no texto e na chave resultaram em 0(zero)
 e os diferentes resultaram em 1(um).

 se, agora, eu opero o xor entre o resultado criptografado e a chave, tenho exa-
tamente o caminho inverso, restaurando o texto original (descriptogrando-o),
pela mesma razão que atuou no processo original de criptografia (inversão por
contraste).
 Obs.: este é apenas um exemplo de uso do xor, pois para criptografia profissi-
onal, há algorítmos bem mais robustos.
Vejamos o exemplo:
#include <iostream>
#include <string>
class Cripta
{
public:
enum { MAX_KEY = 32 };
private:
char m_Key[ MAX_KEY + 1 ];
size_t m_Size;
public:
Cripta() // construtora
: m_Size ( 0 )
{
*m_Key = 0;
m_Key[ MAX_KEY ] = 0;
}
void SetKey( const char * Key ) {
strncpy( m_Key, Key, MAX_KEY );
}
const char * GetKey() const { return m_Key; }
void SetSize( size_t Size ) { m_Size = Size; }
size_t GetSize () const { return m_Size; }
// Função “Cript”: para criptografar e descriptografar
// (pois a lógica é simplesmente simétrica):
void Cript( const char * Text, char * Output)
{
const char * keyPtr ;
size_t size = m_Size;

for ( keyPtr = m_Key; size>0; ++Text, ++Output, --size )


{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


356 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

// xor bit a bit entre byte entrada e correspondente na chave:


*Output = *Text ^ *keyPtr; // 
// evoluir a posição da chave:
++keyPtr;
if ( !*keyPtr ) // se atingiu o terminador zero...
keyPtr=m_Key; // voltar ao primeiro byte da chave
}
} // fim da função “Cript”
} ; // fim da declaração da classe “Cripta”
int main()
{
using namespace std;
char Text[255], Password[255], CriptText[255];
size_t Size;
cout << "informe texto e senha" << endl;
[Link](Text, sizeof(Text));
[Link](Password, sizeof(Password));
cout << endl;
Size= strlen(Text);
Cripta crpt ; // cria um objeto “Cripta'
[Link] ( Password ); // informa a senha
[Link]( Size ); // informa o tamanho do texto a criptografar

// 1) Criptografar de Text para CriptText:


[Link]( Text, CriptText);
CriptText[Size] = 0x0;
cout << "\nCriptografei (pode ter caracteres estranhos ou "
"terminador zero no meio...):\n" << CriptText << endl;

// 2) Des-criptografar de CriptText para Text:


[Link](CriptText, Text );
cout << "\nDescriptografei: \n" << Text << endl << endl;
return 0;
}

Um outro exemplo do mesmo princípio (simetria) pode ser visto numa interessante
implementação da função “swap” usando o operador xor.
Essa função clássica troca o valor de duas variáveis. A sua implementação mais co-
mum é a que está na função “swap_1” do exemplo abaixo. Em seguida temos uma fun-
ção “swap_2”, usando o xor.
#include <iostream>
template < typename T > void swap_1(T & a, T & b)
{
T tmp = a;
a = b;
b = tmp;
// e "b" ficara com o valor original de "a"
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.3 • Operações e operadores • C++ • AGIT informática • 357

template < typename T > void swap_2(T & a, T & b)


{
a ^= b ; // "a" contem a inversao de "a" e de "b"
// e desse modo permitrár recuperar 'a' e 'b' trocados
b ^= a; // b = a
a ^= b; // a = b
}

int main()
{
using namespace std;

int a = 5, b = 6;
cout << "- swap_1: valores originais: " << a << ", " << b << endl;
swap_1( a, b );
cout << "- swap_1: valores trocados : " << a << ", " << b << endl;

cout << '\n' ;

a = 5, b = 6;
cout << "- swap_2: valores originais: " << a << ", " << b << endl;
swap_2( a, b );
cout << "- swap_2: valores trocados : " << a << ", " << b << endl;

return 0;
}
/*
RESULTADO:
- swap_1: valores originais: 5, 6
- swap_1: valores trocados : 6, 5
- swap_2: valores originais: 5, 6
- swap_2: valores trocados : 6, 5
*/

[Link].d ▪ Exemplo com xor (2).


Um outro exemplo de uso prático da inversão verdadeiro/falso ocasionada pelo xor, é o
de descobrir qual é o último dia de cada mês.
Vimos esse exemplo no exercício “class data”, mas a operação com o xor não foi ali
explicada com detalhes.
É o que faremos aqui.
Analisando o problema do “último dia de cada mês”, descobrimos três situações di-
ferentes:

1) Quando o mês é fevereiro teremos 29 dias se o ano for bissexto ou, do contrá-
rio, 28.
 Então poderíamos primeiro escrever uma função chamada Bissexto, que re-
tornaria falso (zero) se o ano não for bissexto, e retornaria verdadeiro(um) se
o ano for bissexto:
 Agora bastaria fazer:

if ( mes == 2 )

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


358 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

return 28 + Bissexto ( Ano ); // Ou seja: 28 + um (ano bissexto),


// ou 28 + zero (não bissexto)
2) De janeiro a julho sabemos que os meses pares têm 30 dias e os ímpares têm
31. Então é simples:
if ( mes <= 7 )
return 30 + ( mes & 1 ); // O operador de bits and retornará um
// se o mes for ímpar.
// Assim, teremos : meses ímpares = 30 + um ;
// e meses pares = 30 + zero
3) De agosto a dezembro ocorre o contrário: meses pares têm 31 dias e os ímpa-
res têm 30:
if ( mes > 7 )
return 30 + ! ( mes & 1 ); // O operador de bit’s and retornará um
// se o mes for ímpar.
// Em seguida usamos o operador lógico not ( ! ) para inverter
// zero para um, ou um para zero (já que os pares tem 31 dias)
// Assim, teremos : meses pares = 30 + um ;
// e meses ímpares = 30 + zero
E poderíamos também fazer (economizando uma operação):
if ( mes > 7 )
return 31 - ( mes & 1 ); // O operador de bitss and retornará um
// se o mes for ímpar.
// Neste caso, não precisaríamos inverter o resultado do and.
// Pois teremos : meses pares = 31 – zero ;
// e meses ímpares = 31 - um

A pergunta é: existe algum meio de unir as duas última situações em uma única
operação sem usar o if? Sim, através do xor. Para entender, vamos ver a se-
guinte tabela.
Meses Cálculo necessário Resultado final
6 (junho) 30 + zero 30
7 (julho) 30 + um 31
8 (agosto) 30 + um 31
9 (setem- 30 + zero 30
bro)
Veja agora como obter o resultado desejado através do xor:
Agora será efetuado
É ímpar ? É maior que um XOR entre as Resultado final
7(julho) ? duas colunas ante- (30 +
Meses (mes & 1) ( mes > 7 ) riores coluna anterior):
6(junho) zero zero zero (estão 30 (30 + zero)
iguais)
7(julho) um zero um (diferentes) 31 (30 + um)
8(agosto) zero um um (diferentes) 31 (30 + um)
9(setembro) um um zero (iguais) 30 (30 + zero)

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.3 • Operações e operadores • C++ • AGIT informática • 359

Vamos então implementar a solução.


// Primeiro precisamos de uma função para determinar se um ano é bissexto:
bool Bissexto(short nAno)
{
// um número é bissexto se for divisível por 4 mas não por 100
// (exceto se também for divisível por 400)
// podemos usar o operador and bit a bit, para saber se é divisível por 4:
return (( nAno & 3) == 0) &&
((nAno % 100) != 0 || (nAno % 400) == 0);
// já para saber se é divisível por 100 ou por 400, não tem jeito,
// temos que usar o resto de divisão.
}
// Agora, já podemos encontrar o último dia de um mês:
enum {FEVEREIRO = 2, JULHO = 7 };
char ultimoDiaMes (char Mes, short Ano )
{
// se for fevereiro: 28 ou 29 dias; de janeiro a julho ( Mes <=7), os meses
// ímpares tem 31 dias e os pares tem 30; agosto a dezembro (Mes>7),
// os meses ímpares tem 30 dias e os pares tem 31
return ( nMes==FEVEREIRO) ? 28+Bissexto(nAno)
: 30 + ( (nMes & 1) ^ (nMes > JULHO) );

// usando o xor bit a bit


}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


360 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

13.4 • Controles do fluxo de processamento.


13.4.1 ▪ Chamadas de função.
O controle de fluxo de processamento mais importante da linguagem é baseado na or-
ganização de uma aplicação em diversas subrotinas (funções). Assim a chamada de
função representa um desvio de fluxo organizado (estruturado) já que a operação de
salto é feita com garantia de retorno ao ponto imediatamente seguinte.
Já vimos como são feitas chamadas de função de modo convencional.
Mas podemos também armazenar o endereço de uma função em uma variável e chamar
a função a partir dessa variável.

[Link] ▪ Parâmetros para funções e retorno de funções.


[Link] ▪ Declarando parâmetros.
Uma função pode ou não receber parâmetros, isto é, informações de que necessitará
para realizar o seu trabalho. Um parâmetro de função também deve ser declarado en-
quanto variável, devendo a declaração indicar o seu tipo, como ocorre em qualquer
declaração de variável.

[Link] ▪ Formas de declaração.


Em C há duas formas de declarar variáveis que cumprem o papel de parâmetros de
função. Mas em C++ há apenas uma.

[Link].a ▪ Forma de declaração válida em em C e C++


int SomaNumeros ( int A , int B ) /* as variáveis A e B são parâmetros */
{
int C; /* a variável C não é um parâmetro */
INSTRUÇÕES ........... ;
}

[Link].b ▪ Forma de declaração inválida em C++


Existe uma outra forma de declaração, mas ela é válida apenas em C – e não é valida
em C++.
int SomaNumeros ( A , B )
int A ; /* a variável A é um parâmetro */
int B ; /* a variável B é um parâmetro */
{
int C; /* a variável C não é um parâmetro */
INSTRUÇÕES ........... ;
}

O que há em comum entre essas duas formas de declaração das variáveis que servem
como parâmetros é que, em ambos os casos, a declaração das variáveis-parâmetro é
feita antes da chave que abre o bloco de instruções nomeado pela função.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 361

A primeira delas sempre foi a mais usada por ser mais clara e simples. Felizmente, C+
+ não manteve a compatibilidade com C neste aspecto, e, devido à falta de clareza da
segunda alternativa, ela não é válida nesta linguagem.

[Link] ▪ Funções com quantidade fixa de parâmetros.


O mais habitual (e melhor) é quando uma função tem uma quantidade fixa de parâme-
tros, todos com os seus tipos claramente estabelecidos.
Assim, a função SomaNumeros, do exemplo acima, deverá ser chamada sempre com os
parâmetros adequados.
E isto significa que se uma função foi definida para receber dois parâmetros do tipo int
ela só deve ser chamada mediante a passagem de dois valores desse tipo.
Assim, uma chamada à função SomaNumeros poderia ser escrita:
deste modo:
SomaNumeros ( 5 , 4 ) ;
ou deste:
{
int A = 5 ; int B = 4 ;
SomaNumeros ( A , B ) ;
}

mas não assim (provocará uma advertência do compilador):


{
float A = 5.5 ; int B = 4 ;
SomaNumeros ( A , B ) ; /* AVISO! a variável “A” é do tipo float:
possível perda de precisão na
passagem para int */
}

Contudo, poderia assumir a perda de precisão explicitamente através da con-


versão (“cast”):
SomaNumeros ( (int)a , b );
OU
SomaNumeros ( int( a ) , b ) ; // apenas C++

 A chamada a uma função com quantidade fixa de parâmetros deve obede-


cer tanto a essa quantidade quanto aos tipos dos parâmetros da função.

[Link] ▪ Funções com quantidade variável de parâmetros.


Embora de um modo geral o uso desta espécie de função seja considerado um estilo
pobre de programação, tanto C quanto C++ admitem funções com uma quantidade va-
riável de parâmetros.
Esse é o caso de funções como printf e scanf, por exemplo.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


362 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Nesse tipo de função devemos ter pelo menos um primeiro parâmetro fixo (que servirá
para indicar quantos parâmetros teremos em seguida).
A partir daí é responsabilidade do programador extrair os demais parâmetros da pilha.

 Evite criar funções com quantidade de parâmetros variável.


Elas impedem que o compilador analise se estão sendo chamadas correta-
mente

[Link].a ▪ Declarando os parâmetros desconhecidos


Na declaração de parâmetros desta espécie de função devemos ter um ou mais parâme-
tros fixos, separados normalmente por vírgulas.
Após o último deles, deve existir uma vírgula seguida de reticências (que simbolizam
uma lista com quantidade variável de parâmetros).
Exemplo:
double Media( int NumParams , ... )

[Link].b ▪ Exemplo de função com quantidade de parâmetros variável.

#include <iostream>
#include <stdarg.h>

// O primeiro parâmetro, fixo, informa a quantidade de parâmetros adicionais.


// Em seguida, temos uma vírgula e reticências.
double Media( int NumParams , ... )
{
// inicia a lista de parâmetros
va_list Param;
va_start( Param, NumParams );
int iSoma = 0;
for ( int iC=0; iC < NumParams; iC++)
{
iSoma += va_arg( Param, int);
// Na linha acima, os parâmetros são, um a um,
// extraídos da lista
}
va_end( Param ); // libera a lista de parâmetros
return ( iSoma / NumParams );
}

// Usando a função com quantidade de parâmetros variável:

int main()
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 363

// Abaixo, será chamada a função Media,


// com quantidade de parâmetros variável.
// Na chamada da função, o primeiro parâmetro indica a quantidade
// de parâmetros adicionais. Neste exemplo temos quatro:

cout << Media ( 4 ,


8 , 10 , 6 , 8 ) << endl;
return 0;
}

[Link] ▪ Valor de retorno, tipo e protótipo de funções.


[Link].a ▪ Valor de retorno.
Há funções que apenas realizam uma tarefa.
Por exemplo: uma função denominada “LimpaTela()”, simplesmente imprime brancos
na tela e retorna sem devolver qualquer valor. Sua tarefa não implica em um resultado
formal.
Já uma função como a SomaNumeros tem como tarefa somar dois dados do tipo int.
Assim, ela só faz sentido se informar para quem a chamou qual foi o resultado dessa
operação de soma

 Isso significa que além de receber informações na forma de parâmetros,


uma função também pode retornar uma (e somente uma) informação.

Desse modo a função SomaNumeros deveria ser escrita assim:


int SomaNumeros( int A , int B )
{
int C = A + B
return C; /* retorna o resultado da soma */
/ * logo, a função SomaNumeros retorna um valor do tipo int */
}

Deve ficar claro que a declaração de retorno (return) só pode ser associada a um úni-
co valor.
O valor de retorno define o tipo da própria função. Assim ela só pode retornar um úni-
co dado (o tipo desse dado determina o tipo da função).
Quando uma função retorna um valor ocorrerá que uma cópia desse valor será armaze-
nada em uma memória temporária de retorno. Nesse momento, essa memória pode-
rá ser utilizada em qualquer operação pendente (por exemplo, uma atribuição). Por isso
fazem sentido as seguintes expressões:
C = SomaNumeros ( A , B ) ; /* o valor de retorno da função é copiado para a variável C */

OU
C = SomaNumeros ( A , B ) * 5 ; /* o valor de retorno da função é primeiro multiplicado por 5

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


364 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

e o resultado final será então atribuído à variável C */

[Link].b ▪ Tipo de uma função.

 Utilizando o seu retorno formal (ou resultado), uma função pode retornar
um único valor, o qual determina o seu tipo.
Ou seja: o tipo de uma função é determinado pelo tipo do valor que ela
retorna.

E se ela não retornar um valor (como no exemplo acima, da função LimpaTela), dize-
mos que essa função é do tipo void (ausência de valor de retorno). Desse modo, dize-
mos que a seguinte função é do tipo int:
int SomaNumeros( int A , int B )
{
int C = A + B ;
return C ; /* retorna “C” que é um valor do tipo int */
}

 Desse modo quando chamamos uma função devemos observar:


 que os parâmetros passados o sejam na mesma quantidade e com os mes-
mos tipos conforme serão recebidos.
 mas também que o retorno seja recebido de modo compatível; não é possí-
vel armazenar (sem perda de precisão) o retorno de uma função do tipo float
em uma variável do tipo int.

[Link].c ▪ Protótipo de funções.


Quando queremos usar uma função como printf, por exemplo, precisamos informar ao
compilador qual é o modo correto de chamada a essa função. Isto para que ele possa
analisar se todas as chamadas feitas à função estão corretas.
Por isso, sempre que usamos printf, precisamos incluir o arquivo stdio.h. Porque neste
arquivo está descrito o modo correto de emprego da função.
E essa descrição é estabelecida através do protótipo da função.
 Um protótipo é uma máscara que define exatamente a configuração de uma
determinada função;
 Isso significa que um protótipo deve especificar os seguintes aspectos da
função:
 quantidade de parâmetros;
 tipo de cada parâmetro;
 tipo do retorno (tipo da função).

 Desse modo o protótipo:


 documenta como deve ser usada a função;
 permite que o compilador cheque se todas as chamadas a uma deter-
minada função estão corretas).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 365

Exemplo:
O protótipo da função SomaNumeros deveria ser escrito assim:
int SomaNumeros( int , int ) ;
- a função recebe dois parâmetros;
- os dois são do tipo int;
- e o tipo da função é int (logo ela retorna um valor int).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


366 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link] ▪ Modos de passagem de parâmetros

 Há três modos de passar parâmetros para uma função:


 1 - por valor;
 2 - por endereço;
 3 - por referência.

[Link].a ▪ 1 - Passando parâmetros por valor.

 A função que recebe parâmetros recebe apenas cópias de valores, nada


sabendo sobre os endereços em que tais valores estão armazenados no
contexto de quem chamou a função.

chamada da função (envia dados):


int main( )
{
int A = 5 ; int B = 4 ;
SomaNumeros ( A , B ) ;
return 0;
}

definição da função (recebe os dados como parâmetros)


int SomaNumeros ( int A, int B )
{
return A + B ;
}

 as variáveis A e B na função main( ) são locais e privativas do seu contexto;


 o mesmo ocorre com A e B em SomaNumeros;
 ou seja: embora, por coincidência, tenham o mesmo nome, são regiões de me -
mória diferentes.
 o que ocorre então é uma cópia o conteúdo da primeira variável da chamada
para a primeira variável da recepção - e igualmente para a segunda.
 Por isso, dizemos que os parâmetros aqui foram passados por valor.

 Veja o seguinte mapa:

Endereço de memória (hipóte- Variável Valor


se)
2000 A(main) 5
2004 B(main) 4
5000 A(somaNumeros) 5 (recebeu cópia de conteú-
do)

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 367

5004 B(somaNumeros) 4 (recebeu cópia de conteú-


do)

 Os endereços são diferentes. Logo são variáveis independentes.


 Qualquer alteração que SomaNumeros( ) faça nas suas variáveis-parâ-
metro em nada afetará as variáveis de main( ).

[Link].b ▪ 2 - Passando parâmetros por endereço:

 Neste caso, as variáveis-parâmetro, ao invés de receber uma cópia dos va-


lores passados por quem chamou a função, recebem uma cópia dos ende-
reços onde esses valores estão armazenados.

 Como já vimos, o que torna possível trabalhar diretamente com o endereço de


uma variável, em C/C++, são os operadores de endereço (&) e ponteiro (*).
Assim se escrevermos:
1 int B ;
2 int * A ;
3 A = &B ;
estaremos ordenando:
1 Reserve memória para um valor do tipo int (e a apelide de “B”).
2 Reserve a memória necessária para armazenar um endereço (e
a apelide de “A”);
e garanta que nessa memória sejam gravados endereços para
valores do tipo int.
3 Armazene em “A” o endereço de “B” [ A = &B ].

 O que é válido para uma variável qualquer é válido também para uma variá-
vel colocada no papel de parâmetro de função. Assim só podemos passar
endereços para as funções que estejam aptas a receber endereços. Por
isso:
As variáveis-parâmetro que pretendem receber endereços precisam ser
ponteiros.

 Então, uma função que recebe endereços deve estar:


1) definida de modo apropriado e
2) deve ser chamada de modo apropriado.

chamada da função (envia endereços):


int main( )
{
int A = 5 ; int B = 4 ;
SomaNumeros ( &A , &B ) ;
return 0;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


368 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

definição da função (recebe endereços em variáveis-ponteiro)


int SomaNumeros ( int * A , int * B )
{
int C = * A + * B ;
return C;
}

Observe que a linha de instrução escrita acima [ C = * A + * B ] significa:


1) vá ao endereço guardado em “A” e pegue o valor ali armazenado;
2) vá ao endereço guardado em “B” e pegue o valor ali armazenado;
3) some os dois valores e armazene o resultado em “C”.
 Se o operador ponteiro (*) não estivesse presente estaríamos ordenando
que o conteúdo da variável “A” (um endereço) fosse somado ao conteúdo
da variável “B” (outro endereço). Isso não faria o menor sentido: estaría-
mos pretendendo que a variável “C” armazenasse a soma de dois endere-
ços!!!
Mas, felizmente, nem chegaria a ocorrer, pois o compilador acusaria o
erro: manipulação incorreta de tipos de dados.

 Cabe aqui uma observação importante:


Se estamos passando um endereço como parâmetro isto significa que a
função que recebe esse parâmetro passa a ter acesso a uma posição de
memória pertencente ao contexto de quem chamou a função.
Logo, esta função poderá alterar o conteúdo dessa posição de memória
pois, neste caso, ela está sendo enxergada.

 na passagem de parâmetros por endereço o que ocorre então é uma cópia do


endereço da variável e não o seu conteúdo (o valor).
 as variáveis de main e as variáveis-parâmetro de SomaNumeros são variáveis
independentes (ocupam posições de memória diferentes); mas como as variá-
veis de SomaNumeros estão de posse dos endereços de variáveis pertencentes
a main, SomaNumeros dispõe agora de um meio de acesso às variáveis de
main, podendo então alterá-las.
 Veja o seguinte mapa:
Considerada esta situação:
int main( )
{
int A = 5 ; int B = 4 ;
SomaNumeros ( &A , &B ) ;
return 0;
}
int SomaNumeros ( int * A , int * B )
{
………………

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 369

Poderíamos ter a seguinte disposição de memória:

Endereço de memória (hipó- Variável Conteúdo


tese)
2000 A (main) 5 (um valor int)
2004 B (main) 4 (um valor int)
5000 *A (somaNume- 2000 (recebeu cópia do endere-
ros) ço)
5004 *B (somaNume- 2004 (recebeu cópia do endere-
ros) ço)

 Os endereços das variáveis são diferentes. Logo são variáveis independen-


tes.
 Contudo, as variáveis de SomaNumeros( ) armazenam os endereços das
variáveis de main.
 Desse modo SomaNumeros( ) poderá alterar o conteúdo das variáveis em
main( ).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


370 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link].c ▪ 3 - Passando parâmetros por referência:

 Embora este modo de passagem de parâmetros aparente alguma semelhança


com a passagem por endereço, na verdade são dois modos completamente di-
ferentes.

 Na passagem por referência não temos uma segunda posição de memória ar-
mazenando um endereço e sim um segundo nome para uma mesma posição
de memória.

 Exemplo da diferença entre passagem por endereço e referência:

por endereço:
int main( )
{
int A;
Segunda ( &A ) /* captura o endereço de “A” e o envia como parâmetro para a função segunda */
return 0;
}
void Segunda( int * X ) /* a função segunda está apta a receber o endereço de um valor do tipo
int */

 desse modo, (hipótese) se “A” fosse o nome do endereço 2000 na


memória, “X”,
(que, por sua vez, seria o nome do endereço 2004), armazenaria
2000.

A X
(nome de 2000) (nome de 2004)

armazena o armazena o
valor 5 endereço 2000

por referência:
 neste caso, a função que receberá parâmetros deverá declará-los como o
operador de referência (&)
int main( )
{
int A;
Segunda ( A )
return 0;
}
void Segunda ( int & X ) /* devido ao operador de referência, o compilador considera “X” como
um segundo nome para a posição de memória que, em main, recebeu o nome "A” */

 desse modo, (hipótese) se “A” fosse o nome do endereço 2000 da


memória, “X” seria um segundo nome para 2000.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 371

A X
(nome de 2000) (segundo nome
para 2000)
Armazena o valor 5

[Link] ▪ Há dois motivos para passar parâmetros por endereço ou


por referência.
1) Retornos extras
Como uma função tem apenas um retorno formal podemos precisar de “retor-
nos-extras”.
 Não há como fazer isso contando com o retorno formal de função (pois este
permite um e apenas um retorno).
 Mas, se passarmos o endereço de variáveis, a função chamada poderá acessar
e alterar essas variáveis produzindo efeitos no contexto de quem chamou.
 É assim que atua a função scanf( ):
 Ela já tem um retorno, que informa se a operação de entrada de dados foi
bem sucedida.
 Mas precisamos de um “retorno-extra”, sem o qual a função não faria senti-
do: o resultado da própria entrada de dados, que é sua tarefa real.
 por isso o segundo parâmetro de scanf( ) deve ser passado com o operador
de endereço (&).
 desse modo essa variável será “preenchida” por scanf( ).

2) Performance
O segundo motivo é performance (velocidade de cópia na passagem de parâme-
tros).
 Se estamos passando um int, um char, ou mesmo um float, é preferível passar
por valor. Pois estamos lidando com um número reduzido de bytes.
 Mas podemos criar nossos próprios tipos de dados. São as estruturas de dados
que funcionam como registros com diversos campos, podendo conter vários
int, ou vários char, etc.
 Dados desse tipo podem ocupar grande quantidade de memória e nesse caso a
cópia por valor (o que significa a cópia de todos dos campos), será bem mais
lenta.
 Além disso uma cópia por valor duplica -ainda que momentaneamente- o uso
da memória (é preciso manter o “original” e a “cópia” simultaneamente. Isto
porque quem chamou a função usou uma memória (e ela ainda está viva); e a

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


372 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

própria função reservou uma memória para receber uma cópia desse valor (o
parâmetro) que, naturalmente, também está vivo nesse momento.
 Nesse caso se passarmos uma cópia de endereço estaremos passando apenas
dois ou quatro bytes, que é o espaço necessário para armazenar um endereço
(dois ou quatro bytes para sistemas de 16 bits; e sempre quatro bytes para sis-
temas de 32 bits).

[Link] ▪ Impedindo efeitos colaterais desnecessários.

 ATENÇÃO:

 Quando utilizarmos passagem por endereço ou por referência apenas pelo


motivo “performance” (não havendo nenhum interesse em “retornos ex-
tras”) abrimos uma porta para efeitos colaterais.

 Não queremos que a função chamada possa alterar memórias de outros con-
textos.
Queremos apenas velocidade.
 E como garantir que a função chamada seja impedida de promover altera-
ções não previstas ou não desejadas em outros contextos?
 Através do especificador const.

 Uma função que recebe endereços ou referências apenas pelo motivo “per-
formance”, deve ter direito de acesso ao endereço recebido somente para
leitura e não para gravação.

 O especificador const determina essa restrição, estabelecendo um status


“constante” (read-only) para a variável.

 Desse modo a função SomaNumeros(usando ponteiros como parâmetros) po-


deria ser escrita assim:
int SomaNumeros ( const int * A , const int * B )
{
int C = * A + * B ;
return C;
}
 E, se usasse referências como parâmetros, poderia ser escrita assim:
int SomaNumeros ( const int & A , const int & B )
{
int C = A + B ;
return C;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 373

13.4.2 ▪ Ponteiros para função.


Um ponteiro para função é uma variável. E ela deverá receber o endereço de uma de-
terminada função.
Agora, na chamada da função, iremos usar o nome da variável no lugar do nome da
função.
Fazemos isso quando não queremos chamar uma função específica (que será sempre a
mesma) e sim queremos abrir a possibilidade de que, em um determinado momento,
possa haver uma troca da função que deve ser executada (o que será feito atribuindo o
endereço de uma nova função àquela variável).
E como o compilador analisa essa chamada de função feita de modo indireto?
Sabemos que quando uma função é chamada pelo seu nome, o compilador faz o se -
guinte:
a. Ele verifica se os parâmetros estão sendo passados de modo correto.
b. E verifica também se não cometemos algum equívoco no aproveitamento do
valor de retorno.
Mas, ao chamarmos uma função a partir de uma variável-ponteiro, como ficam então
essas verificações que devem ser feitas pelo compilador?

[Link] ▪ Definindo os tipos dos ponteiros para função.


Para que o compilador continue cumprindo seu papel de policiar as chamadas de fun-
ção, precisaremos aplicar o protótipo da função a essa variável que pretende substi-
tuir o nome da função.
E isso pode ser feito na definição do tipo dessa variável. Ou seja: o tipo de um pontei-
ro para função deve representar o próprio protótipo da função.
Exemplo:
Considerada a seguinte função:
int funcao ( int Param ) ;
Vejamos como ela poderia ser chamada a partir de um ponteiro:
int main()
{
int ( * pFunc )(int) = funcao; // o endereço “funcao”
// é atribuído à variável “pFunc”
/* Acima, na definição do tipo da variável ponteiro, aplicou-se o
protótipo da função:
a) Primeiro, o retorno : int
b) Depois, entre parênteses, fazemos a indicação de ponteiro(*) seguida
do nome (pFunc) da variável : (* pFunc )
c) Finalmente a lista de parâmetros: (int)
// Agora, chamamos normalmente a função a partir da variável-ponteiro:
pFunc ( 10 );
return 0;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


374 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

É mais interessante contudo criar os tipos previamente (através de typedef). Principal-


mente no caso de funções que recebem um número maior de parâmetros, essa escrita,
inserida diretamente no código, prejudicará a legibilidade.
Exemplo:
Consideradas as seguintes funções:

int funcao_1 ( int Param ) ;


double funcao_2 ( int Param1 , double Param2 );
Vejamos como criar previamente os tipos:
typedef int ( * TipoPtFunc_1 ) (int) ;
typedef double ( * TipoPtFunc_2 ) ( int , double ) ;
E agora utilizamos os tipos, dando mais clareza às declarações das
variáveis-ponteiro:

int main()
{
// Declarar a variável a partir do tipo e
// atribuir o endereço da função:
TipoPtFunc_1 pFunc_1 = funcao_1 ;
// idem:
TipoPtFunc_2 pFunc_2 = funcao_2 ;
// e agora chamamos normalmente as funções a partir das variáveis:
pFunc_1 ( 10 ) ;
double Resultado = pFunc_2 ( 15 , 3.14 ) ;
return 0;
}

[Link] ▪ Ponteiros para função permitem estabelecer eventos.


Ponteiros para funções são muito úteis porque permitem escrever um código genérico
que, nos pontos onde é necessária alguma especificação, pode chamar uma função a
partir de um ponteiro.
 Se chamamos uma função pelo seu nome sermpre será executada a mes-
ma ação (sempre a mesma função).

 Mas se, ao invés disso, invocamos uma variável, ela poderá ser preenchida
de muitas maneiras diferentes. Desse modo, para cada caso poderemos ter
uma função diferente.
Um ponteiro para função pode assim cumprir o papel de um “trigger”(um disparo au-
tomático), propiciando o disparo de eventos sempre que alguma coisa está para acon-
tecer ou logo após ter acontecido.
Podemos usar como exemplo uma rotina genérica para inclusão de dados em arquivos:
a. Se conhecemos as regras gerais de inclusão e gravação podemos deixar o có -
digo básico pronto.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 375

b. Mas para que essa rotina seja realmente útil ela deve ser flexível, isto é, deve
permitir que em cada caso específico o comportamento geral possa ser modifi-
cado.
c. Por exemplo: antes que a inclusão se complete seria oportuno que uma função
fosse chamada para validação final. Se tudo estivesse bem, ela retornaria “ver-
dadeiro” e a inclusão poderia se completar. Do contrário, a inclusão seria sus-
pensa.
d. Mas se usarmos um nome de função não haverá flexibilidade, pois estaremos
chamando sempre a mesma.
e. Já se, ao contrário, usarmos uma variável, diversas aplicações específicas
(cada qual utilizando arquivos diferentes, com necessidades diferentes) poderão
tirar melhor proveito da nossa rotina de inclusão, pois cada uma delas poderá
indicar a sua própria função para o momento da validação.
Vejamos um esboço de código:
// tipo para o ponteiro:
typedef bool ( * TipoPtFunc_EventoPreIncluir )( ) ;
struct Arquivos
{
public:
Arquivos ( ) ; // construtora
bool Incluir ( char * Buffer );
bool Alterar ( char * Buffer );
………………………………………
// variável ponteiro para a função “evento pré incluir”:
TipoPtFunc_EventoPreIncluir m_pfEventoPreIncluir ;
};
Arquivos::Arquivos( ) // construtora
{
m_pfEventoPreIncluir = NULL; // Inicia ponteiro para função-evento:nulo.
// Quando necessário, deverá ser preenchido com um endereço válido.
// Do contrário, simplesmente não será usado
}
bool Arquivos::Incluir ( char * Buffer );
{
// ……………………………. // código preparatório para a inclusão
// agora checa o ponteiro para função
if ( m_pfEventoPreIncluir != NULL ) // se estiver nulo, é porque neste
{ // caso a validação não é necessária
if ( ! m_pfEventoPreIncluir ( ) ) // chama função a partir da variável
{
return false; // a função resolveu impedir que a inclusão
} // seja finalizada
}
// …………………. // completar a inclusão
}

// A estrutura acima poderia ser usada do seguinte modo:


int main ( )
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


376 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Arquivos ArqClientes ;
// preenche o ponteiro com o endereço de uma função específica:
ArqClientes.m_pfEventoPreIncluir = OnPreIncluirClientes ;
………………………………………..
Arquivos ArqFornecedores ;
// usa uma outra função para o arquivo de fornecedores:
ArqFornecedores.m_pfEventoPreIncluir = OnPreIncluirFornecedores;
………………………………………..
return 0;
}

[Link] ▪ Exemplo com ponteiros para função: a estrutura relató-


rio.
Um bom exemplo do uso de ponteiros para função seria a impressão de relatórios. De
um modo geral, diferentes relatórios apresentam características comuns: todos podem
ter um cabeçalho e todos devem ter linhas de detalhe, por exemplo. Além desses atri -
butos comuns, existe uma lógica comum:
 É preciso sempre checar se o total de linhas que cabem em uma folha já foi im -
presso.
 Se isso ocorreu deve-se saltar de página.
 Em seguida, na maioria dos casos, é preciso imprimir um cabeçalho.
 Agora, devem ser impressas as linhas de detalhe.
 etc.

 Desse modo, podemos escrever uma camada de código genérico onde


essa lógica fique pronta.

 Mas, evidentemente, o código genérico não saberá como imprimir as li-


nhas de detalhe, pois este é o aspecto de um relatório que é sempre espe-
cífico.

 Então, nesse ponto da sequência de operações, chamamos uma função a


partir de um ponteiro.
Assim, quem for usar esse código genérico poderá preencher esse ponteiro com o en-
dereço de uma função de impressão específica.
Para cada relatório diferente escrevemos uma função específica que será responsável
pela impressão das linhas de detalhe daquele relatório.
Em seguida preenchemos o ponteiro para função da estrutura de relatório com o ende -
reço dessa função.
Podemos fazer coisa semelhante também para o cabeçalho.
No caso do cabeçalho, o código genérico já pode oferecer uma solução pronta, impri -
mindo uma ou mais linhas de cabeçalho informadas como parâmetro.
Mas ele poderia oferecer também um ponteiro para função, que, se estiver preenchido
com o endereço de uma função específica, permitirá modificar o modo padrão de im-
primir o cabeçalho. E, caso o ponteiro esteja nulo, seria usado simplesmente o cabeça-
lho padrão.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 377

Veja, abaixo, o exemplo de um relatório padrão. Ele está implementado de forma bem
simplificada. Mais a frente teremos oportunidade de ver uma outra versão desse mes -
mo exemplo implementada de modo mais detalhado.
#include <fstream.h>
#include <string.h>
#include <stdio.h>
// tipo de ponteiro para função
typedef bool ( * tpfRelatImpr )();
struct RelatPadrao
{
private: // ATRIBUTOS:
char * m_psCabecalho; // string cabeçalho
short m_nContaFolhas; // folhas impressas
short m_nContaLinhas; //linhas impressas:
short m_nLinhasPorFolha; // linhas por Folha
public:
ofstream m_osSaida; //objeto para imprimir
public: // MÉTODOS
RelatPadrao(char * psCabecalho);
bool Imprime( char * sSaida );
void ImprCabecalho();
// ponteiros para funções:
tpfRelatImpr m_pfCabec ;
tpfRelatImpr m_pfDetalhe;
};
RelatPadrao::RelatPadrao(char * psCabecalho)
{
// armazena strings para cabeçalhos:
m_psCabecalho = psCabecalho;
m_nLinhasPorFolha = 58 ;
m_pfCabec = NULL;
m_pfDetalhe = NULL;
}
void RelatPadrao::ImprCabecalho()
{
if ( m_psCabecalho == NULL ) return ;
if(m_nContaFolhas > 1)
m_osSaida << "\f\n" ; // SALTO DE PÁGINA
// posição para número de folha deve começar com
// um '#' e terminar com um '#'
char * pNum = strchr(m_psCabecalho, '#');
char * pFimNum;
if ( pNum != NULL )
{
*pNum = ' ' ;
pFimNum = strchr(pNum + 1 , '#');
int nQuantosNumeros = (pFimNum==NULL)
? 0 : pFimNum- pNum - 1;
if ( nQuantosNumeros > 0 )
{
sprintf(pNum+1, "%0*d",
nQuantosNumeros, m_nContaFolhas);

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


378 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

*pFimNum = ' ';


}
}
// imprime cabeçalho já com número da folha:
m_osSaida << m_psCabecalho;
// imprime uma linha separadora em branco:
m_osSaida << '\n';
if ( pNum != NULL ) // restaura '#'
{
*pNum = '#';
if ( pFimNum != NULL )
*pFimNum = '#';
}
}
bool RelatPadrao::Imprime(char * sSaida)
{
//se o ponteiro para a função que imprime a linha
//de detalhe estiver NULO, então nada a fazer:
if ( m_pfDetalhe == NULL )
return false;
m_osSaida.open(sSaida);
m_nContaFolhas = 0;
m_nContaLinhas = m_nLinhasPorFolha + 1;
bool bOk = m_osSaida.is_open() ;
while ( bOk )
{
m_nContaLinhas++;
if( m_nContaLinhas > m_nLinhasPorFolha )
{
++m_nContaFolhas ;
// se o ponteiro m_pfCabec estiver NULO, OU se //
contiver o endereço de uma função e esta
// retornar true, então imprime o cabeçalho.
if( m_pfCabec == NULL ||  m_pfCabec() )
ImprCabecalho();
m_nContaLinhas = 3;
}
// AQUI CHAMA O PONTEIRO PARA IMPRIMIR DETALHES:
 bOk = m_pfDetalhe();
} //Fim do laço: encerrará quando bOK se tornar falso.
if ( m_osSaida.is_open() )
{
m_osSaida << "\f\n" ; // Salto de página
m_osSaida.close() ; //Fecha saída
}
return bOk;
}
 // Agora vamos usar a estrutura de relatório.
char Titulo[]= "Titulo Relatorio - Folha =#000#\n";
RelatPadrao Rel( Titulo ); //objeto RelatPadrao;
static int nLinhaTeste=0;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 379

// função para impressão da linha de detalhe. Seu endereço // deverá


ser atribuído ao ponteiro membro da classe
 bool ImprimeDetalhes()
{
nLinhaTeste ++;
// Imprime a linha de detalhe:
Rel.m_osSaida << "Linha = "
<< nLinhaTeste << '\n';
// quando atingir 120 linhas retorna falso,
// finalizando o relatório:
return ( nLinhaTeste <= 119 );
}
int main() // função main
{
// alimenta o ponteiro para função
 Rel.m_pfDetalhe = ImprimeDetalhes;
[Link]("[Link]");
// se quiser enviar para a impressora,
// troque a linha acima para:
// [Link]( "LPT1" )
return 0;
}

13.4.3 ▪ Lógica estruturada através de objetos.


Ponteiros para função, conforme vimos acima, são um recurso importante.
Mas em C++ raramente iremos utilizar esse recurso diretamente.
Isto porque a linguagem suporta a criação automática de ponteiros para função median-
te um recurso denominado “funções virtuais”.
E a utilização destes ponteiros implementados pelo próprio compilador ocorrerá de for-
ma indireta, mais disciplinada, através da lógica interna das estruturas C++.
Mas isso não significa que perdemos tempo no item anterior aprendendo como funcio-
nam os ponteiros para função. Pois, sem essa compreensão, seria praticamente impos-
sível entender, mais tarde, como realmente trabalham as “funções virtuais”.
Este assunto será tratado na próxima seçao (orientação a objetos em C++).
Mas, por enquanto, vamos deixar registrado que as estruturas C++ afetam o fluxo
de processamento através de uma lógica própria:
a. Disparo automático de funções construtoras sempre que uma variável estrutura-
da é criada.
Já vimos funções construtoras na primeira parte desta apostila; mas precisa-
remsos voltar ao assunto.
b. Disparo automático de funções destrutoras sempre que uma variável estrutura-
da é liberada;.
c. A tabela de ponteiros para funções ditas virtuais.
Na próxima seção veremos os ítens acima em detalhe.
E aproveitaremos então para reescrever o exercío “Relatório Padrão” utilizando os
melhores recursos da estrutura C++ e acrescentando novos atributos e funções.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


380 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

13.4.4 ▪ Funções recursivas.


A linguagem admite que uma função chame a si mesma. Essa característica é denomi-
nada recursão.
Na prática, quando uma função chama a si mesma, temos uma situação semelhante a
um laço, já que o conjunto de instruções contido pela função será repetido até que uma
condição de fim ocorra.
Nos casos mais simples a semelhança entre uma função recursiva e um laço é muito
grande.
Já em outras situações, mais complexas, embora o princípio básico seja o mesmo (re-
petir um conjunto de instruções até que uma condição se cumpra), o uso de funções re-
cursivas, ao invés de laços, simplificará muito a lógica necessária.
Vejamos inicialmente um exemplo simples, onde o uso de um laço for ou de uma fun-
ção recursiva quase se equivalem.
Assim, para melhor comparar os dois métodos, vamos escrever duas funções de cálcu-
lo de fatorial.
A primeira usará o laço for; e a segunda será uma função recursiva.
#include <iostream>

// 1) esta versão do cálculo de Fatorial usa o laço for:


double Fatorial_Usando_For( int Numero ) // retorna double pois resultado
{ // pode ser muito grande
double Fatorial = 1;
for ( ; Numero > 1 ; Numero-- )
Fatorial *= Numero;
return Fatorial;
}

// 2) esta versão do cálculo de Fatorial usa uma função recursiva:


double Fatorial_Recursiva ( int Numero )
{
if ( Numero > 1 )
return Numero * Fatorial_Recursiva( Numero - 1 );
else
return Numero;
}

/*
A função acima chamará a si mesma até que Numero fique igual a 1.
Só então começará a retornar. Então, se o número for 3, ocorrerá que:
- ela chamará a si mesma passando 3 menos 1, como parâmetro;
- agora, ela receberá 2 como parâmetro e novamente chamará a si mesma,
desta vez passando 2 menos 1 como parâmetro;
- agora, ela receberá 1 como parâmetro e teremos o primeiro retorno: 1.
- O retorno (1), será multiplicado por 2, retornando este resultado;
- O resultado anterior (2) será multiplicado por 3, dando o retorno final: 6
*/

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 381

int main()
{
cout << int( Fatorial_Usando_For ( 6 ) ) << endl;
cout << int( Fatorial_Recursiva ( 6 ) ) << endl;
cout << int( Fatorial_Usando_For ( 10 ) ) << endl;
cout << int( Fatorial_Recursiva ( 10 ) ) << endl;
return 0;
}
/*
RESULTADO
720
720
3628800
3628800
*/
Assim, os resultados são os mesmos para a versão recursiva e para a versão que usa o
laço for.
Vejamos agora a diferença entre um laço e uma função recursiva.
Uma função pode ter variáveis automáticas (parâmetros, variáveis internas e área de re-
torno).
 Assim a cada nova chamada é criada uma nova instância desse conjunto
de variáveis.

 E poderá ocorrer um estouro de pilha caso a função tenha muitas variáveis


e/ou a condição de fim da recursão esteja distante .
E assim um número muito grande de chamadas continuará pendente, e as
variáveis correspondentes à cada chamada continuarão reservadas, au-
mentando continuamente a ocupação da pilha.
Por isso é preciso um cuidado maior quando usamos funções recursivas.
Mas há situações em que sua utilização (justamente por recomeçar do zero, criando
uma nova instância das variáveis) simplificará enormemente o algoritmo da solução.
São casos em que laços convencionais levariam a um grande aumento de testes de de-
cisão e desvios sucessivos.
Um exemplo clássico são os algoritmos de classificação de dados.
É possível escrevê-los sem usar funções recursivas. Mas isso aumentaria o trabalho e
implicaria em sequências lógicas muito complicadas.
Por outro, em algorítmos desse tipo, a recursão é limitada por sua própria natureza, não
existindo o menor perigo de estouro de pilha.

13.4.5 ▪ Controles de laço.


Um laço permite repetir um bloco de linhas de instrução enquanto uma condição for
verdadeira.
Já utilizamos os laços “for” e “while” em capítulos anteriores. Vamos agora precisar
melhor a sintaxe desses laços e ver o que ainda falta.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


382 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link] ▪ O laço for.


[Link].a ▪ Forma geral e funcionamento.
for ( <Início> ; <condição para continuidade> ; <progressão> )
Onde:
 <Início> : é executado apenas na primeira vez (antes do laço iniciar).
 <Condição para continuidade> : é executada antes de cada passada do
laço, logo é executada também na primeira vez (antes do laço iniciar)
 se a avaliação resultar verdadeira, o bloco de instruções associado ao laço será
executado; do contrário ocorrerá um salto para a primeira instrução após o fim
do laço.
 <Progressão> é executada ao final de cada passada do laço (logo não é exe-
cutada na primeira vez).
Exemplo:
#include <iostream>
int main()
{
for ( int Contador = 1 ; Contador <= 3 ; Contador++)
cout << "Valor de Contador = " << Contador << endl ;

cout << "Valor de Contador apos o fim do laco = "


<< Contador << endl ;
return 0 ;
}
/*
RESULTADO
Valor de Contador =1
Valor de Contador =2
Valor de Contador =3
Valor de Contador apos o fim do laco = 4
*/
Analisando o resultado:
 <Inicio> é executado antes de qualquer coisa; e não mais será executado.
 <Condição para continuidade> é executada em seguida. O valor de Contador
neste momento é 1. Como está menor que 3, a avaliação retorna verdadeiro e
o laço é iniciado (do contrário ocorreria um salto para o fim do laço e ele não se-
ria executado sequer uma vez).
 O bloco de instruções associado ao laço é executado e a linha “Valor de Conta-
dor = 1” é impressa.
 <Progressão> é executada. Contador é incrementado para 2.
 <Condição para continuidade> é executada. O valor de Contador neste mo-
mento é 2. Como está menor que 3, a avaliação retorna verdadeiro e o laço
continua.
 A linha “Valor de Contador = 2” é impressa.
 <Progressão> é executada. Contador é incrementado para 3.
 <Condição para continuidade> é executada. O valor de Contador neste mo-
mento é 3. Como está igual a 3, a avaliação retorna verdadeiro e o laço conti-
nua.
 A linha “Valor de Contador = 3” é impressa.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 383

 <Progressão> é executada. Contador é incrementado para 4.


 <Condição para continuidade> é executada. O valor de Contador neste mo-
mento é 4. Como está maior que 3, a avaliação retorna falso e o laço é inter-
rompido, com um salto para a primeira linha após as chaves de fechamento do
bloco de instruções associado.
 Imediatamente pós o fim do laço, cout imprime o valor de Contador. Pelo que
vimos acima, o valor impresso neste ponto só poderia ser 4.
Em Resumo:
 <Início> [ int Contador = 1 ] – foi executado uma vez (antes do laço iniciar);
 <Condição para continuidade> foi executada antes de cada passada no laço;
portanto, foi executada uma vez a mais (momento em que a condição ficou fal-
sa) que o número de execuções do laço. Logo foi executada quatro vezes.
 <Progressão> foi executada ao final de cada execução das instruções associa-
das ao laço. Logo foi executada três vezes.

[Link].b ▪ Sintaxe.
a. A estrutura de controle:
for ( [... ,] […] ; [… ,] [...] ; [... ,] […] )
<Início> ; <condição ; <progressão>
continuidade> ou <passo>

 A estrutura de controle do laço for é composta por três segmentos ( <início>,


<condição para continuidade> e <progressão> ), separados por ponto e vírgu-
la.
 Cada um dos três segmentos pode conter uma, nenhuma ou diversas instru-
ções, separadas por vírgula.
Exemplo 1 ( o TERCEIRO segmento contem DUAS instruções):
int StrCopia ( char * sDestino, const char * sOrigem, int nMaxBytes )
{
const char * sOrigemInicial ;
for ( sOrigemInicial = sOrigem ;
*sOrigem != '\0' && sOrigem - sOrigemInicial < nMaxBytes ;
 ++sOrigem , ++sDestino )
{
*sDestino = *sOrigem ;
}
*sDestino = '\0' ;
return sOrigem - sOrigemInicial; // retorna:
// quantidade de bytes copiados.
}

Exemplo 2 ( o PRIMEIRO segmento não contém NENHUMA instrução):


double Fatorial_UsandoFor( int Numero )
{
double Fatorial = 1;


REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional
384 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

for ( ; Numero > 1 ; --Numero )


Fatorial *= Numero;
return Fatorial;
}
No exemplo acima, como a variável utilizada para o controle de laço já está com o va-
lor correto para iniciar o laço (pois é o próprio valor que foi passado como parâmetro),
não é necessário usar o segmento <Início> para essa variável (embora possa ser usado
para outras, se for o caso).

b. Instruções associadas:
 Diversas instruções associadas: se precisamos executar diversas instruções,
elas devem compor um bloco e este bloco deve estar imediatamente em segui-
da à estrutura de controle do laço for; desse modo, o bloco estará automatica-
mente associado ao laço:
for ( … ; … ; … )
{
instrucao_1;
instrucao_2;
……………….
}
 Uma instrução única associada: se precisamos executar apenas uma, não é
necessário um bloco. A próxima instrução imediatamente seguinte à estrutura de
controle do laço é automaticamente associada a ele:
for ( … ; … ; … )
instrucao_Unica;
 Nenhuma instrução associada: se tudo o que precisamos executar é a pró-
pria estrutura de controle do laço (não havendo assim nenhuma instrução
associada), assinalamos isso incluindo um ponto e vírgula imediatamente em
seguida à estrutura de controle (que com isso passa a ser uma instrução com-
pleta):
for ( … ; … ; … ) ;
// devido ao ponto e vírgula final, nenhuma
// instrução abaixo será associada a este laço for
Exemplo de um laço for fechado pelo ponto e vírgula:

for ( int Conta = 1 ; Conta <= 12 && Pausa() ; ++Conta ) ;

bool Pausa( )
{
PararProcessamentoPorMilisegundos ( 1 ) ; // pausa
// de 1 milisegundo

if ( AlguemApertouUmaTeclaRecentemente () )
return false;
else
return true;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 385

No exemplo acima, o laço for serve para esperar que alguma coisa ocorra no te-
clado durante um certo tempo. Se “Conta” atingir 12, o laço será interrompido;
e ele será interrompido também se a função Pausa retornar falso.
A cada chamada à função Pausa, o processamento é interrompido por um milisegundo.
Se nesse período algo ocorreu no teclado, a função retornará falso, interrompendo o
for.
O número 12 funciona aqui como um timeout (se nada ocorrer em um pouco mais que
12 milisegundos, então o laço deve desistir de esperar por eventos no teclado).

 Atenção: no exemplo acima, o ponto e vírgula fechando o laço for foi colo-
cado intencionalmente. Mas saiba que um erro comum cometido por pro-
gramadores iniciantes na linguagem é colocar um ponto e vírgula após um
for por distração, ocasionando resultados não desejados.

[Link].c ▪ Cuidados a tomar com o laço for.


 Ponto e vírgula fechando o laço quando isso não se aplica (ver comentário aci-
ma).
 Uso de mais do que uma condição, separadas por vírgulas. Observe os resul-
tados dos dois exemplos abaixo.
int A , B ;
Exemplo 1:

for ( A= 1 , B=4 ; A < 3,A<B ; A++ )


cout << A << endl;
/* RESULTADO (executou TRÊS vezes)
1
2
3
*/

Exemplo 2:

for ( A= 1 , B=4 ; A < B , A < 3 ; A++ )


cout << A << endl;
/* RESULTADO (executou DUAS vezes)
1
2
*/

 Quando colocamos diversas condições separadas por vírgulas em um


laço for, todas são executadas, mas apenas a última é levada em conta
como resultado da avaliação para determinar se o laço deve ter continui-
dade.

Caso quiséssemos combinar as duas comparações teríamos que utilizar o operador ló-
gico adequado em uma única condição, por exemplo:
; A<B && A < 3 ; // AND

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


386 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

ou então:
; A<B || A < 3 ; // OR
Justamente porque existem várias formas de combinar comparações, não seria possível
esperar que o compilador pudesse unir comparações soltas, separadas por vírgula.

[Link] ▪ O laço “while”.


[Link].a ▪ Forma geral e funcionamento.
while ( <condição para continuidade> )
A estrutura de controle de um laço while é mais simples do que a do laço for. Temos
apenas uma condição para continuidade.
Do mesmo modo que no laço for, ela é executada antes da execução das instruções as-
sociadas. E, do mesmo modo, se na primeira avaliação for obtido um resultado falso, o
laço não será executado sequer uma vez.
Exemplo 1:
………………………..
int x = 1 ;
while ( x < 5 )
{
cout << “x= “ << x << endl;
x++ ;
}
/* RESULTADO:
x= 1
x= 2
x= 3
x= 4
*/
Exemplo 2:
………………………..
int x = 6 ;
while ( x < 5 ) // condição estará falsa na primeira avaliação
{
cout << “x= “ << x << endl;
x++ ;
}
/* RESULTADO (nada foi impresso)
*/

[Link].b ▪ Sintaxe.
a. A estrutura de controle:
while ( [ ... , ] [ … ] )
<condição
continuidade>

No único segmento de controle (<condição para continuidade>) do laço while, pode-


mos ter diversas instruções separadas por vírgula.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 387

b. Instruções associadas.
As três situações abaixo são as mesmas de um laço for. Assim, tudo o que foi dito aci-
ma para o for, aplica-se aqui.
 Diversas instruções associadas: se precisamos executar diversas instruções,
elas devem compor um bloco;
while ( condicao )
{
instrucao_1 ;
instrucao_2 ;
……………….
}
 Uma única instrução associada: se precisamos executar apenas uma, não é
necessário um bloco. Será considerada a próxima instrução.
while ( condicao )
intrucao_unica ;
 Nenhuma instrução associada: um ponto e vírgula imediatamente após a es-
trutura de controle do laço torna-a uma instrução completa; nenhuma outra
instrução será associada.
while ( condicao ) ;

[Link].c ▪ Cuidados a tomar com o laço while.


 Ponto e vírgula fechando o laço quando isso não se aplica (ver comentário fei-
to para o laço for).
 Uso de mais do que uma condição, separadas por vírgulas. Do mesmo modo
que no laço for apenas a segunda condição é considerada como resultado
para a avaliação de continuidade. Observe os resultados dos exemplos abaixo.

Exemplo 1:
int a = 1 , b=4 ;
while ( a < 3 , a < b )
{
cout << a << endl ; a++ ;
}
/* RESULTADO:
1
2
3
*/
Exemplo 2:
int a=1 , b=4 ;
while ( a < b , a < 3 )
cout << a << endl ; a++ ;
/* RESULTADO:
1
2
*/

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


388 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

 Em ambos os casos: era isso o que você queria?


Se realmente isso foi intencional, o seu código irá ficar extremamente con-
fuso (o que é o primeiro indício de código mal escrito).

 Cuidado também ao aproveitar vírgulas na condição de continuidade para alte-


rar variáveis:
int a=1 ;
while ( a++ , a < 4 )
cout << a << endl;
/* RESULTADO:
2
3
*/
Se você desejava imprimir 1, 2 e 3 não deveria ter usado esse método. Pois a primeira
instrução dentro da estrutura de controle ( a++ ) é executada em primeiro lugar como
uma instrução independente. Assim, mesmo o operador de incremento estando pós-
fixado, a variável a é incrementada antes da comparação.
E, se você tentasse resolver isso, colocando a instrução de incremento ( a++ ) em se-
gundo lugar só iria piorar as coisas:
int a = 1;
while ( a < 4 , a++ )
cout << a << endl ;
/* RESULTADO:
LOOP PRATICAMENTE INFINITO
*/
O while entende a última instrução da estrutura de controle como aquela cuja ava-
liação deve determinar a continuidade do laço.
E a última instrução no exemplo acima é a++.
Essa instrução se desdobra em duas:
 para o while ela deve dar um resultado lógico (verdadeiro ou falso);
 e, além disso, ela deve ser incrementada (++).
 Ocorre então o seguinte:
 Como o operador de incremento está pós-fixado, o incremento será feito por últi-
mo.
 Então, na primeira vez, o valor de “a” é 1 – e para a linguagem isso significa
verdadeiro (diferente de zero).
 Agora a é incrementado, ficando com 2.
 O laço é executado.
 Na próxima avaliação, sabendo-se que o valor de a é 2, novamente o resultado
da avaliação é verdadeiro (diferente de zero).
 Novamente “a” será incrementada e o laço será executado.
 E assim sucessivamente. Para fins práticos, temos um loop infinito.
 Ele só será interrompido porque, em algum momento, será estourada a capaci-
dade de armazenamento do int com sinal (0x77777777 em 32 bits). Então a va-
riável “a” assumirá um valor negativo e continuará sendo incrementado até atin-
gir zero. E só nesse momento o laço será interrompido.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 389

 E, se fizéssemos:
int a = 0 ; // inicializado com zero
while ( a < 4 , a++ )
cout << a << endl ;
/* RESULTADO:
O LAÇO NÃO FOI EXECUTADO NENHUMA VEZ
*/
 Pois o while avaliou o valor de a como sendo a condição lógica de continuida-
de; na primeira vez a está com zero. Logo o resultado é falso.
 Em seguida a operação de incremento pós-fixada é executada e a fica com 1.
Mas o laço já havia sido encerrado.

[Link] ▪ Considerações gerais sobre os laços for e while (tenha


cuidado com…)

a. No caso do laço for, algumas vezes é bem conveniente o uso das vírgulas
no primeiro segmento (início) ou no terceiro(progressão).
 Contudo, exageros devem ser evitados: muitas vírgulas seguidas, em qual-
quer um desses dois segmentos tornam o código confuso e, em consequên-
cia, mal escrito.
b. Quanto à condição de continuidade(segundo segmento no for; e segmento úni-
co no while):
 tanto no caso do laço for, como no while, considere como “recurso de baixa
legibilidade” o uso da condição de continuidade para incluir diversas instru-
ções separadas por vírgulas.

 Tanto no laço for como no while, raramente (ou mesmo nunca) utilize di-
versas instruções separadas por vírgulas dentro da condição de continuida-
de.

 Um outro problema potencial diz respeito ao uso do operador de atribui-


ção dentro da condição de continuidade dos laços.
Analisaremos este problema mais a frente, ao falar do controle de decisão
“if”, já que isto também se aplica a ele.

[Link] ▪ O laço “do … while”.


[Link].a ▪ Forma geral, funcionamento e sintaxe:
do <instruções associadas> while ( <condição de continuidade> ) ;

Detalhes da sintaxe:

 Também é possível separar instruções com vírgulas na condição de conti-


nuidade. E com os mesmos problemas já levantados acima..

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


390 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Funcionamento:

 A principal diferença deste controle de laço para os anteriores é que a con-


dição de continuidade é avaliada no final, após a execução das instruções
associadas.
Logo, o bloco será executado pelo menos uma vez.

Instruções associadas:
 Diversas instruções associadas:

int x=1;
do
{ // bloco de instruções
cout << x << endl;
x++ ;
}
while ( x < 4 ) ;
/* RESULTADO:
1
2
3
*/
 Uma única instrução associada:
int x=1;
do
cout << x++ << endl; // Bloco não é obrigatório; portanto, sem chave.
// Mas aqui as chaves melhorariam a legibilidade.
while ( x < 4 ) ;
/* RESULTADO:
1
2
3
*/
 Nenhuma instrução associada:
 Neste caso isso não faz sentido pelo seguinte:
int x=1;
do ; // o ponto e vírgula aqui indica “nenhuma instrução associada”
// e, neste caso, o “do” simplesmente está encerrado.
// LOGO, essa linha é ignorada.

E, se, em seguida, escrevêssemos:


while ( x++ < 4 && Pausa( ) ) ;

 Esta linha será tratada como um while (independente do “do” acima)

Portanto não faria sentido escrever:


do ; while ( x++ < 4 && Pausa() ) ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 391

//escrita correta, mas inútil. Bastaria:


while( x++ < 4 && Pausa() );
 E também não podemos escrever o “do” seguido diretamente pelo “while”:
do while ( x++ < 4 && Pausa() ) ; // escrita incorreta.

Exercício: usando o laço “do … while”.


Um bom exemplo seria uma variação do exercício “lista de números pares”.
Se garantirmos, no início, que o primeiro e o último números a imprimir são pares e o
primeiro não é maior que o último, então isso significa que todos serão impressos.
Portanto não é necessário fazer a avaliação no início do laço. Por isso pode ser usado o
“do … while” (a avaliação será feita no final).
Para este exercício, siga os seguintes passos:
1) Peça ao usuário que informe o primeiro e o último números a imprimir.
2) Garanta que eles sejam pares e o primeiro não seja maior que o último.
3) Usando “do … while”, evolua do primeiro ao último imprimindo todos.
Após concluir, compare com a solução abaixo.
#include <iostream>
int main()
{
int iPrimeiro , iUltimo;
bool bDadosCorretos = false ;
cout << "Imprimir lista de numeros pares" << endl;
cout << "Informe o primeiro e o ultimo numeros a imprimir" << endl;

// 1) No laço while abaixo serão alimentados o número inicial e o final


while ( ! bDadosCorretos )
{
cin >> iPrimeiro >> iUltimo ;
// Se um dos dois números não é par...
if ( iPrimeiro & 1 || iUltimo & 1 )
cout << "Digite apenas numeros pares\n" ;
else if ( iPrimeiro > iUltimo ) // primeiro maior que último...
cout << "Primeiro numero nao pode ser maior que o ultimo\n"
else
bDadosCorretos = true; // Isto fará com que
// o while seja interrompido
} // Fim do while para pegar dados.
cout << '\n' << "Lista dos numeros pares entre " << iPrimeiro
<< " e " << iUltimo << endl;
// 2) No laço “do … while” abaixo, serão impressos os números pares.
do // será executado pelo menos uma vez
{
cout << iPrimeiro << endl ; // Imprime.
iPrimeiro += 2 ; // Evolui para o próximo número par.
} while ( iPrimeiro <= iUltimo ) ; // Encerra aqui se a condição for falsa.
return 0;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


392 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

 O criador de C++, Bjarne Stroustrup, considera o “do … while”, uma fonte


de erros e confusões. Isto porque, como o laço é sempre executado pelo
menos uma vez, determinadas mudanças feita no código anterior ao laço
podem modificar a situação, de tal modo que a primeira execução torne-se
agora incorreta.
É o que aconteceria, no exercício acima, se alguém modificasse a primei-
ra parte, suprimindo a análise que é feita para impedir que o número inicial
seja maior que o final. Neste caso o primeiro número passaria a ser im-
presso, pelo fato de que a condição (iPrimeiro <= iUltimo) só é avaliada
ao final.

 Isto poderia ocorrer principalmente em situações semelhantes a essa,


mas onde as duas partes do código estivessem escritas em funções sepa-
radas
(e talvez até em módulos separados), de tal modo que, ao alterar a pri-
meira parte, o programador não visse (ou não lembrasse) que, na segunda
parte, é usado um
“do … while” ao invés de um “while”.
Até porque o programador responsável pela alteração pode não ser o mes-
mo que criou o código original.
Mas sempre que não exista essa possibilidade de que o laço possa ser
afetado por outro trecho de código, e nas situações em que sempre será
preciso executar o laço pelo menos uma vez, então o “do … while” é uma
alternativa melhor que o “while”.

13.4.6 ▪ Desvios por salto incondicional.


[Link] ▪ Desvio return.
Um return provoca um retorno de função. Isto significa que ele retorna sempre ao
ponto imediatamente seguinte à chamada de função. Então o return é um desvio es-
truturado, pois o programador não é livre para indicar o alvo desse desvio.
Se a função retornar valor, o return copiará esse valor para a memória de retorno, an-
tes de proceder ao desvio para retornar.
Um return pode ser incluído em qualquer ponto de uma função e não tem, em si mes-
mo, nenhum recurso para teste de condição.
Contudo pode ser associado a algum outro controle de fluxo que permita um teste de
condição, como, por exemplo o if:
if ( Condicao )
return ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 393

[Link] ▪ Desvio break.


Um break pode interromper um laço ( “for”, “while” e “do … while”) ou o controle
de decisão “switch” (que veremos no próximo ítem).
Em um laço, o break provoca um salto para a próxima instrução após o fim do laço
(exatamente como ocorreria se o laço tivesse sido encerrado normalmente).
Assim sendo, o break também é um desvio estruturado, pois o programador não é li-
vre para indicar o alvo do salto.
O break também não tem um recurso próprio para teste de condição. Mas pode ser as-
sociado a qualquer outro controle de fluxo que ofereça o teste de condição.
Exemplo:
 No exemplo abaixo, pede-se ao usuário que informe as notas de um aluno para
cálculo de média.
 Um laço é usado para que o usuário informe a próxima nota até atingir o máxi-
mo de notas.
 Se, contudo, o aluno tiver menos notas do que o máximo, o operador poderá in-
terromper o laço a qualquer momento digitando um número negativo. Para esta
interrupção será usado um break.
#include <iostream>
#define MAX_NOTAS 6
int main( )
{
int TotalNotas = 0;
double Soma = 0;
double NotaDaVez = 0;
cout << "Calculo de Media do Aluno" << endl;
cout << "Digite ate " << MAX_NOTAS <<
" notas, entre zero e dez" << endl;
cout << "(para interromper, digite -1)" << endl << endl;
while ( TotalNotas < MAX_NOTAS )
{
cin >> NotaDaVez;

if ( NotaDaVez < 0 ) // Número negativo: não há mais notas a informar.

 break ; // Salta para a primeira instrução após o fim do laço.

TotalNotas++ ;
Soma += NotaDaVez ;
}

cout << endl;


cout << "Total de Notas Informadas: " << TotalNotas << “\n\n”;
if ( TotalNotas > 0 ) // Prevenir divisão por zero.
{
[Link](5) ; // Largura máxima do número a imprimir (incluindo decimais e ponto)
[Link](3) ; // Quantidade de casas decimais (mais o ponto) a imprimir
// Neste caso, será arredondado para duas casas decimais

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


394 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

cout << "Media = " << Soma / TotalNotas << endl;


}
else
cout << "Media zero por ausencia de notas" << endl ;
return 0;
}
/* RESULTADO (notas informadas: 7, 6 e 9)
Calculo de Media do Aluno
Digite ate 6 notas, entre zero e dez
(para interromper, digite -1)
7
6
9
-1
Total de Notas Informadas: 3
Media = 7.33
*/

[Link] ▪ Desvio continue.


Conceitualmente, um continue provoca um salto para o fim de um laço. Mas, na im-
plementação, os compiladores podem levar em conta as diferenças entre os diversos la-
ços para, assim, gerarem um código de máquina mais eficiente.
Em um laço “for” este salto tem como alvo a linha onde está o segmento de progres-
são do laço. Em, seguida será executada a condição de continuidade.
Em um laço “while”, saltar para o fim significa voltar para nova execução da condi-
ção de continuidade.
Resumindo:
 em um laço “for”, um continue despreza (saltando) todas as linhas entre ele e
a progressão; então a progressão é executada imediatamente e, em seguida,
ocorre o salto normal para o teste de condição no início do laço.
 em um laço “while”, um continue despreza (saltando) todas as linhas entre ele
e o fim do laço: então deve ser executado imediatamente o teste de condição
no início do laço (não faria sentido saltar para o fim para então usar o salto nor -
mal para o início).
 em um laço “do … while” o salto para o fim coincide com o teste de condição.
O continue também é um desvio estruturado, pois o programador não é livre para in-
dicar o alvo do salto. E pode ser associado a qualquer teste de condição.
Exemplo:
 No exemplo anterior (cálculo de média, utilizado para exemplificar o break), ha-
via um erro. Embora seja definido que a nota máxima é dez, o programa não im-
pede que seja informada uma nota acima de 10. Iremos corrigir isso agora,
usando o “continue”.
 Desse modo, o laço while do exemplo anterior ficaria assim:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 395

while ( TotalNotas < MAX_NOTAS )


{
cin >> NotaDaVez;
if ( NotaDaVez < 0 ) // Não há mais notas a informar.
break ; // Salta para a primeira instrução após o fim do laço.
if ( NotaDaVez > 10 )
{
cout << "Nota não pode ser superior a 10" << endl ;

 continue ; // Salta para o fim do laço, o que, aqui,


// significa voltar imediatamente à linha onde é feito
// o teste da condição de continuidade.
}
TotalNotas++ ;
Soma += NotaDaVez ;
}
O continue, acima, cumpre o seu papel, desprezando todo o código abaixo.
Mas, exceto quando é necessário desprezar um trecho de código muito grande, talvez
contendo muitos outros testes de decisão(e, então, um continue irá simplificar a lógi-
ca), devemos dar preferência a controles que permitam uma única direção de fluxo
(sempre para a frente) pois isto simplifica a leitura.
Assim, o exemplo acima ficaria melhor do seguinte modo:
…………………………….
if ( NotaDaVez > 10 )
{
cout << "Nota não pode ser superior a 10" << endl ;
}
else // mais claro !!!
{
TotalNotas++ ;
Soma += NotaDaVez ;
}
…………………………….
ATENÇÃO: se estivéssemos usando um laço for para a situação acima,
teríamos o seguinte problema:

for ( TotalNotas = 0 ; TotalNotas < MAX_NOTAS ; ++TotalNotas)


{
cin >> NotaDaVez;
if ( NotaDaVez < 0 ) // Não há mais notas a informar.
break ; // Salta para a primeira instrução após o fim do laço.
if ( NotaDaVez > 10 )
{
cout << "Nota não pode ser superior a 10" << endl ;
 continue ; // Salta para o fim do laço, o que, aqui, significa saltar
// para a instrução de progressão. Em seguida, executará
// a avaliação da condição de continuidade.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


396 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Soma += NotaDaVez ;
 } // Fim do laço, a progressão será executada agora.
O exemplo acima, com o laço for, levará a resultados errados.
 Isto ocorrerá se a variável NotaDaVez, em algum momento, apresentar um nú-
mero superior a dez. Neste caso o valor será desprezado (devido ao continue).
 Mas a próxima coisa que será executada será a progressão: TotalNotas++.
 E isso significa que será considerada uma nota a mais (e a média ficará
menor).
 Usando o laço for não seria possível usar o continue desse modo. E para re-
solver o problema seria preciso complicar o código, deixando-o mal escrito:
if ( NotaDaVez > 10 )
{
cout << "Nota não pode ser superior a 10" << endl ;
 --TotalNotas ; // Decrementa TotalNotas, para compensar.
// Isto resolve, mas é horrível !
continue ; // Vai saltar para a instrução de progressão: ++TotalNotas
}

 Observe que o problema aqui não está no continue e sim no fato de estar-
mos usando o laço for.
Pois se usássemos o “if … else” daria na mesma:

Conforme podemos ver no código abaixo:

for ( TotalNotas = 0 ; TotalNotas < MAX_NOTAS ; TotalNotas++ )


{ …………………………….
if ( NotaDaVez > 10 )
{
cout << "Nota não pode ser superior a 10" << endl ;
 TotalNotas-- ; // Precisa decrementar TotalNotas, pois, após este
// if-else, teremos o fim do bloco, e, em seguida, TotalNotas
// será incrementada. Péssima solução! 
}
else
Soma += NotaDaVez ;
 } // Final do laço: a progressão será executada agora (++TotalNotas).

 Em determinadas situações não haverá uma diferença drástica entre usar


um laço while ou um laço for.

 Mas, em outras ocasiões, essa escolha fará uma grande diferença.

No exemplo acima, temos uma situação em que o laço while é a melhor


opção, representando a escolha correta.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 397

E o laço for revelou-se inadequado para o caso.

 Assim, devemos analisar a situação específica para saber qual o controla-


dor de laço adequado a cada uma.
E um critério importante para essa análise é:
Se usamos uma variável como contadora (determinando a progressão de
um laço) só devemos usar o laço for se a variável for alterada apenas no
segmento de progressão da estrutura de controle e não nas instruções
associadas ao laço.
Pois, caso isso ocorra, em geral o laço while será uma escolha me-
lhor.

[Link] ▪ Desvio goto.


O goto é um desvio minimamente estruturado. O programador não é livre para saltar
de um ponto de uma função para outro ponto em outra função.
Mas, dentro da mesma função, o programador é livre para escolher o alvo do desvio.
E o alvo para um desvio pode ser definido através de um rótulo.

[Link].a ▪ Sintaxe.
goto <rótulo> ; ……… <rótulo:>
Exemplo:
………………
if ( Condicao )
goto Saltou ; // O rótulo “Saltou” só pode estar
// nesta mesma função
………………
Saltou: // o rótulo “Saltou” indica que o processamento
// continuará na próxima instrução
Na primeira parte desta apostila, dissemos que devemos evitar o goto, justamente por-
que, ainda que dentro de uma mesma função, ele permite quebrar sequências lógicas
estruturadas, criando um código confuso e entrelaçado.

Por exemplo:
………………..
int x , y , z , CondicaoDeFim ;
…………………..
if ( x < y )
goto Rotulo ;
……………………
if ( x > z )
{
Rotulo:
…………..
goto Fim:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


398 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

}
……………..
Fim:
if ( ! CondicaoDeFim )
goto Rotulo;
…………………

Acima temos uma situação típica de entrelaçamento de diversas direções de fluxo que
tornará a sua leitura e análise mais difícil. As estruturas de decisão são quebradas e há
várias maneiras de atingir um ponto do processamento.
Evidentemente o exemplo acima mostra o pior caso de uso do goto.
Contudo há situações em que o goto é aceitável (e até recomendável). Vejamos então
as situações em que ele não é aceitável e aquelas em que é.

[Link].b ▪  1 - goto: usos não aceitáveis.


1) Nem precisamos falar da situação exemplificada acima. Fluxos entrelaçados não
são aceitáveis, seja qual for o pretexto. Há sempre uma forma melhor de resolver o
problema.
2) Além disso devemos estabelecer que um goto nunca deve ser usado para fazer
com que o fluxo de processamento volte a um ponto anterior. Isto também tornará
o código mais confuso. Se o goto for necessário, ele deverá saltar para um ponto
posterior, mantendo a direção do fluxo “sempre para a frente”.
3) Um outro uso não aceitável é quando utilizamos o goto para criar um laço, sendo
que os controles de laço da linguagem permitem resolver qualquer situação desse
tipo.
4) E é justamente por isso que temos mais do que um controle de laço. Em determi-
nadas situações um for se ajusta melhor; em outras o while. E ainda temos a alter-
nativa do do … while. Simplesmente não existe justificativa para usar o goto com
esta finalidade.
Além de contar com desvios estruturados, os laços formais da linguagem tornam a
escrita do código muito mais simples e clara. Basta comparar:
1 - Laço for:
for ( int Contador = 1 ; Contador <= 3 ; Contador++)
cout << "Valor de Contador = " << Contador << endl ;

2 – Laço implementado com goto:


Contador = 1;
Inicio:
if ( Contador > 3 )
goto Fim;
cout << "Valor de Contador = "<<Contador << endl;
Contador++;
goto Inicio;
Fim:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 399

[Link].c ▪  2 - goto: uso aceitável.


Em uma situação em que temos laços aninhados (um laço dentro de outro) o goto se
justifica, sendo até recomendável.
Em tais situações, utilizado corretamente, o goto virá simplificar a lógica tornando o
código mais bem escrito e também mais eficiente.
Exemplo; // uma planilha será preenchida com dados informados pelo usuário;
// o processamento será encerrado quando ele digitar zero.
int main( )
{
enum { MAX_COLS = 6 , MAX_LINS = 12 } ;
double PlanilhaCalculo [ MAX_LINS ][ MAX_COLS ] ;
double Valor ;
for ( int Linha = 0 ; Linha < MAX_LINS ; Linha++ )
{
for ( int Coluna = 0 ; Coluna < MAX_COLS ; Coluna ++ )
{
cin >> Valor ;
if ( Valor < 1 )

 break; // Este break encerrará o laço mais interno.


PlanilhaCalculo[ Linha ][ Coluna ] = Valor ;
}
// É preciso consistir novamente aqui se Valor está com zero:
if ( Valor < 1 )
 break; // E este break encerrará o laço mais externo.
}
………………. // instruções para gravação ou impressão...
return 0;

} // Fim de main()

 Como o break encerra apenas o laço onde está inserido, logo após o encerra-
mento deste laço precisamos checar novamente o conteúdo da variável para
poder encerrar o laço mais externo.
 E, se tivéssemos ainda mais aninhamentos (um terceiro ou um quarto laços),
pior ficaria (precisaríamos de uma sucessão de três ou quatro break’s).
 Apenas se, após o fim do primeiro laço, houvesse algo a ser feito (e só depois
seria usado o segundo break) é que o primeiro break seria útil. Do contrário,
torna-se melhor usar o goto.
 Além de mais eficiente (menos instruções, logo maior velocidade) o código tam-
bém ficará mais simples e legível, conforme podemos ver abaixo:
int main( )
{ …………………………………….
for ( int Linha = 0 ; Linha < MAX_LINS ; Linha++ )
{
for ( int Coluna = 0 ; Coluna < MAX_COLS ; Coluna ++ )
{
cin >> Valor ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


400 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

if ( Valor < 1 )
 goto Fim ; // Este goto irá encerrar os dois laços.
PlanilhaCalculo[ Linha ][ Coluna ] = Valor ;
}
}
}
Fim:
………………. // instruções para gravação ou impressão...
return 0;
} // Fim de main()

13.4.7 ▪ Funções inline.


Funções inline são escritas como uma função qualquer, mas não serão executadas
como uma função.
Pois o compilador simplesmente troca a chamada da função pelo código da própria
função em todos os lugares em que ela estiver sendo chamada.
Melhor dizendo: o compilador avalia o que é melhor fazer (melhor relação “custo/be-
nefício”).
 Assim, se a função estiver sendo chamada em muitos lugares, mas for peque-
na (contendo poucas instruções) o compilador irá trocar as chamadas pelo códi-
go da função. No executável, portanto, a função não existirá.
 Se a função for grande (contendo muitas instruções) mas estiver sendo chama-
da em um único lugar o compilador também irá efetuar a troca, pois de qual-
quer modo essa quantidade de linhas de instrução ficará em um único local. No
executável, portanto, a função não existirá.
 Já se a função for grande (contendo muitas instruções) e estiver sendo chama-
da em diversos lugares, então o compilador irá avaliar até que ponto a opera-
ção de troca aumentará o tamanho do executável. Desse modo a função poderá
existir ou não no executável.

O critério de avaliação do compilador portanto é baseado no tamanho final do executá-


vel.
Assim podemos usar funções inline para tornar mais legível e simples o uso de deter-
minadas operações, nomeando-as.
Vejamos alguns exemplos:
// Determinando se um ano é bissexto:
inline bool Bissexto(short nAno)
{
return (( nAno & 3) == 0) &&
((nAno % 100) != 0 || (nAno % 400) == 0);
}

 Uma operação, como a do exemplo acima, não é rapidamente legível.


Se, ao invés da função, inseríssemos essa operação em todos os lugares

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 401

onde fosse necessária, teríamos sempre que fazer um comentário para que
o seu sentido ficasse claro.
Muito melhor uma função inline:
Além da clareza, pelo fato de a operação ter recebido um nome que já in-
dica a sua finalidade, a função mantem esse código em um único lugar, fa-
cilitando manutenções futuras.
Podemos também usar funções inline para garantir que determinadas operações
sejam sempre realizadas de modo completo, ganhando assim em segurança.
 Por exemplo: ao usar a função strncpy, corremos um risco. Se a origem da có-
pia for maior que a quantidade de bytes passada no terceiro parâmetro, essa
função irá copiar até o último byte. Não ultrapassará o tamanho de memória in-
dicado, mas também não colocará o terminador zero no último byte.
Portanto, a seguinte função inline seria útil:
inline void CopiaString ( char * Destino, const char * Origem , int
nBytes )
{
strncpy ( Destino , Origem , nBytes );
Destino [ nBytes ] = ‘\0’;
}

Além disso podemos querer isolar e nomear determinados trechos inter-


nos de uma função maior apenas para dar legibilidade. É o caso, por ex-
emplo, dos laços aninhados:
 A escrita de funções tem a vantagem de melhorar a legibilidade ao dar um
nome a cada nível do aninhamento e também por isolar o começo e fim de
cada um.
 Pois, quando temos muitos aninhamentos temos dois problemas: em primeiro
lugar, entender o que cada nível está fazendo; e, em segundo lugar, procurar
onde começa um nível e onde acaba o outro (imagine por exemplo uma situa-
ção com quatro ou mais aninhamentos).
 Contudo em C isso era mais oneroso, pois perdemos eficiência efetuando cha-
madas de função para um código que na realidade só será executado naquele
ponto
 Em C++ podemos criar funções inline. E isso resolve o problema da perda de
performance.

13.4.8 ▪ Controles de decisão.


[Link] ▪ Os controles de decisão if e else.
[Link].a ▪ Sintaxe.
if ( <condicao> )
if ( <condicao> ) … else ….
O if deverá estar sempre seguido de uma condição entre parênteses.
 A condição será testada: se verdadeira, serão executadas uma ou mais instru-
ções associadas.
Já o else não é um controlador de fluxo independente.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


402 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

 Assim, ele não pode ser usado separadamente.


 Um else só pode ser usado para completar um if, e será usado sempre que for
necessário indicar instruções que devam ser executadas quando a condição
analisada pelo if estiver falsa.

[Link].b ▪ Instruções associadas:


 Diversas instruções associadas. Tanto para o if como para o else, sempre
que houver mais do que uma instrução associada deve ser usado um bloco, de-
limitando o conjunto de linhas de instrução que deve ser executado caso a con-
dição seja verdadeira (if) ou falsa (else):
if ( condicao ) // executará o bloco abaixo se a condição for VERDADEIRA
{
instrucao_1 ;
instrucao_2 ;
………………..
} // neste caso, se a condição for falsa, não há nada que precise ser feito

if ( condicao ) // executará o bloco abaixo se a condição for VERDADEIRA


{
instrucao_1 ;
instrucao_2 ;
………………..
}
else // executará o bloco abaixo se a condição for FALSA
{
instrucao_1 ;
instrucao_2 ;
………………..
}

 Uma única instrução associada. Neste caso o bloco não é necessário, poden-
do ou não ser usado.

if ( condicao ) // executará a instrução abaixo se a condição for VERDADEIRA


instrucao_unica ;
if ( condicao ) // executará a instrução abaixo se a condição for VERDADEIRA
instrucao_unica ;
else // executará a instrução abaixo se a condição for FALSA
instrucao_unica ;
OU
if ( condicao ) // executará o bloco abaixo se a condição for VERDADEIRA
{
instrucao_1 ;
instrucao_2;
………………..
}
else // executará a instrução abaixo se a condição for FALSA
instrucao_unica;

OU

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 403

if ( condicao ) // executará a instrução abaixo se a condição for VERDADEIRA


instrucao_unica;
else // executará o bloco abaixo se a condição for FALSA
{
instrucao_1 ;
instrucao_2;
………………..
}

OU
if ( condicao_1 )
instrucao_1;
else if ( condicao_2 )
instrucao_2;
else
intrucao_3;
 Nenhuma instrução associada. No caso do if isto não faz sentido.
 Assim, o código abaixo provocará uma advertência do compilador (algo como
“é isso mesmo que você quer fazer?” ):
if ( condicao ) ; // o ponto e vírgula indica que
// nenhuma instrução foi associada ao if

 Ao contrário de um laço, um if executará uma única avaliação da condi-


ção.
Assim sendo, tanto faz que essa avaliação retorne falso ou verdadeiro:
será executado aquilo que estiver após o if em ambos os casos.

 Em um laço podemos ter::


while ( Pausa ( ) ) ;
 E isto faz sentido, porque a função Pausa (que deverá retornar verdadeiro ou
falso), poderá ser executada muitas vezes (até que ela retorne falso, provocan-
do o encerramento desse laço).
 Já se fizéssemos :
if ( Pausa( ) ) ;
 Seria a mesma coisa que, simplesmente:
Pausa( ) ;
 Pois a função, em ambos os casos, seria executada uma única vez, sem que o
seu retorno tenha qualquer consequência.
 Assim, uma linha de instrução como essa [ “if ( Pausa( ) ) ; “ ], ou é o resultado
de uma distração (e por isso o compilador emite um aviso) ou não passa de uma
grande bobagem.

[Link].c ▪ Precauções no uso do controlador if.


1) Aninhamento.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


404 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

 Ao usar um if dentro de outro, é aconselhável utilizar blocos seja para tornar


mais rápida a releitura do código, seja para evitar enganos. Assim:
int x , y ;
……………
if ( x > 5 )
y = x ; // Esta é uma instrução completa terminada no ponto e vírgula:
……………. // “se ‘x’ for maior que 5, copie o valor de ‘x’ para ‘y’ ”

// Na linha anterior não tivemos novidades.


// Vamos agora inserir um if aninhado
if ( x > 5 )
if ( y == 0 )
y = x ; // Aqui termina uma instrução completa:
// “se ‘x’ for maior que 5,
// e se ‘y’ for igual a zero, copie o valor de‘x’ para‘y’ “

// E agora vamos introduzir um else:


if ( x > 5 )
if ( y == 0 )
y=x; // Aqui termina uma instrução completa:
“se ‘x’ for maior que 5,
// e se ‘y’ for igual a zero, copie o valor de ‘x’ para ‘y’ “
else
x = y ; // Aqui termina outra instrução completa:
// “se ‘x’ for maior que 5,
// e se ‘y’ não for igual a zero, copie o valor de ‘y’ para ‘x’ “
 O trecho acima está correto. Mas poderia ser escrito também assim:
if ( x > 5 ) // Se ‘x’ for maior que 5 execute o bloco abaixo.
{
if ( y == 0 ) // Se ‘y’ for igual a zero execute a instrução abaixo.
y=x;
else // Do contrário ( ‘y’ diferente de zero), execute a instrução abaixo.
x =y;
}

 A segunda alternativa é melhor porque é mais clara e também porque ajuda a


evitar distrações. E o que ela deixa claro é que não queríamos fazer isto:
// Terceira alternativa (que não é a desejada neste caso):
if ( x > 5 ) ) // Se ‘x’ for maior que 5 execute o bloco abaixo.
{
if ( y == 0 ) // se ‘y’ for diferente de zero, não haverá nada a fazer.
y=x;
}
else // Do contrário, ( ‘x’ menor ou igual a 5) execute a instrução abaixo:
x =y;

 Um else está sempre relacionado ao último if que foi empregado. Por isso as
duas primeiras alternativas levam ao mesmo resultado. Mas perceba que quan-
do não usamos as chaves abrimos mão de ser taxativos (e, caso o programa
apresente problemas, alguém que releia o código poderá ter dúvidas e perder

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 405

tempo analisando se não deveria ter sido usada a terceira alternativa – embora
o problema não esteja nesse ponto).
 Com as chaves estamos optando de forma intencional e direta por uma deter-
minada alternativa lógica. Estamos afirmando: “não deve haver qualquer dúvi-
da”.

2) Utilização do operador de atribuição dentro da condição.


O fato de o operador de atribuição utilizar como símbolo um sinal de igual e o opera-
dor de comparação por igualdade utilizar dois sinais de igual seguidos, costuma ser
uma fonte de erros que ocorrem com alguma frequência entre programadores iniciantes
na linguagem.
Por exemplo:
if ( x == 1 ) // aqui foi usado o operador de igualdade
……………….. // (simbolizado por um dois sinais de igual)

É muito diferente de:


if ( x = 1 ) // e aqui foi usado o operador de atribuição
……………….. // (simbolizado por um único sinal de igual)
E o que, exatamente, será provocado pela linha acima?
 Em primeiro lugar, o número inteiro 1 será atribuído à variável ‘x’.
 Em segundo lugar, será executado o seguinte teste de condição:
 Pergunta: o resultado da operação anterior é verdadeiro (diferente de zero)
ou falso (igual a zero) ?
 Como o resultado da operação de atribuição é 1 (verdadeiro) então o resulta-
do lógico é “a condição é verdadeira”.
Portanto, serão executadas as instruções associadas ao “if”.
 E, como a atribuição é feita com um valor constante (1), o resultado também
será constante. Desse modo, o “if” não faz o menor sentido: pois simples-
mente as instruções a ele associadas serão sempre executadas.

 E a única conclusão possível, nesse caso, é que ocorreu um descuido do


programador, esquecendo de inserir o segundo sinal de igual para que fos-
se estabelecida a comparação: if ( x == 1 ).
A linguagem permite esse modo de escrita porque em alguns casos isso pode servir
para economizar linhas de código. Mas, obviamente, isso só fará sentido se o resulta-
do da operação não for constante.
Vejamos o seguinte exemplo:
int x , y ;
…………………..
if ( x = y )
…………………..

No caso acima, ocorrerá o seguinte:


 Em primeiro lugar, o valor de ‘y’ será copiado para ‘x’ (atribuição).
 Agora, o resultado será testado (verdadeiro ou falso?).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


406 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

 Como ‘y’ é uma variável, em alguns casos poderá conter valores diferentes de
zero (levando à decisão: verdadeiro); e, em outros, poderá estar valendo zero
(levando à decisão: falso).
 Isto faz sentido.
Esse modo de escrita permitiu economizar uma linha adicional de código fonte. Senão,
teríamos que escrever as duas operações (atribuição e comparação com zero) como li-
nhas de instrução distintas:
x = y ; // Copie o valor de ‘y’ para ‘x’.
if ( x ) // Agora, analise se ‘x’ é verdadeiro (diferente de zero)
…………
Do ponto de vista do resultado final não há diferença entre uma forma e outra. Mas é
verdade também que, quando usamos o modo econômico, fica a dúvida: foi intencio-
nal ou também aqui tivemos um descuido ?
De todo modo, se utilizada uma constante, temos certeza: foi um descuido.
Mas, se utilizada uma variável, pode ser intencional ou não.
E, se o programa estiver apresentando problemas, é muito possível que alguém suspei-
te dessa linha de código e perca tempo analisando a situação lógica.
Por isso mesmo, alguns compiladores emitem um aviso, dizendo mais ou menos o se-
guinte: “esse uso é intencional?”.
E, para não receber essa mensagem de advertência, precisaremos escrever do seguinte
modo:
if ( ( x = y ) )
…………….
Escrevendo dessa forma, estamos dizendo ao compilador (e a outros programadores
que releiam esse código):
“Eu sei o que estou fazendo: propositalmente, esta linha contem duas opera-
ções, primeiro uma atribuição (parênteses interno) e depois um teste de con-
dição (parênteses externo).”
Infelizmente, nem todos os compiladores emitem esse aviso (já que, pelo padrão da lin-
guagem, eles não são obrigados a fazer isso).

 Sempre prefira a clareza. Faça com que suas intenções estejam presentes
no código: ou não utilize operações de atribuição dentro de testes de condi-
ção, ou então seja o mais explícito possível, utilizando os parênteses.

[Link].d ▪ A precaução com o operador de atribuição também se aplica


aos laços.

 Tome um cuidado especial ao utilizar esse modo de escrita em laços.


Pois, além de atribuições constantes serem um erro (do mesmo modo que
no “if”), precisaremos prestar mais atenção mesmo nas atribuições variá-
veis.
Por exemplo: a linha abaixo faz sentido ?
while ( ( x = y ) )

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 407

………………..
Depende:
1) Se o valor de ‘y’ for alterado em uma das instruções associadas ao laço e su-
pondo-se que em algum momento ‘y’ fique falso (para que não tenhamos aí um
laço infinito), então faz sentido.
2) Do contrário isso não faz sentido. Pois sempre chegaremos ao laço com ‘y’ verda-
deiro ou falso. E então, já que o seu valor não será alterado, temos apenas duas
possibilidades:
 Nas ocasiões em que estiver verdadeiro o laço será infinito.

 E, quando estiver falso, o laço simplesmente não será executado sequer uma
vez.
[Link] ▪ O controle de decisão switch.
[Link].a ▪ Sintaxe.
switch ( <expressão> )
{
case <constante> :
<instruções>
[ case <constante> :
<instruções> ]
….............................
[ default :
<instruções> ]
}
 A cláusula “default” é opcional.
 O “switch” e pelo menos um “case” são obrigatórios.
 Não pode existir “default” sem pelo menos um “case”.

[Link].b ▪ Funcionamento.
O switch irá executar <expressao>, que pode ser a leitura de uma variável, ou uma
chamada de função que retorne um valor, ou a avaliação de uma expressão lógica qual -
quer.
 A avaliação deverá retornar um resultado e esse resultado será agora compara-
do, por igualdade, às constantes associadas a cada rótulo case.
 Quando a comparação for verdadeira serão executadas todas as instruções
abaixo do rótulo case onde a igualdade ocorreu até o final do switch.
 Se nenhuma das constantes for igual ao resultado de <expressao>, então se-
rão executadas as instruções abaixo do rótulo “default” (caso este exista).
Para melhor entender em que situações podemos usar este controlador de fluxo, vamos
ver primeiro um exemplo com “if … else”:
int x ;
………………….
if ( x == 1 )
{
....................
}
else if ( x == 2 )

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


408 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

{
....................
}
else
{
....................
}
O conjunto [ if ... else if... else ... ] acima, tem as seguintes características:
1) O valor que está sendo analisado é sempre o mesmo. No exemplo acima, esse
valor é o resultado da leitura da variável “x”. Mas poderia ser o valor resultante de
qualquer expressão.
2) O operador empregado é sempre o operador relacional de igualdade, não sendo
usado qualquer outro operador relacional ou lógico.
3) E apenas uma comparação por igualdade é feita em cada uso do if.
4) O valor analisado é sempre comparado a um valor constante (1 e 2, no exemplo).
Nestes casos, podemos usar:
switch ( x )
{
case 1:
....................
break;
case 2:
....................
break;
default:
....................
}
Agora iremos analisar uma característica de fluxo do switch, já mencionada, mas que
precisa ser melhor explicada.
E essa explicação permitirá esclarecer porque motivo, no exemplo acima, foram usados
dois desvios break.
Vejamos, passo a passo, como o switch é executado:
 A expressão é comparada (por igualdade) à constante associada a cada rótu-
lo case.
 Se, em em todos eles, a comparação por igualdade for falsa, então serão exe-
cutadas as instruções existentes abaixo do rótulo default
 Se o rótulo default não existir(pois é opcional), nada será executado.
 Se, em um determinado rótulo case, a comparação for verdadeira (a variável
está igual à constante associada a esse case), então serão executadas todas
as instruções abaixo desse case até o fim do switch (delimitado pela chave de
fechamento).
 Isto significa que, daí para baixo, a comparação não será mais feita
 Por isso, sempre que esse comportamento não for desejado devemos utilizar
um desvio incondicional, para saltar para a próxima instrução após o fim do
switch.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.4 • Controles do fluxo de processamento. • C++ • AGIT informática • 409

 O break é o mais utilizado, pois ele “entende” a lógica do switch e salta exata-
mente para a primeira instrução após o fechamento de chaves do switch.
 Se, após o switch, não existirem linhas de instrução (o que ocorre quando, logo
após o término do switch, temos o término da função em que ele está inserido),
então ao invés do break podemos usar o return.
 Quanto ao goto, devemos usá-lo apenas em casos de aninhamento (quando ti-
vermos um switch dentro de outro, ou um switch dentro de um laço), confor-
me já vimos mais acima a respeito de laços aninhados.
 O desvio continue não se aplica ao switch. Assim só podemos usá-lo em um
switch se este estiver dentro de um laço (do contrário ocorrerá erro de compi-
lação).
Se o switch estiver dentro de um laço, o continue desvia para a instrução de
progressão (laço for), ou diretamente para a avaliação da condição de conti-
nuidade (laço while).

[Link].c ▪ Exemplo de uso do switch.


Usando return e break para não prosseguir no próximo case:
void Funcao()
{
int x ;
//........... e “x” recebe algum valor...

switch( x )
{
case 1:
Funcao1(); // prossegue abaixo, sem interrupção:
case 2:
Funcao2() ;
return; // interrompe, retornando desta funçao
case 3:
Funcao3() ;
break; // interrompe, saltando para a primeira instrução
// após o fim do switch;
default:
Funcao4();

} // fim do switch

//...................
}

13.5 • Ponteiros, Matrizes e Referências.


13.5.1 ▪ Ponteiros
Em C podemos tanto capturar o endereço de uma variável existente, como também so-
licitar que ele reserve uma posição livre qualquer e nos devolva o seu endereço.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


410 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

No segundo caso, usamos o “heap” (resto), isto é a área de memória que não está sendo
usada para armazenar variáveis globais, static ou auto.

[Link] ▪ Como a memória é dividida (global, pilha e heap)


 É reservada uma área de memória para as variáveis globais e static.
Isto faz sentido, pois, como essas variáveis não morrem, essa área não precisa-
rá mais ser redimensionada. Essa é portanto a área global (também chamada
de estática).
 É reservada uma outra área para as variáveis auto (é a pilha ou stack); dentro
desta área haverá um movimento constante, pois teremos sempre variáveis sen-
do criadas e morrendo, conforme as funções são chamadas e em seguida retor-
nam.
 A área restante é o “heap”. Essa é uma área de livre alocação: o programador
pode assim solicitar que determinadas quantidades de memória desse trecho
sejam reservadas.
Mas, neste caso, o próprio programador deverá também liberar as memórias
que tenha alocado.
Um ponteiro é uma variável destinada a guardar não um valor e sim um endereço de
memória onde um valor poderá estar armazenado.
Para isso ela deve ser formalmente declarada como sendo um ponteiro, através do ope-
rador apropriado - o operador de ponteiro.
// A variável abaixo é um ponteiro:
int * pa ; // … pois é usado o operador apropriado (*) em sua declaração.
Na linha de exemplo acima é criada a variável pa, com o tipo ponteiro para int. Foi
utilizada a regra da linguagem adequada para que essa variável possa mais tarde arma-
zenar um endereço.
Assim podemos ter, por exemplo, um valor do tipo int que esteja armazenado em um
lugar qualquer da memória. O endereço desse lugar deverá ser armazenado em pa no
momento oportuno para, então, permitir que esse valor possa ser acessado, indireta-
mente, através de pa.
Mas não podemos esquecer que um ponteiro deve ser uma variável que guarda o ende-
reço de um valor de um determinado tipo e esse tipo deve estar claro na criação (de-
claração) do ponteiro.
Isto porque, no momento de acessar uma informação através de um ponteiro, deverão
estar claros tanto o tamanho quanto a forma de armazenamento dessa informação(ou
seja, o seu tipo). Do contrário o acesso não será possível, simplesmente porque não po-
demos ler ou gravar em um lugar se não sabemos quantos bytes devem ser gravados e
de que modo devem ser gravados (com ou sem ponto flutuante, por exemplo).
E há dois modos de armazenar um endereço de memória.
[Link] ▪ Capturando o endereço de uma variável já existente.
int a = 5 ;
int * pa = &a;
/*
A variável pa (declarada com o operador ponteiro) foi inicializada para
armazenar o endereço da variável a.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.5 • Ponteiros, Matrizes e Referências. • C++ • AGIT informática • 411

Isso foi possível porque o operador de endereço (&) lê e retorna o endereço


de uma variável.
*/

[Link] ▪ Alocando livremente memória no heap


Podemos solicitar que uma região de memória (localizada no heap) seja reservada e o
seu endereço inicial seja retornado:
 isto é possível utilizando-se o operador new
Exemplo 1:
int * pa = new int; // new é um operador específico de C++;
// em C use a função malloc;
// Na linha acima, foi solicitado que seja reservado o espaço necessário para
// um valor do tipo int - e que seu endereço seja gravado na variável pa;
Exemplo 2:
int * pa = new int [5];
// Na linha acima, foi solicitado que seja reservado o espaço necessário para
// uma série de cinco valores do tipo int.
// E o endereço inicial dessa região é armazenado na variável pa;

 Quando alocamos memória no heap através de new, devemos tam-


bém proceder à liberação dessa memória, usando:

a. o operador delete, para liberar a memória alocada para um único ele-


mento de um determinado tipo (“Exemplo 1”, acima).
b. ou o operador delete[ ] (delete seguido de abre e fecha colchetes),
para liberar a memória alocada para uma série de elementos de um
determinado tipo (“Exemplo 2”, acima).
Exemplos:
int * pa = new int; // new é um operador específico de C++;
// em C use a função malloc;
.....................................
delete pa; // delete é um operador específico de C++;
// em C use a função free;
OU
int * pa = new int [ 5 ];
.....................................
delete [ ] pa ; // neste caso foi usado “delete [ ]”
// pois havia sido alocada memória para uma série de 5 int’s.
Uma vez criada uma variável ponteiro e preenchida com um endereço de memória vá-
lido, podemos utilizá-la para acessar os valores armazenados na memória apontada.
Mas como deve ser feito esse acesso?

[Link] ▪ Acessando valores através de ponteiros.


Para acessar a região de memória cujo endereço está armazenado em uma variável
ponteiro, precisamos também utilizar o operador ponteiro:
int * pa = new int ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


412 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

*pa = 5 ;

 Acima, o operador de ponteiro indica que não queremos escrever o nú-


mero “5” em “pa”.

 O que queremos é que “pa” seja acessada para que seja lido o endereço
nela armazenado.
 Em seguida deve ser acessado esse endereço e, aí sim, escrever o número
“5”.
E, caso estivéssemos alocando memória para uma lista de inteiros (ao invés de um úni-
co “int”, poderíamos utilizar também o operador ponteiro, indicando contudo qual dos
elementos da lista (um vetor, nesse caso) queremos acessar.
 Isso pode ser feito com um deslocamento a partir do endereço inicial:

int * pa = new int [ 3 ];


*pa = 5 ; // foi acessado o primeiro elemento da série { *( pa + 0) = 5 ; }.
// abaixo, é acessado o segundo elemento da série:
*( pa+1 ) = 10 ; // O “+1” aqui indica “mais um elemento desse tipo”
// e não “mais um byte”.
// Como, no caso, temos um ponteiro para int, em um sistema
// de 32 bits teremos um deslocamento de quatro bytes
* ( pa + 2 ) = 4 ; // acessa o terceiro elemento da série
E neste caso podemos também usar o operador de índice [ ] :
pa[ 0 ] = 5;
pa[ 1 ] = 10;
pa[ 2 ] = 4;

 Nos dois exemplos acima, alocamos memória para uma série de 3 ints.
 Em seguida utilizamos o endereço corretamente para acessar cada um dos
três inteiros da série.
 Fizemos isso, no primeiro caso, através do operador ponteiro.
 E, no segundo caso, através do operador de índice.

[Link] ▪ Exercícios: demonstrando o funcionamento de ponteiros.

#include <iostream>
using namespace std;
int VariavelExterna = 0 ;
int main()
{
int Var = 5;
int V2 = Var;
/* Ler o conteúdo armazenado na memória apelidada de "Var"
(que, no caso, é o número 5), e copiá-lo para “V2” (“V2” é uma variável do
tipo int, isto é, ela está destinada a armazenar um valor inteiro)
*/

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.5 • Ponteiros, Matrizes e Referências. • C++ • AGIT informática • 413

int * pVar = &Var ; /* Ler o endereço apelidado de "Var" e armazenar em


“pVar” (“pVar” é uma variável do tipo ponteiro para int,
isto é ela está destinada a armazenar endereços).
*/
int * Notas = new int[ 3 ] ; /* Endereço obtido no heap, com reserva de
memória para 3 valores inteiros, e armazenado na variável ponteiro
Notas
*/
/*
Abaixo, ERRO:
int FalsoPonteiro = &Var; // FalsoPonteiro NÃO É um Ponteiro, logo
// não pode armazenar Endereços.
// Pois foi declarado como int e não como int *
*/

// abaixo iremos imprir as variáveis e seus endereços:


cout << “Conteudo de Var = “ << Var << endl ;
cout << “Conteudo de V2 = “ << V2 << endl ;
cout << “Endereco de Var = “ << (int)&Var << endl ;
cout << “Endereco de V2 = “ << (int)&V2 << endl ;
cout << “Conteudo de pVar = “ << (int) pVar << endl ;
cout << “Endereco de pVar = “ << (int)&pVar << endl;
cout << “Conteudo APONTADO por pVar = “ << *pVar << endl ;

cout << “Endereco de Notas = “ << (int) &Notas << endl ;


cout<<“Conteudo armazenado por Notas (endereco obtido no
heap)=”
<< ( int ) Notas << endl ;
cout << “Endereco de VariavelExterna (memoria global) = ”
<< (int) &VariavelExterna << endl ;
delete [] Notas; // libera memória alocada no “heap”
return 0;
}
/*
RESULTADO (obs: os endereços podem variar de acordo com a
plataforma):
Conteudo de Var = 5
Conteudo de V2 = 5
Endereco de Var = 6749684
Endereco de V2 = 6749680
Conteudo de pVar = 6749684
Endereco de pVar = 6749676
Conteudo APONTADO por pVar = 5
Endereco de Notas = 6749672

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


414 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Conteudo armazenado por Notas (endereco obtido no heap) =


7933328
Endereco de VariavelExterna (memoria global) = 4382832
*/

Analisando o resultado:

 O endereço de Var corresponde ao conteúdo de pVar (que armazenou


esse endereço).
E pVar tem o seu próprio endereço.
 Observe também que as variáveis V2, Var e pVar e Notas têm os seus
endereços bem próximos (essa é a faixa de memória da pilha).
 Já o endereço obtido no heap (ponteiro para o primeiro de três inteiros) e
armazenado em Notas, está em outra faixa de memória: 7.933.328 (essa
é a faixa do heap).
 E o endereço da variável global VariavelExterna está em uma terceira
faixa: 4.382.832(essa é a faixa da memória global).

Segundo exercício. Percorrendo os elementos apontados por “Notas”.

#include <iostream>
int main()
{
int * Notas = new int[ 3 ];
cout << "Endereco obtido no heap e armazenado em Notas = "
<< (int) Notas << endl;
int iC;
for ( iC = 0 ; iC < 3 ; iC++ )
{
Notas[ iC ] = iC+1 ;
cout << "Elemento " << iC
<< "\tValor = " << Notas[ iC ]
<< "\tEndereco = " << int( Notas + iC )
<< endl;
}
return 0;
}

/*
RESULTADO (em plataforma de 32 bit’s):

Endereco obtido no heap e armazenado em Notas = 7933328

Elemento 0 Valor = 1 Endereco = 7933328

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.5 • Ponteiros, Matrizes e Referências. • C++ • AGIT informática • 415

Elemento 1 Valor = 2 Endereco = 7933332


Elemento 2 Valor = 3 Endereco = 7933336
*/

Analisando o resultado.

 O endereço armazenado em “Notas” é o endereço do primeiro elemento


da série.

 O endereço dos demais elementos é igual ao endereço do anterior mais


quatro bytes (tamanho de um int em 32 bit’s) .

 Imprimimos os valores de cada elemento da série usando o operador de


índice, com o devido deslocamento:
Notas[ iC ]
Mas também atingiríamos o mesmo resultado usando o operador
ponteiro:
*( Notas + iC )

 Imprimimos os endereços de cada elemento da série simplesmente


somando o deslocamento ao endereço inicial:
Notas + iC

13.5.2 ▪ Vetores e Matrizes.


Nos exemplos mostrados acima, verificamos que podemos utilizar ponteiros tanto para
armazenar o endereço de uma única variável como também para armazenar o endereço
inicial de uma lista de dados(ou seja, o endereço do primeiro elemento da lista).
Uma lista de dados é um vetor. Vetores são uma das aplicações mais importantes de
ponteiros. Se temos uma série de dados do mesmo tipo (uma lista de inteiros, por ex-
emplo), precisaremos de um ponteiro para guardar o endereço do primeiro elemento
da lista (o qual, por sua vez, será endereçado como elemento zero do vetor).
Agora podemos navegar fácil e rapidamente por toda a lista, pois, como todos os ele-
mentos da lista têm o mesmo tamanho (porque são do mesmo tipo) tanto o operador de
ponteiro como o operador de índice poderão atingir qualquer elemento com uma fór-
mula de acesso simples.
 Quando escrevemos:
int * pa = new int [ 5 ];
 E em seguida acessamos um elemento do vetor:
*( pa + 3) = 4;
 ou
pa [ 3 ] = 4;
 Esse código é realizado do seguinte modo:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


416 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

pa + ( 3 * sizeof( int ) ) = 4;
 Ou seja: é feito um deslocamento, a partir do endereço inicial, de modo a atingir
o endereço do elemento do vetor que precisamos acessar.
 Isto significa que tanto o operador de ponteiro como o operador de índice, in-
corporam uma aritmética de ponteiro.
 Assim, pa + 1, por exemplo, não significa “pa mais 1 byte” e sim “pa mais o
número de bytes especificado pelo seu tipo”.

[Link] ▪ Onde alocar vetores: memória global, pilha ou heap ?


Podemos alocar vetores no “heap”, na pilha ou na memória global.
int * p1 = new int [ 5 ] ; // Vetor alocado no “heap”, através
// do operador de livre alocação.
{
int p2 [ 5 ] ; // Declarada dentro de um bloco, sem especificaçõ static;
// então a variável é da classe auto por default.
// Logo, o vetor será alocado na pilha.
}
int p3 [ 5 ] ; // Declarada fora de bloco, sem especificação static: a variável é da
classe global.
// Logo, o vetor será alocado na memória global.

static int p4 [ 5] ; // Declarada fora de bloco, com especificação static.


// Então a variável é da classe static, externa.
// Logo, o vetor será alocado também na memória global.

 A faixa de memória global, portanto, serve para abrigar todas as variáveis


com tempo de vida global, isto é, com o mesmo tempo de vida da aplica-
ção.
E qual é a diferença entre alocar uma variável na pilha ou na memória global ao invés
de alocá-la no “heap” ?
 A memória global tem um tamanho fixo.
 Esse tamanho é determinado pela soma de todas as variáveis cujo tempo de
vida é o mesmo da aplicação (variáveis globais e estáticas).
 E esse tamanho já é conhecido e durante a compilação e link-edição.
 É então fixado e não pode mais ser alterado.
 A pilha também tem um tamanho fixo.
 Ele deve ser estipulado antes de procedermos à link-edição.
 Podemos alterar o tamanho da pilha nas opções do linker. Contudo, uma vez
gerado o executável não será mais possível alterar o tamanho da pilha.
 O tamanho da pilha pode assim ser determinado pelo programador, que deveria
definir um tamanho não muito pequeno (do contrário, teríamos um estouro de pi-
lha), mas também não muito grande (pois isto reduz a eficiência no gerencia-
mento da pilha, reduzindo performance).
 Os link-editores normalmente já definem um tamanho de pilha default levando
em conta a plataforma (e normalmente esse é o melhor tamanho).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.5 • Ponteiros, Matrizes e Referências. • C++ • AGIT informática • 417

 Mas qualquer alteração tem que ser feita antes da link-edição e não em tempo
de execução.
 Conclusão:
 Tanto na memória global como na pilha não podemos utilizar memórias de
tamanho variável ou que devam ser alteradas dinamicamente, em tempo
de execução.
 Se queremos que um vetor tenha uma dimensão desconhecida em tempo
de compilação (pois o seu tamanho só será conhecido já em execução), ou
se queremos redimensionar esse vetor, então o local apropriado é o heap.
 Essa distinção entre memória fixa e memória livre é um dos fatores que visam
garantir performance no uso da memória.

 Portanto, ao alocar um vetor no heap não precisamos conhecer


previamente a quantidade de elementos.

E, assim sendo, podemos fazer:


int x ;
…………… // a variável x receberá um valor qualquer
int * pa = new int [ x ] ; // é reservada memória no heap para x inteiros;
// o endereço inicial é retornado por new e armazenado em pa;
E, igualmente, podemos redimensionar o vetor:
………………
delete [ ] pa ; // a memória reservada no heap para x inteiros é liberada;
pa = new int[ 10 ]; // é reservada memória no heap para 10 inteiros;
// um novo endereço inicial é retornado por new e armazenado em pa;
………………
delete [ ] pa ; // é liberada a memória para 10 inteiros reservada no heap
int y ;
……………… // a variável y receberá um valor qualquer

pa = new int [ y ] ; // é reservada memória no heap para y inteiros;


// um novo endereço inicial é retornado por new e armazenado em pa;
………………
delete [ ] pa ; // a memória reservada no heap para y inteiros é liberada;

Já se o vetor tivesse sido declarado na pilha, ou na memória estática, não poderíamos


utilizar uma quantidade variável de elementos(desconhecida em tempo de compilação).
E também não poderíamos redimensionar o vetor.

Exemplos:
{
int iVetorNaPilha[ 20 ] ; // vetor de 20 elementos declarada na pilha;
// A quantidade de elementos é constante,
// e portanto conhecida em tempo de compilação.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


418 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

// O vetor não poderá ser redimensionado.


}
int iVetorNaMemoriaEstatica[ 30 ] ; // vetor de 20 elementos declarada na
// memória global;
// A quantidade de elementos é constante,
// e portanto conhecida em tempo de compilação.
// Também neste caso, o vetor não poderá ser redimensionado.

Além da distinção básica entre tamanho fixo e tamanho variável, há um outro elemento
que devemos levar em conta:

 Vetores muito grandes não devem ser alocadas na pilha, justamente por-
que o seu tamanho é arbitrado antes da link-edição.
Assim um vetor muito grande ocasionará um estouro de pilha.
Por isso, vetores muito grandes ou são alocados na memória global (se
forem necessários durante todo o tempo de vida da aplicação) ou, prefe-
rencialmente, no heap

[Link] ▪ Matrizes - usando múltiplas dimensões.


A linguagem não estabelece um limite para a quantidade de dimensões para uma ma-
triz. Na prática contudo esse limite será estabelecido pela memória disponível.

Assim podemos fazer:


#include <iostream>
int main()
{
double PlanilhaCalculos[ 6 ][ 7 ]; // matriz de duas dimensões
// representando uma planilha de cálculos de 6 linhas e 7 colunas
// escrever 9.8 na "Linha 1", "Coluna 1":
PlanilhaCalculos[ 0 ][ 0 ] = 9.8; // matriz é indexada a partir de zero
// escrever 10.3 na "Linha 5", "Coluna 4":
PlanilhaCalculos[ 4 ][ 3 ] = 10.3; // matriz é indexada a partir de zero

/* RESULTADO:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.5 • Ponteiros, Matrizes e Referências. • C++ • AGIT informática • 419

0 1 2 3 4 5 6

0 9.8

4 10.3

*/

cout << PlanilhaCalculos[ 0 ][ 0 ] << endl;


cout << PlanilhaCalculos[ 4 ][ 3 ] << endl;

/* e será impresso:
9.8
10.3
*/

return 0;
}

[Link] ▪ Inicialização de vetores e matrizes.

A regra para inicialização (atribuição na própria linha de declaração) de matrizes prevê


que seja apresentada uma lista de valores delimitada por chaves. Não é necessário con -
tudo inicializar todos os elementos da matriz:

// A) inicializando todos os elementos de uma matriz:


int MatrizDeInt[3] = { 1 , 4, 2 } ;

// B) inicializando apenas alguns elementos de uma matriz:


int OutraMatrizDeInt[3] = { 1 , 4 } ;

 Se uma matriz for inicializada, podemos omitir a dimensão explícita. Neste caso o
compilador se baseará na quantidade de elementos inicializados e usará esse número
constante como dimensão da matriz:
int Matriz [ ] = { 1 , 3 , 4 , 2 } ;
// A Matriz acima será dimensionada para quatro elementos, já que a
// dimensão explícita foi omitida e foram inicializados quatro
elementos.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


420 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

[Link] ▪ Vetores e Matrizes de estruturas.


Uma matriz de estruturas não difere de uma matriz para um tipo primitivo. Basta com-
binar a lógica de matrizes e a lógica de estruturas:
struct Area
{
public:
int Largura;
int Altura;
};

int main ( )
{
Area MatrizDeAreas [ 10 ]; // uma matriz para dez variáveis do tipo “Area”
// agora vai acessar os dois campos do quinto elemento da matriz:
MatrizDeAreas[ 4 ].Largura = 10;
MatrizDeAreas[ 4 ].Altura = 20;
return 0;
}
[Link].a ▪ Inicializando uma matriz de estruturas

Em primeiro lugar, podemos inicializar uma estrutura também utilizando as


chaves delimitadoras para definir um valor para cada campo da estrutura.
Por exemplo:
struct Area
{
public:
int Largura;
int Altura;
};

int main ( )
{
Area MinhaArea = { 10 , 15 } ;
// Acima, foi inicializada a variável estruturada “MinhaArea”,
// com a atribuição do valor 10 para o campo
Largura
// e 15 para o campo Altura
return 0 ;
}

Observe que a inicialização de uma estrutura, embora em ocasiões muito raras possa
ser útil,apresenta um problema grave: a inicialização é baseada na ordem de declara-
ção dos campos e não nos seus nomes.
Assim, se essa ordem for modificada algum dia (ou um novo campo for acrescentado,
mudando a ordem) todas as inicializações feitas no passado ficarão incorretas e terão
que ser alteradas.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.5 • Ponteiros, Matrizes e Referências. • C++ • AGIT informática • 421

 Além disso a struct ou class C++ não poderá ser inicializada com esse
método se uma construtora for declarada explicitamente. Neste caso a ini-
cialização deverá ser feita na própria construtora.

De todo modo, vejamos como seria a inicialização de uma matriz de estruturas, combi-
nando a regra de inicialização de uma matriz com a regra de inicialização de uma
struct:
int main( )
{
Area MinhaArea [3] =
{ // abre a chave para inicializar a MATRIZ

// abaixo: vai inicializar cada elemento da matriz, isto é, cada variável


// estruturada, também abrindo e fechando chaves para cada um.
// Os três pares de chaves abaixo, portanto, referem-se à inicialização
// dos campos da estrutura.
{ 10 , 20 }, // largura e altura para o primeiro elemento da matriz
{ 15 , 9 }, // largura e altura para o segundo elemento da matriz
{ 6, 8 } // idem para o terceiro e último elemento

}; // fecha a chave de inicialização da MATRIZ

return 0 ;
}

[Link] ▪ Vetores e Matrizes de caracteres.

Podemos usar o tipo inteiro char (um byte) para representar um caracter. Isto porque o
char pode ser relacionado à tabela de caracteres da máquina (tabela ASC, no caso do
IBM-PC).
Assim podemos fazer:
char Letra_A = 65 ;
OU
char Letra_A = ‘A’ ; // caracter A delimitado com aspas simples

O valor armazenado em ambos os casos será sempre o número 65 em


sua representação binária.

Assim se fizermos:
char Letra_A = ‘A’ ;
char Letra_B = ‘B’ ;
cout << Letra_A + Letra_B << endl ;
OU
cout << ‘A’ + ‘B’ << endl ;
Em ambos os casos o resultado é 131 ( 65 + 66 ).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


422 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

Para fins de impressão e visualização humana, a exibição do número será feita através
do caracter correspondente na tabela de caracteres. Isso permitirá imprimir textos.
Em C ou C++, podemos compor um texto através de uma série de caracteres, e isto
pode ser implementado como uma matriz de caracteres.
E, para facilitar o trabalho com textos, a linguagem oferece duas convenções específi-
cas, válidas apenas para matrizes de caracteres:
a) as aspas duplas.
 Uma matriz de caracteres (ou uma string) constante pode ser representada
com aspas duplas, como está abaixo:
“JOSE DA SILVA”

b) o terminador zero.
 Em um texto normal o zero binário não é utilizado (pois não tem qualquer repre-
sentação na tabela de caracteres).
 Por isso, por convenção, ele é empregado para indicar “fim de texto”.
 Desse modo se declararmos uma matriz de caracteres com dimensão 30, mas
utilizarmos apenas as 3 primeiras posições (por exemplo, com o nome “IVO”), o
quarto caracter será preenchido com o zero binário (pois as aspas duplas sem-
pre reservam um byte a mais e o preenchem com o zero binário).
 Se, em seguida, utilizarmos printf ou cout, a convençao será respeitada e será
impressa apenas a palavra “IVO”, não sendo impresso o “lixo” posterior ao ter-
minador zero.
 O terminador zero pode ser representado de duas maneiras:
 em formato hexadecimal : 0x0.
 O “0x” antes de um número indica que esse número usa a base hexadecimal e
não decimal
 na representação caracter, entre aspas simples e precedido pelo caracter de
controle ‘\’: ‘\0’
Para uma grande quantidade de casos (texto propriamente dito) o uso do terminador é
sem dúvida o modo mais eficiente e simples de indicar o fim de texto.
Contudo, encontraremos algumas situações em que não poderemos considerar o zero
binário como sendo um terminador.
Por exemplo, em um texto criptografado o zero binário poderá aparecer diversas vezes.
E, neste caso, ele não estará indicando o fim do texto e sim o resultado da criptografia
sobre o caracter original. Nenhum algoritmo eficiente de criptografia pode proibir o
uso de qualquer valor possível (inclusive o zero), pois do contrário no momento de
descriptografar teríamos uma grande complicação.
E há outras situações desse tipo. Nesses casos, teremos que utilizar o controle do fim
de texto por tamanho e não por um terminador. Isto é: teremos que reservar alguns
bytes extras para armazenar um número inteiro que indique quantos caracteres estão
sendo efetivamente usados em uma cadeia de caracteres.

[Link].a ▪ Inicializando uma matriz de caracteres


Uma matriz de caracteres pode ser inicializada das seguintes maneiras:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.5 • Ponteiros, Matrizes e Referências. • C++ • AGIT informática • 423

1) usando a forma geral de inicialização de matrizes:


char String_1 [ 4 ] = { 65 , 66 , 67 , 0x0 } ;
char String_2 [ 4 ] = { ‘A’ , ‘B’ , ‘C’ , ‘\0’ } ;
char String_3 [ ] = { ‘A’ , ‘B’ , ‘C’ , 0x0 } ; //dimensão = 4

2) usando as convenções arbitrárias para matriz de caracteres


(aspas duplas com terminador zero embutido):
char String_4 [ 4 ] = “ABC” ;
char String_5 [ ] = “ABC” ; // dimensão = 4
Se agora fizermos:
cout << String_1 << endl;
cout << String_2 << endl;
cout << String_3 << endl;
cout << String_4 << endl;
cout << String_5 << endl;
Em TODOS os casos será impresso: ABC
[Link].b ▪ Entendendo matriz de caracteres como um endereço
Como ocorre com qualquer matriz, uma variável matriz de caracteres também repre-
senta o endereço do primeiro elemento da lista. A partir desse endereço podemos nos
deslocar, atingindo os demais elementos.
char Nome[ 30 ] = “MARIA” ;
Nome[ 3 ] = ‘T’ ; //aspas simples pois é um único char.
// Se agora imprimirmos a variável Nome, o resultado será: MARTA

char Nome[ ] = “Jose da Silva” ;


cout << Nome +5 << endl ;
// Será impresso: da Silva

cout << “IVO MOREIRA” +4 << endl ;


// Será impresso: MOREIRA
[Link].c ▪ Exemplo de matriz de caracteres
Vamos ver agora um exemplo mais completo. Nele utilizaremos duas das fun-
ções da biblioteca padrão do C: strcpy, strncpy e strlen;
 strcpy copia uma matriz de caracteres para outra (o que significa uma cópia a
partir de um endereço para um outro endereço);
 já strncpy também copia, mas acrescenta um parâmetro numérico para permitir
que seja copiada apenas uma quantidade máxima de bytes, caso o terminador
zero não seja encontrado antes;
 e strlen retorna quantos bytes existem antes do terminador zero.
Além disso vamos escrever uma função (StrCopia) semelhante a strncpy. Essa função
irá fazer basicamenente a mesma coisa, mas diferentemente de strncpy, ela garante
sempre um terminador zero após o último caracter copiado.
#include <iostream>
#include <string>

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


424 • AGIT informática • C++ • • Capítulo 13 ▪ Referência técnica

// função para copiar todos os elementos de uma matriz de caracteres


para outra:
int StrCopia ( char * sDestino, const char * sOrigem, int nMaxBytes );

int main()
{
// Abaixo: reserva de 31 bytes; no momento estão sendo usados 6
// ( [ m, a, r, i , a ] mais o terminador zero)
char Nome1 [ 31 ] = "Maria";
cout << Nome1 << endl; // imprime: Maria
// abaixo, ERRO:
// char Nome[5] = "Maria"; // faltou reserva para o terminador zero
// ( no mínimo deveria ser feito : Nome[ 6 ] = "Maria"; )
// reserva de 6 bytes, todos em uso:
char Nome2[ ] = "Pedro";
cout << Nome2 << endl; // imprime: Pedro
char Nome3[31] = { 'M', 'A', 'R', 'I', 'A', '\0' };
cout << Nome3 << endl; // imprime: MARIA
strcpy(Nome1, "Jose da Silva");
cout << Nome1 << endl; // Imprime : Jose da Silva

// Abaixo, ERRO: Nome2 tem uma reserva para apenas 6 bytes:


// strcpy(Nome2, "Jose da Silva"); // “Jose da Silva” tem 13 bytes

// agora vamos usar strncpy, com controle de cópia por tamanho máximo:
strncpy(Nome3, "Carlos Lopes Costa", 50 );
cout << Nome3 << endl; // Imprime : Carlos Lopes Costa.
// Antes de atingir 50 caracteres strncpy encontrou o terminador zero

// Agora vamos usar a função StrCopia,


// implementada mais abaixo
StrCopia(Nome1, "Antonio da Costa", 30);
cout << Nome1 << endl; // Imprime: Antonio da Costa
StrCopia(Nome1, "Marcos Albuquerque Medeiros e Silva", 30);
cout << Nome1 << endl; // Imprime: Marcos Albuquerque Medeiros e
// simulando uma função "Left"
StrCopia(Nome1, "Joao da Silva", 4);
cout << "Left: " << Nome1 << endl; // Imprime: Left: Joao
// simulando uma função "Mid" ou "SubString"
StrCopia(Nome1, Nome3 + 7, 5 );
cout << "Mid: " << Nome1 << endl; // Imprime : Mid:Lopes
StrCopia ( Nome1 , "Jose Leonardo Rocha" + 5 , 8 );
cout << "Mid: " << Nome1 << endl; // Imprime: Mid:Leonardo
// simulando uma função "right"
StrCopia(Nome1, Nome3 + strlen(Nome3) - 5 , 5 );
cout << "Right: " << Nome1 << endl; // Imprime: Right:Costa
return 0;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


13.5 • Ponteiros, Matrizes e Referências. • C++ • AGIT informática • 425

// função para copiar todos os elementos de uma matriz de caracteres


para outra:
int StrCopia ( char * sDestino , const char * sOrigem , int nMaxBytes )
{
int nByteDaVez = 0;
while ( sOrigem[nByteDaVez] != '\0' && nByteDaVez < nMaxBytes )
{
sDestino[nByteDaVez] = sOrigem[nByteDaVez];
// outra forma de escrita:
//*(sDestino+nByteDaVez) = *(sOrigem+nByteDaVez);
nByteDaVez++;
}
sDestino[nByteDaVez] = '\0';
return nByteDaVez; //retorna a quantidade de bytes efetivamente
copiados.
}

//OUTRA FORMA de escrever a função StrCopia.


// Obs: Esta forma é mais eficiente (maior velocidade de execução).
int StrCopia ( char * sDestino , const char * sOrigem , int nMaxBytes )
{
const char * sOrigemInicial = sOrigem;
while ( *sOrigem != '\0' && sOrigem - sOrigemInicial < nMaxBytes )
{
*sDestino = *sOrigem ;
sOrigem++ ; sDestino++;
}
*sDestino = '\0';
return sOrigem - sOrigemInicial; //retorna: quantidade de bytes
copiados.
}

// Outra opção: usando o laço for:


int StrCopia ( char * sDestino, const char * sOrigem, int nMaxBytes )
{
const char * sOrigemInicial ;
for ( sOrigemInicial = sOrigem ;
*sOrigem != '\0' && sOrigem - sOrigemInicial < nMaxBytes ;
sOrigem++ , sDestino++ )
{
*sDestino = *sOrigem ;
}
*sDestino = '\0' ;
return sOrigem - sOrigemInicial;
//retorna: quantidade de bytes copiados.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


426 • AGIT informática • C++ •

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


• Anexos • C++ • AGIT informática • 427

• Anexos

Anexo A. Compilador e linker ........................................................429

Anexo B. Respostas às questões para revisão .................................431


[Link]ões do Capítulo 1 .........................................................................432
[Link]ões do Capítulo 2 .........................................................................434
1. Exercício .......................................................................................... 434
2. Questões ..........................................................................................435
[Link]ões do Capítulo 3 .........................................................................438
1. Exercício ......................................................................................... 438
2. Questões ..........................................................................................440
[Link]ões do Capítulo 4 .........................................................................444
1. Exercício ......................................................................................... 444
2. Questões ..........................................................................................448
[Link]ões do Capítulo 5 .........................................................................453
1. Exercício 1.......................................................................................453
2. Exercício 2 ......................................................................................461
3. Questões ..........................................................................................464
[Link]ões do Capítulo 6 .........................................................................477
[Link]ões do Capítulo 7 .........................................................................479
[Link]ões do Capítulo 11 .......................................................................481

Anexo C. Guia de consulta rápida ..................................................485


[Link] de tipos primitivos ......................................................................486
2. Tabela de operadores básicos por tipo de operação ............................487
[Link] completa de operadores e suas precedências ...........................489
[Link] de fluxo de processamento ...................................................491
[Link] diretivas de compilação .........................................................493
6. Seqüências escape ..............................................................................494
7. Palavras reservadas .............................................................................494
8. Modificadores .......................................................................................495

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


428 • AGIT informática • C++ • • Anexos

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo A. Compilador e linker • C++ • AGIT informática • 429

Anexo A. Compilador e linker

O compilador gera código de máquina (arquivos objeto: .obj ou .o), mas isso ainda
não pode ser executado.
Pois o compilador não resolve os endereços de símbolos externos, ou seja variáveis
globais e funções que estejam implementadas em outros módulos ou em bibliotecas
(inclusive a biblioteca padrão).
O linker, resolve esses endereços.
Por exemplo:
call printf
será substituído por
call <endereço-de-printf>

#include <stdio.h>
// os includes só são utilizados pelo compilador
// contem:
- regras de sintaxe (como structs, protótipos, etc),
- funções inline
- templates

int fatorial(int);
int funcao2(int);
ARQ1.c ARQ2.c
printf(...); int fatorial(int x)
funcao2(3); { /* implementa */ }
fatorial(5);
................ ..................
| |
| |
compilador -----------------------------------------------
| |
arq1.o arq2.o
call printf |
call fatorial |
. | | BIBLIOTECAS
| | |
linker -----------------------------------------------------------------------------
|
|
binario-executavel

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


430 • AGIT informática • C++ • • Anexos

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 431

Anexo B. Respostas às questões para revisão

[Link]ões do Capítulo 1 .........................................................................432


[Link]ões do Capítulo 2 .........................................................................434
1. Exercício .......................................................................................... 434
2. Questões ..........................................................................................435
[Link]ões do Capítulo 3 .........................................................................438
1. Exercício ......................................................................................... 438
2. Questões ..........................................................................................440
[Link]ões do Capítulo 4 .........................................................................444
1. Exercício ......................................................................................... 444
2. Questões ..........................................................................................448
[Link]ões do Capítulo 5 .........................................................................453
1. Exercício 1.......................................................................................453
2. Exercício 2 ......................................................................................461
3. Questões ..........................................................................................464
[Link]ões do Capítulo 6 .........................................................................477
[Link]ões do Capítulo 7 .........................................................................479
[Link]ões do Capítulo 11 .......................................................................481

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


432 • AGIT informática • C++ • • Anexos

1. Questões do Capítulo 1

Estas questões foram propostas na seção 1.11, página 39.

Cap.1 - 1. Com relação à padronização das linguagens C e C++:


a.  C é uma linguagem padronizada.
b.  C++ é uma linguagem padronizada.
c.  C não é uma linguagem padronizada.
{
Errado. A linguagem C é padronizada (C89 e C99)
}
d.  C++ não é uma linguagem padronizada.
{
Errado. A linguagem C++ é padronizada (C++98, C++03).
}
e.  A linguagem C já foi completamente padronizada, mas a padronização
de C++ ainda está em sua fase inicial.
{
Errado. Já existe um padrão oficialmente estabelecido; o que não
impede que novos padrões sejam criados, como uma evolução, man-
tendo compatibilidade com o padrão existente.
}
f.  O padrão da linguagem C++ foi publicado em 1998, com uma atualiza -
ção mínima em 2003. Isso é definitivo. Nunca mais haverá um novo
padrão de C++.
{
Errado. Como afirmado na apostila, um novo padrão para C++ está
atualmente em fase de finalização: o C++0x.
}

Cap.1 - 2. Assinale as afirmações verdadeiras sobre C++:


a.  C++ introduziu apenas pequenas melhorias com relação ao C.
{
Errado. Existem realmente melhorias que podemos chamar de “pe-
quenas”.
Mas as principais melhorias de C++ com relação a C, são importan-
tes a ponto de propiciar novas técnicas de programação, suporta-
das diretamente (ou nativamente) pela linguagem.
}
b.  C++ é baseada apenas em C.
{
Errado. As linguagens Simula, principalmente, mas também ADA,
ALGOL e CLU, em maior ou menor escala, foram referências impor-
tantes para a criação de C++, o que explica sua amplitude (múltiplas
técnicas de programação). Certamente, a linguagem C é uma impor-
tantíssima referência para C++, mas essa influência não é exclusiva.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 433

}
c.  C++ é baseada em C, mas também herdou recursos decisivos de outras
linguagens, o que torna C++ muito diferente de C, em termos de técni-
cas de programação suportadas diretamente (ou nativamente).
d.  Além do C, a outra linguagem que também serviu de inspiração para a
criação do C++ foi JAVA.
{
Errado. As linguagens C++ e SmallTalk herdaram da linguagem Si-
mula a noção de orientação a objetos.
C++ é também herdeira de outras linguagens, mas não de Java.
Ao contrário, é Java que descende de C++ (e de SmallTalk).
}
e.  C++ suporta apenas uma única técnica de programação conhecida
como “orientação a objetos
{
Errado. C++ admite múltiplas técnicas de programação
}
f.  C++ é uma linguagem multi-paradigma, admitindo múltiplas técnicas
de programação.

Cap.1 - 3. Há uma série de áreas dependentes de plataforma não cober-


tas pela biblioteca padrão de C++. Sendo assim, o programador
precisará:
a.  Para resolver esse problema, o programador deve desenvolver sempre
suas próprias bibliotecas, criando o código de infra-estrutura que cuide
dos detalhes dessas plataformas, e sirva de base para a criação de apli-
cações que possam ser compiladas em qualquer uma delas.
{
Errado. Existem bibliotecas disponíveis, open source, suportadas
por comunidades amplas de programadores e/ou empresas. Além
disso, há também bibliotecas comerciais para certas áreas específi-
cas.
Para que reinventar a pólvora? Produtividade é essencial.
Bibliotecas próprias só se justificam para resolver problemas específi-
cos de um projeto, ou para áreas nas quais não encontremos nada
que já esteja pronto.
}
b.  Já existem boas bibliotecas que resolvem a maior parte desses proble-
mas, como, por exemplo, boost e Qt. Bastará escolher e usar a(s) bibli-
oteca(s) mais adequada(s) para resolver esse tipo de problema, poden-
do assim criar aplicações que rodam em diversas plataformas.
c.  Os compiladores são responsáveis por resolver esse tipo de problema.
{
Errado. Compiladores servem apenas para analisar a sintaxe utili-
zada pelo programador e, se estiver correta, traduzir o código fonte
para código de máquina.
Compiladores não criam código original.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


434 • AGIT informática • C++ • • Anexos

d.  Os ambientes de desenvolvimento são responsáveis por resolver


esse tipo de problema.
{
Errado. Ambientes integrados de desenvolvimento (IDE's) servem
para elevar a produtividade do programador, integrando editores de
texto, chamadas ao compilador, chamadas ao programa de detecção
de erros (debugger), podendo também permitir acesso rápido à docu-
mentação, ou outras atividades auxiliares.
Há ambientes de desenvolvimento que geram código. Mas, nesse
caso, eles se baseiam em alguma(s) biblioteca(s) já existente(s) -
e dela(s) fazem uso em situações muito comuns e previsíveis, que
não exijam um desenvolvimento lógico específico.
}

2. Questões do Capítulo 2

1. Exercício
Este exercício foi proposto na seção 2.7.1, página 70.

Enunciado: com base no exemplo da função "Maximo", escreva a função "Minimo",


no arquivo /cursoCPP/00_funcoes/[Link] (acima da função "main").

Resultado que deve ser impresso


(ou semelhante):

Passos para atingir o objetivo:


a. A função "Minimo" deve:
 Selecionar o menor de dois valores inteiros e retornar esse valor.
b. Em seguida, na função "main":
 Chame a função "Minimo", passando-lhe os valores apropriados.
 Imprima o resultado (retorno) devolvido pela função "Minimo".

Solução (obs.: uma parte do código já foi escrita; considere os trechos assinalados
como acréscimos novos):
#include <iostream>
int Maximo( int x , int y )
{
if ( x > y ) // se "x" é maior-que "y"
return x ; // retorna um resultado
else // do contrário
return y ; // retorna outro resultado
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 435

// acrescentei : 
int Minimo( int x , int y )
{
if ( x < y ) // se "x" é menor-que "y"
return x ; // retorna um resultado
else // do contrário
return y ; // retorna outro resultado
}

// o programa inicia aqui:


int main()
{
int a, b, c ;
a = 10 ;
b = 20 ;
c = Maximo ( a, b ) ;
std::cout << “O maior valor entre “ << a << “ e “ << b
<< “ = “ << c << “\n” ;
// acrescentei : 
c = Minimo ( a, b ) ;
std::cout << “O menor valor entre “ << a << “ e “ << b
<< “ = “ << c << “\n” ;

return 0 ;
}
c. Compilar, em um ambiente ou chamando na linha de comando:
cl /EHsc /Fe"00_funcoes" [Link] // compilador microsoft - Windows
g++ [Link] -o 00_funcoes // compilador gcc - Windows ou Unix/Linux
d. Executar, em um ambiente ou chamando na linha de comando:
00_funcoes (Windows)
./00_funcoes (Unix/Linux)

2. Questões
Estas questões foram propostas na seção 2.7.2, página 71.

Cap.2 - 1. A respeito de funções, assinale as afirmações corretas:


a.  Uma função é o mesmo que uma linha de instrução.
{
Errado. Uma função contém um bloco de instruções (ainda que esse
bloco seja constituído por uma única instrução).
}
b.  Uma função é um bloco contendo um conjunto de linhas de instrução
podendo ou não ter um nome.
{
Errado. Uma função realmente é um bloco (um conjunto) de instru-
ções, mas esse bloco não pode ser anônimo. A função sempre deve
ter um nome.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


436 • AGIT informática • C++ • • Anexos

c.  Uma função é um bloco contendo um conjunto de linhas de instrução, el


obrigatoriamente deve ter um nome que a identifique.
d.  Nomes de função são seguidos obrigatoriamente por parênteses.
e.  O bloco de instruções de uma função é iniciado pelo próprio nome da
função e encerrado com a instrução return.
{
Errado. Uma função é iniciada e encerrada por chaves de abertura e
fechamento: { ... }
}
f.  O bloco de instruções de uma função é iniciado e encerrado com cha-
ves: { ... }
g.  Se uma função tiver apenas uma única linha de instrução, as chaves
podem ser omitidas.
{
Errado. Em uma função, nunca podemos omitir as chaves de aber-
tura e encerramento.
}
h.  A expressão [ c = Maximo ( a , b) ; ] contem uma chamada de fun-
ção.

Cap.2 - 2. Sobre linhas de instrução, assinale as afirmações corretas:


a.  Uma linha de instrução é encerrada pela quebra de linha do editor de
textos.
{
Errado. Uma linha de instrução é encerrada com um ponto e vírgu-
la.
}
b.  Uma linha de instrução é encerrada com um ponto e vírgula.
Cap.2 - 3. Considerando o código abaixo, assinale as afirmações corretas:
int x , y ;
// ...
if ( x > y )
std::cout << "x é maior que y" << "\n";
std::cout << "agora vou encerrar\n" ;

a.  Apenas se "x" for maior que "y", será impresso x é maior que y e ago-
ra vou encerrar.
{
Errado. Se "x" for maior que "y" será impresso x é maior que y. Em
seguida, sempre será impresso agora vou encerrar. Pois, como as
chaves foram omitidas, a única instrução a executar caso a condi-
ção seja verdadeira é a linha do primeiro "cout".
Para que essa afirmação fosse verdadeira, seria necessário que o
código estivesse escrito assim:
if ( x > y )
{
std::cout
REPRODUÇÃO PROIBIDA << "x
– registro é maior
298.397 que y"
- Biblioteca << "\n";
Nacional
std::cout << "agora vou encerrar\n" ;
}
Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 437

}
b.  Nada será impresso.
{
Errado. Pelo menos o segundo "cout" será executado e assim sem-
pre será impresso agora vou encerrar.
}
c.  Apenas se "x" for maior que "y", será impresso x é maior que y.
Em qualquer caso, sempre será impresso agora vou encerrar.
d.  Sempre será impresso x é maior que y e agora vou encerrar.
{
Errado. Sempre será impresso agora vou encerrar. Mas x é maior
que y só será impresso se o valor de 'x' for maior que o valor de 'y'.
}

Cap.2 - 4. Uma aplicação tem seu início (ou ponto de entrada) em:
a.  Nesta linha: [ #include <iostream> ].
{
Errado. Uma diretiva [ #include ] indica ao compilador que ele deve
procurar, no arquivo especificado, pela declaração de símbolos usa-
dos em um arquivo de código fonte mas que não são declarados aí.
Por exemplo, cout está declarado no arquivo iostream, e é lá que o
compilador ficará sabendo como ele pode ser usado. Desse modo,
isso não tem nada a ver com o início de uma aplicação.
}
b.  Na primeira função que esteja escrita em um arquivo que deve ter o
nome de [Link].
{
Errado. Não existe nenhuma convenção sobre nomes de arquivos
especiais. O início de uma aplicação ocorre, independentemente de
arquivo, na função main. Esta sim é uma convenção da linguagem, e
por isso esse nome de função é especial: é aí que a aplicação tem
início.
}
c.  Em uma função que deve ter o nome especial de main, não importando
o nome do arquivo em que esteja escrita. Essa função é obrigatória em
qualquer aplicação executável.

Cap.2 - 5. A respeito do compilador podemos afirmar que:


a.  O compilador serve apenas para analisar se o código foi escrito correta-
mente segundo as regras da linguagem.
{
Errado. Além de analisar a correção da escrita (sintaxe e gramática
da linguagem), um compilador deve também traduzir o código fonte

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


438 • AGIT informática • C++ • • Anexos

para linguagem de máquina, caso esteja tudo correto.


}
b.  Ele analisa se o código foi escrito corretamente, de acordo com a lin-
guagem, e, em caso positivo, irá traduzi-lo para linguagem de máquina.
c.  O compilador indica erros de escrita, mas não fornece informações so-
bre esse erro.
{
Errado. É responsabilidade do compilador fornecer uma descrição
sobre o tipo de erro, e indicar a linha onde o mesmo ocorreu.
}
d.  O compilador indica erros de escrita, emitindo uma mensagem sobre o
tipo do erro e a linha em que ele ocorreu. Devemos usar essas duas
informações para corrigir os erros mais rapidamente.

 Para fazer: crie um projeto de teste para as questões deste capítulo


(quando isso seja possível). Procure reproduzir, no código, as situações
descritas nas questões utilizadas. Introduza algumas variações.

3. Questões do Capítulo 3

1. Exercício

Este exercício foi proposto na seção 3.5.1, página 95.

Enunciado: implemente a soma dos números entre um número inicial e um final,


com a distância "razão" entre cada número. Esses 3 valores devem ser informados. E
o usuário deverá decidir também se deseja um novo cálculo ou encerrar o programa.

Resultado que deve ser impresso (ou semelhante):

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 439

Modelo
esquemático:

 Lembrando: para a soma, usar o laço for e não o cálculo de soma dos termos
da progressão, pois queremos exercitar o laço.
Solução:
#include <iostream>
#include <limits>
int main()
{
int continuar = 1; // flag para continuidade do programa
while ( continuar ) // enquanto for verdadeiro...
{
// solicita ao usuário os 3 números necessários:
int inicial, final, razao, result ;
std::cout << "\ninforme os números inicial, final e a razão\n" ;
std::cin >> inicial >> final >> razao ;
// se houve falha na entrada:
if ( std::[Link]( ) )
{
std::cout << "valores incorretos: tente outra vez\n";
// como houve falha é preciso limpar os flags de erro de "cin":
std::[Link]( );
// e desprezar quebras de linha pendentes no buffer de "cin":
std::[Link](std::numeric_limits<int>::max(), '\n');
}
else
{
if ( razao == 0 ) // analisa "razao"
std::cout << "razao não pode ser zero - tente outra vez\n";
else

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


440 • AGIT informática • C++ • • Anexos

{
if ( inicial > final ) // se "inicial" é maior-que "final":
std::swap( inicial, final ); // troca valores de "inicial" e "final";
// agora "inicial" é o menor
if ( razao < 0 ) // neste caso, "razao" deve ser positivo,
{ // para evoluir do menor para o maior
razao = -razao; // "razao" passa de negativo para positivo
std::cout << "razao alterada para : " << razao << "\n";
}
// o laço para cálculo da soma:
for ( result = 0 ; inicial <= final ; inicial=inicial+razao )
result = result + inicial ; // executa se condição verdadeira
std::cout << "resultado da soma: " << result << "\n";
std::cout << "\ndeseja realizar novo calculo?\n"
<< " (0 para encerrar, qualquer outro numero "
<< "para continuar)\n";
std::cin >> continuar ; // o usuário pode ter digitado zero...

 A partir daqui, o valor de "continuar" poderá ser verdadeiro ou falso


(zero), afetando a próxima avaliação de condição do laço "while".

if ( std::[Link]() ) // deve ter digitado uma letra...


{ // consideremos isso como falso
std::cout << "entrada incorreta - " <<
"entendida como encerramento\n" ;
continuar = 0 ; //  com isso, o laço "while" será encerrado
}
} // fim do "if (razao == 0 )"
} // fim do primeiro "if (std::[Link]( ) )"
} // fim do laço "while'
std::cout << "\nfim de processamento\n";
return 0;
} // fim de "main".

2. Questões

Estas questões foram propostas na seção 3.5.2, página 99.

 Nas questões abaixo, considere as seguintes declarações iniciais:


int x , y ;

Cap.3 - 1. A respeito de uso da memória, assinale as afirmações corretas:


a.  Não é preciso ter preocupações especiais com a memória de um com-
putador. Isso é resolvido pelo compilador.
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 441

Errado. Devemos declarar memórias (constantes ou variáveis) com


um determinado tipo. Isso permite evitar enganos (pois o compilador
avisará sobre usos incorretos) e também elevar a performance, pois
não será necessário, durante a execução, avaliar que tipo de memó-
ria está sendo alocada a cada momento: pois isso já foi resolvido na
compilação.
}
b.  Uma memória deve ser reservada e qualificada com um determinado
tipo pelo programador. Por exemplo, usando int, como na expressão
[ int x ; ]
c.  A expressão [ x = 10 ; ] visa confirmar se 'x' contem o valor 10.
{
Errado. Essa expressão não contém uma comparação (ou confirma-
ção). E sim uma atribuição: copie 10 para 'x'.
}
d.  A expressão [ x = 10 ; ] visa armazenar o valor 10 em 'x', copiando
esse valor para a área de memória à qual associamos o nome 'x'.

Cap.3 - 2. Sobre fluxo de processamento, podemos dizer que:


a.  O processamento é sempre baseado na execução seqüencial de um
certo número de instruções.
{
Errado. A execução em princípio é seqüencial, mas, nessa seqüên-
cia, podemos ter testes de decisão que podem conduzir a diferentes
caminhos de execução, dependendo de uma avaliação de condição.
}
b.  O processamento pode ser bifurcado em caminhos diferentes, através
de tomadas de decisão.
c.  A expressão [ if ( x > y ) ] contem uma operação que leva à seguinte
avaliação: "'será que x' pode ser armazenado em y" ?
{
Errado. Essa expressão contém uma comparação maior-que.
}
d.  Em [ if ( x > y ) ] temos uma operação que implica nesta avaliação: "o
valor armazenado em 'x' é maior-que o valor armazenado em y" ?
e.  Após [ if ( x > y ) ] o processamento poderá seguir um caminho diferen-
te se o resultado da avaliação dessa condição for verdadeiro

Cap.3 - 3. Sobre os conceitos de verdadeiro e falso, podemos dizer que:


a.  A avaliação da expressão contida em [ if ( x > y ) ] retornará um resulta-
do verdadeiro se 'x' for maior-que 'y'; do contrário retornará um resul-
tado falso.
b.  A avaliação da expressão contida em [ if ( x >= y ) ] retornará um resul-
tado verdadeiro se 'x' for maior-que 'y'; do contrário retornará um re-
sultado falso

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


442 • AGIT informática • C++ • • Anexos

{
Errado. O resultado será verdadeiro se 'x' for maior-ou-igual a 'y',
pois o operador empregado foi [ >= ] e não [ > ].
}
c.  A expressão contida em [ if ( x ) ] não é aceita pelas linguagens C e
C++. Logo, ocorrerá um erro de compilação.
{
Errado. Um valor pode ser avaliado como verdadeiro ou falso. Se for
zero, a avaliação retorna falso. Se diferente de zero, retornará ver-
dadeiro.
}
d.  A avaliação da expressão contida em [ if ( x ) ] retornará um resultado
falso se o valor de 'x' for igual a 0(zero); e retornará um resultado ver-
dadeiro se o valor de 'x' for exatamente igual a 1(um).
{
Errado. Ver a resposta a resposta 'e', abaixo.
}
e.  A avaliação da expressão contida em [ if ( x ) ] retornará um resultado
falso se o valor de 'x' for igual a 0(zero); e retornará um resultado ver-
dadeiro se o valor de 'x' for diferente de 0(zero)

Cap.3 - 4. O que será impresso na execução do código abaixo?


x=5 ; y=x+1 ; x=x+2 ;
if ( x >= y )
std::cout << "x é maior ou igual a y\n" ;
else
std::cout << "x é menor que y\n" ;
a.  Será impresso x é maior ou igual a y
b.  Será impresso x é menor que y
{
Errado. Só será impresso [ x é menor que y ], se [ x >= y ] for fal-
so, ou seja se 'x' for menor-que 'y'. No caso, 'y' contem 6 ( [ y = 5 + 1
] ) e 'x' contem 7 ( [ x = 5 + 2 ] ). Logo 'x' é maior que 'y'.
}
c.  Nada será impresso.
{
Errado. Seja lá qual for o resultado da avaliação da condição [ x >=
y ], algo será impresso. Se verdadeira, temos um caminho de execu-
ção. E, se falsa, temos outro caminho de execução. Uma dessas
duas impressões terá que ser executada.
}
d.  Será impresso x é maior ou igual a y e, em seguida, será impresso x é
menor que y
{
Errado. Apenas uma dessas duas cadeias de caracteres será im-
pressa: [ if (...) <executa-uma-coisa> ; else <executa-outra-coisa> ; ].
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 443

Cap.3 - 5. Sobre laços, podemos dizer que:


a.  Um laço sempre repete, infinitamente, um certo número de instruções.
{
Errado. Todo laço contem uma avaliação de condição. Dependendo
dessa avaliação, o laço poderá prosseguir ou será interrompido. A re-
petição só será infinita se a avaliação retornar sempre um resultado
verdadeiro.
}
b.  Um laço pode conter uma avaliação de condição que, caso seja ou
torne-se falsa, resultará na interrupção da repetição das instruções a ele
associadas.
c.  O laço while contem uma avaliação de condição que permite inter-
rompê-lo. Já o laço for contem apenas uma possibilidade de progres-
são, mas não prevê uma avaliação de condição.
{
Errado. O laço for, como qualquer laço, contém uma avaliação de
condição: [ for ( <início> ; <condição> ; <progressão> ) ]
}

Cap.3 - 6. Quantas vezes serão executadas as instruções associadas ao se-


guinte laço:
for ( x = 1 ; x < 1 ; x = x + 1 ) { /* instruções */ }
a.  Uma vez.
{
Errado. A instrução de início [ x = 1 ; ] é executada na primeira vez e,
imediatamente em seguida, é executada a avaliação da condição.
As <instruções> só serão executadas se o resultado da avaliação for
verdadeiro.
E, no caso, o valor de 'x' é 1 e a condição é [ x < 1 ]. Logo, a avalia-
ção tem resultado falso, pois 'x' não é menor-que 1.
Desse modo, a primeira avaliação já tem resultado falso e as <ins-
truções> nunca serão executadas.
}
b.  Duas vezes.
{
Errado. Ver resposta 'a', acima.
}
c.  Nunca serão executadas.
d.  Serão executadas infinitamente.
{
Errado. Ver resposta 'a', acima.
}

 Para fazer: crie um projeto de teste para as questões deste capítulo


(quando isso seja possível). Procure reproduzir, no código, as situações
descritas nas questões utilizadas. Introduza algumas variações.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


444 • AGIT informática • C++ • • Anexos

4. Questões do Capítulo 4

1. Exercício
Este exercício foi proposto na seção 4.8.1, página 125.
Enunciado:
 Crie um vetor de inteiros que deverá armazenar todos os números entre um
número inicial e um final, existindo entre eles uma distância denominada "ra-
zão".
 O "inicial", o "final" e a "razão" devem ser informados pelo usuário.
 Após o armazenamento dos números no vetor, o programa deve imprimir to-
dos os elementos do vetor.
 Esses números também devem ser inseridos em uma string, formando uma
cadeia de caracteres. Por isso, o intervalo entre 'inicial' e "final' deve ser compa-
tível com o tipo char (por exemplo, entre 'A' e 'Z', ou outro).
 Ao final, imprimir: o total de números no intervalo, a soma dos elementos do
vetor e a string formada pelos números.
 O programa deve também permitir que o usuário repita a operação, informando
novos valores.

Resultado que deve ser


impresso:

Solução:

#include <iostream>
#include <string>
#include <vector>
#include <limits>
int main()
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 445

std::vector< int > vec;


std::string str;
std::cout << "progressao com caracteres\n";
// limites para entrada de dados de 'inicial' e 'final'
const char inicial_min = 'A';
const char final_max = 'Z';
bool continuar = true; // flag para continuidade do programa
while ( continuar ) // enquanto for verdadeiro
{
// flag para indicar se os dados foram informados corretamente:
bool valores_validos = true;
// solicita ao usuário o inicial, o final e a razão:
char inicial, final;
int razao ;
std::cout << "\nInforme Inicial, Final e Razao.\n"
<< "Limites: " << inicial_min
<< " <= Inicial <= Final <= " << final_max << ".\n"
<< "Razao: maior que zero \n"
<< "e menor ou igual a (Final-Inicial+1).\n" ;
std::cin >> inicial >> final >> razao ;
// Se houve falha na entrada:
if ( std::[Link]( ) )
{
std::cout << "valores incorretos\n";
// como houve falha é preciso limpar os flags de erro de "cin":
std::[Link]( );
// e desprezar quebras de linha pendentes no buffer de "cin":
std::[Link](std::numeric_limits<int>::max(), '\n');
valores_validos = false; // flag: valores inválidos.
}
// Se não houve erro até aqui:
if ( valores_validos )
{
// converte 'inicial' e 'final' para "uppercase" (caso não seja):
inicial = char(toupper(inicial)); // se 'a' -> 'A'...
final = char(toupper(final)); // se 'b' -> 'B'...

A função "toupper" da biblioteca padrão retorna "int". Mas a conversão para


"char" será segura já que abaixo checaremos os limites de "inicial" e "final".

// Analisa todas as regras e emite todas as mensagens cabíveis:


if ( razao <= 0 )
{
std::cout << "Razao deve ser maior que zero.\n";
valores_validos = false;
}
if ( inicial < inicial_min )
{
std::cout << "Inicial deve ser maior ou igual a "
<< inicial_min << ".\n";

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


446 • AGIT informática • C++ • • Anexos

valores_validos = false;
}
if ( final > final_max )
{
std::cout << "Final deve ser menor ou igual a "
<< final_max << ".\n";
valores_validos = false;
}
if ( inicial > final )
{
std::cout << "Inicial deve ser menor ou igual a Final.\n";
valores_validos = false;
}
if ( razao > (final-inicial+1) )
{
std::cout << "Razao nao pode ser maior que "
<< " (Final-Inicial+1).\n";
valores_validos = false;
}
}
// se tudo ocorreu corretamente:
if ( valores_validos )
{
// Quantidade total de números no intervalo:
unsigned int quant_num = (final-inicial+razao) / razao ;

 OBS: a conversão do resultado das operações aritméticas acima, o qual


terá o tipo "int" (o tipo de 'razao') para a variável "quant_num", que é "un-
signed int", seria insegura caso pudesse ter um valor negativo. Além dis-
so o resultado não pode ser zero, pois servirá para dimensionar o vetor e
a string. Mas neste caso nada disso acontecerá (e teremos uma conversão
segura) pois:
- garantimos que 'inicial' e 'final' estarão dentro de um intervalo positivo e que
'inicial' não será maior que 'final'.
- garantimos que 'razao' não pode ser maior que [ inicial - final +1 ] e desse
modo o resultado da operação não será zero.
- finalmente, garantimos também que 'razão' não pode ser zero(impedindo a
divisão por zero.
Agora podemos usar, coerente e seguramente, "quant_num" como dimensão
do vetor e da string:

[Link]( quant_num ); // Dimensiona o vetor (garantimos que


// dimensão é maior que zero).
[Link]( quant_num ); // Dimensiona a string (idem).
unsigned int index;
char r = char(razao);

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 447

 Esta conversão visa evitar que o último segmento do laço "for" abaixo
[ inicial = inicial + razao ] receba uma eventual "warning" do compilador.
Pois como "razao" é int, poderia conter um valor que, somado a 'inicial',
não coubesse na atribuíção que é feita a 'inicial' [ inicial = <int> ].
Acima, garantimos que isso não poderá acontecer, mas o compilador não é
obrigado a analisar toda a lógica do programa. Ele apenas verifica uma pos-
sível truncagem de um determinado valor. Assim, ao invés de
[ inicial = inicial + razao ], fazemos [ inicial = inicial + r ], com todos os ti-
pos compatíveis:

for ( index = 0 ; inicial <= final ; inicial = inicial + r )


{
vec[ index ] = inicial; // Conversão de "char" para "int": segura.
str[ index ] = inicial; // Alimenta a string (OK: 'inicial' é "char").
index = index + 1; // Evoluir o índice.
}
// Acumular os elementos do vetor e imprimi-los:
int soma = 0;
for ( index = 0; index < [Link]() ; index = index + 1 )
{
soma = soma + vec[ index ];
std::cout << vec[ index ] << "\n";
}

std::cout << "\nTotal de numeros no intervalo: "


<< quant_num << "\n";
std::cout << "Resultado da soma: " << soma << "\n";
std::cout << "String: " << str << "\n";

std::cout << "\ndeseja realizar novo calculo?\n"


<< "(0 para encerrar, 1 para continuar)\n\n";

std::cin >> continuar ;

 A partir daqui, o valor de "continuar" poderá ser verdadeiro ou falso


afetando próxima avaliação de condição do laço "while"

if ( std::[Link]() ) // Deve ter qualquer coisa diferente de zero ou um...


// consideremos isso como falso:
{
std::cout << "entrada incorreta - entendida como "
<< "encerramento\n" ;
continuar = false ; // o laço "while" será encerrado
}
std::cout << "\n";

} // --- fim do último "if (valores_validos)"


else // Um (ou mais) valor inválido:
std::cout << "Tente outra vez\n";
} // -- fim do laço "while'

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


448 • AGIT informática • C++ • • Anexos

std::cout << "fim de processamento\n";


return 0;
} // - fim de main.

2. Questões
Estas questões foram propostas na seção 4.8.2, página 129.

Cap.4 - 1. Assinale as respostas corretas, considerando estas declarações:


long i ;
float f ;

a.  'i'(long) tem o mesmo tamanho de 'f'(float): 4 bytes. Logo podemos di-


zer que não há qualquer diferença quanto ao modo como essas variá-
veis serão representadas na memória.
{
Errado. Embora tenham o mesmo tamanho, esses dois tipos (long e
float) têm forma de armazenamento diferentes: long é armazenado
como um número inteiro e float é armazenado através de ponto flu-
tuante.
}
b.  'i'(long) tem uma forma de armazenamento diferente de 'f'(float), já
que esta é representada na memória através de ponto flutuante. Logo,
apesar de terem o mesmo tamanho, são representadas de modo dife-
rente. Mas isso não tem nenhuma implicação prática.
{
Errado. A forma de armazenamento do float (ponto flutuante) é
mais complexa, implicando em acesso mais lento, do que a forma
de armazenamento do long (número inteiro).
}
c.  A forma de armazenamento de 'f'(float), através de ponto flutuante, tor-
na o seu acesso mais lento do que o acesso a números inteiros.

Cap.4 - 2. Assinale as respostas corretas, considerando o código abaixo:


const int constante ;
//...
constante = 5 ;

a.  As duas linhas do código acima estão corretas.


{
Errado. Uma constante, por sua própria natureza, deve ser obrigato-
riamente inicializada, o que não foi feito, e, além disso não pode
sofrer atribuições (ou não seria constante), e isso foi feito.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 449

b.  A primeira linha do código acima está incorreta, pois uma declaração


const exige inicialização. A segunda linha está correta.
{
Errado. A primeira afirmação está correta (exige inicialização). Mas
a segunda afirmação está incorreta ao dizer que a segunda linha [
constante = 5 ; ] está correta. Temos aí uma atribuição e constantes
não podem receber atribuições.
}
c.  A primeira linha do código acima está incorreta, pois uma declaração
const exige inicialização. A segunda linha também está incorreta, pois
constantes podem apenas ser inicializadas mas não podem sofrer atri-
buições.

Cap.4 - 3. Assinale as respostas corretas, considerando o código abaixo:


int variavel ;
//...
variavel = 5 ;
a.  As duas linhas do código acima estão incorretas.
{
Errado. A primeira linha está correta: declara uma variável do tipo int
- que não é inicializada. Nada de anormal, pois variáveis podem ou
não ser inicializadas (a inicialização não é obrigatória).
A segunda linha também está correta pois variáveis podem, a qual-
quer momento, receber atribuições.
}
b.  A primeira linha do código acima está incorreta, pois uma declaração
de variável exige inicialização obrigatoriamente. A segunda linha está
correta.
{
Errado. Para variáveis, a inicialização é opcional. Ver resposta 'a',
acima.
}
c.  As duas linhas do código acima estão corretas.
Cap.4 - 4. Assinale as respostas corretas, considerando estas declarações:
double d = 10.9 ;
int i = d ;

a.  O código acima não apresenta nenhum tipo de problema.


{
Errado. Dependendo da situação real, isso poderá ser um proble-
ma. Ver resposta 'b', abaixo.
}
b.  A variável 'i'(int) não tem suporte a ponto flutuante. Logo o valor de 'd'
(10.9) será truncado para 10 ao ser copiado para 'i'. Isso pode ser um
problema, dependendo da situação em que ocorra. E, se isso está sen-
do feito de modo intencional, deveria ser usada uma operação de cast

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


450 • AGIT informática • C++ • • Anexos

(conversão) explícita, pois, em princípio, essa é uma operação suspei-


ta.

Cap.4 - 5. Assinale as respostas corretas, considerando estas declarações:


double d = double (4) * 1024 * 1024 * 1024 ;
long i = d ;
// OBS.: o resultado de [ double (4) * 1024 * 1024 * 1024 ]
// é [Link].

a.  O código acima não apresenta nenhum tipo de problema.


{
Errado. Embora o resultado das operações de multiplicação, que é
atribuído a 'd', tenha sua parte fracionária zerada (e assim, neste
aspecto, nada será truncado), na parte inteira temos um valor muito
grande para caber em um long: [Link].
Então, na atribuição a 'i' (long), a parte inteira será truncada para
-[Link]. Dependendo da situação, isso pode ser um proble-
ma (perda de valor). E se isso foi usado de modo intencional, temos
um código confuso. No mínimo deveria ser usada uma operação de
cast (conversão) explícita, pois é uma operação muito suspeita.
}
b.  O valor de 'd' (double) será truncado ao ser copiado para 'i' (long)
Cap.4 - 6. Assinale as respostas corretas, considerando o código abaixo:
std::string nome ;
nome = "Maria" ;
nome = nome + " Aparecida" ;
a.  As três linhas do código acima estão corretas e não implicam em ne-
nhum tipo de problema.
b.  As três linhas do código acima estão incorretas.
{
Errado. Nada de incorreto. "nome" foi declarada corretamente com
o tipo std::string. Em seguida, houve uma operação de atribuição,
totalmente suportada e garantida por esse tipo. Finalmente, uma
operação de concatenação, igualmente suportada e garantida. Ver
ainda a resposta 'c', abaixo.
}
c.  A terceira linha do código acima pode implicar em um problema, escre-
vendo em memória ainda não alocada.
{
Errado. O tipo std::string garante que serão feitas as alocações de
memória necessárias em suas operações. Assim, tanto na atribuição [
= ] como na concatenação [ += ], se houver necessidade de alocar
mais memória, std::string cuidará disso.
}

Cap.4 - 7. Assinale as respostas corretas, considerando o código abaixo:


std::vector <int > vetor ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 451

vetor [ 0 ] = 10 ;

a.  As duas linhas do código acima estão corretas e não implicam em ne-


nhum tipo de problema.
{
Errado. As duas linhas estão sintaticamente corretas. Mas a segunda
linha apresenta um problema potencial em tempo de execução. Ver
resposta 'c', abaixo.
}
b.  As duas linhas do código acima estão incorretas.
{
Errado. Não há nadada de incorreto nessas duas linhas. Apenas, a
segunda linha pode levar a erros de execução. Ver resposta 'c', abai-
xo.
}
c.  A primeira linha do código acima está correta. A segunda linha apresen-
ta um problema potencial, já que "vetor" não foi dimensionada e há
um acesso ao seu primeiro elemento ([0]). Antes disso, alguma coisa
deveria ter sido feita, como, por exemplo:
[ [Link]( <dimensão> ) ; ].
Desse modo a execução da segunda linha implica em resultados impre-
visíveis (ou indetermináveis).

Cap.4 - 8. Assinale as respostas corretas, considerando o código abaixo:


enum Meses { Janeiro=1, Fevereiro, Marco, Abril, Maio, Junho, Julho,
Agosto, Setembro, Outubro, Novembro, Dezembro } ;
std::vector < std::string > vec ;
[Link]( Dezembro ) ;
vec[ Janeiro ] = "Janeiro" ;
// faz o mesmo para Fevereiro, Marco... até vec[Dezembro]="Dezembro";
unsigned int index;
for ( index = 0 ; index < [Link]() ; index = index + 1 )
std::cout << vec[ index] << "\n" ;
a.  Todas as linhas do código acima estão corretas e não implicam em ne-
nhum tipo de problema.
{
Errado. Todas as linhas de código estão formalmente corretas, em
conformidade com as regras da linguagem. Mas teremos um proble-
ma potencial quando a linha [ vec [ Dezembro ] = "Dezembro" ; ]
for executada, pois o vetor foi dimensionado para 12 elementos, e
portanto só pode ser acessado com os índices de 0 a 11. A constante
Dezembro do tipo Meses será convertida para 12 do tipo int.
Logo, [ vec [ 12 ] = "Dezembro"; ] está tentando acessar um dé-
cimo-terceiro elemento, para o qual não foi alocada memória. O cor-
reto seria fazer:
vec [ Janeiro -1 ] = "Janeiro"; // índice zero.
// …
vec[ Dezembro -1 ] = "Dezembro"; // índice 11.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


452 • AGIT informática • C++ • • Anexos

Ver, abaixo, a resposta à opção 'k'.


}
b.  Todas as linhas do código acima estão incorretas.
{
Errado. Todas as linhas do código acima estão formalmente corretas.
Apenas uma delas, já apontada na resposta anterior, apresenta um
problema potencial.
}
c.  Não é possível fazer: [ std::vector < std::string > vec ; ].
Só são possíveis os usos de std::vector para tipos básicos como:
[ std::vector < int > vi ; ] ou [ std::vector < double > vd ; ].
{
Errado. std::vector<>, permite criar um vetor de qualquer tipo que
aceite a operação de atribuição, inclusive std::string.
}
d.  Não é possível fazer: [ [Link]( Dezembro ) ; ].
{
Errado. A constante Dezembro do tipo Meses pode ser convertida
para int. Logo, nesse caso, temos o mesmo que [ [Link](12); ].
}
e.  Não é possível fazer: [ vec [ Janeiro ] = "Janeiro" ; ].
{
Errado. A constante Janeiro do tipo Meses pode ser convertida para
int. Ou seja: nesse caso, temos o mesmo que:
[ vec[ 1 ] = "Janeiro"; ].
Logo, é possível, embora em função da situação lógica do exemplo,
o correto seria fazer:
vec [ Janeiro -1 ] = "Janeiro"; // índice zero.
}
f.  A expressão [ [Link]( Dezembro ) ; ] está correta pois "Meses"
pode ser convertido para int. Nesse caso, seria o mesmo que:
[ [Link]( 12 ) ; ].
g.  A expressão [ vec [ Janeiro ] = "Janeiro" ; ] está correta pois "Meses"
pode ser convertido para int. Nesse caso, seria o mesmo que:
[ vec [ 1 ] = "Janeiro" ; ].
h.  Caso não ocorra um erro de execução, o laço for do código acima irá
imprimir:
Janeiro
Fevereiro
// Marco, Abril ... Novembro
Dezembro.
{
Errado. Como a atribuição de valores aos elementos do vetor come-
ça com [ vec[ Janeiro ] = "Janeiro"; ] (que é o mesmo que [ vec[ 1 ]
= "Janeiro"; ] ), o primeiro elemento do vetor (vec[ 0 ]), contém uma
string vazia. Logo, ao imprimir o primeiro elemento, será impressa
essa string vazia. Ver, acima, a resposta à opção 'a'.
Além disso, não irá atingir Dezembro. Ver, abaixo, as resposta às

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 453

opções 'i' e 'j'.


}
i.  Caso não ocorra um erro de execução, o laço for do código acima irá
imprimir:
// <uma string vazia>
Janeiro
Fevereiro
// Marco, Abril ... Novembro
Dezembro.
{
Errado. Realmente, a primeira coisa a ser impressa será uma string
vazeia. Mas a impressão não atingirá "Dezembro", pois o laço for
está percorrendo o vetor para imprimir todos os elementos entre os
índices 0 e [ [Link]() -1 ] ( [ index < [Link]( ) ], ou seja de 0 a 11.
Logo, a última string a ser impressa será Novembro.
}
j.  Caso não ocorra um erro de execução, o laço for do código acima irá
imprimir:
// <uma string vazia>
Janeiro
Fevereiro
// Marco, Abril ... Outubro
Novembro.
k.  É possível que ocorra um erro de execução na linha:
[ vec [ Dezembro ] = "Dezembro" ; ], ou em algum momento qualquer
após sua execução.
Isso porque o vetor foi dimensionado para 12 elementos e deve ser
acessado com os índices de 0 a 11.
Mas, nessa linha, está sendo acessado com o índice 12 - pois esse
será o resultado da conversão da constante Dezembro, do tipo Meses,
para int. Logo, está tentando acessar um décimo-terceiro elemento,
para o qual não há memória alocada.
Caso isso atinja uma área de memória não reservada para a aplicação
em um sistema multi-tarefa, o SO irá interromper a aplicação.
Do contrário, teremos resultados indeterminados em operações seguin-
tes, pois é possível que essa operação esteja escrevendo em memória
alocada para outra finalidade.

 Para fazer: crie um projeto de teste para as questões deste capítulo


(quando isso seja possível). Procure reproduzir, no código, as situações
descritas nas questões utilizadas. Introduza algumas variações.

5. Questões do Capítulo 5

1. Exercício 1
Este exercício foi proposto na seção 5.11.2, página 164.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


454 • AGIT informática • C++ • • Anexos

Enunciado: criar um projeto com dois módulos e testar várias funções.

 Neste exercício serão criados duas unidades de tradução (ou módulos), isto é,
dois arquivos fontes. O arquivo [Link] deve conter apenas a função main.
O arquivo [Link]) deverá conter diversas funções que realizarão tarefas
específicas. main deverá testar todas as funções implementadas no módulo
[Link]. As funções que devem ser escritas no módulo [Link] são:
 double Fatorial ( int num ) ;
double Potencia ( int base, int exp ) ;
int PA_TotalTermos ( int inicial, int final, int razao ) ;
void ImprimePares ( int inicial , int final ) ;
double DobraValor ( int ultimo_dia ) ;
double DobraValor_for ( int ultimo_dia ) ;
double TotalCombinacoes ( int conjunto, int escolhas ) ;

Solução:

a) Arquivo "[Link]":
#include <iostream>

// Calcula o fatorial de um número inteiro.


// Mas retorna double, pois o resultado pode, facilmente,
// não caber em um int(em plataformas de 32 ou menos bits) ou um long
double Fatorial ( int num )
{
double result ;
for ( result = 1 ; num > 1 ; --num )
result *= num ; // instrução a executar se condição verdadeira
// Laço para cálculo:
// Inicia "result".
// Avalia.
// Executa [ result*=num ] (se e enquanto o número "num" for maior que 1).
// Após a primeira execução (se ela chegou a ocorrer)
// irá decrementar "num" antes de uma próxima avaliação.
return result ;
}
// Calcula o resultado de "base" elevada a "exp", ambos inteiros.
// Retorna double pela mesma razão de Fatorial.
double Potencia ( int base, int exp )
{
double result ;
for ( result = 1 ; exp > 0 ; --exp )
result *= base ; // instrução a executar se condição verdadeira
// Aplicam-se aqui os comentários sobre o laço da função acima (Fatorial)
// trocando-se apenas as variáveis empregadas e a condição [ exp>0 ]
return result ;
}
// Calcula o Total de Termos (e não a sua soma)
// de uma Progressão Aritmética:

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 455

int PA_TotalTermos ( int inicial, int final, int razao )


{
// Inicialmente, testar se "razao" é diferente de zero
// e também se é compatível com os valores de "inicial" e "final":
/*
// Solução com varios "ifs":

if ( razao == 0 )
return 0 ; // Progressão impossível.

if ( inicial > final ) // inicial maior-que final


{
if ( razao > 0 ) // Razao deveria ser negativa
return 0 ; // nada a fazer
}
else if ( inicial < final ) // inicial menor-que final
{
if ( razao < 0 ) // Razao deveria ser positiva
return 0 ; // nada a fazer
}
*/

// Solução usando operadores lógicos:


if ( razao == 0 || ( inicial > final && razao > 0)
|| ( inicial < final && razao < 0) )
return 0 ; // nada a fazer
// Agora podemos efetuar o cálculo:
// Eleva a precedência da subtração seguida de soma
// e divide o resultado por "razao":
return (final-inicial+razao) / razao ; // retorna o resultado da divisão
}
// Imprime lista de números pares:
void ImprimePares ( int inicial , int final )
{
std::cout << "\nLista dos numeros pares entre "
<< inicial << " e " << final << '\n';

// Descobrir se o inicial é ímpar.


// Se for, não poderá ser impresso
// Então deve ser incrementado, tornando-se par:
// Primeira possibilidade de solução. Usa o operador de módulo:
// se o resto da divisão por 2 for igual a 1, então o número é ímpar.
// if ( inicial % 2 == 1 )
// ++inicial; // "inicial" torna-se par
// Segunda possibilidade. Como queremos calcular o resto da divisão por 2, e
// 2 é uma potência de 2, podemos usar o bitwise "and" entre "inicial" e 2-1 (1):
// if ( ( inicial & 1 ) == 1 ) // se o resultado da divisão por 2 for 1
// ++inicial ; // "inicial" torna-se par

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


456 • AGIT informática • C++ • • Anexos

 Observar que no "if" acima, a precedência do bitwise "and" deve ser defi-
nida explicitamente: comparação por igualdade tem precedência mais alta.

// OK. Mas como o resultado só poderá ser ZERO ou UM,


// podemos fazer simplesmente:
if ( inicial & 1 ) // se o resultado for UM a expressão será verdadeira: é ímpar.
++inicial ; // "inicial" torna-se par
// Para saber quantos números serão impressos, chamar a função
// "PA_TotalTermos" com o valor 2 para "razao"
// já que essa é a distância entre números pares:
int total_pares = PA_TotalTermos( inicial, final, 2) ;
std::cout << "Total de numeros a imprimir: " << total_pares << '\n';

// Imprimir a lista de pares:


std::cout << "Pares no intervalo:\n";
if ( total_pares > 0 ) // se há o que imprimir:
{
// Laço para imprimir os números pares no intervalo, separados por vírgulas:
for ( int numero = inicial ; numero <= final ; numero += 2 )
// "numero" só poderá ser usada neste laço...
{
if ( numero > inicial ) // se já é o segundo ou superior:
std::cout << " , " ; // imprime branco-vírgula-branco antes
// Imprime o número:
std::cout << numero ;
}
std::cout << '\n'; // quebra de linha
// Se tentássemos aqui:
// std::cout << numero << '\n';
// teríamos um erro de compilação pois "numero"
// foi declarada no escopo do laço "for".
}
else
std::cout << "Nenhum numero par nesse intervalo\n";

// E isso é tudo, pois:


// nenhum valor de retorno pode ser devolvido por esta função
// já que o valor de retorno foi especificado como "void".
}

// Ganhando 1 real no primeiro dia, dobrar o valor a cada dia


// até o dia "ultimo_dia":
double DobraValor (int ultimo_dia)
{
// Fórmula de cálculo para o enésimo termo nesse tipo de Progressão Geométrica
// - neste exemplo, o dia "ultimo_dia" (que pode ser 31 ou outro qualquer):
/*
[ an = a1 * q ^ (n-1) ] - (modelo genérico: o símbolo ^ simboliza
exponenciação - isso não é assim em C ou C++)
Onde 'a1' é o primeiro termo, 'q' é a razão de multiplicação e 'n' é

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 457

o total de termos.
ou:
an = a1 * Potencia( q , n-1 ) ;
Neste caso (dobrar o valor a partir do dia 1), o primeiro termo é o dia 1 e
a razão só pode ser 2 (a cada dia ganho o dobro do dia anterior);
então podemos substituir:
 'a1' por 1;
 'q' por 2
 e 'n' por 'ultimo_dia' (que é o parâmetro da função).
*/
// Então, no último dia (o dia "ultimo_dia") ganharei:
// 1 * Potencia( 2 , ultimo_dia - 1 );
// ou simplesmente:
return Potencia( 2 , ultimo_dia-1 ) ;
}
// O mesmo, usando um laço "for":
double DobraValor_for (int ultimo_dia )
{
double result = 1;
for ( int dia = 1; dia < ultimo_dia ; ++dia )
result *= 2 ;
// Multiplico o valor por 2, começando de 1, enquanto [ dia < ultimo_dia ]
// Pois, se ultimo_dia == 1, ganho apenas 1
return result ;
}
// Calcula a quantidade de combinações únicas, considerando uma quantidade de
// escolhas dentro de um conjunto de possibilidades:
double TotalCombinacoes( int conjunto, int escolhas )
{
return Fatorial ( conjunto ) /
( Fatorial(escolhas) * Fatorial(conjunto-escolhas) ) ;
}

b) Arquivo "[Link]":

Neste arquivo implementaremos a função main. Ela deverá testar todas as funções do
módulo anterior ([Link]). Para isso terá que chamá-las. Mas, como não estão vi-
síveis neste módulo, o compilador não reconhecerá essas funções.
Precisamos portanto declarar os seus protótipos antes de efetuar as chamadas. Pode-
mos fazer isso no topo do arquivo, antes da função main:

#include <iostream>

// Protótipos:
double Fatorial ( int num ) ;
double Potencia ( int base, int exp ) ;
int PA_TotalTermos ( int inicial, int final, int razao ) ;
void ImprimePares ( int inicial , int final ) ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


458 • AGIT informática • C++ • • Anexos

double DobraValor ( int ultimo_dia ) ;


double DobraValor_for ( int ultimo_dia ) ;
double TotalCombinacoes ( int conjunto, int escolhas ) ;
Uma idéia melhor é colocar esses protótipos em um arquivo header. Pois, se quiser-
mos usar essas funções em outros projetos, bastará incluir esse arquivo, ao invés de
copiar e colar os protótipos.
Assim, podemos criar um novo arquivo, por exemplo funcoes.h, e inserir esses protó-
tipos nesse arquivo:
Arquivo "funcoes.h", contendo apenas os protótipos abaixo:
double Fatorial ( int num ) ;
double Potencia ( int base, int exp ) ;
int PA_TotalTermos ( int inicial, int final, int razao ) ;
void ImprimePares ( int inicial , int final ) ;
double DobraValor ( int ultimo_dia ) ;
double DobraValor_for ( int ultimo_dia ) ;
double TotalCombinacoes ( int conjunto, int escolhas ) ;
E agora fazer a inclusão no topo de [Link]. Aliás a inclusão desse header seria útil
no próprio arquivo [Link]. Pois, dentro desse módulo:
 A função ImprimePares chama a função PA_TotalTermos.
 A função DobraValor chama a função Potencia.
 A funcao TotalCombinacoes chama a função Fatorial.
No momento não temos problemas pois as funções são chamadas após sua implemen-
tação. Mas isso significa que temos que nos preocupar com a ordem de escrita das fun-
ções. Poderíamos simplesmente esquecer esse assunto, fazendo uma inclusão no topo
de [Link].
O arquivo "[Link]", passa a ter estas duas linhas iniciais:
#include <iostream>
#include "funcoes.h" 
E o arquivo "[Link]", ficaria assim:
#include <iostream>
#include <limits>
#include "funcoes.h" 
// A função abaixo é prototipada aqui, pois só sera usada neste módulo.
// O protótipo é necessario pois está implementada após sua chamada em "main"
// Analisa status de "cin". Em caso de erro,
// imprime mensagem e limpa flags de erro:
bool ValidarEntrada();

int main( )
{
std::cout << "Testa Fatorial:\n";
int num ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 459

for ( num = 0 ; num < 5 ; ++num )


std::cout << "Fatorial de " << num << " = "
<< Fatorial(num) << '\n';
std::cout << "\nTesta Potencia:\n";
for ( num = 0 ; num < 5 ; ++num )
std::cout << "10 elevado a " << num << " = "
<< Potencia(10, num) << '\n';
std::cout << "\nTesta PA_TotalTermos:\n";
int razao;
// Testa com inicial menor que final, razao positiva:
for ( razao = 0 ; razao < 4 ; ++razao )
std::cout << "Total termos entre 1 e 10, razao " << razao << " = "
<< PA_TotalTermos(1, 10, razao) << '\n';
// Testa com inicial maior que final, razao negativa:
for ( razao = -1 ; razao >- 4 ; --razao )
std::cout << "Total termos entre 10 e 1, razao " << razao << " = "
<< PA_TotalTermos(10, 1, razao) << '\n';
// Testa com inicial menor que final, razao negativa:
std::cout << "Total termos entre 1 e 10, razao " << -1 << " = "
<< PA_TotalTermos(1, 10, -1) << '\n';
// Testa com inicial maior que final, razao positiva:
std::cout << "Total termos entre 10 e 1, razao " << 1 << " = "
<< PA_TotalTermos(10, 1, 1) << '\n';
// Testa com inicial igual a final:
std::cout << "Total termos entre 10 e 10, razao " << 1 << " = "
<< PA_TotalTermos(10, 10, 1) << '\n';
std::cout << "Total termos entre 10 e 10, razao " << -1 << " = "
<< PA_TotalTermos(10, 10, -1) << '\n';
std::cout << "\nTesta ImprimePares:\n";
ImprimePares(1,11); // inicial ímpar, final ímpar
ImprimePares(1,10); // inicial ímpar, final par
ImprimePares(2,11); // inicial par, final ímpar
ImprimePares(2,10); // inicial par, final par
ImprimePares(12,12); // iguais e pares
ImprimePares(11,11); // iguais e ímpares
ImprimePares(12,11); // inicial maior-que final
std::cout << "\nTesta DobraValor:\n";
std::cout << "Ganhando 1 real no primeiro dia de um mes,\n"
<< "e dobrando o valor todos os dias,\n"
<< "no dia 31 ganharei: \n";
std::cout << int(DobraValor(31)) << "\n";
std::cout << "O mesmo calculo usando o 'for': \n";
std::cout << int(DobraValor_for(31)) << "\n";

std::cout << "\nTesta TotalCombinacoes:\n";


std::cout << "Usando Fatorial, sei as chances de ganhar:";

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


460 • AGIT informática • C++ • • Anexos

std::cout << "\nNa MegaSena : 1 em "


<< int( TotalCombinacoes(60, 6) ) ;
std::cout << "\nNa LotoFacil: 1 em "
<< int ( TotalCombinacoes(25, 15) ) ;
// Testando com valores informados pelo usuário.
std::cout << "\n\nSolicitando valores ao usuario\n";
std::cout << "\nInforme numero para calculo de fatorial: " ;
std::cin >> num;
if ( ValidarEntrada() )
std::cout << "Fatorial de " << num << " = "
<< Fatorial(num) << '\n';
std::cout << "\nInforme base e expoente para calculo de Potencia: " ;
int base, exp;
std::cin >> base >> exp ;
if ( ValidarEntrada() )
std::cout << base << " elevado a " << exp << " = "
<< Potencia(base, exp) << '\n';
std::cout << "\nInforme inicial, final e razao p/calculo"
<< " do total de termos da PA: " ;
int inicial, final ;
std::cin >> inicial >> final >> razao ;
if ( ValidarEntrada() )
std::cout << "\nTotal de termos nessa PA: " << " = "
<< PA_TotalTermos(inicial, final, razao) << '\n';
std::cout << "\nInforme inicial e final p/lista de pares: " ;
std::cin >> inicial >> final ;
if ( ValidarEntrada() )
ImprimePares( inicial, final );
return 0 ;
}
// Analisa status de "cin". Em caso de erro,
// imprime mensagem e limpa flags de erro:
bool ValidarEntrada()
{
if ( std::[Link]() )
{
std::cout << "valores incorretos\n";
// Limpa erros:
std::[Link]();
// Ignora "new-lines" pendentes no buffer:
std::[Link]( std::numeric_limits<int>::max() , '\n' );
return false; // entrada inválida
}
return true; // entrada válida
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 461

2. Exercício 2

Este exercício foi proposto na seção 5.11.3, página 174.

Enunciado: Implemente uma calculadora que realize as 4 operações básicas (soma,


subtração, divisão e multiplicação).

 Use dois módulos. O primeiro deles ([Link]) deverá conter a função main, a
qual irá testar as funções do segundo módulo. No segundo módulo (calculado-
[Link]), escreva duas versões da função Calculadora. As duas irão pedir ao
usuário, dentro de um laço, que informe: o primeiro operando, o operador e o
segundo operando.
 Após cada operação, perguntar se deve ser feita nova operação.

 A única diferença entre essas duas funções é que uma usará [ if else ] para
analisar o operador e a segunda usará [ switch ( operador ) ].
void Calculadora_if_else ( ) ;
void Calculadora_switch ( ) ;

Solução:

a) Arquivo "calculadora.h", contendo estes protótipos:


void Calculadora_if_else( ) ;
void Calculadora_switch( ) ;

b) Arquivo "[Link]":
#include <iostream>
#include <limits>

// A função abaixo é prototipada aqui, pois só sera usada neste módulo.


// O protótipo é necessario pois está implementada após sua chamada em "main"
// Analisa status de "cin". Em caso de erro,
// imprime mensagem e limpa flags de erro:
bool ValidarEntrada();

// ==== Calculadora que usa [ if else ]


void Calculadora_if_else( )
{
bool continuar = true;
while ( continuar )
{
int operando_1 , operando_2 ;
char operador ;
std::cout << "\nInforme operando_1, operador e operando_2: " ;
// solicita dados:
std::cin >> operando_1 >> operador >> operando_2 ;
// Checa se dados estão corretos:
if ( ValidarEntrada() )
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


462 • AGIT informática • C++ • • Anexos

if ( operador == '+' )
std::cout << "Somar: " << operando_1 + operando_2 << '\n' ;
else if ( operador == '-' )
std::cout << "Subtrair: " << operando_1 - operando_2 << '\n' ;
else if ( operador == '*' )
std::cout << "Multiplicar: " << operando_1 * operando_2
<< '\n' ;
else if ( operador == '/' )
std::cout << "Dividir: " << operando_1 / operando_2 << '\n' ;
else
std::cout << "operador invalido\n";

std::cout << "Nova operacao? (0)encerrar, (1)continuar: ";


std::cin >> continuar ; // isto poderá levar à interrupção do laço
if ( !ValidarEntrada() ) // entrada incorreta aqui:
continuar = false; // fim: interrompe laço
} // fim do primeiro "if ( ValidarEntrada ( ) )"
} // fim do "while"
} // fim da função "Calculadora_if_else( )"

// ==== Calculadora que usa [ switch ]


void Calculadora_switch( )
{
bool continuar = true;
while ( continuar )
{
int operando_1 , operando_2 ;
char operador ;
std::cout << "\nInforme operando_1, operador e operando_2: " ;
// solicita dados:
std::cin >> operando_1 >> operador >> operando_2 ;
// Checa se dados estão corretos:
if ( ValidarEntrada() )
{
switch ( operador )
{
case '+' :
std::cout << "Somar: " << operando_1 + operando_2
<< '\n' ;
break;
case '-' :
std::cout << "Subtrair: " << operando_1 - operando_2
<< '\n' ;
break;
case '*' :
std::cout << "Multiplicar: " << operando_1 * operando_2
<< '\n' ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 463

break;
case '/' :
std::cout << "Dividir: " << operando_1 / operando_2
<< '\n' ;
break;
default:
std::cout << "operador invalido\n";

} // fim do "switch"

std::cout << "Nova operacao? (0)encerrar, (1)continuar: ";


std::cin >> continuar ; // isto podera levar à interrupção do laço
if ( !ValidarEntrada() ) // entrada incorreta aqui:
continuar = false; // fim: interrompe laço
} // fim do primeiro "if ( ValidarEntrada ( ) )"
} // fim do "while"
} // fim da função "Calculadora_switch( )"

// Analisa status de "cin". Em caso de erro,


// imprime mensagem e limpa flags de erro:
bool ValidarEntrada()
{
if ( std::[Link]() )
{
std::cout << "valores incorretos\n";
// Limpa erros:
std::[Link]();
// Ignora "new-lines" pendentes no buffer:
std::[Link]( std::numeric_limits<int>::max() , '\n' );
return false; // entrada inválida
}
return true; // entrada válida
}

c) Arquivo "[Link]":
#include <iostream>
#include "calculadora.h"
int main( )
{
// 1) Testa "Calculadora_if_else"
std::cout << "Testa 'Calculadora_if_else'\n";
Calculadora_if_else( );
// 2) Testa "Calculadora_switch"
std::cout << "\nTesta 'Calculadora_switch'\n";
Calculadora_switch( ) ;
return 0 ;
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


464 • AGIT informática • C++ • • Anexos

3. Questões
Estas questões foram propostas na seção 5.11.4, página 175.

Cap.5 - 1. Assinale as afirmações verdadeiras:


a.  O comentário iniciado por /* e finalizado por */ só é válido em C.
{
Errado. É válido também em C++.
}
b.  O comentário iniciado por /* e finalizado por */ só é válido em C++.
{
Errado. É válido também em C.
}
c.  O comentário iniciado por /* e finalizado por */ é válido em C e em C++.
d.  O comentário iniciado por // e finalizado pela quebra de linha do texto
(new-line) só é válido em C++ ou em C99, embora muitos compiladores,
dependendo das opções de compilação, o aceitem para C89.

Cap.5 - 2. Em uma linha de instrução podemos:


a.  Encadear diversas operações, inclusive chamadas de função, indepen-
dentemente dos retornos dessas funções.
{
Errado. O tipo do retorno das funções deve ser compatível com as
operações envolvidas. Por exemplo, atribuir o retorno de uma função
cujo valor de retorno é double a uma variável do tipo int, pode levar
à truncagem do valor retornado. Outro exemplo de incompatibilidade
está na resposta 'c', abaixo.
}
b.  Combinar operações, inclusive chamadas de funções, desde que os re-
tornos das funções, usadas como operandos, sejam compatíveis com
os operadores associados.
c.  Atribuir a uma variável o retorno de uma função com valor de retorno
void.
{
Errado. O valor de retorno void indica retorno "vazio" ou inexistên-
cia de um valor de retorno. Logo, não pode haver atribuição do retor-
no de funções assim a nenhuma variável, já que não existem variá-
veis do tipo void (vazias).
}

Cap.5 - 3. Assinale as afirmações corretas, considerando o código abaixo:


char c ; for ( c = 2 ; c >= 0 ; ++c ) { std::cout << c ; }
a.  O código acima leva a um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 465

b.  O bloco de instruções associado ao laço será executado 2 vezes.


{
Errado. No laço, a variável 'c' é iniciada com 2. Em seguida, ocorre o
teste de condição [ c >= 0 ]. O valor 2 é maior-que zero, e a avaliação
resulta verdadeira. Então, em seguida o valor de 'c' será impresso.
Agora, 'c' será incrementada (passando para 3). Continuará assim
maior que zero, o mesmo ocorrendo no próximo incremento (4). Será
executada mais do que duas vezes.
}
c.  O bloco de instruções associado ao laço será executado uma vez.
{
Errado. Ver a resposta 'b', acima.
}
d.  Será executado infinitamente (laço infinito).
{
Errado. Esta afirmação estaria correta se o teste de condição fosse
[ c >= 0 || c < 0 ]. Mas o que temos é [ c >= 0 ].
Como o tipo de 'c' é char, o seu valor máximo é 127. Ao atingir esse
valor, ocorrerá um estouro e o valor de 'c' será -128 (valor negativo).
Logo, estará menor-que zero e com isso a avaliação da condição
terá resultado falso, interrompendo o laço.
}
e.  Nunca será executado.
{
Errado. No laço, a variável 'c' é iniciada com 2. Em seguida, ocorre o
teste de condição [ c > = 0 ]. O valor 2 é maior-que zero. Logo, será
executada no mínimo uma vez Ver também a resposta 'b', acima.
}
f.  Nenhuma das respostas acima está correta.
{ Pois o laço será executado bem mais do que duas vezes, mas será
interrompido quando, após atingir 127, receber novo incremento, tor-
nando-se negativo: -128. Ver também a resposta 'd', acima. }

Cap.5 - 4. Assinale as afirmações corretas, considerando o código abaixo:


unsigned char uc ; for ( uc = 2 ; uc >= 0 ; ++uc )
{ std::cout << uc ; }
a.  O código acima leva a um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
b.  O bloco de instruções associado ao laço será executado 2 vezes.
{
Errado. A resposta '3.b', acima, também é válida aqui.
}
c.  O bloco de instruções associado ao laço será executado uma vez.
{
Errado. A resposta '3.b', acima, também é válida aqui.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


466 • AGIT informática • C++ • • Anexos

d.  Será executado infinitamente (laço infinito).


{ Pois, como o tipo de 'uc' é unsigned char, seu valor máximo é 255.
Após atingir esse valor, receberá novo incremento e ocorrerá um es-
touro, passando para zero. Como a condição é [ uc >= 0 ], o resultado
da avaliação será sempre verdadeiro. }
e.  Nunca será executado.
{
Errado. No mínimo uma vez, já que inicia 'uc' com 2 e o teste de
condição é [ uc >= 0 ]. Ver a resposta '3.b', acima: também é válida
aqui.
}
f.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'd' está correta.
}

Cap.5 - 5. Assinale as afirmações corretas, considerando o código abaixo:


char c ; for ( c = 2 ; c >= 0 || c < 0 ; ++c ) { std::cout << c ; }
a.  O código acima leva a um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
b.  O bloco de instruções associado ao laço será executado 2 vezes.
{
Errado. Observar que a condição [ c >= 0 || c < 0 ] só pode levar a
um laço infinito. Qualquer que seja o valor de 'c' ele será sempre [
maior-ou igual-a zero ] ou [ menor-que zero ]. Logo será executado
infinitamente.
}
c.  O bloco de instruções associado ao laço será executado uma vez.
{
Errado. Ver a resposta 'b', acima.
}
d.  Será executado infinitamente (laço infinito).
e.  Nunca será executado.
{
Errado. Ver a resposta 'b', acima.
}
f.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'd' está correta.
}

Cap.5 - 6. Assinale as afirmações corretas, considerando o código abaixo:


int x ; for ( x=10 ; x > 1 ; --x ) { std::cout << x ; }

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 467

O bloco de instruções associadas ao laço for será executado:


a.  11 vezes.
{
Errado. Imprimirá de 10 a 2: 10, 9, 8, 7, 6, 5, 4, 3, 2.
Pois 'x' é iniciado com 10 e a condição é [ x > 1 ], sendo que após a
execução da impressão, ocorre um decremento.
Logo, o bloco de instruções será executado 9 vezes, o que poderia
ser comprovado por uma chamada à função PA_TotalTermos (que
vimos no exercício 06_dois modulos): PA_TotalTermos( 10, 2, -1 ).
}
b.  10 vezes.
{
Errado. Ver a resposta 'a', acima.
}
c. 9 vezes, de acordo com o resultado de uma chamada à função
"PA_TotalTermos", implementada no exercício "06_dois_modulos",
acima: [ PA_TotalTermos ( 10 , 2 , -1 ) ; ].
d.  Infinitamente (laço infinito).
{
Errado. Ver a resposta 'a', acima.
}
e.  Nunca será executado.
{
Errado. Ver a resposta 'a', acima.
}
f.  Após o encerramento do for o valor de "x" será zero.
{
Errado. Se a condição é [ x > 1 ] o laço será interrompido quando 'x'
atingir 1, e com isso a avaliação da condição terá resultado falso. As-
sim, dentro do laço o último valor a ser impresso será 2 (x>1), e após
o laço, o valor de 'x' será 1.
}
g.  Após o encerramento do for o valor de "x" será um.
h.  Após o encerramento do for o valor de "x" será dois.
{
Errado. Ver a resposta 'f', acima.
}

Cap.5 - 7. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; if ( x == 10) { ++x ; }
// atenção para o símbolo em ( x = = 10 ): dois sinais de igual
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  10 vezes, se 'x' for igual a 10..
{
Errado. Não se aplica. Um teste de condição if não é um laço.
Como há uma única instrução de incremento associada ao if (e não

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


468 • AGIT informática • C++ • • Anexos

dez delas), no máximo haverá um único incremento se a avaliação


da condição [ x == 10 ] tiver resultado verdadeiro (se o valor de 'x'
for 10). Do contrário, não haverá nenhum incremento.
}
b.  Sempre será incrementada uma vez.
{
Errado. A variável 'x' será incrementada uma vez sempre que a
avaliação da condição [ x == 10 ] tiver resultado verdadeiro (se o
valor de 'x' for 10). Do contrário, não haverá nenhum incremento.
}
c.
 Uma vez, somente se 'x' for igual a 10.
d.  Nunca será incrementada.
{
Errado. Depende da avaliação da condição. Ver resposta 'b', acima.
}
e.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
f.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'c' está correta.
}

Cap.5 - 8. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; if ( x = 10) { ++x ; }
// atenção para o símbolo em ( x = 10 ): um sinal de igual
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  10 vezes.
{
Errado. Não se aplica. Não é um laço e, no bloco associado ao if,
há um único incremento (e não dez deles).
}
b.  Uma vez, somente se 'x' for igual a 10.
{
Errado. Observar que a condição é [ x = 10 ] (um sinal de igual) e
não [ x == 10 ] (dois sinais de igual). Isto significa que antes da
avaliação ocorre uma atribuição.
Isto é, independentemente do valor anterior de 'x', nessa expressão
10 é copiado para 'x'. Logo 'x' passa a conter 10, e em seguida é
avaliado: como 10 é diferente de zero (diferente de falso) a
avaliação sempre terá resultado verdadeiro.
Em conseqüência, 'x' sempre será incrementada.
}
c.  Nunca será incrementada.
{
Errado. Ver resposta 'b', acima.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 469

d.  Sempre será incrementada.


e.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
f.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'd' está correta.
}

Cap.5 - 9. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; if ( x > 20 && x < 21 ) { ++x; }
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  Nunca será incrementada.
b.  Sempre será incrementada.
{
Errado. Não existem números inteiros que sejam [ maiores que
20 ] e [ menores que 21 ].
Se maior que 20 será no mínimo 21, logo não será menor que 21.
E se menor que 21 será no máximo 20, logo não será maior que 20).
Seria diferente se 'x' fosse double ou float: neste caso poderíamos
ter 20.1, 20.2, etc.
Então, do modo como está formulada esta questão, a variável 'x'
nunca será incrementada.
}
c.  Será incrementada sempre que 'x' for igual a 20.
{
Errado. Ver a resposta 'b', acima.
}
d.  Será incrementada sempre que 'x' for igual a 21.
{
Errado. Ver a resposta 'b', acima.
}
e.  Será incrementada se 'x' for [ maior que 20 ] OU [ menor que 21 ].
{
Errado. A condição é [ x > 20 ] e [ x < 21 ]. Isto é temos um opera-
dor lógico and, e, assim, as duas operações relacionais precisam re-
tornar um resultado verdadeiro.
Para que esta resposta fosse correta, a condição teria que ser:
[ x > 20 || x < 21 ], ou seja: teríamos um operador lógico or conec-
tando as duas operações relacionais. Nesse caso, bastaria que uma
delas fosse verdadeira.
}
f.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


470 • AGIT informática • C++ • • Anexos

g.  Nenhuma das respostas acima está correta.


{
Errado. A resposta 'a' está correta.
}

Cap.5 - 10. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ; if ( x > 20 || x < 21 ) { ++x; }
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  Nunca será incrementada.
{
Errado. Qualquer número inteiro pode ser [ maior que 20 ] ou [ me-
nor que 21 ]. No limite, se 'x' for 20 será menor que 21; e se for 21,
será maior que 20.
Observar que a condição é [ x > 20 ] ou [ x < 21 ]. Logo, as duas
operações relacionais estão conectadas pelo operador lógico or, e
assim basta que uma delas seja verdadeira para que a avaliação re-
torne resultado verdadeiro.
Então, a variável 'x' sempre será incrementada.
}
b.  Sempre será incrementada.
c.  Será incrementada somente se 'x' for igual a 20.
{
Errado. Ver a resposta 'a' acima.
}
d.  Será incrementada somente se 'x' for igual a 21.
{
Errado. Ver a resposta 'a' acima.
}
e.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
f.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'b' está correta.
}

Cap.5 - 11. Assinale as afirmações corretas, considerando o código abaixo:


int x ; std::cin >> x ;
if ( ( x > 20 || x < 21 ) && ( x > 20 && x < 21 ) ) { ++x; }
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  Nunca será incrementada.
b.  Sempre será incrementada.
{
Errado. Temos uma operação lógica and conectando:
- uma operação lógica or [ x > 20 || x < 21 ]

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 471

- uma operação lógica and [ x > 20 && x < 21 ]


A primeira operação (or) terá sempre resultado verdadeiro (ver a res-
posta '10.a', acima).
Já a segunda operação (and) terá sempre resultado falso (ver a res-
posta '9.b', acima).
Em consequência a primeira operação terá sempre resultado falso e
a segunda sempre verdadeiro. Como a expressão completa é:
{ ( [ x > 20 ] ou [ x < 21 ] ) e ( [ x > 20 ] e [ x < 21 ) }, as duas de-
veriam ser verdadeiras para que o resultado final da avaliação fosse
verdadeiro. Como uma delas será sempre falsa, o resultado final tam-
bém será sempre falso. Então, a variável 'x' nunca será incremen-
tada.
}
c.  Será incrementada sempre que 'x' for igual a 20.
{
Errado. Ver resposta 'b', acima.
}
d.  Será incrementada sempre que 'x' for igual a 21.
{
Errado. Ver resposta 'b', acima.
}
e.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
f.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'a' está correta.
}

Cap.5 - 12. Assinale as afirmações corretas, considerando o código abaixo:


int x , y ; std::cin >> x >> y ;
if ( ( x > 20 && x < 22 ) && ( y > 20 && y < 22 ) ) { ++x; }
Quando o 'if' acima for executado, a variável 'x' será incrementada:
a.  Nunca será incrementada.
{
Errado. Temos uma operação lógica and conectando duas opera-
ções lógicas and. Ambas poderão ter resultados verdadeiros depen-
dendo dos valores de 'x' e 'y'.
Pois, tanto para 'x' como para 'y', é exigido que o valor seja [ maior
que 20 ] e [ menor que 22 ].
E existe um número inteiro entre 20 e 22: o número 21.
Logo, se [ 'x' for 21 ] e [ 'y' for 21 ] as duas operações terão resulta-
do verdadeiro e o resultado final também será verdadeiro.
}
b.  Sempre será incrementada.
{
Errado. Depende dos valores de 'x' e de 'y'. Ver resposta 'a', acima.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


472 • AGIT informática • C++ • • Anexos

c.  Sempre que 'x' for igual a 21; 'y' poderá conter 20, 21 e 22.
{
Errado. A variável 'y' deve ser [ maior que 20 ] e [ menor que 22 ].
Ver também a resposta 'a', acima.
}
d.  Sempre que 'y' for igual a 21; 'x' poderá conter 20, 21 e 22.
{
Errado. A variável 'x' deve ser [ maior que 20 ] e [ menor que 22 ].
Ver também a resposta 'a', acima.
}
e.  Sempre que [ 'x' for igual a 21 ] E [ 'y' for igual a 21 ].
f.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
g.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'e' está correta.
}

Cap.5 - 13. Assinale as afirmações corretas, considerando o código abaixo:


int x ; cin >> x ; while ( true ) { ++x ; }
A cada vez que o 'while' acima for executado, a variável 'x' será
incrementada:
a.  Uma vez.
{
Errado. A condição estabelecida para avaliação no laço while é,
simplemente: [ true ].
Isto é, o que será avaliado é uma constante (inalterável). E o resulta-
do da avaliação será sempre verdadeiro, pois true é verdadeiro.
Logo, a variável 'x' será incrementada infinitamente (laço infinito).
}
b.  Nunca será incrementada.
{
Errado. Ocorrerá exatamente o oposto. Ver resposta 'a', acima.
}
c.  Será incrementada infinitamente (laço infinito).
d.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
e.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'c' está correta.
}

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 473

Cap.5 - 14. Assinale as afirmações corretas, considerando o código abaixo:


int x ; cin >> x ; while ( false ) { ++x ; }
A cada vez que o 'while' acima for executado, a variável 'x' será
incrementada:
a.  Uma vez.
{
Errado. A condição estabelecida para avaliação no laço while é,
simplemente: [ false ].
Isto é, o que será avaliado é uma constante (inalterável). E o resulta-
do da avaliação será sempre falso, pois false é falso.
Logo, a variável 'x' nunca será incrementada.
}
b.  Nunca será incrementada.
c.  Será incrementada infinitamente (laço infinito).
{
Errado. Ocorrerá exatamente o oposto. Ver resposta 'a', acima.
}
d.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
e.  Nenhuma das respostas acima está correta.
{
Errado. A resposta 'b' está correta.
}

Cap.5 - 15. Assinale as afirmações corretas, considerando o código abaixo:


int y ; cin >> y ; int x = y > 5 && y < 7 ;
a.  'x' poderá armazenar o número inteiro 5 ou o número inteiro 7.
{
Errado. O resultado de operações relacionais e lógicas é sempre ló-
gico (ou booleano). Assim, [ y > 5 ] retornará true ou false, do mes-
mo modo que [ y < 7 ].
E a avaliação final da expressão completa, baseada no operador lógi-
co and { [ y > 5 ] e [ y < 7 ] }, também terá resultado lógico: true
se as duas operações relacionais forem verdadeiras e false se uma
delas for falsa.
O resultado da avaliação(true ou false) será finalmente convertido
para int [ int x = ... ], assumindo os valores 1 (verdadeiro) ou zero
(falso).
Neste caso, 'x' receberá o valor 1 se 'y' for igual a 6, pois esse é o
único número inteiro que é [ maior que 5 ] e [ menor que 7 ]. Do
contrário, receberá o valor 0(zero).
}
b.  'x' sempre armazenará o valor inteiro 1.
{
Errado. Poderá armazenar 1 ou 0, dependendo do valor de 'y' .

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


474 • AGIT informática • C++ • • Anexos

Ver resposta 'a', acima.


}
c.  'x' sempre armazenará o valor inteiro 0.
{
Errado. Poderá armazenar 0 ou 1, dependendo do valor de 'y'
Ver resposta 'a', acima.
}
d.  'x' armazenará o valor inteiro 1 se 'y' for igual a 6.
e.  'x' armazenará o valor inteiro 0 se 'y' for diferente de 6.
f.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
g.  Nenhuma das respostas acima está correta.
{
Errado. As respostas 'd' e 'e' estão corretas.
}

Cap.5 - 16. Assinale as afirmações corretas, considerando o código abaixo:


int y ; cin >> y ; bool x = y > 5 && y < 7 ;
a.  'x' poderá armazenar o número inteiro 5 ou o número inteiro 7.
{
Errado. Em primeiro lugar, a variável 'x' é do tipo bool (e não int).
Logo, os valores que pode armazenar são true ou false.
Além disso, como vimos na questão acima, o resultado de operações
relacionais e lógicas é sempre lógico (ou booleano). Assim, [ y > 5 ]
retornará true ou false, do mesmo modo que [ y < 7 ].
E a avaliação final da expressão completa, baseada no operador lógi-
co and { [ y > 5 ] e [ y < 7 ] }, também terá resultado lógico: true
se as duas operações relacionais forem verdadeiras e false se uma
delas for falsa.
Neste caso, 'x' receberá o valor true se 'y' for igual a 6, pois esse é o
único número inteiro que é [ maior que 5 ] e [ menor que 7 ]. Do
contrário, receberá o valor false.
}
b.  'x' sempre armazenará o valor booleano true.
{
Errado. Poderá armazenar true ou false, dependendo do valor de
'y'. Ver resposta 'a', acima.
}
c.  'x' sempre armazenará o valor booleano false.
{
Errado. Poderá armazenar false ou true, dependendo do valor de
'y'. Ver resposta 'a', acima.
}
d.  'x' armazenará o valor booleano true se 'y' for igual a 6.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 475

e.  'x' armazenará o valor booleano false, se 'y' for diferente de 6.


f.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
g.  Nenhuma das respostas acima está correta.
{
Errado. As respostas 'd' e 'e' estão corretas.
}

Cap.5 - 17. Assinale as afirmações corretas, considerando o código abaixo:


int y ; cin >> y ; bool x = y <= 5 || y >= 7 ;
a.  'x' sempre armazenará o valor booleano true.
{
Errado. Dependendo do valor de 'y', a variável 'x' poderá armazenar
true ou false. Neste caso, 'y' deverá ser:
[ menor-ou-igual-a 5 ] ou [ maior-ou-igual-a 7 ].
O único número inteiro que não atende a essa condição é o número
6: pois nem é menor-ou-igual-a 5, nem é maior-ou-igual-a 7, logo,
nesse caso, as duas operações relacionais têm resultado falso. O re-
sultado da avaliação final é também falso, já que pelo menos uma
das duas deveria ser verdadeira (or).
Assim, se 'y' contiver 6, o resultado da avaliação será false, do con-
trário, true. E esse resultado será atribuído a 'x'.
}
b.  'x' sempre armazenará o valor booleano false.
{
Errado. Ver a resposta 'a', acima.
}
c.  'x' armazenará o valor booleano true se 'y' for diferente de 6.
d.  'x' armazenará o valor booleano false, se 'y' for igual a 6.
e.  A operação é incorreta e ocorrerá um erro de compilação.
{
Errado. Nenhum erro de sintaxe foi cometido nesse código.
}
f.  Nenhuma das respostas acima está correta.
{
Errado. As respostas 'c' e 'd' estão corretas.
}

Cap.5 - 18. Assinale as afirmações corretas, considerando o código abaixo:


// ...
int x ;
std::cin >> x ;
switch ( x )
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


476 • AGIT informática • C++ • • Anexos

case 1 :
funcao1 ( );
break;
case 2 :
funcao2 ( );
case 3 :
funcao3 ( );
return;
case 4 :
funcao4 ( );
default :
funcao5 ( );
}
++x ;

a.  se 'x' for igual a 1, a funcao1 será chamada e em seguida 'x' será


incrementada.
b.  se 'x' for igual a 2, a funcao2 será chamada e em seguida 'x' será
incrementada.
{
Errado. Como, após a chamada a funcao2, não existe um break ou
um return, o processamento prosseguirá imediatamente abaixo, no
código associado ao label [ case 3 : ].
Assim, imediatamente em seguida, chamará a funcao3, e só agora
encontrará um return, que provocará retorno imediato.
}
c.  se 'x' for igual a 2, a funcao2 será chamada, em seguida a funcao3
será chamada e em seguida 'x' será incrementada.
{
Errado. Realmente funcao2 e funcao3 serão chamadas em seqüên-
cia. Mas após executar funcao3, o processamento encontrará um
desvio return, que provocará retorno imediato. Desse modo, a linha
onde 'x' é incrementada nunca será atingida.
}
d.  se 'x' for igual a 2, a funcao2 será chamada, em seguida a funcao3
será chamada, ocorrendo retorno em seguida.
e.  se 'x' for igual a 3, a funcao3 será chamada e em seguida 'x' será
incrementada.
{
Errado. Há um return após a chamada a funcao3. Desse modo, a
linha onde 'x' é incrementada nunca será atingida.
}
f.  se 'x' for igual a 3, a funcao3 será chamada, ocorrendo retorno em
seguida.
g.  se 'x' for igual a 4, a funcao4 será chamada e em seguida 'x' será
incrementada.
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 477

Errado. Após a chamada a funcao4 não há nenhum desvio. Logo,


antes de atingir a linha onde 'x' é incrementada, ocorrerá uma chama-
da a funcao5.
}
h.  se 'x' for igual a 4, a funcao4 será chamada, em seguida a funcao5
será chamada e em seguida 'x' será incrementada.
i.  a funcao5 será chamada apenas quando 'x' for maior que 4.
{
Errado. A chamada à funcao5 está sob o label default.
Logo, funcao5 será chamada se nenhum case acima tiver resultado
verdadeiro, ou se o case imediatamente anterior tiver resultado ver-
dadeiro, mas não contiver um desvio que impeça que o código de-
fault seja executado.
Então, se 'x' for maior que 4, apenas o default será executado e,
realmente, funcao5 será chamada.
Mas temos duas situações em que 'x' não é maior que 4, e a
funcao5 será chamada:
a. 'x' é menor que 1: não existem cases para essa situação; logo
apenas o default será executado, e funcao5 será chamada.
b. 'x' é igual a 4: neste caso, será chamada a funcao4 e, em segui-
da, também será chamada a funcao5.
}
j.  para qualquer valor menor que 1 e maior que 4 a funcao5 será cha-
mada, ocorrendo retorno em seguida.
{
Errado. Realmente funcao5 será chamada. Mas não há um return
após essa chamada. Então, ao invés de retorno imediato, será atingi-
da a linha onde 'x' é incrementada.
}
k.  para qualquer valor menor que 1 e maior que 4 a funcao5 será chama-
da, e em seguida 'x' será incrementada.
{ A afirmação está correta. Lembrar apenas que, como exposto na
resposta 'i', acima, caso 'x' seja igual a 4, a funcao4 será chamada,
e, em seguida, a funcao5 também será chamada, e finalmente 'x'
será incrementada. }

 Para fazer: crie um projeto de teste para as questões deste capítulo


(quando isso seja possível). Procure reproduzir, no código, as situações
descritas nas questões utilizadas. Introduza algumas variações.

6. Questões do Capítulo 6

Estas questões foram propostas na seção 6.4, página 196.

Cap.6 - 1. Assinale as afirmações corretas, considerando o código abaixo:


int x = 5;
int * px = &x;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


478 • AGIT informática • C++ • • Anexos

a.  'px' é uma referência para 'x'.


{
Errado.
}
b.  'px' é um ponteiro para 'x'.
c.  'px' guarda o endereço de 'x'.
d.  O valor armazenado em 'px' é 5.
{
Errado.
}
e.  O valor armazenado em 'x' é 5.
f.  É correto fazer: *x = 10;
{
Errado.
}
g.  É correto fazer: *px = 10;
Cap.6 - 2. Assinale as afirmações corretas, considerando o código abaixo:
int x = 5 ;
int & rx = x ;
a.  'rx' é uma referência para 'x'.
b.  'rx' é um ponteiro para 'x'.
{
Errado.
}
c.  'rx' guarda o endereço de 'x'.
{
Errado.
}
d.  O valor armazenado em 'rx' é 5.
e.  O valor armazenado em 'x' é 5.
f.  'rx' é um sinônimo de 'x'.
g.  É correto fazer: *rx = 10;
{
Errado.
}
h.  É correto fazer: x = 10;
i.  É correto fazer: rx = 10;
Cap.6 - 3. Assinale as afirmações corretas, considerando o código abaixo:
int x = 5 ;

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 479

int * px ;
px = &x ;
a.  'px' é uma ponteiro para 'x'.
b.  O valor apontado por 'px', a partir da terceira linha, é 5.
c.  O código acima está incorreto e ocorrerá um erro de compilação.
{
Errado.
}

Cap.6 - 4. Assinale as afirmações corretas, considerando o código abaixo:


int x = 5 ;
int & rx ;
rx = x ;
a.  'rx' é uma referência para 'x'.
{
Errado.
}
b.  O valor armazenado em 'rx' é 5.
{
Errado.
}
c.  O código acima está incorreto e ocorrerá um erro de compilação.

7. Questões do Capítulo 7

Estas questões foram propostas na seção 7.6 página 254.

Porque as estruturas (struct ou class) do C++ são melhores que as estruturas


(struct) do C?
 Uma struct ou class C++ permite:
- incluir funções como membros;
- restringir o acesso a determinados membros de tal forma que só possam ser
acessados nas funções declaradas dentro da própria struct ou class;
- incluir uma função especial, a construtora, que irá garantir a correta inicialização
dos membros;
- por isso, a struct ou class C++ é melhor que a struct C: os dados serão protegi-
dos pelas funções e assim poderemos garantir a sua inicialização e validação.

O que são as restrições de acesso private e public ?


 private restringe o acesso aos membros assim declarados; apenas as funções de-
claradas dentro da struct ou class poderão acessar os membros “private”;
public libera o acesso aos membros assim declarados; qualquer função, seja ela
membra da estrutura ou não, poderá acessar os membros “public”.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


480 • AGIT informática • C++ • • Anexos

O que significa “this” ?


 “this” é o parâmetro oculto recebido por funções membras de uma struct ou class;
ele contem o endereço da variável (objeto) que foi utilizada para chamar a função;
assim, neste exemplo,
Data dt ; [Link]( );
será chamada a função “Imprime” da class Data e o endereço de “dt” será recebi-
do em “Imprime” no parâmetro “this”.

O que são funções operadoras ?


 São funções criadas para serem utilizadas no formato “operador” e não apenas no
formato “função”.
Isto é possível utilizando-se a palavra reservada “operator” mais um símbolo de
operação para compor o nome da função.

O que significa “const” na definição do tipo de um parâmetro de função?


 “const “ indica que um ponteiro ou uma referência recebida como argumento, só
poderá servir para que possamos ler o conteúdo apontado ou referenciado por
esse ponteiro ou referência; não poderemos alterar esse conteúdo.

Responda à pergunta que está no comentário do código abaixo:


class X
{
X( )  // Que função é esta e para que serve?
{
}
};

 Essa é a função construtora da “class X”, já que tem o mesmo nome da class.
Serve para inicializar os membros de “X”.

Responda à pergunta que está no comentário do código abaixo:


class X
{
~X( )  // Que função é esta e para que serve?
{
}
};

 Essa é a função destrutora da class “X”, já que tem o mesmo nome da class pre-
cedido pelo sinal “~”.
Serve para realizar qualquer finalização necessária (como fechar arquivos ou libe-
rar memória alocada no heap), já que será chamada obrigatoriamente pelo menos
uma vez: no momento em que um objeto dessa classe estiver para ser desaloca-
do da memória.

Responda às 4 perguntas que estão nos comentários do código abaixo:


class X
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 481

bool operator < ( const X & obj_2 ) const 


// 1- Que função é esta e para que serve?
// 2- Por que a palavra “const” aparece aí duas vezes?
// 3- O que faz aí o “e-comercial” (&) ?
// 4- O que faz aí a letra “X” ?
{};

 1 – Essa é uma função operadora que servirá sejam feitas operações relacionais
“menor que” entre dois objetos dessa classe.
Assim, esses dois objetos poderão ser comparados no formato operação: “ if ( x <
y ); “

 2 – O primeiro “const” especifica que o parâmetro “obj_2” é uma referência “const”


(seus membros poderão ser lidos, mas não alterados);
O segundo “const” especifica que o ponteiro “this” é const (os membros aponta-
dos por ele poderão ser lidos mas não alterados).
 3 – O “e-comercial” (&) indica que o parâmetro “obj_2” está sendo passado por re -
ferência.

 4- “X” é aí o tipo do parâmetro “obj_2”; neste caso, então, “obj_2” é um objeto da


própria classe “X”.

8. Questões do Capítulo 11

Estas questões foram propostas na seção 11.4 página 300.

Responda à pergunta que está no comentário do código abaixo:


class X
{
};
class Y : public X  // O que significa esta declaração?
{
};

 Estamos declarando a class “Y” como uma derivada pública de “X”.


Responda à pergunta que está no comentário do código abaixo:
class X
{
virtual bool Funcao( )  // Que função é esta
e para que serve?
{
}
};

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


482 • AGIT informática • C++ • • Anexos

 Essa é uma função virtual.


Sendo assim, servirá para que uma derivada possa substituí-la (declarando uma
função de mesmo nome).

Essa substituição ocorrerá mesmo que “Funcao” seja chamada por outra função
qualquer da “class X”.
Pois, sendo virtual, será chamada por endereço (armazenado na “virtual table”).

Assim, havendo uma derivada e tendo sido criado um objeto dessa derivada, esse
endereço será alterado antes que a construtora da derivada seja executada.
Desse modo, ao invés do endereço de “Funcao” da base, a “virtual table” conterá
o endereço de “Funcao” da derivada.

Responda à pergunta que está no comentário do código abaixo:


class X
{
X( )
{
}
};
class Y : public X
{
Y( )
{
}
};
int main ( )
{
Y meu_Y ;  // Que funções serão executadas aqui ?
E em que ORDEM ?
}

 a) será chamada a construtora “Y”;

b) antes que a construtora “Y” seja executada, será chamada e executada a cons-
trutora “X”;

c) finalmente será executada a construtora “Y”.

responda à pergunta que está no comentário do código abaixo:


class X
{
int Imprime ( )
{
// ….
}
};
class Y : public X
{
int Imprime( )
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo B. Respostas às questões para revisão • C++ • AGIT informática • 483

//...
}
};
int main ( )
{
Y meu_Y ;
meu_Y.Imprime( ) ;  // Que funções (e de quais classes)
// serão chamadas aqui ?
// E em que ORDEM ?
// ...
}

 Será executada apenas: Y::Imprime( )


(função “Imprime” da “class Y”).

Responda à pergunta que está no comentário do código abaixo:


class X
{
virtual bool Funcao( ) {
// ...
}
int Imprime ( ) {
Funcao( ) ;
// ...
}
};
class Y : public X
{
bool Funcao( ) {
// ...
}
};
int main ( )
{
Y meu_Y ;
meu_Y.Imprime( ) ;  // Que funções (e de quais classes)
// serão chamadas aqui ?
// E em que ORDEM ?
// …
}

 a) será executada X::Imprime( )


(função “Imprime” da “class X”)

b) será executada Y::Funcao()


(função “Funcao” da “class Y”)

Responda à pergunta que está no comentário do código abaixo:


template <class T> class X
{

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


484 • AGIT informática • C++ • • Anexos

T Valor ;  // Qual é o tipo do membro de dados “Valor” ?


};

 Como “X” é um template de class, “T” é, por enquanto, apenas uma parametriza-
ção de tipo e, assim, “Valor” não tem ainda um tipo definido.
Assim sendo, quando criarmos um objeto usando esse template, obrigatoriamente
teremos que indicar um tipo.
Então, “Valor” será criado com o tipo que indicarmos na declaração de cada obje-
to.
Exemplos:
X < int > x1; // Valor será do tipo int.
X <double > x2; // Valor será do tipo double.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo C. Guia de consulta rápida • C++ • AGIT informática • 485

Anexo C. Guia de consulta rápida

[Link] de tipos primitivos ..........................................................................486


2. Tabela de operadores básicos por tipo de operação ...............................487
[Link] completa de operadores e suas precedências ..............................489
[Link] de fluxo de processamento ......................................................491
[Link] diretivas de compilação ............................................................493
6. Seqüências escape .................................................................................494
7. Palavras reservadas ................................................................................494
8. Modificadores ..........................................................................................495

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


486 • AGIT informática • C++ • • Anexos

1. Tabela de tipos primitivos


Tamanho Forma de arma-
mínimo (*1) zenamento
Tipo em bytes Intervalo de valores / natureza
unsigned char 1 0 a 255 inteiro
char 1 -128 a 127 inteiro
unsigned short 2 0 a 65535 inteiro
short 2 -32768 a 32767 inteiro
unsigned long 4 0 a +4294967295 inteiro
long 4 -2147483648 a + 2147483647 inteiro
unsigned int word (*2) (*2) inteiro
int word (*2) (*2) inteiro
enum De 1 byte até, no máximo, o tamanho que te- inteiro (enume-
nha o int, dependendo dos valores usados. ra constantes)
bool 1 true / false (1 ou zero) inteiro (só C++)
float 4 3.4E-38 a 3.4E+38 ponto flutuante
double 8 1.7E-308 a 1.7E+308 ponto flutuante
long double 10 (*4) 3.4E-4932 a 1.1E+4932 ponto flutuante
ponteiro <tipo>* word (*2) (*2) endereço (*3)
void * near, far ou Caso especial para ponteiros: endereço (*3)
word (*2) um ponteiro que não conhece o
tipo apontado (e precisará sofrer
um cast, antes que seja usado).
void Vazio: não é possível declarar objetos desse tipo. Usado apenas
para definir o retorno de funções que não retornam valor.

(*1) O que o padrão de C++ garante são os tamanhos mínimos para cada tipo.
Poderão ocupar mais bytes, dependendo da plataforma. Mas, para saber se
um tipo é capaz de armazenar um determinado valor, independentemente da
plataforma, precisamos nos basear na garantia mínima. Assim, por exem-
plo, não temos garantia de que o número 2000 (que precisa de 2 bytes)
possa ser armazenado em um tipo char (apenas 1 byte garantido). Além dis-
so, observar que o tamanho do tipo "int" sempre depende da plataforma (ver
a nota *2). E o tipo "long double" pode não ser suportado (ver a nota *4).
(*2) Word (“palavra da máquina”): o int, como os ponteiros, têm o tamanho
da palavra da máquina. Exemplos: em sistemas de 16 bits, o int tem 2
bytes; em sistemas de 32 bits, tem 4 bytes; em sistemas de 64 bits, tem 8
bytes..
(*3) Um ponteiro é um número inteiro sem sinal que armazena endereços de
memória e não valores.
(*4) O tipo long double não é suportado em vários sistemas, sendo substituído
por double.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo C. Guia de consulta rápida • C++ • AGIT informática • 487

2. Tabela de operadores básicos por tipo de operação


OPERADORES ARITMÉTICOS
- Subtração
+ Adição
* Multiplicação
/ Divisão
% Resto de uma divisão de inteiros (módulos)
-- Decrementa em 1 (pode ser pré ou pós-fixado)
++ Incrementa em 1 (pode ser pré ou pós-fixado)
OPERADORES RELACIONAIS
> Maior que
>= Maior que ou igual
< Menor que
<= Menor que ou igual
== Igual (equal)
!= Desigual (not equal)
OPERADORES LÓGICOS
&& E (AND)
|| Ou (OR)
! Não (NOT)
OPERADORES BIT A BIT
& E (AND)
| Ou (OR)
^ Ou Exclusivo (Exclusive Or ou XOR)
~ Negação para bits (NOT) - ou “complemento de 1”
>> Deslocamento à direita
<< Deslocamento à esquerda
OUTROS OPERADORES
? : Sintaxe: condição ? expressão1 : expressão2.
Retorno: se a condição for verdadeira retorna a avaliação da ex-
pressão1, caso contrário a avaliação da expressão2.
& &xxx retorna o endereço de xxx (ponteiro a xxx).
* *yyy retorna o valor do objeto cujo endereço está em yyy.
sizeof(<x>) Retorna o tamanho, em bytes, de x (tipo, objeto ou pontei-
ro)
, Seqüencia de diversas operações separadas pela vírgula.
. Referencia os elementos de uma struct ou de uma union.
-> Ponteiro aos elementos de uma struct ou de uma union
( <operação> ) Aumenta a precedência da operação dentro dos parênte-
ses
(tipo)<d> Força a modificação de tipo (cast) de dados (*1)
[ ] Dimensão e índice de uma matriz.
<f>() Os parênteses representam aí uma chamada à função “f”

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


488 • AGIT informática • C++ • • Anexos

OPERADORES COMBINADOS
(uma operação aritmética ou bit-a-bit, seguida de uma operação de atribuição)
Operador Forma equivalente
x += y x = x + y
x -= y x = x - y
x *= y x = x * y
x /= y x = x / y
x %= y x = x % y
x >>= y x = x >> y
x <<= y x = x << y
x &= y x = x & y
x ^= y x = x ^ y
x |= y x = x | y
`
(*1) - CASTS - o operador de molde ou conversão de tipo.
O operador de molde converte uma expressão para um determinado tipo.

Exemplos:
int r = (int) (pi * raio); // o resultado da multiplicação é convertida para int
float a = (float)b / 3; // 'b' é convertido para float e em seguida é dividido por 3

Em C++ (mas não em C89) podemos usar esse operador no “formato função”:
int r = int ( pi * raio ); // o resultado da multiplicação é convertida para int
float a = float ( b ) / 3 ; // 'b' é convertido para float e em seguida é dividido por 3

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo C. Guia de consulta rápida • C++ • AGIT informática • 489

3. Tabela completa de operadores e suas precedências


Cada grupo tem precedência maior que o grupo seguinte.
Dentro do mesmo grupo todos têm a mesma precedência.
Associati-
Operadores Descrição vidade
:: Resolução de escopo 
esquerda
para direi-
ta
++ Incremento pós-fixado 
–- Decremento pós-fixado esquerda
( ) Parênteses (agrupamento de operações) para direi-
[ ] Índice de vetor ta
. Ligação de membro a objeto
-> Ligação de membro a ponteiro para objeto
typeid(<t>) Informações sobre um tipo em run-time.
const_cast Conversão de const para não-const
dynamic_cast Conversão de tipos relacionáveis com verifica-
ção em tempo de execução
static_cast Conversão de tipos relacionáveis sem verifica-
ção
reinterpret_cast Conversão de tipos não relacionáveis
++ Incremento pré-fixado 
-– Decremento pré-fixado direita
+ Adição unária para es-
- Subtração unária querda
! Not lógico (negação)
~ Not para bits (ou complemento)
* Pointer (Ponteiro, de-referência)
& Address Of (retorna endereço de um objeto)
new Alocação dinâmica de memória (1 elemento).
new[] Alocação dinâmica de memória (n elementos).
delete Libera memória alocada por new.
delete[] Libera memória alocada por new[ ].
sizeof(<t>) Tamanho de um tipo, objeto ou ponteiro
(tipo)d e Conversão (cast) genérica de tipo de dados
tipo(d)
.* Ligação de objeto a ponteiro para membro. 
->* Ligação de ponteiro para objeto a ponteiro esquerda
para membro para direi-

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


490 • AGIT informática • C++ • • Anexos

* Multiplicação ta
/ Divisão
% Módulo (resto)
+ Adição
- Subtração
<< Deslocamento de bits à esquerda
>> Deslocamento de bits à esquerda à direita
< “menor que”
<= “menor ou igual que”
> “maior que”
>= “maior ou igual que”
== “Igual a”
!= “diferente de ”
& And para bits
^ Or exclusivo para bits
| Or para bits
&& And lógico
|| Or lógico
<c> ? <t> : <f> Condicional ternário (condição) ? true : false 
= Atribuição Direita
+= x += y : Soma 'x' e 'y', atribui resultado a 'x' para es-
querda
-= x -= y : Subtrai 'y' de 'x', atribui resultado a 'x'
*= x *= y : Multiplica 'x' por 'y', atribui resultado a
/= 'x'
%= x /= y : Divide 'x' por 'y', atribui resultado a 'x'
<<= x &= y: Resto de 'x' dividido por 'y'; atribui a 'x'
>>= x <<= y: Desloca() 'y' bits em 'x'; atribui a 'x'
&= x >>= y: Desloca() 'y' bits em 'x'; atribui a 'x'
|= x &= y: And de bits (x & y); atribui a 'x''
^= x |= y: Or de bits (x | y); atribui a 'x''
x ^= y: Xor de bits (x ^ y); atribui a 'x''
throw Lançamento de exceção -x-
, Vírgula: seqüencia de diversas operações se- 
paradas pela vírgula. esquerda
para direi-
ta

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo C. Guia de consulta rápida • C++ • AGIT informática • 491

4. Controles de fluxo de processamento


[ função( ) ] { }
Abertura de um bloco de instruções, esteja ele iniciando uma função ou mesmo
{ um grupo qualquer de instruções dentro da função (bloco aninhado)
[ declaração de variáveis ou constantes ; ]


instrução ;


} Fechamento de um bloco de instruções, esteja ele encerrando uma função ou
mesmo um grupo qualquer de instruções dentro da função (bloco aninhado)
Um bloco pode ser o corpo completo de uma função ou um bloco interno (ani-
nhado) à uma função.
Aloca memória para a declaração (quanto presente) e executa cada instrução
seqüencialmente, a não ser que o controle de fluxo force uma transferência para
outra instrução, desviando da seqüência natural.

if (expressão) [ { ] <instrução; instrução; ...> [ } ] [else [ { ] <instrução; instrução;...>[ } ] ]


Se a expressão for verdadeira, o primeiro bloco de instruções será executado.
Caso contrário o segundo bloco (se existir) será executado.
Se houver mais do que uma instrução, os blocos de instruções associados ao if
ou ao else devem iniciar e encerrar com chaves [ { …; ….; …. } ].

switch (expressão)

{
[ declaração ; ]

case <constante 1> :


instrução;

...
[ case <constante N> :

instrução ; ]
….

[ default :
instrução ; ]

...
}

Avalia expressão e executa as instruções associadas à constante (“case”) que


tenha este valor. Caso nenhuma constante preencha esta condição, executa o
statement default. As declarações são locais e não permitem inicialização.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


492 • AGIT informática • C++ • • Anexos

for ( [início] ; [condição] ; [ progressão ] ) [ { ] [ <instrução> ] ; [ … ; ] [ } ]


Define um laço. Executa a instrução enquanto “condição” for verdadeira; se
houver mais do que uma instrução, o bloco de instruções associado ao for, deve
iniciar e encerrar com chaves [ { … ; …. ; } ].

início Zero ou mais expressões separadas por vírgulas que serão execu-
tadas antes do laço.
condição O laço será executado enquanto esta condição for verdadeira.
progressão Zero ou mais expressões, separadas por vírgulas, que serão avali-
adas após cada passo.

while (expressão) [ { ] [ <instrução> ] ; [ … ; ] [ } ]

Define um laço. Executa a instrução enquanto “condição” for verdadeira; se


houver mais do que uma instrução, o bloco de instruções associado ao for, deve
iniciar e encerrar com chaves [ { …; ….; …. } ].

do [ { ] <instruções> ; [ } ] while (expressão) ;

Define um laço que será executado, no mínimo uma vez. A partir daí continuará
executando enquanto a expressão for verdadeira. Se houver mais do que uma
instrução, o bloco de instruções associado ao do...while, deve iniciar e encerrar
com chaves [ { …; ….; …. } ].

break ;
Finaliza os laços for, while ou do...while mais recentes;
ou o switch mais recente.

continue ;

Salta as instruções posteriores em um laço, transferindo o fluxo para a próxima


avaliação de condição.

goto <rótulo> ;
Desvia o fluxo do programa para o rótulo especificado. O rótulo pode anteceder
qualquer instrução da função e deve ser seguido por “:”.
Um rótulo obedece ao escopo da função; isto é, não é possível executar goto
para qualquer rótulo que não esteja na mesma função.

return [expressão] ;

Finaliza uma função e opcionalmente retorna o valor da expressão (no caso de


funções cujo tipo de retorno não seja void).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo C. Guia de consulta rápida • C++ • AGIT informática • 493

5. Principais diretivas de compilação


/* <comentário> */ e // <comentário>
Identificam comentários – o compilador ignora tudo o que estiver entre /*
e */, ou após o // até o final da linha de texto.
#define <identificador> <substituto>
Substitui as ocorrências do identificador pelo substituto (simples troca).
#undef <identificador>
Cancela a definição anterior de identificador criado por #define.
#if <expressão>
Permite compilação condicional das declarações seguintes se o valor da
expressão for verdadeiro.
#else
Estabelece uma alternativa se a condição #if fracassar.
#endif
Usada para identificar o fim de uma compilação condicional inciada
com #if, #ifdef ou #ifindef.
#ifdef <identificador> ou #if defined <identificador>
Se o identificador foi definido anteriormente (com #define), as instru-
ções até #else (se existir) ou até #endif são compiladas.
#ifndef (identificador>
Se o identificador não foi definido anteriormente as instruções até
#else (se existir) ou até #endif são compiladas.
Exemplo com #ifdef ... #else ... #endif : // se o identificador “WINDOWS”
#ifdef WINDOWS // foi anteriormente criado com um #define
// … código específico para Windows ...
#else
// … código específico para outras plataformas (Unix, por exemplo) ...
#endif
Apenas uma dessas duas seções de código será levada em conta pelo
compilado (dependendo da existência ou não do identificador “WIN-
DOWS”). A outra será desprezada. .
#error <mensagem>
Na compilação, mostra a <mensagem> e encerra a compilação.
#include <arquivo> ou #include “arquivo”
Inclui o conteúdo do arquivo especificado no módulo corrente.
O nome do arquivo pode estar entre aspas ou entre um sinal de menor
[<] e um sinal de maior [>], o que estabelece a seguinte diferença:
“arquivo” : Busca o arquivo especificado no diretório corrente.
<arquivo> : Não considera o diretório corrente e busca o arquivo em di-
retórios definidos por variáveis de ambientes ou por argumentos passa-
dos para o compilador.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


494 • AGIT informática • C++ • • Anexos

6. Seqüências escape
Código Valor ASCII Significado
\a 0x07 BEL Campainha
\b 0x08 BS Retrocesso (Backspace)
\f 0x0C FF Quebra de Folha (Form feed)
\n 0x0A LF Quebra de Linha (Newline ou Line feed )
\r 0x0D CR Retorno de Carro (Carriage Return)
\t 0x09 HT Tabulação (Horizontal)
\v 0x0B VT Tabulação (Vertical)
\\ 0x5C \ Barra invertida (literal). Exemplo:
cout << “\\”; - imprime a segunda barra.
\’ 0x2C ‘ Apóstrofe (literal): ‘\’’ ; é considerado o caractere ’
\” 0X22 “ Aspas (literal): “\”” ; é considerado o caractere ”
\ooo Qualquer número em octal. Exemplo:
cout << "\101"; - imprime A (decimal 65 em ASCII)
\xhhh Qualquer número em hexadecimal. Exemplo:
cout << "\x41"; - imprime A (decimal 65 em ASCII)

7. Palavras reservadas
asm extern return class
auto far short public
break float signed private
case for sizeof protected
cdecl goto static friend
char huge struct inline
const if switch this
continue int typedef delete
default interrupt union new
do long unsigned operator
double . near void virtual
else pascal volatile try
enum register while catch
namespace mutable explicit throw
using template typeid typename
true false bool

 Atenção: palavras reservadas não podem ser usadas como nomes de identifi-
cadores.

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional


Anexo C. Guia de consulta rápida • C++ • AGIT informática • 495

8. Modificadores

MODIFICADORES DE TIPO
signed Número com sinal.
unsigned Número sem sinal.
long Número inteiro com o tamanho mínimo garantido de 4 bytes.
short Número inteiro com o tamanho mínimo garantido de 2 bytes..
MODIFICADORES DE ACESSO
const Valor constante; inicialização obrigatória; não alterável (read-only).
volatile Valor pode mudar a qualquer momento (não deve ser otimizado).
MODIFICADORES DE CLASSE DE ARMAZENAMENTO
auto Não reserva espaço permanente de memória. Variáveis desta clas-
se são alocadas na pilha, sendo desalocadas automaticamente,
ao final do bloco de sua declaração. É a classe default, para va-
riáveis declaradas dentro de blocos.
extern Indica ao compilador que as variáveis que o seguem serão declara-
dos em algum outro módulo. Usado quando mais de um arquivo
compartilha as mesmas variáveis globais, as quais reservam me-
mória permanente (tempo de vida global).
static Em um escopo local, define variáveis locais ao bloco, mas que
manterão seus valores após o retorno da função. No escopo glo-
bal, variáveis com tempo de vida global (permanentes), mas vi-
sibilidade restrita ao módulo (arquivo) em que foram declaradas.
register Sugere ao compilador manter o valor da variável em registrador
da CPU ao invés da pilha (uma "dica" de otimização, desnecessá-
ria em compiladores modernos).
MODIFICADORES DE MEMÓRIA (não padrão; para plataformas de 16 bits)
<tipo> near Ponteiros que armazenam apenas o offset, que representa um
* deslocamento dentro de um mesmo segmento de memória.
<tipo> far * Ponteiros que armazenam o segmento e o offset .
MODIFICADORES DA FORMA DE CHAMADA DE FUNÇÕES

 Não podem ser usados para funções membras de classe, exceto funções está-
ticas, que não recebem o this como argumento implícito.

pascal Usa convenção de passagem de parâmetros ao modo de Pascal.


Os parâmetros são colocados na pilha da esquerda para a direita e
é responsabilidade da função chamada limpar a pilha antes de re-
tornar.
cdecl Oposto de pascal. Os parâmetros são colocados na pilha da direita
para a esquerda e é responsabilidade de quem chama limpar a pi-
lha após o retorno da função chamada. Em C++, na maioria dos
compiladores, esse é o default para funções globais e estáticas
membras de classe. Em C, para funções globais e estáticas de mó-
dulo (sempre dependendo do compilador).

REPRODUÇÃO PROIBIDA – registro 298.397 - Biblioteca Nacional

Você também pode gostar