Você está na página 1de 624

-

PEARSON
Addison
\\esley
www. aw. com / sav itch_l5r
Página em branco
C++ HBSOLUTO

Walter J. Savitch

Tradução
Claudia Martins

Revisão Técnica
Oswaldo Ortiz Fernandes Jr.
Professor concursado em Teoria da Computação e Compiladores
no Centro Universitário Municipal de SC do Sul
Bacharel e Licenciado em Física pela USP
Pós-graduado em Física pela USP
Mestrando em Engenharia Eletrônica e Computação pelo ITA

PEARSON

• •• • ASSOCIAÇÃO BRASILEIRA DE DIREITOS REPROGRÁFICOS

'~d'°.;
~o.,111z•"° •

São Paulo

Brasil Argentina Colômbia Costa Rica Chile Espanha Guatemala México Peru Porto Rico Venezuela
 2004 by Pearson Education do Brasil
Título original: Absolute C++ — first edition
 2002 by Pearson Education, Inc.

Publicação autorizada a partir da edição original em inglês publicada pela


Pearson Education, Inc., sob o selo Addison Wesley

Todos os direitos reservados. Nenhuma parte desta publicação poderá ser reproduzida ou trans-
mitida de qualquer modo ou por qualquer outro meio, eletrônico ou mecânico, incluindo fotocó-
pia, gravação ou qualquer outro tipo de sistema de armazenamento e transmissão de
informação, sem prévia autorização, por escrito, da Pearson Education do Brasil.

Diretor Editorial: José Martins Braga


Editor: Roger Trimer
Editora de Texto: Adriane Gozzo
Preparação: Sandra Garcia
Revisão: Nilma Guimarães
Designer de Capa: Marcelo Françozo, sobre o projeto original de Leslie Haimes, com foto de Re-
nee Lynn/Stone by Getty Images
Editoração Eletrônica: ERJ Composição Editorial e Artes Gráficas Ltda.

Dados Internacionais de Catalogação na Publicação (CIP)


(Câmara Brasileira do Livro, SP, Brasil)

Savitch, Walter J.
C++ absoluto / Walter Savitch ; tradução Claudia Martins ;
revisão técnica Oswaldo Ortiz Fernandes Jr. -- São Paulo :
Addison Wesley, 2004.

ISBN: 85-88639-09-2

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


I. Título.

03-2860 CDD-005.133

Índices para catálogo sistemático


1. C++ : Linguagem de programação : Computadores :
Processamento de dados 005.133

2004
Direitos exclusivos para a língua portuguesa cedidos à
Pearson Education do Brasil,
uma empresa do grupo Pearson Education
Av. Ermano Marchetti, 1435
CEP: 05038-001, Lapa – São Paulo – SP
Tel: (11) 3613-1222 Fax: (11) 3611-0444
e-mail: vendas@pearsoned.com
Prefácio

C++ Absoluto foi projetado como um manual e livro de referência para a programação na linguagem C++. Embora
inclua técnicas de programação, foi organizado mais em função dos recursos da linguagem C++ do que de algum
currículo específico de técnicas de programação. O público que eu tinha em mente ao escrevê-lo era o de estudan-
tes universitários, especialmente de ciências da computação, ainda sem muita experiência em programação com a
linguagem C++. Este livro foi projetado para ser útil a um grande número de usuários. Os capítulos iniciais foram
escritos em um nível acessível a iniciantes, embora os quadros desses capítulos sirvam para apresentar rapidamente
a sintaxe básica do C++ a programadores mais experientes. Os últimos capítulos também são acessíveis, mas foram
escritos em um nível adequado a estudantes que já evoluíram para tópicos mais avançados.
Este livro também inclui uma introdução aos padrões e à Linguagem Unificada de Modelagem (UML) e um
capítulo inteiro sobre Recursão.

RECURSOS ESPECIAIS
PADRÕES ANSI/ISO C++
Este livro foi escrito de acordo com os novos padrões ANSI/ISO C++.

STANDARD TEMPLATE LIBRARY


A Standard Template Library é uma extensa coleção de bibliotecas de classes de estrutura de dados pré-programa-
das e algoritmos importantes. A STL talvez seja um tópico tão extenso quanto o núcleo da linguagem C++. Este
livro contém uma sólida introdução à STL. Há um capítulo inteiro sobre templates e outro sobre as particularida-
des da STL, além de outros assuntos relacionados com a STL em capítulos diversos.

PROGRAMAÇÃO ORIENTADA A OBJETOS


Este livro trata da estrutura da linguagem C++. Dessa forma, os primeiros capítulos, que abordam aspectos do
C++ comuns a quase todas as linguagens de programação de alto nível, não estão direcionados especificamente à
programação orientada a objetos (OOP). Isso faz sentido em se tratando de um livro de referência e para ensino
de uma segunda linguagem. Entretanto, considero C++ uma linguagem OOP. Se você estiver programando real-
mente em C++ e não em C, precisa tirar proveito dos recursos OOP do C++. Este livro fornece uma extensa
abordagem sobre encapsulamento, herança e polimorfismo como entendidos na linguagem C++. O capítulo final
sobre padrões e UML apresenta outros assuntos relacionados à OOP.

FLEXIBILIDADE NA ORDENAÇÃO DOS TÓPICOS


C++ Absoluto permite aos professores uma grande liberdade de reordenação do material. Isso é importante para
uma obra de referência e combina com a minha filosofia de escrever livros que se adaptem ao estilo do professor
em vez de amarrá-lo à preferência pessoal de ordenamento de tópicos do autor. Tendo isso em mente, a introdu-
ção de cada capítulo explica que material deve ser estudado antes que se execute cada seção do capítulo.

ACESSÍVEL AOS ESTUDANTES


Não é suficiente que um livro apresente os tópicos certos na ordem certa. Nem é suficiente que seja claro e corre-
to quando lido por um professor ou outro especialista. O material precisa ser apresentado em uma forma acessível
a quem ainda não o conhece. Como meus outros manuais, que se revelaram bastante populares entre os estudan-
tes, este livro foi redigido de maneira amigável e acessível.

Quadros
Todos os pontos principais são resumidos em quadros, espalhados ao longo de cada capítulo, que servem como
resumos do conteúdo, como fonte de referência rápida e como forma de aprender rapidamente a sintaxe do C++
VI Prefácio

para recursos que o leitor já conhece de forma geral, mas para os quais necessita saber os detalhes do emprego da
linguagem C++.

Exercícios de Autoteste
Cada capítulo contém diversos Exercícios de Autoteste em pontos estratégicos. As respostas completas para todos
os exercícios são dadas ao final de cada capítulo.

Outros Recursos
Seções de "armadilhas", de técnicas de programação e exemplos de programas completos com amostras E/S são
dadas ao longo de cada capítulo, que termina com uma seção de resumo e vários projetos de programação ade-
quados para serem atribuídos aos estudantes.

MATERIAL DE APOIO
Este livro foi planejado para uso com o Microsoft Visual C++. No site do livro em www.aw.com/savitch_br você
encontra links para diversos sites relacionados, além dos seguintes recursos:
■ Código-fonte do livro
■ Transparências em PowerPoint
Os seguintes recursos estão disponíveis somente para os professores que adotam o livro. Por favor, entre em
contato com o seu representante de vendas local ou envie um e-mail para universitarios@pearsoned.com para ter
acesso ao:
■ Manual do professor (em inglês)

AGRADECIMENTOS
Diversas pessoas contribuíram de forma inestimável para tornar este livro uma realidade. Frank Ruggirello e minha
editora Susan Hartman, da Addison-Wesley, foram os primeiros a imaginarem esta obra. Susan Hartman, Galia
Shokry, Lisa Kalner e outras pessoas fantásticas da Addison-Wesley foram uma contínua fonte de apoio e encora-
jamento para a revisão técnica, revisão de provas e publicação.
Cindy Kogut fez um incrível trabalho de edição de texto. Sally Boylan e outros da Argosy Publishing fizeram
um ótimo trabalho, efetuado em um curto espaço de tempo, na digitalização das páginas.
David Teague merece um agradecimento especial. Apreciei muito seu trabalho árduo, suas ótimas sugestões e
a pesquisa cuidadosa para este livro.
Agradeço a meu bom amigo Mario Lopez pelas muitas conversas proveitosas que tivemos sobre o C++.
Os seguintes revisores forneceram correções e sugestões que contribuíram imensamente para o produto final.
Agradeço a todos. Em ordem aleatória, eles são: Kenrick Mock, University of Alaska, Anchorage; Richard Al-
bright, University of Delaware; H. E. Dunsmore, Purdue University; Christopher E. Cramer; Drue Coles, Boston
University; Evan Golub, University of Maryland; Stephen Corbesero, Moravian College; Fredrick H. Colclough,
Colorado Technical University; Joel Weinstein, Northeastern University; Stephen P. Leach, Florida State Univer-
sity; Alvin S. Lim, Auburn University; e Martin Dulberg, North Carolina State University.
Mais uma vez, agradeço a David Teague, desta vez pelo seu excelente trabalho na preparação do manual do
professor.
Finalmente, agradeço a Christina por ter sido companheira quando eu ficava trabalhando até tarde no livro e
por haver me encorajado em vez de reclamar.
Walter Savitch
http://www-cse.ucsd.edu/users/savitch/
wsavitch@ucsd.edu
SUMÁRIO

Sumário

Capítulo 1 Fundamentos do C++ 1


1.1 Introdução ao C++ 1
1.2 Variáveis, Expressões e Declarações de Atribuição 4
1.3 Entrada/Saída de Terminal 18
1.4 Estilo de Programa 23
1.5 Bibliotecas e Namespaces 24

Capítulo 2 Fluxo de Controle 29


2.1 Expressões Booleanas 29
2.2 Estruturas de Controle 35
2.3 Loops 43

Capítulo 3 Fundamentos das Funções 61


3.1 Funções Predefinidas 61
3.2 Funções Definidas pelo Programador 69
3.3 Regras de Escopo 79

Capítulo 4 Parâmetros e Sobrecarga 91


4.1 Parâmetros 91
4.2 Sobrecarga e Argumentos-Padrão 103
4.3 Testando e Depurando Funções 110

Capítulo 5 Vetores 117


5.1 Introdução aos Vetores 117
5.2 Vetores em Funções 123
5.3 Programando com Vetores 132
5.4 Vetores Multidimensionais 139

Capítulo 6 Estruturas e Classes 153


6.1 Estruturas 153
6.2 Classes 162

Capítulo 7 Construtores e Outras Ferramentas 177


7.1 Construtores 177
7.2 Mais Ferramentas 191
7.3 Vectors — Introdução à Standard Template Library 200
VIII Sumário

Capítulo 8 Sobrecarga de Operador, Amigos e Referências 207


8.1 Fundamentos da Sobrecarga de Operador 207
8.2 Funções Amigas e Conversão de Tipo Automática 218
8.3 Referências e Mais Operadores Sobrecarregados 223

Capítulo 9 Strings 241


9.1 Tipo Vetor para Strings 241
9.2 Ferramentas de Manipulação de Caracteres 249
9.3 Classe-Padrão string 258

Capítulo 10 Ponteiros e Vetores Dinâmicos 277


10.1 Ponteiros 277
10.2 Vetores Dinâmicos 288
10.3 Classes, Ponteiros e Vetores Dinâmicos 297

Capítulo 11 Compilação Separada e Namespaces 313


11.1 Compilação Separada 313
11.2 Namespaces 324

Capítulo 12 E/S de Arquivo e Streams 343


12.1 Streams de E/S 344
12.2 Ferramentas para E/S de Stream 355
12.3 Hierarquias de Stream: Introdução à Herança 363
12.4 Acesso Aleatório a Arquivos 369

Capítulo 13 Recursão 377


13.1 Funções void Recursivas 377
13.2 Funções Recursivas que Retornam um Valor 386
13.3 Pensando Recursivamente 390

Capítulo 14 Herança 403


14.1 Fundamentos da Herança 403
14.2 Programando com Herança 409

Capítulo 15 Polimorfismo e Funções Virtuais 435


15.1 Princípios das Funções Virtuais 435
15.2 Ponteiros e Funções Virtuais 444

Capítulo 16 Templates (Gabaritos) 455


16.1 Templates de Função 455
16.2 Templates de Classe 464
16.3 Templates e Herança 472

Capítulo 17 Estruturas de Dados Ligadas 481


17.1 Nós e Listas Ligadas 482
17.2 Aplicações de Lista Ligada 498
17.3 Iteradores 508
17.4 Árvores 515
Sumário IX

Capítulo 18 Tratamento de Exceções 529


18.1 Fundamentos do Tratamento de Exceções 530
18.2 Técnicas de Programação para o Tratamento de Exceções 543

Capítulo 19 Standard Template Library 549


19.1 Iteradores 550
19.2 Containers 559
19.3 Algoritmos Genéricos 569

Capítulo 20 Padrões e UML 585


20.1 Padrões 585
20.2 UML 593

Apêndice 1 599

Apêndice 2 600

Apêndice 3 602

Apêndice 4 603

Apêndice 5 608

Índice 609
CAPÍTULO

Fundamentos do C++

Capítulo 1C++ Básico


Fundamentos do C++
A Máquina Analítica não tem nenhuma pretensão de criar nada. Pode fazer qual-
quer coisa que saibamos como mandá-la fazer. Pode acompanhar a análise; mas não
tem o poder de antecipar quaisquer relações analíticas ou verdades. Sua ocupação é
nos assistir tornando disponível aquilo que já conhecemos.
Ada Augusta, Condessa de Lovelace

INTRODUÇÃO
Este capítulo apresenta a linguagem C++ e fornece detalhes suficientes para permitir que você
lide com programas simples envolvendo expressões, atribuições e entrada e saída (E/S) de ter-
minal. Os detalhes das atribuições e expressões são semelhantes aos da maioria de outras lin-
guagens de alto nível. Cada linguagem possui sua sintaxe de E/S de terminal; portanto, se
você não está familiarizado com C++, esse aspecto pode lhe parecer novo e diferente.

1.1 Introdução ao C++


A linguagem é o único instrumento da ciência.
Samuel Johnson

Esta seção fornece uma visão geral da linguagem de programação C++.

■ ORIGENS DA LINGUAGEM C++


Pode-se pensar nas linguagens de programação C++ como a linguagem de programa-
ção C com classes (e outros recursos modernos adicionados). A linguagem de programação C
foi desenvolvida por Dennis Ritchie, dos AT&T Bell Laboratories, na década de 70. Foi
usada, a princípio, para escrever e manter o sistema operacional UNIX. (Até aquela época,
os programas de sistema UNIX eram escritos em linguagem assembly ou em uma lingua-
gem chamada B, desenvolvida por Ken Thompson, o criador do UNIX.) C é uma lin-
guagem de finalidade geral que pode ser usada para escrever qualquer tipo de programa,
mas seu sucesso e popularidade estão intimamente ligados ao sistema operacional UNIX.
Se você quisesse preservar seu sistema UNIX, precisava usar C. C e UNIX se deram tão
bem que logo não só os programas de sistema mas quase todos os programas comerciais
executados no UNIX eram escritos na linguagem C. C se tornou tão popular que versões
da linguagem foram escritas para outros sistemas operacionais populares; assim, seu uso
não se limitou aos computadores que utilizavam UNIX. Entretanto, apesar de sua popula-
ridade, C não era uma linguagem isenta de problemas.
A linguagem C é peculiar porque é uma linguagem de alto nível com muitos recursos
de linguagem de baixo nível. C está entre os dois extremos, o de uma linguagem de nível
muito alto e o de uma linguagem de baixo nível, e nisso residem tanto sua força quanto
sua fraqueza. Como a linguagem (de baixo nível) assembly, os programas em linguagem
2 Fundamentos do C++

C podem manipular diretamente a memória do computador. Por outro lado, C possui recursos de uma linguagem
de alto nível, o que a torna mais fácil de ler e escrever do que a linguagem assembly. Isso faz de C uma excelente
escolha para escrever programas de sistema, mas para outros programas (e em certo sentido até para programas de
sistema) C não é tão fácil de entender quanto outras linguagens; além disso, não possui tantas verificações auto-
máticas quanto outras linguagens de alto nível.
Para superar essas e outras desvantagens de C, Bjarne Stroustrup, dos AT&T Bell Laboratories, desenvolveu o
C++ no início da década de 80. Stroustrup projetou o C++ como um C aperfeiçoado. A maior parte da lingua-
gem C é um subconjunto da C++, e, assim, muitos programas em C também são programas em C++. (O inverso
não é verdade; muitos programas em C++ não são, definitivamente, programas em C.) Ao contrário de C, C++
possui recursos para classes e, portanto, pode ser usada para a programação orientada a objetos.

■ C++ E PROGRAMAÇÃO ORIENTADA A OBJETOS


A programação orientada a objetos (Object-oriented programming — OOP) é uma técnica de programação
atual popular e poderosa. As principais características da OOP são encapsulamento, herança e polimorfismo. O encap-
sulamento é uma forma de ocultação de informação, ou abstração. A herança tem a ver com a escrita de código reuti-
lizável. O polimorfismo se refere à forma pela qual um único nome pode ter múltiplos significados no contexto da
herança. Tendo dado essas definições, precisamos admitir que elas possuem pouco significado para os leitores que nun-
ca ouviram falar de OOP. Entretanto, descreveremos todos esses termos em detalhes no decorrer deste livro. C++ favo-
rece a OOP fornecendo classes, um tipo de dado que combina dados e algoritmos. C++ não é o que algumas
autoridades chamariam de uma "linguagem pura de OOP". C++ compatibiliza seus recursos OOP com preocupações
em relação à eficiência e o que poderíamos chamar de "praticidade". Essa combinação tornou o C++ a linguagem de
OOP mais amplamente utilizada, embora nem sempre seu uso siga estritamente a filosofia da OOP.

■ CARACTERÍSTICAS DO C++
C++ possui classes que permitem sua utilização como uma linguagem orientada a objetos. Admite a sobrecarga
de funções e operadores. (Todos esses termos serão explicados ao longo do texto; não fique preocupado se não en-
tender alguns deles.) A ligação do C++ com a linguagem C lhe fornece uma aparência mais tradicional do que a
das linguagens orientadas a objetos mais recentes, e, no entanto, ele possui mais mecanismos poderosos de abstra-
ção do que muitas das linguagens populares atuais. C++ possui modelos que possibilitam a implementação total e
direta da abstração do algoritmo. Os modelos de C++ permitem que se escreva código utilizando parâmetros para
tipos. Os mais novos padrões de C++ e a maioria dos compiladores de C++ permitem namespaces múltiplos para
possibilitar maior reutilização dos nomes de classes e funções. Os recursos de tratamento das exceções são seme-
lhantes aos encontrados em outras linguagens de programação. O gerenciamento da memória em C++ é semelhan-
te ao de C. O programador deve alocar sua própria memória e lidar com sua própria coleção de lixo. A maioria
dos compiladores permitirá que você faça em C++ um gerenciamento de memória estilo C, já que o C é, em essência,
um subconjunto de C++. Entretanto, o C++ também tem sua própria sintaxe para um gerenciamento de memória es-
tilo C++, e seria aconselhável que você utilizasse o estilo C++ de gerenciamento de memória ao escrever código em
C++. Este livro utiliza apenas o gerenciamento de memória estilo C++.

■ TERMINOLOGIA DO C++
Todas as entidades semelhantes a procedimentos são chamadas de funções em C++. Tudo o que é chamado
de procedimento, método, função ou subprograma em outras linguagens é chamado de função em C++. Como vere-
mos na próxima subseção, um programa em C++ é basicamente apenas uma função chamada main. As outras ter-
minologias de C++ são praticamente as mesmas que as de outras linguagens de programação e serão explicadas
quando da apresentação de cada conceito.

■ AMOSTRA DE PROGRAMA EM C++


O Painel 1.1 contém um programa simples em C++ e duas possíveis saídas de tela que podem ser geradas
quando um usuário executa o programa. Um programa em C++ é, na realidade, uma definição de função para
Introdução ao C++ 3

uma função chamada main. Quando o programa é executado, a função chamada main é invocada. O corpo da
função main fica entre chaves, { }.Quando o programa é executado, as declarações entre as chaves são executadas.
As duas linhas seguintes fazem com que as bibliotecas com entrada e saída de terminal estejam disponíveis
para o programa. Os detalhes concernentes a essas duas linhas e tópicos relativos são tratados na Seção 1.3 e nos
Capítulos 9, 11 e 12.
# include <iostream>
using namespace std;

A linha seguinte diz que main é uma função sem parâmetros que ao terminar sua execução retornará um valor
inteiro int:
int main ( )

Alguns compiladores permitirão que você omita o int ou substitua-o por void, o que indica uma função que
não retorna nenhum valor. Entretanto, a forma acima é a mais aceita universalmente para iniciar a função main
em um programa C++.
O programa termina quando o seguinte comando é executado:
return 0;

Este comando termina a invocação da função main e fornece 0 como o valor da função. De acordo com o pa-
drão ANSI/ISO C++, este comando não é obrigatório, mas muitos compiladores o exigem. O Capítulo 3 discutirá
as funções de C++ em todos os detalhes.

Painel 1.1 Amostra de programa em C++ (parte 1 de 2)


1 #include <iostream>
2 using namespace std;

3 int main( )
4 {
5 int numberOfLanguages;

6 cout << "Olá, leitor.\n"


7 << "Bem-vindo ao C++.\n";

8 cout << "Quantas linguagens de programação você já usou? ";


9 cin >> numberOfLanguages;

10 if (numberOfLanguages < 1)
11 cout << "Leia o Prefácio. Talvez você prefira\n"
12 << "um livro mais básico do mesmo autor.\n";
13 else
14 cout << "Divirta-se!\n";

15 return 0;
16 }

DIÁLOGO PROGRAMA-USUÁRIO I
Olá, leitor.
Bem-vindo ao C++.
Quantas linguagens de programação você já usou? 0 O usuário digitou 0 no teclado.
Leia o Prefácio. Talvez você prefira
um livro mais básico do mesmo autor.
4 Fundamentos do C++

Painel 1.1 Amostra de programa em C++ (parte 2 de 2)


DIÁLOGO PROGRAMA-USUÁRIO 2
Olá, leitor.
Bem-vindo ao C++.
Quantas linguagens de programação você já usou? 1 O usuário digitou 1 no teclado.
Divirta-se!

A declaração de variáveis em C++ é similar à de outras linguagens de programação. A linha seguinte do Painel
1.1 declara a variável numeroDeLinguagens:
int numeroDeLinguagens;

O tipo int é um dos tipos de C++ para números inteiros (integers).


Se você nunca programou em C++, o uso de cin e cout para a E/S de terminal deve ser novo para você. Este
tópico será abordado mais adiante neste capítulo, mas a idéia geral pode ser observada neste programa-amostra.
Por exemplo, considere as duas linhas seguintes do Painel 1.1:
cout << "Quantas linguagens de programação você já usou? ";
cin >> numeroDeLinguagens;

A primeira linha faz com que o texto entre aspas seja exibido na tela. A segunda linha lê um número que o
usuário digita no teclado e estabelece o número digitado como o valor da variável numeroDeLinguagens,
As linhas
cout << "Leia o Prefácio. Talvez você prefira\n"
<< "um livro mais básico do mesmo autor. \n";

fazem com que duas strings sejam exibidas, em vez de uma. Os detalhes são explicados na Seção 1.3. O símbolo
\n é o caractere de nova linha, que instrui o computador a começar uma nova linha de saída.
Embora você possa não estar certo sobre os detalhes exatos de como escrever essas declarações, provavelmente será
capaz de adivinhar o significado do comando if-else. Os detalhes serão explicados no próximo capítulo.
(A propósito, se você ainda não teve experiência com nenhuma linguagem de programação, deveria ler o prefácio
para ver se o livro mais básico de que falamos neste programa não lhe seria mais adequado. Você não precisa ter tido
qualquer experiência com C++ para ler este livro, mas é necessária uma experiência mínima com programação.)

1.2 1 Variáveis, Expressões e Declarações de Atribuição


Uma vez que uma pessoa tenha compreendido como as variáveis são usadas na programação, entendeu a quin-
tessência da programação.
E. W. Dijkstra, Notes on Structured Programming

As variáveis, expressões e atribuições em C++ são similares às da maioria das outras linguagens de finalidade geral.

■ IDENTIFICADORES
O nome de uma variável (ou outro item que possa ser definido em um programa) é chamado de identificador.
Um identificador em C++ deve começar com uma letra ou um símbolo de sublinhado, e todos os outros caracteres de-
vem ser letras, dígitos ou o símbolo de sublinhado. Por exemplo, todos os identificadores seguintes são válidos:
x x1 x_1_abc ABC123z7 soma TAXA contagem dado2 grandeBonus

Todos os nomes acima são corretos e deveriam ser aceitos pelo compilador, mas os primeiros cinco são esco-
lhas ruins para identificadores, porque não descrevem o uso do identificador. Nenhum dos identificadores seguin-
tes é correto, e todos seriam rejeitados pelo compilador:
12 3X %troco dado-1 meuprimeiro.c PROG.CPP
Variáveis, Expressões e Declarações de Atribuição 5

Os três primeiros não são permitidos porque não começam com uma letra nem com um caractere de sublinhado. Os
três restantes não são identificadores porque contêm símbolos que não são letras, dígitos ou o caractere de sublinhado.
Embora seja legal começar um identificador com um sublinhado, você deveria evitar fazer isso, porque os identificado-
res que começam com um sublinhado são reservados informalmente para identificadores do sistema e bibliotecas-padrão.
C++ é uma linguagem que percebe a diferença entre maiúsculas e minúsculas nos identificadores. Assim, os
três identificadores a seguir são diferentes e poderiam ser usados para nomear três variáveis diferentes.
taxa TAXA Taxa

Entretanto, não é uma boa idéia usar duas variantes desse tipo no mesmo programa, já que isso poderia criar
confusão. Embora não seja exigido pelo C++, as variáveis normalmente são escritas com a primeira letra em mi-
núscula. Os identificadores predefinidos, como main, cin, cout e outros, devem ser escritos apenas com letras mi-
núsculas. A convenção que agora está se tornando universal na programação orientada a objetos é escrever nomes
de variáveis com uma mistura de letras maiúsculas e minúsculas (e dígitos), começando sempre o nome da variável
com letra minúscula e indicando os limites das "palavras" com uma letra maiúscula, como ilustrado nos seguintes
nomes de variáveis:
velMax, taxaBanco1, taxaBanco2, horaDeChegada

Essa convenção não é tão comum em C++ quanto em algumas outras linguagens orientadas a objetos, mas
está sendo usada mais amplamente e é uma boa convenção a seguir.
Um identificador C++ pode ter qualquer comprimento, embora alguns compiladores ignorem todos os carac-
teres excedentes, caso a quantidade de caracteres seja maior que um número especificado.

IDENTIFICADORES
Um identificador C++ deve começar com uma letra ou com um caractere de sublinhado, e os caracteres restantes devem ser
apenas letras, dígitos, ou o caractere de sublinhado. Os identificadores C++ fazem diferença entre maiúsculas e minúsculas e
não têm limite de comprimento.

Há uma classe especial de identificadores, chamados de palavras-chave ou palavras reservadas, que possuem um signi-
ficado predefinido em C++ e não podem ser usados como nomes de variáveis ou qualquer outra coisa. Neste livro, as pa-
lavras-chave aparecem destacadas no texto. Uma lista completa das palavras-chave é fornecida no Apêndice 1.
Algumas palavras predefinidas, como cin e cout, não são palavras-chave. Essas palavras predefinidas não fazem
parte do núcleo da linguagem C++, e você não é autorizado a redefini-las. Embora essas palavras predefinidas não
sejam palavras-chave, são definidas em bibliotecas exigidas pela linguagem C++ padrão. Não é preciso dizer que
usar um identificador predefinido para algo diferente do padrão pode criar confusão e perigo; assim, isso deve ser evi-
tado. A prática mais segura e fácil é tratar todos os identificadores predefinidos como se fossem palavras-chave.

■ VARIÁVEIS
Cada variável em um programa em C++ deve ser declarada antes de ser usada. Quando você declara uma va-
riável, está dizendo ao compilador — e, em última análise, ao computador — que tipo de dados serão armazena-
dos na variável. Por exemplo, aqui estão duas definições que podem ocorrer em um programa C++:
int numeroDeFeijoes;
double umPeso, totalPeso;

A primeira define a variável numeroDeFeijoes de forma a conter um valor do tipo int, ou seja, um número
inteiro. O nome int é uma abreviação de "integer" (inteiro). O tipo int é um dos tipos para números inteiros. A
segunda definição declara umPeso e totalPeso como variáveis de tipo double, que é um dos tipos para números
com um ponto decimal (conhecidos como números de ponto flutuante). Como ilustrado aqui, quando há mais
de uma variável em uma definição, as variáveis são separadas por vírgulas. Observe também que cada definição
termina com um ponto-e-vírgula.
6 Fundamentos do C++

Cada variável deve ser declarada antes de ser usada; obedecida essa regra, podem-se declarar variáveis em qualquer
lugar. É óbvio que elas devem sempre ser declaradas em um local que torne o programa mais fácil de ser lido. Normal-
mente, as variáveis são declaradas logo antes de serem usadas ou no início de um bloco (indicado por uma chave de
abertura, { ). Qualquer identificador legal, exceto as palavras reservadas, pode ser usado para um nome de variável.*
O C++ possui tipos básicos para caracteres, números inteiros e números de ponto de flutuação (números com
um ponto decimal). O Painel 1.2 lista os tipos básicos de C++. O tipo comumente usado para intervalos é int. O
tipo char é o tipo para caracteres únicos e pode ser tratado como um tipo inteiro, mas nós não o aconselhamos a
fazer isso. O tipo comumente usado para números de ponto flutuante é double e, assim, você deve usar double
para números de ponto flutuante, a não ser que tenha alguma razão específica para usar um dos outros tipos de
ponto flutuante. O tipo bool (abreviação de booleano) possui os valores true e false. Não é um tipo inteiro, mas,
para se adaptar a códigos antigos, você pode converter bool em qualquer outro dos tipos inteiros e vice-versa.
Além disso, a biblioteca-padrão chamada string fornece o tipo string, que é usado para strings de caracteres. O
programador pode definir tipos para vetores, classes e apontadores, o que será discutido em capítulos posteriores
deste livro.

Painel 1.2 Tipos simples


NOME DO TIPO MEMÓRIA UTILIZADA INTERVALO PRECISÃO
short 2 bytes –32.767 a 32.767 Não aplicável
(também chamado
short int)
int 4 bytes –2.147.483.647 a Não aplicável
2.147.483.647
long 4 bytes –2.147.483.647 a Não aplicável
(também chamado 2.147.483.647
long int)
float 4 bytes aproximadamente 7 dígitos
10–38 a 1038
double 8 bytes aproximadamente 15 dígitos
10–308 a 10308
long double 10 bytes aproximadamente 19 dígitos
10–4932 a 104932
char 1 byte Todos os caracteres ASCII (Também Não aplicável
pode ser usado como um tipo integer,
embora não o recomendemos.)
bool 1 byte true, false Não aplicável
Os valores listados aqui são apenas valores experimentais para lhe dar uma idéia geral de como os tipos diferem. Os valores
para cada um desses registros podem ser diferentes em seu sistema. Precisão refere-se ao número de dígitos significativos,
incluindo dígitos na frente do ponto decimal. Os intervalos para os tipos float, double e long double são os intervalos
para os números positivos. Os números negativos possuem um alcance similar, mas com um sinal negativo diante de cada
número.

DECLARAÇÕES DE VARIÁVEIS
Todas as variáveis devem ser declaradas antes de serem usadas. A sintaxe para declarações de variável é a seguinte:
SINTAXE
Tipo_Nome Variavel_Nome_1, Variavel_Nome_2, . . . ;
EXEMPLO
int count, numeroDeDragoes, numeroDeTrolls;
double distancia;

* O C++ faz uma distinção entre declarar e definir um identificador. Quando um identificador é declarado, o nome é
introduzido. Quando é definido, aloca-se espaço para o item nomeado. Para o tipo de variáveis que discutimos neste
capítulo e para várias outras partes do livro, o que estamos chamando de declaração de variável declara a variável e a
define ao mesmo tempo, ou seja, aloca espaço para a variável. Muitos autores fazem distinção entre definição de variável
e declaração de variável. A diferença entre declarar e definir um identificador é mais importante para outros tipos de
identificadores, o que encontraremos em outros capítulos. (N. do R.T.)
Variáveis, Expressões e Declarações de Atribuição 7

Cada um dos tipos inteiros possui uma versão sem sinal que inclui apenas valores não-negativos. Esses tipos
são unsigned short, unsigned int e unsigned long. Seus intervalos não correspondem exatamente aos intervalos
dos valores positivos dos tipos short, int e long, mas tendem a ser maiores (já que usam o mesmo espaço de ar-
mazenamento que seus tipos correspondentes short, int ou long, mas não precisam se lembrar do sinal). Dificil-
mente você precisará desses tipos, mas pode encontrá-los em especificações para funções predefinidas em algumas
das bibliotecas de C++, como discutiremos no Capítulo 3.

■ DECLARAÇÕES DE ATRIBUIÇÃO
A forma mais direta de se mudar o valor de uma variável é usar uma declaração de atribuição. Em C++, o sinal
de igual é utilizado como um operador de atribuição. Uma declaração de atribuição sempre consiste em uma va-
riável no lado esquerdo do sinal de igual e uma expressão no lado direito. Uma declaração de atribuição termina
com um ponto-e-vírgula. A expressão no lado direito de um sinal de igual pode ser uma variável, números, opera-
dores e invocações a funções. Uma declaração de atribuição instrui o computador a avaliar a (ou seja, a calcular o
valor da) expressão do lado direito do sinal de igual e fixar o valor da variável do lado esquerdo como igual ao da
expressão. Aqui estão alguns exemplos de declarações de atribuição em C++:
totalPeso = umPeso * numeroDeFeijoes;
temperatura = 98.6;
contagem = contagem + 2;

A primeira declaração de atribuição fixa o valor de totalPeso como igual ao número na variável umPeso multi-
plicado pelo número em numeroDeFeijoes. (A multiplicação é expressa por meio do asterisco, *, em C++.) A se-
gunda declaração de atribuição fixa o valor de temperatura como 98,6. A terceira declaração de atribuição
aumenta o valor da variável contagem em 2.

DECLARAÇÕES DE ATRIBUIÇÃO
Em uma declaração de atribuição, primeiro a expressão do lado direito do sinal de igual é avaliada e depois a variável do lado
esquerdo do sinal de igual é fixada como igual a esse valor.
SINTAXE
Variavel = Expressao;
EXEMPLOS
distancia = velocidade * tempo;
contagem = contagem + 2;

Em C++, as declarações de atribuição podem ser usadas como expressões. Quando usadas como expressão,
uma declaração de atribuição fornece o valor atribuído à variável. Por exemplo, considere
n = (m = 2);

A subexpressão (m = 2) tanto altera o valor de m para 2 quanto fornece o valor 2. Assim, fixa tanto n quanto
m como igual a 2. Como você verá quando discutirmos em detalhe a precedência de operadores, no Capítulo 2,
podem-se omitir os parênteses; assim, a declaração de atribuição em questão pode ser escrita como
n = m = 2;

Nós o aconselhamos a não utilizar uma declaração de atribuição como uma expressão, mas você deve conhecer
esse comportamento, porque isso o ajudará a entender certos tipos de erro de código. Por exemplo, isso explicará
por que você não receberá uma mensagem de erro quando escrever, erroneamente
n = m = 2;

quando queria escrever


n = m + 2;

(Este é um erro comum, já que os caracteres = e + ficam na mesma tecla.)


8 Fundamentos do C++

LVALUES E RVALUES
Os autores se referem muitas vezes a lvalue e rvalue em livros sobre C++. Um lvalue é qualquer coisa que possa aparecer
do lado esquerdo de um operador de atribuição (=), o que significa qualquer tipo de variável. Um rvalue é qualquer coisa que
possa aparecer do lado direito de um operador de atribuição, o que significa qualquer expressão que calcule um valor.

- Armadilha VARIÁVEIS NÃO-INICIALIZADAS


Uma variável não possui valor com significado até que um programa lhe atribua um. Por exemplo, se a
variável numeroMinimo não recebeu um valor nem como lado esquerdo de uma declaração de atribuição nem
de outra forma (como a de receber um valor de entrada através de um comando cin), então a linha seguinte
está errada:
numeroDesejado = numeroMinimo + 10;
Isto porque numeroMinimo não possui um valor com significado e, assim, toda a expressão do lado direito do
sinal de igual não possui valor com significado. Uma variável como numeroMinimo, que não recebeu um valor, é
chamada de não-inicializada. Essa situação é, na verdade, pior do que se numeroMinimo não tivesse nenhum
valor. Uma variável não-inicializada, como numeroMinimo, simplesmente assumirá um valor qualquer. O valor de
uma variável não-inicializada é determinado pelo padrão de zeros e uns deixado em sua porção na memória
pelo último programa que utilizou aquela porção.
Uma forma de evitar uma variável não-inicializada é inicializar as variáveis ao mesmo tempo em que são
declaradas. Isso pode ser feito acrescentando-se um sinal de igual e um valor, desta forma:
int numeroMinimo = 3;
Esta linha tanto declara numeroMinimo como uma variável do tipo int como fixa o valor da variável
numeroMinimo como igual a 3. Você pode usar uma expressão mais complicada envolvendo operações como
adição ou multiplicação quando inicializa uma variável dentro da declaração assim. Os seguintes exemplos
declaram três variáveis e inicializam duas delas:
double velocidade = 0.07, tempo, saldo = 0.00;
O C++ permite uma notação alternativa para inicializar variáveis quando estas são declaradas. Essa notação
alternativa é ilustrada a seguir, como uma declaração equivalente à anterior:
double velocidade(0.07), tempo, saldo(0.00);

INICIALIZANDO VARIÁVEIS EM DECLARAÇÕES


Você pode inicializar uma variável (ou seja, atribuir-lhe um valor) no momento em que a declara.
SINTAXE
Tipo_Nome Variavel_Nome_1 = Expressao_para_Valor_1,
Variavel_Nome_2 = Expressao_para_Valor_2, ... ;
EXEMPLOS
int contagem = 0, limite = 10, fatorBobo = 2;
double distancia = 999.99;
SINTAXE
Sintaxe alternativa para inicializar em declarações:
Tipo_Nome Variavel_Nome_1 (Expressao_para_Valor_1),
Variavel_Nome_2 (Expressao_para_Valor_2), ... ;
EXEMPLOS
int contagem(0), limite(10), fatorBobo(2);
double distancia (999.99);

Dica
Nomes de variáveis e outros nomes em um programa devem pelo menos aludir ao significado ou ao uso da
coisa que estão nomeando. É muito mais fácil entender um programa se as variáveis possuem nomes com
significado. Compare
*
x = y z;
Com os nomes mais descritivos
Variáveis, Expressões e Declarações de Atribuição 9

Dica (continuação)
distancia = velocidade * tempo;
As duas declarações efetuam a mesma coisa, mas a segunda é muito mais fácil de entender.

■ MAIS DECLARAÇÕES DE ATRIBUIÇÃO


Existe uma notação abreviada que combina o operador de atribuição (=) e o operador aritmético de forma que
uma dada variável possa ter seu valor alterado por meio de adição ou subtração a um dado valor, multiplicação ou
divisão por um dado valor. A forma geral é
VariavelOperador = Expressao

que é equivalente a
Variavel = VariavelOperador (Expressao)

A Expressao pode ser outra variável, uma constante ou uma expressão aritmética mais complicada. A lista se-
guinte fornece exemplos:

EXEMPLO EQUIVALENTE A
contagem += 2; contagem = contagem + 2;
total –= desconto; total = total – desconto;
bônus *= 2; bônus = bônus * 2;
tempo /= fatorPressa; tempo = tempo / fatorPressa;
troco %= 100; troco = troco % 100;
quantia *= cnt1 + cnt2; quantia = quantia * (cnt1 + cnt2);

Exercícios de Autoteste
1. Escreva a declaração para duas variáveis chamadas pés* e polegadas.** Ambas as variáveis são do tipo
int e devem ser inicializadas com o valor zero na declaração. Forneça as alternativas de inicialização.
2. Escreva a declaração para duas variáveis chamadas contagem e distancia. contagem é do tipo int e
inicializada com o valor zero. distancia é do tipo double e inicializada com o valor 1.5. Forneça as
alternativas de inicialização.
3. Escreva um programa que contenha declarações que apresentem como saída os valores de cinco ou seis
variáveis que tenham sido definidas, mas não inicializadas. Compile e execute o programa. Qual é a saída?
Explique.
* **

■ COMPATIBILIDADE DE ATRIBUIÇÃO
Como regra geral, não se pode armazenar um valor de um tipo em uma variável de outro tipo. Por exemplo, a maioria
dos compiladores não aceitará as seguintes linhas:
int intVariavel;
intVariavel = 2.99;

O problema é uma má combinação de tipos. A constante 2.99 é do tipo double, e a variável intVariavel é do
tipo int. Infelizmente, nem todos os compiladores reagirão da mesma forma à declaração de atribuição acima. Al-
guns emitirão uma mensagem de erro, outros, apenas uma mensagem de alerta, e alguns não apresentarão nenhu-
ma forma de erro. Mesmo se o compilador permitir que você use a atribuição acima, ele dará a intVariavel o
valor int 2, não o valor 3. Como você não pode contar com a aceitação do seu compilador à atribuição acima,
não deve atribuir um valor double a uma variável de tipo int.

* Um pé equivale a 30,5 cm no Sistema Internacional de Unidades. (N. do R.T.)


** Uma polegada equivale a 2,54 cm no Sistema Internacional de Unidades. (N. do R.T.)
10 Fundamentos do C++

Mesmo se o compilador permitir que você misture tipos em uma declaração de atribuição, na maioria dos ca-
sos isso não é aconselhável, pois torna seu programa menos portátil, além de causar confusões.
Existem alguns casos especiais em que é permitido atribuir um valor de um tipo a uma variável de outro tipo.
É aceitável atribuir um valor de um tipo inteiro, como int, a uma variável de tipo ponto flutuante, como o tipo
double. Por exemplo, o seguinte estilo é ao mesmo tempo legal e aceitável:
double doubleVariavel;
doubleVariavel = 2;

Isto fixará o valor de uma variável chamada doubleVariavel como igual a 2.0.
Embora em geral essa seja uma má idéia, você pode armazenar um valor int, como 65, em uma variável de
tipo char, e armazenar uma letra como ’Z’ em uma variável de tipo int. Para muitas finalidades, a linguagem C
considera os caracteres como pequenos inteiros e, talvez infelizmente, o C++ herdou isso do C. A razão para per-
mitir isso é que as variáveis de tipo char consomem menos memória do que as variáveis de tipo int. Assim, fazer
operações aritméticas com variáveis do tipo char pode economizar um pouco de memória. Entretanto, é mais cor-
reto utilizar o tipo int quando se lida com inteiros e o tipo char quando se lida com caracteres.
A regra geral é que não se pode colocar um valor de um tipo em uma variável de outro tipo — embora possa parecer
que há mais exceções à regra do que casos que obedeçam a ela. Mesmo que o compilador não imponha essa regra com
muito rigor, é uma boa prática segui-la. Colocar dados de um tipo em uma variável de outro tipo pode causar problemas
porque o valor deve ser alterado para um valor do tipo apropriado e esse valor pode não ser aquele esperado.
Valores de tipo bool podem ser atribuídos a variáveis de um tipo inteiro (short, int, long), e inteiros podem ser atri-
buídos a variáveis do tipo bool. Entretanto, ao fazer isso você prejudica seu estilo. Para maior coerência e para conseguir ler
o código de outras pessoas, é bom você saber: quando atribuído a uma variável de tipo bool, qualquer inteiro diferente de
zero será armazenado como o valor true. O zero será armazenado como o valor false. Quando se atribui a um va-
lor bool uma variável inteira, true será armazenado como 1, e false será armazenado como 0.

■ LITERAIS
Literal é um nome para um valor específico. Os literais muitas vezes são chamados de constantes, para se contra-
por às variáveis. Literais ou constantes não mudam de valor; variáveis podem mudar de valor. Constantes inteiras
são escritas do modo como se costuma escrever números. Constantes de tipo int (ou qualquer outro tipo inteiro)
não devem conter um ponto decimal. Constantes de tipo double podem ser escritas em qualquer das duas formas.
A forma simples para constantes double é o jeito normal de escrever frações decimais. Quando escrita desta forma,
uma constante double deve conter um ponto decimal. Nenhuma constante numérica (inteira ou de ponto flutuan-
te) em C++ pode conter uma vírgula.
Uma notação mais complicada para constantes do tipo double é chamada notação científica ou notação de
ponto flutuante e é particularmente útil para escrever números muito extensos e frações reduzidas. Por exemplo,
3.67 x 1017que é o mesmo que
367000000000000000.00

é mais bem expresso em C++ pela constante 3.67e17. O número 5.89 x 10-6, que é o mesmo que 0.00000589, é mais
bem expresso em C++ pela constante 5.89e-6. E é o símbolo para expoente e significa "multiplicado por 10 na po-
tência que se segue". O e pode ser escrito em letra maiúscula ou minúscula.
Pense no número após o e como aquele que lhe diz a direção e o número de dígitos para mover o ponto de-
cimal. Por exemplo, para mudar 3.49e4 para um número sem um e, mova o ponto decimal quatro casas para a
direita para obter 34900.0, que é outra forma de se escrever o mesmo número. Se o número após o e é negativo,
mova o ponto decimal pelo mesmo número indicado de casas para a esquerda, inserindo zeros extras se necessário.
Assim, 3.49e-2 é o mesmo que 0.0349.
O número antes do e pode conter um ponto decimal, embora isso não seja necessário. Entretanto, o expoente
depois do e não deve, de modo algum, conter um ponto decimal.
Variáveis, Expressões e Declarações de Atribuição 11

O QUE É DOUBLE?
Por que o tipo de números com uma parte fracional é chamado de double? Existe um tipo chamado "single" ("único") que
possua a metade do tamanho do double ("duplo")? Não, mas há algo de verdade nisso. Muitas linguagens de programação
utilizavam tradicionalmente dois tipos para números com uma parte fracional. Um tipo ocupava menos espaço e era bastante
impreciso (ou seja, não permitia muitos dígitos significativos). O segundo tipo ocupava o dobro do espaço na memória e,
assim, era bem mais preciso; também permitia números maiores (embora os programadores tendam a se preocupar mais com a
precisão do que com o tamanho). Os tipos de números que utilizavam o dobro do espaço eram chamados de números de
precisão dupla; os que utilizavam menos espaço eram chamados de números de precisão simples. Seguindo essa tradição, o tipo
que corresponde (mais ou menos) a esse tipo de precisão dupla foi denominado double em C++. O tipo que corresponde à
precisão simples em C++ foi denominado float. C++ possui também um terceiro tipo para números com uma parte
fracional, que é chamado de long double.

Constantes do tipo char são expressas colocando-se o caractere entre aspas simples, como ilustrado aqui:
char symbol = ’Z’;

Observe que a aspa da direita é o mesmo símbolo que a da esquerda.


Constantes para strings de caracteres são dadas entre aspas duplas, como ilustrado pela seguinte linha, retirada
do Painel 1.1:
cout << "Quantas linguagens de programação você já usou? ";

Não se esqueça de que constantes string são colocadas entre aspas duplas, enquanto as de tipo char são colocadas
entre aspas simples. Os dois tipos de aspas possuem significados diferentes. Em particular, ’A’ e "A" possuem significa-
dos diferentes. ’A’ é um valor de tipo char e pode ser armazenado em uma variável de tipo char. "A" é uma string de
caracteres. O fato de que a string contenha apenas um caractere não transforma "A" em um valor de tipo char. Obser-
ve também que tanto nas strings quanto nos caracteres as aspas do lado esquerdo e direito são as mesmas.
Strings entre aspas duplas, como "Oi", muitas vezes são chamadas strings C. No Capítulo 9 veremos que o
C++ possui mais de um tipo de string, e esse tipo particular se chama strings C.
O tipo bool possui duas constantes, true e false. Essas constantes podem ser atribuídas a uma variável de
tipo bool ou usadas em qualquer outro lugar em que uma expressão de tipo bool é permitida. Devem ser escritas
só com letras minúsculas.

■ SEQÜÊNCIAS DE ESCAPE
Uma contrabarra, \, precedendo um caractere diz ao compilador que a seqüência que se segue à contrabarra não tem o
mesmo significado que o caractere sozinho. Tal seqüência é chamada de seqüência de escape. A seqüência é digitada
como dois caracteres sem nenhum espaço entre os símbolos. Existem muitas seqüências de escape definidas em C++.
Se você quiser colocar uma contrabarra, \, ou aspas, ", em uma constante string, precisa escapar à capacidade
das " de terminar uma constante string utilizando \", ou a capacidade da \ de escapar utilizando \\. \\ diz ao com-
putador que você quer uma barra "ao contrário" de verdade, \, e não uma seqüência de escape; o \" diz que você
quer realmente aspas, não o final de uma constante string.
Uma \ desgarrada, digamos \z, em uma constante string terá efeitos diferentes em compiladores diferentes. Um
compilador pode simplesmente devolver um z; outro pode produzir um erro. O padrão ANSI/ISO afirma que se-
qüências de escape não-especificadas possuem comportamento indefinido. Isso significa que um compilador pode
fazer qualquer coisa que seu autor achar conveniente. A conseqüência é que o código que usa seqüências de escape
não-definidas não é portátil. Você não deve utilizar seqüências de escape além daquelas fornecidas pelo padrão
C++. Esses caracteres de controle estão listados no Painel 1.3.

■ DANDO NOMES A CONSTANTES


Números em um computador criam dois problemas. O primeiro é que eles não carregam nenhum valor mnemô-
nico. Por exemplo, quando o número 10 é encontrado em um programa, ele não fornece nenhuma pista do seu
significado. Se o programa é um programa bancário, pode ser o número de filiais ou o número de caixas na cen-
tral. Para entender o programa, você precisa conhecer o significado de cada constante. O segundo problema é que,
quando é preciso alterar alguns números em um programa, a alteração tende a produzir erros. Suponha que 10
12 Fundamentos do C++

ocorra doze vezes em um programa bancário — quatro vezes ele representa o número de filiais e oito vezes ele re-
presenta o número de caixas na central. Quando o banco abrir uma nova filial e o programa precisar ser atualiza-
do, há uma boa possibilidade de que alguns dos 10 que deveriam ser alterados para 11 não sejam, ou que alguns
que não deveriam ser alterados sejam. A maneira de evitar esses problemas é dar nome a cada número e utilizar o
nome em vez do número dentro do seu programa. Por exemplo, um programa bancário poderia ter duas constan-
tes com os nomes CONTAGEM_DE_FILIAIS e CONTAGEM_DE_CAIXAS. Ambos os números poderiam ter
o valor 10, mas, quando o banco abrir uma nova filial, tudo o que você precisa fazer para atualizar o programa é
mudar a definição de CONTAGEM_DE_FILIAIS.

Painel 1.3 Algumas seqüências de escape


SEQÜÊNCIA SIGNIFICADO
\n Nova linha
\r Sinal de retorno (Posiciona o cursor no início da linha atual. Provavelmente você não o utilizará muito.)
\t (Horizontal) Tabulação (Avança o cursor até a próxima tabulação.)
\a Alerta (Soa o sinal de alerta, em geral uma campainha.)
\\ Contrabarra (Permite que você coloque uma contrabarra em uma expressão citada.)
\’ Aspas simples (Em geral usada para colocar aspas simples dentro de uma citação de um caractere.)
\” spas duplas (Em geral usada para colocar aspas duplas dentro de uma citação em string.)
As seqüências seguintes não costumam ser usadas, mas nós as incluímos para fornecer um quadro completo.
\v Tabulação vertical
\b Retrocesso
\f Suprimento de folha (comando que faz a impressora retirar a folha atual)
\? Interrogação

Como se dá nome a um número em um programa C++? Uma das formas é inicializar uma variável com o va-
lor daquele número, como no seguinte exemplo:
int CONTAGEM_DE_FILIAIS = 10;
int CONTAGEM_DE_CAIXAS = 10;

Existe, porém, um problema com esse método de nomear constantes-número: você poderia, inadvertidamente,
trocar o valor de uma dessas variáveis. O C++ fornece uma forma de marcar uma variável inicializada de modo
que esta não possa ser alterada. Se o seu programa tentar mudar uma dessas variáveis, um erro será produzido.
Para marcar uma declaração de variável de modo que o valor da variável não possa ser alterado, coloque antes da
declaração a palavra const (que é uma abreviação de constante). Por exemplo,
const int CONTAGEM_DE_FILIAIS = 10;
const int CONTAGEM_DE_CAIXAS = 10;

Se as variáveis são do mesmo tipo, é possível combinar as linhas acima em uma declaração, como se segue:
const int CONTAGEM_DE_FILIAIS = 10, CONTAGEM_DE_CAIXAS = 10;

Entretanto, a maioria dos programadores pensa que colocar cada definição de nome em uma linha separada
torna o programa mais claro. A palavra const geralmente é chamada de modificador, porque modifica (restringe)
as variáveis declaradas.
Uma variável declarada utilizando o modificador const é, em geral, chamada de constante declarada. Escrever
constantes declaradas com todas as letras em maiúscula não é uma exigência da linguagem C++, mas é uma práti-
ca comum entre os programadores de C++.
Uma vez que um número tenha sido nomeado dessa forma, o nome pode então ser usado em qualquer lugar em
que o número seja permitido, e terá exatamente o mesmo significado que o número que nomeia. Para alterar uma
constante nomeada, você precisa apenas mudar o valor de inicialização na declaração da variável const. O significado
de todas as ocorrências de CONTAGEM_DE_FILIAIS, por exemplo, pode ser mudado de 10 para 11 simplesmente
alterando-se o valor de inicialização 10 na declaração de CONTAGEM_DE_FILIAIS.
O Painel 1.4 contém um programa simples que ilustra o uso do modificador de declaração const.
Variáveis, Expressões e Declarações de Atribuição 13

■ OPERADORES ARITMÉTICOS E EXPRESSÕES


Como a maioria das outras linguagens, o C++ permite que você forme expressões utilizando variáveis, constantes e
os operadores aritméticos: + (adição), – (subtração), * (multiplicação), / (divisão) e % (módulo, resto). Essas ex-
pressões podem ser usadas em qualquer lugar em que seja legal utilizar um valor do tipo produzido pela expressão.
Todos os operadores aritméticos podem ser usados com números de tipo int, números de tipo double e até
mesmo com um número de cada tipo. Entretanto, o tipo do valor produzido e o valor exato do resultado depen-
dem dos tipos de números que estão sendo combinados. Se ambos os operandos (ou seja, ambos os números) são
de tipo int, então o resultado de combiná-los com um operador aritmético é do tipo int. Se um ou ambos os
operandos for do tipo double, então o resultado é do tipo double. Por exemplo, se as variáveis quantiaBase e ju-
ros são do tipo int, o número produzido pela expressão seguinte é do tipo int:
quantiaBase + juros

Painel 1.4 Constante nomeada


1 #include <iostream>
2 using namespace std;
3
4 int main( )
5 {
6 const double TAXA = 6.9;
7 double deposito;
8 cout << "Digite o total do seu depósito $";
9 cin >> deposito;
10 double novoBalanco;
11 novoBalanco = deposito + deposito* ( TAXA /100);
12 cout << "Em um ano, esse depósito aumentará para\n"
13 << "$" << novoBalanco << "uma quantia pela qual vale a pena esperar.\n";

14 return 0;
15 }

DIÁLOGO PROGRAMA-USUÁRIO

Digite o total do seu depósito R$ 100


Em um ano, esse depósito aumentará para
R$ 106.9, uma quantia pela qual vale a pena esperar.

Entretanto, se uma ou ambas as variáveis são do tipo double, então o resultado é do tipo double. Isso também
é verdade se você substituir o operador + por qualquer dos outros operadores, –, *, ou /.
De modo mais geral, você pode combinar quaisquer tipos aritméticos em expressões. Se todos os tipos forem
tipos inteiros, o resultado será de tipo inteiro. Se pelo menos uma das subexpressões for de tipo ponto flutuante,
o resultado será de tipo ponto flutuante. O C++ faz o possível para tornar o tipo de uma expressão int ou dou-
ble, mas, se o valor produzido pela expressão não for um desses tipos devido ao tamanho do valor, um inteiro ou
tipo ponto flutuante diferente adequado será produzido.
Você pode especificar a ordem das operações em uma expressão aritmética inserindo parênteses. Se você omitir
os parênteses, o computador seguirá as chamadas regras de precedência, que determinam a ordem em que as ope-
rações, como a adição e a multiplicação, são executadas. Essas regras de precedência são similares às regras utiliza-
das na álgebra e em outras classes matemáticas. Por exemplo,
x + y * z

é calculada fazendo-se primeiro a multiplicação e depois a adição. Exceto em alguns casos-padrão, como na adição
de strings ou na multiplicação simples inserida dentro de uma adição, normalmente é melhor incluir os parênte-
ses, mesmo que a ordem pretendida de operações seja aquela ditada pelas regras de precedência. Os parênteses tor-
14 Fundamentos do C++

nam a expressão mais legível e menos sujeita a erros do programador. Um conjunto completo de regras de prece-
dência em C++ é fornecido no Apêndice 2.

NOMEANDO CONSTANTES COM O MODIFICADOR const


Quando se inicializa uma variável dentro de uma declaração, pode-se marcar a variável de modo que o programa não tenha a
permissão de alterar o seu valor. Para fazer isso, coloque a palavra const na frente da declaração, como descrito abaixo:
SINTAXE
const Tipo_Nome Variavel_Nome = Constante;
EXEMPLOS
const int MAX = 3;
const double PI = 3.14159;

■ DIVISÃO DE INTEIROS E DE PONTO FLUTUANTE


Quando usado com um ou ambos os operadores do tipo double, o operador de divisão, /, comporta-se como es-
perado. Entretanto, quando usado com dois operadores do tipo int, o operador de divisão fornece a parte inteira
resultante da divisão. Em outras palavras, a divisão inteira descarta a parte depois do ponto decimal. Assim, 10/3
é 3 (não 3.3333...), 5/2 é 2 (não 2.5) e 11/3 é 3 (não 3.6666...). Observe que o número não é arredondado; a parte
depois do ponto decimal é descartada, não importa quão grande seja.
O operador % pode ser usado com operadores de tipo int para recuperar a informação perdida quando se usa
/ para fazer a divisão com números do tipo int. Usado com valores do tipo int, os dois operadores / e % forne-
cem os dois números produzidos quando se efetua o algoritmo longo da divisão, que você aprendeu na escola. Por
exemplo, 17 dividido por 5 dá 3 com resto 2. O operador / fornece o número de vezes que um número "cabe"
dentro de outro. O operador % fornece o resto. Por exemplo, os comandos
cout << "17 dividido por 5 é " << (17/5) << "\n";
cout << "com um resto de " << (17%5) << "\n";
fornece a seguinte saída:
17 dividido por 5 é 3
com resto 2

Quando usado com valores negativos do tipo int, o resultado dos operadores / e % pode ser diferente para
implementações diferentes de C++. Assim, você deve usar / e % com valores int só quando souber que ambos os
valores são não-negativos.

- Armadilha
Quando se usa o operador de divisão / com dois inteiros, o resultado é um inteiro. Isso pode ser um
problema caso se espere uma fração. Além disso, o problema pode facilmente passar despercebido,
resultando em um programa que parece funcionar, mas que produz uma saída incorreta sem que você nem
se dê conta. Por exemplo, suponha que você seja um arquiteto paisagista que cobra cinco mil reais
(R$ 5.000) por milha* no projeto de uma estrada, e suponha que você conheça a extensão da estrada na qual
trabalhará em pés. O preço que você cobrará pode ser facilmente calculado pela seguinte declaração em C++:
precoTotal = 5000 * (pes/5280.0);
Isto funciona porque há 5.280 pés em uma milha. Se a extensão da estrada for 15.000 pés, a fórmula lhe
dirá que o preço total é
5000 * (15000/5280.0)
Seu programa em C++ obtém o valor final da seguinte forma: 15000/5280.0 é calculado como 2.84. Então o
programa multiplica 5000 por 2.84 para obter o valor 14200.00. Com a ajuda do seu programa em C++,
você sabe que deveria cobrar R$14.200 pelo projeto.
Agora suponha que a variável pes seja do tipo int, e que você se esqueça de colocar o ponto decimal e o
zero, de modo que a declaração de atribuição do seu programa fique assim:
precoTotal = 5000 * (pes/5280);

* Uma milha terrestre equivale a 1,609 km. (N. do R.T.)


Variáveis, Expressões e Declarações de Atribuição 15

- Armadilha (continuação)
Ainda parece bom, mas causará um problema sério. Se você utilizar esta segunda forma da declaração de
atribuição, estará dividindo dois valores de tipo int e, assim, o resultado da divisão pes/5280 é
15000/5280, que é o valor int 2 (em vez do valor 2.84 que você pensa estar obtendo). O valor atribuído a
precoTotal é, então, 5000 * 2 ou 10000.00. Se você esquecer o ponto decimal, cobrará R$ 10.000.
Entretanto, como já vimos, o valor correto é R$ 14.200. A falta de um ponto decimal lhe custou R$ 4.200.
Note que isso será verdade quer o tipo de precoTotal seja int quer seja double; o estrago é causado
antes que o valor seja atribuído a precoTotal.

Exercícios de Autoteste
4. Converta cada uma das seguintes fórmulas matemáticas em uma expressão em C++.
x+y 3x + y
3x 3x + y
7 z+2
5. Qual é a saída das seguintes linhas de programa quando estão inseridas em um programa correto que
declara todas as variáveis como de tipo char?
a = ’b’;
b = ’c’;
c = a;
cout << a << b << c << ’c’;
6. Qual é a saída das seguintes linhas de programa quando estão inseridas em um programa correto que
declara número como de tipo int?
numero = (1/3) * 3;
cout << "(1/3) * 3 é igual a " << numero;
7. Escreva um programa completo em C++ que leia dois números inteiros em duas variáveis de tipo int e
então apresente como saída tanto a parte inteira do número quanto o resto quando o primeiro número é
dividido pelo segundo. Isso pode ser feito utilizando-se os operadores / e %.
8. Dado o seguinte fragmento que pretende converter graus Celsius em graus Fahrenheit, responda às
seguintes questões:
double c = 20;
double f;
f = (9/5) * c + 32.0;
a. Qual é o valor atribuído a f?
b. Explique o que está acontecendo na verdade e o que, provavelmente, o programador desejava.
c. Reescreva o código como o programador pretendia.

■ CONVERSORES DE TIPOS (CASTING)


Um conversor de tipo, ou casting, é uma forma de alterar um valor de um tipo para um valor de outro tipo. Um cas-
ting é um tipo de função que toma o valor de um tipo e produz um valor de outro tipo que seja, em C++, o mais
aproximado de um valor equivalente. O C++ possui de quatro a seis tipos diferentes de conversores, dependendo de
como eles são contados. Há uma forma mais antiga de conversor de tipo que pode ser expressa por meio de duas no-
tações e quatro formas novas de conversores de tipos introduzidas com o último padrão. Os novos conversores de tipo
foram concebidos como substitutos para a forma antiga; neste livro, usaremos os novos. Entretanto, o C++ conserva os
velhos conversores com os novos, e, por isso, descreveremos brevemente os velhos também.
Vamos começar com os conversores de tipo mais novos. Considere a expressão 9/2. Em C++ essa expressão
produz o resultado 4, porque, quando ambos os operandos são de um tipo inteiro, o C++ efetua divisão inteira.
Em algumas situações, você pode querer que a resposta seja o valor double 4.5. Você pode obter um resultado de
4.5 utilizando o valor de ponto flutuante "equivalente" 2.0 em lugar do valor inteiro 2, como em 9/2.0, que pro-
duz o resultado 4.5. Mas e se o 9 e o 2 forem variáveis de tipo int chamadas n e m? Então n/m dá 4. Se você qui-
ser uma divisão de ponto flutuante neste caso, precisará de um conversor de tipos de int para double (ou outro
tipo de ponto flutuante), como no seguinte exemplo:
double ans = n/static_cast<double>(m);
16 Fundamentos do C++

A expressão
static_cast<double>(m)

é um conversor de tipos. A expressão static_cast<double> é como uma função que toma um argumento int (na
realidade, um argumento de praticamente qualquer tipo) e fornece um valor "equivalente" do tipo double. Assim,
se o valor de m é 2, a expressão static_cast<double>(m) fornece o valor double 2.0.
Observe que static_cast<double>(n) não altera o valor da variável n. Se n possuía o valor 2 antes de a expres-
são ser calculada, então n ainda possuirá o valor 2 depois de a expressão ser calculada. (Se você sabe o que é uma
função em matemática ou em alguma linguagem de programação, pode pensar em static_cast<double> como
uma função que fornece um valor "equivalente" de tipo double.)
Você pode usar qualquer nome de tipo em lugar de double para obter um conversor de um tipo para o outro.
Dissemos que isso produz um valor "equivalente" do tipo-alvo. A palavra equivalente está entre aspas porque não
existe uma noção clara de equivalente aplicável entre dois tipos quaisquer. No caso de um conversor de tipo de
inteiro para ponto flutuante, o efeito é acrescentar um ponto decimal e um zero. O conversor de tipo na outra di-
reção, de ponto flutuante a inteiro, simplesmente apaga o ponto decimal e todos os dígitos depois dele. Observe
que quando a conversão de tipos é de tipo ponto flutuante para inteiro, o número é truncado, não arredondado.
static_cast<int>(2.9) é 2, e não 3.
Esse static_cast é o tipo mais comum de conversor de tipos e o único que usaremos por algum tempo. Para você
ter uma idéia geral e como fonte de referência, listamos todas as quatro formas de conversores de tipo. Algumas podem
não fazer sentido até que você chegue aos tópicos relevantes. Se alguma ou todas as outras três formas não fizerem sen-
tido para você a esta altura, não se preocupe. As quatro formas de conversores de tipos são as seguintes:
static_cast<Tipo>(Expressao)
const_cast<Tipo>(Expressao)
dynamic_cast<Tipo>(Expressao)
reinterpret_cast<Tipo>(Expressao)

Já falamos sobre static_cast. É um conversor de finalidade geral que se aplica às situações mais "comuns". O
const_cast é usado para se desfazer de constantes. O dynamic_cast é usado para descer de um tipo a um tipo in-
ferior em uma hierarquia de herança. O reinterpret_cast é um conversor que depende da implementação, que
não discutiremos neste livro e de que dificilmente você necessitará. (Essas descrições podem não fazer sentido para
você até que se estudem os tópicos apropriados, nos quais a discussão será mais aprofundada. Por enquanto, usa-
remos apenas static_cast.)
A forma antiga de conversores de tipo é aproximadamente equivalente ao tipo static_cast, mas utiliza uma
notação diferente. Uma das duas notações utiliza um nome de tipo como se fosse um nome de função. Por exem-
plo, int(9.3) fornece o valor int 9; double(42) fornece o valor 42.0. Na segunda notação, equivalente, para a for-
ma antiga de conversor de tipo, escreveríamos (double)42 em vez de double(42). Qualquer das notações pode ser
usada com variáveis e expressões mais complicadas e não só com constantes.
Embora o C++ conserve esta forma antiga de conversor de tipo, nós o aconselhamos a utilizar as formas mais novas.
(Algum dia, a forma antiga desaparecerá, ainda que não haja, por enquanto, nenhum plano para sua eliminação.)
Como observamos antes, você sempre pode atribuir um valor de tipo inteiro para uma variável de tipo ponto
flutuante, como em
double d = 5;

Nesses casos, o C++ efetua uma conversão automática de tipos, convertendo 5 em 5.0 e colocando 5.0 na variável
d. Você não pode armazenar o 5 como o valor de d sem uma conversão de tipos, mas às vezes o C++ faz a conversão
de tipos para você. Uma conversão automática como essa geralmente é chamada de coerção de tipo.

■ OPERADORES DE INCREMENTO E DECREMENTO


O ++ no nome da linguagem C++ vem do operador de incremento, ++. O operador de incremento acrescenta 1
ao valor de uma variável. O operador de decremento subtrai 1 do valor de uma variável. Normalmente eles são
usados com variáveis de tipo int, mas podem ser usados com qualquer tipo numérico. Se n é uma variável de um
tipo numérico, então n++ aumenta o valor de n em 1 e n-- diminui o valor de n em 1. Assim, n++ e n-- (quando
seguidos por um ponto-e-vírgula) são comandos executáveis. Por exemplo, as linhas
Variáveis, Expressões e Declarações de Atribuição 17

int n = 1, m = 7;
n++;
cout << "O valor de n é alterado para " << n << "\n";
m--;
cout << "O valor de m é alterado para " << m << "\n";

produzem a seguinte saída:


O valor de n é alterado para 2
O valor de m é alterado para 6

Uma expressão como n++ fornece um valor e também altera o valor da variável n. Assim, n++ pode ser usada
em uma expressão aritmética como
2*(n++)

A expressão n++ primeiro fornece o valor da variável n, e depois o valor de n é aumentado em 1. Por exemplo,
considere o seguinte código:
int n = 2;
int valorProduzido = 2 * (n++);
cout << valorProduzido << "\n";
cout << n << "\n";

Esse código produzirá a saída:


4
3

Observe a expressão 2*(n++). Quando o C++ calcula esta expressão, utiliza o valor que aquele número possuía
antes de ser incrementado, não o valor que possui após ser incrementado. Assim, o valor produzido pela expressão
n++ é 2, mesmo que o operador de incremento troque o valor de n para 3. Isso pode parecer estranho, mas às ve-
zes é exatamente isso que você deseja. E, como verá a seguir, se você quiser uma expressão que se comporte de
maneira diferente, você pode ter.
A expressão n++ calcula o valor da variável n e depois o valor da variável n é incrementado em 1. Se você in-
verter a ordem e colocar o ++ na frente da variável, a ordem dessas duas ações será invertida. A expressão ++n pri-
meiro incrementa o valor da variável n e depois fornece o valor incrementado de n. Por exemplo, considere o
código seguinte:
int n = 2;
int valorProduzido = 2 * (++n);
cout << valorProduzido << "\n";
cout << n << "\n";

Esse código é o mesmo que o trecho anterior, a não ser pelo fato de o ++ estar antes da variável. Assim, esse
código produzirá a saída seguinte:
6
3

Observe que os dois operadores de incremento n++ e ++n exercem o mesmo efeito sobre a variável n: ambos
aumentam o valor de n em 1. Mas as duas expressões chegam a valores diferentes. Lembre-se, se o ++ estiver antes
da variável, o incremento é feito antes de o valor ser fornecido; se o ++ estiver depois da variável, o incremento
será feito depois que o valor é fornecido.
Tudo o que dissemos sobre o operador de incremento se aplica ao operador de decremento, a não ser pelo
fato de a variável ser diminuída em 1 em vez de aumentada. Por exemplo, considere o seguinte código:
int n = 8;
int valorProduzido = n--;
cout << valorProduzido << "\n";
cout << n << "\n";

Esse código produzirá a saída:


18 Fundamentos do C++

8
7

Por outro lado, o código


int n = 8;
int valorProduzido = --n;
cout << valorProduzido << "\n";
cout << n << "\n";
Produz a saída
7
7

n-- fornece o valor de n e depois decrementa n; por outro lado, --n primeiro decrementa n e depois fornece o va-
lor de n.
Não se podem aplicar os operadores de incremento e decremento a algo que não seja uma variável única. Ex-
pressões como (x + y)++, --(x + y), 5++, e assim por diante, são todas ilegais em C++.
Os operadores de incremento e decremento podem ser perigosos quando utilizados dentro de expressões mais
complicadas, como explicado na Armadilha.

- Armadilha ORDEM DE EXECUÇÃO


Para a maioria dos operadores, a ordem de execução de subexpressões não é garantida. Em particular,
normalmente não se pode assumir que a ordem de execução seja da esquerda para a direita. Por exemplo,
considere a seguinte expressão:
n + (++n)
Suponha que n tenha o valor 2 antes de a expressão ser executada. Então, se a primeira expressão é
executada primeiro, o resultado é 2 + 3. Se a segunda expressão é executada primeiro, o resultado é 3 +
3. Como o C++ não garante a ordem de execução, a expressão pode produzir como resultado 5 ou 6. A
moral da história é que você não deve programar de uma forma que dependa da ordem de execução, a não
ser para os operadores discutidos no próximo parágrafo.
Alguns operadores garantem que sua ordem de execução de subexpressões seja da esquerda para a direita.
Para os operadores &&(e), || (ou) e o operador vírgula (que será discutido no Capítulo 2), C++ garante que
a ordem de execução seja da esquerda para a direita. Felizmente, esses são os operadores para os quais é
mais provável que desejemos uma ordem de execução previsível. Por exemplo, considere
(n <= 2) && (++n > 2)
Suponha que n possua o valor 2 antes de a expressão ser executada. Nesse caso, você sabe que a
subexpressão (n <= 2) é calculada antes de o valor de n ser incrementado. Assim, você sabe que (n <=
2) será true e que o mesmo ocorrerá com a expressão inteira.
Não confunda a ordem das operações (por regras de precedência) com a ordem de execução. Por exemplo,
(n + 2) * (++n) + 5
sempre quer dizer
((n + 2) * (++n)) + 5
Entretanto, não é claro se o ++n é calculado antes ou depois de n + 2. Qualquer um deles poderia ser
calculado primeiro.
Agora você sabe por que dissemos que costuma ser uma má idéia usar operadores de incremento (++) ou
decremento (--) como subexpressões de expressões maiores.
Se isso estiver muito confuso, apenas siga a regra básica de não escrever código que dependa da ordem de
execução de subexpressões.

1.3 1 Entrada/Saída de Terminal


Lixo para dentro quer dizer lixo para fora.
Ditado do programador

A entrada simples de terminal é feita com os objetos cin, cout e cerr, todos definidos na biblioteca iostream. Para uti-
lizar essa biblioteca, seu programa deve conter as seguintes linhas junto ao início do arquivo contendo seu código:
Entrada/Saída de Terminal 19

#include <iostream>
using namespace std;

■ SAÍDA UTILIZANDO cout


Os valores das variáveis, assim como de strings de texto, podem ser apresentados como saída na tela por meio de
cout. Qualquer combinação de variáveis e strings pode ser apresentada. Por exemplo, considere as seguintes linhas
do programa no Painel 1.1:
cout << "Olá, leitor.\n"
<< "Bem-vindo ao C++.\n";

Este comando apresenta como saída duas strings, uma por linha. Utilizando cout, você pode encaminhar para
a saída qualquer número de itens, seja uma string, uma variável ou expressões mais complicadas. Simplesmente in-
sira um << antes de cada item a ser encaminhado.
Como outro exemplo, considere:
cout << numeroDeJogos << " jogos realizados.";

O comando diz ao computador para fornecer dois itens: o valor da variável numeroDeJogos e a string entre as-
pas " jogos realizados.".
Observe que você não precisa de uma cópia separada do objeto cout para cada item de saída. É só listar todos
os itens a serem encaminhados para a saída, com os símbolos << antecedendo cada um desses itens. O comando
único anterior cout é equivalente aos dois seguintes:
cout << numeroDeJogos;
cout << " jogos realizados.";

Você pode incluir expressões aritméticas em um comando cout, como mostra o exemplo a seguir, em que
preço e imposto são as variáveis:
cout << "O custo total é de R$" << (preço + imposto);

Os parênteses ao redor de expressões aritméticas, como preço + imposto, são exigidos por alguns compilado-
res, então é melhor incluí-los.
Os dois símbolos < devem ser digitados sem nenhum espaço entre eles. A notação de seta << muitas vezes é
chamada de operador de inserção. Todo comando cout termina com um ponto-e-vírgula.
Note os espaços dentro das aspas em nossos exemplos. O computador não insere nenhum espaço extra antes
ou depois de itens encaminhados para a saída por um comando cout, e é por essa razão que as strings entre aspas
nos exemplos muitas vezes começam ou terminam com um espaço em branco. Os brancos impedem que as diver-
sas strings e números se misturem. Se você só precisar de um espaço e não houver strings entre aspas no local em
que deseja inserir o espaço, utilize uma string que contenha apenas um espaço, como no exemplo a seguir:
cout << primeiroNumero << " " << segundoNumero;

■ NOVAS LINHAS NA SAÍDA


Como foi observado na subseção sobre seqüências de escape, \n diz ao computador para iniciar uma nova linha de
saída. A não ser que você diga ao computador para ir para a próxima linha, ele colocará toda a saída na mesma li-
nha. Dependendo de como sua tela é configurada, isso pode produzir qualquer coisa, desde quebras de linha arbi-
trárias até linhas que saem para fora da tela. Observe que o \n vem entre aspas. Em C++, ir para a próxima linha
é considerado um caractere especial, e o modo como se digita esse caractere especial dentro de uma string entre
aspas é \n, sem espaço entre os dois símbolos de \n. Embora esse caractere especial seja digitado como dois sím-
bolos, o C++ considera \n como um único caractere, chamado caractere de nova linha.
Se você quiser inserir uma linha em branco na saída, pode colocar o caractere de nova linha \n sozinho:
cout << "\n";
20 Fundamentos do C++

Outra forma de inserir uma linha em branco é usar endl, que significa essencialmente o mesmo que "\n".
Portanto, você também pode obter uma linha em branco na saída assim:
cout << endl;

Embora "\n" e endl queiram dizer a mesma coisa, seu uso é levemente diferente: \n deve sempre vir entre as-
pas, enquanto endl não deve ser colocado entre aspas.
Uma boa regra para decidir se você deve usar \n ou endl é a seguinte: se você pode incluir o \n ao final de
uma string mais longa, então use \n, como no seguinte exemplo:
cout << "O rendimento do combustível é "
<< mpg << " milhas por galão*\n";

Por outro lado, se o \n for ficar sozinho, como na string curta "\n", então é melhor utilizar endl:
cout << "Você digitou " << numero << endl;

INICIANDO NOVAS LINHAS NA SAÍDA


Para iniciar uma nova linha, você pode incluir \n em uma string entre aspas, como no seguinte exemplo:
cout << "Você acaba de ganhar\n"
<< "um dos seguintes prêmios: \n";
Lembre-se de que \n é digitado como dois símbolos sem nenhum espaço entre si.
Uma outra alternativa seria começar uma nova linha com endl. Uma forma equivalente de escrever o comando cout acima
seria:
cout << "Você acaba de ganhar" << endl
<< "um dos seguintes prêmios:" << endl;

Dica 1 TERMINE CADA PROGRAMA COM \n OU endl


É uma boa idéia encaminhar para a saída uma instrução de nova linha ao final de cada programação. Se o
último item a ser encaminhado for uma string, inclua um \n ao final da string; se não, inclua um endl
como a última ação a ser encaminhada para a saída. Isso atende a dois objetivos. Alguns compiladores não
apresentam a saída da última linha do programa se você não incluir uma instrução de nova linha ao final.
Em outros sistemas, o programa pode trabalhar bem sem essa instrução final de nova linha, mas o próximo
programa a ser executado terá sua primeira linha mesclada à última linha do programa anterior. Mesmo que
nenhum desses problemas ocorra em seu sistema, colocar uma instrução de nova linha ao final tornará o
programa mais portátil.

■ FORMATANDO NÚMEROS COM UM PONTO DECIMAL


Quando o computador apresenta como saída um valor do tipo double, o formato pode não ser do jeito que você
gostaria. Por exemplo, o seguinte comando simples cout pode produzir diversas saídas:
cout << "O preço é R$" << preco << endl;

Se preco tiver o valor 78.5, a saída poderá ser


O preço é R$ 78.500000

ou
O preço é R$ 78.5

ou então a saída poderá ser na seguinte notação (explicada na subseção intitulada Literais):
O preço é R$ 7.850000e01

É extremamente improvável que a saída seja a seguinte, mesmo que seja o formato que faz mais sentido:
O preço é R$ 78.50

* Um galão equivale a 3,7806 litros no Sistema Internacional de Unidades. (N. do R.T.)


Entrada/Saída de Terminal 21

Para garantir que a saída esteja na forma que você deseja, seu programa deve conter algumas instruções que
digam ao computador como apresentar os números.
Há uma "fórmula mágica" que você pode inserir no programa para fazer com que números que contêm um
ponto decimal, como números do tipo double, sejam apresentados na saída na notação cotidiana com o número
exato de dígitos após o ponto decimal que você especificar. Se você quer dois dígitos após o ponto decimal, utilize
a seguinte fórmula mágica:
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(2);

Se você inserir os três comandos acima em seu programa, quaisquer comandos cout que sigam esses comandos
apresentarão valores de qualquer tipo de ponto flutuante em notação comum, com exatamente dois dígitos após o
ponto decimal. Por exemplo, suponha que o seguinte comando cout apareça em algum lugar depois desta fórmula
mágica e suponha que o valor de preço é 78.5.
cout << "O preço é R$" << preco << endl;

A saída será apresentada assim:


O preço é R$ 78.50

Você pode utilizar qualquer outro número inteiro não-negativo em lugar de 2 para especificar um número di-
ferente de dígitos após o ponto decimal. Pode até utilizar uma variável de tipo int em lugar do 2.
Explicaremos esta fórmula mágica em detalhes no Capítulo 12. Por enquanto, pense nela como uma longa
instrução que diz ao computador como você deseja que sejam apresentados os números que contêm um ponto de-
cimal.
Se desejar alterar o número de dígitos após o ponto decimal de modo que valores diferentes em seu programa
sejam apresentados com diferentes números de dígitos, repita a fórmula mágica com algum outro número no lu-
gar de 2. Entretanto, quando você repete a fórmula mágica, só precisa repetir a última linha da fórmula. Se a fór-
mula mágica já apareceu uma vez em seu programa, a linha seguinte alterará o número de dígitos após o ponto
decimal para cinco para todos os valores subseqüentes de qualquer tipo de ponto flutuante apresentado na saída:
cout.precision(5);

SAÍDA DE VALORES DE TIPO double


Se você inserir a seguinte "fórmula mágica" em seu programa, todos os números de tipo double (ou qualquer outro tipo de
número de ponto flutuante) serão apresentados na saída em notação comum com dois dígitos após o ponto decimal:
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(2);
Você pode usar qualquer outro número inteiro não-negativo no lugar do 2 para especificar um número diferente de dígitos
após o ponto decimal. Pode até usar uma variável de tipo int no lugar do 2.

■ SAIDA COM cerr


O objeto cerr é utilizado da mesma forma que cout. O objeto cerr envia sua saída para o fluxo de saída padrão
de erro, que normalmente é a tela do terminal. Isso proporciona a você uma forma de distinguir dois tipos de saí-
da: cout para a saída regular e cerr para a mensagem de saída de erro. Se você não fizer nada de especial para al-
terar as coisas, tanto cout quanto cerr enviarão suas saídas para a tela do terminal, de modo que não há diferença
entre eles.
Em alguns sistemas é possível redirecionar a saída do programa para um arquivo. Esta é uma instrução do sis-
tema operacional, não do C++, mas pode ser útil. Em sistemas que permitem o redirecionamento de saída, cout e
cerr podem ser redirecionados para arquivos diferentes.
22 Fundamentos do C++

■ ENTRADA UTILIZANDO cin


Usa-se cin para a entrada mais ou menos da mesma forma que cout para a saída. A sintaxe é similar, a não ser
pelo fato de cin ser usado no lugar de cout e de as setas apontarem para a direção oposta. Por exemplo, no pro-
grama no Painel 1.1, a variável numeroDeLinguagens era preenchida pelo seguinte comando cin:
cin >> numeroDeLinguagens;

Pode-se listar mais de uma variável em um único comando cin, como ilustrado a seguir:
cout << "Digite o número de dragões\n"
<< "seguido do número de trolls.\n";
cin >> dragoes >> trolls;

Se você preferir, o comando cin acima pode ser escrito em duas linhas:
cin >> dragoes
>> trolls;

Observe que, como no comando cout, há apenas um ponto-e-vírgula para cada ocorrência de cin.
Quando um programa chega a um comando cin, espera que a entrada seja fornecida a partir do teclado e
atribui à primeira variável o primeiro valor digitado ao teclado, à segunda variável o segundo valor digitado, e as-
sim por diante. Entretanto, o programa não lê a entrada até que o usuário pressione a tecla Return. Isso permite
que o usuário possa utilizar a tecla de retrocesso e corrigir erros na digitação de uma linha de entrada.
Os números na entrada devem ser separados por um ou mais espaços ou por uma quebra de linha. Quando se
usam comandos cin, o computador ignorará qualquer número de brancos ou quebras de linha até encontrar o
próximo valor de entrada. Assim, não importa se os números de entrada são separados por um ou vários espaços,
ou mesmo uma quebra de linha.
Você pode ler números inteiros, de ponto flutuante ou caracteres por meio de cin. Mais adiante neste livro
discutiremos a leitura de outros tipos de dados utilizando cin.

COMANDOS cin

Um comando cin atribui a variáveis valores digitados no teclado.


SINTAXE
cin >> Variavel_1 >> Variavel_2 >>...;
EXEMPLOS
cin >> numero >> tamanho;
cin >> tempoRestante
>> pontosNecessarios;

Dica QUEBRAS DE LINHA EM E/S


É possível manter a saída e a entrada na mesma linha, e às vezes isso pode produzir uma interface mais
agradável para o usuário. Se você simplesmente omitir um \n ou endl ao final da última linha digitada ao
prompt, então a entrada do usuário aparecerá na mesma linha que o prompt. Por exemplo, suponha que
você use os seguintes comandos de prompt e entrada:
cout << "Digite o custo por pessoa: R$";
cin >> custoPorPessoa;
Quando o comando cout é executado, o que aparecerá na tela é:
Digite o custo por pessoa: R$
Quando o usuário digitar a entrada, esta aparecerá na mesma linha, assim:
Digite o custo por pessoa: R$ 1.25
Estilo de Programa 23

Exercícios de Autoteste
9. Forneça uma declaração de saída que produza a seguinte mensagem na tela:
A resposta à questão da
Vida, Universo e Sabe Lá o Que Mais é 42.
10. Forneça uma declaração de entrada que preencherá a variável oNumero (de tipo int) com um número
digitado ao teclado. Antes da declaração de entrada, coloque uma declaração de prompt pedindo ao usuário
para digitar um número inteiro.
11. Que declarações você incluiria em seu programa para assegurar que, quando um número de tipo double for
apresentado na saída, ele será apresentado em notação comum com três dígitos após o ponto decimal?
12. Escreva um programa completo em C++ que escreva a frase Olá mundo na tela. O programa não faz nada
além disso.
13. Forneça uma declaração de saída que produza a letra ’A’, seguida pelo caractere de nova linha, seguido pela
letra ’B’, seguido pelo caractere de tabulação, seguido pela letra ’C’.

1.4 1 Estilo de Programa


Em questões de muita importância, o estilo, não a sinceridade, é que é vital.
Oscar Wilde, A Importância de Ser Prudente

O estilo de programação em C++ é similar ao que é usado em outras linguagens. O objetivo é tornar o código
fácil de ler e de modificar. Vamos falar um pouco sobre o alinhamento no próximo capítulo. Já falamos a respeito
das constantes definidas. Em um programa, a maioria dos literais, senão todos, devem ser constantes definidas. A
escolha dos nomes de variáveis e um alinhamento cuidadoso eliminam a necessidade de muitos comentários, mas
quaisquer pontos que permanecerem obscuros merecem comentário.

■ COMENTÁRIOS
Existem duas formas de se inserir comentários em um programa em C++. Em C++, duas barras, //, são usadas
para indicar o início de um comentário. Todo o texto entre as // e o fim da linha é um comentário. O compila-
dor simplesmente ignora qualquer coisa que se siga às // em uma linha. Se você quiser um comentário que abran-
ja mais do que uma linha, coloque // em cada linha do comentário. Os símbolos // não possuem um espaço entre si.
Outro modo de se inserir comentários em um programa em C++ é usar o par de símbolos /* e */. O texto entre
esses símbolos é considerado um comentário e ignorado pelo compilador. Diferentemente dos comentários com //, que
requerem // adicionais em cada linha, os comentários entre /* e */ podem abranger várias linhas, como:
/* Este é um comentário que abrange
três linhas. Observe que não há nenhum símbolo
de comentário de nenhum tipo na segunda linha.*/
Comentários do tipo /* */ podem ser inseridos em qualquer lugar em um programa em que um espaço ou
quebra de linha seja permitido. Entretanto, eles não devem ser inseridos em qualquer lugar, a não ser que sejam
fáceis de ler e não perturbem a estrutura do programa. Normalmente, os comentários são colocados nos finais das
linhas ou em linhas separadas.
As opiniões diferem em relação a que tipo de comentário é melhor. As duas variedades (o tipo // ou /* */) podem
ser eficazes se usadas com cuidado. Um bom método é utilizar os comentários com // em códigos finais e reservar o es-
tilo /* */ para "comentar" o código (fazendo o compilador ignorar o trecho) quando se faz a depuração (debugging).
É difícil dizer quantos comentários deve conter um programa. A única resposta correta é "somente o necessá-
rio", o que, é claro, não significa muito para o programador iniciante. É necessário um pouco de experiência para se
saber quando é melhor incluir um comentário. Quando algo é importante e não óbvio, merece um comentário. Entre-
tanto, comentários demais são tão prejudiciais quanto de menos. Um programa que tenha comentários em cada linha
estará tão carregado de comentários que a estrutura do programa ficará oculta em um mar de observações óbvias. Co-
mentários como o seguinte não contribuem em nada para a compreensão e não deveriam aparecer em um programa:
distancia = velocidade * tempo; // Calcula a distância percorrida.
24 Fundamentos do C++

1.5 Bibliotecas e Namespaces


O C++ vem com diversas bibliotecas-padrão. Essas bibliotecas colocam suas definições em um namespace, que
é simplesmente um nome dado a uma coleção de definições. As técnicas para incluir bibliotecas e lidar com na-
mespaces serão discutidas em detalhe mais adiante neste livro. Esta seção tratará apenas dos detalhes necessários
para que você utilize as bibliotecas-padrão C++.

■ BIBLIOTECAS E INSTRUÇÕES DE include


O C++ contém diversas bibliotecas-padrão. Na verdade, é quase impossível escrever um programa em C++ sem
utilizar pelo menos uma dessas bibliotecas. O jeito normal de se tornar uma biblioteca disponível em um programa é
com uma instrução de include. Uma instrução de include para uma biblioteca-padrão possui a forma:
#include <Nome_Biblioteca>

Por exemplo, a biblioteca para E/S de terminal é iostream. Assim, a maioria de nossos programas de demons-
tração começará com
#include <iostream>

Os compiladores (pré-processadores) podem ser muito rigorosos com as questões de espaço em instruções de
include. Assim, é mais seguro digitar uma instrução de include sem nenhum espaço extra: nenhum espaço antes
de #, nenhum espaço depois de # e nenhum espaço dentro do <>.
Uma instrução de include é simplesmente uma instrução para incluir o texto que se encontra em um arquivo
no local especificado pela instrução de include. Um nome de biblioteca é apenas o nome de um arquivo que in-
clui todas as definições de itens na biblioteca. Mais tarde trataremos das instruções de include para outras coisas
que não as bibliotecas-padrão, mas por enquanto só precisaremos de instruções de include para bibliotecas-padrão
de C++. Uma lista de algumas bibliotecas-padrão de C++ é fornecida no Apêndice 4.
O C++ tem um pré-processador que lida com algumas manipulações textuais simples antes que o texto do seu
programa seja dado ao compilador. Algumas pessoas lhe dirão que as instruções de include não são processadas
pelo compilador, e sim por um pré-processador. Isso está correto, mas esta é uma diferença com a qual você não
deve se preocupar muito. Em quase todos os compiladores, o pré-processador é chamado automaticamente quan-
do você compila o seu programa.
Falando tecnicamente, apenas parte da definição da biblioteca é fornecida no arquivo de cabeçalho. Entretan-
to, neste estágio, esta não é uma distinção importante, já que utilizar a instrução de include com o arquivo de ca-
beçalho para uma biblioteca (em quase todos os sistemas) fará com que o C++ acrescente automaticamente o que
faltar da definição de biblioteca.

■ NAMESPACES
Um namespace é uma coleção de definições de nome. Um nome, como por exemplo um nome de função,
pode receber diferentes definições em dois namespaces. Um programa pode, então, utilizar um desses namespaces
em um lugar e o outro em outra localização. Vamos tratar dos namespaces em detalhe posteriormente neste livro.
Por enquanto, só precisamos falar a respeito do namespace std. Todas as bibliotecas-padrão que usaremos colocam
suas definições no namespace std (standard, ou padrão). Para usar qualquer dessas definições em seu programa,
você deve inserir as seguintes instruções de using:
using namespace std;

Assim, um programa simples que utilize E/S de terminal começará:


#include <iostream>
using namespace std;

Se você quiser que alguns nomes em um namespace, mas não todos, fiquem disponíveis em seu programa, há
uma forma da instrução de using que torna apenas um nome disponível. Por exemplo, se você quer tornar so-
mente o nome cin do namespace std disponível em seu programa, pode utilizar a seguinte instrução de using:
Bibliotecas e Namespaces 25

using std::cin;

Assim, se os únicos nomes do namespace std que seu programa utilizará forem cin, count e endl, você poderá
iniciar seu programa desta forma:
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
em vez de
#include <iostream>
using namespace std;

Arquivos de cabeçalho de C++ mais antigos não colocavam suas definições no namespace std; então, se você
observar para códigos mais antigos de C++, provavelmente perceberá que os nomes dos arquivos de cabeçalho são
escritos de modo ligeiramente diferente e que o código não contém nenhuma instrução de using. Isso é permitido
para garantir a compatibilidade com programas mais antigos. Entretanto, você deve usar os arquivos de cabeçalhos
das bibliotecas mais novas e a instrução de namespace std.

-Armadilha PROBLEMAS COM NOMES DE BIBLIOTECAS


A linguagem C++ está atualmente em transição. Um novo padrão surgiu, entre outras coisas, com novos
nomes para bibliotecas. Se você estiver usando um compilador que ainda não foi revisado para se adaptar
aos novos padrões, precisará adotar nomes diferentes de bibliotecas.
Se a seguinte instrução não funcionar
#include <iostream>
use

#include <iostream.h>

De forma similar, outros nomes de bibliotecas são diferentes em compiladores antigos. O Apêndice 5 fornece
a correspondência entre os nomes de bibliotecas antigos e novos. Este livro sempre utiliza os nomes dos
compiladores mais novos. Se um nome de biblioteca não trabalha com seu compilador, troque-o por um
nome de biblioteca correspondente mais antigo. Provavelmente ou todos os novos nomes de bibliotecas
funcionarão ou você precisará utilizar todos os nomes de bibliotecas antigos. É improvável que apenas
alguns dos nomes de bibliotecas tenham sido atualizados em seu sistema.
Se você utiliza os antigos nomes de bibliotecas (aqueles que terminam em .h), não precisa utilizar a
instrução
using namespace std;

Resumo do Capítulo

■ O C++ faz diferença entre maiúsculas e minúsculas. Por exemplo, count e COUNT são dois identificadores diferentes.
■ Utilize nomes significativos para as variáveis.
■ As variáveis devem ser declaradas antes de ser usadas. Desde que essa regra seja obedecida, uma declaração
de variável pode vir em qualquer lugar.
■ Assegure-se de que as variáveis sejam inicializadas antes que o programa tente utilizar seu valor. Isso pode ser
feito quando a variável é declarada com uma declaração de atribuição antes de ser utilizada pela primeira vez.
■ Você pode atribuir um valor de um tipo inteiro, como int, a uma variável de tipo de ponto flutuante,
como double, mas o contrário não é verdadeiro.
■ Quase todas as constantes numéricas em um programa devem receber nomes com significado que possam
ser utilizados em lugar dos números. Isso pode ser feito utilizando-se o modificador const em uma declara-
ção de variável.
■ Use parênteses em expressões aritméticas para tornar clara a ordem das operações.
■ O objeto cout é utilizado para a saída de terminal.
26 Fundamentos do C++

■ Um \n em uma string entre aspas ou um endl enviado para a saída do terminal inicia uma nova linha de saída.
■ O objeto cerr é utilizado para mensagens de erro. Em um ambiente típico, cerr se comporta da mesma
forma que cout.
■ O objeto cin é utilizado para a entrada de terminal.
■ Para usar cin, cout ou cerr, você deve colocar as seguintes instruções junto ao começo do arquivo com o
seu programa:
#include <iostream>
using namespace std;

■ Há duas formas de comentários em C++. Tudo o que se seguir a // na mesma linha é um comentário, e
tudo o que estiver entre /* */ é um comentário.
■ Não exagere nos comentários.

RESPOSTAS DOS EXERCÍCIOS DE AUTOTESTE


1. int pes = 0, polegadas = 0;
int pes(0), polegadas(0);

2. int count = 0;
double distancia = 1.5;

int count(0);
double distancia(1.5);

3. A saída real de um programa como esse depende do sistema e do histórico de uso do sistema.
#include <iostream>
using namespace std;
int main( )
{
int primeiro, segundo, terceiro, quarto, quinto;
cout<< primeiro << " " << segundo << " " << terceiro
<< " " << quarto << " " << quinto << "\n";
return 0;
}

4. 3*x
3*x + y
(x + y)/7 Observe que x + y/7 não é correto.
(3*x + y)/(z + 2)
5. bcbc
6. (1/3) * 3 é igual a 0
Como 1 e 3 são do tipo int, o operador / efetua divisão de inteiros, que descarta o resto, assim o valor de
1/3 é 0, não 0.3333… Isso faz com que o valor da expressão toda, 0 * 3, seja igual, é claro, a 0.

7. #include <iostream>
using namespace std;
int main( )
{
int numero1, numero2;
cout << "Digite dois números inteiros: ";
cin >> numero1 >> numero2;
cout << numero1 << " dividido por " << numero2
<< " é igual a " << (numero1/numero2) << "\n"
<< "com resto " << (numero1%numero2)
Projetos de Programação 27

<< "\n";
return 0;
}
8. a. 52.0
b. 9/5 possui valor int 1. Como tanto o numerador quanto o denominador são int, é feita a divisão de
inteiros; a parte fracional é descartada. O programador provavelmente queria a divisão de ponto flutuante,
que não descarta a parte após o ponto decimal.
c. f = (9.0/5) * c + 32.0;
ou
f = 1.8* c + 32.0;
9. cout << "A resposta para a pergunta da\n"
<< "Vida, Universo e Sabe Lá o que Mais é 42.\n";

10. cout << "Digite um número inteiro e aperte Return: ";


cin >> oNumero;

11. cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(3);

12. #include <iostream>


using namespace std;

int main( )
{
cout << "Ola mundo\n";
return 0;
}

13. cout << ’A’ << endl << ’B’ << ’\t’ << ’C’;
Outras respostas também são corretas. Por exemplo, as letras poderiam estar entre aspas duplas em vez de
aspas simples. Outra resposta possível é a seguinte:
cout << "A\nB\tC";

PROJETOS DE PROGRAMAÇÃO
1. Uma tonelada métrica, ou simplesmente tonelada, equivale a 35.273,92 onças. Escreva um programa que
leia o peso de um pacote de cereal matinal em onças e apresente na saída o peso em toneladas métricas,
assim como o número de caixas necessárias para fornecer uma tonelada métrica de cereal.
2. Um laboratório de pesquisas governamental conclui que um adoçante artificial comumente usado em re-
frigerantes dietéticos causa a morte de ratos de laboratório. Um amigo seu está desesperado para perder
peso e não consegue deixar de tomar refrigerantes. Seu amigo quer saber quanto refrigerante dietético é
possível tomar sem morrer. Escreva um programa para dar essa resposta. Os dados de entrada serão a
quantidade de adoçante artificial necessária para matar um rato, o peso do rato e o peso da pessoa em
dieta. Para garantir a segurança do seu amigo, faça com que o programa requisite o peso com o qual ele
deseja ficar, em vez do peso atual. Assuma que o refrigerante dietético contém um décimo de 1% de ado-
çante artificial. Utilize uma declaração de variável com o modificador const para dar um nome a esta fra-
ção. Você pode querer expressar a porcentagem como o valor double 0.001.
3. Os trabalhadores de uma empresa em particular receberam um aumento de 7,6% retroativo a seis meses. Es-
creva um programa que tome o salário anual anterior de um empregado como entrada e apresente como saída
a quantidade de pagamento retroativo devido ao empregado, o novo salário anual e o novo salário mensal.
Utilize uma declaração de variável com o modificador const para expressar o aumento de pagamento.
4. Nem sempre é fácil negociar um empréstimo ao consumidor. Uma forma de empréstimo é com abati-
mento nas prestações, que funciona da seguinte forma: suponha que um empréstimo tenha um valor no-
minal de R$ 1.000, taxa de juros de 15% e duração de 18 meses. O juro é calculado multiplicando-se o
valor nominal de R$ 1.000 por 0,15, o que dá R$ 150. Essa cifra é então multiplicada pelo período do
28 Fundamentos do C++

empréstimo de 1,5 anos, resultando em R$ 225 como o total de juros devidos. Essa quantia é imediata-
mente deduzida do valor nominal, deixando o consumidor apenas com R$ 775. O reembolso é feito em
prestações iguais com base no valor nominal. Assim, o pagamento mensal do empréstimo será R$ 1.000
dividido por 18, que dá R$ 55,56. Escreva um programa que necessitará de três dados de entrada: a
quantia que o consumidor precisa receber, a taxa de juros e a duração do empréstimo em meses. O pro-
grama deve, então, calcular o valor nominal requerido para que o consumidor receba a quantidade neces-
sária. Deve também calcular o pagamento mensal.
5. Escreva um programa que determine se uma sala de conferências está violando as normas legais de incên-
dio relativas à sua capacidade máxima. O programa lerá a máxima capacidade da sala e o número de pes-
soas que comparecerão à conferência. Se o número de pessoas for menor ou igual à capacidade máxima
da sala, o programa anuncia que a conferência está de acordo com as normas legais e diz quantas outras
pessoas poderão participar conforme essas normas. Se o número de pessoas exceder a capacidade máxima
da sala, o programa anuncia que a conferência não poderá ocorrer, devido às normas de incêndio, e diz
quantas pessoas devem ser excluídas a fim de obedecer às normas.
6. Um empregado recebe R$ 16,78 por horas regulares trabalhadas em uma semana. Se esse empregado fizer
hora extra, deve receber essa mesma taxa multiplicada por 1,5. Do pagamento bruto do empregado, 6%
são retidos pela Previdência Social, 14%, pelo Imposto de Renda Federal, 5%, por impostos estaduais, e R$
10 por semana, para o Sindicato. Se o empregado tiver três ou mais dependentes, um adicional de R$ 35 é re-
tido para cobrir o custo extra do seguro de saúde. Escreva um programa que leia o número de horas trabalha-
das em uma semana e o número de dependentes como entrada e apresente como saída o pagamento bruto do
empregado, o valor de cada imposto retido e o salário líquido por semana.
CAPÍTULO

Fluxo de Controle

CAPÍTULO 2Fluxo de Controle


Fluxo de Controle
"Você poderia me dizer, por favor, que caminho devo seguir?" "Isso depende muito de
para onde você quer ir", disse o Gato.
Lewis Carroll, Alice no País das Maravilhas

INTRODUÇÃO
Como a maioria das linguagens de programação, C++ lida com o fluxo de controle por
meio de comandos de seleção e loopings. Os comandos de seleção e loopings de C++ são
semelhantes aos de outras linguagens. São os mesmos da linguagem C e bastante similares
aos da linguagem de programação Java. O tratamento de exceções também é um modo
de se lidar com fluxo de controle. O tratamento de exceções será abordado no Capítulo 18.

2.1 Expressões Booleanas


Aquele que quiser distinguir o verdadeiro do falso precisa ter uma idéia adequada do
que é verdadeiro e falso.
Benedictus de Spinoza, Ética

A maioria dos comandos de seleção é controlada por expressões booleanas. Uma ex-
pressão booleana é qualquer expressão que seja ou verdadeira ou falsa. A forma mais sim-
ples de expressão booleana consiste em duas expressões, como números ou variáveis, que
são comparadas com um dos operadores de comparação mostrados no Painel 2.1. Obser-
ve que alguns dos operadores são digitados com dois símbolos, por exemplo, ==, !=, <=,
>=. Tenha cuidado para utilizar um sinal duplo de igual, ==, para simbolizar o sinal de
igual e para utilizar os dois símbolos != para não-igual. Esses dois operadores-símbolo não
devem conter espaços entre os dois símbolos.

■ CONSTRUINDO EXPRESSÕES BOOLEANAS


Você pode combinar duas comparações utilizando o operador "e", que se escreve &&
em C++. Por exemplo, a seguinte expressão booleana é verdadeira desde que x seja maior
que 2 e x seja menor que 7:
(2 < x) && (x < 7)

Quando duas comparações são ligadas por meio de um &&, toda a expressão é verda-
deira, desde que ambas as comparações sejam verdadeiras; caso contrário, toda a expressão
é falsa.
30 Fluxo de Controle

OPERADOR "E", &&


Você pode formular uma expressão booleana mais elaborada combinando duas expressões booleanas mais simples por meio do
o operador "e", &&.
SINTAXE PARA UMA EXPRESSÃO BOOLEANA UTILIZANDO &&
(Exp_Booleana_1) && (Exp_Booleana_2)
EXEMPLO (DENTRO DE UM COMANDO if-else)
if ( (resultado > 0) && (resultado < 10)
cout << "o resultado está entre 0 e 10.\n";)
else
cout << "o resultado não está entre 0 e 10.\n";
Se o valor de resultado é maior que 0 e também é menor que 10, então o primeiro comando cout será executado; caso contrá-
rio, o segundo comando cout será executado. (O comando if-else será abordado em uma próxima seção deste capítulo, mas o
significado desse exemplo simples deve ser intuitivamente claro.)

Você pode combinar também duas comparações utilizando o operador "ou", que se escreve || em C++. Por
exemplo, a linha seguinte é verdadeira desde que y seja menor que 0 ou y seja maior que 12:
(y < 0) || (y > 12)

Quando duas comparações são ligadas por meio de um ||, toda a expressão é verdadeira desde que uma ou
ambas as comparações sejam verdadeiras; caso contrário, toda a expressão é falsa.
Você pode negar qualquer expressão booleana utilizando o operador !. Se quiser negar uma expressão boolea-
na, coloque a expressão entre parênteses e o operador ! na frente dele. Por exemplo, !(x < y) significa "x não é
menor que y". O operador ! pode, muitas vezes, ser evitado. Por exemplo, !(x < y) é equivalente a x >= y. Em
alguns casos você pode omitir com segurança os parênteses, mas estes nunca causam nenhum dano. Os detalhes
exatos da omissão de parênteses serão dados na subseção intitulada Regras de Precedência.

STRINGS DE DESIGUALDADES
Não utilize uma string de desigualdades como x < z < y. Se você fizer isso, o programa provavelmente
compilará e será executado, mas produzirá uma saída incorreta. Em vez disso, você deve usar duas desigual-
dades ligadas por um &&, da seguinte forma:
(x < z) && (z < y)

Painel 2.1 Operadores de comparação


SÍMBOLO PORTUGUÊS NOTAÇÃO C++ SIMPLES EQUIVALENTE
MATEMÁTICO C++ MATEMÁTICO

= Igual a == x + 7 == 2*y x + 7 = 2y

≠ diferente de != resp != ’n’ resp ≠ ’n’

< Menor que < contagem < m + 3 contagem < m + 3

≤ Menor ou igual a <= tempo <= limite tempo ≤ limite

> Maior que > tempo > limite tempo > limite

≥ Maior ou igual a >= idade >= 21 idade ≥ 21

OPERADOR "OU", ||
Pode-se formular uma expressão booleana mais elaborada combinando duas expressões booleanas mais simples utilizando o
operador "ou", ||.
SINTAXE PARA UMA EXPRESSÃO BOOLEANA UTILIZANDO ||
(Exp_Booleana_1) || (Exp_Booleana_2)
Expressões Booleanas 31

(continuação)
EXEMPLO (DENTRO DE UM COMANDO if-else)
if ( (x == 1) || (x == y))
cout << "x é 1 ou x é igual a y.\n";
else
cout << "x não é 1 nem é igual a y.\n";
Se o valor de x é igual a 1 ou o valor de x é igual ao valor de y (ou ambos), então o primeiro comando cout será executado;
caso contrário, o segundo comando cout será executado. (O comando if-else será abordado em uma próxima seção deste ca-
pítulo, mas o significado desse exemplo simples deve ser intuitivamente claro.)

■ AVALIANDO EXPRESSÕES BOOLEANAS


Como você verá nas próximas duas seções deste capítulo, as expressões booleanas são usadas para controlar os
comandos de seleção e loopings. Entretanto, uma expressão booleana possui uma identidade independente do co-
mando de seleção ou looping na qual você possa utilizá-la. Uma variável de tipo bool pode armazenar qualquer
um dos valores, true ou false. Assim, você pode fixar uma variável de tipo bool como igual a uma expressão
booleana. Por exemplo:
bool resultado = (x < z) && (z < y);

Uma expressão booleana pode ser executada da mesma forma que uma expressão aritmética. A única diferença
é que uma expressão aritmética utiliza operações como +, * e / e produz um número como resultado final, en-
quanto uma expressão booleana utiliza operações relacionais, como == e < e operações booleanas, como &&, ||, !
e produz um dos dois valores true ou false como resultado final. Observe que =, !=, <, <=, etc. atuam em pares
de qualquer tipo integrado para produzir um valor booleano true ou false.
Primeiro vamos rever o cálculo de uma expressão aritmética. A mesma técnica funcionará para executar expres-
sões booleanas. Considere a seguinte expressão aritmética:
(x + 1) * (x + 3)

Presuma que a variável x tenha o valor 2. Para calcular esta expressão aritmética, você calcula as duas somas e
obtém os números 3 e 5, e então combina esses dois números utilizando o operador * e obtém 15 como valor fi-
nal. Observe que, ao efetuar esse cálculo, você não multiplica as expressões (x + 1) e (x + 3). Em vez disso,
você multiplica os valores dessas expressões. Você utiliza 3, não (x + 1). Você utiliza 5, não (x + 3).
O computador calcula expressões booleanas da mesma forma. As subexpressões são calculadas para obter valo-
res, cada um dos quais é true ou false. Esses valores individuais de true ou false são então combinados de
acordo com as regras nas tabelas exibidas no Painel 2.2. Por exemplo, considere a expressão booleana:
!((y < 3) || (y > 7))

Painel 2.2 Tabelas da verdade


E

Exp_I Exp_2 Exp_I && Exp_2


true true true
true false false NÃO
false true false
Exp ! (Exp)
false false false
true false
OU
false true
Exp_I Exp_2 Exp_I || Exp_2
true true true
true false true
false true true
false false false
32 Fluxo de Controle

que pode ser a expressão controladora para um comando if-else. Suponha que o valor de y é 8. Nesse caso (y <
3) é false e (y > 7) é true. Assim, a expressão booleana apresentada é equivalente a
!(false || true)

Consultando as tabelas para || (OU), o computador calcula essa expressão dentro dos parênteses como true.
Assim, o computador vê toda a expressão como equivalente a
!(true)

Consultando as tabelas novamente, o computador vê que !(true) é false, e conclui que false é o valor da
expressão booleana original.

OS VALORES BOOLEANOS (bool) SÃO true E false


true e false são constantes predefinidas de tipo bool. (Devem ser escritas em letras minúsculas.) Em C++, uma expressão boo-
leana produz o valor bool true quando é satisfeita e o valor bool false quando não é satisfeita.

■ REGRAS DE PRECEDÊNCIA
As expressões booleanas (e aritméticas) não precisam ficar todas entre parênteses. Se você omitir os parênteses,
a precedência-padrão é a seguinte: ! é executada primeiro, depois as operações relacionais como <, depois && e
então ||. Entretanto, é uma boa prática incluir parênteses para tornar a expressão mais fácil de ser entendida. Um
lugar em que os parênteses podem seguramente ser omitidos é uma string simples de && ou || (mas não uma
mistura dos dois). A seguinte expressão é aceitável em C++, tanto em relação ao compilador quanto à legibilidade:
(temperatura > 90) && (umidade > 0.90) && (piscina == ABERTA)

Como os operadores relacionais > e == são executados antes da operação &&, você poderia omitir os parênteses na
expressão acima e ela teria o mesmo significado, mas incluir alguns parênteses torna a expressão mais legível.
Quando os parênteses são omitidos em uma expressão, o compilador agrupa os itens de acordo com regras co-
nhecidas como regras de precedência. A maioria das regras de precedência em C++ são fornecidas no Painel 2.3.
A tabela apresenta um número de operadores que serão discutidos mais adiante neste livro, mas que foram in-
cluídos para deixá-la mais completa e para aqueles que já são capazes de compreendê-los.

Painel 2.3 Precedência de operadores (parte 1 de 2)


Maior precedência
:: Operador de resolução de escopo
(efetuado antes)
. Operador ponto
→ Seleção de membros
[] Indexação vetorial
() Chamada de função
++ Operador de incremento em posição de sufixo (colocado após a variável)
–– . Operador de decremento em posição de sufixo (colocado após a variável)
++ Operador de incremento em posição de prefixo (colocado antes da variável)
–– . Operador de decremento em posição de prefixo (colocado antes da variável)
! Não
– Sinal negativo
+ Sinal positivo
* Desreferenciação
& Endereço de
new Cria (aloca memória)
delete Destrói (libera)
delete [ ] Destrói vetor (libera)
sizeof Tamanho do objeto
() Casting de tipo
* Multiplicação
/ Divisão Menor precedência
% Resto (módulo) (efetuado depois)
Expressões Booleanas 33

Painel 2.3 Precedência de operadores (parte 2 de 2)


Todos os operadores da parte 2 têm menor precedência que os da parte 1.
+ Adição
– Subtração
<< Operador de inserção (saída de console)
>> Operador de extração (entrada de console)
< Menor que
> Maior que
<= Maior ou igual a
>= Menor ou igual a
== Igual
!= Não-igual
&& E
|| Ou
= Atribuição
+= Adição e atribuição
–= Subtração e atribuição
*= Multiplicação e atribuição
/= Divisão e atribuição
%= Módulo e atribuição
?: Operador condicional
throw Lança uma exceção
Menor precedência
, Operador vírgula (efetuado depois)

Se uma operação é executada antes de outra, dizemos que a operação executada primeiro possui maior prece-
dência. Todos os operadores em uma dada caixa no Painel 2.3 possuem a mesma precedência. Os operadores nas
caixas mais elevadas apresentam maior precedência que os operadores de caixas mais baixas.
Quando os operadores têm a mesma precedência e a ordem não é determinada por parênteses, as operações
unárias são realizadas da direita para a esquerda. As operações de atribuição também são feitas da direita para a es-
querda. Por exemplo, x = y = z significa x = (y = z). Outras operações binárias que possuem as mesmas
precedências são executadas da esquerda para a direita. Por exemplo, x+y+z significa (x+y)+z.
Observe que as regras de precedência incluem tanto operadores aritméticos, como + e *, quanto operadores
booleanos, como && e ||. Isso porque muitas expressões combinam operações aritméticas e booleanas, como no
seguinte exemplo simples:
(x + 1) > 2 || (x + 1) < -3

Se você verificar as regras de precedência fornecidas no Painel 2.2, verá que essa expressão equivale a:
((x + 1) > 2) || ((x + 1) < -3)

porque > e < possuem maior precedência que ||. Na realidade, você poderia omitir todos os parênteses na expres-
são anterior e ela teria o mesmo significado, embora ficasse mais difícil de ler. Ainda que não aconselhemos a
omissão de todos os parênteses, pode ser instrutivo ver como tal expressão é interpretada utilizando-se as regras de
precedência. Aqui está a expressão sem parênteses:
x + 1 > 2 || x + 1 < -3

As regras de precedência dizem para aplicar primeiro o unário -, depois o unário +, então o > e o <, e final-
mente o ||, que é exatamente o que a versão cheia de parênteses indicaria.
A descrição acima de como uma expressão booleana é executada é basicamente correta, mas em C++ o com-
putador segue um atalho ocasional quando executa uma expressão booleana. Observe que em muitos casos você
precisa calcular apenas a primeira de duas subexpressões em uma expressão booleana. Por exemplo, considere a se-
guinte linha:
(x >= 0) && (y > 1)
34 Fluxo de Controle

Se x é negativo, então (x >= 0) é false. Como você pode ver nas tabelas do Painel 2.1, quando uma subex-
pressão em uma expressão && é false, toda a expressão é false, não importa se a outra expressão é true ou
false. Assim, se sabemos que a primeira expressão é false, não há necessidade de avaliar a segunda expressão.
Algo similar acontece com expressões com ||. Se a primeira de duas expressões unidas pelo operador || é true, en-
tão se sabe que toda a expressão é true, sem importar se a segunda expressão é true ou false. A linguagem C++
utiliza esse fato para, às vezes, poupar a si própria o trabalho de avaliar a segunda subexpressão em uma expressão
lógica ligada por um && ou ||. O C++ avalia primeiro a expressão mais à esquerda das duas expressões unidas
por && ou ||. Se esta fornece informação suficiente para determinar o valor final da expressão (independentemen-
te do valor da segunda expressão), o C++ não se preocupa em avaliar a segunda expressão. Esse método de avalia-
ção é chamado avaliação curto-circuito.
Algumas outras linguagens, que não C++, utilizam a avaliação completa. Na avaliação completa, quando duas
expressões estão unidas por && ou ||, ambas as subexpressões são sempre avaliadas e depois se utilizam as tabelas
de verdade para obter o valor da expressão final.
Tanto a avaliação curto-circuito quanto a avaliação completa fornecem a mesma resposta, então por que você
deveria se preocupar se o C++ utiliza a avaliação curto-circuito? Na maioria das vezes, não precisa se preocupar.
Desde que ambas as subexpressões unidas por && ou || possuam um valor, os dois métodos produzem o mesmo
resultado. Entretanto, se a segunda subexpressão é indefinida, talvez você fique feliz em saber que C++ utiliza a
avaliação curto-circuito. Vamos ver um exemplo que ilustra esta questão. Considere as seguintes linhas:
if ( (criancas != 0) && (( pedacos/criancas) >= 2) )
cout << "Cada criança pode ficar com dois pedacos!";

Se o valor de criancas não é zero, esse comando não envolve problemas. Entretanto, suponha que o valor de
criancas seja zero; considere como a avaliação curto-circuito lida com esse caso. A expressão (criancas != 0)
dá como resultado false, então não haveria necessidade de avaliar a segunda expressão. Utilizando a avaliação cur-
to-circuito, C++ diz que toda a expressão é false, sem se preocupar em avaliar a segunda expressão. Isso impede
um erro de execução, já que a avaliação da segunda expressão envolveria uma divisão por zero.

VALORES INTEIROS PODEM SER USADOS COMO VALORES BOOLEANOS


O C++ às vezes utiliza inteiros como se fossem valores booleanos e valores bool como se fossem inteiros.
Em particular, o C++ converte o inteiro 1 em true e o inteiro 0 em false, e vice-versa. A situação é ainda
mais complicada do que simplesmente utilizar 1 para true e 0 para false. O compilador tratará qualquer
número não-zero como se fosse o valor true e 0 como se fosse o valor false. Desde que você não cometa
erros ao escrever expressões booleanas, essa conversão não causa problemas. Entretanto, quando você está
fazendo uma depuração (debug), pode ser útil saber que o compilador aceita combinar inteiros utilizando
operadores booleanos &&, || e !.
Por exemplo, suponha que você queira uma expressão booleana que seja true desde que o tempo não te-
nha ainda se esgotado (em algum jogo ou processo). Você poderia utilizar:
!tempo > limite
Isso parece perfeito se você ler em voz alta: "não-tempo maior que limite". A expressão booleana está erra-
da, todavia, e o compilador não emitirá uma mensagem de erro. O compilador aplicará as regras de prece-
dência do Painel 2.3 e interpretará sua expressão booleana como o seguinte:
(!tempo) > limite
Isso parece absurdo, e intuitivamente é absurdo. Se o valor do tempo é, por exemplo, 36, qual poderia ser
o significado de (!tempo)? Afinal, isso é o equivalente a "não 36". Mas em C++ qualquer inteiro não-zero
se converte em true e 0 é convertido em false. Assim, !36 é interpretado como "não true" e produz como re-
sultado false, que é convertido novamente em 0, porque estamos fazendo a comparação com um int.
O que queremos como valor dessa expressão booleana e o que o C++ nos fornece não é o mesmo. Se
tempo possui um valor de 36 e limite, um valor de 60, você quer que a expressão booleana acima produza
como resultado true (porque é não true que tempo > limite). Infelizmente, a expressão booleana, em vez
disso, é executada da seguinte forma: (!tempo) é false e convertido em 0 e, assim, toda a expressão boo-
leana é equivalente a:
0 > limite
O que, por sua vez, é equivalente a 0 > 60, porque 60 é o valor de limite e isso é avaliado como false.
Assim, a expressão lógica acima é avaliada como false, quando você desejaria que fosse true.
Há duas formas de corrigir esse problema. Uma forma é usar o operador ! corretamente. Quando usar o opera-
dor !, inclua parênteses em torno do argumento. A forma correta de se escrever a expressão booleana acima é
Estruturas de Controle 35

(continuação)
!(tempo > limite)
Outra forma de corrigir o problema é evitar completamente a utilização do operador !. Por exemplo, a linha
seguinte também é correta e mais fácil de ler:
if (tempo <= limite)
Quase sempre você pode evitar utilizar o operador !, e alguns programadores aconselham a evitá-lo sempre
que possível.

1 Exercícios de Autoteste 1

1. Determine o valor, true ou false, de cada uma das seguintes expressões booleanas, presumindo que o
valor da variável contagem seja 0 e o valor da variável limite seja 10. Forneça a resposta como um dos va-
lores true ou false.
a. (contagem == 0) && (limite < 20)
b. contagem == 0 && limite < 20
c. (limite > 20) || (contagem < 5)
d. !(contagem == 12)
e. (contagem == 1) && (x < y)
f. (contagem < 10) || (x < y)
g. ! ( ((contagem < 10) || (x < y)) && (contagem >= 0) )
h. ((limite/contagem) > 7) || (limite < 120)
i. (limite < 20) || ((limite/contagem) > 7)
j. ((limite/contagem) > 7) && (limite < 0)
k. (limite < 0) && ((limite/contagem) > 7)
l. (5 && 7) + (!6)
2. Às vezes você vê intervalos numéricos fornecidos como
2 < x < 3
Em C++, esse intervalo não possui o significado que você esperaria. Explique e forneça a expressão boo-
leana correta em C++ que especifica que x está entre 2 e 3.
3. Considere a expressão quadrática
x2 - x - 2
Descrever em que intervalo esta quadrática é positiva (ou seja, maior que 0) envolve a descrição de um
conjunto de números que são ou menores que a menor raiz (que é -1) ou maiores que a maior raiz (que
é 2). Escreva uma expressão booleana em C++ que seja true quando essa fórmula tiver valores positi-
vos.
4. Considere a expressão quadrática
x2 - 4x + 3
Descrever onde esta quadrática é negativa envolve a descrição de um conjunto de números que são si-
multaneamente maiores que a menor raiz (1) e menores que a maior raiz (3). Escreva uma expressão
booleana em C++ que seja true quando o valor dessa quadrática for negativo.

2.2 1 Estruturas de Controle


Quando você chegar a uma bifurcação na estrada, siga por ela.
Atribuído ao Iogue Berra

■ COMANDOS if-else
Um comando if-else escolhe entre dois comandos alternativos, com base no valor de uma expressão boolea-
na. Por exemplo, suponha que você queira escrever um programa para calcular o salário semanal de um emprega-
do que ganha por hora de trabalho. Presuma que a empresa pague uma hora extra de 1,5 vezes a taxa regular
após as primeiras 40 horas trabalhadas. Quando o empregado trabalha 40 horas ou mais, o salário é igual a
taxa*40 + 1.5*taxa*(horas - 40)
36 Fluxo de Controle

Entretanto, se o empregado trabalha menos de 40 horas, a fórmula de pagamento correta é simplesmente


taxa*horas

O seguinte comando if-else calcula o pagamento correto para um empregado quer este trabalhe menos de
40 horas, quer trabalhe 40 horas ou mais,
if (horas > 40)
pagamentoBruto = taxa*40 + 1.5*taxa*(horas - 40);
else
pagamentoBruto = taxa*horas;

A sintaxe para um comando if-else é dada na tabela a seguir. Se a expressão booleana entre parênteses (de-
pois do if) resultar em true, então o comando antes de else é executado. Se a expressão booleana resultar em
false, o comando depois de else é executado.

COMANDO if-else
O comando if-else escolhe entre duas ações alternativas, com base no valor de uma expressão booleana. A sintaxe é mostrada
abaixo. Observe que a expressão booleana deve estar entre parênteses.
SINTAXE: UM COMANDO ÚNICO PARA CADA ALTERNATIVA
if (Expressao_Booleana)
Sim_Comando
else
Nao_Comando
Se a Expressao_Booleana é true, então Sim_Comando é executada. Se a Expressao_Booleana é false, então Nao_Comando é exe-
cutada.
SINTAXE: UMA SEQÜÊNCIA DE COMANDOS
if (Expressao_Booleana)
{
Sim_Comando_1
Sim_Comando_2
...
Sim_Comando_Final
}
else
{
Nao_Comando_1
Nao_Comando_2
...
Nao_Comando_Final
}
EXEMPLO
if (meusPontos > seusPontos)
{
cout << "Eu ganhei!\n";
aposta = aposta + 100;
}
else
{
cout << "Queria que esses pontos fossem de golfe.\n";
aposta = 0;
}

Observe que um comando if-else possui comandos menores inseridos em seu interior. A maioria das formas
de comandos em C++ permite que se façam comandos maiores a partir de comandos menores combinando os co-
mandos menores de determinado modo.
Lembre-se de que, quando você utiliza uma expressão booleana em um comando if-else, a expressão boolea-
na deve estar entre parênteses.
Estruturas de Controle 37

■ COMANDOS COMPOSTOS
Muitas vezes você vai querer que as ramificações de um comando if-else executem mais do que um coman-
do cada uma. Para conseguir isso, encerre os comandos de cada ramificação entre chaves, { e }, como indicado no
segundo modelo de sintaxe na caixa intitulada Comando if-else. Uma lista de comandos entre chaves é chama-
da de comando composto. Um comando composto é tratado como um comando único em C++ e pode ser usa-
do em qualquer lugar em que um comando único possa ser usado. (Assim, o segundo modelo de sintaxe na caixa
intitulada Comando if-else é, na verdade, apenas um caso especial do primeiro.)
Existem duas formas comumente usadas de alinhar e colocar chaves nos comandos if-else, que são ilustradas
abaixo:
if (meusPontos > seusPontos)
{
cout << "Eu ganhei!\n";
aposta = aposta + 100;
}
else
{
cout << "Queria que esses pontos fossem de golfe.\n";
aposta = 0;
}

e
if (meusPontos > seusPontos){
cout << "Eu ganhei!\n";
aposta = aposta + 100;
}else{
cout << "Queria que esses pontos fossem de golfe.\n";
aposta = 0;
}

A única diferença é na colocação das chaves. Achamos a primeira forma mais fácil de ler e, portanto, é a nossa
preferida. A segunda forma economiza linhas e alguns programadores a preferem, da forma como está escrita ou
com alguma pequena variação.

UTILIZANDO = EM LUGAR DE ==
Infelizmente, é possível escrever muitas coisas em C++ que você pensa ser comandos incorretos, mas que
revelam ter algum significado obscuro. Isso significa que, se você cometer um erro e escrever algo que espe-
raria produzir uma mensagem de erro, pode descobrir que o programa compila e é executado sem mensa-
gens de erro, mas produz um resultado incorreto. Como talvez você não perceba que escreveu algo de
modo incorreto, isso pode causar sérios problemas. Por exemplo, considere um comando if-else que co-
mece da seguinte forma:
if (x = 12)
Faca_Alguma_Coisa
else
Faca_Outra_Coisa
Suponha que você quisesse testar para ver se o valor de x é igual a 12, de forma que você desejasse real-
mente utilizar == em vez de =. Você acharia que o compilador perceberia seu erro. A expressão
x = 12
não é algo que é satisfeito ou não. É uma declaração de atribuição e, assim, é claro que o compilador deve-
ria emitir uma mensagem de erro. Infelizmente, não é esse o caso. Em C++ a expressão x = 12 é uma ex-
pressão que fornece um valor, exatamente como x + 12 ou 2 + 3. O valor de uma expressão de atribuição
é o valor transferido para a variável à esquerda. Por exemplo, o valor de x = 12 é 12. Vimos, em nossa dis-
cussão sobre a compatibilidade de valores booleanos, que valores int não-zeros são convertidos em true.
Se você usar x = 12 como uma expressão booleana em um comando if-else, a expressão booleana sem-
pre será avaliada como true.
Esse erro é bem difícil de encontrar, porque parece certo. O compilador pode encontrar o erro sem nenhu-
ma instrução especial se você colocar o 12 no lado esquerdo da comparação: 12 == x não produzirá men-
sagem de erro, mas 12 = x sim.
38 Fluxo de Controle

(continuação)
5. A seqüência seguinte produz divisão por zero?
j = -1;
if ((j > 0) && (1/(j+1) > 10))
cout << i << endl;
6. Escreva um comando if-else que apresente como saída a palavra Alto, se o valor da variável pontos
for maior que 100, e Baixo, se o valor de pontos for no máximo 100. A variável pontos é do tipo int.
7. Suponha que economias e despesas são variáveis de tipo double que receberam valores. Escreva um co-
mando if-else que apresente como saída a palavra Solvente, subtraia o valor de despesas do valor de
economias e atribua o valor 0 a despesas desde que economias seja no mínimo igual a despesas. Se, to-
davia, economias for menor que despesas, o comando if-else simplesmente apresenta como saída a pa-
lavra Falido e não altera o valor de nenhuma variável.
8. Escreva um comando if-else que apresente como saída a palavra Aprovado desde que o valor da variá-
vel exame seja maior ou igual a 60 e o valor da variável programasFeitos seja maior ou igual a 10. Caso
contrário, o comando if-else apresenta como saída a palavra Reprovado. As variáveis exame e progra-
masFeitos são ambas de tipo int.
9. Escreva um comando if-else que apresente como saída a palavra Alerta desde que o valor da variável
temperatura seja maior ou igual a 100 ou o valor da variável pressao seja maior ou igual a 200, ou am-
bos. Caso contrário, o comando if-else apresenta como saída a palavra OK. As variáveis temperatura e
pressao são ambas de tipo int.
10. Qual é a saída dos seguintes trechos? Explique suas respostas.
a. if(0)
cout << "0 é true";
else
cout << "0 é false";
cout << endl;
b. if(1)
cout << "1 é true";
else
cout << "1 é false";
cout << endl;
c. if(-1)
cout << "-1 é true";
else
cout << "-1 é false";
cout << endl;
Observação: Isto é apenas um exercício, e não foi elaborado para ilustrar estilos de programação que você
deveria seguir.

■ OMITINDO O else
Às vezes você deseja que uma das alternativas de um comando if-else não faça absolutamente nada. Em
C++, isso pode ser realizado omitindo-se a parte do else. Esses tipos de comando são conhecidos como comandos if,
para diferenciá-los dos comandos if-else. Por exemplo, o primeiro dos dois comandos seguintes é um comando if:
if (vendas >= minimo)
salario = salario + bonus;
cout << "salario = R$" << salario;

Se o valor de vendas for maior ou igual ao valor de minimo, a declaração de atribuição é executada e depois o
comando cout seguinte. Por outro lado, se o valor de vendas for menor que minimo, a declaração de atribuição
não é executada. Assim, o comando if não provoca nenhuma alteração (ou seja, nenhuma bonificação é acrescen-
tada ao salário-base) e o programa procede diretamente para o comando cout.
Estruturas de Controle 39

■ COMANDOS ANINHADOS
Como já vimos, comandos if-else e if contêm comandos menores dentro deles. Até agora utilizamos co-
mandos compostos e simples, tais como declarações de atribuição, ou subcomandos menores, mas há outras possi-
bilidades. Na realidade, qualquer comando pode ser utilizado como subparte de um comando if-else ou de
outros comandos que possuam um ou mais comandos dentro deles.
Quando aninhamos comandos, normalmente se alinha cada nível de subcomandos aninhados, embora existam
algumas situações especiais (como uma ramificação if-else de seleções múltiplas) em que essa regra não é seguida.

■ COMANDO if-else DE SELEÇÕES MÚLTIPLAS


O comando if-else de seleções múltiplas não é, na realidade, um tipo diferente de comando em C++. É ape-
nas um comando if-else comum aninhado dentro de comandos if-else, mas é considerado um tipo de coman-
do, e é alinhado diferentemente de outros comandos aninhados para refletir essa idéia.
A sintaxe para um comando if-else de seleções múltiplas e um exemplo básico são fornecidos na caixa que
acompanha esta seção. Observe que as expressões booleanas estão alinhadas uma com a outra e suas ações corres-
pondentes também estão alinhadas umas com as outras. Isso torna fácil observar a correspondência entre expres-
sões booleanas e ações. As expressões booleanas são avaliadas em ordem até que uma expressão booleana true seja
encontrada. A essa altura, a avaliação das expressões booleanas pára e a ação correspondente à primeira expressão
booleana true é executada. O else final é opcional. Se existir um else final e todas as expressões booleanas fo-
rem false, a ação final é executada. Se não existir um else final e todas as expressões booleanas forem false, ne-
nhuma ação é executada.

COMANDO if-else DE SELEÇÕES MÚLTIPLAS


SINTAXE
if (Expressao_Booleana_1)
Comando_1
else if (Expressao_Booleana_2)
Comando_2
.
.
.
else if (Expressao_Booleana_n)
Comando_n
else
Comando_Para_Todas_As_Outras_Possibilidades
EXEMPLO
if ((temperatura < -10) && (dia == DOMINGO))
cout << "Fique em casa.";
else if (temperatura < - 10) // e dia != DOMINGO
cout << "Fique em casa, mas ligue para o trabalho.";
else if (temperatura <= 0) // e temperatura >= -10
cout << "Vista roupas quentes.";
else // temperatura > 0
cout << "Vá firme, trabalhe duro.";
As expressões booleanas são verificadas em ordem até a primeira expressão booleana true ser encontrada, e então o comando
correspondente é executado. Se nenhuma das expressões booleanas é true, o Comando_Para_Todas_As_Outras_Possibilidades é
executado.

1 Exercícios de Autoteste 1

11. Que saída será produzida pelo seguinte código?


int x = 2;
cout << "Início\n";
40 Fluxo de Controle

Exercícios de Autoteste (continuação)


if (x <= 3)
if (x != 0)
cout << "Olá do segundo if.\n";
else
cout << "Olá do else.\n";
cout << "Fim\n";
cout << "Início de novo\n";
if (x > 3)
if (x != 0)
cout << "Olá do segundo if.\n";
else
cout << "Olá do else.\n";
cout << "Fim de novo\n";
12. Que saída será produzida pelo seguinte código?
int extra = 2;
if (extra < 0)
cout << "pequeno";
else if (extra == 0)
cout << "médio";
else
cout << "grande";
13. Qual seria a saída do Exercício de Autoteste 12 se a atribuição fosse alterada para a seguinte?
int extra = -37;
14. Qual seria a saída do Exercício de Autoteste 12 se a atribuição fosse alterada para a seguinte?
int extra = 0;
15. Escreva um comando if-else de seleções múltiplas que classifique o valor de n, uma variável int, em
uma das seguintes categorias e redija uma mensagem apropriada.
n < 0 ou 0 ≤ n ≤ 100 ou n > 100

■ COMANDO switch
O comando switch é o único tipo de comando C++ que implementa ramificações de seleções múltiplas. A
sintaxe para um comando switch e um exemplo simples são mostrados na caixa que acompanha esta seção.
Quando um comando switch é executado, uma das várias ramificações diferentes é executada. A escolha da
ramificação a executar é determinada por uma expressão de controle dada entre parênteses, após a palavra-chave
switch. A expressão de controle para um comando switch sempre deve fornecer um valor bool, uma constante
enum (de que falaremos mais adiante neste capítulo), um dos tipos inteiros ou um caractere. Quando o comando
switch é executado, essa expressão de controle é avaliada e o computador procura entre os valores da constante
fornecidos após as várias ocorrências dos identificadores case. Se ele encontra uma constante que seja igual ao va-
lor da expressão de controle, executa o código para esse case. Não se pode ter duas ocorrências de case com o
mesmo valor de constante porque isso criaria uma instrução ambígua.

COMANDO switch
SINTAXE
Você não precisa colocar um comando break
switch (Expressao_De_Controle)
em cada case. Se omitir um break, esse case
{
continua até encontrar um break (ou até o
case Constante_1:
final do comando switch).
Sequencia_Do_Comando_1
break;
case Constante_2:
Sequencia_Do_Comando_2
break;
.
.
.
Estruturas de Controle 41

(continuação)
case Constante_n:
Sequencia_Do_Comando_n
break;
default:
Sequencia_Do_Comando_Default
}
EXEMPLO
int classeVeiculo;
double pedagio;
cout << "Informe a classe do veículo: ";
cin >> classeVeiculo;

switch (classeVeiculo)
{
case 1:
cout << "Carro de passageiro."; Se você esquecer esse break, os carros
pedagio = 0.50; de passageiro pagarão R$1,50
break;
case 2:
cout << "Ônibus.";
pedagio = 1.50;
break;
case 3:
cout << "Caminhão.";
pedagio = 2.00;
break;
default:
cout << "Classe de veículo desconhecida!";
}

O comando switch termina quando um comando break é encontrado ou quando se chega ao fim do coman-
do switch. Um comando break é formado pela palavra-chave break seguida por um ponto-e-vírgula. Quando o
computador executa os comandos depois de um case, continua até chegar a um comando break. Quando o com-
putador encontra um comando break, o comando switch se encerra. Se você omitir o comando break, então, de-
pois de executar o código para um case, o computador continuará e executará o código do próximo case.
Observe que se pode ter dois case para a mesma seção de código, como no seguinte trecho de um comando
switch:
case ’A’:
case ’a’:
cout << "Excelente."
<< "Você não precisa tirar o final.\n";
break;

Como o primeiro case não possui comando break (na realidade não possui comando nenhum), o efeito é o
mesmo que se houvesse dois rótulos para um case, mas a sintaxe do C++ exige uma palavra-chave para cada ró-
tulo, como ’A’ e ’a’.
Se nenhum rótulo de case possuir uma constante que iguale o valor da expressão de controle, então os co-
mandos que se seguirem ao rótulo default serão executados. Não é preciso haver uma seção default. Se não
houver seção default e nenhuma constante igualar o valor da expressão de controle, então nada acontecerá quan-
do o comando switch for executado. Entretanto, é mais seguro ter sempre uma seção default. Se você acha que
seus rótulos case listam todas as saídas possíveis, pode colocar uma mensagem de erro na seção default.
42 Fluxo de Controle

ESQUECENDO UM break EM UM COMANDO switch


Se você esquecer um break em um comando switch, o compilador não emitirá uma mensagem de erro.
Você terá escrito um comando switch sintaticamente correto, mas que não fará o que você pretendia que
fizesse. Observe a anotação no exemplo na caixa intitulada Comando switch.

IDica USE COMANDOS switch PARA MENUS


O comando if-else de seleções múltiplas é mais versátil que o comando switch, e você pode usar um co-
mando if-else de seleções múltiplas em qualquer lugar em que seja possível utilizar um comando switch.
Entretanto, às vezes o comando switch é mais claro. Por exemplo, o comando switch é perfeito para imple-
mentar menus. Cada ramificação do comando switch pode ser uma opção do menu.

■ TIPO ENUMERAÇÃO
Um tipo enumeração é um tipo cujos valores são definidos por uma lista de constantes de tipo int. Um tipo
enumeração é parecido com uma lista de constantes declaradas. Tipos enumeração podem ser úteis para definir
uma lista de identificadores para usar como rótulos case em um comando switch.
Quando se define um tipo enumeração, podem-se usar quaisquer valores int e pode-se definir qualquer núme-
ro de constantes. Por exemplo, o seguinte tipo enumeração define uma constante para a duração de cada mês:
enum DuracaoDoMes { DURACAO_JAN = 31, DURACAO_FEV = 28, DURACAO_MAR = 31, DURACAO_ABR = 30, DURACAO_MAI
= 31, DURACAO_JUN = 30, DURACAO_JUL = 31, DURACAO_AGO = 31, DURACAO_SET = 30, DURACAO_OUT = 31,
DURACAO_NOV = 30, DURACAO_DEZ = 31 };

Como mostra esse exemplo, duas ou mais constantes nomeadas em um tipo enumeração podem receber o
mesmo valor int.
Se você não especificar nenhum valor numérico, são atribuídos valores consecutivos aos identificadores, a co-
meçar do 0. Por exemplo, a definição do tipo
enum Direcao { NORTE = 0, SUL = 1, LESTE = 2, OESTE = 3 };

é equivalente a
enum Direcao { NORTE, SUL, LESTE, OESTE };

A forma que não lista explicitamente os valores int normalmente é usada quando você deseja uma lista de no-
mes e não se importa com que valores estes possuem.
Suponha que você inicialize uma constante de enumeração com algum valor, digamos
enum MinhaEnum { UM = 17, DOIS, TRES, QUATRO = -3, CINCO };

então UM assume o valor 17; DOIS assume o próximo valor int, 18; TRES assume o próximo valor, 19; QUATRO as-
sume -3 e CINCO assume o próximo, -2. Em suma, o padrão para a primeira constante de enumeração é 0. O res-
to vai aumentando de 1 em 1, a não ser que se defina uma ou mais constantes de enumeração.
Embora as constantes em um tipo enumeração sejam dadas como valores int e possam ser usadas como intei-
ros em muitos contextos, lembre-se de que um tipo enumeração é um tipo separado e tratado como um tipo di-
ferente do tipo int. Utilize tipos enumeração como rótulos e evite fazer operações aritméticas com variáveis de
um tipo enumeração.

■ OPERADOR CONDICIONAL
É possível inserir um condicional dentro de uma expressão, utilizando um operador ternário conhecido como
operador condicional (também chamado de operador ternário ou if aritmético). Seu uso decorre de um velho esti-
lo de programação e não o aconselhamos a utilizá-lo. Está incluído aqui em nome da abrangência (caso você dis-
corde de nosso estilo de programação).
O operador condicional é uma variante de notação em certas formas do comando if-else. Essa variante está
ilustrada a seguir. Considere o comando
Loops 43

if (n1 > n2)


max = n1;
else
max = n2;

Isto pode ser expresso utilizando o operador condicional da seguinte forma:


max = (n1 > n2) ? n1 : n2;

A expressão do lado direito da declaração de atribuição é a expressão de operador condicional:


(n1 > n2) ? n1 : n2

O ? e o : juntos formam um operador ternário conhecido como o operador condicional. Uma expressão de
operador condicional começa com uma expressão booleana seguida por um ? e depois seguida por duas expressões
separadas por dois-pontos. Se a expressão booleana é true, a primeira das duas expressões é fornecida; caso contrá-
rio, a segunda das duas expressões é que é fornecida.

1 Exercícios de Autoteste 1

16. Dadas as seguintes declarações e comando de saída, presuma que tenham sido inseridos em um progra-
ma correto, que é executado. Qual é a saída?
enum Direcao { N, S, L, O };
//...
cout << O << " " << L << " " << S << " " << N << endl;
17. Dadas as seguintes declarações e comando de saída, presuma que tenham sido inseridos em um progra-
ma correto, que é executado. Qual é a saída?
enum Direcao { N = 5, S = 7, L = 1, O };
//...
cout << O << " " << L << " " << S << " " << N << endl;

2.3 1 Loops
Não é verdade que a vida seja uma chatice atrás da outra. É uma chatice que fica se repetindo.
Edna St. Vincent Millay
Carta a Arthur Darison Ficke, 24 de outubro de 1930

Os mecanismos de loop em C++ são semelhantes aos de outras linguagens de alto nível. Os três comandos
loop em C++ são o comando while, o comando do-while e o comando for. A mesma terminologia utilizada em
outras linguagens é utilizada em C++. O código repetido em um loop é chamado corpo do loop. Cada repetição
do corpo do loop é chamada de uma iteração do loop.

■ COMANDOS while E do-while


A sintaxe para o comando while e sua variante, o comando do-while, é fornecida na caixa que acompanha a
seção. Em ambos os casos, a sintaxe do corpo de múltiplos comandos é um caso especial da sintaxe para um loop
com um corpo de um comando único. O corpo de comandos múltiplos é um comando composto de comandos
únicos. Exemplos de um comando while e de um comando do-while são fornecidos nos Painéis 2.4 e 2.5.

SINTAXE PARA COMANDOS while E do-while


UM COMANDO while COM UM CORPO DE COMANDO ÚNICO
while (Expressao_Booleana)
Comando
UM COMANDO while COM UM CORPO DE COMANDOS MÚLTIPLOS
while (Expressao_Booleana)
44 Fluxo de Controle

(continuação)
{
Comando_1
Comando_2
.
.
.
Comando_Final
}
UM COMANDO do-while COM UM CORPO DE COMANDO ÚNICO
do
Comando
while (Expressao_Booleana);
Não se esqueça do
UM COMANDO do-while COM UM CORPO DE COMANDOS MÚLTIPLOS
ponto-e-vírgula final.
do
{
Comando_1
Comando_2
.
.
.
Comando_Final
} while (Expressao_Booleana);

Painel 2.4 Exemplo de um comando while (parte 1 de 2)


1 #include <iostream>
2 using namespace std;

3 int main( )
4 {
5 int countDown;

6 cout << "Quantas saudações você quer? ";


7 cin >> countDown;

8 while (countDown > 0)


9 {
10 cout << "Olá ";
11 countDown = countDown - 1;
12 }

13 cout << endl;


14 cout << "Acabou! \n";

15 return 0;
16 }

DIÁLOGO PROGRAMA-USUÁRIO 1
Quantas saudações você quer? 3
Olá Olá Olá
Acabou!
Loops 45

Painel 2.4 Exemplo de um comando while (parte 2 de 2)


DIÁLOGO PROGRAMA-USUÁRIO 2

Quantas saudações você quer? 0


Acabou! O corpo do loop é executado zero vezes.

Painel 2.5 Exemplo de um comando do-while


1 #include <iostream>
2 using namespace std;

3 int main( )
4 {
5 int countDown;

6 cout << " Quantas saudações você quer? ";


7 cin >> countDown;

8 do
9 {
10 cout << "Olá ";
11 countDown = countDown - 1;
12 }while (countDown > 0);

13 cout << endl;


14 cout << "Acabou!\n";

15 return 0;
16 }

DIÁLOGO PROGRAMA-USUÁRIO 1
Quantas saudações você quer? 3
Olá Olá Olá
Acabou!

DIÁLOGO PROGRAMA-USUÁRIO 2
Quantas saudações você quer? 0
Olá O corpo do loop é executado
Acabou! pelo menos uma vez.

A diferença importante entre os loops while e do-while envolve o momento em que a expressão booleana de
controle é verificada. Com um comando while, a expressão booleana é verificada antes que o corpo do loop seja
executado. Se a expressão booleana é avaliada como false, o corpo não é executado. Com um comando do-while, o
corpo do loop é executado primeiro, e a expressão booleana é verificada depois que o corpo do loop é executado.
Assim, o comando do-while sempre executa o corpo do loop pelo menos uma vez. Depois deste início, o loop
while e o do-while se comportam da mesma forma. Após cada iteração do corpo do loop, a expressão booleana é
verificada novamente; se for true, o loop é iterado outra vez. Caso tenha mudado de true para false, o coman-
do loop se encerra.
46 Fluxo de Controle

A primeira coisa que acontece quando um loop while é executado é que a expressão booleana de controle é
avaliada. Se a expressão booleana é avaliada como false a essa altura, o corpo do loop nunca é executado. Pode
parecer inútil executar o corpo de um loop zero vezes, mas às vezes esta é a ação desejada. Por exemplo, muitas
vezes se usa um loop while para somar uma lista de números, mas a lista poderia estar vazia. Para sermos mais es-
pecíficos, um programa de contabilidade de cheques poderia usar um loop while para somar os valores de todos
os cheques que você emitiu em um mês — mas você pode ter tirado um mês de férias, em que não emitiu ne-
nhum cheque. Nesse caso, há zeros a serem somados e, assim, o loop é iterado zero vezes.

■ NOVA VISÃO DOS OPERADORES DE INCREMENTO E DECREMENTO


Em geral não aconselhamos o uso de operadores de incremento e decremento em expressões. Entretanto, mui-
tos programadores gostam de utilizá-los nas expressões booleanas de controle de um comando while ou do-while.
Se realizado com cuidado, isso pode funcionar bem. Apresentamos um exemplo no Painel 2.6. Não se esqueça de
que em contagem++ <= numeroDeItens, o valor fornecido por contagem++ é o valor de contagem antes de ser in-
crementado.

Painel 2.6 Operador de incremento em uma expressão


1 #include <iostream>
2 using namespace std;

3 int main( )
4 {
5 int numeroDeProdutos, count,
6 caloriasPorProduto, totalCalorias;

7 cout << "Quantos produtos você consumiu hoje? ";


8 cin >> numberOfItems;

9 totalCalories = 0;
10 count = 1;
11 cout << "Informe o número de calorias em cada um dos\n"
12 << numberOfItems << " produtos consumidos:\n";

13 while (count++ <= numberOfItems)


14 {
15 cin >> caloriesForItem;
16 totalCalories = totalCalories
17 + caloriesForItem;
18 }

19 cout << "Total de calorias consumidas hoje = "


20 << totalCalories << endl;
21 return 0;
22 }

DIÁLOGO PROGRAMA-USUÁRIO
Quantos produtos você consumiu hoje? 7
Informe o número de calorias em cada um dos
7 produtos consumidos:
300 60 1200 600 150 1 120
Total de calorias consumidas hoje = 2431
Loops 47

1 Exercícios de Autoteste 1

18. Qual é a saída do seguinte trecho de programa?


int contagem = 3;
while (contagem-- > 0)
cout << contagem << " ";
19. Qual é a saída do seguinte trecho de programa?
int contagem = 3;
while (--contagem > 0)
cout << contagem << " ";
20. Qual é a saída do seguinte trecho de programa?
int n = 1;
do
cout << n << " ";
while (n++ <= 3);
21. Qual é a saída do seguinte trecho de programa?
int n = 1;
do
cout << n << " ";
while (++n <= 3);
22. Qual é a saída do seguinte trecho de programa? (x é de tipo int.)
int x = 10;
while (x > 0)
{
cout << x << endl;
x = x - 3;
}
23. Que saída seria produzida no exercício anterior se o sinal > fosse substituído por <?
24. Qual é a saída do seguinte trecho de programa? (x é de tipo int.)
int x = 10;
do
{
cout << x << endl;
x = x - 3;
} while (x > 0);
25. Qual é a saída do seguinte trecho de programa? (x é de tipo int.)
int x = -42;
do
{
cout << x << endl;
x = x - 3;
} while (x > 0);
26. Qual é a diferença mais importante entre um comando while e um comando do-while?

■ OPERADOR VÍRGULA
O operador vírgula é uma forma de avaliar uma lista de expressões e fornecer o valor da última expressão. Às
vezes é útil empregar um loop for, como indicado em nossa discussão sobre o loop for na próxima subseção.
Não o aconselhamos a utilizá-lo em outros contextos, mas seu uso é legal em qualquer expressão.
O operador vírgula é ilustrado pela seguinte declaração de atribuição:
resultado = (primeiro = 2, segundo = primeiro + 1);

O operador vírgula é a vírgula mostrada no exemplo. A expressão vírgula é a expressão do lado direito do opera-
dor de atribuição. O operador vírgula possui duas expressões como operandos. Nesse caso, os dois operandos são
primeiro = 2 e segundo = primeiro + 1
48 Fluxo de Controle

A primeira expressão é avaliada, depois a segunda. Como dissemos no Capítulo 1, a declaração de atribuição,
quando usada como uma expressão, fornece o novo valor da variável do lado esquerdo do operador de atribuição.
Assim, essa expressão vírgula fornece o valor final da variável segundo, o que significa que a variável resultado é
fixada como igual a 3.
Como apenas o valor da segunda expressão é fornecido, a primeira expressão é avaliada apenas por seus efeitos
colaterais. No exemplo anterior, o efeito colateral da primeira expressão é mudar o valor da variável primeiro.
Pode-se ter uma lista maior de expressões ligadas por vírgulas, mas isso deve ser feito somente quando a ordem de
avaliação não for importante. Se a ordem de avaliação for importante, você deve usar parênteses. Por exemplo:
resultado = ((primeiro = 2, segundo = primeiro + 1), terceiro = segundo + 1);

estabelece o valor de resultado como sendo 4. Entretanto, o valor que a seguinte linha dá a resultado é imprevi-
sível, porque não há garantias de que a expressão será avaliada em ordem:
resultado = (primeiro = 2, segundo = primeiro + 1, terceiro = segundo + 1);

Por exemplo, terceiro = segundo + 1 pode ser avaliado antes de segundo = primeiro + 1.1

■ COMANDO for
O terceiro e último comando loop em C++ é o comando for. O comando for é mais usado para se percorrer
uma variável inteira em incrementos iguais. Como veremos no Capítulo 5, o comando for geralmente é utilizado
para se percorrer um vetor. O comando for é, entretanto, um mecanismo geral de looping que pode fazer qual-
quer coisa que um loop while faça.
Por exemplo, o seguinte comando for soma os inteiros de 1 a 10:
soma = 0;
for (n = 1; n <= 10; n++)
soma = soma + n;

Um comando for começa com a palavra-chave for seguida por três expressões entre parênteses que dizem ao
computador o que fazer com a variável de controle. O início de um comando for tem esta aparência:
for (Acao_De_Inicializacao; Expressao_Booleana; Acao_De_Atualizacao)

A primeira expressão, Acao_De_Inicializacao, diz como a variável, variáveis ou outras coisas são inicializadas; a
segunda, Expressao_Booleana fornece uma expressão booleana que é utilizada para verificar quando o loop deve ter-
minar, e a última expressão, Acao_De_Atualizacao, diz como a variável de controle do loop é atualizada após cada
iteração do corpo do loop.
As três expressões no início de um comando for são separadas por dois — e apenas dois — ponto-e-vírgulas.
Não caia na tentação de colocar um ponto-e-vírgula depois da terceira expressão. (A explicação técnica é que essas
três coisas são expressões, não comandos, e não requerem um ponto-e-vírgula no final.)
Um comando for em geral emprega uma única variável int para controlar a iteração e o final do loop. Entre-
tanto, as três expressões no início de um comando for podem ser quaisquer expressões em C++; portanto, podem
envolver mais (ou até menos) do que uma variável, e as variáveis podem ser de qualquer tipo.
Utilizando o operador vírgula, você pode acrescentar múltiplas ações à primeira ou à última (mas normalmen-
te não à segunda) das três expressões dentro dos parênteses. Por exemplo, você pode deslocar a inicialização da va-
riável soma para dentro do loop for para obter a seguinte linha, que é equivalente ao código do comando for que
mostramos anteriormente:
for (soma = 0, n = 1; n <= 10; n++)
soma = soma + n;
Embora não o aconselhemos a fazer isso, porque não é tão fácil de ler, você pode deslocar todo o corpo do
loop for para o terceiro item dentro dos parênteses. O comando for anterior é equivalente ao seguinte:
for (soma = 0, n = 1; n <= 10; soma = soma + n, n++);

1. O padrão C++ especifica que as expressões unidas por vírgulas devem ser avaliadas da esquerda para a direita. Entretanto,
nossa experiência revela que nem todos os computadores seguem o padrão a esse respeito.
Loops 49

O Painel 2.7 mostra a sintaxe para um comando for e também descreve a ação do comando for mostrando
como traduzi-lo em um comando while equivalente. Observe que em um comando for, como no comando whi-
le correspondente, a condição de parada é testada antes da primeira iteração do loop. Assim, é possível ter um
loop for cujo corpo é executado zero vezes.
O corpo de um comando for pode ser, e em geral é, um comando composto, como no seguinte exemplo:
for (numero = 100; numero >= 0; numero--)
{
cout << numero
<< "garrafas de cerveja na prateleira.\n";
if (numero > 0)
cout << "Pegue uma e coloque na roda.\n";
}

A primeira e a última expressões entre parênteses no início do comando for podem ser quaisquer expressões
em C++ e, assim, podem envolver qualquer número de variáveis e ser de qualquer tipo.
Em um comando for, uma variável pode ser declarada ao mesmo tempo em que é inicializada. Por exemplo:
for (int n = 1; n < 10; n++)
cout << n << endl;

Pode haver variação no modo como os compiladores lidam com tais declarações dentro de um comando for. Isso
será discutido no Capítulo 3, na subseção intitulada "Variáveis Declaradas em um Loop for". Seria bom evitar tais de-
clarações dentro de um comando for até chegar ao Capítulo 3; apenas as mencionamos aqui para efeito de referência.

Painel 2.7 Comando for


SINTAXE DO COMANDO for
for (Acao_De_Inicializacao; Expressao_Booleana; Acao_De_Atualizacao)
Corpo_Do_Comando
EXEMPLO
for (numero = 100; numero >= 0; numero--)
cout << numero
<< "garrafas de cerveja na prateleira.\n";
SINTAXE DO LOOP while EQUIVALENTE
Acao_De_Inicializacao;
while (Expressao_Booleana)
{
Corpo_Do_Comando
Acao_De_Atualizacao;
}
EXEMPLO EQUIVALENTE
numero = 100;
while (numero >= 0)
{
cout << numero
<< "garrafas de cerveja na prateleira.\n";
numero--;
}

DIÁLOGO PROGRAMA-USUÁRIO
100 garrafas de cerveja na prateleira.
99 garrafas de cerveja na prateleira.
.
.
.
0 garrafas de cerveja na prateleira.
50 Fluxo de Controle

COMANDO for
SINTAXE
for (Acao_De_Inicializacao; Expressao_Booleana; Acao_De_Atualizacao)
Corpo_Do_Comando
EXEMPLO
for (soma = 0, n = 1; n <= 10; n++)
soma = soma + n;
Veja o Painel 2.7 para uma explicação da ação do comando for.

IDica LOOPS QUE SE REPETEM N VEZES


Um comando for pode ser utilizado para produzir um loop que repete o corpo do loop um número prede-
terminado de vezes. Por exemplo, o seguinte corpo de loop repete seu corpo de loop três vezes:
for (int contagem = 1; contagem <= 3; contagem++)
cout << "Hip, Hip, Hurra\n";
O corpo de um comando for não precisa fazer referência a uma variável de controle do loop, como a variá-
vel contagem.

PONTO-E-VÍRGULA EXTRA EM UM COMANDO for


Normalmente não se coloca ponto-e-vírgula depois do parênteses no início de um loop for. Para ver o que
acontece, considere o seguinte loop for:
for (int contagem = 1; contagem <= 10; contagem++); Ponto-e-vírgula problema
cout << "Olá\n";
Se você não notar o ponto-e-vírgula extra, vai esperar que esse loop for escreva Olá na tela dez vezes. Se
você notar o ponto-e-vírgula, vai esperar que o compilador emita uma mensagem de erro. Nenhuma das
duas coisas acontece. Se você inserir esse loop for em um programa completo, o compilador não irá recla-
mar. Se você executar o programa, apenas um Olá será escrito, em vez dos dez esperados. O que está
acontecendo? Para responder a essa questão, precisamos de algumas informações.
Uma forma de criar um comando em C++ é colocar um ponto-e-vírgula depois de alguma coisa. Se você
colocar um ponto-e-vírgula depois de x++, altera a expressão
x++
para o comando
x++;
Se você colocar um ponto-e-vírgula depois de nada, criará um comando. Assim, o ponto-e-vírgula em si é
um comando, que é chamado de comando vazio ou comando nulo. O comando vazio não executa ne-
nhuma ação, mas continua sendo um comando. Portanto, a linha seguinte é um loop for completo e legíti-
mo, cujo corpo é um comando vazio:
for (int contagem = 1; contagem <= 10; contagem++);
Esse loop for é, na realidade, iterado dez vezes, mas, como o corpo é o comando vazio, nada acontece
quando o corpo é iterado. Esse loop não faz nada, e não faz nada dez vezes!
O mesmo tipo de problema pode surgir com um loop while. Tenha o cuidado de não colocar um ponto-e-
vírgula depois de fechar os parênteses que encerram a expressão booleana no início de um loop while. Um
loop do-while apresenta o problema oposto. Você deve se lembrar de sempre terminar um loop do-while
com um ponto-e-vírgula.

LOOPS INFINITOS
Um loop while, do-while ou for não termina enquanto a expressão booleana de controle não for verdadei-
ra. Essa expressão booleana normalmente contém uma variável que será alterada pelo corpo do loop, e em
geral o valor dessa variável é alterado de uma forma que pode acabar tornando a expressão booleana falsa
e, assim, finalizar o loop. Entretanto, se você cometer um erro e escrever seu programa de modo que a ex-
pressão booleana seja sempre verdadeira, o loop será executado para sempre. Um loop que é executado
para sempre é chamado de loop infinito.
Infelizmente, exemplos de loops infinitos não são difíceis de encontrar. Primeiro vamos descrever um loop
que é finalizado. O seguinte código C++ escreverá os números pares positivos inferiores a 12. Ou seja,
dará como saída os números 2, 4, 6, 8 e 10, um em cada linha, e então o loop se encerrará.
Loops 51

(continuação)
x = 2;
while (x != 12)
{
cout << x << endl;
x = x + 2;
}
O valor de x é incrementado em 2 a cada iteração do loop até chegar a 12. A essa altura, a expressão boo-
leana após a palavra while não é mais verdadeira, então o loop se encerra.
Agora suponha que você queira escrever os números ímpares inferiores a 12, em vez dos números pares.
Você pode pensar, erroneamente, que só precisaria alterar o comando inicial para
x = 1;
Mas esse erro criará um loop infinito. Porque o valor de x pula de 11 para 13, o valor de x nunca será igual
a 12; assim, o loop jamais terminará.
Esse tipo de problema é comum quando os loops são encerrados pela verificação de uma quantidade numé-
rica utilizando-se == ou !=. Quando se lida com números, sempre é mais seguro testar passando um va-
lor. Por exemplo, o comando seguinte funcionará bem como a primeira linha de nosso loop while:
while (x < 12)
Com essa alteração, x pode ser inicializado com qualquer número e o loop sempre terminará.
Um programa que é um loop infinito será executado para sempre a não ser que forças externas o dete-
nham. Como você agora pode escrever programas que contenham um loop infinito, é uma boa idéia apren-
der como forçar um programa a terminar. O método para forçar um programa a parar varia de sistema para
sistema. A combinação de teclas Control-C terminará um programa em muitos sistemas. (Para teclar Con-
trol-C, segure a tecla Control enquanto pressiona a tecla C.)
Em programas simples, um loop infinito é quase sempre um erro. Entretanto, alguns programas são escritos
propositadamente para ser executados para sempre (em princípio), tal como o principal loop externo de um
programa de reservas de passagens aéreas, que só fica pedindo mais reservas até que você desligue o com-

1 Exercícios de Autoteste 1

27. Qual é a saída do seguinte trecho (quando inserido em um programa completo)?


for (int contagem = 1; contagem < 5; contagem++)
cout << (2 * contagem) << " ";
28. Qual é a saída do seguinte trecho (quando inserido em um programa completo)?
for (int n = 10; n > 0; n = n - 2)
{
cout << "Olá ";
cout << n << endl;
}
29. Qual é a saída do seguinte trecho (quando inserido em um programa completo)?
for (double amostra = 2; amostra > 0; amostra = amostra - 0.5)
cout << amostra << " ";
30. Reescreva os seguintes loops como loops for.
a.
int i = 1;
while (i <= 10)
{
if (i < 5 && i != 2)
cout << ’X’;
i++;
}
b.
int i = 1;
while (i <= 10)
{
cout << ’X’;
52 Fluxo de Controle

Exercícios de Autoteste (continuação)


i = i + 3;
}
c.
long n = 100;
do
{
cout << ’X’;
n = n + 100;
} while (n < 1000);
31. Qual é a saída deste loop? Identifique a conexão entre o valor de n e o valor da variável log.
int n = 1024;
int log = 0;
for (int i = 1; i < n; i = i * 2)
log++;
cout << n << " " << log << endl;
32. Qual é a saída deste loop? Comente a respeito do código. (Não é o mesmo do exercício anterior.)
int n = 1024;
int log = 0;
for (int i = 1; i < n; i = i * 2);
log++;
cout << n << " " << log << endl;
33. Qual é a saída deste loop? Comente a respeito do código. (Não é o mesmo dos dois exercícios anterio-
res.)
int n = 1024;
int log = 0;
for (int i = 0; i < n; i = i * 2);
log++;
cout << n << " " << log << endl;
34. Para cada uma das seguintes situações, diga qual tipo de loop (while, do-while ou for) funcionaria melhor.
a. Soma de uma série, como 1/2 + 1/3 + 1/4 + 1/5 + ... + 1/10.
b. Leitura da lista de notas de prova de um estudante.
c. Leitura do número de dias de licença-saúde tirados pelos empregados de um departamento.
d. Teste de uma função para verificar como ela funciona para diferentes valores de seus argumentos.
35. Qual é a saída produzida pelo seguinte trecho? (x é do tipo int.)
int x = 10;
while (x > 0)
{
cout << x << endl;
x = x + 3;
}

■ COMANDOS break E continue


Nas subseções anteriores, descrevemos o fluxo de controle básico para os loops while, do-while e for. É assim
que os loops normalmente devem ser e são usados. Entretanto, você pode alterar o fluxo de controle de duas for-
mas, o que, em casos raros, pode ser uma técnica útil e segura. As duas formas de se alterar o fluxo de controle são in-
serir um comando break ou continue. O comando break encerra o loop. O comando continue encerra a iteração
atual do corpo do loop. O comando break pode ser usado com qualquer um dos comandos loop de C++.
Descrevemos o comando break quando discutirmos o comando switch. O comando break consiste na pala-
vra-chave break seguida por um ponto-e-vírgula. Quando executado, o comando break encerra o comando loop
ou switch mais próximo em que está inserido. O Painel 2.8 contém um exemplo de um comando break que ter-
mina um loop quando dados de entrada inapropriados são introduzidos.
O comando continue é formado pela palavra-chave continue seguida por um ponto-e-vírgula. Quando execu-
tado, o comando continue encerra a iteração atual do corpo do loop do comando loop mais próximo em que
está inserido. O Painel 2.9 apresenta um exemplo de um loop que contém um comando continue.
Loops 53

Um ponto que deveria ser observado quando se usa o comando continue em um loop for é que o comando
continue transfere o controle para a expressão atualizada. Assim, qualquer variável de controle de loop será atuali-
zada imediatamente depois que o comando continue for executado.
Observe que um comando break encerra completamente o loop. Em contraste, um comando continue apenas
encerra uma iteração do loop; a próxima iteração (se houver) continua o loop. Você achará instrutivo comparar os de-
talhes dos programas no Painel 2.8 e 2.9. Preste atenção, especialmente, na mudança da expressão booleana de controle.

Painel 2.8 Comando break em um loop


1 #include <iostream>
2 using namespace std;

3 int main( )
4 {
5 int numero, soma = 0, contagem = 0;
6 cout << "Digite 4 números negativos:\n";

7 while (++contagem <= 4)


8 {
9 cin >> number;

10 if (number >= 0)
11 {
12 cout << "ERRO: número positivo"
13 << " ou zero foi digitado na posição\n"
14 << contagem << " O último número digitado "
15 << "deve ser o da posição" << contagem << " O número da posição\n"
16 << contagem << "não foi acrescentado.\n";
17 break;
18 }

19 soma = soma + numero;


20 }

21 cout << sum << " é a soma dos primeiros "


22 << (count - 1) << " números.\n";

23 return 0;
24 }

DIÁLOGO PROGRAMA-USUÁRIO
Digite 4 números negativos:
-1 -2 3 -4
ERRO: número positivo ou zero foi digitado na posição
3! O último número digitado deve ser o da posição 3.
O número da posição
3 não foi acrescentado.
-3 é a soma dos dois primeiros números.

Painel 2.9 Comando continue em um loop (parte 1 de 2)


1 #include <iostream>
2 using namespace std;

3 int main( )
4 {
5 int numero, soma = 0, contagem = 0;
6 cout << "Digite 4 números negativos, UM EM CADA LINHA:\n";
54 Fluxo de Controle

Painel 2.9 Comando continue em um loop (parte 2 de 2)


7 while (contagem < 4)
8 {
9 cin >> numero;

10 if (numero >= 0)
11 {
12 cout << "ERRO: número positivo (ou zero)!\n"
13 << "Digite novamente esse número e continue:\n";
14 continue;
15 }
16 soma = soma + numero;
17 contagem++;
18 }
19 cout << soma << "é a soma dos "
20 << contagem << " números.\n";
21 return 0;
22 }

DIÁLOGO PROGRAMA-USUÁRIO
Digite 4 números negativos, UM EM CADA LINHA:
1
ERRO: número positivo (ou zero)!
Digite novamente esse número e continue:
-1
-2
3
ERRO: número positivo!
Digite novamente esse número e continue:
-3
-4
-10 é a soma dos 4 números.

Observe que você não precisa obrigatoriamente de um comando break ou continue. Os programas dos Pai-
néis 2.8 e 2.9 podem ser reescritos eliminando-se os comandos break e continue. O comando continue pode ser
especialmente enganador e tornar seu código ilegível. É melhor evitar o comando continue completamente ou, ao
menos, utilizá-lo apenas em raras ocasiões.

■ LOOPS ANINHADOS
É perfeitamente legal aninhar um loop dentro de outro. Quando fizer isso, lembre-se de que qualquer coman-
do break ou continue se aplica ao loop (ou switch) mais interno que contenha o comando break ou continue.
É melhor evitar loops aninhados, colocando o loop interno dentro de uma definição de função e uma invocação
de função fora do loop externo. Falaremos sobre funções no Capítulo 3.

1 Exercícios de Autoteste 1

36. O que faz um comando break? Onde é correto colocar um comando break?
37. Preveja a saída dos seguintes loops aninhados:
int n, m;
for (n = 1; n <= 10; n++)
for (m = 10; m >= 1; m--)
cout << n << " vezes " << m
<< " = " << n * m << endl;
Respostas dos Exercícios de Autoteste 55

Resumo do Capítulo
■ As expressões booleanas são avaliadas de forma semelhante às expressões aritméticas.
■ As estruturas de controle em C++ são o comando if-else e o comando switch.
■ Um comando switch é uma estrutura de controle de seleções múltiplas. Também se podem formar estru-
turas de controle de seleções múltiplas aninhando-se comandos if-else para formar um comando if-else
de seleções múltiplas.
■ Um comando switch é uma boa forma de se implementar um menu para o usuário do seu programa.
■ Os loops em C++ são os comandos while, do-while e for.
■ Um comando do-while sempre itera seu corpo de loop pelo menos uma vez. Tanto o comando while
quanto o comando for podem iterar seu corpo de loop zero vezes.
■ Um loop for pode ser usado para obter o equivalente da instrução "repita o corpo do loop n vezes".
■ Um loop pode ser interrompido por meio do comando break. Uma única iteração do corpo do loop pode
ser interrompida por meio do comando continue. Não se deve exagerar no uso de comandos break. É
melhor evitar comandos continue, embora alguns programadores os utilizem em raras ocasiões.

RESPOSTAS DOS EXERCÍCIOS DE AUTOTESTE


1. a. true.
b. true. Observe que as expressões a e b querem dizer exatamente a mesma coisa. Como os operadores
== e < têm maior precedência que &&, você não precisa incluir parênteses. Os parênteses, todavia,
tornam o código mais legível. A maioria das pessoas acha a expressão a mais fácil de ler que a b, em-
bora o significado seja o mesmo.
c. true.
d. true.
e. false. Como o valor da primeira subexpressão, (contagem == 1), é false, você sabe que toda a ex-
pressão é false sem se preocupar em avaliar a segunda subexpressão. Assim, não importa o que são os
valores x e y. Esta é a avaliação curto-circuito.
f. true. Como o valor da primeira subexpressão, (contagem < 10), é true, você sabe que toda a expres-
são é true sem se preocupar em avaliar a segunda subexpressão. Assim, não importa o que são os valo-
res x e y. Esta é a avaliação curto-circuito.
g. false. Observe que a expressão em g inclui a expressão em f como uma subexpressão. Essa subexpressão é
avaliada por meio da avaliação curto-circuito que descrevemos em f. Toda a expressão em g é equivalente a
!( (true || (x < y)) && true )
que, por sua vez, é equivalente a !( true && true ), e que é equivalente a !(true), que, por sua
vez, é equivalente ao valor final false.
h. Essa expressão produz um erro quando é avaliada, porque a primeira subexpressão, ((limite/conta-
gem) > 7), envolve uma divisão por zero.
i. true. Como o valor da primeira expressão, (limite < 20), é true, você sabe que a expressão inteira
é true sem se importar em avaliar a segunda expressão. Assim, a segunda subexpressão
((limite/contagem) > 7),
nunca é avaliada e, portanto, o fato de que envolve uma divisão por zero nunca é notado pelo compu-
tador. Trata-se de uma avaliação curto-circuito.
j. Esta expressão produz um erro quando é avaliada porque a primeira subexpressão, ((limite/contagem)
> 7), envolve uma divisão por zero.
k. false. Como o valor da subexpressão, (limite < 0), é false, você sabe que a expressão inteira é
false sem se preocupar em avaliar a segunda subexpressão. Assim, a segunda subexpressão,
((limite/contagem) > 7),
nunca é avaliada e, portanto, o fato de que envolve uma divisão por zero nunca é notado pelo compu-
tador. Trata-se de uma avaliação curto-circuito.
56 Fluxo de Controle

l. Se você acha que esta expressão é absurda, tem razão. A expressão não possui nenhum significado in-
tuitivo, mas o C++ converte os valores int para bool e avalia as operações && e !. Assim, o C++ ava-
liará essa bagunça! Lembre-se de que em C++ qualquer inteiro não-zero se converte em true, e 0 se
converte em false. Logo, C++ avaliará
(5 && 7) + (!6)
da seguinte forma: na expressão (5 && 7), 5 e 7 se convertem em true; true && true se convertem em
true, que o C++ converte em 1. Na expressão (!6) 6 é convertido em true, e !(true) resulta em false, que
o C++ converte em 0. Assim, a expressão inteira resulta em 1 + 0, que é 1. O valor final é 1. O C++ con-
verterá o número 1 em true, mas a resposta não possui grande significado intuitivo como true; talvez seja
melhor dizer apenas que a resposta é 1. Você não precisa se especializar em avaliar expressões absurdas como
esta, mas um pouco de treino ajudará a entender por que o compilador não fornece uma mensagem de erro
quando você se engana e mistura operadores numéricos e booleanos em uma única expressão.
2. A expressão 2 < x < 3 é legal. Entretanto, não significa
(2 < x) && (x < 3)

como muitos desejariam. Significa (2 < x) < 3. Como (2 < x) é uma expressão booleana, seu valor é
true ou false, sendo convertido em 0 ou 1 e, portanto, menor que 3. Assim, 2 < x < 3 é sempre
true. O resultado é true independentemente do valor de x.
3. (x < -1) || (x > 2)
4. (x > 1) && (x < 3)
5. Não. Na expressão booleana, (j > 0) é false (j acabou de receber a atribuição do valor -1). O &&
utiliza avaliação curto-circuito, que não avalia a segunda expressão se o resultado final puder ser determi-
nado pela primeira expressão. A primeira expressão é false, então a segunda não tem importância.
6. if (pontos > 100)
cout << "Alto";
else
cout << "Baixo";
Você pode querer acrescentar um \n ao final das strings entre aspas acima, dependendo de outros detalhes
do programa.
7. if (economias >= despesas)
{
economias = economias - despesas;
despesas = 0;
cout << "Solvente";
}
else
{
cout << "Falido";
}
Você pode querer acrescentar um \n ao final das strings entre aspas acima, dependendo de outros detalhes
do programa.
8. if ( (exame >= 60) && (programasFeitos >= 10) )
cout << "Aprovado";
else
cout << Reprovado";
Você pode querer acrescentar um \n ao final das strings entre aspas acima, dependendo de outros detalhes
do programa.
9. if ( (temperatura >= 100) || (pressao >= 200) )
cout << "Alerta";
else
cout << "OK";
Você pode querer acrescentar um \n ao final das strings entre aspas acima, dependendo de outros detalhes
do programa.
Respostas dos Exercícios de Autoteste 57

10. Todos os inteiros não-zero são convertidos em true; 0 é convertido em false.


a. 0 é false
b. 1 é true
c. -1 é true
11. Início
Olá do segundo if.
Fim
Início de novo
Fim de novo
12. grande
13. pequeno
14. médio
15. Qualquer uma dessas duas formas está correta:
if (n < 0)
cout << n << " é menor que zero.\n";
else if ( (0 <= n) && (n <= 100) )
cout << n << " está entre 0 e 100 (inclusive).\n";
else if (n > 100)
cout << n << " é maior que 100.\n";
e
if (n < 0)
cout << n << " é menor que zero.\n";
else if (n <= 100)
cout << n << " está entre 0 e 100 (inclusive).\n";
else
cout << n << " é maior que 100.\n";
16. 3 2 1 0
17. 2 1 7 5
18. 2 1 0
19. 2 1
20. 1 2 3 4
21. 1 2 3
22. 10
7
4
1
23. Não haverá saída; o loop é iterado zero vezes.
24. 10
7
4
1
25. -42
26. Com um comando do-while, o corpo do loop é sempre executado pelo menos uma vez. Com um co-
mando while, pode haver condições em que o corpo do loop não é executado nenhuma vez.
27. 2 4 6 8
28. Olá 10
Olá 8
Olá 6
Olá 4
Olá 2
29. 2.000000 1.500000 1.000000 0.500000
30. a. for (int i = 1; i <= 10; i++)
if (i < 5 && i != 2)
cout << ’X’;
58 Fluxo de Controle

b. for (int i = 1; i <= 10; i = i + 3)


cout << ’X’;
c. cout << ’X’; //Necessário para manter a mesma saída. Observe
//também a mudança na inicialização de n
for (long n = 200; n < 1000; n = n + 100)
cout << ’X’;
31. A saída é 1024 10. O segundo número é o logaritmo de base 2 do primeiro número. (Se o primeiro nú-
mero não for uma potência de 2, então é produzida apenas uma aproximação do logaritmo de base 2.)
32. A saída é 1024 1. O ponto-e-vírgula depois da primeira linha do loop for provavelmente é uma armadi-
lha, um erro.
33. Este é um loop infinito. Considere a expressão de atualização, i = i * 2. A expressão não pode alterar
i, porque o valor inicial de i é 0. Não há saída, por causa do ponto-e-vírgula após a primeira linha do
loop for.
34. a. Um loop for.
b. e c. Ambos requerem um loop while, porque a lista de entrada pode estar vazia.
d. Um loop do-while pode ser usado, porque pelo menos um teste será executado.
35. Este é um loop infinito. As primeiras linhas da saída serão assim:
10
13
16
19
21
36. Um comando break é usado para sair de um loop (um comando while, do-while ou for) ou para termi-
nar um comando switch. Um comando break não é legal em nenhuma outra parte de um programa em
C++. Note que, se os loops estiverem aninhados, o comando break encerra apenas um nível do loop.
37. A saída é muito longa para reproduzirmos aqui. O padrão é o seguinte:
1 vezes 10 = 10
1 vezes 9 = 9
.
.
.
1 vezes 1 = 1
2 vezes 10 = 20
2 vezes 9 = 18
.
.
.
2 vezes 1 = 2
3 vezes 10 = 30
.
.
.

PROJETOS DE PROGRAMAÇÃO
1. É difícil elaborar um orçamento que abranja vários anos, porque os preços não são estáveis. Se sua
empresa necessita de 200 lápis por ano, você não pode simplesmente utilizar o preço dos lápis este ano
para uma projeção para daqui dois anos. Devido à inflação, o custo provavelmente será maior do que é
hoje. Escreva um programa para estimar o custo esperado de um item em um número especificado de
anos. O programa pede o custo de cada item, o número de anos, a partir de agora, em que os itens serão
adquiridos e a taxa de inflação. Então, o programa apresenta como saída o custo estimado de cada item
após o período especificado. Faça com que o usuário informe a taxa de inflação como uma porcentagem,
como, por exemplo, 5,6 (por cento). Seu programa deve converter a porcentagem em uma fração, como
0,056, e utilizar um loop para estimar o preço ajustado com a inflação. (Dica: utilize um loop.)
Projetos de Programação 59

2. Você acaba de adquirir um aparelho estereofônico que custa R$ 1.000 por meio do seguinte plano de cre-
diário: zero de entrada, juros de 18% ao ano (e, portanto, 1,5% ao mês) e prestações mensais de R$ 50.
A prestação mensal de R$ 50 é utilizada para pagar os juros, e o restante é utilizado para pagar parte da
dívida remanescente. Assim, no primeiro mês você paga 1,5% de R$ 1.000 em juros. Isso dá R$ 15. Os
restantes R$ 35 são deduzidos do seu débito, o que o deixa com um débito de R$ 965,00. No mês se-
guinte você paga um juro de 1,5% sobre R$ 965,00, que dá R$ 14,48. Assim, você pode deduzir
R$ 35,52 (que é R$ 50 – R$ 14,48) da soma que deve.
Escreva um programa que lhe diga quantos meses você levará para pagar o que deve, assim como a soma
total paga em juros. Utilize um loop para calcular a soma paga em juros e o tamanho do débito a cada
mês. (Seu programa final não precisa fornecer a quantia paga mensalmente a título de juros, mas você
pode querer escrever uma versão preliminar do programa que apresente esses valores.) Utilize uma variável
para contar o número de iterações do loop e, portanto, o número de meses até que o débito seja zero.
Você pode querer utilizar outras variáveis também. O último pagamento pode ser inferior a R$ 50 se o
débito for menor, mas não se esqueça dos juros. Se você deve R$ 50, então sua prestação mensal de
R$ 50 não saldará seu débito, embora vá chegar perto disso. Os juros de um mês sobre R$ 50 são de
apenas 75 centavos.
Página em branco
CAPÍTULO

Fundamentos das Funções


Fundamentos das Funções

Capítulo 3Os Fundamentos das Funções


Os melhores perfumes vêm nos menores frascos.
Sabedoria popular

INTRODUÇÃO
Se você já programou em alguma outra linguagem, o conteúdo deste capítulo lhe será
bastante familiar. Mesmo assim, você deve dar uma olhada neste capítulo para ver a sinta-
xe e a terminologia de C++ para os fundamentos das funções. O Capítulo 4 contém o
material sobre funções em C++ que pode ser diferente das outras linguagens.
Pode-se considerar que um programa consiste em subpartes, como a obtenção dos da-
dos de entrada, o cálculo dos dados de saída e a exibição dos dados de saída. C++, como
a maioria das linguagens de programação, possui recursos para nomear e codificar cada
uma dessas partes em separado. Em C++, essas subpartes são chamadas funções. A maioria
das linguagens de programação possui funções ou algo similar, embora nem sempre sejam
chamadas por esse nome. Os termos procedimento, subprograma e método, dos quais você
já deve ter ouvido falar, significam essencialmente a mesma coisa que função. Em C++,
uma função pode retornar um valor (produzir um valor) ou pode executar alguma ação
sem retornar um valor, mas, quer a subparte forneça um valor ou não, ainda é chamada
de função em C++. Este capítulo apresenta os detalhes básicos sobre as funções em C++.
Antes de lhe dizer como escrever suas próprias funções, vamos lhe contar como utilizar al-
gumas das funções predefinidas do C++.

3.1 Funções Predefinidas


Não reinvente a roda.
Sabedoria popular

O C++ vem com bibliotecas de funções predefinidas que você pode utilizar em seus progra-
mas. Existem dois tipos de funções em C++; funções que retornam (produzem) um valor e fun-
ções que não retornam um valor. Funções que não retornam um valor são chamadas de funções
void. Primeiro falaremos das funções que retornam um valor, e depois das funções void.

■ FUNÇÕES PREDEFINIDAS QUE RETORNAM UM VALOR


Vamos usar a função sqrt para ilustrar como se utiliza uma função predefinida que
retorna um valor. A função sqrt calcula a raiz quadrada de um número. (A raiz quadrada
de um número é aquele número que, quando multiplicado por si mesmo, produzirá o
número com o qual você começou. Por exemplo, a raiz quadrada de 9 é 3, porque 32 é
igual a 9.) A função sqrt começa com um número, como 9.0, e calcula sua raiz quadra-
da, no caso 3.0. O valor da função começa com o que é chamado seu argumento. O va-
62 Fundamentos das Funções

lor que ela calcula é chamado de valor retornado. Algumas funções podem ter mais de um argumento, mas ne-
nhuma função pode ter mais de um valor retornado.
A sintaxe para utilizar funções em seu programa é simples. Para estabelecer que uma variável chamada aRaiz é
igual à raiz quadrada de 9.0, você pode utilizar a seguinte declaração de atribuição:
aRaiz = sqrt(9.0);

A expressão sqrt(9.0) é conhecida como uma chamada de função ou invocação de função. Um argumento
em uma função pode ser uma constante, como 9.0, uma variável, ou uma expressão mais complicada. Uma cha-
mada de função é uma expressão que pode ser usada como qualquer outra expressão. Por exemplo, o valor retor-
nado por sqrt é do tipo double; portanto, a linha seguinte é legal (embora talvez muito restrita):
bonus = sqrt(vendas)/10;

vendas e bonus são variáveis que normalmente seriam do tipo double. A chamada de função sqrt(vendas) é um
item único, como se estivesse entre parênteses. Assim, a declaração de atribuição acima é equivalente a
bonus = (sqrt(vendas))/10;

Você pode utilizar uma chamada de função onde seja legal utilizar uma expressão do tipo especificado pelo va-
lor retornado pela função.
O Painel 3.1 contém um programa completo que utiliza a função predefinida sqrt. O programa calcula o ta-
manho da maior casinha de cachorro que pode ser construída com a quantidade de dinheiro que o usuário está
disposto a gastar. O programa pede ao usuário uma quantia e, então, determina quantos pés* quadrados de área
podem ser adquiridos com essa quantia. O cálculo fornece a área da casinha de cachorro em pés quadrados. A
função sqrt fornece o comprimento de um lado do piso da casinha.
A biblioteca cmath contém a definição da função sqrt e diversas outras funções matemáticas. Se seu programa
utiliza uma função predefinida de alguma biblioteca, deve conter uma instrução de include que dê nome a essa
biblioteca. Por exemplo, o programa no Painel 3.1 utiliza a função sqrt e assim contém
#include <cmath>

Este programa em particular possui duas instruções de include. Não importa em que ordem estejam essas ins-
truções. As instruções de include foram discutidas no Capítulo 1.
As definições para funções predefinidas normalmente colocam essas funções no ambiente de nomes std e tam-
bém requerem a seguinte instrução de using, como ilustrado no Painel 3.1:
using namespace std;

Painel 3.1 Função predefinida que retorna um valor (parte 1 de 2)


1 //Calcula o tamanho de uma casinha de cachorro que possa ser adquirida
2 //dado o orçamento do usuário.
3 #include <iostream
4 #include <cmath>
5 using namespace std;

6 int main( )
7 {
8 const double COST_PER_SQ_FT = 10.50;
9 double orcamento, area, comprimentoLado;
10 cout << "Informe quanto quer gastar com a casinha de cachorro $";
11 cin >> budget;
12 area = budget/COST_PER_SQ_FT;
13 lengthSide = sqrt(area);
14 cout.setf(ios::fixed);
15 cout.setf(ios::showpoint);

* Um pé equivale a 30,48 cm. (N. do R.T.)


Funções Predefinidas 63

Painel 3.1 Função predefinida que retorna um valor (parte 2 de 2)


16 cout.precision(2);
17 cout << "Por um preço de $" << budget << endl
18 << "Posso lhe construir uma magnífica casinha de cachorro\n"
19 << "com " << lengthSide
20 << " pés em cada lado.\n";
21 return 0;
22 }
DIÁLOGO PROGRAMA-USUÁRIO
Informe quanto quer gastar com a casinha de cachorro $25.00
Por um preço de $25.00
Posso construir uma magnífica casinha de cachorro
com 1.54 pés em cada lado.

Normalmente, tudo o que você precisa fazer para utilizar uma biblioteca é colocar uma instrução de include
e outra de using para aquela biblioteca no arquivo com seu programa. Se tudo funcionar somente com essas ins-
truções, você não precisa se preocupar em fazer mais nada. Entretanto, para algumas bibliotecas em alguns siste-
mas você precisa fornecer instruções adicionais para o compilador ou executar explicitamente um programa de
ligação (linker) para estabelecer a ligação com a biblioteca. Os detalhes variam de um sistema para outro; você terá
de verificar seu manual ou consultar um especialista para ver exatamente o que é necessário.

FUNÇÕES QUE RETORNAM UM VALOR


Para uma função que retorna um valor, uma chamada de função é uma expressão que consiste no nome da função seguido por
argumentos entre parênteses. Se houver mais de um argumento, os argumentos são separados por vírgulas. Se a chamada da
função retornar um valor, então a chamada da função é uma expressão que pode ser usada como qualquer outra expressão do
tipo especificado para o valor retornado pela função.
SINTAXE
Nome_Da_Funcao(Lista_De_Argumentos)
em que Lista_De_Argumentos é uma lista de argumentos separados por vírgulas:
Argumento_1, Argumento_2, . . . , Argumento_Final
EXEMPLOS
lado = sqrt(area);
cout << "2.5 elevado a 3.0 é "
<< pow(2.5, 3.0);

Algumas funções predefinidas são descritas no Painel 3.2. Mais funções predefinidas são descritas no Apêndice 4.
Observe que as funções de valor absoluto abs e labs estão na biblioteca com o arquivo de cabeçalho cstdlib, as-
sim qualquer programa que utilizar qualquer uma dessas funções deve conter a seguinte instrução:
#include <cstdlib>

Observe também que existem três funções de valor absoluto. Se você quiser produzir o valor absoluto de um
número de tipo int, utilize abs; se quiser produzir o valor absoluto de um número do tipo long, utilize labs; e
se quiser produzir o valor absoluto de um número de tipo double, utilize fabs. Para complicar ainda
mais as coisas, abs e labs estão na biblioteca com o arquivo de cabeçalho cstdlib, enquanto fabs está na biblio-
teca com o arquivo de cabeçalho cmath. fabs é uma abreviação de floating-point absolute value (valor absoluto de
ponto flutuante). Lembre-se de que os números com uma fração após o ponto decimal (ou vírgula decimal),
como os números de tipo double, geralmente são chamados de números de ponto flutuante.
Outro exemplo de uma função predefinida é pow, que está na biblioteca com o arquivo de cabeçalho cmath. A
função pow pode ser usada para efetuar a potenciação em C++. Por exemplo, se você quiser fixar uma variável re-
sultado como igual a xy, pode utilizar a forma:
resultado = pow (x, y);
64 Fundamentos das Funções

Assim, as três linhas seguintes de código apresentarão como saída na tela o número 9.0, porque (3.0)2.0 é 9.0:
double resultado, x = 3.0, y = 2.0;
resultado = pow(x, y);
cout << resultado;

Painel 3.2 Algumas funções predefinidas


NOME DESCRIÇÃO TIPO DE TIPO OU EXEMPLO VALOR BIBLIOTECA
ARGUMENTOS VALOR HEADER
RETORNADO
sqrt Raiz quadrada double double sqrt(4.0) 2.0 cmath

pow Potência double double pow(2.0,3.0) 8.0 cmath

abs Valor int int abs(-7) 7 cstdlib


absoluto abs(7) 7
para int
labs Valor long long labs(-70000) 70000 cstdlib
absoluto labs(70000) 70000
para long
fabs Valor double double fabs(-7.5) 7.5 cmath
absoluto fabs(7.5) 7.5
para double
ceil Ceiling (arredonda double double ceil(3.2) 4.0 cmath
para próximo inteiro) ceil(3.9) 4.0

floor Floor (arredonda double double floor(3.2) 3.0 cmath


para inteiro anterior) floor(3.9) 3.0

exit Finaliza o programa int void exit(1); Nenhum cstdlib

rand Número aleatório Nenhum int rand( ) Varia cstdlib

srand Estabelece a unsigned int void srand(42); Nenhum cstdlib


semente para rand

Todas essas funções predefinidas requerem using namespace std, além de uma instrução de include.
Observe que a chamada anterior a pow retorna 9.0, não 9. A função pow sempre retorna um valor de tipo
double, não de tipo int. Observe também que a função pow requer dois argumentos. Uma função pode ter qual-
quer número de argumentos. Além disso, toda posição de argumento possui um tipo específico, e o argumento
utilizado em uma chamada de função deve ser desse tipo. Em muitos casos, se você utiliza um argumento de tipo
errado, o C++ realizará conversões automáticas de tipo para você. Entretanto, os resultados podem não ser os que
você esperava. Quando se chama uma função, devem-se usar argumentos do tipo específico para aquela função.
Uma exceção a isso é a conversão automática de argumentos do tipo int para o tipo double. Em muitas situa-
ções, inclusive chamadas à função pow, pode-se utilizar com segurança um argumento do tipo int (ou outro tipo
inteiro) quando um argumento de tipo double (ou outro tipo de ponto flutuante) é especificado.

FUNÇÕES void
Uma função void executa alguma ação, mas não retorna um valor. Para uma função void, uma chamada de função é um co-
mando formado pelo nome da função seguido por argumentos entre parênteses e terminado por um ponto-e-vírgula. Se houver
mais de um argumento, os argumentos são separados por vírgulas. Para uma função void, uma invocação de função (chamada
de função) é um comando que pode ser usado como qualquer outro comando em C++.
SINTAXE
Nome_Da_Funcao(Lista_De_Argumentos)
em que Lista_De_Argumentos é uma lista de argumentos separados por vírgulas:
Argumento_1, Argumento_2, . . . , Argumento_Final
EXEMPLO
exit(1);
Funções Predefinidas 65

Muitas implementações de pow possuem uma restrição quanto aos argumentos que podem ser utilizados. Nes-
sas implementações, se o primeiro argumento de pow é negativo, o segundo argumento deve ser um número intei-
ro. Pode ser mais fácil e seguro utilizar pow apenas quando o primeiro argumento é não-negativo.

■ FUNÇÕES void PREDEFINIDAS


Uma função void executa alguma ação, mas não retorna um valor. Como executa uma ação, uma invocação
de função void é um comando. A chamada de função para uma função void é escrita de maneira similar à de
uma chamada de função para uma função que retorna um valor, a não ser pelo fato de ser terminada por um
ponto-e-vírgula e usada como um comando e não como uma expressão. Funções void predefinidas são tratadas da
mesma forma que as funções predefinidas que retornam um valor. Assim, para utilizar uma função void predefini-
da, seu programa deve ter uma instrução de include que dê o nome da biblioteca que define a função.
Por exemplo, a função exit é definida na biblioteca cstdlib e, assim, um programa que utiliza essa função
deve conter as seguintes linhas no início (ou próximo do início) do arquivo:
#include <cstdlib>
using namespace std;
A linha seguinte é uma amostra de invocação (amostra de chamada) da função exit:
exit(1);

FUNÇÃO exit
A função exit é uma função void predefinida que requer um argumento de tipo int. Assim, uma invocação à função exit é um
comando escrito da seguinte forma:
exit(Valor_Inteiro);
Quando a função exit é invocada (ou seja, quando o comando acima é executado), o programa termina imediatamente. Qualquer
Valor_Inteiro pode ser usado, mas, por convenção, 1 é usado para uma chamada a exit que seja provocada por um erro, e 0 é
usado em outros casos.
A definição da função exit está na biblioteca cstdlib e coloca a função exit no ambiente de nomes std (namespace std). Por-
tanto, qualquer programa que utilizar a função exit deve conter as seguintes instruções:
#include <cstdlib>
using namespace std;

Uma invocação à função exit encerra o programa imediatamente. O Painel 3.3 contém um programa que de-
monstra a função exit.

Painel 3.3 Chamada de função para uma função void predefinida (parte 1 de 2)

1 #include <iostream>
2 #include <cstdlib> Este é apenas um exemplo fictício.
3 using namespace std; Produziria o mesmo efeito se você
omitisse estas linhas.
4 int main( )

/
5 {
6 cout << "Olá. Fora!\n";
7 exit(1);

8 cout << "Este comando é inútil,\n"


9 << "porque nunca será executado.\n"
10 << "Isto é só um programa fictício para exemplificar exit.\n";

11 return 0;
12 }
66 Fundamentos das Funções

Painel 3.3 Chamada de função para uma função void predefinida (parte 2 de 2)
DIÁLOGO PROGRAMA-USUÁRIO
Olá. Fora!

Observe que a função exit possui um argumento, que é de tipo int. O argumento é fornecido para o sistema opera-
cional. No que se refere ao seu programa em C++, você pode utilizar qualquer valor int como argumento, mas, por con-
venção, 1 é utilizado para uma chamada a exit que seja provocada por um erro, e 0 é utilizado nos outros casos.
Uma função void pode ter qualquer número de argumentos. Os detalhes a respeito dos argumentos para fun-
ções void são os mesmos que para as funções que retornam um valor. Em particular, se você utilizar um argu-
mento do tipo errado, em muitos casos o C++ realizará a conversão automática de tipos para você. Entretanto, os
resultados podem não ser os que você esperava.

1 Exercícios de Autoteste 1

1. Determine o valor de cada uma das seguintes expressões aritméticas.


sqrt(16.0) sqrt(16) pow(2.0, 3.0)
pow(2, 3) pow(2.0, 3) pow(1.1, 2)
abs(3) abs(-3) abs(0)
fabs(-3.0) fabs(-3.5) fabs(3.5)
ceil(5.1) ceil(5.8) floor(5.1)
floor(5.8) pow(3.0, 2)/2.0 pow(3.0, 2)/2
7/abs(-2) (7 + sqrt(4.0))/3.0 sqrt(pow(3, 2))
2. Converta cada uma das seguintes expressões matemáticas em uma expressão aritmética em C++.
a. √

x +y b. xy + 7 
c.√area +fudge
d. √

time + tide 
√ 
b2

−4ac
e.−b + f. |x − y |
nobody 2a
3. Escreva um programa completo em C++ para calcular e apresentar como saída a raiz quadrada dos nú-
meros inteiros de 1 a 10.
4. Qual é a função do argumento int à função void exit?

■ GERADOR DE NÚMEROS ALEATÓRIOS


Gerador de números aleatórios é uma função que retorna um número "aleatoriamente escolhido". É diferente
das funções que já vimos até agora, no sentido de que o valor retornado não é determinado por argumentos (que
normalmente não existem), e sim por algumas condições globais. Como é possível pensar-se no valor retornado
como sendo um número aleatório, pode-se utilizar um gerador de números aleatórios para simular eventos aleató-
rios, como o resultado do lançamento de um dado ou moeda. Além de simular jogos de azar, os geradores de nú-
meros aleatórios podem ser usados para simular coisas que, estritamente falando, podem não ser aleatórias, mas
que parecem ser, como o intervalo de tempo entre a chegada de carros em um posto de pedágio.
A biblioteca C++ com o arquivo de cabeçalho <cstdlib> contém uma função de números aleatórios chamada
rand. Essa função não possui argumentos. Quando seu programa invoca rand, a função retorna um inteiro no intervalo
entre 0 e RAND_MAX, inclusive. (O número gerado pode ser igual a 0 ou a RAND_MAX.) RAND_MAX é uma constante inteira
definida cuja definição também está na biblioteca com o arquivo de cabeçalho <cstdlib>. O valor exato de RAND_MAX
depende do sistema, mas será no mínimo 32767 (o máximo inteiro positivo de dois bytes). Por exemplo, as linhas se-
guintes apresentam como saída uma lista de dez números "aleatórios" no intervalo entre 0 e RAND_MAX:
int i;
for (i = 0; i < 10; i++)
cout << rand( ) << endl;

É mais provável que você queira um número aleatório em algum intervalo menor, como o intervalo entre 0 e
10. Para assegurar que o valor esteja no intervalo entre 0 e 10 (incluindo os extremos), você pode usar
rand( ) % 11
Funções Predefinidas 67

Isso é chamado de ajuste de escala scaling (colocar em escala).* As seguintes linhas apresentam como saída dez
inteiros "aleatórios" no intervalo entre 0 e 10 (inclusive):
int i;
for (i = 0; i < 10; i++)
cout << (rand( ) % 11) << endl;

Geradores de números aleatórios, como a função rand, não geram números verdadeiramente aleatórios. (Por
isso as aspas que utilizamos em "aleatório".) Uma seqüência de chamadas à função rand (ou a quase todos os ge-
radores de números aleatórios) produzirá uma seqüência de números (os valores retornados por rand) que parecem
ser aleatórios. Entretanto, se você pudesse fazer com que o computador voltasse ao estado anterior, quando a se-
qüência de chamadas a rand se iniciou, você obteria a mesma seqüência de "números aleatórios". Números que
parecem ser aleatórios, mas que, na realidade, não são, como uma seqüência de números gerada por chamadas a
rand, são chamados de números pseudo-aleatórios.
Uma seqüência de números pseudo-aleatórios geralmente é determinada por um número conhecido como se-
mente. Se você iniciar o gerador de números aleatórios com a mesma semente, todas as vezes a mesma seqüência
(aparentemente aleatória) de números será produzida. Você pode utilizar a função srand para fixar a semente para
a função rand. A função void srand requer um argumento inteiro (positivo), que é a semente. Por exemplo, as li-
nhas seguintes apresentarão como saída duas seqüências idênticas de dez números pseudo-aleatórios:
int i;
srand(99);
for (i = 0; i < 10; i++)
cout << (rand( ) % 11) << endl;
srand(99);
for (i = 0; i < 10; i++)
cout << (rand( ) % 11) << endl;
Não há nada de especial com o número 99, fora o fato de havermos utilizado o mesmo número para ambas as
chamadas a srand.
Observe que a seqüência de números pseudo-aleatórios produzida por uma determinada semente pode depen-
der do sistema. Caso seja reexecutada em um sistema diferente com a mesma semente, a seqüência de números
pseudo-aleatórios pode ser diferente nesse sistema. Entretanto, desde que você esteja no mesmo sistema utilizando
a mesma implementação de C++, a mesma semente produzirá a mesma seqüência de números pseudo-aleatórios.

NÚMEROS PSEUDO-ALEATÓRIOS
A função rand não requer argumentos e retorna um inteiro pseudo-aleatório no intervalo entre 0 e RAND_MAX (inclusive). A fun-
ção void srand requer um argumento, que é a semente para o gerador de números aleatórios rand. O argumento à srand é do
tipo unsigned int, então o argumento deve ser não-negativo. As funções rand e srand, assim como a constante definida
RAND_MAX, são definidas na biblioteca cstdlib, e os programas que as utilizam devem conter as seguintes instruções:
#include <cstdlib>
using namespace std;

Esses números pseudo-aleatórios são suficientemente próximos de números aleatórios verdadeiros para a maio-
ria das aplicações. Na realidade, muitas vezes eles são preferíveis aos números aleatórios verdadeiros. Um gerador
de números pseudo-aleatórios possui uma grande vantagem em relação a um gerador de números aleatórios verda-
deiros: a seqüência de números que ele produz pode ser repetida. Se executado duas vezes com o mesmo valor de
semente, produzirá a mesma seqüência de números. Isso pode ser muito útil, em muitos casos. Quando um erro
é descoberto e consertado, o programa pode ser reexecutado com a mesma seqüência de números pseudo-aleató-
rios que apresentaram o erro. De forma similar, uma execução particularmente interessante do programa pode ser
repetida, desde que um gerador de números pseudo-aleatórios seja utilizado. Com um gerador de números verda-
deiramente aleatórios cada execução do programa provavelmente será diferente.

* O número 11 é chamado de fator de escala. (N. do R.T.)


68 Fundamentos das Funções

O Painel 3.4 mostra um programa que utiliza o gerador de números aleatórios para "prever" o clima. Nesse
caso, a previsão é aleatória, mas algumas pessoas a consideram tão boa quanto qualquer previsão meteorológica.
(As previsões meteorológicas podem, na realidade, ser bastante precisas, mas este programa é apenas um jogo para
ilustrar os números pseudo-aleatórios.)
Observe que, no Painel 3.4, o valor da semente usado para o argumento de srand é o mês multiplicado pelo
dia. Assim, se o programa é reexecutado e a mesma data é fornecida, a mesma previsão será dada. (Claro que se
trata de um programa bastante simples. A previsão para o dia depois do dia 14 pode ou não ser a mesma que a
do dia 15, mas esse programa serve como um exemplo simples.)
Probabilidades geralmente são representadas como um número de ponto flutuante entre 0.0 e 1.0. Suponha
que você queira uma probabilidade aleatória em vez de um inteiro aleatório. Isso pode ser produzido por outra
forma de ajuste de escala. A linha seguinte gera um valor de ponto flutuante pseudo-aleatório entre 0.0 e 1.0:
rand( )/static_cast <double> (RAND_MAX)

A conversão (cast) de tipo é feita para que obtenhamos uma divisão de ponto flutuante em vez de uma divisão
de inteiros.

Painel 3.4 Função utilizando um gerador de número aleatório (parte 1 de 2)


1 #include <iostream>
2 #include <cstdlib>
3 using namespace std;

4 int main( )
5 {
6 int month, day;
7 cout << "Bem-vindo ao seu programa de previsão do tempo.\n"
8 << "Informe a data de hoje, utilizando dois inteiros para o mês e para o dia:\n";
9 cin >> month;
10 cin >> day;
11 srand(month*day);
12 int prediction;
13 char ans;
14 cout << "Previsão para hoje:\n";

-
15 do
16 {
17 prediction = rand( ) % 3;
18 switch (prediction)
19 {
20 case 0:
21 cout << "O dia será ensolarado!!\n";
22 break;
23 case 1:
24 cout << "O dia será nublado.\n";
25 break;
26 case 2:
27 cout << "Vai haver fortes chuvas!\n";
28 break;
29 default:
30 cout << "Programa de previsão do tempo não está funcionando corretamente.\n";
31 }
32 cout << "Quer a previsão para o dia seguinte?(y/n): ";
33 cin >> ans;
34 } while (ans == ’y’ || ans == ’Y’);
35 cout << "Este é o final do seu programa de previsão do tempo de 24 horas.\n";
36 return 0;
37 }
Funções Definidas pelo Programador 69

Painel 3.4 Função utilizando um gerador de número aleatório (parte 2 de 2)


DIÁLOGO PROGRAMA-USUÁRIO
Bem-vindo ao seu programa de previsão do tempo.
Informe a data de hoje utilizando dois inteiros para o mês e para o dia:
2 14
Previsão para hoje:
O dia será nublado.
Quer a previsão para o dia seguinte? (s/n): s
O dia será nublado.
Quer a previsão para o dia seguinte? (s/n): s
Vai haver fortes chuvas!
Quer a previsão para o dia seguinte? (s/n): s
Vai haver fortes chuvas!
Quer a previsão para o dia seguinte? (s/n): s
O dia será ensolarado!!
Quer a previsão para o dia seguinte? (s/n): n
Este é o final do seu programa de previsão do tempo de 24 horas.

1 Exercícios de Autoteste 1

5. Forneça uma expressão para produzir um número inteiro pseudo-aleatório no intervalo entre 5 e 10 (in-
clusive).
6. Escreva um programa completo que peça ao usuário uma semente e depois apresente uma lista de dez
números aleatórios baseados nessa semente. Os números devem ser de ponto flutuante no intervalo
entre 0.0 e 1.0 (inclusive).

3.2 1 Funções Definidas pelo Programador


Um terno feito sob medida sempre cai melhor do que um de marca.
Meu tio, alfaiate

A seção anterior explicou como utilizar funções predefinidas. Esta seção lhe dirá como definir suas próprias
funções.

■ DEFININDO FUNÇÕES QUE RETORNAM UM VALOR


Você pode definir suas próprias funções, no mesmo arquivo da parte principal do seu programa (main) ou em
um arquivo separado, de modo que as funções possam ser utilizadas por vários programas diferentes.
A definição é a mesma em ambos os casos, mas por enquanto vamos assumir que a definição da função esteja
no mesmo arquivo que a parte main do seu programa. Esta subseção trata apenas de funções que retornam um va-
lor. Uma subseção posterior lhe dirá como definir funções void.
O Painel 3.5 contém uma amostra de definição de função que é um programa completo demonstrando uma
chamada à função. A função se chama custoTotal e requer dois argumentos — o preço de um item e o número
de itens adquiridos. A função retorna o custo total, incluindo imposto sobre vendas, para todos os itens com o
preço especificado. A função é chamada da mesma forma que uma função predefinida. A definição da função que
o programador deve escrever é um pouco mais complicada.
A descrição da função é fornecida em duas partes. A primeira parte é chamada declaração de função ou protótipo
de função. A linha seguinte é a declaração de função (protótipo de função) da função definida no Painel 3.5:
double custoTotal(int numeroParametro, double precoParametro);
70 Fundamentos das Funções

A primeira palavra em uma declaração de função especifica o tipo do valor retornado pela função. Assim, para
a função custoTotal, o tipo do valor retornado é double. A seguir, a declaração de função diz a você o nome da
função; nesse caso, custoTotal. A declaração de função diz a você (e ao compilador) tudo o que você precisa sa-
ber para escrever e utilizar uma chamada de função. Diz a você quantos argumentos a função requer e de que
tipo; nesse caso, a função custoTotal requer dois argumentos, o primeiro de tipo int e o segundo de tipo dou-
ble. Os identificadores numeroParametro e precoParametro são chamados de parâmetros formais, ou simplesmen-
te parâmetros. Um parâmetro formal é utilizado como um tipo de espaço em branco, ou "guardador" de lugar,
para ficar no lugar do argumento. Quando escreve uma declaração de função, você não sabe o que vão ser os ar-
gumentos, então utiliza os parâmetros formais no lugar dos argumentos. Nomes de parâmetros formais podem ser
quaisquer identificadores válidos. Observe que uma declaração de função termina com um ponto-e-vírgula.
Embora a declaração de função lhe revele tudo o que precisa saber para escrever uma chamada de função, não
lhe conta que valor será retornado como saída. O valor retornado é determinado pela definição de função. No
Painel 3.3 a definição de função está nas linhas 2 a 30 do programa. Uma definição de função descreve como a
função calcula o valor que retorna como saída. Uma definição de função consiste em um cabeçalho de função se-
guido por um corpo de função. Um cabeçalho de função é escrito de forma similar à declaração de função, a não
ser pelo fato de o cabeçalho não ter um ponto-e-vírgula no final. O valor retornado é determinado pelos coman-
dos no corpo da função.
O corpo da função segue o cabeçalho e completa a definição da função. O corpo da função consiste em de-
clarações e comandos executáveis entre chaves. Assim, o corpo da função é exatamente como o corpo da parte
main de um programa. Quando uma função é chamada, os valores do argumento são conectados aos parâmetros
formais e os comandos do corpo da função são executados. O valor retornado pela função é determinado quando
a função executa um comando return. (Os detalhes dessa "conexão" serão discutidos no Capítulo 4.)

Painel 3.5 Função utilizando um gerador de número aleatório (parte 1 de 2)


1 #include <iostream>
2 using namespace std;

3 double totalCost(int numberParameter, double priceParameter);


4 //Calcula o custo total, inclusive 5% de imposto sobre a venda,
5 //em numberParameter itens a um custo de priceParameter cada. Declaração de função;
também chamada de
6 int main( ) protótipo de função.
7 {
8 double price, bill;
9 int number;

10 cout << "Informe o número de itens adquiridos: ";


11 cin >> number;
12 cout << "Informe o preço por item $";
13 cin >> price;

14 bill = totalCost(number, price); Chamada de função

15 cout.setf(ios::fixed);
16 cout.setf(ios::showpoint);
17 cout.precision(2);
18 cout << number << " itens a "
19 << "$" << price << " cada.\n"
20 << "A soma final, incluindo impostos, é $" << bill
21 << endl;

22 return 0;
23 }
Funções Definidas pelo Programador 71

Painel 3.5 Função utilizando um gerador de número aleatório (parte 2 de 2)


Cabeçalho de função

24 double totalCost(int numberParameter, double priceParameter)


25 {
26 const double TAXRATE = 0.05; //5% de imposto sobre a venda
27 double subtotal; Corpo da
função Definição de função
28 subtotal = priceParameter * numberParameter;
29 return (subtotal + subtotal*TAXRATE);
30 }

DIÁLOGO PROGRAMA-USUÁRIO
Informe o número de itens adquiridos: 2
Informe o preço por item: $10.10
2 itens a $10.10 cada.
A soma final, incluindo impostos, é $21.21

Um comando return é formado pela palavra-chave return seguida por uma expressão. A definição de função
no Painel 3.5 contém o seguinte comando return:
return (subtotal + subtotal*IMPOSTO)

Quando esse comando return é executado, o valor da seguinte expressão é retornado como o valor da chama-
da de função:
(subtotal + subtotal*IMPOSTO)

Os parênteses não são necessários. O programa será executado da mesma forma se os parênteses forem omiti-
dos. Entretanto, com expressões mais longas, os parênteses tornam o comando return mais legível. Para obter
maior consistência, alguns programadores aconselham o uso desses parênteses mesmo com expressões simples. Na
definição de função no Painel 3.3 não há nenhum comando após o comando return, mas, se houvesse, o comando
não seria executado. Quando um comando return é executado, a chamada de função termina.
Observe que o corpo da função pode conter quaisquer comandos em C++ e que os comandos serão executa-
dos quando a função for chamada. Assim, uma função que retorna um valor pode executar qualquer outra ação
além de retornar um valor. Na maioria dos casos, todavia, o principal objetivo de uma função que retorna um va-
lor é retornar esse valor.
Ou a definição de função completa ou a declaração de função (protótipo de função) devem aparecer no códi-
go antes que a função seja chamada. O mais comum é a declaração de função e a parte main do programa apare-
cerem em um ou mais arquivos, com a declaração de função antes da parte main, e a definição da função aparecer
em outro arquivo. Não abordamos ainda a questão da divisão de um programa em mais de um arquivo, e por
isso colocaremos as definições de função depois da parte main do programa. Se a definição de função completa for
colocada antes da parte main do programa, a declaração da função pode ser omitida.

■ FORMA ALTERNATIVA PARA DECLARAÇÕES DE FUNÇÃO


Não é preciso listar os nomes de parâmetros formais em uma declaração de função (protótipo de função). As
duas declarações de função seguintes são equivalentes:
double custoTotal(int numeroParametro, double precoParametro);

e
double custoTotal(int, double);

Normalmente utilizamos a primeira forma para nos referir aos parâmetros formais no comentário que acompa-
nha a declaração de função. Entretanto, muitas vezes você encontrará a segunda forma em manuais.
72 Fundamentos das Funções

Esta forma alternativa se aplica apenas a declarações de função. Uma definição de função deve sempre listar os
nomes dos parâmetros formais.

ARGUMENTOS NA ORDEM ERRADA


Quando uma função é chamada, o computador substitui o primeiro parâmetro formal pelo primeiro argu-
mento, o segundo parâmetro formal pelo segundo argumento e assim por diante. Embora o computador ve-
rifique o tipo de cada argumento, ele não verifica a coerência. Se você confundir a ordem dos argumentos, o
programa não vai saber o que você pretendeu fazer. Se houver uma violação de tipo devido a um argumen-
to de tipo errado, você receberá uma mensagem de erro. Se não houver violação de tipo, seu programa pro-
vavelmente será executado normalmente, mas o valor retornado pela função será incorreto.

USO DOS TERMOS PARÂMETRO E ARGUMENTO


O uso dos termos parâmetro formal e argumento que adotamos neste livro é consistente com o uso comum,
mas muitas vezes os termos parâmetro e argumento são utilizados de forma invertida. Quando vir os termos
parâmetro e argumento, você deve determinar seu significado exato a partir do contexto. Muitas pessoas
utilizam o termo parâmetro tanto para o que chamamos de parâmetros formais quanto para o que chama-
mos de argumentos. Outras adotam o termo argumento tanto para o que chamamos de parâmetros formais
quanto para o que chamamos argumentos. Não espere consistência na forma como as pessoas utilizam es-
ses termos. (Neste livro, às vezes empregamos o termo parâmetro significando parâmetro formal, mas isto é
mais uma abreviação do que uma verdadeira inconsistência.)

■ FUNÇÕES CHAMANDO FUNÇÕES


Um corpo de função pode conter chamada para outra função. A situação para esses tipos de chamadas de fun-
ção é a mesma que se a chamada de função houvesse ocorrido na parte main do programa; a única restrição é que
a declaração de função (ou definição de função) deve aparecer antes de a função ser usada. Se você houver mon-
tado seu programa conforme nossas orientações, isso ocorrerá automaticamente, já que todas as declarações de
função vêm antes da parte main do programa e todas as definições de função vêm depois da parte main do progra-
ma. Embora seja possível incluir uma chamada de função dentro da definição de outra função, não se pode colo-
car a definição de uma função dentro do corpo de outra definição de função.

FUNÇÃO ARREDONDADORA
A tabela de funções predefinidas (Painel 3.2) não inclui qualquer função para o arredondamento de um nú-
mero. As funções ceil e floor são quase, mas não completamente, funções arredondadoras. A função ceil
sempre retorna o próximo número inteiro maior (ou seu argumento, se for um número inteiro). Assim,
ceil(2.1) retorna 3.0, não 2.0. A função floor sempre retorna o próximo número inteiro menor que o ar-
gumento, ou igual a ele. Assim, floor(2.9) retorna 2.0, não 3.0. Felizmente, é fácil definir uma função que
faça um verdadeiro arredondamento. A função é definida em Painel 3.6. A função round arredonda seu argu-
mento para o inteiro mais próximo. Por exemplo, round(2.3) retorna 2 e round (2.6) retorna 3.
Para verificar se round funciona corretamente, vamos utilizar alguns exemplos. Considere round(2.4). O va-
lor retornado é o seguinte (convertido em um valor int):
floor(2.4 + 0.5)
que é floor(2.9), ou 2.0. Na verdade, para qualquer número que seja maior ou igual a 2.0 e estritamente
menor que 2.5, esse número mais 0.5 será menor que 3.0 e, assim, floor aplicado a esse número mais 0.5
retornará 2.0. Assim, round aplicado a qualquer número maior ou igual a 2.0 e estritamente menor que 2.5
apresentará como resultado 2. (Como a declaração de função para round especifica que o tipo para o valor
retornado é int, convertemos o tipo do valor calculado em int.)
Agora considere números maiores ou iguais a 2.5; por exemplo, 2.6. O valor retornado pela chamada
round(2.6) é o seguinte (convertido em valor int):
floor(2.6 + 0.5)
que é floor(3.1) ou 3.0. Na realidade, para qualquer número que seja maior que 2.5 e menor ou igual a
3.0, esse número mais 0.5 será maior que 3.0. Assim, round chamada com qualquer número que seja maior
que 2.5 e menor que 3.0 apresentará como resultado 3.
Desse modo, round funciona corretamente para todos os argumentos entre 2.0 e 3.0. É óbvio que não exis-
te nada de especial em relação aos argumentos entre 2.0 e 3.0. Um argumento similar se aplica a todos os
números não-negativos. Portanto, round funciona corretamente para todos os argumentos não-negativos.
Funções Definidas pelo Programador 73

Painel 3.6 Função round


1 #include <iostream>
Teste do programa
2 #include <cmath>
de função round
3 using namespace std;

4 int round(double number);


5 //Assumes number >= 0.
6 //Returns number rounded to the nearest integer.

7 int main( )
8 {
9 double doubleValue;
10 char ans;

11 do
12 {
13 cout << "Forneça um valor double: ";
14 cin >> doubleValue;
15 cout << "Arredondado, esse número é " << round(doubleValue) << endl;
16 cout << "Outra vez? (s/n): ";
17 cin >> ans;
18 }while (ans == ’s’ || ans == ’s’);
19 cout << "Fim do teste.\n";

20 return 0;
21 }

22 //Uses cmath:
23 int round(double number)
24 {
25 return static_cast<int>(floor(number + 0.5));
26 }

DIÁLOGO PROGRAMA-USUÁRIO
Forneça um valor double: 9.6
Arredondado, esse número é 10
Outra vez? (s/n): s
Forneça um valor double: 2.49
Arredondado, esse número é 2
Outra vez? (s/n): n
Fim do teste.

1 Exercícios de Autoteste 1

7. Qual é a saída produzida pelo seguinte programa?


#include <iostream>
using namespace std;
char misterio(int primeiroParametro, int segundoParametro);
int main( )
{
cout << misterio(10, 9) << "ato\n";
return 0;
}
char misterio(int primeiroParametro, int segundoParametro);
{
74 Fundamentos das Funções

Exercícios de Autoteste (continuação)


if (primeiroParametro >= segundoParametro)
return ’G’’;
else
return ’R’;
}
8. Escreva uma declaração de função (protótipo de função) e uma definição de função para uma função
que necessite de três argumentos, todos de tipo int, e que forneça a soma desses três argumentos.
9. Escreva uma declaração e uma definição de função para uma função que necessite de um argumento de
tipo double. A função retorna o valor de caractere ’P’, se seu argumento for positivo, e ’N’, se seu ar-
gumento for zero ou negativo.
10. Pode uma definição de função aparecer dentro do corpo de outra definição de função?
11. Liste as similaridades e diferenças entre como se invoca (chama) uma função predefinida (ou seja, de bi-
blioteca) e uma função definida pelo usuário.

■ FUNÇÕES QUE RETORNAM UM VALOR BOOLEANO


O tipo retornado por uma função pode ser bool. Uma chamada para uma função assim retorna um dos valo-
res true ou false e pode ser usada em qualquer lugar onde uma expressão booleana seja permitida. Por exemplo,
pode ser utilizada em uma expressão booleana para controlar um comando if-else ou um loop. Isso pode, mui-
tas vezes, tornar um programa mais legível. Por meio de uma declaração de função, associa-se uma expressão boo-
leana complexa a um nome com significado. Por exemplo, o comando
if (((taxa >= 10) && (taxa < 20)) || (taxa == 0))
{
...
}
pode ser escrito
if (apropriada(taxa))
{
...
}
desde que a seguinte função tenha sido definida:
bool apropriada(int taxa)
{
return (((taxa >= 10) && (taxa < 20)) || (taxa == 0));
}

1 Exercícios de Autoteste 1

12. Escreva uma definição de função para uma função chamada emOrdem que requer três argumentos de tipo
int. A função apresenta como saída true se os três argumentos estiverem em ordem ascendente; caso
contrário, apresenta como saída false. Por exemplo, tanto emOrdem(1, 2, 3) quanto emOrdem(1, 2,
2) apresentam true como saída, enquanto emOrdem(1, 3, 2) apresenta false como saída.
13. Escreva uma definição de função para uma função chamada par que requer um argumento de tipo int e
retorna um valor bool. A função apresenta true como saída se seu único argumento for um número
par; caso contrário, apresenta false como saída.
14. Escreva uma definição de função para uma função chamada digito que requer um argumento de tipo
char e retorna um valor bool. A função apresenta true como saída se o argumento for um dígito deci-
mal; caso contrário, apresenta false como saída.

■ DEFININDO FUNÇÕES void


Em C++, uma função void é definida de forma similar à das funções que retornam um valor. Por exemplo, a
função seguinte é uma função void que apresenta como saída o resultado de um cálculo que converte uma tem-
peratura expressa em graus Fahrenheit para graus Celsius. O verdadeiro cálculo é feito em outra parte do progra-
ma. Esta função void implementa apenas a subtarefa de apresentar os resultados do cálculo.
Funções Definidas pelo Programador 75

void mostraResultados(double fGraus, double cGraus)


{
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(1);
cout << fGraus
<< " graus Fahrenheit equivalem a\n"
<< cGraus << " graus Celsius.\n";
}

Como a definição de função acima ilustra, há apenas duas diferenças entre uma definição de função para uma
função void e outra para uma função que forneça um valor. Uma diferença é que nós utilizamos a palavra-chave
void em lugar de especificarmos o tipo do valor a ser retornado. Isso diz ao compilador que essa função não re-
tornará nenhum valor. O nome void (vazio, em inglês) é empregado como uma forma de dizer "nenhum valor é
retornado por esta função". A segunda diferença é que uma definição de função void não requer um comando
return. A execução da função termina quando o último comando no corpo da função é executado.
Uma chamada de função void é um comando executável. Por exemplo, a função mostraResultados acima
pode ser calculada da seguinte forma:
mostraResultados(32.5, 0.3);

Se esse comando fosse executado em um programa, faria com que as seguintes linhas surgissem na tela:
32.5 graus Fahrenheit equivalem a
0.3 graus Celsius.

Observe que a chamada de função termina com um ponto-e-vírgula, que diz ao compilador que a chamada de
função é um comando executável.
Quando uma função void é chamada, os parâmetros formais são substituídos pelos argumentos, e os coman-
dos no corpo da função são executados. Por exemplo, uma chamada à função void mostraResultados, que apre-
sentamos anteriormente nesta seção, fará com que algumas linhas sejam escritas na tela. Uma forma de pensar em
uma chamada a uma função void é imaginar que o corpo da função é copiado para dentro do programa no lugar
da chamada de função. Quando a função é chamada, os parâmetros formais são substituídos pelos argumentos e,
então, é exatamente como se o corpo da função fossem linhas do programa. (O Capítulo 4 descreve o processo de
substituição de parâmetros formais por argumentos em detalhe. Até lá, utilizaremos apenas exemplos simples que
sejam suficientemente claros sem uma descrição formal do processo de substituição.)
É perfeitamente legal, e às vezes útil, haver uma função sem argumentos. Nesse caso, simplesmente não há parâme-
tros formais listados na declaração de função e nenhum argumento é utilizado quando a função é chamada. Por exem-
plo, a função void inicializaTela, definida a seguir, apenas imprime um comando de nova linha na tela:
void inicializaTela( )
{
cout << endl;
}

Se seu programa inclui a seguinte chamada a essa função como seu primeiro comando executável, a saída do
programa executado anteriormente será separada da saída do seu programa:
inicializaTela( );

Observe que, mesmo quando não existem parâmetros em uma função, você ainda precisa incluir os parênteses
na declaração de função e em uma chamada à função.
A colocação da declaração de função (protótipo de função) e a definição de função é a mesma para funções
void que a descrita para funções que retornam um valor.

■ COMANDOS return EM FUNÇÕES void


Tanto as funções void quanto as funções que retornam um valor podem ter comandos return. No caso de
uma função que retorna um valor, o comando return especifica o valor retornado. No caso de uma função void,
76 Fundamentos das Funções

o comando return não inclui qualquer expressão para um valor retornado. Um comando return em uma função
void apenas termina a chamada de função. Toda função que retorna um valor deve terminar executando um co-
mando return. Entretanto, uma função void não precisa conter um comando return. Se não o contiver, termi-
nará após executar o código no corpo da função. É como se houvesse um comando return implícito antes da
chave de fechamento, }, ao final do corpo da função.

DECLARAÇÃO DE FUNÇÃO (PROTÓTIPO DE FUNÇÃO)


Uma declaração de função (protótipo de função) informa tudo o que você precisa saber para escrever uma chamada de função.
Uma declaração de função (ou a definição de função completa) deve aparecer em seu código antes da chamada à função. Decla-
rações de função normalmente são colocadas antes da parte main do seu programa.
SINTAXE
Tipo_Retornado_Ou_void NomeDaFuncao(Lista_De_Parametros);
----
em que Lista_De_Parametros é uma lista de parâmetros separada por vírgulas:
Tipo_1 Parametro_Formal_1, Tipo_2 Parametro_Formal_2, ...
... Tipo_Final Parametro_Formal_Final
Não se esqueça deste ponto-e-vírgula.

EXEMPLOS
double pesoTotal(int numero, double pesoDeUm);
//Retorna o peso total de numero itens
//cujo peso unitário é pesoDeUm.

void mostraResultados(double fGraus, double cGraus);


//Exibe uma mensagem dizendo que fGraus Fahrenheit
//equivalem a cGraus Celsius.

O fato de que há um comando return implícito antes da chave de fechamento em um corpo de função não
significa que você jamais necessite de um comando return em uma função void. Por exemplo, a definição de
função no Painel 3.7 pode ser usada como parte de um programa de gerenciamento de restaurante. Essa função
apresenta como saída instruções para dividir uma dada quantidade de sorvete entre as pessoas de uma mesa. Se
não existirem pessoas na mesa (ou seja, se numero é igual a 0), o comando return dentro do comando if termina
a chamada de função e evita uma divisão por zero. Se numero não é 0, a chamada de função termina quando o
último comando cout é executado ao final do corpo da função.

■ PRÉ-CONDIÇÕES E PÓS-CONDIÇÕES
Uma boa forma de se escrever um comentário de declaração de função é dividi-lo em dois tipos de informa-
ção, chamadas pré-condição e pós-condição. A pré-condição afirma o que se presume ser verdade quando a função
é chamada. A função não deve ser usada e não se deve esperar que atue corretamente a não ser que a pré-condi-
ção se sustente. A pós-condição descreve o efeito da chamada de função; ou seja, a pós-condição diz o que será
verdadeiro depois que a função é executada em uma situação na qual a pré-condição se sustenta. Para uma função
que retorna um valor, a pós-condição descreverá o valor retornado pela função. Para uma função que altera o valor

Painel 3.7 Uso de return em uma função void (parte 1 de 2)


1 #include <iostream>
2 using namespace std;

3 void iceCreamDivision(int number, double totalWeight);


4 //Mostra na tela as instruções para dividir totalWeight onças de sorvete entre
5 //o número de pessoas. Se o número for 0, apenas uma mensagem de erro será mostrada.

6 int main( )
7 {
8 int number;
9 double totalWeight;
Funções Definidas pelo Programador 77

Painel 3.7 Uso de return em uma função void (parte 2 de 2)


10 cout << "Informe o número de fregueses: ";
11 cin >> number;
12 cout << "Informe o peso do sorvete a dividir (em gramas): ";
13 cin >> totalWeight;

14 iceCreamDivision(number, totalWeight);

15 return 0;
16 }

17 void iceCreamDivision(int number, double totalWeight)


18 {
19 double portion;

20 if (number == 0)
21 {
22 cout << "Não é possível dividir entre zero fregueses.\n";
23 Se o númerto for Ø, então a
return ;
24 } função de execução termina aqui.
25 portion = totalWeight/number;
26 cout << "Cada um recebe "
27 << portion << " gramas de sorvete." << endl;
28 }

DIÁLOGO PROGRAMA-USUÁRIO
Informe o número de fregueses: 0
Informe o peso do sorvete a dividir (em gramas): 12
Não é possível dividir entre zero fregueses.

de algumas variáveis de argumento, a pós-condição descreverá todas as mudanças feitas nos valores dos argumentos.
Por exemplo, eis uma declaração de função com pré-condição e pós-condição:
void mostraJuro(double saldo, double taxa);
//Pré-condição: saldo é um saldo não-negativo de uma conta de poupança.
//taxa é a taxa de juros expressa como porcentagem, como 5 para 5%.
//Pós-condição: O valor em juros sobre um dado saldo
//à determinada taxa é mostrado na tela.

Você não precisa saber a definição da função mostraJuro a fim de utilizar essa função. Tudo o que você pre-
cisa saber é dado pela pré-condição e pós-condição.
Quando a única pós-condição é uma descrição do valor retornado, os programadores geralmente omitem a pa-
lavra Pós-condição, como no seguinte exemplo:
double celsius(double fahrenheit);
//Pré-condição: fahrenheit é uma temperatura em graus Fahrenheit.
//Retorna a temperatura equivalente expressa em graus Celsius.

Alguns programadores preferem não usar as palavras pré-condição e pós-condição em seus comentários de fun-
ções. Entretanto, quer você utilize as palavras, quer não, deve sempre pensar em termos de pré-condição e pós-
condição quando projeta uma função e quando decide o que incluir no comentário da função.

■ main É UMA FUNÇÃO


Como já observamos, a parte main de um programa é, na realidade, a definição de uma função chamada main.
Quando o programa é executado, a função main é automaticamente chamada; esta, por sua vez, pode chamar ou-
tras funções. Embora possa parecer que o comando return na parte main de um programa deveria ser opcional,
78 Fundamentos das Funções

na prática não é. O padrão C++ diz que você pode omitir o comando return 0 na parte main do seu programa,
mas muitos compiladores ainda o exigem e quase todos eles permitem que você o inclua. Em nome da portabili-
dade, você deve incluir o comando return 0 na função main. Você deve considerar a parte main de um programa
uma função que retorna um valor de tipo int e, assim, requer um comando return. Tratar a parte main do seu
programa uma função que retorna um inteiro pode parecer estranho, mas é a tradição que muitos compiladores
adotam.
Embora alguns compiladores possam permitir que você o faça, não o aconselhamos a incluir uma chamada a
main em seu código. Só o sistema deve chamar main, o que é feito quando seu programa é executado.

■ FUNÇÕES RECURSIVAS
O C++ permite que você defina funções recursivas. As funções recursivas serão abordadas no Capítulo 13. Se
você não sabe o que são, não há necessidade de se preocupar até que chegue a esse capítulo. Se quiser saber a res-
peito de funções recursivas antes, leia as Seções 13.1 e 13.2 do Capítulo 13 depois de completar o Capítulo 4.
Observe que a função main não deve ser chamada recursivamente.

1 Exercícios de Autoteste 1

15. Qual é a saída do seguinte programa?


#include <iostream>
using namespace std;

void amigavel( );

void timida(int contagemPlateia);


int main( )
{
amigavel( );
timida(6);
cout << "Mais uma vez:\n";
timida(2);
amigavel( );
cout << "Fim do programa.\n";
return 0;
}
void amigavel( )
{
cout << "Olá\n";
}
void timida(int contagemPlateia)
{
if (contagemPlateia < 5)
return;
cout << "Adeus\n";
}
16. Suponha que você tenha omitido o comando return na definição de função para Divisao_Do_Sorvete no
Painel 3.7. Que efeito isso exerceria sobre o programa? O programa compilaria ou não? Ele se comporta-
ria de forma diferente?
17. Escreva uma definição para uma função void que possua três argumentos de tipo int e que apresente
na tela o produto desses três argumentos. Coloque a definição em um programa completo que leia os
três números e depois chame essa função.
18. Seu compilador permite void main( ) e int main ( )? Que mensagens de alerta são emitidas se você
utilizar int main( ) e não retornar um comando return 0;? Para descobrir, escreva vários pequenos
programas-teste ou pergunte ao seu orientador ou guru.
19. Inclua uma pré-condição e uma pós-condição para a função predefinida sqrt, que retorna a raiz quadra-
da de seu argumento.
Regras de Escopo 79

3.3 1 Regras de Escopo


Que o final seja legítimo, que esteja dentro do escopo da Constituição...
John Marshall, Presidente da Suprema Corte dos EUA,
McCulloch v. Maryland (1803)

As funções devem ser unidades independentes, que não interferem com outras funções — ou com qualquer
outro código. Para conseguir isso, muitas vezes é preciso fornecer à função variáveis próprias que são distintas de
quaisquer outras variáveis declaradas fora da definição de função e que podem ter os mesmos nomes que as variá-
veis que pertençam à função. Essas variáveis que são declaradas em uma definição de função são chamadas variá-
veis locais e são o assunto desta seção.

■ VARIÁVEIS LOCAIS
Dê uma olhada no programa do Painel 3.1. Ele inclui uma chamada à função predefinida sqrt. Não precisa-
mos saber nada sobre os detalhes da definição de função de sqrt a fim de utilizar essa função. Em particular, não
precisamos saber que variáveis foram declaradas na definição de sqrt. Uma função que você defina não é diferen-
te. Declarações de variáveis dentro de uma definição de função são como se fossem declarações de variáveis em uma
função predefinida ou em outro programa. Se você declara uma variável em uma definição de função e depois declara
outra variável com o mesmo nome na função main do programa (ou no corpo de alguma outra definição de função),
então essas duas variáveis são diferentes, mesmo que possuam o mesmo nome. Vamos ver um exemplo.
O programa no Painel 3.8 possui duas variáveis chamadas ervilhaMedia; uma é declarada e utilizada na defi-
nição da função estimativaDoTotal, e a outra é declarada e utilizada na função main do programa. A variável er-
vilhaMedia na definição de função para estimativaDoTotal e a variável ervilhaMedia na função main são duas
variáveis diferentes. É como se a função estimativaDoTotal fosse uma função predefinida. As duas variáveis cha-
madas ervilhaMedia não interferirão uma com a outra, tanto quanto duas variáveis em dois programas completa-
mente diferentes não interfeririam.

Painel 3.8 Variáveis locais (parte 1 de 2)


1 //Calcula o rendimento médio de uma plantação experimental de ervilhas.
2 #include <iostream>
3 using namespace std;

4 double estimateOfTotal(int minPeas, int maxPeas, int podCount);


5 //Retorna uma estimativa do número total de ervilhas colhidas.
6 //O parâmetro formal podCount é o número de vagens.
7 //Os parâmetros formais MinPeas são o número mínimo
8 //e máximo de ervilhas em uma vagem.

9 int main( ) Esta variável chamada


10 { ervilhaMedia é o local da

-----------
11 int maxCount, minCount, podCount; função main.
12 double averagePea , yield;

13 cout << "Informe o número mínimo e máximo de ervilhas em uma vagem: ";
14 cin >> minCount >> maxCount;
15 cout << "Informe o número de vagens: ";
16 cin >> podCount;
17 cout << "Informe o peso de uma ervilha média (em onças): ";
18 cin >> averagePea;

19 yield =
20 estimateOfTotal(minCount, maxCount, podCount) * averagePea;

21 cout.setf(ios::fixed);
80 Fundamentos das Funções

Painel 3.8 Variáveis locais (parte 2 de 2)


22 cout.setf(ios::showpoint);
23 cout.precision(3);
24 cout << "Número mínimo de ervilhas por vagem = " << minCount << endl
25 << "Número máximo de ervilhas por vagem = " << maxCount << endl
26 << "Contagem de vagens = " << podCount << endl
27 << "Peso médio da ervilha = "
28 << averagePea << " onças" <<endl
29 << "Rendimento médio estimado = " << yield << " onças"
30 << endl;

31 return 0;
32 }
33
34 double estimateOfTotal(int minPeas, int maxPeas, int podCount)
35 {
36 double averagePea; Esta variável chamada ervilhaMedia é o
local da função estimativaDoTotal.
37 averagePea = (maxPeas + minPeas)/2.0;
38 return (podCount * averagePea );
39 }

DIÁLOGO PROGRAMA-USUÁRIO
Informe o número mínimo e máximo de ervilhas em uma vagem: 4 6
Informe o número de vagens: 10
Informe o peso de uma ervilha média (em onças*): 0.5
Número mínimo de ervilhas por vagem = 4
Número máximo de ervilhas por vagem = 6
Contagem de vagens = 10
Peso médio da ervilha = 0.500 onças
Rendimento médio estimado = 25.000 onças

Quando a variável ervilhaMedia recebe um valor na chamada de função para estimativaDoTotal, isso não al-
tera o valor da variável, também chamada ervilhaMedia, na função main.
Uma variável declarada dentro do corpo de uma definição de função é chamada de local àquela função, ou
então se diz que a função está em seu escopo. Se uma variável é local a alguma função, às vezes a chamamos sim-
plesmente de variável local, sem especificar a função.
Outro exemplo de variáveis locais pode ser visto no Painel 3.5. A definição da função custoTotal naquele
programa começa assim:
double custoTotal(int numeroParametro, double precoParametro)
{
const double IMPOSTO = 0.05; //5% de imposto sobre vendas
double subtotal;

A variável subtotal é local à função custoTotal. A constante nomeada IMPOSTO também é local à função
custoTotal. (Uma constante nomeada é, na verdade, nada mais do que uma variável que é inicializada com um
valor e que não pode ter esse valor alterado.)

VARIÁVEIS LOCAIS
Uma variável definida dentro do corpo de uma função é chamada de local àquela função, ou então se diz que possui aquela
função como escopo. Se a variável é local a uma função, então se pode ter outra variável (ou outro tipo de item) com o mesmo
nome, declarada em outra definição de função; essas duas variáveis serão diferentes, embora possuam o mesmo nome. (Em par-
ticular, isso é verdade mesmo se uma das funções é a função main.)

* Uma onça equivale a 28,35 g. (N. do R.T.)


Regras de Escopo 81

■ ABSTRAÇÃO PROCEDURAL
Uma pessoa que utiliza um programa não precisa conhecer os detalhes do código desse programa. Imagine
como seria difícil sua vida se você tivesse de saber e lembrar o código para o compilador que você usa. Um programa
tem um trabalho a fazer, como compilar seu programa ou verificar a ortografia das palavras em seu documento. Você
precisa saber o que o programa faz, para poder utilizá-lo, mas não precisa (ou, pelo menos, não deveria precisar) saber
como o programa realiza o trabalho. Uma função é como um programa pequeno e deve ser usada de forma similar.
Um programador que utiliza uma função em um programa precisa saber o que a função faz (como calcular uma raiz
quadrada ou converter uma temperatura em graus Fahrenheit para graus Celsius), mas não precisa saber como a
função realiza essa tarefa. Geralmente se diz que isso é tratar uma função como se fosse uma caixa preta.
Chamar algo de caixa preta é uma figura de linguagem que procura evocar a imagem de um dispositivo mate-
rial que você sabe como usar, mas cujo método de operação é um mistério, pois está encerrado em uma caixa pre-
ta cujo conteúdo você não pode ver (e não pode abri-la). Se uma função é bem projetada, o programador pode
utilizar a função como se fosse uma caixa preta. Tudo o que o programador precisa saber é que, se ele inserir os
argumentos apropriados na caixa preta, ela efetuará uma ação apropriada. Projetar uma função para que possa ser
utilizada como uma caixa preta às vezes é chamado de ocultação da informação, para enfatizar o fato de que o
programador age como se o corpo da função estivesse oculto à sua visão.
Escrever e utilizar funções como se fossem caixas pretas também é chamado de abstração procedural. Quando
se programa em C++, talvez fizesse mais sentido chamar de abstração funcional. Entretanto, procedimento é um ter-
mo mais geral que função, e os cientistas da computação o utilizam para todos os sistemas de instruções "estilo
função" e, assim, preferem o termo abstração procedural. O termo abstração transmite a idéia de que, quando se
usa uma função, como uma caixa preta, abstraem-se os detalhes do código contido no corpo da função. Essa téc-
nica pode ser chamada de princípio da caixa preta ou princípio da abstração procedural ou ocultação da informação.
Os três termos querem dizer o mesmo. Seja lá como for chamado esse princípio, o importante é que você deve
utilizá-lo quando projeta e escreve suas definições de função.

ABSTRAÇÃO PROCEDURAL
Quando aplicado a uma definição de função, o princípio da abstração procedural significa que sua função deve ser escrita de
modo que possa ser utilizada como uma caixa preta. Isso quer dizer que o programador que utiliza a função não deve precisar
olhar para o corpo da definição da função para ver como a função opera. A declaração de função e o comentário que a acom-
panha são tudo o que o programador precisa saber a fim de utilizar a função. Para garantir que suas definições de função te-
nham essa importante propriedade, obedeça estritamente às seguintes regras:
COMO ESCREVER UMA DEFINIÇÃO DE FUNÇÃO CAIXA PRETA
■ O comentário da declaração de função deve dizer ao programador toda e qualquer condição requerida dos argumentos da
função e deve descrever o resultado de uma invocação à função.
■ Todas as variáveis utilizadas no corpo da função devem ser declaradas no corpo da função. (Os parâmetros formais não pre-
cisam ser declarados, porque estão listados no cabeçalho da função.)

■ CONSTANTES GLOBAIS E VARIÁVEIS GLOBAIS


Como observamos no Capítulo 1, você pode e deve dar nome a valores constantes utilizando o modificador
const. Por exemplo, no Painel 3.5 usamos o modificador const para dar nome à taxa de imposto sobre vendas
com a seguinte declaração:
const double IMPOSTO = 0.05; // 5% de imposto sobre vendas

Se essa declaração estiver dentro da definição de uma função, como no Painel 3.5, o nome IMPOSTO é local à
definição de função, o que significa que, fora da definição da função que contém a declaração, você pode usar o
nome IMPOSTO para outra constante nomeada, ou variável, ou qualquer outra coisa.
Por outro lado, se essa declaração aparecer no início do programa, fora do corpo de todas as funções (e fora
do corpo da parte main do programa), diz-se que a constante nomeada é uma constante nomeada global e a
constante nomeada pode ser usada em qualquer definição de função que siga a declaração da constante.
82 Fundamentos das Funções

O Painel 3.9 mostra um programa com um exemplo de uma constante nomeada global. O programa pede o
valor de um raio e depois calcula tanto a área de um círculo quanto o volume de uma esfera com aquele raio, uti-
lizando as seguintes fórmulas:
area = π x (raio)2
volume = (4/3) x π x (raio)3

Ambas as fórmulas incluem a constante π, que é aproximadamente igual a 3.14159. O símbolo π é a letra
grega chamada "pi". O programa utiliza a seguinte constante nomeada global:
const double PI = 3.14159;

que aparece fora da definição de qualquer função (inclusive fora da definição de main).
O compilador permite a você uma ampla liberdade quanto ao local onde deve colocar as declarações de suas
constantes nomeadas globais. Para facilitar a leitura, contudo, você deve colocar todas as suas instruções de inclu-
de juntas, todas as suas declarações de constantes nomeadas globais em outro grupo e todas as suas declarações de
função (protótipos de função) juntas. Seguiremos a prática-padrão e colocaremos todas as nossas declarações de cons-
tantes nomeadas globais após as instruções de include e using e antes das declarações de função.

Painel 3.9 Constante nomeada global (parte 1 de 2)


1 //Calcula a área de um círculo e o volume de uma esfera.
2 //Utiliza o mesmo raio para ambos os cálculos.
3 #include <iostream>
4 #include <cmath>
5 using namespace std;
6 const double PI = 3.14159;
7 double area(double radius);
8 //Retorna a área de um círculo com o raio especificado.
9 double volume(double radius);
10 //Retorna o volume de uma esfera com o raio especificado.
11 int main( )
12 {
13 double radiusOfBoth, areaOfCircle, volumeOfSphere;
14 cout << "Informe um raio que será utilizado tanto em um círculo"
15 << "quanto em uma esfera (em polegadas): ";
16 cin >> radiusOfBoth;
17 areaOfCircle = area(radiusOfBoth);
18 volumeOfSphere = volume(radiusOfBoth);
19 cout << "Raio = " << radiusOfBoth << " polegadas\n"
20 << "Área do círculo = " << areaOfCircle
21 << " polegadas quadradas\n"
22 << "Volume da esfera = " << volumeOfSphere
23 << " polegadas cúbicas\n";
24 return 0;
25 }
26
27 double area(double radius)
28 {
29 return (PI * pow(radius, 2));
30 }
31 double volume(double radius)
32 {
33 return ((4.0/3.0) * PI * pow(radius, 3));
34 }
Regras de Escopo 83

Painel 3.9 Constante nomeada global (parte 2 de 2)


DIÁLOGO PROGRAMA-USUÁRIO
Informe um raio que será utilizado tanto em um círculo
quanto em uma esfera (em polegadas): 2
Raio = 2 polegadas*
Área do círculo = 12.5664 polegadas quadradas**
Volume da esfera = 31.5103 polegadas cúbicas***

Colocar todas as constantes nomeadas no início do programa aumenta a legibilidade, mesmo se a constante
nomeada for usada apenas por uma função. Se for preciso alterar a constante nomeada em uma futura versão do
programa, será mais fácil encontrá-la se estiver no início. Por exemplo, colocar a declaração da constante para a
taxa de imposto sobre vendas no início de um programa de contabilidade tornará mais fácil revisar o programa se
a taxa do imposto mudar.
É possível declarar variáveis ordinárias, sem o modificador const, como variáveis globais, que são acessíveis a
todas as definições de função no arquivo. Isso é feito de maneira similar à utilizada para as constantes nomeadas
globais, a não ser pelo fato de o modificador const não ser usado na declaração de variável. Entretanto, raramente
há necessidade de se utilizar tais variáveis globais. Além disso, as variáveis globais podem tornar um programa
mais difícil de se entender e manter, por isso o aconselhamos a evitá-las.

1 Exercícios de Autoteste 1

20. Se você utilizar uma variável em uma definição de função, onde deve declarar a variável? Na definição
de função? Na função main? Em qualquer lugar que seja conveniente?
21. Suponha que uma função chamada funcao1 possua uma variável chamada sam declarada dentro da defi-
nição de funcao1 e uma função chamada funcao2 que também possui uma variável chamada sam decla-
rada dentro da definição de funcao2. Será que o programa compilará (presumindo que todo o resto
esteja correto)? Se for executado, gerará uma mensagem de erro ao ser executado (presumindo que todo
o resto esteja correto)? Se for executado e não produzir mensagem de erro na execução, fornecerá a
resposta correta (presumindo que todo o resto esteja correto)?
22. Qual é a finalidade do comentário que acompanha a declaração de uma função?
23. Qual é o princípio da abstração procedural como aplicado a definições de função?
24. O que significa quando dizemos que o programador que utiliza uma função poderia tratar a função
como uma caixa preta? (Esta pergunta está intimamente relacionada à pergunta anterior.)

■ BLOCOS
Uma variável declarada dentro de um comando composto (ou seja, dentro de chaves) é local ao comando
composto. O nome da variável pode ser usado para algo mais, como o nome de uma variável diferente, fora do
comando composto.
Um comando composto com declarações normalmente é chamado de bloco. Na realidade, bloco e comando
composto são dois termos que designam a mesma coisa. Entretanto, quando nos concentramos nas variáveis decla-
radas dentro de um comando composto, normalmente utilizamos o termo bloco em vez de comando composto e di-
zemos que as variáveis declaradas dentro do bloco são locais ao bloco.
Se uma variável é declarada em um bloco, a definição se aplica desde o local da declaração até o final do blo-
co. Costuma-se dizer que o escopo da declaração vai desde o local da declaração até o final do bloco. Assim, se
uma variável é declarada no início de um bloco, a declaração não surte efeito até que o programa chegue ao local
da declaração (veja Exercício de Autoteste 25).
Observe que o corpo de uma definição de função é um bloco. Assim, uma variável que é local a uma função
é a mesma coisa que uma variável que é local ao corpo da definição de função (que é um bloco).

* Uma polegada equivale a 2,54 cm. (N. do R.T.)


** Uma polegada quadrada equivale a 6,452 cm2. (N. do R.T.)
*** Uma polegada cúbica equivale a 16,39 cm3. (N. do R.T.)
84 Fundamentos das Funções

BLOCOS
Um bloco é um código em C++ entre chaves. As variáveis declaradas em um bloco, são locais ao bloco, e, portanto, os nomes
das variáveis podem ser usados fora do bloco para alguma outra coisa (como ser reutilizadas como nomes para variáveis dife-
rentes).

■ ESCOPOS ANINHADOS
Suponha que você tenha um bloco aninhado dentro de outro bloco e que um identificador é declarado como
uma variável em cada um desses blocos. Existem duas variáveis diferentes com o mesmo nome. Uma variável exis-
te só dentro do bloco interno e não se pode ter acesso a ela fora do bloco interno. A outra variável existe apenas
no bloco externo e não se pode ter acesso a ela no bloco interno. As duas variáveis são distintas, portanto mudan-
ças realizadas em uma delas não exercerão efeito sobre a outra.

REGRA DE ESCOPO PARA BLOCOS ANINHADOS


Se um identificador é declarado como uma variável em cada um de dois blocos, um dentro do outro, então temos duas variáveis
diferentes com o mesmo nome. Uma variável existe apenas dentro do bloco interno e não se pode ter acesso a ela de fora do
bloco interno. A outra variável existe apenas no bloco externo e não se pode ter acesso a ela do bloco interno. As duas variá-
veis são distintas, portanto mudanças realizadas em uma delas não exercerão efeito sobre a outra.

IDica USE CHAMADAS DE FUNÇÕES EM COMANDOS DE SELEÇÃO E LOOPS


O comando switch e o comando if-else permitem a introdução de vários comandos diferentes em cada se-
leção. Entretanto, isso pode tornar o comando switch ou if-else difíceis de ler. Em vez de colocar um co-
mando composto em uma estrutura de controle, normalmente é preferível converter o comando composto
em uma definição de função e colocar uma chamada de função na ramificação. De maneira similar, se o cor-
po de um loop é extenso, é preferível converter o comando composto em uma definição de função e trans-
formar o corpo do loop em uma chamada de função.

■ VARIÁVEIS DECLARADAS EM UM LOOP for


Uma variável pode ser declarada no cabeçalho de um comando for de modo que a variável seja ao mesmo
tempo declarada e inicializada no início do comando for. Por exemplo,
for (int n = 1; n <= 10; n++)
soma = soma + n;
O padrão C++ ANSI/ISO requer que um compilador C++ que alegue obedecer ao padrão trate qualquer de-
claração na inicialização de um loop for como se fosse local ao corpo do loop. Os compiladores C++ antigos não
fazem isso. Você deve verificar como seu compilador trata as variáveis declaradas em uma inicialização de loop
for. Se a portabilidade for muito importante para sua aplicação, você não deve escrever código que dependa desse
comportamento. Com o tempo, todos os compiladores C++ amplamente usados se adaptarão a essa regra, mas os
compiladores disponíveis atualmente podem ou não obedecer a ela.

1 Exercícios de Autoteste 1

25. Embora não o encorajemos a programar utilizando este estilo, estamos incluindo um exercício que utili-
za blocos aninhados para ajudá-lo a entender as regras de escopo. Determine a saída que este fragmen-
to de código produziria se inserido em um programa completo que, a não ser por este trecho, seria
correto.
{
int x = 1;
cout << x << endl;
{
cout << x << endl;
int x = 2;
Regras de Escopo 85

Exercícios de Autoteste (continuação)


cout << x << endl;
{
cout << x << endl;
int x = 3;
cout << x << endl;
}
cout << x << endl;
}
cout << x << endl;
}

Resumo do Capítulo
■ Existem dois tipos de funções em C++: funções que retornam um valor e funções void.
■ Uma função deve ser definida de forma que possa ser utilizada como uma caixa preta. O programador que
utiliza a função não precisa saber os detalhes do código dessa função. Tudo o que o programador precisa
saber é a declaração da função e os comentários que a acompanham, que descrevem o valor retornado.
Essa regra, às vezes, é chamada de princípio da abstração procedural.
■ Uma boa forma de escrever um comentário de declaração de função é utilizar uma pré-condição e uma
pós-condição. A pré-condição afirma o que se presume que seja verdade quando a função é chamada. A pós-
condição descreve o efeito da chamada de função; ou seja, a pós-condição diz o que será verdade depois
que a função for executada em uma situação em que a pré-condição se sustente.
■ Uma variável declarada em uma definição de função é chamada de local à função.
■ Um parâmetro formal é um tipo de "guardador" de lugar que é preenchido com um argumento de função
quando a função é chamada. Os detalhes desse processo de "preenchimento" serão abordados no Capítulo 4.

RESPOSTAS DOS EXERCÍCIOS DE AUTOTESTE


1. 4.0 4.0 8.0
8.0 8.0 1.21
3 3 0
3.0 3.5 3.5
6.0 6.0 5.0
5.0 4.5 4.5
3 3.0 3.0
2. a. sqrt(x + y)
b. pow(x, y + 7)
c. sqrt(area + caramelo)
d. sqrt(tempo+mare)/ninguem
e. (-b + sqrt(b*b - 4*a*c))/(2*a)
f. abs(x - y) or labs(x - y) or fabs(x - y)
3. #include <iostream>
#include <cmath>
using namespace std;
int main( )
{
int i;
for (i = 1; i <= 10; i++)
cout << "A raiz quadrada de " << i
<< " é " << sqrt(i) << endl;
return 0;
86 Fundamentos das Funções

}
4. O argumento é fornecido ao sistema operacional. No que se refere ao seu programa em C++, você pode
utilizar qualquer valor int como argumento. Por convenção, todavia, usa-se 1 para uma chamada a exit
provocada por um erro, e 0 nos outros casos.
5. (5 + (rand( ) % 6))
6. #include <iostream>
#include <cstdlib>
using namespace std;

int main( )
{
cout << "Forneça um inteiro não-negativo para usar como\n"
<< "semente para o gerador de números aleatórios: ";
unsigned int semente;
cin >> semente;
srand(semente);

cout << "Aqui há dez possibilidades aleatórias:\n";


int i;
for (i = 0; i < 10; i++)
cout << ((RAND_MAX - rand( ))/static_cast<double>(RAND_MAX))
<< endl;

return 0;
}
7. Wow
8. A declaração de função é
int soma(int n1, int n2, int n3);
//Retorna a soma de n1, n2, e n3.
A definição de função é
int soma(int n1, int n2, int n3)
{
return (n1 + n2 + n3);
}
9. A declaração de função é
char testePositivo(double numero)
//Retorna ’P’ se número é positivo.
//Retorna ’N’ se número é negativo ou zero.
A definição de função é
char testePositivo(double numero)
{
if (numero > 0)
return ’P’;
else
return ’N’;
}
10. Não, uma definição de função não pode aparecer dentro do corpo de outra definição de função.
11. Funções predefinidas e funções definidas pelo usuário são invocadas (chamadas) da mesma forma.
12. bool emOrdem(int n1, int n2, int n3)
{
return ((n1 <= n2) && (n2 <= n3));
}
13. bool par(int n)
{
return ((n % 2) == 0);
}
Respostas dos Exercícios de Autoteste 87

14. bool digito(char ch)


{
return (’0’ <= ch) && (ch <= ’9’);
}
15. Olá
Adeus
Mais uma vez:
Olá
Final do programa.
16. Se você omitiu o comando return na definição de função para DivisaoDoSorvete no Painel 3.7, o pro-
grama compilará e será executado. Entretanto, se você inserir zero como entrada para o número de fregue-
ses, o programa sofrerá um erro de execução devido a uma divisão por zero.
17. #include <iostream>
using namespace std;
void produto(int n1, int n2, int n3);

int main( )
{
int num1, num2, num3;
cout << "Forneça três números inteiros: ";
cin >> num1 >> num2 >> num3;
produto(num1, num2, num3);
return 0;
}

void produto(int n1, int n2, int n3)


{
cout << "O produto dos três números "
<< n1 << ", " << n2 << " e"
<< n3 << " é " << (n1*n2*n3) << endl;
}
18. Essas respostas dependem do sistema.
19. double sqrt(double n);
//Pré-condição: n >= 0.
//Retorna a raiz quadrada de n.
Você pode reescrever a segunda linha de comentário da seguinte forma, se preferir, mas a versão acima é
a forma usual utilizada para uma função que retorna um valor:
//Pós-condição: Retorna a raiz quadrada de n.
20. Se você usar uma variável em uma definição de função, deve declarar a variável no corpo da definição de
função.
21. Tudo vai dar certo. O programa compilará (presumindo-se que todo o resto esteja correto). O programa
será executado (presumindo-se que todo o resto esteja correto). O programa não gerará mensagem de erro
quando for executado (presumindo-se que todo o resto esteja correto). O programa fornecerá a saída cor-
reta (presumindo-se que todo o resto esteja correto).
22. O comentário explica que ação a função efetua, inclusive o valor retornado, e apresenta qualquer outra in-
formação de que você necessite a fim de utilizar a função.
23. O princípio da abstração procedural afirma que uma função deve ser escrita de modo a poder ser utilizada
como uma caixa preta. Isso significa que o programador que utiliza a função não precisa olhar para o cor-
po da definição de função para saber como essa função atua. A declaração de função e os comentários que
a acompanham devem ser suficientes para que o programador possa utilizar a função.
24. Quando dizemos que o programador que utiliza a função deve ser capaz de tratar a função como uma
caixa preta, queremos dizer que o programador não precisa olhar para o corpo da definição de função
para saber como a função atua. A declaração de função e os comentários que a acompanham devem ser
suficientes para que o programador possa utilizar a função.
88 Fundamentos das Funções

25. Alterar levemente o código ajuda a entender a que se refere cada declaração. O código possui três variáveis
diferentes chamadas x. No trecho seguinte, renomeamos essas três variáveis como x1, x2 e x3. A saída é
dada nos comentários.
{
int x1 = 1;// saída nesta coluna
cout << x1 << endl;// 1<nova linha>
{
cout << x1 << endl;// 1<nova linha>
int x2 = 2;
cout << x2 << endl;// 2<nova linha>
{
cout << x2 << endl;// 2<nova linha>
int x3 = 3;
cout << x3 << endl;// 3<nova linha>
}
cout << x2 << endl;// 2<nova linha>
}
cout << x1 << endl;// 1<nova linha>
}

PROJETOS DE PROGRAMAÇÃO
1. Um litro equivale a 0.264179 galões. Escreva um programa que leia o número de litros de gasolina con-
sumidos pelo carro do usuário e o número de milhas* que o carro andou e apresente como saída o núme-
ro de milhas por galão que o carro rendeu. Seu programa deve permitir que o usuário repita o cálculo
quantas vezes quiser. Defina uma função para calcular o número de milhas por galão. Seu programa deve
usar uma constante globalmente definida para o número de galões por litro.
2. Escreva um programa para medir a taxa de inflação no ano passado. O programa pede o preço de um
item (como um cachorro quente ou um diamante de um quilate) no ano passado e hoje. Estima a taxa de
inflação como a diferença no preço dividida pelo preço do ano passado. Seu programa deve permitir que o
usuário repita esse cálculo quantas vezes desejar. Defina uma função para calcular a taxa de inflação. A taxa de
inflação deve ser um valor de tipo double, fornecendo a taxa como porcentagem, por exemplo, 5.3 para 5.3%.
3. Aperfeiçoe o programa do exercício anterior fazendo com que apresente também o preço estimado do
item um e dois anos depois da época do cálculo. O aumento no custo em um ano é estimado como a
taxa de inflação multiplicada pelo preço no início do ano. Defina uma segunda função para determinar o
custo estimado de um item em um número especificado de anos, dados o preço atual do item e a taxa de
inflação como argumentos.
4. A força de atração gravitacional entre dois corpos com massas m1 e m2, separados por uma distância d, é
dada pela seguinte fórmula:
Gm1m2
F=
d2
onde G é a constante de gravitação universal:
G = 6.673 x 10-8 cm3/(g • sec2)
Escreva uma definição de função que utilize argumentos para as massas de dois corpos e a distância entre
eles e forneça a força gravitacional entre eles. Como você irá utilizar a fórmula acima, a força gravitacional
será dada em dynes (dinas). Um dyne (dina) equivale a
1g • cm/sec2
Você deve usar uma constante globalmente definida para a constante de gravitação universal. Insira sua defini-
ção de função em um programa completo que calcule a força gravitacional entre dois objetos com dados de
entrada adequados. Seu programa deve permitir que o usuário repita esse cálculo quantas vezes desejar.

* Uma milha terrestre equivale a 1,609 km. (N. do R.T.)


Projetos de Programação 89

5. Escreva um programa que peça a altura, o peso e a idade do usuário e calcule o tamanho das roupas de
acordo com as seguintes fórmulas.
■ Tamanho do chapéu = peso em libras* dividido pela altura em polegadas e tudo isso multiplicado por 2.9.
■ Tamanho do casaco (tórax em polegadas) = altura vezes peso dividido por 288 e um ajuste efetuado
pelo acréscimo de um oitavo de uma polegada para cada 10 anos acima dos 30 anos. (Observe que o
ajuste só ocorre após 10 anos completos. Assim, não há ajuste para as idades de 30 a 39, mas um oi-
tavo de uma polegada é acrescentado para a idade 40.)
■ Cintura em polegadas = peso dividido por 5.7 e um ajuste efetuado pelo acréscimo de um décimo de
uma polegada para cada 2 anos acima dos 28 anos. (Observe que o ajuste só ocorre após 2 anos com-
pletos. Assim, não há ajuste para os 29 anos, mas um décimo de uma polegada é acrescentado para os
30 anos.)
Utilize funções para cada cálculo. Seu programa deve permitir que o usuário repita esse cálculo quantas
vezes desejar.
6. Escreva uma função que calcule o desvio médio e padrão de quatro pontuações. O desvio-padrão é defini-
do como a raiz quadrada da média dos quatro valores: (si - a)2, em que a é a média das quatro pontua-
ções, s1, s2, s3 e s4. A função terá seis parâmetros e chamará duas outras funções. Insira a função em um
programa que lhe permita testar a função repetidas vezes até dizer ao programa que terminou.
7. Quando está frio, os meteorologistas transmitem um índice chamado fator de frio do vento, que leva em
consideração a velocidade do vento e a temperatura. O índice fornece uma medida do efeito resfriador do
vento em uma dada temperatura do ar. Esse índice pode ser aproximado pela seguinte fórmula:
W = 13.12 + 0.6215*t – 11.37*v0.16 + 0.3965*t*v0.016
em que
v = velocidade do vento em m/s
t = temperatura em graus Celsius: t <= 10
W = índice de frio do vento (em graus Celsius)
Escreva uma função que forneça o índice de frio do vento. Seu código deve assegurar que a restrição a
respeito da temperatura não seja violada. Verifique alguns boletins meteorológicos em edições anteriores
de jornais em sua biblioteca e compare o índice de frio do vento que você calculou com o resultado di-
vulgado no jornal.

* Uma libra equivale a 453,6 g. (N. do R.T.)


CAPÍTULO

Parâmetros e Sobrecarga
Parâmetros e Sobrecarga

Capítulo 4Parâmetros e Sobrecarga


É só preencher os espaços em branco.
Instrução comum

INTRODUÇÃO
Este capítulo discute os detalhes dos mecanismos utilizados pelo C++ para conectar argu-
mentos a parâmetros em chamadas de função. Discute também a sobrecarga, que é uma
forma de dar duas (ou mais) definições de função diferentes para o mesmo nome de fun-
ção. Finalmente, trata de algumas técnicas básicas para testar funções.

4.1 Parâmetros
Não se pode colocar um pino quadrado em um buraco redondo.
Ditado popular

Esta seção descreve os detalhes dos mecanismos utilizados pelo C++ para conectar um ar-
gumento a um parâmetro formal quando uma função é invocada. Existem dois tipos bási-
cos de parâmetros e, portanto, dois mecanismos básicos de conexão em C++. Os dois
tipos básicos de parâmetros são parâmetros chamados por valor e parâmetros chamados por
referência. Todos os parâmetros que aparecem antes deste ponto no livro eram parâmetros
chamados por valor. Com parâmetros chamados por valor, apenas o valor do argumento é
conectado. Com os parâmetros chamados por referência, o argumento é uma variável e a
própria variável é conectada; portanto, o valor das variáveis pode ser alterado pela invocação
da função. Um parâmetro chamado por referência é indicado pela anexação do sinal de “e”
comercial, &, ao tipo do parâmetro, como ilustrado pelas seguintes declarações de função:
void getEntrada(double& variavelUm, int& variavelDois);

Um parâmetro chamado por valor é indicado pela ausência do “e” comercial. Os detalhes
sobre os parâmetros chamados por valor e por referência serão dados nas próximas subseções.

■ PARÂMETROS CHAMADOS POR VALOR


Os parâmetros chamados por valor são mais do que apenas espaços em branco preen-
chidos com os valores dos argumentos da função. Um parâmetro chamado por valor é, na
realidade, uma variável local. Quando a função é invocada, o valor de um parâmetro cha-
mado por valor é calculado, e o parâmetro chamado por valor correspondente, que é uma
variável local, é inicializado com esse valor.
Na maioria dos casos, pode-se pensar em um parâmetro chamado por valor como um
tipo de espaço em branco, ou “guardador” de lugar, que é preenchido pelo valor do argu-
mento correspondente na invocação da função. Entretanto, em alguns casos é útil empre-
92 Parâmetros e Sobrecarga

gar um parâmetro chamado por valor como uma variável local e alterar o valor de seu parâmetro dentro do corpo
da definição de função. Por exemplo, o programa no Painel 4.1 ilustra um parâmetro chamado por valor utilizado
como uma variável local cujo valor é alterado no corpo da definição de função. Observe o parâmetro formal mi-
nutosTrabalhados na definição da função taxa. Ele é usado como uma variável, e seu valor é alterado pela se-
guinte linha, que aparece dentro da definição de função:
minutosTrabalhados = horasTrabalhadas*60 + minutosTrabalhados;

Painel 4.1 Parâmetro formal utilizado como variável local


1 //Programa de faturamento de um escritório de advocacia.
2 #include <iostream>
3 using namespace std;

4 const double RATE = 150.00; //Dólares por 15 minutos de consulta.

5 double fee(int hoursWorked, int minutesWorked);


6 //Retorna o preço da hora hoursWorked e
7 //os minutos minutesWorked de serviços.

8 int main( )
9 {
10 int hours, minutes;
11 double bill;

12 cout << “Bem-vindo ao escritório de advocacia de \n”


13 << “Dewey, Cheatham e Howe.\n”
14 << “O escritório que tem coração.n” Os valores dos minutos não são
15 << “Informe as horas e minutos” alterados para a chamada honorários.
16 << “ de sua consulta:\n”;
17 cin >> hours >> minutes;

18 bill = fee(hours, minutes );

19 cout.setf(ios::fixed);
20 cout.setf(ios::showpoint);
21 cout.precision(2);
22 cout << “Por ” << hours << “ horas e ” << minutes
23 << “ minutos, sua conta é de $” << bill << endl;

24 return 0;
25 } MinutosTrabalhados é uma local variável
26 double fee(int hoursWorked, int minutesWorked )
inicializada para os valores dos minutos.
27 {
28 int quarterHours;

29 minutesWorked = hoursWorked*60 + minutesWorked;


30 quarterHours = minutesWorked/15;
31 return (quarterHours*RATE);
32 }

DIÁLOGO PROGRAMA-USUÁRIO
Bem-vindo ao escritório de advocacia de
Dewey, Cheatham e Howe.
O escritório que tem coração.
Informe as horas e minutos de sua consulta:
5 46
Por 5 horas e 46 minutos, sua conta é de $3450.00
Parâmetros 93

Parâmetros chamados por valor são variáveis locais exatamente como as variáveis declaradas no corpo de uma função.
Entretanto, não se deve acrescentar uma declaração de variável para os parâmetros formais. Listar o parâmetro formal mi-
nutosTrabalhados no cabeçalho da função serve também como a declaração da variável. A forma seguinte é errada e não
deve ser utilizada para iniciar a definição de função para taxa porque declara minutosTrabalhados duas vezes:
double fee(int hoursWorked, int minutesWorked)
{
int quarterHours; Não faça isso quando
int minutesWorked; minutosTrabalhados for
. . . um parâmetro!

1 Exercícios de Autoteste 1

1. Descreva cuidadosamente o mecanismo do parâmetro chamado por valor.


2. Supõe-se que a seguinte função exija como argumentos um comprimento expresso em pés e polegadas
e retorne o número total de polegadas. Por exemplo, totalPolegadas(1, 2) deve apresentar como saída
14, porque 1 pé e 2 polegadas é o mesmo que 14 polegadas. A função seguinte atuará corretamente?
Se não, por quê?
double totalPolegadas(int pes, int polegadas)
{
polegadas = 12*pes + polegadas;
return polegadas;
}

■ PRIMEIRA VISÃO DOS PARÂMETROS CHAMADOS POR REFERÊNCIA


O mecanismo de chamada por valor que utilizamos até agora não é suficiente para todas as tarefas que se pos-
sa querer que uma função desempenhe. Por exemplo, uma tarefa comum para uma função é obter um valor de
entrada do usuário e estabelecer o valor de uma variável de argumento para esse valor de entrada. Com os parâ-
metros formais chamados por valor que utilizamos até agora, um argumento correspondente em uma chamada de
função pode ser uma variável, mas a função recebe apenas o valor da variável e não altera a variável de forma al-
guma. Com um parâmetro formal chamado por valor apenas o valor do argumento substitui o parâmetro formal.
Para uma função de entrada, o que se quer é que a variável (não o valor da variável) substitua o parâmetro for-
mal. O mecanismo de chamada por referência atua exatamente dessa forma. Com um parâmetro formal chamado
por referência, o argumento correspondente em uma chamada de função deve ser uma variável, e essa variável de
argumento substitui o parâmetro formal. É quase como se a variável do argumento fosse literalmente copiada para
dentro do corpo da definição de função em lugar do parâmetro formal. Depois que a substituição é efetuada, o
código no corpo da função é executado e pode alterar o valor da variável do argumento.
Um parâmetro chamado por referência deve ser assinalado de alguma forma para que o compilador o distinga
de um parâmetro chamado por valor. O modo como se indica um parâmetro chamado por referência é a anexa-
ção de um sinal de “e” comercial, &, ao final do nome do tipo na lista de parâmetros formais. Isso é feito tanto
na declaração da função (protótipo da função) como no cabeçalho da definição de função. Por exemplo, a seguin-
te definição de função possui um parâmetro formal, receptor, que é um parâmetro chamado por referência:
void getEntrada(double& receptor)
{
cout << “Forneça o número de entrada:\n”;
cin >> receptor;
}

Em um programa que contenha essa definição de função, a seguinte chamada de função fixará a variável dou-
ble numeroEntrada como igual a um valor lido a partir do teclado:
getEntrada(numeroEntrada);

O C++ permite que você coloque o símbolo de “e” comercial junto ao nome do tipo ou junto ao nome do
parâmetro. Assim, às vezes você verá
94 Parâmetros e Sobrecarga

void getEntrada(double &receptor);

que equivale a
void getEntrada(double& receptor);

Painel 4.2 Parâmetros chamados por referência


1 //Programa para demonstrar parâmetros chamados por referência.
2 #include <iostream>
3 using namespace std;

4 void getNumbers(int& input1, int& input2 );


5 //Lê dois inteiros a partir do teclado.

6 void swapValues(int& variable1, int& variable2 );


7 //Troca os valores variable1 e variable2.

8 void showResults(int output1, int output2);


9 //Mostra os valores de variable1 e variable2, nessa ordem.

10 int main( )
11 {
12 int firstNum, secondNum;

13 getNumbers(firstNum, secondNum);
14 swapValues(firstNum, secondNum);
15 showResults(firstNum, secondNum);
16 return 0;
17 }

18 void getNumbers( int& input1, int& input2 )


19 {
20 cout << “Forneça dois números inteiros: ”;
21 cin >> input1
22 >> input2;
23 }
24 void swapValues(int& variable1, int& variable2)
25 {
26 int temp;
27 temp = variable1;
28 variable1 = variable2;
29 variable2 = temp;
30 }
31
32 void showResults(int output1, int output2)
33 {
34 cout << “Em ordem inversa, os números são: ”
35 << output1 << “ ” << output2 << endl;
36 }
DIÁLOGO PROGRAMA-USUÁRIO
Forneça dois números inteiros: 5 6
Em ordem inversa, os números são: 6 5

O Painel 4.2 apresenta os parâmetros chamados por referência. O programa lê dois números e escreve esses
números na tela, mas em ordem inversa. Os parâmetros nas funções getNumeros e trocaValores são parâmetros
chamados por referência. A entrada é efetuada pela chamada de função
Parâmetros 95

getNumeros(primeiroNum, segundoNum);

Os valores das variáveis primeiroNum e segundoNum são estabelecidos por essa chamada de função. Depois dis-
so, a função seguinte inverte os valores nas duas variáveis primeiroNum e segundoNum:
swapNumeros(primeiroNum, segundoNum);

As próximas subseções descrevem o mecanismo de chamada por referência em mais detalhes e explicam tam-
bém as funções particulares utilizadas no Painel 4.2.

PARÂMETROS CHAMADOS POR REFERÊNCIA


Para transformar um parâmetro formal em parâmetro chamado por referência, anexe o símbolo de “e” comercial, &, ao seu nome
de tipo. O argumento correspondente em uma chamada à função deve, então, ser uma variável, não uma constante ou outra
expressão. Quando a função é chamada, o argumento da variável correspondente (não seu valor) substituirá o parâmetro formal.
Qualquer alteração no parâmetro formal no corpo da função será feita na variável do argumento quando a função é chamada.
Os detalhes exatos dos mecanismos de substituição serão fornecidos no texto deste capítulo.
EXEMPLO
void getDados(int& primeiraEntrada, double& segundaEntrada);

■ MECANISMO DE CHAMADA POR REFERÊNCIA EM DETALHE


Na maioria das situações o mecanismo de chamada por referência funciona como se o nome da variável dado
como argumento da função substituísse literalmente o parâmetro formal chamado por referência. Entretanto, o
processo é um pouco mais sutil do que isso. Em algumas situações, essa sutileza é importante. Por isso, precisamos
analisar mais detalhadamente esse processo de substituição de chamada por referência.
Variáveis em um programa são implementadas como posições na memória. Cada posição na memória possui
um endereço único, que é um número. O compilador atribui uma posição de memória a cada variável. Por exem-
plo, quando o programa no Painel 4.2 é compilado, pode ser atribuída à variável primeiroNum a posição 1010, e
à variável segundoNum a posição 1012. Para todos os efeitos práticos, essas posições de memória são as variáveis.
Por exemplo, considere a seguinte declaração de função do Painel 4.2:
void getNumeros(int& entrada1, int& entrada2);

Os parâmetros formais chamados por referência entrada1 e entrada2 são “guardadores” de lugar para os ver-
dadeiros argumentos utilizados em uma chamada de função.
Agora considere uma chamada de função como a seguinte, do mesmo programa:
getNumeros(primeiroNum, segundoNum);

Quando a chamada de função é executada, a função não recebe os nomes de argumentos primeiroNum e se-
gundoNum. Em vez disso, recebe uma lista das posições de memória associadas a cada nome. Neste exemplo, a lista
consiste nas posições
1010
1012

que são as posições atribuídas às variáveis de argumento primeiroNum e segundoNum, nesta ordem. E são essas as
posições de memória associadas aos parâmetros formais. A primeira posição de memória ao primeiro parâmetro
formal, a segunda posição de memória ao segundo parâmetro formal, e assim por diante. Em um diagrama, neste
caso a correspondência é
primeiroNum → 1010 → entrada1
segundoNum → 1012 → entrada2

Quando os comandos da função são executados, o que quer que o corpo da função diga para fazer com um
parâmetro formal é, na verdade, feito com a variável na posição de memória associada com aquele parâmetro for-
mal. Nesse caso, as instruções no corpo da função getNumeros dizem que um valor deve ser armazenado no parâ-
metro formal entrada1 utilizando um comando cin, e assim o valor é armazenado na variável na posição de
96 Parâmetros e Sobrecarga

memória 1010 (que é a variável primeiroNum). De forma similar, as instruções no corpo da função getNumeros di-
zem que outro valor deve, então, ser armazenado no parâmetro formal entrada2 por meio de um comando cin e,
dessa forma, aquele valor é armazenado na variável na posição de memória 1012 (que é a variável segundoNum).
Assim, o que quer que a função instrua o computador a fazer com entrada1 e entrada2 é, na verdade, feito com
as variáveis primeiroNum e segundoNum.
Pode parecer que existam detalhes demais, ou, pelo menos, palavras demais nessa história. Se primeiroNum é a
variável com a posição de memória 1010, por que insistimos em dizer “a variável na posição de memória 1010"
em vez de simplesmente dizer “primeiroNum”? Essa quantidade a mais de detalhes é necessária se os argumentos e
os parâmetros formais contêm alguma coincidência de nomes criadora de confusão. Por exemplo, a função getNu-
meros possui parâmetros formais denominados entrada1 e entrada2. Suponha que você queira mudar o programa
no Painel 4.2 para que ele utilize a função getNumeros com argumentos que também se chamem entrada1 e en-
trada2, e suponha que você queira fazer algo que não seja tão óbvio. Suponha, ainda, que você queira que o pri-
meiro número digitado seja armazenado em uma variável chamada entrada2, e o segundo número digitado na
variável chamada entrada1 — talvez porque o segundo número será processado primeiro ou porque é o número
mais importante. Agora, vamos supor que às variáveis entrada1 e entrada2, que são declaradas na parte main do

---
seu programa, tenham sido atribuídas as posições de memória 1014 e 1016. A função poderia ser assim:
int entrada1, entrada2; Observe a ordem dos argumentos.
getNumeros(entrada2, entrada1);

Neste caso, se você disser “entrada1”, não saberemos se você se refere à variável chamada entrada1 declarada
na parte main do seu programa ou ao parâmetro formal entrada1. Entretanto, se à variável entrada1 declarada na
função main do seu programa é atribuída a posição de memória 1014, a frase “a variável de posição de memória
1014” é inequívoca. Vamos analisar os detalhes dos mecanismos de substituição nesse caso.
Nesta chamada o argumento correspondente ao parâmetro formal entrada1 é a variável entrada2, e o argu-
mento correspondente ao parâmetro formal entrada2 é a variável entrada1. Isso pode parecer confuso para nós,
mas não causa problema para o computador, já que este, na verdade, nunca “substitui entrada1 por entrada2” ou
“substitui entrada2 por entrada1”. O computador simplesmente lida com posições de memória. O computador
substitui o parâmetro formal entrada1 pela “variável na posição de memória 1016” e o parâmetro formal entra-
da2 pela “variável na posição de memória 1014”.

FUNÇÃO trocaValores
A função trocaValores definida no Painel 4.2 troca os valores armazenados nas duas variáveis. A descrição
da função é dada pela seguinte declaração de função e comentário que a acompanha:
void trocaValores(int& variavel1, int& variavel2);
//Troca os valores da variavel1 e variavel2.
Para ver como se espera que a função trabalhe, presuma que a variável primeiroNum tenha valor 5 e a variá-
vel segundoNum tenha valor 6 e considere a seguinte chamada de função:
trocaValores(primeiroNum, segundoNum);
Depois desta chamada de função, o valor de primeiroNum será 6 e o valor de segundoNum será 5.
Como mostra o Painel 4.2, a definição da função trocaValores utiliza uma variável local chamada temp. A
variável local é necessária. Você pode ser levado a pensar que a definição de função poderia ser simplificada
para
void trocaValores(int& variavel1, int& variavel2);
{

}
variavel1 = variavel2;
variavel2 = variavel1;
> Isto não funciona!

Para verificar que esta definição alternativa não funciona, pense no que poderia acontecer com essa defini-
ção e a chamada de função
trocaValores(primeiroNum, segundoNum);
As variáveis primeiroNum e segundoNum substituiriam os parâmetros formais variavel1 e variavel2, de
modo que, com esta definição de função incorreta, a chamada de função seria equivalente a:
primeiroNum = segundoNum;
segundoNum = primeiroNum;
Parâmetros 97

Este código não produz o resultado desejado. O valor de primeiroNum é fixado como igual ao valor de se-
gundoNum, como deveria ser. Mas, então, o valor de segundoNum é fixado como igual ao valor alterado de
primeiroNum, que agora é o valor original de segundoNum. Assim, o valor de segundoNum não é alterado. (Se
isso não estiver claro para você, atribua valores específicos às variáveis primeiroNum e segundoNum e refaça o
processo.) O que a função precisa fazer é salvar o valor original de primeiroNum, para que o valor não seja
perdido. É para isso que se utiliza a variável local temp na definição de função correta. A definição correta é
aquela apresentada no Painel 4.2. Quando esta versão correta é utilizada e a função é chamada com os ar-
gumentos primeiroNum e segundoNum, a chamada de função é equivalente ao código seguinte, que funciona
corretamente:
temp = primeiroNum;
primeiroNum = segundoNum;
segundoNum = temp;

■ PARÂMETROS DE REFERÊNCIA CONSTANTES


Colocamos esta subseção aqui porque um dos objetivos deste livro é servir como referência. Se você estiver lendo o
livro na seqüência, pode pular esta seção. O tópico será explicado com mais detalhes posteriormente.
Se você colocar um const antes de um tipo de parâmetro chamado por referência, obterá um parâmetro cha-
mado por referência que não pode ser alterado. Para os tipos que vimos até agora, isso não apresenta vantagens.
Entretanto, vai se revelar um recurso bastante eficiente com vetores e parâmetros de tipo classe. Discutiremos esses
parâmetros constantes quando falarmos em vetores e classes.

IDica PENSE EM AÇÕES, NÃO EM CÓDIGO


Embora possamos explicar como uma chamada de função atua em termos de substituir uma chamada de
função pelo código, não é assim que costumamos pensar em uma chamada de função. Em vez disso, você
deve pensar em uma chamada de função como uma ação. Por exemplo, considere a função trocaValores no
Painel 4.2 e uma invocação como
trocaValores(primeiroNum, segundoNum);
É mais fácil e mais claro pensar nessa chamada de função como a ação de trocar os valores desses dois ar-
gumentos. É muito mais obscuro pensar nela como o código
temp = primeiroNum;
primeiroNum = segundoNum;
segundoNum = temp;

1 Exercícios de Autoteste 1

3. Qual é a saída do seguinte programa?


#include <iostream>
using namespace std;

void descubra(int& x, int y, int& z);

int main( )
{
int a, b, c;
a = 10;
b = 20;
c = 30;
descubra(a, b, c);
cout << a << “ ” << b << “ ” << c << endl;
return 0;
}
void descubra (int& x, int y, int & z)
{
98 Parâmetros e Sobrecarga

Exercícios de Autoteste (continuação)


cout << x << " " << y << " " << z << endl;
x = 1;
y = 2;
z = 3;
cout << x << " " << y << " " << z << endl;
}
4. Qual seria a saída do programa no Painel 4.2 caso se omitisse o “e” comercial (&) do primeiro parâme-
tro na declaração de função e do cabeçalho da função trocaValores? O “e” comercial não é removido
do segundo parâmetro. Suponha que o usuário digite os números como no diálogo programa-usuário no
Painel 4.2.
5. Escreva uma definição de função void para uma função chamada zeroAmbos que possui dois parâmetros
chamados por referência, sendo ambos variáveis do tipo int, e fixa os valores de ambas as variáveis
como 0.
6. Escreva uma definição de função void para uma função chamada somaImposto. A função chamada so-
maImposto possui dois parâmetros formais: taxaImposto, que é a quantia do imposto sobre vendas ex-
pressa em porcentagem e custo, que é o custo de um item antes do imposto. A função altera o valor
de custo para incluir o imposto sobre vendas.

■ LISTA DE PARÂMETROS MISTOS


A definição de um parâmetro formal como sendo chamado por valor ou por referência é determinada pela
presença ou não de um “e” comercial anexo à sua especificação de tipo. Se o “e” comercial estiver presente, o pa-
râmetro formal é um parâmetro chamado por referência. Se não houver, é um parâmetro chamado por valor.

PARÂMETROS E ARGUMENTOS
Todos os termos diferentes que se referem a parâmetros e argumentos podem causar confusão. Entretanto, se você tiver em
mente algumas questões simples, poderá lidar com esses termos com facilidade.
1. Os parâmetros formais para uma função são listados na declaração de função e usados no corpo da definição de função. Um
parâmetro formal (de qualquer espécie) é um tipo de espaço em branco ou “guardador” de lugar que é preenchido com algu-
ma coisa quando a função é chamada.
2. Um argumento é algo que é usado para preencher um parâmetro formal. Quando se escreve uma chamada de função, os ar-
gumentos são listados entre parênteses depois do nome da função. Quando a chamada de função é executada, os argumen-
tos são conectados aos parâmetros formais.
3. Os termos chamada por valor e chamada por referência se referem ao mecanismo utilizado no processo de conexão. No mé-
todo da chamada por valor, apenas o valor do argumento é utilizado. No mecanismo de chamada por valor, o parâmetro for-
mal é uma variável local que é inicializada com o valor do argumento correspondente. No mecanismo de chamada por
referência, o argumento é uma variável e toda a variável é utilizada. No mecanismo de chamada por referência, a variável do
argumento substitui o parâmetro formal, de modo que qualquer mudança no parâmetro formal é, na realidade, feita na variá-
vel do argumento.

É perfeitamente legítimo misturar parâmetros formais chamados por valor e por referência na mesma função.
Por exemplo, o primeiro e o último argumentos formais na seguinte declaração de função são parâmetros formais
chamados por referência e o do meio é um programa chamado por valor.
void muitoBom(int& par1, int par2, double& par3);

Parâmetros chamados por referência não estão restritos a funções void. Pode-se, também, utilizá-los em fun-
ções que retornam um valor. Assim, uma função com um parâmetro chamado por referência tanto poderia alterar
o valor de uma variável dada quanto de um argumento e retornar um valor.

!Dica QUE TIPO DE PARÂMETRO UTILIZAR


O Painel 4.3 ilustra as diferenças entre como o compilador trata parâmetros formais chamados por valor e
chamados por referência. Aos dois parâmetros par1Valor e par2Ref é atribuído um valor dentro do corpo da
definição de função. No entanto, como são tipos diferentes de parâmetros, o efeito é diferente nos dois casos.
par1Valor é um parâmetro chamado por valor, portanto é uma variável local. Quando a função é chamada da
seguinte forma
Parâmetros 99

IDica (continuação)
efetueIsso(n1, n2);
a variável local par1Valor é inicializada com o valor de n1. Ou seja, a variável local par1Valor é inicializada
como 1, e a variável n1 é então ignorada pela função. Como você pode ver pelo diálogo programa-usuário,
o parâmetro formal par1Valor (que é a variável local) é fixado como 111 no corpo da função, e esse valor é
apresentado na tela. Entretanto, o valor do argumento n1 não é alterado. Como exibido no diálogo progra-
ma-usuário, n1 reteve o valor de 1.
Por outro lado, par2Ref é um parâmetro chamado por referência. Quando a função é chamada, o argumento
da variável n2 (não apenas seu valor) substitui o parâmetro formal par2Ref. Então, quando o seguinte códi-
go é executado
par2Ref = 222;
é o mesmo que se este outro código fosse executado:
n2 = 222;
Portanto, o valor da variável n2 é alterado quando o corpo da função é executado; assim, como o diálogo
mostra, o valor de n2 é alterado de 2 para 222 pela chamada de função.
Se você não se esquecer da lição do Painel 4.3, é fácil decidir que mecanismo de parâmetro utilizar. Se você
quer que uma função altere o valor de uma variável, então o parâmetro formal correspondente deve ser um
parâmetro formal chamado por referência e deve ser assinalado com um sinal de “e” comercial, &. Em todos
os outros casos, pode-se usar um parâmetro formal chamado por valor.

Painel 4.3 Comparando mecanismos de argumentos


1 //Ilustra a diferença entre parâmetros chamados por valor
2 //e parâmetros chamados por referência.
3 #include <iostream>
4 using namespace std;
5 void doStuff(int par1Value, int& par2Ref);
6 //par1Valor é um parâmetro chamado por valor formal e
7 //par2Ref é um parâmetro chamado por referência formal.
8 int main( )
9 {
10 int n1, n2;
11
12 n1 = 1;
13 n2 = 2;
14 doStuff(n1, n2);
15 cout << “n1 depois da chamada de função = ” << n1 << endl;
16 cout << “n2 depois da chamada de função = ” << n2 << endl;
17 return 0;
18 }
19 void doStuff(int par1Value, int& par2Ref)
20 {
21 par1Value = 111;
22 cout << “par1Valor na chamada de função = ”
23 << par1Value << endl;
24 par2Ref = 222;
25 cout << “par2Ref na chamada de função = ”
26 << par2Ref << endl;
27 }

DIÁLOGO PROGRAMA-USUÁRIO
par1Valor na chamada de função = 111
par2Ref na chamada de função = 222
n1 depois da chamada de função = 1
n2 depois da chamada de função = 222
100 Parâmetros e Sobrecarga

DESCUIDOS COM VARIÁVEIS LOCAIS


Se você quer que uma função altere o valor de uma variável, o parâmetro formal correspondente deve ser
um parâmetro chamado por referência e, portanto, deve ter o “e” comercial, &, anexado ao seu tipo. Se você
omitir o “e” comercial, a função terá um parâmetro chamado por valor em vez de um parâmetro chamado
por referência. Quando o programa for executado, você descobrirá que a chamada de função não altera o
valor do argumento correspondente, porque um parâmetro formal chamado por valor é uma variável local.
Se o parâmetro tiver seu valor alterado na função, então, como com qualquer variável local, essa alteração
não exercerá efeito fora do corpo da função. Este é um erro que pode ser bastante difícil de perceber, por-
que o código parece certo.
Por exemplo, o programa no Painel 4.4 é similar ao programa no Painel 4.2, a não ser pelo fato de o “e”
comercial ter sido erroneamente omitido na função trocaValores. Em conseqüência, os parâmetros formais
variavel1 e variavel2 são variáveis locais. As variáveis de argumento primeiroNum e segundoNum nunca
substituem variavel1 e variavel2; variavel1 e variavel2 são, em vez disso, inicializadas com os valores de
primeiroNum e segundoNum. Então, os valores de variavel1 e variavel2 são trocados, mas os valores de pri-
meiroNum e segundoNum permanecem inalterados. A omissão de dois “ee” comerciais tornou o programa to-
talmente errado e, no entanto, ele parece quase idêntico ao programa correto e compilará e será executado
sem qualquer mensagem de erro.

IDica ESCOLHENDO NOMES DE PARÂMETROS FORMAIS


As funções devem ser módulos independentes projetados separadamente do resto do programa. Em grandes
projetos de programação, programadores diferentes são contratados para escrever funções diferentes. O pro-
gramador deve escolher os nomes mais descritivos que encontrar para os parâmetros formais. Os argumen-
tos que substituirão os parâmetros formais podem ser variáveis em outra função ou na função main. Essas
variáveis também devem receber nomes descritivos, muitas vezes escolhidos por outra pessoa que não o
programador que escreve a definição de função. Isso torna provável que alguns ou todos os argumentos te-
nham os mesmos nomes que alguns dos parâmetros formais. Isso é perfeitamente aceitável. Não importa
que nomes sejam escolhidos para as variáveis que serão utilizadas como argumentos, esses nomes não cau-
sarão qualquer confusão com os nomes empregados para os parâmetros formais.

COMPRANDO PIZZA
Nem sempre sai mais barato comprar o produto de maior tamanho. Isso é especialmente verdade em se tra-
tando de pizzas. Nos Estados Unidos, os tamanhos das pizzas são dados pelo diâmetro da pizza em pole-
gadas. Entretanto, a quantidade de pizza é determinada pela área da pizza, e a área da pizza não é
proporcional ao diâmetro. A maioria das pessoas não consegue estimar com facilidade a diferença de área
entre uma pizza de dez polegadas e uma de doze e, assim, não consegue decidir facilmente que tamanho é
o melhor para se comprar — isto é, que tamanho tem o preço mais baixo por polegada quadrada (1polega-
da quadrada = 6,4516 cm2). O Painel 4.5 mostra um programa que o consumidor pode utilizar para decidir
qual dos dois tamanhos de pizza comprar.
Observe que as funções getDados e forneceResultados possuem os mesmos parâmetros, mas como getDa-
dos alterará os valores de seus argumentos, seus parâmetros são chamados por referência. Por outro lado,
forneceResultados só necessita dos valores de seus argumentos e, assim, seus parâmetros são chamados
por valor.
Note também que forneceResultados possui duas variáveis locais e que seu corpo de função inclui chama-
das para as funções precoUnidade. Finalmente, observe que a função precoUnidade tem tanto as variáveis
locais quanto uma variável local definidas como constantes.

Painel 4.4 Descuidos com variáveis locais (parte 1 de 2)


1 //Programa para demonstrar parâmetros chamados por referência.
2 #include <iostream>
3 using namespace std;
4 void getNumbers(int& input1, int& input2);
5 //Lê dois inteiros a partir do teclado. Esqueça o & aqui.
6
~-----~
void swapValues(int variable1, int variable2);
7 //Troca os valores de variable1 e variable2.
Parâmetros 101

Painel 4.4 Descuidos com variáveis locais (parte 2 de 2)


8 void showResults(int output1, int output2);
9 //Mostra os valores de variable1 e variable2, nessa ordem.

10 int main( )
11 {
12 int firstNum, secondNum;

13 getNumbers(firstNum, secondNum);
14 swapValues(firstNum, secondNum);
15 showResults(firstNum, secondNum);
16 return 0; Esqueça o & aqui.
17 }

18 void swapValues(int variable1, int variable2)


19 {
20 int temp;

21 temp = variable1; Descuido com variáveis locais.


22 variable1 = variable2;
23 variable2 = temp;
24 }
25 As definições de getNumbers e
26 showResults são as mesmas do Painel 4.2.

DIÁLOGO PROGRAMA-USUÁRIO
Forneça dois inteiros: 5 6
Em ordem inversa os números são: 5 6

Painel 4.5 Comprando pizza (parte 1 de 3)


---- Erro devido ao descuido
com variáveis locais.

1 //Determina qual dos dois tamanhos de pizza é o melhor para comprar.


2 #include <iostream>
3 using namespace std;

4 void getData(int& smallDiameter, double& priceSmall,


5 int& largeDiameter, double& priceLarge);

6 void giveResults(int smallDiameter, double priceSmall,


7 int largeDiameter, double priceLarge);

8 double unitPrice(int diameter, double price);


9 //Fornece o preço por polegada quadrada de uma pizza.
10 //Pré-condição: O parâmetro diameter é o diâmetro da pizza
11 //em polegadas. O parâmetro price é o preço da pizza.

12 int main( ) As variáveis diameterSmall, diameterLarge,


13 { priceSmall e priceLarge são utilizadas para
14 int diameterSmall, diameterLarge;
transportar dados da função getData para a
15 double priceSmall, priceLarge;
função giveResults.
16 getData(diameterSmall, priceSmall, diameterLarge, priceLarge) ;
17 giveResults(diameterSmall, priceSmall, diameterLarge, priceLarge) ;
18 return 0;
19 }
20 void getData(int& smallDiameter, double& priceSmall,
102 Parâmetros e Sobrecarga

Painel 4.5 Comprando pizza (parte 2 de 3)


21 int& largeDiameter, double& priceLarge)
22 {
23 cout << “Bem-vindo à União dos Consumidores de Pizza.\n”;
24 cout << “Informe o diâmetro de uma pizza pequena (em polegadas): ”;
25 cin >> smallDiameter;
26 cout << “Informe o preço de uma pizza pequena: $”;
27 cin >> priceSmall;
28 cout << “Informe o diâmetro de uma pizza grande (em polegadas): ”;
29 cin >> largeDiameter;
30 cout << “Informe o preço de uma pizza grande: $”;
31 cin >> priceLarge;
32 }
33
34 void giveResults(int smallDiameter, double priceSmall,
35 int largeDiameter, double priceLarge)
36 { Uma função chamada
37

38
double unitPriceSmall, unitPriceLarge;

unitPriceSmall = unitPrice(smallDiameter, priceSmall) ;


/ dentro de outra função.

39 unitPriceLarge = unitPrice(largeDiameter, priceLarge) ;


40 cout.setf(ios::fixed);
41 cout.setf(ios::showpoint);
42 cout.precision(2);
43 cout << “Pizza pequena:\n”
44 << “Diâmetro = ” << smallDiameter << “ polegadas\n”
45 << “Preço = $” << priceSmall
46 << “ Por polegada quadrada = $” << unitPriceSmall << endl
47 << “Pizza grande:\n”
48 << “Diâmetro = ” << largeDiameter << “ polegadas\n”
49 << “Preço = $” << priceLarge
50 << “ Por polegada quadrada = $” << unitPriceLarge << endl;
51 if (unitPriceLarge < unitPriceSmall)
52 cout << “É melhor comprar a grande.\n”;
53 else
54 cout << “É melhor comprar a pequena.\n”;
55 cout << “Buon Appetito!\n”;
56 }
57 double unitPrice(int diameter, double price)
58 {
59 const double PI = 3.14159;
60 double radius, area;
61 radius = diameter/static_cast<double>(2);
62 area = PI * radius * radius;
63 return (price/area);
64 }

DIÁLOGO PROGRAMA-USUÁRIO
Bem-vindo à União dos Consumidores de Pizza.
Informe o diâmetro de uma pizza pequena (em polegadas): 10
Informe o preço de uma pizza pequena: $7.50
Informe o diâmetro de uma pizza grande (em polegadas): 13
Informe o preço de uma pizza grande: $14.75
Pizza pequena:
Diâmetro = 10 polegadas
Preço = $7.50 Por polegada quadrada = $0.10
Pizza grande:
Sobrecarga e Argumentos-Padrão 103

Painel 4.5 Comprando pizza (parte 3 de 3)

Diâmetro = 13 polegadas
Preço = $14.75 Por polegada quadrada = $0.11
É melhor comprar a pequena.
Buon Appetito!

1 Exercícios de Autoteste 1

7. Qual seria a saída do programa no Painel 4.3 se você alterasse a declaração da função efetueIsso para
a seguinte linha e alterasse o cabeçalho da função para combinar, de modo que o parâmetro formal
par2Ref fosse alterado para um parâmetro chamado por valor?
void efetueIsso(int par1Valor, int par2Valor);

4.2 1 Sobrecarga e Argumentos-Padrão


— … e isso mostra que há trezentos e sessenta e quatro dias em que você poderia ganhar um presente de não-
aniversário…
— É verdade — reconheceu Alice.
— E apenas um para presentes de aniversário, você sabe. Isto é a glória para você!
— Não sei o que você quer dizer com “glória” — disse Alice.
Humpty Dumpty sorriu com desdém.
— Claro que você não sabe… até eu lhe contar. Quero dizer “este é um argumento arrasador para você!”
— Mas “glória” não quer dizer “um argumento arrasador” — objetou Alice.
— Quando eu uso uma palavra — disse Humpty Dumpty, em tom de desprezo —, ela quer dizer exatamente
o que eu quero que signifique. Nem mais nem menos.
— A questão — observou Alice — é se você pode fazer as palavras significarem tantas coisas diferentes.
— A questão é — disse Humpty Dumpty — quem é que manda. Só isso.
Lewis Carroll, Através do Espelho

O C++ permite que você dê duas ou mais definições diferentes para o mesmo nome de função, o que significa
que você pode reutilizar nomes com forte apelo intuitivo em uma grande variedade de situações. Por exemplo,
você poderia ter três funções chamadas max: uma que calcula o maior de dois números, outra que calcula o maior
de três números e ainda outra que calcula o maior de quatro números. Diz-se que dar duas (ou mais) definições
de função para o mesmo nome de função é sobrecarregar o nome da função.

■ INTRODUÇÃO À SOBRECARGA
Suponha que você escreva um programa que exija que se calcule a média de dois números. Você poderia usar
a seguinte definição de função:
double med(double n1, double n2)
{
return ((n1 + n2)/2.0);
}

Agora suponha que seu programa exija também uma função para calcular a média de três números. Você po-
deria definir uma nova função chamada med3 da seguinte forma:
double med3(double n1, double n2, double n3)
{
return ((n1 + n2 + n3)/3.0);
}
104 Parâmetros e Sobrecarga

Isto funcionará, e em muitas linguagens de programação você não tem escolha exceto fazer algo assim. Entretanto,
o C++ permite uma solução mais elegante. Em C++ você pode simplesmente utilizar o mesmo nome de função med
para ambas as funções. Em C++, você faz a seguinte definição de função em lugar da definição de função med3:
double med(double n1, double n2, double n3)
{
return ((n1 + n2 + n3)/3.0);
}

de modo que o nome de função med possua duas definições. Este é um exemplo de sobrecarga. Nesse caso, sobrecarre-
gamos o nome de função med. O Painel 4.6 contém essas duas definições de função para med dentro de um programa-
amostra completo. Não deixe de observar que cada definição de função possui sua própria declaração (protótipo).
O compilador pode dizer que definição de função utilizar verificando o número e o tipo dos argumentos em
uma chamada de função. No programa do Painel 4.6, uma das funções chamadas med possui dois argumentos, e
a outra, três. Quando há dois argumentos em uma chamada de função, aplica-se a primeira definição. Quando há
três, aplica-se a segunda definição.

SOBRECARREGANDO UM NOME DE FUNÇÃO


Caso se tenha uma ou mais definições de função para o mesmo nome de função, isso se chama sobrecarga. Quando se sobre-
carrega um nome de função, as definições da função devem ter números diferentes de parâmetros formais ou alguns parâmetros
formais de tipos diferentes. Quando há uma chamada de função, o compilador utiliza a definição de função cujo número de pa-
râmetros formais e tipos de parâmetros formais combina com os argumentos na chamada de função.

Sempre que são dadas duas ou mais definições para o mesmo nome de função, as várias definições de função
devem ter diferentes especificações para seus argumentos; ou seja, quaisquer duas definições de função que pos-
suam o mesmo nome de função devem usar números diferentes de parâmetros formais ou possuir um ou mais pa-
râmetros de tipos diferentes (ou ambos). Observe que, quando se sobrecarrega um nome de função, as declarações
para as duas definições diferentes devem diferir em seus parâmetros formais. Não se pode sobrecarregar uma função
dando duas definições que diferem apenas no tipo do valor fornecido. Também não se pode sobrecarregar com base
em qualquer diferença que não seja a da quantidade ou dos tipos de parâmetros. Não se pode sobrecarregar com base
apenas em const ou em uma diferença de parâmetro chamado por valor versus parâmetro chamado por referência.1

Painel 4.6 Sobrecarregando um nome de função (parte 1 de 2)


1 //Ilustra a sobrecarga da função med.
2 #include <iostream>
3 using namespace std;

4 double ave(double n1, double n2);


5 //Retorna a média de dois números n1 e n2.
6
7 double ave(double n1, double n2, double n3);
8 //Retorna a média de três números n1, n2 e n3.

9 int main( )
10 {
11 cout << “A média de 2.0, 2.5 e 3.0 é ”
12 << ave(2.0, 2.5, 3.0) << endl;

13 cout << “A média de 4.5 e 5.5 é ”


14 << ave(4.5, 5.5) << endl;

15 return 0;
16 }

1. Alguns compiladores, na realidade, permitem que se sobrecarregue com base em const versus não const, mas você não
deve contar com isso. O padrão C++ diz que isso não é permitido.
Sobrecarga e Argumentos-Padrão 105

Painel 4.6 Sobrecarregando um nome de função (parte 2 de 2)


17 double ave(double n1, double n2) Dois argumentos
18 {
19 return ((n1 + n2)/2.0);

20 }

21 double ave(double n1, double n2, double n3)


22 {
23
24 }
return ((n1 + n2 + n3)/3.0);
----- Três argumentos

DIÁLOGO PROGRAMA-USUÁRIO
A média de 2.0, 2.5 e 3.0 é 2.5
A média de 4.5 e 5.5 é 5.0

Você já viu um tipo de sobrecarga no Capítulo 1 (revisto aqui) com o operador de divisão, /. Se ambos os operan-
dos são do tipo int, como em 13/2, o valor retornado é o resultado da divisão de inteiros, nesse caso, 6. Por ou-
tro lado, se um operando ou ambos são do tipo double, o valor retornado é o resultado da divisão regular; por
exemplo, 13/2.0 retorna o valor 6.5. Existem duas definições para o operador de divisão, /, e as duas definições
diferem não por terem números diferentes de operandos, e sim por exigirem operandos de tipos diferentes. A úni-
ca diferença entre sobrecarregar o / e sobrecarregar nomes de funções está em que os criadores da linguagem C++
já fizeram a sobrecarga de /, enquanto a sobrecarga dos seus nomes de função deve ser programada por você mes-
mo. O Capítulo 8 discute como sobrecarregar operadores como +, -, e assim por diante.

ASSINATURA
A assinatura de uma função é o nome da função com a seqüência de tipos na lista de parâmetros, não incluindo a palavra-chave
const nem o “e” comercial, &. Quando você sobrecarrega um nome de função, as duas definições do nome de função devem ter as-
sinaturas diferentes, utilizando essa definição de assinatura. (Alguns especialistas incluem const e/ou o “e” comercial como parte da
assinatura, mas queríamos uma definição que funcionasse para explicar a sobrecarga.)

CONVERSÃO AUTOMÁTICA DE TIPO E SOBRECARGA


Suponha que a seguinte definição de função ocorra em seu programa e que você não tenha sobrecarregado
o nome de função mpg (então esta é a única definição de uma função chamada mpg).
double mpg(double milhas, double galoes)
//Retorna milhas por galão.
{
return (milhas/galoes);
}
Se você chamar a função mpg com argumentos de tipo int, então o C++ converterá automaticamente qual-
quer argumento de tipo int em um valor de tipo double. Dessa forma, a linha seguinte apresentará como
saída 22.5 milhas por galão:
cout << mpg(45, 2) << “ milhas por galão”;
O C++ converte o 45 em 45.0 e o 2 em 2.0 e, então, executa a divisão 45.0/2.0 e obtém o valor a ser re-
tornado, que é 22.5.
Se uma função requer um argumento de tipo double e você lhe fornece um argumento de tipo int, o C++
converterá automaticamente o argumento int em um valor de tipo double. Isso é tão útil e natural que
nem nos damos conta do processo. Entretanto, a sobrecarga pode interferir com essa conversão automática
de tipos. Vamos ver um exemplo.
Suponha que você tenha (tolamente) sobrecarregado o nome de função mpg, de modo que seu programa
contenha a seguinte definição de mpg além da anterior:
int mpg(int gols, int erros)
//Retorna a Medida de Gols Perfeitos
//que é calculada como (gols - erros).
106 Parâmetros e Sobrecarga

(continuação)
{
return (gols - erros);
}
Em um programa que contém ambas as definições para o nome de função mpg, a linha seguinte (infelizmen-
te) apresentará como saída 43 milhas por galão (já que 43 é 45 - 2):
cout << mpg(45, 2) << “ milhas por galão”;
Quando o C++ vê a chamada de função mpg(45, 2), que possui dois argumentos de tipo int, o C++ pri-
meiro procura por uma definição de função de mpg que possua dois parâmetros formais de tipo int. Se en-
contrar tal definição de função, o C++ utiliza essa definição de função. O C++ não converte um
argumento int em um valor de tipo double a não ser que esta seja a única forma de encontrar uma defini-
ção de função que combine.
O exemplo mpg ilustra mais uma questão a respeito da sobrecarga: você não deve usar o mesmo nome de função
para duas funções não-relacionadas. Esse descuido no uso dos nomes de função acaba produzindo confusão.

1 Exercícios de Autoteste 1

8. Suponha que você tenha duas definições de função com as seguintes declarações:
double placar(double tempo, double distancia);
int placar(double pontos);
Que definição de função seria usada na seguinte chamada de função e por que seria esta a usada? (x é
do tipo double.)
double placarFinal = placar(x);
9. Suponha que você tenha duas definições de função com as seguintes declarações:
double aResposta(double dado1, double dado2);
double aResposta(double tempo, int contagem);
Que definição de função seria usada na seguinte chamada de função e por que seria esta a usada? (x e
y são do tipo double.)
x = aResposta(y, 6.0);

■ REGRAS PARA RESOLVER SOBRECARGA


Se você usar sobrecarga para produzir duas definições do mesmo nome de função com listas de parâmetros si-
milares (mas não idênticas), a interação da sobrecarga e da conversão automática de tipos pode causar confusões.
As regras que o compilador utiliza para resolver qual das múltiplas definições sobrecarregadas de um nome de fun-
ção aplicar a uma dada chamada de função são as seguintes:
1. Identidade perfeita: se o número e os tipos dos argumentos são exatamente iguais à definição (sem qual-
quer conversão automática de tipos), então essa é a definição usada.
2. Identidade com conversão automática de tipos: se não há uma identidade perfeita, mas há uma identidade
por meio da conversão automática de tipos, então essa definição é usada.
Se duas identidades são encontradas no estágio 1 ou se nenhuma for encontrada no estágio 1 e duas forem
encontradas no estágio 2, então há uma situação ambígua e uma mensagem de erro é emitida.
Por exemplo, a seguinte sobrecarga é de estilo dúbio, mas é perfeitamente válida:
void f(int n, double m);
void f(double n, int m);

Entretanto, se você tiver também a invocação


f(98, 99);

o compilador não sabe qual dos dois argumentos int converter para um valor de tipo double, e uma mensagem
de erro é gerada.
Para ver quão confusa e perigosa pode ser a situação, suponha que você acrescente a seguinte terceira sobrecarga:
void f(int n, int m);
Sobrecarga e Argumentos-Padrão 107

Com o acréscimo dessa terceira sobrecarga, você não recebe mais uma mensagem de erro, já que agora há uma
identidade perfeita. Obviamente, sobrecargas confusas como essas devem ser evitadas.
As duas regras mencionadas funcionarão em quase todas as situações. De fato, se você precisa de regras mais
precisas, deve reescrever seu código para ser mais compreensível. Entretanto, as regras exatas são ainda mais com-
plicadas. Para cumprir com nosso objetivo de fazer um livro de referências, fornecemos as regras exatas a seguir.
Alguns dos termos podem não fazer sentido para você até que se leiam mais capítulos deste livro, mas não se
preocupe. As duas regras simples servirão até que você entenda as mais complicadas.
1. Identidade perfeita, como descrito anteriormente.
2. Identidades utilizando promoções dentro de tipos inteiros ou dentro de tipos de ponto flutuante, como de
short para int ou float para double. (Observe que conversões de bool para int e char para int são
consideradas promoções dentro dos tipos inteiros.)
3. Identidades utilizando outras conversões de tipos predefinidos, como de int para double.
4. Identidades utilizando conversões de tipos definidos pelo usuário (veja Capítulo 8).
5. Identidades utilizando elipses... (Este assunto não será abordado neste livro, e, se você não o utilizar, não
haverá problemas.)
Se duas identidades forem encontradas no primeiro estágio em que uma identidade é encontrada, então há
uma situação ambígua e uma mensagem de erro será emitida.

PROGRAMA “COMPRANDO PIZZA” REVISADO


A União dos Consumidores de Pizza gostou muito do programa que escrevemos para ela no Painel 4.5.
Agora todos querem comprar a pizza proporcionalmente mais barata. Uma pizzaria desonesta costumava
ganhar dinheiro enganando os consumidores, fazendo-os comprar a pizza mais cara, mas nosso programa
pôs um fim nessa prática maléfica. Entretanto, os proprietários quiseram continuar com esse comportamen-
to desprezível e inventaram um novo jeito de enganar os consumidores. Eles agora oferecem tanto pizzas
redondas como retangulares. Eles sabem que o programa que escrevemos não consegue lidar com pizzas re-
tangulares, e esperam poder confundir mais uma vez os consumidores. O Painel 4.7 é outra versão de nos-
so programa que compara uma pizza redonda e uma pizza retangular. Observe que o nome de função
precoUnidade foi sobrecarregado para podermos aplicá-lo tanto a pizzas redondas quanto a retangulares.

Painel 4.7 Programa “comprando pizza” revisado (parte 1 de 3)


1 //Determina se é melhor comprar a pizza redonda ou a retangular.
2 #include <iostream>
3 using namespace std;

4 double unitPrice(int diameter, double price) ;


5 //Retorna o preço por polegada quadrada de uma pizza redonda.
6 //O parâmetro formal chamado diameter é o diâmetro da pizza
7 //em polegadas. O parâmetro formal chamado price é o preço da pizza.

8 double unitPrice(int length, int width, double price) ;


9 //Retorna o preço por polegada quadrada de uma pizza retangular
10 //com dimensões de comprimento e largura em polegadas.
11 //O parâmetro formal price é o preço de pizza.

12 int main( )
13 {
14 int diameter, length, width;
15 double priceRound, unitPriceRound,
16 priceRectangular, unitPriceRectangular;

17 cout << “Bem-vindo à União dos Consumidores de Pizza.\n”;


18 cout << “Informe o diâmetro em polegadas”
19 << “ de uma pizza redonda: ”;
20 cin >> diameter;
21 cout << “Informe o preço de uma pizza redonda: $”;
108 Parâmetros e Sobrecarga

Painel 4.7 Programa “comprando pizza” revisado (parte 2 de 3)


22 cin >> priceRound;
23 cout << “Informe o comprimento e a largura em polegadas\n”
24 << “de uma pizza retangular: ”;
25 cin >> length >> width;
26 cout << “Informe o preço de uma pizza retangular: $”;
27 cin >> priceRectangular;
28 unitPriceRectangular =
29 unitPrice(length, width, priceRectangular);
30 unitPriceRound = unitPrice(diameter, priceRound);
31 cout.setf(ios::fixed);
32 cout.setf(ios::showpoint);
33 cout.precision(2);
34 cout << endl
35 << “Pizza redonda: Diâmetro = ”
36 << diameter << “ polegadas\n”
37 << “Preço = $” << priceRound
38 << “ Por polegada quadrada = $” << unitPriceRound
39 << endl
40 << “Pizza retangular: Largura = ”
41 << length << “ polegadas\n”
42 << “Pizza retangular: Width = ”
43 << width << “ polegadas\n”
44 << “Preço = $” << priceRectangular
45 << “ Por polegada quadrada = $” << unitPriceRectangular
46 << endl;
47 if (unitPriceRound < unitPriceRectangular)
48 cout << “É melhor comprar a redonda.\n”;
49 else
50 cout << “É melhor comprar a retangular.\n”;
51 cout << “Buon Appetito!\n”;
52 return 0;
53 }
54 double unitPrice(int diameter, double price)
55 {
56 const double PI = 3.14159;
57 double radius, area;
58
59 radius = diameter/double(2);
60 area = PI * radius * radius;
61 return (price/area);
62 }
63 double unitPrice(int lenght, int width, double price)
64 {
65 double area = length * width;
66 return (price/area);
67 }
DIÁLOGO PROGRAMA-USUÁRIO
Bem-vindo à União dos Consumidores de Pizza.
Informe o diâmetro em polegadas de uma pizza redonda: 10
Informe o preço de uma pizza redonda: $8.50
Informe o comprimento e a largura em polegadas
de uma pizza retangular: 6 4
Informe o preço de uma pizza retangular: $7.55
Sobrecarga e Argumentos-Padrão 109

Painel 4.7 Programa “comprando pizza” revisado (parte 3 de 3)

Pizza redonda: Diâmetro = 10 polegadas


Preço = $8.50 Por polegada quadrada = $0.11
Pizza retangular: Comprimento = 6 polegadas
Pizza retangular: Largura = 4 polegadas
Preço = $7.55 Por polegada quadrada = $0.31
É melhor comprar a redonda.
Buon Appetito!

■ ARGUMENTOS-PADRÃO
Você pode especificar um argumento-padrão para um ou mais parâmetros chamados por valor em uma fun-
ção. Se o argumento correspondente for omitido, então é substituído pelo argumento-padrão. Por exemplo, a fun-
ção volume no Painel 4.8 calcula o volume de uma caixa a partir de seus comprimento, largura e altura. Se
nenhuma altura for dada, presume-se que a altura seja 1. Se nem a largura nem a altura forem dadas, presume-se
que ambas sejam 1.
Observe que no Painel 4.8 os argumentos-padrão são dados na declaração de função, mas não na definição de
função. Um argumento-padrão é dado na primeira vez que a função é declarada (ou definida, se isso ocorrer pri-
meiro). Declarações subseqüentes ou uma definição a seguir não devem dar os argumentos-padrão novamente
porque alguns compiladores considerarão isso um erro mesmo se os argumentos dados forem con-
sistentes com aqueles dados anteriormente.
Você pode ter mais de um argumento-padrão, mas todas as posições dos argumentos-padrão devem ser as po-
sições mais à direita. Assim, para a função volume no Painel 4.8, poderíamos ter dado argumentos-padrão para o
último, os últimos dois ou três parâmetros, mas qualquer outra combinação de argumentos-padrão não é permitida.

Painel 4.8 Argumentos-padrão (parte 1 de 2)


1 Argumentos-padrão

~
2 #include <iostream>
3 using namespace std;
4 void showVolume(int length, int width = 1, int height = 1);
5 //Retorna o volume da caixa.
6
7

10
11
12
showVolume(4, 6, 2);
showVolume(4, 6);
showVolume(4);
\
//Se a altura não for dada, presume-se que ela seja 1.
//Se nem a altura nem a largura forem dadas, presume-se que ambas sejam 1.
8 int main( )
9 {
Um argumento-padrão não deveria
ser dado uma segunda vez.

13 return 0;
14 }
15 void showVolume(int length, int width, int height)
16 {
17 cout << “ Volume de uma caixa com\n”
18 << “Comprimento = ” << length << “, Largura = ” << width << endl
19 << “e Altura = ” << height
20 << “ é ” << length*width*height << endl;
21 }

DIÁLOGO PROGRAMA-USUÁRIO
Volume de uma caixa com
Comprimento = 4, Largura = 6
e Altura = 2 é 48
110 Parâmetros e Sobrecarga

Painel 4.8 Argumentos-padrão (parte 2 de 2)

Volume de uma caixa com


Comprimento = 4, Largura = 6
e Altura = 1 é 24
Volume de uma caixa com
Comprimento = 4, Largura = 1
e Altura = 1 é 4

Se houver mais de um argumento-padrão, quando a função é invocada você pode omitir argumentos a come-
çar da direita. Por exemplo, observe que no Painel 4.8 há dois argumentos-padrão. Quando só um argumento é
omitido, presume-se que seja o último argumento. Não há como se omitir o segundo argumento em uma invoca-
ção de volume sem omitir também o terceiro argumento.
Argumentos-padrão possuem valor limitado, mas às vezes podem ser usados para refletir sua maneira de pensar
a respeito de argumentos. Argumentos-padrão só podem ser usados com parâmetros chamados por valor. Eles não
fazem sentido com parâmetros chamados por referência. Qualquer coisa que se possa fazer com argumentos-pa-
drão também pode ser feita utilizando-se a sobrecarga, embora a versão com argumento-padrão provavelmente
possa ser mais curta do que a com sobrecarga.

1 Exercícios de Autoteste 1

10. Esta pergunta tem a ver com o exemplo de programação intitulado Programa “Comprando Pizza” Re-
visado. Suponha que a pizzaria desonesta que está sempre tentando enganar os consumidores crie uma
pizza quadrada. Será que você pode sobrecarregar a função precoUnidade para que esta possa calcular o
preço por polegada quadrada de uma pizza quadrada, além do preço por polegada quadrada de uma
pizza redonda? Por que sim ou por que não?

4.3 1 Testando e Depurando Funções


Contemplei o infeliz — o monstro miserável que eu havia criado.
Mary Wollstonecraft Shelley, Frankenstein

Esta seção apresenta algumas orientações gerais para testar programas e funções.

■ MACRO assert
Uma asserção é um comando que é verdadeiro ou falso. As asserções são utilizadas para documentar e verificar
a correção de programas. Pré-condições e pós-condições, que discutimos no Capítulo 3, são exemplos de asserções.
Quando expressa adequadamente e na sintaxe de C++, uma asserção é simplesmente uma expressão booleana. Se
você converter uma asserção em uma expressão booleana, a macro predefinida assert pode ser usada para verificar
se seu código satisfaz ou não a asserção. (Uma macro é bastante semelhante a uma função inline e é usada exata-
mente como uma função.)
A macro assert é utilizada como uma função void que requer um parâmetro chamado por valor de tipo
bool. Como uma asserção não passa de uma expressão booleana, isso significa que o argumento para assert é
uma asserção. Quando a macro assert é invocada, seu argumento de asserção é avaliado. Se é avaliado como
true, então nada acontece. Se o argumento é avaliado como false, o programa termina e uma mensagem de erro
é enviada. Assim, chamadas à macro assert são uma forma compacta de incluir verificações de erro em seu pro-
grama.
Por exemplo, a seguinte declaração de função retirada do Projeto de Programação 3:
void calculaMoeda(int valorDaMoeda, int& numero, int& quantiaRestante);
//Pré-condição: 0 < valorDaMoeda < 100; 0 <= quantiaRestante < 100.
Testando e Depurando Funções 111

//Pós-condição: numero fixado como igual ao número máximo


//de moedas de denominação valorDaMoeda centavos que possa ser obtido
//a partir de quantiaRestante centavos. quantiaRestante diminui conforme
//o valor das moedas, ou seja, diminui de numero*valorDaMoeda.

Você pode verificar se essa pré-condição se sustenta para uma invocação de função, como mostra o seguinte exemplo:
assert((0 < moedaAtual) && (moedaAtual < 100)
&& (0 <= quantiaRestanteAtual) && (quantiaRestanteAtual < 100));
calculaMoeda(moedaAtual, numero, quantiaRestanteAtual);

Se a pré-condição não é satisfeita, seu programa terminará e enviará uma mensagem de erro.
A macro assert está definida na biblioteca cassert, portanto qualquer programa que utilizar a macro assert
deve conter a seguinte instrução:
#include <cassert>

Uma vantagem de utilizar assert é que você pode desativar invocações a assert. Você pode utilizar as invo-
cações a assert em seu programa para depurá-lo e, depois, desativá-las para que os usuários não recebam mensa-
gens de erro que talvez não entendam. Isso reduz os gastos de memória do seu programa. Para desativar todas as
asserções #define NDEBUG em seu programa, acrescente #define NDEBUG antes da instrução de include, da se-
guinte forma:
#define NDEBUG
#include <cassert>

Assim, se você inserir #define NDEBUG em seu programa depois de este estar totalmente depurado, todas as invoca-
ções a assert em seu programa serão desativadas. Se depois você alterar seu programa e precisar depurá-lo outra vez,
pode ativar as invocações novamente apagando a linha #define NDEBUG (ou transformando-a em comentário).
Nem todas as asserções de comentários podem ser facilmente traduzidas em expressões booleanas em C++. É
mais provável que as pré-condições sejam mais facilmente traduzidas que as pós-condições. Assim, a macro assert
não é uma panacéia para a depuração de suas funções, mas pode ser muito útil.

■ STUBS E DRIVERS
Cada função deveria ser projetada, codificada e testada como uma unidade separada do resto do programa.
Quando se trata cada função como uma unidade à parte, transforma-se uma tarefa grande em várias menores,
mais facilmente tratáveis. Mas como se testa uma função fora do programa para o qual foi projetada? Uma forma
é escrever um programa especial para fazer os testes. Por exemplo, o Painel 4.9 mostra um programa para testar a
função precoUnidade que foi usada no programa do Painel 4.5. Programas como esses são chamados de progra-
mas driver. Esses programas driver são ferramentas temporárias e podem ser bem pequenos. Não precisam ter ro-
tinas de entrada muito complexas. Não precisam executar todos os cálculos que o programa final executará. Tudo
o que precisam fazer é obter valores razoáveis para os argumentos da função da maneira mais simples possível
— normalmente do usuário — e então executar a função e mostrar o resultado. Um loop, como no pro-
grama mostrado no Painel 4.9, permitirá que se teste novamente a função com diferentes argumentos
sem ter de executar de novo o programa.

Painel 4.9 Programa driver (parte 1 de 2)


1
2 //Programa driver para a função unitPrice.
3 #include <iostream>
4 using namespace std;

5 double unitPrice(int diameter, double price);


6 //Retorna o preço por polegada quadrada de uma pizza.
7 //Pré-condição: O parâmetro diameter é o diâmetro da pizza
8 //em polegadas. O parâmetro price é o preço da pizza.

9 int main( )
112 Parâmetros e Sobrecarga

Painel 4.9 Programa driver (parte 2 de 2)


10 {
11 double diameter, price;
12 char ans;
13 do
14 {
15 cout << “Informe o diâmetro e o preço:\n”;
16 cin >> diameter >> price;

17 cout << “O preço por unidade é $”


18 . << unitPrice(diameter, price) << endl;

19 cout << “Mais um teste? (s/n)”;


20 cin >> ans;
21 cout << endl;
22 } while (ans == ’s’ || ans == ’S’);

23 return 0;
24 }
25
26 double unitPrice(int diameter, double price)
27 {
28 const double PI = 3.14159;
29 double radius, area;

30 radius = diameter/static_cast<double>(2);
31 area = PI * radius * radius;
32 return (price/area);
33 }

DIÁLOGO PROGRAMA-USUÁRIO
Informe o diâmetro e o preço:
13 14.75
O preço por unidade é: $0.111126
Mais um teste? (s/n): s
Informe o diâmetro e o preço:
2 3.15
O preço por unidade é: $1.00268
Mais um teste? (s/n): n

Se você testar cada função separadamente, descobrirá a maioria dos erros em seu programa. Além disso, descobrirá que
funções contêm os erros. Se você fosse testar apenas o programa inteiro, provavelmente descobriria que existe um erro, mas
talvez não tivesse a menor idéia de onde ele estaria. Pior ainda, poderia pensar que sabe onde está e se enganar.
Uma vez que tenha testado completamente uma função, você pode usá-la no programa driver para alguma ou-
tra função. Cada função deve ser testada em um programa no qual é a única função ainda não testada. Entretan-
to, é bom usar uma função já testada quando se testa alguma outra função. Se um erro for encontrado, você
saberá que o erro está na função ainda não testada.
Às vezes é impossível ou inconveniente testar uma função sem utilizar alguma outra função que não tenha ain-
da sido escrita ou testada. Nesse caso, você pode usar uma versão simplificada da função que falta ou que não foi
testada. Essas funções simplificadas são chamadas de stubs. Os stubs não precisam, necessariamente, efetuar os cál-
culos corretos, e sim fornecer valores suficientes para o teste, e são tão simples que você pode ter confiança em
seu desempenho. Por exemplo, eis aqui um possível stub para a função precoUnidade:
//Um stub. A função final precisa ser escrita.
double precoUnidade(int diametro, double preco)
Resumo do Capítulo 113

{
return(9.99);//Não é correto mas é suficientemente bom para um stub.
}

Utilizar um esboço de programa com stubs permite que você teste e depois inicie o esboço básico do progra-
ma, em vez de escrever um programa completamente novo para testar cada função. Por essa razão, um esboço de
programa com stubs costuma ser o método mais eficiente para testes. Uma abordagem comum é utilizar progra-
mas drivers para testar algumas funções básicas, como as de entrada e saída, e depois utilizar um programa com
stubs para testar as funções restantes. Os stubs são substituídos por funções, um de cada vez: um stub é substituí-
do por uma função completa e testado; quando essa função já foi completamente testada, outro stub é substituído
por uma definição de função completa e assim por diante, até o programa final ser produzido.

REGRA FUNDAMENTAL PARA O TESTE DE FUNÇÕES


Cada função deve ser testada em um programa em que todas as outras funções já foram totalmente testadas e depuradas.

1 Exercícios de Autoteste 1

11. Qual é a regra fundamental para o teste de funções? Por que esta é uma boa forma de se testar fun-
ções?
12. O que é um programa driver?
13. O que é um stub?
14. Escreva um stub para a função cuja declaração é dada abaixo. Não escreva um programa inteiro, apenas
o stub que entraria em um programa. (Dica: o stub fica bem curto.)
double chuvaProb(double pressao, double umidade, double temp);
//Pré-condição: pressao é a pressão barométrica em polegadas de mercúrio.
//umidade é a umidade relativa como porcentagem, e
//temp é a temperatura em graus Fahrenheit.
//Retorna a probabilidade de chuva, que é um número entre 0 e 1.
//0 significa nenhuma probabilidade de chuva. 1 significa chuva com 100% de probabilidade.

Resumo do Capítulo
■ Um parâmetro formal é um tipo de “guardador” de lugar que é preenchido com um argumento de função
quando a função é chamada. Em C++, existem dois métodos para efetuar essa substituição, a chamada por valor
e a por referência. Assim, há dois tipos básicos de parâmetro: chamados por valor e chamados por referência.
■ Um parâmetro formal chamado por valor é uma variável local inicializada com o valor de seu argumento
correspondente quando a função é chamada. Ocasionalmente, é útil empregar um parâmetro formal cha-
mado por valor como uma variável local.
■ No mecanismo de substituição da chamada por referência, o argumento deve ser uma variável e toda a va-
riável é substituída pelo argumento correspondente.
■ O modo de indicar um parâmetro chamado por referência em uma definição de função é anexar o símbolo
de “e” comercial, &, ao tipo do parâmetro formal. (Um parâmetro chamado por valor é indicado pela au-
sência do “e” comercial.)
■ Um argumento correspondente a um parâmetro chamado por valor não pode ser alterado por uma chama-
da de função. Um argumento correspondente a um parâmetro chamado por referência pode ser alterado
por uma chamada de função. Se você quiser que uma função altere o valor de uma variável, é necessário
usar um parâmetro chamado por referência.
■ Podem-se dar múltiplas definições ao mesmo nome de função, desde que as diferentes funções com o mes-
mo nome possuam diferentes números de parâmetros ou algumas posições de parâmetro com tipos diferen-
tes, ou ambos. Isso se chama sobrecarga do nome de função.
■ Pode-se especificar um argumento-padrão para um ou mais parâmetros chamados por valor em uma fun-
ção. Argumentos-padrão são sempre as posições de argumento mais à direita.
114 Parâmetros e Sobrecarga

■ A macro assert auxilia a depuração de seus programas, verificando se as asserções se sustentam ou não.
■ Toda função deve ser testada em um programa em que todas as outras funções já foram completamente
testadas e depuradas.

RESPOSTAS DOS EXERCÍCIOS DE AUTOTESTE


1. Um parâmetro chamado por valor é uma variável local. Quando a função é invocada, o valor do argu-
mento chamado por valor é calculado e o correspondente parâmetro chamado por valor (que é uma variá-
vel local) é inicializado com esse valor.
2. A função atuará bem. Essa resposta é completa e suficiente, mas queremos apresentar uma informação adi-
cional: o parâmetro formal polegadas é um parâmetro chamado por valor, e portanto, como foi discuti-
do, é uma variável local. Assim, o valor do argumento não será alterado.
3. 10 20 30
1 2 3
1 20 3
4. Forneça dois inteiros: 5 10
Em ordem inversa os números são: 5 5
5. void zeroAmbos(int& n1, int& n2)
{
n1 = 0;
n2 = 0;
}
6. void somaImposto(double taxaImposto, double& custo)
{
custo = custo + (taxaImposto/100.0)*custo;
}
A divisão por 100 é para converter a porcentagem em uma fração. Por exemplo, 10% é 10/100.0, ou um
décimo do custo.
7. par1Valor na chamada de função = 111
par2Ref na chamada de função = 222
n1 depois da chamada de função = 1
n2 depois da chamada de função = 2 Diferente
8. Seria usada a definição de função com um parâmetro, porque a chamada de função tem apenas um parâ-
metro.
9. A primeira seria usada porque é uma identidade perfeita, já que há dois parâmetros de tipo double.
10. Isso não pode ser feito (pelo menos não de um jeito aceitável). O jeito natural de se representar uma
pizza quadrada e redonda é o mesmo. Cada uma é naturalmente representada como um número, que,
para a pizza redonda, é o raio e, para a pizza quadrada, é o comprimento de um lado. Em ambos os ca-
sos, a função precoUnidade precisa ter um parâmetro formal de tipo double para o preço e um parâmetro
formal de tipo int para o tamanho (raio ou lado). Assim, as duas declarações de função teriam o mesmo
número de tipos de parâmetros formais. (Especificamente, ambas teriam um parâmetro formal de tipo
double e um parâmetro formal de tipo int.) Logo, o compilador não seria capaz de decidir que definição
usar. Você ainda pode derrotar a estratégia da pizzaria desonesta definindo duas funções, mas elas precisa-
riam ter nomes diferentes.
11. A regra fundamental para testar funções é que cada função deve ser testada em um programa em que to-
das as outras funções já foram totalmente testadas e depuradas. Esta é uma boa forma de se testar uma
função porque, se você seguir essa regra, quando encontrar um erro, saberá qual função o contém.
12. Um programa driver é um programa escrito com o único propósito de testar uma função.
13. Um stub é uma versão simplificada de uma função usada no lugar da função para que outras funções pos-
sam ser testadas.
14. //ISTO É APENAS UM STUB
double chuvaProb(double pressao,
double umidade, double temp)
{
Projetos de Programação 115

return 0.25; //Não é correto,


//mas serve para fazer o teste.
}

PROJETOS DE PROGRAMAÇÃO
1. Escreva um programa que converta da notação de 24 horas para a notação de 12 horas. Por exemplo, o
programa deve converter 14:25 em 2:25 P.M. A entrada é dada em dois inteiros. Deve haver pelo menos
três funções: uma para a entrada, uma para fazer a conversão e uma para a saída. Registre a informação
A.M./P.M. como um valor de tipo char, ’A’ para A.M. e ’P’ para P.M. Assim, a função para efetuar as
conversões terá um parâmetro formal chamado por referência de tipo char para registrar se é A.M. ou
P.M. (A função terá outros parâmetros também.) Inclua um loop que permita que o usuário repita esse
cálculo para novos valores de entrada todas as vezes que desejar, até o usuário dizer que deseja encerrar o
programa.
2. A área de um triângulo arbitrário pode ser calculada por meio da fórmula
area = √

s(s − a)(s − b)(s −c)
onde a, b e c são as medidas dos lados e s é o semiperímetro.
s = (a + b + c) /2
Escreva uma função void que utilize cinco parâmetros: três parâmetros chamados por valor que forneçam
a medida dos lados e dois parâmetros chamados por referência que calculem a área e o perímetro (não o
semiperímetro). Torne sua função robusta. Observe que nem todas as combinações de a, b e c produzem
um triângulo. Sua função deve corrigir resultados para dados legais e resultados coerentes para combina-
ções ilegais.
3. Escreva um programa que diga quantas moedas retornar para qualquer quantia de 1 a 99 centavos. Por
exemplo, se a quantia é 86 centavos, a saída deve ser algo parecido com:
86 centavos podem ser fornecidos como
3 de 25 centavo(s), 1 de 10 centavo(s) e 1 de 1 centavo(s)
Utilize denominações para moedas de 25 centavos, 10 centavos e 1 centavo. Não utilize as moedas de 50
centavos nem de 5 centavos.
Seu programa utilizará a seguinte função (entre outras):
void calculaMoedas(int valorDaMoeda, int& numero, int& quantiaRestante);
//Pré-condição: 0 < valorDaMoeda < 100; 0 <= quantiaRestante < 100.
//Pós-condição: número fixado como igual ao número máximo
//de moedas de denominação valorDaMoeda centavos que possa ser obtido
//a partir de quantiaRestante centavos. quantiaRestante diminui conforme
//o valor das moedas, ou seja, diminui de numero*valorDaMoeda.
Por exemplo, suponha que o valor da variável quantiaRestante seja 86. Então, depois da seguinte chamada, o
valor de numero será 3 e o valor da quantiaRestante será 11 (porque se você tira 75 de 86, restam 11):
calculaMoedas(25, numero, quantiaRestante);
Inclua um loop que permita ao usuário repetir esse cálculo para novos dados de entrada até o usuário di-
zer que deseja encerrar o programa. (Dica: utilize divisão de inteiros e o operador % para implementar
essa função.)
4. Escreva um programa que leia um comprimento em pés e polegadas e apresente a saída equivalente em
metros e centímetros. Utilize pelo menos três funções: uma para entrada, uma ou mais para o cálculo e
uma para a saída. Inclua um loop que permita ao usuário repetir esse cálculo para novos dados de entrada
até o usuário dizer que deseja encerrar o programa. Existem 0.3048 metros em um pé, 100 centímetros
em um metro e 12 polegadas em um pé.
5. Escreva um programa como o do exercício anterior que converta metros e centímetros em pés e polega-
das. Utilize funções para as subtarefas.
6. (Você deve fazer os dois projetos de programação anteriores antes de fazer este.) Escreva um programa
que combine as funções dos dois projetos de programação anteriores. O programa pergunta ao usuário se
deseja converter pés e polegadas em metros e centímetros ou metros e centímetros em pés e polegadas.
116 Parâmetros e Sobrecarga

Então, o programa efetua a conversão desejada. Faça com que o usuário responda digitando o inteiro 1
para um tipo de conversão e 2 para o outro. O programa lê a resposta do usuário e executa o comando
if-else. Cada ramificação do comando if-else será uma chamada de função. As duas funções chamadas
no comando if-else terão definições de função bastante similares às dos programas dos dois projetos de
programação anteriores. Assim, serão definições de função bastante complexas que chamam outras fun-
ções. Inclua um loop que permita ao usuário repetir esse cálculo para novos dados de entrada até o usuá-
rio dizer que deseja encerrar o programa.
7. Escreva um programa que leia o peso em libras (1 libra = 453,59 gramas) e onças (1 onça = 28,34 gra-
mas) e apresente como saída o equivalente em quilogramas e gramas. Use pelo menos três funções: uma
para entrada, uma ou mais para o cálculo e uma para a saída. Inclua um loop que permita ao usuário re-
petir esse cálculo para novos dados de entrada até o usuário dizer que deseja encerrar o programa. Existem
2.2046 libras em um quilograma, 1.000 gramas em um quilograma e 16 onças em uma libra.
8. Escreva um programa como o do exercício anterior que converta quilogramas e gramas em libras e onças.
Utilize funções para as subtarefas.
9. (Você deve fazer os dois projetos de programação anteriores antes de fazer este.) Escreva um programa
que combine as funções dos dois projetos de programação anteriores. O programa pergunta ao usuário se
deseja converter libras e onças em quilogramas e gramas ou quilogramas e gramas em libras e onças. En-
tão o programa efetua a conversão desejada. Faça com que o usuário responda digitando o inteiro 1 para
um tipo de conversão e 2 para o outro. O programa lê a resposta do usuário e executa o comando if-
else. Cada ramificação do comando if-else será uma chamada de função. As duas funções chamadas no
comando if-else terão definições de função bastante similares às dos programas dos dois projetos de pro-
gramação anteriores. Assim, serão definições de função bastante complexas que chamam outras funções.
Inclua um loop que permita ao usuário repetir esse cálculo para novos dados de entrada até o usuário di-
zer que deseja encerrar o programa.
10. (Você deve fazer os Projetos de Programação 6 e 9 antes de fazer este.) Escreva um programa que combi-
ne as funções dos dois Projetos de Programação 6 e 9. O programa pergunta ao usuário se deseja conver-
ter comprimentos ou pesos. Se o usuário escolher comprimentos, o programa pergunta ao usuário se
deseja converter pés (1 pé = 30,5 cm) e polegadas (1 polegada = 2,54 cm) em metros e centímetros ou
metros e centímetros em pés e polegadas. Se o usuário escolher peso, uma pergunta similar é feita a res-
peito de libras, onças, quilogramas e gramas. Assim, o programa efetua a conversão desejada. Faça com
que o usuário responda digitando o inteiro 1 para um tipo de conversão e 2 para o outro. O programa lê
a resposta do usuário e executa o comando if-else. Cada ramificação do comando if-else será uma
chamada de função. As duas funções chamadas no comando if-else terão definições de função bastante
similares às dos programas dos Projetos de Programação 6 e 9. Observe que seu programa terá comandos
if-else inseridos dentro de comandos if-else, mas apenas de maneira indireta. O comando if-else ex-
terior incluirá duas chamadas de função, como suas duas ramificações. Essas duas chamadas de função,
por sua vez, incluirão um comando if-else, mas você não precisa pensar nisso. São apenas chamadas de
função e os detalhes estão na caixa preta que você cria quando define essas funções. Se você tentar criar
uma ramificação de quatro caminhos, provavelmente está na pista errada. Você só precisa pensar em rami-
ficações de dois caminhos (embora o programa inteiro se ramifique, no fim das contas, em quatro casos).
Inclua um loop que permita ao usuário repetir esse cálculo para novos dados de entrada, até o usuário di-
zer que deseja encerrar o programa.
CAPÍTULO

Vetores
Vetores

Capítulo 5Vetores
É um erro capital teorizar antes de ter os dados.
Sir Arthur Conan Doyle, Escândalo na Boêmia (Sherlock Holmes)

INTRODUÇÃO
Um vetor é usado para processar uma coleção de dados de mesmo tipo, como uma lista
de temperaturas ou uma lista de nomes. Este capítulo aborda os princípios básicos de de-
finição e utilização de vetores em C++ e apresenta muitas das técnicas básicas para proje-
tar algoritmos e programas que empregam vetores.
Se quiser, pode ler o Capítulo 6 e quase todo o Capítulo 7, que trata de classes, antes
de ler este capítulo. A única seção daqueles capítulos que utiliza conceitos deste é a Seção
7.3, que apresenta os vectors.

5.1 Introdução aos Vetores


Suponha que desejemos escrever um programa que leia cinco notas de provas e execu-
te algumas manipulações sobre essas notas. Por exemplo, o programa poderia calcular a
maior nota de prova e depois apresentar como saída a quantidade que faltou para cada
uma das outras provas se igualar à nota mais alta. Esta não é conhecida até que todas as
cinco notas sejam lidas. Dessa forma, todas as cinco notas devem ser armazenadas para
que, depois que a mais alta seja calculada, cada nota possa ser comparada com ela. Para
conservar as cinco notas, precisaremos de algo equivalente a cinco variáveis de tipo int.
Poderíamos usar cinco variáveis individuais de tipo int, mas cinco variáveis são difíceis de
controlar e depois poderemos querer mudar nosso programa para lidar com 100 notas;
com certeza, 100 variáveis é algo impraticável. Um vetor é a solução ideal. Um vetor
comporta-se como uma lista de variáveis com um mecanismo uniforme de nomeação que
pode ser declarado em uma única linha de código simples. Por exemplo, os nomes para as
cinco variáveis individuais que precisamos poderiam ser nota[0], nota[1], nota[2],
nota[3] e nota[4]. A parte que não muda, nesse caso, nota, é o nome do vetor. A parte
que pode mudar é o inteiro entre colchetes, [ ].

■ DECLARANDO E REFERENCIANDO VETORES


Em C++, um vetor que consiste em cinco variáveis de tipo int pode ser declarado da
seguinte forma:
int nota[5];

É como declarar as cinco variáveis seguintes como sendo todas de tipo int:
nota[0], nota[1], nota[2], nota[3], nota[4]
118 Vetores

É possível se referir de diversas formas a essas variáveis individuais que juntas constituem o vetor. Nós as cha-
maremos de variáveis indexadas, embora muitas vezes também sejam chamadas variáveis subscritas ou elementos
do vetor. O número entre colchetes é chamado índice ou subscrito. Em C++, os índices são numerados a começar
do 0, não do 1 nem de outro número que não seja o 0. O número de variáveis indexadas em um vetor é chamado
de tamanho declarado do vetor, ou às vezes simplesmente de tamanho do vetor. Quando um vetor é declarado, o
tamanho do vetor é dado entre colchetes depois do nome do vetor. As variáveis indexadas são, então, numeradas
(também utilizando colchetes), começando do 0 e terminando com um inteiro que seja um número inferior ao ta-
manho do vetor.
Em nosso exemplo, as variáveis indexadas eram do tipo int, mas um vetor pode ter variáveis indexadas de qualquer
tipo. Por exemplo, para declarar um vetor com variáveis indexadas de tipo double, é só usar o nome de tipo double
em vez de int na declaração do vetor. Todas as variáveis indexadas para um vetor, contudo, são de mesmo tipo. Esse
tipo é chamado de tipo-base de um vetor. Assim, em nosso exemplo do vetor nota, o tipo-base é int.
Podem-se declarar vetores e variáveis regulares juntos. Por exemplo, a linha seguinte declara as duas variáveis
int proximo e max, além do vetor nota:
int proximo, nota[5], max;

Uma variável indexada como nota[3] pode ser usada em qualquer lugar em que uma variável ordinária de
tipo int possa ser usada.
Não confunda as duas formas de se utilizar os colchetes, [ ], com um nome de vetor. Em uma declaração, como
int nota[5];

o número entre colchetes especifica quantas variáveis indexadas o vetor possui. Quando usado em qualquer outro
lugar, o número entre colchetes especifica a que variável indexada se refere. Por exemplo, nota[0] até nota[4] são
variáveis indexadas do vetor declarado acima.
O índice dentro dos colchetes não precisa ser fornecido como uma constante inteira. Pode-se usar qualquer
expressão entre colchetes, desde que essa expressão seja avaliada como um dos inteiros de 0 até o inteiro um nú-
mero inferior ao tamanho do vetor. Por exemplo, o trecho seguinte estabelecerá o valor de nota[3] como igual a
99:
int n = 2;
nota[n + 1] = 99;

Embora possam parecer diferentes, nota[n + 1] e nota[3] são a mesma variável indexada no código acima,
porque n + 1 é calculado como 3.
A identidade de uma variável indexada, como nota[i], é determinada pelo valor de seu índice, que, neste
exemplo, é i. Assim, você pode escrever programas que dizem algo como “faça isso e aquilo com a iésima variável
indexada”, em que o valor de i é calculado pelo programa. Por exemplo, o programa no Painel 5.1 lê notas e as
processa da forma descrita no início deste capítulo.

Painel 5.1 Programa utilizando um vetor (parte 1 de 2)


1 //Lê as cinco notas e mostra como cada
2 //uma difere da nota mais alta.
3 #include <iostream>
4 using namespace std;

5 int main( )
6 {
7 int i, score[5] , max;

8 cout << "Forneça 5 notas:\n";


9 cin >> score[0];
10 max = score[0];
11 for (i = 1; i < 5; i++)
12 {
13 cin >> score[i];
Introdução aos Vetores 119

Painel 5.1 Programa utilizando um vetor (parte 2 de 2)


14 if (score[i] > max)
15 max = score[i];
16 //max é o maior entre os valores score[0],..., score[i].
17 }

18 cout << "A nota mais alta é " << max << endl
19 << "As notas e suas diferenças\n"
20 << "em relação à nota mais alta são:\n";
21 for (i = 0; i < 5; i++)
22 cout << score[i] << " inferior a "
23 << (max - score[i]) << endl;

24 return 0;
25 }

DIÁLOGO PROGRAMA-USUÁRIO
Forneça 5 notas:
5 9 2 10 6
A nota mais alta é 10
As notas e suas diferenças
em relação à nota mais alta são:
5 inferior a 5
9 inferior a 1
2 inferior a 8
10 inferior a 0
6 inferior a 4

IDica USE LOOPS for COM VETORES


O segundo loop for no Painel 5.1 ilustra uma forma comum de se percorrer um vetor
for (i = 0; i < 5; i++)
cout << nota[i] << " inferior a "
<< (max - nota[i]) << endl;
O comando for é ideal para a manipulação em vetores.

ÍNDICES DE VETORES SEMPRE COMEÇAM COM ZERO


Os índices de um vetor sempre começam com 0 e terminam com o inteiro que seja igual ao tamanho do
vetor menos um.

IDica USE UMA CONSTANTE DEFINIDA PARA O TAMANHO DE UM VETOR


Leia novamente o programa no Painel 5.1. Ele só funciona para classes que tenham exatamente cinco alu-
nos. A maioria das classes não tem exatamente cinco alunos. Uma forma de tornar o programa mais versá-
til é utilizar uma constante definida para o tamanho de cada vetor. Por exemplo, o programa no Painel 5.1
poderia ser reescrito para utilizar a seguinte constante definida:
const int NUMERO_DE_ALUNOS = 5;
A linha com a declaração do vetor seria, então
int i, nota[NUMERO_DE_ALUNOS], max;
Claro que todos os lugares no programa em que o tamanho do vetor é 5 também deveriam ser alterados
para ter NUMERO_DE_ALUNOS em vez de 5. Se essas mudanças forem feitas no programa (ou, melhor ainda, se
o programa for escrito assim), então o programa pode ser revisado para trabalhar com qualquer número de
alunos simplesmente trocando a linha que define a constante NUMERO_DE_ALUNOS.
120 Vetores

IDica (continuação)
Observe que você não pode utilizar uma variável para o tamanho do vetor, como no trecho:
cout << "Informe o número de alunos:\n";
cin >> numero;
int nota[numero]; //ILEGAL EM MUITOS COMPILADORES!
Alguns compiladores, mas não todos, permitirão que você especifique um tamanho de vetor com uma variá-
vel desta forma. Entretanto, em nome da portabilidade, você não deve fazer isso, mesmo que o seu compi-
lador o permita. (No Capítulo 10, discutiremos um tipo diferente de vetor cujo tamanho pode ser
determinado quando o programa é executado.)

DECLARAÇÃO DE VETOR
SINTAXE
Nome_Tipo Nome_Vetor[Tamanho_Declarado];
EXEMPLOS
int grandeVetor[100];
double a[3];
double b[5];
char serie[10], serieUm;
Uma declaração de vetor da forma mostrada acima definirá Tamanho_Declarado variáveis indexadas, ou seja, as variáveis indexa-
das de Nome_Vetor[0] até Nome_Vetor[Tamanho_Declarado-1]. Cada variável indexada é uma variável de tipo Nome_Tipo.
O vetor a consiste nas variáveis indexadas a[0], a[1] e a[2], todas de tipo double. O vetor b consiste nas variáveis indexadas
b[0], b[1], b[2], b[3] e b[4], todas também de tipo double. Você pode combinar declarações de vetor com declarações de sim-
ples variáveis, como na variável serieUm acima.

■ VETORES NA MEMÓRIA
Antes de tratarmos de como os vetores são representados na memória de um computador, vamos ver primeiro
como uma variável simples (de tipo int ou double, por exemplo) é representada na memória de um computador.
A memória de um computador consiste em uma lista de posições numeradas chamadas bytes.1 O número de um
byte é conhecido como endereço. Uma variável simples é implementada como uma porção de memória que con-
siste em alguns números de bytes consecutivos. O número de bytes é determinado pelo tamanho da variável. As-
sim, uma variável simples na memória é descrita por dois pedaços de informação: um endereço na memória
(dando a posição do primeiro byte para aquela variável) e o tipo da variável, que diz quantos bytes de memória a va-
riável requer. Quando falamos em endereço de uma variável, é a esse endereço que nos referimos. Quando seu programa
armazena um valor na variável, o que realmente acontece é que o valor (codificado como zeros e uns) é colocado na-
queles bytes de memória atribuídos àquela variável. De forma similar, quando uma variável é dada como um argumen-
to (chamada por referência) a uma função, é o endereço da variável que, na realidade, é transmitido para a função que
faz a chamada. Agora vamos tratar da questão de como os vetores são armazenados na memória.
Variáveis indexadas de vetores são representadas na memória da mesma forma que as variáveis comuns, mas
com vetores a história é um pouco mais complicada. As posições para as diversas variáveis indexadas de vetores são
sempre conjuntas umas às outras na memória. Por exemplo, considere a seguinte declaração:
int a[6];

Quando se declara esse vetor, o computador reserva memória suficiente para abrigar seis variáveis de tipo int.
Além disso, o computador sempre coloca essas variáveis, uma depois da outra, na memória. Então o computador
se lembra do endereço das variáveis indexadas a[0], mas não se lembra do endereço de nenhuma outra variável
indexada. Quando seu programa precisa do endereço de alguma outra variável indexada nesse vetor, o computador
calcula o endereço para essa outra variável indexada a partir do endereço de a[0]. Por exemplo, se você começa
no endereço de a[0] e conta posições de memória suficientes para três variáveis de tipo int, então estará no ende-
reço de a[3]. Para obter o endereço de a[3], o computador começa no endereço de a[0] (que é um número).

1. Um byte consiste em oito bits, mas o tamanho exato de um byte não é importante para esta discussão.
Introdução aos Vetores 121

Então, o computador adiciona o número de bytes necessário para abrigar três variáveis de tipo int ao número do
endereço de a[0]. O resultado é o endereço de a[3]. Essa implementação é apresentada em diagrama no Painel
5.2. Muitas das peculiaridades dos vetores em C++ só podem ser entendidas em relação a esses detalhes sobre a
memória. Por exemplo, na próxima seção "Armadilha", utilizaremos esses detalhes para explicar o que acontece
quando seu programa utiliza um índice ilegal.

ÍNDICE DE VETOR FORA DO INTERVALO


O erro mais comum de programação é feito quando se usam vetores tentando referenciar um índice não
existente. Por exemplo, considere a seguinte declaração de vetor:
int a[6];
Quando se usa o vetor a, toda expressão de índices deve ter como resultado um dos inteiros de 0 a 5. Por
exemplo, se seu programa contém a variável indexada a[i], o i deve ser avaliado como um dos seis inteiros
0, 1, 2, 3, 4 ou 5. Se i é avaliado como qualquer outra coisa, é um erro. Quando uma expressão de índice
é avaliada como algum valor além daqueles permitidos pela declaração de vetor, diz-se que o índice está
fora do intervalo ou simplesmente que é ilegal. Na maioria dos sistemas, o resultado de um índice ilegal
é que seu programa simplesmente fará algo errado, às vezes desastrosamente errado, e fará isso sem lhe dar
qualquer aviso.
Por exemplo, suponha que seu sistema seja típico, o vetor a seja declarado como acima e seu programa
contenha a seguinte declaração:
a[i] = 238;
Agora suponha que o valor de i, infelizmente, seja 7. O computador procede como se a[7] fosse uma va-
riável indexada legal. O computador calcula o endereço onde a[7] deveria estar (se existir um a[7]) e colo-
ca o valor 238 nessa posição de memória. Entretanto, não existe a variável indexada a[7] e a memória que
recebe esse 238 provavelmente pertence a alguma outra variável, talvez uma variável chamada outraCoisa.
Assim, o valor de outraCoisa é alterado inadvertidamente. Essa situação é ilustrada no Painel 5.2.
Índices de vetor geralmente saem do intervalo na primeira ou última iteração de um loop que percorre o ve-
tor. Desse modo, é preciso verificar cuidadosamente todos os loops que percorrem vetores para ter certeza
de que iniciem e terminem em índices legais.

Painel 5.2 Vetor na memória

int a[6];
Endereço de a [0]

~:m 1024
Nesse computador cada 1025
variável indexada utiliza 1026
1027
2 bytes, então a[3]
1028
começa 2 x 3 = 6 bytes 1029
depois do início de a [0]
1030
1031 >a[4]

1032
1033 >a[5]

----< - >
Não existe a variável
indexada a[6], mas, se 1034 Variável

------<1---- >
houvesse uma, ficaria aqui. chamada stuff
Variável
Não existe a variável chamadamoreStuff
indexada a[7], mas, se
houvesse uma, ficaria aqui.
122 Vetores

■ INICIALIZANDO VETORES
Um vetor pode ser inicializado quando é declarado. Quando se inicializa o vetor, os valores das diversas variá-
veis indexadas ficam entre chaves e separados com vírgulas. Por exemplo:
int criancas[3] = {2, 12, 1};

A declaração anterior é equivalente ao seguinte código:


int criancas[3];
criancas[0] = 2;
criancas[1] = 12;
criancas[2] = 1;

Se você listar menos valores do que variáveis indexadas, esses valores serão usados para inicializar as primeiras
variáveis indexadas e as variáveis indexadas restantes serão inicializadas com o valor zero do tipo base de vetor.
Nessa situação, as variáveis indexadas sem inicializadores são inicializadas como zero. Entretanto, vetores sem ini-
cializadores e outras variáveis declaradas dentro de uma definição de função, inclusive a função main de um pro-
grama, não são inicializados. Apesar de as variáveis indexadas de vetor (e outras variáveis) poderem, às vezes, ser
automaticamente inicializadas como zero, não se pode e não se deve contar com isso.
Caso você inicialize um vetor em sua declaração, pode omitir o tamanho do vetor e este será automaticamente
declarado com o tamanho mínimo necessário para os valores de inicialização. Por exemplo, a declaração seguinte
int b[] = {5, 12, 11};

é equivalente a
int b[3] = {5, 12, 11};

1 Exercícios de Autoteste 1

1. Descreva a diferença do significado de int a[5]; e do significado de a[4]. Qual é o significado do [5] e
do [4] em cada caso?
2. Na declaração do vetor
double nota[5];
identifique o seguinte:
a. O nome do vetor
b. O tipo-base
c. O tamanho declarado do vetor
d. O intervalo de valores que um índice que se refira a esse vetor pode ter
e. Uma das variáveis indexadas (ou elementos) desse vetor
3. Identifique os erros nas seguintes declarações de vetor.
a. int x[4] = { 8, 7, 6, 4, 3 };
b. int x[] = { 8, 7, 6, 4 };
c. const int TAMANHO = 4;
int x[TAMANHO];
4. Qual é a saída do seguinte código?
char simbolo [3] = {’a’, ’b’, ’c’};
for (int indice = 0; indice < 3; indice++)
cout << simbolo[indice];
5. Qual é a saída do seguinte código?
double a[3] = {1.1, 2.2, 3.3};
cout << a[0] << " " << a[1] << " " << a[2] << endl;
a[1] = a[2];
cout << a[0] << " " << a[1] << " " << a[2] << endl;
6. Qual é a saída do seguinte código?
int i, temp[10];
Vetores em Funções 123

Exercícios de Autoteste (continuação)


for (i = 0; i < 10; i++)
temp[i] = 2*i;
for (i = 0; i < 10; i++)
cout << temp[i] << " ";
cout << endl;
for (i = 0; i < 10; i = i + 2)
cout << temp[i] << " ";
7. O que há de errado no seguinte trecho de código?
int vetorAmostra[10];
for (int indice = 1; indice <= 10; indice++)
vetorAmostra[indice] = 3*indice;
8. Suponha que esperemos que os elementos do vetor sejam ordenados de forma que
a[0] ≤ a[1] ≤ a[2] ≤ ...
Entretanto, para termos certeza, queremos que nosso programa teste o vetor e envie um aviso caso se
descubra que alguns elementos estão fora de ordem. O código seguinte deveria enviar esse aviso, mas
contém um erro. Qual é?
double a[10];
<O código para preencher o vetor vai aqui.>
for (int indice = 0; indice < 10; indice++)
if (a[indice] > a[indice + 1])
cout << "Os elementos do vetor " << indice << " e "
<< (indice + 1) << " estão fora de ordem.";
9. Escreva um código em C++ que preencha um vetor com 20 valores de tipo int lidos a partir do tecla-
do. Não precisa escrever um programa inteiro, apenas o código para isso, mas forneça as declarações do
vetor e de todas as variáveis.
10. Suponha que você tenha a seguinte declaração de vetor em seu programa:
int seuVetor[7];
Suponha também que, em sua implementação do C++, variáveis do tipo int utilizem dois bytes de
memória. Quando seu programa for executado, quanta memória esse vetor consumirá? Suponha que,
quando você executar o programa, o sistema atribua o endereço de memória 1000 à variável indexada
seuVetor[0]. Qual será o endereço da variável indexada seuVetor[3]?

5.2 1 Vetores em Funções


Você pode utilizar tanto variáveis indexadas de vetor quanto vetores completos como argumentos de funções.
Vamos tratar primeiro das variáveis indexadas de vetor como argumentos de funções.

■ VARIÁVEIS INDEXADAS COMO ARGUMENTOS DE FUNÇÃO


Uma variável indexada pode ser um argumento de uma função exatamente da mesma forma que qualquer va-
riável do tipo-base de vetor pode ser um argumento. Por exemplo, suponha que um programa contenha as seguin-
tes declarações:
double i, n, a[10];

Se minhaFuncao requer um argumento de tipo double, então a linha seguinte é legal:


minhaFuncao(n);

Como uma variável indexada do vetor a também é uma variável de tipo double, exatamente como n, a linha
seguinte também é legal:
minhaFuncao(a[3]);

Uma variável indexada pode ser um argumento chamado por valor ou por referência.
Há, contudo, uma sutileza que se aplica a variáveis indexadas utilizadas como argumentos. Por exemplo, con-
sidere a seguinte chamada de função:
124 Vetores

minhaFuncao(a[i]);

Se o valor de i é 3, então o argumento é a[3]. Por outro lado, se o valor de i é 0, essa chamada é equivalen-
te à seguinte:
minhaFuncao(a[0]);

A expressão indexada é avaliada a fim de determinar exatamente que variável indexada é fornecida como argu-
mento.

1 Exercícios de Autoteste 1

11. Considere a seguinte definição de função:


void triplicador(int& n)
{
n = 3*n;
}
Qual das seguintes chamadas de função é aceitável?
int a[3] = {4, 5, 6}, numero = 2;
triplicador(a[2]);
triplicador(a[3]);
triplicador(a[numero]);
triplicador(a);
triplicador(numero);
12. O que há de errado (se houver algo) com o seguinte código? A definição de triplicador é dada no
Exercício de Autoteste 11.
int b[5] = {1, 2, 3, 4, 5};
for (int i = 1; i <= 5; i++)
triplicador(b[i]);

■ VETORES INTEIROS COMO ARGUMENTOS DE FUNÇÃO


Uma função pode ter um parâmetro formal para um vetor completo de modo que, quando a função é chama-
da, o argumento conectado a esse parâmetro formal seja um vetor completo. Entretanto, um parâmetro formal de
um vetor completo não é um parâmetro chamado por valor nem por referência, é um novo tipo de parâmetro
formal que se chama parâmetro vetorial. Vamos começar com um exemplo.
A função definida no Painel 5.3 possui um parâmetro vetorial, a, que será substituído por um vetor completo
quando a função for chamada. Possui também um parâmetro comum chamado por valor (tamanho) que se presu-
me ser um valor inteiro igual ao tamanho do vetor. A função preenche seu argumento de vetor (ou seja, preenche
todas as variáveis indexadas do vetor) com valores digitados no teclado; então, a função envia uma mensagem para
a tela com o índice do último índice de vetor usado.

Painel 5.3 Função com um parâmetro vetorial


DECLARAÇÃO DE FUNÇÃO
void preenche(int a[], int tamanho);
//Pré-condição: tamanho é o tamanho declarado do vetor a.
//O usuário digitará os inteiros da variável tamanho.
//Pós-condição: O vetor a é preenchido com inteiros da variável tamanho
//a partir do teclado.
DEFINIÇÃO DE FUNÇÃO
void preenche(int a[], int tamanho);
{
cout << "Informe " << tamanho << " os números:\n";
for (int i = 0; i < tamanho; i++)
cin >> a[i];
cout << "O último índice de vetor usado é " << (tamanho - 1) << endl;
}
Vetores em Funções 125

O parâmetro formal int a[] é um parâmetro vetorial. Os colchetes, sem nenhuma expressão de índice den-
tro, são usados pelo C++ para indicar um parâmetro vetorial. Um parâmetro vetorial não é exatamente um parâ-
metro chamado por referência, mas, para a maioria dos objetivos práticos, se comporta de maneira bastante similar
a um parâmetro chamado por referência. Vamos a um exemplo detalhado para ver como um argumento vetorial fun-
ciona nesse caso. (Um argumento vetorial é, obviamente, um vetor conectado a um parâmetro vetorial, como a[].)
Quando a função preenche é chamada, deve ter dois argumentos: o primeiro fornece um vetor de inteiros e o
segundo deve fornecer o tamanho declarado do vetor. Por exemplo, a chamada de função seguinte é aceitável:
int nota[5], numeroDeNotas = 5;
preenche(nota, numeroDeNotas);

Esta chamada a preenche preencherá o vetor nota com cinco inteiros digitados ao teclado. Observe que o pa-
râmetro formal a[] (que é utilizado na declaração de função e no cabeçalho da definição de função) é dado com
os colchetes, mas sem expressão de índice. (Você pode inserir um número dentro dos colchetes para um parâme-
tro vetorial, mas o compilador simplesmente ignorará esse número, por isso neste livro não usaremos tais núme-
ros.) Por outro lado, o argumento dado na chamada de função (nota, nesse exemplo) é dado sem colchetes ou
expressão de índice.
O que acontece com o argumento vetorial nota nesta chamada de função? Falando de modo geral, o argu-
mento nota é conectado ao parâmetro vetorial formal a no corpo da função, e então o corpo da função é execu-
tado. Assim, a chamada de função
preenche(nota, numeroDeNotas);

é equivalente ao seguinte código:


{ 5 é o valor de numeroDeNotas
tamanho = 5;
cout << "Digite " << size << " os números:\n";
for (int i = 0; i < size; i++)
cin >> nota[i];
cout << "O último índice de vetor usado é " << (tamanho - 1) << endl;
}

O parâmetro formal a é um tipo de parâmetro diferente dos que vimos até agora. O parâmetro formal a é
apenas um "guardador" de lugar para o argumento nota. Quando a função preenche é chamada com nota como
argumento vetorial, o computador se comporta como se a fosse substituído pelo argumento correspondente nota.
Quando um vetor é utilizado como um argumento em uma chamada de função, qualquer ação executada no pa-
râmetro vetorial é executada sobre o argumento vetorial, portanto os valores das variáveis indexadas do argumento
vetorial podem ser alterados pela função. Se o parâmetro formal no corpo da função é alterado (por exemplo, com
um comando cin), o argumento vetorial será alterado.
Até agora, talvez pensemos que um parâmetro vetorial é apenas um parâmetro chamado por referência para
um vetor. Isso é quase verdade, mas um parâmetro vetorial é ligeiramente diferente de um parâmetro chamado
por referência. Para ajudar a explicar a diferença, vejamos alguns detalhes sobre vetores.
Lembre-se de que um vetor é armazenado como um bloco de memória contíguo. Por exemplo, considere a se-
guinte declaração do vetor nota:
int nota[5];

Quando se declara esse vetor, o computador reserva memória suficiente para abrigar cinco variáveis de tipo
int, que são armazenadas uma após a outra na memória do computador. O computador não se lembra dos ende-
reços de cada uma das cinco variáveis indexadas; lembra-se apenas do endereço da variável indexada nota[0]. O
computador também se lembra de que nota possui um total de cinco variáveis indexadas, todas de tipo int. Não
se lembra do endereço na memória de qualquer variável indexada além de nota[0]. Por exemplo, quando seu pro-
grama precisa de nota[3], o computador calcula o endereço de nota[3] a partir do endereço de nota[0]. Assim,
para obter o endereço de nota[3], o computador toma o endereço de nota[0] e acrescenta um número que re-
presenta a quantidade de memória utilizada por três variáveis int; o resultado é o endereço de nota[3].
Visto dessa forma, um vetor possui três partes: o endereço (localização na memória) da primeira variável inde-
xada, o tipo-base do vetor (que determina quanta memória cada variável indexada utiliza) e o tamanho do vetor
126 Vetores

(ou seja, o número de variáveis indexadas). Quando um vetor é utilizado como um argumento vetorial de uma
função, apenas a primeira dessas três partes é dada para a função. Quando um argumento vetorial é conectado
com seu parâmetro formal correspondente, tudo o que é conectado é o endereço da primeira variável indexada do
vetor. O tipo-base do argumento vetorial deve ser idêntico ao tipo-base do parâmetro formal, portanto a função
sabe também o tipo-base do vetor. Entretanto, o argumento vetorial não diz à função o tamanho do vetor. Quando
o código no corpo da função é executado, o computador sabe onde o vetor começa na memória e quanta memó-
ria cada variável indexada usa, mas (a não ser que você tome providências especiais) não sabe quantas variáveis in-
dexadas o vetor possui. Por isso é tão importante que você sempre tenha outro argumento int dizendo à função
o tamanho do vetor. (É por isso também que um parâmetro vetorial não é igual a um parâmetro chamado por re-
ferência. Pode-se pensar em um parâmetro vetorial como uma forma fraca de um parâmetro chamado por referên-
cia em que tudo sobre o vetor é dito à função, exceto o tamanho do vetor.)2
Esses parâmetros vetoriais podem parecer um tanto estranhos, mas possuem pelo menos uma boa propriedade como
resultado direto de sua definição aparentemente estranha. Essa vantagem será mais bem ilustrada se olharmos para o nosso
exemplo da função preenche, dado no Painel 5.3. Essa mesma função pode ser usada para preencher um vetor de qualquer ta-
manho, desde que o tipo-base do vetor seja int. Por exemplo, suponha que você tenha as seguintes declarações de vetor:
int nota[5], tempo[10];

A primeira das seguintes chamadas de preenche completa o vetor nota com cinco valores, e a segunda preen-
che o vetor tempo com dez valores:
preenche(nota, 5);
preenche(tempo, 10);

Você pode usar a mesma função para argumentos vetoriais de diferentes tamanhos, porque o tamanho é um
argumento separado.

■ PARÂMETRO MODIFICADOR const


Quando você usa um argumento vetorial em uma chamada de função, a função pode alterar os valores arma-
zenados no vetor. Isso normalmente é bom. Entretanto, em uma definição de função complicada, você pode que-
rer escrever um código que altere inadvertidamente um ou mais valores armazenados em um vetor, apesar de o
vetor não dever ser alterado. Como precaução, você pode dizer ao compilador que não pretende alterar o argu-
mento do vetor, e o computador verificará que seu código não altere inadvertidamente qualquer dos valores no
vetor. Para dizer ao compilador que um argumento vetorial não deve ser alterado pela sua função, insira o modi-
ficador const antes do parâmetro vetorial para aquela posição de argumento. Um parâmetro vetorial que é modificado
por um const é chamado de parâmetro vetorial constante.

PARÂMETROS FORMAIS E ARGUMENTOS VETORIAIS


Um argumento de uma função pode ser um vetor completo, mas um argumento para um vetor completo não é um argumento
chamado por valor nem um argumento chamado por referência. É um novo tipo de argumento conhecido como argumento veto-
rial. Quando um argumento vetorial é conectado a um parâmetro vetorial, tudo o que é fornecido para a função é o endereço
na memória da primeira variável indexada do argumento vetorial (aquele indexado por 0). O argumento vetorial não diz à fun-
ção o tamanho do vetor. Portanto, quando se tem um parâmetro vetorial de uma função, normalmente é preciso ter outro pa-
râmetro formal de tipo int que forneça o tamanho do vetor (como no exemplo abaixo).
Um argumento vetorial é como um argumento chamado por referência da seguinte forma: se o corpo da função altera o parâ-
metro vetorial, então, quando a função é chamada, essa alteração é, na verdade, feita no argumento vetorial. Assim, uma fun-
ção pode alterar os valores de um argumento vetorial (ou seja, pode mudar os valores de suas variáveis indexadas).
A sintaxe para uma declaração de função com um parâmetro vetorial é a seguinte:
SINTAXE
Tipo_Retornado Nome_Da_Funcao(..., Tipo_Base Nome_Do_Vetor[],...);
EXEMPLO
void somaVetor(double& soma, double a[], int tamanho);

2. Se você já ouviu falar em ponteiros, isso soará como se fossem ponteiros e, com efeito, um argumento vetorial é
transmitido passando-se um ponteiro para sua primeira variável indexada (a de número 0). Trataremos disso no Capítulo
10. Se você nunca ouviu falar de ponteiros, pode ignorar esta nota.
Vetores em Funções 127

Por exemplo, a seguinte função apresenta como saída os valores em um vetor, mas não altera os valores no vetor:
void mostreAoMundo(int a[], int tamanhoDea)
//Pré-condição: tamanhoDea é o tamanho declarado do vetor a.
//Todas as variáveis indexadas de a receberam valores.
//Pós-condição: Os valores em a foram escritos na tela.
{
cout << " O vetor contém os seguintes valores:\n";
for (int i = 0; i < tamanhoDea; i++)
cout << a[i] << " ";
cout << endl;
}

Essa função trabalhará bem. Entretanto, como medida de segurança adicional, você pode acrescentar o modifi-
cador const ao cabeçalho da função, como se segue:
void mostreAoMundo(const int a[], int tamanhoDea)

Com o acréscimo desse modificador const, o computador emitirá uma mensagem de erro se sua definição de
função contiver um erro que altere qualquer dos valores no argumento vetorial. Por exemplo, a seguinte versão da fun-
ção mostreAoMundo contém um erro que altera inadvertidamente o valor do argumento vetorial. Felizmente, esta
versão da definição de função inclui o modificador const; assim, uma mensagem de erro nos dirá que o vetor foi
alterado. Essa mensagem de erro ajudará a explicar o erro:
void mostreAoMundo(const int a[], int tamanhoDea)
//Pré-condição: tamanhoDea é o tamanho declarado do vetor a.
//Todas as variáveis indexadas de a receberam valores.
//Pós-condição: Os valores em a foram escritos na tela.
{
cout << " O vetor contém os seguintes valores:\n";
for (int i = 0; i < tamanhoDea; a[i]++)
cout << a[i] << " "; Erro, mas o compilador não o
cout << endl; acusará a não ser que você
} utilize o modificador const.

Se não houvéssemos usado o modificador const na definição de função acima e tivéssemos cometido o erro
mostrado, a função compilaria e seria executada sem mensagens de erro. Entretanto, o código conteria um loop
infinito que incrementaria continuamente a[0] e escreveria um novo valor na tela.
O problema com esta versão incorreta de mostreAoMundo é que o item errado é incrementado no loop for. A
variável indexada a[i] é incrementada, mas o item índice i é que deveria ser incrementado. Nesta versão incorre-
ta, o índice i começa com o valor 0 e esse valor nunca é alterado. Mas a[i], que é o mesmo que a[0], é incre-
mentada. Quando a variável indexada a[i] é incrementada, altera-se o valor no vetor e, como incluímos o
modificador const, o computador enviará uma mensagem de aviso. Essa mensagem de erro servirá como uma pis-
ta do que está errado.
Normalmente há uma declaração de função em seu programa, além da definição de função. Quando se usa o
modificador const em uma definição de função, deve-se também usá-lo na declaração de função, de modo que o
cabeçalho da função e a declaração de função sejam consistentes.
O modificador const pode ser usado com qualquer tipo de parâmetro, mas normalmente é usado apenas com
parâmetros vetoriais e parâmetros chamados por referência para classes, das quais trataremos nos Capítulos 6 e 7.

USO INCONSISTENTE DE PARÂMETROS DE const


O modificador de parâmetro const é uma proposição de tudo ou nada. Se você usá-lo para um parâmetro
vetorial de tipo particular, deve usá-lo para todos os outros parâmetros vetoriais que possuam esse tipo e
que não sejam alterados pela função. O motivo para isso tem a ver com chamadas de função dentro de
chamadas de função. Considere a definição da função mostraDiferenca, que é dada a seguir com a declara-
ção de uma função usada na definição:
double calculaMedia(int a[], int numeroUsado);
//Retorna a média dos n primeiros elementos do vetor a(n é o valor passado por
128 Vetores

(continuação)
// numeroUsado. O vetor a não é alterado.
void mostraDiferenca(const int a[], int numeroUsado)
{
double media = calculaMedia(a, numeroUsado);
cout << "A média dos " << numeroUsado
<< " números = " << media << endl
<< "Os números são:\n";
for (int indice = 0; indice < numeroUsado; indice++)
cout << a[indice] << " difere da média por "
<< (a[indice] - media) << endl;
}
O código acima emitirá uma mensagem de erro ou de aviso na maioria dos compiladores. A função calcu-
laMedia não altera seu parâmetro a. Entretanto, quando o compilador processa a definição de função para
mostraDiferenca, ele pensará que calculaMedia altera (ou, pelo menos, poderia alterar) o valor de seu parâ-
metro a. Isso porque, quando ele traduz a definição de função para mostraDiferenca, tudo o que o compi-
lador sabe a respeito da função calculaMedia é a declaração de função de calculaMedia, que não contém
um const para dizer ao compilador que o parâmetro a não será alterado. Assim, se você usa const com o
parâmetro a na função mostraDiferenca, deve usar o modificador const também com o parâmetro a na fun-
ção calculaMedia. A declaração de função para calculaMedia deve ser a seguinte:
double calculaMedia(const int a[], int numeroUsado);

■ FUNÇÕES QUE RETORNAM UM VETOR


Uma função pode não retornar um vetor da mesma forma que retorna um valor de tipo int ou double. Não
há como se obter algo mais ou menos equivalente para uma função que retorna um vetor. O que se deve fazer é
retornar um ponteiro para o vetor. Abordaremos esse tópico quando discutirmos a interação de vetores e ponteiros
no Capítulo 10. Até que você aprenda o que são ponteiros, não há como escrever uma função que retorne um vetor.

GRÁFICO DE PRODUÇÃO
O Painel 5.4 contém um programa que utiliza um vetor e alguns parâmetros vetoriais. Esse programa para a
Companhia Clímax de Fabricação de Colheres de Plástico apresenta um gráfico de barras exibindo a produti-
vidade de cada uma de suas quatro fábricas em uma dada semana. As fábricas mantêm cifras de produção
separadas para cada departamento, como o departamento de colheres de chá, departamento de colheres de
sopa, departamento de colheres de coquetel simples, departamento de colheres de coquetel coloridas, e as-
sim por diante. Além disso, cada uma das quatro fábricas possui um número diferente de departamentos.
Como você pode ver pelo diálogo programa-usuário no Painel 5.4, o gráfico utiliza um asterisco para cada
100 unidades de produção. Como a saída é em unidades de 1000, deve ser colocada em escala sendo divi-
dida por 1000. Isso representa um problema, porque o computador precisa exibir um número inteiro de as-
teriscos. Não pode exibir 1,6 asteriscos para 1600 unidades. Por isso, fazemos um arredondamento para o
milhar mais próximo. Assim, 1600 será o mesmo que 2000 e se transformará em dois asteriscos.
O vetor producao contém a produção total para cada uma das quatro fábricas. Em C++, os índices de ve-
tor sempre começam no 0. Mas como as fábricas são numeradas de 1 a 4 e não de 0 a 3, colocamos a pro-
dução total para a fábrica número n na variável indexada producao[n - 1]. A saída total para a fábrica
número 1 estará contida na producao[0], as cifras para a fábrica 2 estarão contidas na producao[1], e assim
por diante.
Como a saída é em milhares de unidades, o programa colocará em escala os valores dos elementos do ve-
tor. Se a saída total da fábrica número 3 é 4040 unidades, o valor de producao[2] será inicialmente fixado
como 4040. Esse valor de 4040 será, então, escalado como 4, de forma que o valor de producao[2] seja al-
terado para 4 e quatro asteriscos sejam apresentados para representar a saída da fábrica número 3. Essa
operação é realizada pela função escala, que toma todo o vetor producao como um argumento e altera os
valores armazenados no vetor.
A função arredonda efetua o arredondamento do seu argumento para o inteiro mais próximo. Por exemplo,
arredonda(2.3) apresenta como resultado 2, e arredonda(2.6) apresenta como resultado 3. A função arre-
donda foi discutida no Capítulo 3, no exemplo de programação intitulado "Uma Função Arredondadora".
Vetores em Funções 129

Painel 5.4 Programa gráfico de produção (parte 1 de 3)


1 //Lê dados e exibe um gráfico de barras mostrando a produtividade de cada fábrica.
2 #include <iostream>
3 #include <cmath>
4 using namespace std;
5 const int NUMBER_OF_PLANTS = 4;

6 void inputData( int a[], int lastPlantNumber);


7 //Pré-condição: lastPlantNumber é o tamanho declarado do vetor a.
8 //Pós-condição: Para plantNumber = 1 até lastPlantNumber:
9 //a[plantNumber-1] é igual à produção total da fábrica de número plantNumber.

10 void scale(int a[], int size );


11 //Pré-condição: a[0] até a[size-1] tem todos valor não-negativo.
12 //Pós-condição: a[i] foi alterado para o número de milhares (arredondado para
13 //um inteiro) que estava originalmente em a[i], para todo i, tal que 0 <= i <= size-1.

14 void graph( const int asteriskCount[], int lastPlantNumber);


15 //Pré-condição: a[0] até a[lastPlantNumber-1] tem todos valor não-negativo.
16 //Pós-condição: Um gráfico de barras foi apresentado dizendo que a fábrica
17 //número N produziu a[N-1] milhares de unidades para cada N, tal que
18 //1 <= N <= lastPlantNumber

19 void getTotal(int& sum);


20 //Lê inteiros não-negativos a partir do teclado e
21 //coloca o total em sum.

22 int round(double number);


23 //Pré-condição: number >= 0.
24 //Retorna número arredondado para o inteiro mais próximo.

25 void printAsterisks(int n);


26 //Imprime n asteriscos na tela.

27 int main( )
28 {
29 int production[NUMBER_OF_PLANTS];

30 cout << "Este programa apresenta um gráfico mostrando\n"


31 << "a produção de cada fábrica na companhia.\n";

32 inputData(production, NUMBER_OF_PLANTS);
33 scale(production, NUMBER_OF_PLANTS);
34 graph(production, NUMBER_OF_PLANTS);
35 return 0;
36 }

37 void inputData(int a[], int lastPlantNumber)


38 {
39 for (int plantNumber = 1;
40 plantNumber <= lastPlantNumber; plantNumber++)
41 {
42 cout << endl
43 << "Informe os dados de produção para a fábrica número "
44 << plantNumber << endl;
45 getTotal(a[plantNumber - 1]);
46 }
47 }
48 void getTotal(int& sum)
130 Vetores

Painel 5.4 Programa gráfico de produção (parte 2 de 3)


49 {
50 cout << "Informe o número de unidades produzidas por cada departamento.\n"
51 << "Inclua um número negativo ao final da lista.\n";

52 sum = 0;
53 int next;
54 cin >> next;
55 while (next >= 0)
56 {
57 sum = sum + next;
58 cin >> next;
59 }
60 cout << "Total = " << sum << endl;
61 }
62
63 void scale(int a[], int size)
64 {
65 for (int index = 0; index < size; index++)
66 a[index] = round(a[index]/1000.0);
67 }
68 int round(double number)
69 {
70 return static_cast<int>(floor(number + 0.5));
71 }
72 void graph(const int asteriskCount[], int lastPlantNumber)
73 {
74 cout << "Unidades produzidas em milhares de unidades:\n";
75 for (int plantNumber = 1;
76 plantNumber <= lastPlantNumber; plantNumber++)
77 {
78 cout << "Fábrica #" << plantNumber << " ";
79 printAsterisks(asteriskCount[plantNumber - 1]);
80 cout << endl;
81 }
82 }
83 void printAsterisks(int n)
84 {
85 for (int count = 1; count <= n; count++)
86 cout << "*";
87 }

DIÁLOGO PROGRAMA-USUÁRIO
Este programa apresenta um gráfico mostrando
a produção de cada fábrica na companhia.
Informe os dados de produção para a fábrica número 1
Informe o número de unidades produzidas por cada departamento.
Inclua um número negativo ao final da lista.
2000 3000 1000 -1
Total = 6000
Informe os dados de produção para a fábrica número 2
Informe o número de unidades produzidas por cada departamento.
Inclua um número negativo ao final da lista.
2050 3002 1300 -1
Total = 6352
Vetores em Funções 131

Painel 5.4 Programa gráfico de produção (parte 3 de 3)

Informe os dados de produção para a fábrica número 3


Informe o número de unidades produzidas por cada departamento.
Inclua um número negativo ao final da lista.
5000 4020 500 4348 -1
Total = 13868
Informe os dados de produção para a fábrica número 4
Informe o número de unidades produzidas por cada departamento.
Inclua um número negativo ao final da lista.
2507 6050 1809 -1
Total = 10366
Unidades produzidas em milhares de unidades:
Fábrica #1 ******
Fábrica #2 ******
Fábrica #3 **************
Fábrica #4 **********

1 Exercícios de Autoteste 1

13. Escreva uma definição de função para uma função chamada maisUm, que possui um parâmetro formal
para um vetor de inteiros e aumenta o valor de cada elemento do vetor em 1. Acrescente quaisquer ou-
tros parâmetros formais que sejam necessários.
14. Considere a seguinte definição de função:
void tambem2(int a[], int quantos)
{
for (int indice = 0; indice < quantos; indice++)
a[indice] = 2;
}
Qual das seguintes seria uma chamada de função aceitável?
int meuVetor[29];
tambem2(meuVetor, 29);
tambem2(meuVetor, 10);
tambem2(meuVetor, 55);
"Ei tambem2. Por favor, venha aqui."
int seuVetor[100];
tambem2(seuVetor, 100);
tambem2(seuVetor[3], 29);
15. Insira const antes de qualquer dos seguintes parâmetros vetoriais que possam ser alterados para parâ-
metros vetoriais constantes.
void saida(double a[], int tamanho);
//Pré-condição: a[0] até a[tamanho - 1] possuem valores.
//Pós-condição: a[0] até a[tamanho - 1] foram escritos.

void descartaImpar(int a[], int tamanho);


//Pré-condição: a[0] até a[tamanho - 1] possuem valores.
//Pós-condição: Todos os números ímpares em a[0] até a[tamanho - 1]
//foram alterados para 0.
16. Escreva uma função chamada foraDeOrdem que tome como parâmetros um vetor de double e um parâ-
metro int chamado tamanho e retorne um valor de tipo int. Essa função testará esse vetor para ver se
está fora de ordem, o que significa que o vetor viola a seguinte condição:
a[0] <= a[1] <= a[2] <= ...
A função apresenta como saída -1 se os elementos não estão fora de ordem; caso contrário, retornará o
índice do primeiro elemento do vetor que esteja fora de ordem. Por exemplo, considere a declaração
132 Vetores

Exercícios de Autoteste (continuação)


double a[10] = {1.2, 2.1, 3.3, 2.5, 4.5,
7.9, 5.4, 8.7, 9.9, 1.0};
No vetor acima, a[2] e a[3] são o primeiro par fora de ordem, e a[3], é o primeiro elemento fora de
ordem, então a função apresenta como saída 3. Se o vetor fosse colocado em ordem, a função apresen-
taria como saída -1.

5.3 1 Programando com Vetores


Nunca confie em impressões gerais, meu rapaz. Concentre-se nos detalhes.
Sir Arthur Conan Doyle, Um Caso de Identidade (Sherlock Holmes)

Esta seção discute vetores parcialmente preenchidos e fornece uma breve introdução à ordenação de vetores e
à busca em vetores. Esta seção não inclui novas informações sobre a linguagem C++, mas acrescenta mais exem-
plos práticos com parâmetros vetoriais em C++.

■ VETORES PARCIALMENTE PREENCHIDOS


Muitas vezes o tamanho exato necessário para um vetor não é conhecido quando um programa é escrito, ou o
tamanho pode variar de uma execução do programa para outra. Uma forma comum e fácil de se lidar nesta situa-
ção é declarar o vetor com o maior tamanho que o programa poderia necessitar. O programa é, então, livre para
usar o máximo ou o mínimo do vetor de que necessitar.
Vetores parcialmente preenchidos requerem algum cuidado. O programa precisa controlar quanto do vetor foi
usado e não deve referenciar nenhuma variável indexada que não tenha recebido um valor. O programa no Painel
5.5 ilustra essa questão. O programa lê uma lista de pontuações de golfe e mostra quanto cada pontuação difere
da média. Esse programa trabalhará com listas de uma até dez pontuações, e de qualquer comprimento entre esses
dois extremos. As pontuações são armazenadas no vetor pontuacao, que possui dez variáveis indexadas, mas o pro-
grama utiliza apenas a parte do vetor de que necessita. A variável numeroUsado controla quantos elementos estão
armazenados no vetor. Os elementos (ou seja, as pontuações) são armazenados nas posições pontuacao[0] até
pontuacao[numeroUsado - 1]. Os detalhes são bastante similares aos que seriam se numeroUsado fosse o tamanho
declarado do vetor e o vetor completo fosse usado. Em particular, a variável numeroUsado normalmente precisa ser
um argumento para qualquer função que manipule o vetor parcialmente preenchido. Como o argumento nume-
roUsado (quando usado adequadamente) pode muitas vezes assegurar que a função não referenciará um índice de
vetor ilegal, isso às vezes (mas não sempre) elimina a necessidade de um argumento que forneça o tamanho decla-
rado do vetor. Por exemplo, as funções mostraDiferenca e calculaMedia utilizam o argumento numeroUsado para
assegurar que apenas índices de vetor legais sejam usados. Entretanto, a função preencheVetor precisa saber o ta-
manho máximo declarado para o vetor de modo que não ultrapasse a capacidade deste.

IDica NÃO POUPE PARÂMETROS FORMAIS


Observe a função preencheVetor no Painel 5.5. Quando preencheVetor é chamada, o tamanho declarado do
vetor MAX_NUMERO_PONTUACAO é fornecido como um dos argumentos, como exibido na seguinte chamada de
função do Painel 5.5:
preencheVetor(pontuacao, MAX_NUMERO_PONTUACAO, numeroUsado);
Você pode protestar dizendo que MAX_NUMERO_PONTUACAO é uma constante definida globalmente, e, assim,
poderia ser usada na definição de preencheVetor sem a necessidade de ser transformada em argumento.
Você teria razão, e se não usássemos preencheVetor em nenhum programa além do exibido no Painel 5.5,
poderíamos deixar de incluir MAX_NUMERO_PONTUACAO como um argumento de preencheVetor. Entretanto,
preencheVetor é uma função de uso geral que você pode querer utilizar em vários programas diferentes.
Com efeito, utilizamos também a função preencheVetor no programa do Painel 5.6, discutido na próxima
subseção. No programa do Painel 5.6, o argumento para o tamanho declarado do vetor é uma constante
global nomeada diferente. Se tivéssemos escrito a constante global MAX_NUMERO_PONTUACAO no corpo da fun-
ção preencheVetor, não poderíamos reutilizar a função no programa do Painel 5.6.
Programando com Vetores 133

IDica (continuação)
Mesmo que utilizássemos preencheVetor em apenas um programa, ainda seria uma boa idéia transformar o
tamanho declarado do vetor em um argumento de preencheVetor. Exibir o tamanho declarado do vetor
como um argumento nos lembra de que a função necessita dessa informação de maneira fundamental.

Painel 5.5 Vetor parcialmente preenchido (parte 1 de 2)


1 //Mostra a diferença entre cada entrada em uma lista de pontuações de golfe e sua média.
2 #include <iostream>
3 using namespace std;
4 const int MAX_NUMBER_SCORES = 10;
5 void fillArray(int a[], int size, int& numberUsed);
6 //Pré-condição: size é o tamanho declarado do vetor a.
7 //Pós-condição: numberUsed é o número de valores armazenado em a.
8 //a[0] até a[numberUsed-1] foi preenchido com
9 //inteiros não-negativos lidos a partir do teclado.
10 double computeAverage(const int a[], int numberUsed);
11 //Pré-condição: a[0] até a[numberUsed-1] tem valores; numberUsed > 0.
12 //Retorna a média dos números a[0] até a[numberUsed-1].
13 void showDifference(const int a[], int numberUsed);
14 //Pré-condição: As primeiras variáveis indexadas numberUsed de a possuem valores.
15 //Pós-condição: Mostra na tela em quanto os primeiros
16 //numberUsed elementos do vetor a diferem de sua média.

17 int main( )
18 {
19 int score[MAX_NUMBER_SCORES], numberUsed;
20 cout << "Este programa lê pontuações de golfe e mostra\n"
21 << "quanto cada uma difere da média.\n";
22 cout << "Informe as pontuações de golfe:\n";
23 fillArray(score, MAX_NUMBER_SCORES, numberUsed);
24 showDifference(score, numberUsed);

25 return 0;
26 }

27 void fillArray(int a[], int size, int& numberUsed)


28 {
29 cout << "Forneça até " << size << " números não-negativos.\n"
30 << "Assinale o final da lista com um número negativo.\n";
31 int next, index = 0;
32 cin >> next;
33 while ((next >= 0) && (index < size))
34 {
35 a[index] = next;
36 index++;
37 cin >> next;
38 }
39 numberUsed = index;
40 }
41 double computeAverage(const int a[], int numberUsed)
42 {
43 double total = 0;
44 for (int index = 0; index < numberUsed; index++)
45 total = total + a[index];
134 Vetores

Painel 5.5 Vetor parcialmente preenchido (parte 2 de 2)


46 if (numberUsed > 0)
47 {
48 return (total/numberUsed);
49 }
50 else
51 {
52 cout << "ERRO: número de elementos é 0 em computeAverage.\n"
53 << "computeAverage retorna 0.\n";
54 return 0;
55 }
56 }
57 void showDifference(const int a[], int numberUsed)
58 {
59 double average = computeAverage(a, numberUsed);
60 cout << "Média das " << numberUsed
61 << " pontuações = " << average << endl
62 << "As pontuações são:\n";
63 for (int index = 0; index < numberUsed; index++)
64 cout << a[index] << " diferem da média por "
65 << (a[index] - average) << endl;
66 }

DIÁLOGO PROGRAMA-USUÁRIO
Este programa lê pontuações de golfe e mostra
quanto cada uma difere da média.
Informe as pontuações de golfe:
Forneça até 10 números não-negativos.
Assinale o final da lista com um número negativo.
69 74 68 -1
A média das 3 pontuações = 70.3333
As pontuações:
69 difere da média por -1.33333
74 difere da média por 3.66667
68 difere da média por -2.33333

BUSCAS EM VETOR
Uma tarefa comum de programação é buscar um determinado valor em um vetor. Por exemplo, o vetor
pode conter os números de identificação escolar de todos os estudantes de um determinado curso. Para di-
zer se um estudante em particular está matriculado, efetua-se uma busca no vetor para verificar se este
contém o número do estudante. O programa simples no Painel 5.6 preenche um vetor e depois procura
neste os valores especificados pelo usuário. Um programa de aplicação real seria bem mais elaborado, mas
este mostra tudo o que é essencial em um algoritmo de busca seqüencial. A busca seqüencial é o algorit-
mo de busca mais simples que se possa imaginar. O programa procura pelos elementos do vetor em ordem,
do primeiro ao último, para ver se o número procurado é igual a algum dos elementos do vetor.
No Painel 5.6 a função busca é utilizada para efetuar a busca no vetor. Quando se efetua uma busca em
um vetor, muitas vezes se deseja saber mais do que apenas se o valor procurado está ou não no vetor. Se
o valor procurado está no vetor, em geral se quer saber o índice da variável indexada que abriga o valor
procurado, já que o índice pode servir como guia para alguma informação adicional sobre o valor procurado.
Desta forma, projetamos a função busca para retornar um índice dando a localização no vetor do valor pro-
curado, desde que o valor procurado esteja, de fato, no vetor. Se o valor procurado não estiver no vetor,
busca apresenta como saída -1. Vamos estudar a função busca com mais atenção.
A função busca utiliza um loop while para verificar os elementos do vetor um após o outro a fim de verifi-
car se algum deles é igual ao valor procurado. A variável encontrado é utilizada como uma sinalização para
registrar se o elemento procurado foi ou não encontrado. Se o elemento procurado foi encontrado no vetor,
encontrado é true, o que encerra o loop while.
Programando com Vetores 135

Painel 5.6 Efetuando uma busca em um vetor (parte 1 de 2)


1 //Efetua uma busca em um vetor parcialmente preenchido de inteiros não-negativos.
2 #include <iostream>
3 using namespace std;
4 const int DECLARED_SIZE = 20;

5 void fillArray(int a[], int size, int& numberUsed);


6 //Pré-condição: size é o tamanho declarado do vetor a.
7 //Pós-condição: numberUsed é o número de valores armazenado em a.
8 //a[0] até a[numberUsed-1] foi preenchido com
9 //inteiros não-negativos a partir do teclado.
10 int search(const int a[], int numberUsed, int target);
11 //Pré-condição: numberUsed é <= ao tamanho declarado de a.
12 //Além disso, a[0] até a[numberUsed-1] possuem valores.
13 //Retorna o primeiro índice tal que a[index] == target,
14 //desde que exista tal índice; caso contrário, retorna -1.

15 int main( )
16 {
17 int arr[DECLARED_SIZE], listSize, target;

18 fillArray(arr, DECLARED_SIZE, listSize);

19 char ans;
20 int result;
21 do
22 {
23 cout << "Informe um número para ser procurado: ";
24 cin >> target;

25 result = search(arr, listSize, target);


26 if (result == -1)
27 cout << target << " não está na lista.\n";
28 else
29 cout << target << " está armazenado na posição do vetor "
30 << result << endl
31 << "(Lembre-se: a primeira posição é 0.)\n";

32 cout << "Outra busca?(s/n mais tecla Enter): ";


33 cin >> ans;
34 } while ((ans != ’n’) && (ans != ’N’));
35 cout << "Fim do programa.\n";
36 return 0;
37 }

38 void fillArray(int a[], int size, int& numberUsed)


39 <O resto da definição de fillArray é dado no Painel 5.5.>
40 int search(const int a[], int numberUsed, int target)
41 {
42 int index = 0;
43 bool found = false;
44 while ((!found) && (index < numberUsed))
45 if (target == a[index])
46 found = true;
47 else
48 index++;

49 if (found)
50 return index;
136 Vetores

Painel 5.6 Efetuando uma busca em um vetor (parte 2 de 2)


51 else
52 return -1;
53 }

DIÁLOGO PROGRAMA-USUÁRIO
Forneça até 20 números inteiros não-negativos.
Assinale o final da lista com um número negativo.
10 20 30 40 50 60 70 80 -1
Informe um número para ser procurado: 10
10 está armazenado na posição de vetor 0
(Lembre-se: a primeira posição é 0.)
Outra busca? (s/n mais tecla Enter): s
Informe um número para ser procurado: 40
40 está armazenado na posição de vetor 3
(Lembre-se: a primeira posição é 0.)
Outra busca? (s/n mais tecla Enter): s
Informe um número para ser procurado: 42
42 não está na lista.
Outra busca? (s/n mais tecla Enter): n
Final do programa.

ORDENANDO UM VETOR
Uma das tarefas de programação mais comuns e certamente a mais estudada é a ordenação de uma lista de
valores, como uma lista de cifras de venda que deve ser ordenada do menor para o maior ou do maior para
o menor, ou uma lista de palavras que deve ser colocada em ordem alfabética. Este exemplo descreve uma
função chamada ordena que ordenará um vetor de números parcialmente preenchido do menor para o
maior.
O procedimento ordena possui um parâmetro vetorial, a. O vetor a será parcialmente preenchido, portanto
existe um parâmetro formal adicional chamado numeroUsado que diz quantas posições de vetor são utiliza-
das. Assim, a declaração e pré-condição da função ordena são as seguintes:
void ordena(int a[], int numeroUsado);
//Pré-condição: numeroUsado <= tamanho declarado do vetor a.
//Os elementos do vetor de a[0] até a[numeroUsado - 1] possuem valores.
A função ordena rearranja os elementos no vetor a de modo que, depois que a chamada de função é com-
pletada, os elementos são ordenados da seguinte forma:
a[0] ≤ a[1] ≤ a[2] ≤ ... ≤ a[numeroUsado - 1]
O algoritmo que usamos para ordenar é chamado ordenação por seleção. É um dos algoritmos de ordenação
mais fáceis de entender.
Uma forma de projetar um algoritmo é confiar na definição do problema. Nesse caso, o problema é ordenar
um vetor do menor para o maior. Isso significa rearranjar os valores de modo que a[0] seja o menor, a[1] o
próximo, e assim por diante. Essa definição fornece um esboço para o algoritmo de ordenação por seleção:
for (int indice = 0; indice < numeroUsado; indice++)
Colocar o indice menor elemento em a[indice]
Há muitas formas de se compreender esta abordagem geral. Os detalhes poderiam ser desenvolvidos com a
utilização de dois vetores e copiando-se os elementos de um para o outro em ordem, mas utilizar apenas
um vetor é adequado e econômico. Portanto, a função ordena utiliza apenas o vetor que contém os valores
a serem ordenados. A função ordena rearranja os valores no vetor trocando pares de valores. Vamos anali-
sar um exemplo concreto para que você veja como o algoritmo funciona.
Considere o vetor mostrado no Painel 5.7. O algoritmo colocará o menor valor em a[0]. O menor valor é o
valor em a[3], logo o algoritmo troca os valores de a[0] e a[3]. Então, o algoritmo procura pelo próximo
elemento. O valor em a[0] é agora o menor elemento, e o próximo é o menor entre os elementos restan-
tes, a[1], a[2], a[3] ,..., a[9]. No exemplo do Painel 5.7, o próximo elemento menor está em a[5], e o al-
goritmo troca os valores de a[1] e a[5]. Esse posicionamento do segundo menor elemento é ilustrado na
Programando com Vetores 137

quarta e quinta figuras de vetores no Painel 5.7. Então, o algoritmo posiciona o terceiro menor elemento, e
assim por diante. À medida que a ordenação prossegue, os primeiros elementos do vetor são fixados na or-
dem correta de valores. A porção ordenada do vetor aumenta com o acréscimo, uns após os outros, dos
elementos da porção não-ordenada do vetor. Observe que o algoritmo não precisa fazer nada com o valor
da última variável indexada, a[9]. Uma vez que os outros elementos tenham sido posicionados corretamen-
te, a[9] também deve estar com o valor correto. Afinal, o valor correto para a[9] é o menor valor restante
a ser movido, e o único valor restante a ser movido é o valor que já está em a[9].
A definição da função ordena, incluída em um programa de demonstração, é dada no Painel 5.8. ordena uti-
liza a função indiceDoMenor para encontrar o índice do menor elemento na extremidade não-ordenada do
vetor e depois efetua a troca para mover o próximo elemento menor para o lado ordenado do vetor.
A função trocaValores, mostrada no Painel 5.8, é usada para trocar os valores das variáveis indexadas. Por
exemplo, a chamada seguinte trocará os valores de a[0] e a[3]:
trocaValores(a[0], a[3]);
A função trocaValores foi explicada no Capítulo 4.

Painel 5.7 Ordenação por seleção

a[O] a[l] a[2] a[3] a[4] a[5] a[6] a[7] a[B] a[9]
8 1 6 1 10 1 2 1 16 1 4 1 18 1 14 1 12 1 20 1

ª(:2 1 10 1 223 1 4 1 18 1 14 1 12 1 20 1

1
2 1
6 1
10 1
8 1
16 1
4 1
18 1
14 1
12 1
20 1

1
2 1
6çl 8 1
16 1
4 ~14 1
12 1
20 1

1 2 1 4 1 10 1 8 1 16 1 6 1 18 1 14 1 12 1 20 1

Painel 5.8 Ordenação por seleção (parte 1 de 3)


1 //Testa o procedimento sort.
2 #include <iostream>
3 using namespace std;
4 void fillArray(int a[], int size, int& numberUsed);
5 //Pré-condição: size é o tamanho declarado do vetor a.
6 //Pós-condição: numberUsed é o número de valores armazenado em a.
7 //a[0] até a[numberUsed - 1] foi preenchido com
8 //inteiros não-negativos lidos a partir do teclado.
9 void sort(int a[], int numberUsed);
10 //Pré-condição: numberUsed é <= ao tamanho declarado de a.
11 //Os elementos de vetor a[0] até a[numberUsed - 1] possuem valores.
12 //Pós-condição: Os valores de a[0] até a[numberUsed - 1] foram
13 //rearranjados, de modo que a[0] <= a[1] <= ... <= a[numberUsed - 1].
14 void swapValues(int& v1, int& v2);
15 //Troca os valores de v1 e v2.
16 int indexOfSmallest(const int a[], int startIndex, int numberUsed);
17 //Pré-condição: 0 <= startIndex < numberUsed. Os elementos do vetor de referência
18 //possuem valores. Fornece o índice i, tal que a[i] é o menor dentre os
138 Vetores

Painel 5.8 Ordenação por seleção (parte 2 de 3)


19 //valores a[startIndex], a[startIndex + 1], ..., a[numberUsed - 1].

20 int main( )
21 {
22 cout << "Este programa ordena números do menor para o maior.\n";

23 int sampleArray[10], numberUsed;


24 fillArray(sampleArray, 10, numberUsed);
25 sort(sampleArray, numberUsed);

26 cout << "Em ordem ascendente, os números são:\n";


27 for (int index = 0; index < numberUsed; index++)
28 cout << sampleArray[index] << " ";
29 cout << endl;

30 return 0;
31 }

32 void fillArray(int a[], int size, int& numberUsed)


33 <O resto da definição de fillArray é dado no Painel 5.5.>

34 void sort(int a[], int numberUsed)


35 {
36 int indexOfNextSmallest;
37 for (int index = 0; index < numberUsed - 1; index++)
38 {//Coloca o valor correto em a[index]:
39 indexOfNextSmallest =
40 indexOfSmallest(a, index, numberUsed);
41 swapValues(a[index], a[indexOfNextSmallest]);
42 //a[0] <= a[1] <=...<= a[index] são os menores elementos do vetor
43 //original. O resto dos elementos estão nas posições remanescentes.
44 }
45 }

46 void swapValues(int& v1, int& v2)


47 {
48 int temp;
49 temp = v1;
50 v1 = v2;
51 v2 = temp;
52 }
53

54 int indexOfSmallest(const int a[], int startIndex, int numberUsed)


55 {
56 int min = a[startIndex],
57 indexOfMin = startIndex;
58 for (int index = startIndex + 1; index < numberUsed; index++)
59 if (a[index] < min)
60 {
61 min = a[index];
62 indexOfMin = index;
63 //min é o menor de a[startIndex] até a[index]
64 }

65 return indexOfMin;
66 }
Vetores Multidimensionais 139

Painel 5.8 Ordenação por seleção (parte 3 de 3)


DIÁLOGO PROGRAMA-USUÁRIO
Este programa ordena números do menor para o maior.
Forneça até 10 números inteiros não-negativos.
Assinale o final da lista com um número negativo.
80 30 50 70 60 90 20 30 40 -1
Em ordem ascendente, os números são:
20 30 40 50 60 70 80 90

1 Exercícios de Autoteste 1

17. Escreva um programa que leia até dez números inteiros não-negativos de um vetor chamado numeroVe-
tor e depois escreva esses números na tela. Para este exercício você não precisa usar nenhuma função.
É apenas um programa-brinquedo e pode ser bem pequeno.
18. Escreva um programa que leia até dez letras de um vetor e escreva as letras na tela em ordem inversa.
Por exemplo, se a entrada for
abcd.
a saída deve ser
dcba
Utilize um ponto final como sentinela para marcar o fim da entrada. Chame o vetor de caixaDeLetras.
Não precisa utilizar nenhuma função. É apenas um programa-brinquedo e pode ser bem pequeno.
19. Abaixo está a declaração para uma versão alternativa da função busca definida no Painel 5.6. A fim de
utilizar esta versão alternativa da função busca, precisaríamos reescrever alguns trechos do programa,
mas para este exercício tudo o que você precisa fazer é escrever a definição de função para esta versão
alternativa de busca.
bool busca(const int a[], int numeroUsado,
int alvo, int& onde);
//Pré-condição: numeroUsado é <= ao tamanho declarado do
//vetor a. Além disso, a[0] até a[numeroUsado - 1] possuem valores.
//Pós-condição: Se alvo é um dos elementos de a[0]
//até a[numeroUsado - 1], então essa função é avaliada como
//true e fixa o valor de onde de modo que a[onde] ==
//alvo; caso contrário, essa função é avaliada como false e o
//valor de onde fica inalterado.

5.4 1 Vetores Multidimensionais


O C++ permite que se declarem vetores com mais de um índice. Esta seção descreve esses vetores multidimensionais.

■ FUNDAMENTOS DOS VETORES MULTIDIMENSIONAIS


Às vezes é útil ter um vetor com mais de um índice, e isso é permitido em C++. A linha seguinte declara um
vetor de caracteres chamado pagina. O vetor pagina possui dois índices. O primeiro vai de 0 a 29, e o segundo
de 0 a 99.
char pagina[30][100];

Cada uma das variáveis indexadas para este vetor possui dois índices. Por exemplo, pagina[0][0], pagi-
na[15][32] e pagina [29][99] são três das variáveis indexadas para este vetor. Observe que cada índice deve estar
dentro de seus próprios colchetes. Como acontecia com os vetores de uma dimensão, que já estudamos, cada va-
riável indexada para um vetor multidimensional é uma variável do tipo-base.
Um vetor pode ter qualquer número de índices, mas talvez o número mais comum seja dois. Um vetor bidi-
mensional pode ser visualizado como uma apresentação de duas dimensões em que o primeiro índice fornece a li-
140 Vetores

nha, e o segundo, a coluna. Por exemplo, as variáveis indexadas do vetor bidimensional pagina podem ser visuali-
zadas da seguinte forma:
pagina[0][0], pagina[0][1], ..., pagina[0][99]
pagina[1][0], pagina[1][1], ..., pagina[1][99]
pagina[2][0], pagina[2][1], ..., pagina[2][99]
.
.
.
pagina[29][0], pagina[29][1], ..., pagina[29][99]

Você pode utilizar o vetor pagina pra armazenar todos os caracteres de uma página de texto que possua trinta
linhas (numeradas de 0 a 29) e 100 caracteres em cada linha (numerados de 0 a 99).
Em C++, um vetor bidimensional, como pagina, é na verdade um vetor de vetores. O vetor pagina acima é, na
realidade, um vetor unidimensional de tamanho 30, cujo tipo-base é um vetor de caracteres unidimensional de tama-
nho 100. Normalmente, isso não deve ser motivo de preocupação, e você pode agir como se o vetor pagina fosse mes-
mo um vetor com dois índices (em vez de um vetor de vetores, o que é mais difícil de entender). Há, todavia, pelo
menos uma situação em que um vetor bidimensional se parece muito com um vetor de vetores: quando se tem uma
função com um parâmetro vetorial para um vetor bidimensional, o que será discutido na próxima subseção.

DECLARAÇÃO DE VETOR MULTIDIMENSIONAL


SINTAXE
Tipo Nome_Vetor[Tamanho_Dimensao_1] [Tamanho_Dimensao_2] ... [Tamanho_Dimensao_Final]
EXEMPLOS
char pagina[30][100];
int matriz[2][3];
double tresDImagem[10][20][30];
Uma declaração de vetor da forma mostrada acima definirá uma variável indexada para cada combinação de índices vetoriais.
Por exemplo, a segunda das declarações acima define as seis variáveis indexadas seguintes para o vetor matriz:
matriz[0][0], matriz[0][1], matriz[0][2],
matriz[1][0], matriz[1][1], matriz[1][2]

■ PARÂMETROS DE VETORES MULTIDIMENSIONAIS


A seguinte declaração de um vetor bidimensional declara, na realidade, um vetor unidimensional de tamanho
30 cujo tipo-base é um vetor unidimensional de caracteres de tamanho 100.
char pagina[30][100];

Visualizar um vetor bidimensional como um vetor de vetores o ajudará a entender como o C++ lida com os
parâmetros de vetores multidimensionais.
Por exemplo, a função seguinte toma um vetor, como pagina, e o imprime na tela:
void exibePagina(const char p[][100], int tamanhoDimensao1)
{
for (int indice1 = 0; indice1 < tamanhoDimensao1; indice1++)
{//Imprimindo uma linha:
for (int indice2 = 0; indice2 < 100; indice2++)
cout << p[indice1][indice2];
cout << endl;
}
}

Observe que, com um parâmetro vetorial bidimensional, o tamanho da primeira dimensão não é dado, e pre-
cisamos incluir um parâmetro int para fornecer o tamanho da primeira dimensão. (Como com vetores comuns, o
compilador permitirá que você especifique a primeira dimensão, colocando um número dentro do primeiro par de
colchetes. Entretanto, tal número é apenas um comentário; o compilador o ignora.) O tamanho da segunda di-
Vetores Multidimensionais 141

mensão (e todas as outras dimensões, se houver mais do que duas) é dado depois do parâmetro vetorial, como
mostrado pelo parâmetro
const char p[][100]

Se você compreende que um vetor multidimensional é um vetor de vetores, essa regra começa a fazer sentido.
Como o parâmetro vetorial bidimensional
const char p[][100]

é um parâmetro para um vetor de vetores, a primeira dimensão é na realidade o índice do vetor e é tratado exata-
mente como um índice de vetor para um vetor comum, unidimensional. A segunda dimensão é parte da descrição
do tipo-base, que é um vetor de caracteres de tamanho 100.

PARÂMETROS VETORIAIS MULTIDIMENSIONAIS


Quando um parâmetro vetorial multidimensional é dado em um cabeçalho ou declaração de função, o tamanho da primeira di-
mensão não é dado, mas os tamanhos remanescentes precisam ser dados entre colchetes. Como o tamanho da primeira dimen-
são não é dado, normalmente você precisa de um parâmetro adicional de tipo int que fornece o tamanho desta primeira
dimensão. A seguir há um exemplo de uma declaração de função com um parâmetro vetorial bidimensional p:
void recebePagina(char p[][100], int tamanhoDimensao1);

PROGRAMA BIDIMENSIONAL DE NOTAS ESCOLARES


O Painel 5.9 contém um programa que utiliza um vetor bidimensional chamado notas para armazenar e de-
pois exibir as notas de uma classe pequena. A classe tem quatro alunos, e os registros incluem três provas.
O Painel 5.10 ilustra como o vetor notas é usado para armazenar dados. O primeiro índice é usado para
designar um aluno, e o segundo é usado para designar uma prova. Como alunos e provas são numerados a
partir do 1 e não do 0, devemos subtrair 1 do número dos alunos e do número da prova para obter a va-
riável indexada que armazena uma nota de prova em particular. Por exemplo, a nota que o aluno de núme-
ro 4 recebeu na prova de número 1 é registrada em nota[3][0].
Nosso programa também usa dois vetores comuns unidimensionais. O vetor aluMed será usado para regis-
trar a nota média para cada um dos alunos. Por exemplo, o programa fixará aluMed[0] como igual à média
das notas de prova recebidas pelo aluno 1, aluMed[1] como igual à média das notas de prova recebidas
pelo aluno 2, e assim por diante. O Painel 5.10 ilustra a relação entre os vetores notas, aluMed e provaMed.
Esse Painel mostra alguns dados de amostra para o vetor notas. Esses dados, por sua vez, determinam os
valores que o programa armazena em aluMed e em provaMed. O Painel 5.11 também mostra esses valores,
que o programa calcula para aluMed e provaMed.
O programa completo para preencher o vetor notas e depois calcular e exibir tanto as médias dos alunos
quanto as médias das provas é mostrado no Painel 5.9. Nesse programa, declaramos as dimensões do vetor
como constantes nomeadas globais. Como os procedimentos são específicos para este programa e não po-
dem ser reutilizados em outro lugar, utilizamos essas constantes definidas globalmente nos corpos dos pro-
cedimentos, em vez de ter parâmetros para o tamanho das dimensões do vetor. Como isso é rotina, o painel
não mostra o código que preenche o vetor.

Painel 5.9 Vetor bidimensional (parte 1 de 3)


1 //Lê pontuações em provas para cada aluno em um vetor bidimensional de notas (mas o código
2 //de entrada não é mostrado no painel). Calcula a pontuação média para cada aluno e a
3 //pontuação média para cada prova. Exibe as pontuações em cada prova e as médias.
4 #include <iostream>
5 #include <iomanip>
6 using namespace std;
7 const int NUMBER_STUDENTS = 4, NUMBER_QUIZZES = 3;

8 void computeStAve( const int grade[][NUMBER_QUIZZES], double stAve[]);


9 //Pré-condição: As constantes globais NUMBER_STUDENTS e NUMBER_QUIZZES
10 //são as dimensões do vetor notas. Cada uma das variáveis indexadas
11 //grade[stNum-1, quizNum-1] contém a pontuação para o aluno stNum na prova quizNum.
12 //Pós-condição: Cada stAve[stNum-1] contém a média para o aluno número stNum.
13
142 Vetores

Painel 5.9 Vetor bidimensional (parte 2 de 3)


14 void computeQuizAve(const int grade[][NUMBER_QUIZZES] , double quizAve[]);
15 //Pré-condição: As constantes globais NUMBER_STUDENTS e NUMBER_QUIZZES
16 //são as dimensões do vetor notas. Cada uma das variáveis indexadas
17 //grade[stNum-1, quizNum-1] contém a pontuação para o aluno stNum na prova quizNum.
18 //Pós-condição: Cada quizAve[quizNum-1] contém a média para a prova número
19 //quizNum.

20 void display( const int grade[][NUMBER_QUIZZES],


21 const double stAve[], const double quizAve[]);
22 //Pré-condição: As constantes globais NUMBER_STUDENTS e NUMBER_QUIZZES
23 //são as dimensões do vetor notas. Cada uma das variáveis indexadas grade[stNum-1,
24 //quizNum-1] contém a pontuação para o aluno stNum na prova quizNum. Cada
25 //stAve[stNum-1] contém a média para o aluno stNum. Cada quizAve[quizNum-1]
26 //contém a média para a prova número quizNum.
27 //Pós-condição: Todos os dados em grade, stAve e quizAve são mostrados na tela.

28 int main( )
29 {
30 int grade[NUMBER_STUDENTS][NUMBER_QUIZZES];
31 double stAve[NUMBER_STUDENTS];
32 double quizAve[NUMBER_QUIZZES];
33
34 <O código para preencher o vetor notas vai aqui, mas não é mostrado.>
35
36 computeStAve(grade, stAve);
37 computeQuizAve(grade, quizAve);
38 display(grade, stAve, quizAve);
39 return 0;
40 }

41 void computeStAve(const int grade[][NUMBER_QUIZZES], double stAve[])


42 {
43 for (int stNum = 1; stNum <= NUMBER_STUDENTS; stNum++)
44 {//Processa um stNum:
45 double sum = 0;
46 for (int quizNum = 1; quizNum <= NUMBER_QUIZZES; quizNum++)
47 sum = sum + grade[stNum-1][quizNum-1];
48 //sum contém a soma das pontuações nas provas para o aluno número stNum.
49 stAve[stNum-1] = sum/NUMBER_QUIZZES;
50 //A média para o aluno stNum é o valor de stAve[stNum-1]
51 }
52 }

53 void computeQuizAve(const int grade[][NUMBER_QUIZZES], double quizAve[])


54 {
55 for (int quizNum = 1; quizNum <= NUMBER_QUIZZES; quizNum++)
56 {//Processa uma prova (para todos os alunos):
57 double sum = 0;
58 for (int stNum = 1; stNum <= NUMBER_STUDENTS; stNum++)
59 sum = sum + grade[stNum-1][quizNum-1];
60 //sum contém a soma das pontuações de todos os alunos nas provas número quizNum.
61 quizAve[quizNum-1] = sum/NUMBER_STUDENTS;
62 //A média para a prova quizNum é o valor de quizAve[quizNum-1]
63 }
64 }

65 void display(const int grade[][NUMBER_QUIZZES],


66 const double stAve[], const double quizAve[])
Vetores Multidimensionais 143

Painel 5.9 Vetor bidimensional (parte 3 de 3)


67 {
68 cout.setf(ios::fixed);
69 cout.setf(ios::showpoint);
70 cout.precision(1);

71 cout << setw(10) << "Aluno"


72 << setw(5) << "Média"
73 << setw(15) << "Provas\n";
74 for (int stNum = 1; stNum <= NUMBER_STUDENTS; stNum++)
75 {//Exibição na tela para stNum:
76 cout << setw(10) << stNum
77 << setw(5) << stAve[stNum-1] << " ";
78 for (int quizNum = 1; quizNum <= NUMBER_QUIZZES; quizNum++)
79 cout << setw(5) << grade[stNum-1][quizNum-1];
80 cout << endl;
81 }

82 cout << "Médias das provas = ";


83 for (int quizNum = 1; quizNum <= NUMBER_QUIZZES; quizNum++)
84 cout << setw(5) << quizAve[quizNum-1];
85 cout << endl;
86 }

DIÁLOGO PROGRAMA-USUÁRIO
<O texto para preencher o vetor notas não é mostrado.>
Aluno Média Provas
1 10.0 10 10 10
2 1.0 2 0 1
3 7.7 8 6 9
4 7.3 8 4 10
Médias das provas = 7.0 5.0 7.5

Painel 5.10 Vetor bidimensional notas

prova 1 prova 2 prova 3

aluno 1
1
aluno 2 nota[O] [O] nota [O] [1] nota [O] [2]
2
aluno 3 nota [1] [O] nota [1] [1] nota [1] [2]
3
aluno 4 nota[2] [O] nota [2] [1] nota [2] [2]
4
nota[3] [O] nota [3] [1] nota [3] [2]

nota [3][0] é a nota nota [3][1] é a nota nota [3][2] é a nota


que o aluno 4 recebeu que o aluno 4 recebeu que o aluno 4 recebeu
na prova 1. na prova 2. na prova 3.
144 Vetores

Painel 5.11 Vetor bidimensional notas


prova 1 prova 2 prova 3

aluno 1 10 10 10 10.0 média[0]


aluno 2
2 o 1 1.0 média[l]

aluno 3
8 6 9 7.7 média[2]
aluno 4
8 4 10 7 .3 média[3]

Média 7.0 5.0 7.5


,....., ,.....,
......
,.....,
E=, ~ ~
"'
-o
"'
"C -o
"'
,w ,w ,w
E E E

1 Exercícios de Autoteste 1

20. Qual é a saída produzida pelo seguinte código?


int meuVetor[4][4], indice1, indice2;
for (indice1 = 0; indice1 < 4; indice1++)
for (indice2 = 0; indice2 < 4; indice2++)
meuVetor[indice1][indice2] = indice2;
for (indice1 = 0; indice1 < 4; indice1++)
{
for (indice2 = 0; indice2 < 4; indice2++)
cout << meuVetor[indice1][indice2] << " ";
cout << endl;
}
21. Escreva código para preencher o vetor a (declarado abaixo) com números digitados no teclado. Serão
fornecidos cinco números por linha, em quatro linhas (embora nossa solução não dependa obrigatoria-
mente de como os números da entrada são divididos em linhas).
int a[4][5];
22. Escreva uma definição de função para uma função void chamada eco de tal forma que a seguinte cha-
mada de função ecoe a entrada descrita no Exercício de Autoteste 21 e no mesmo formato que especi-
ficamos para a entrada (ou seja, quatro linhas de cinco números por linha):
eco(a, 4);

Resumo do Capítulo

■ Um vetor pode ser usado para armazenar e manipular uma coleção de dados que sejam todos de mesmo tipo.
■ As variáveis indexadas de um vetor podem ser usadas exatamente como quaisquer outras variáveis do tipo-
base do vetor.
■ Um loop for é uma boa forma de se percorrer os elementos de um vetor e executar alguma ação do pro-
grama sobre cada variável indexada.
■ O erro mais comum em programação é cometido quando se utilizam vetores tentando ter acesso a índices
vetoriais inexistentes. Verifique sempre a primeira e a última iterações de um loop que manipule um vetor
para garantir que não seja usado um índice ilegalmente pequeno ou grande.
■ Um parâmetro formal vetorial não é um parâmetro chamado por valor nem um parâmetro chamado por
referência, e sim um novo tipo de parâmetro. Um parâmetro vetorial é semelhante a um parâmetro chama-
Respostas dos Exercícios de Autoteste 145

do por referência no sentido de que qualquer mudança no parâmetro formal no corpo da função será feita
no argumento vetorial quando a função for chamada.
■ As variáveis indexadas de um vetor são armazenadas umas ao lado das outras na memória do computador,
de modo que o vetor ocupa uma porção contígua da memória. Quando o vetor é transmitido como argu-
mento para uma função, apenas o endereço da primeira variável indexada (numerada como 0) é fornecido
à função que faz a chamada. Portanto, a função com um parâmetro vetorial normalmente precisa de outro
parâmetro formal de tipo int para fornecer o tamanho do vetor.
■ Quando se usa um vetor parcialmente preenchido, seu programa necessita de uma variável adicional de
tipo int para controlar quanto do vetor é usado.
■ Para dizer ao compilador que um argumento vetorial não deve ser alterado pela sua função, você pode in-
serir o modificador const antes do parâmetro vetorial para aquela posição de argumento. Um parâmetro
vetorial que é modificado com um const é chamado de parâmetro vetorial constante.
■ Se você precisar de um vetor com mais de um índice, utilize um vetor multidimensional, que, na verdade,
é um vetor de vetores.

RESPOSTAS DOS EXERCÍCIOS DE AUTOTESTE


1. int a[5] é uma declaração em que 5 é o número de elementos do vetor. A expressão a[4] é um acesso
ao vetor definido pela declaração anterior. O acesso é para o elemento com o índice 4, que é o quinto (e
último) elemento do vetor.
2. a. nota
b. double
c. 5
d. de 0 a 4
e. nota[0], nota[1], nota[2], nota[3] ou nota[4]
3. a. Um inicializador a mais.
b. Correto. O tamanho do vetor é 4.
c. Correto. O tamanho do vetor é 4.
4. abc
5. 1.1 2.2 3.3
1.1 3.3 3.3
(Lembre-se de que os índices começam com 0, não com 1.)
6. 0 2 4 6 8 10 12 14 16 18
0 4 8 12 16
7. As variáveis indexadas de amostraVetor vão de amostraVetor[0] até amostraVetor[9], mas esse trecho de có-
digo tenta preencher de amostraVetor[1] até amostraVetor[10]. O índice 10 em amostraVetor[10] está fora
do intervalo.
8. Há um índice fora do intervalo. Quando indice é igual a 9, indice + 1 é igual a 10, a[indice + 1],
que é o mesmo que a[10], possui um índice ilegal. O loop deveria acabar uma iteração antes. Para corri-
gir o código, mude a primeira linha do loop for para
for (int indice = 0; indice < 9; indice++)
9. int i, a[20];
cout << "Forneça 20 números:\n";
for (i = 0; i < 20; i++)
cin >> a[i];
10. O vetor consumirá 14 bytes de memória. O endereço da variável indexada seuVetor[3] é 1006.
11. As seguintes chamadas de função são aceitáveis:
triplicador(a[2]);
triplicador(a[numero]);
triplicador(numero);
As seguintes chamadas de função são incorretas:
triplicador(a[3]);
triplicador(a);
146 Vetores

A primeira possui um índice ilegal. A segunda não possui nenhuma expressão indexada. Não se pode usar
um vetor completo como um argumento de triplicador, como na segunda chamada. A seção Vetores
Completos como Argumentos de Função discute uma situação diferente em que você pode usar um vetor
completo como argumento.
12. O loop passa por variáveis indexadas, de b[1] a b[5], mas 5 é um índice ilegal para o vetor b. Os índices
são 0, 1, 2, 3 e 4. A versão correta do código é dada abaixo:
int b[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++)
triplicador(b[i]);
13. void maisUm(int a[], int tamanho)
//Pré-condição: tamanho é o tamanho declarado do vetor a.
//a[0] até a[tamanho-1] receberam valores.
//Pós-condição: a[indice] é aumentado em 1
//para todas as variáveis indexadas de a.
{
for (int indice = 0; indice < size; indice++)
a[indice] = a[indice] + 1;
}
14. As seguintes chamadas de função são aceitáveis:
tambem2(meuVetor, 29);
tambem2(meuVetor, 10);
tambem2(seuVetor, 100);
A chamada
tambem2(meuVetor, 10);
é legal, mas preencherá apenas as primeiras dez variáveis indexadas de meuVetor. Se for isso o desejado, a cha-
mada é aceitável.
As seguintes chamadas de função são incorretas:
tambem2(meuVetor, 55);
"Ei tambem2. Por favor venha aqui."
tambem2(meuVetor[3], 29);
A primeira destas é incorreta porque o segundo argumento é muito extenso, a segunda porque está faltan-
do o ponto-e-vírgula final (e por outras razões) e a terceira porque utiliza uma variável indexada para um
argumento, quando deveria utilizar o vetor completo.
15. Você pode transformar o parâmetro vetorial saida em parâmetro constante, já que não há necessidade de
alterar os valores de qualquer variável indexada do parâmetro vetorial. Não se pode transformar o parâme-
tro descartaImpar em parâmetro constante porque os valores de algumas variáveis indexadas podem ser
alterados.
void saida(const double a[], int tamanho);
//Pré-condição: a[0] até a[tamanho - 1] possuem valores.
//Pós-condição: a[0] até a[tamanho - 1] foram escritos.

void descartaImpar(int a[], int tamanho);


//Pré-condição: a[0] até a[tamanho - 1] possuem valores.
//Pós-condição: Todos os números ímpares em a[0] até a[tamanho - 1]
// foram alterados para 0.
16. int foraDeOrdem(double vetor[], int tamanho)
{
for (int i = 0; i < tamanho - 1; i++)
if (vetor[i] > vetor [i+1] //extrai a[i+1] para cada i.
return i+1;
return -1;
}
17. #include <iostream>
using namespace std;
const int TAMANHO_DECLARADO = 10;
Respostas dos Exercícios de Autoteste 147

int main( )
{
cout << "Forneça até dez inteiros não-negativos.\n"
<< "Coloque um número negativo ao final.\n";
int numeroVetor[TAMANHO_DECLARADO], proximo, indice = 0;
cin >> proximo;
while ( (proximo >= 0) && (indice < TAMANHO_DECLARADO) )
{
numeroVetor[indice] = proximo;
indice++;
cin >> proximo;
}

int numeroUsado = indice;


cout << "Aqui estão eles de volta para você:";
for (indice = 0; indice < numeroUsado; indice++)
cout << numeroVetor[indice] << " ";
cout << endl;
return 0;
}
18. #include <iostream>
using namespace std;
const int TAMANHO_DECLARADO = 10;

int main( )
{
cout << "Forneça até dez letras"
<< " seguidas por um ponto final:\n";
char caixaDeLetras [TAMANHO_DECLARADO], proxima;
int indice = 0;
cin >> proxima;
while ( (proxima != ’.’) && (indice < TAMANHO_DECLARADO) )
{
caixaDeLetras [indice] = proxima;
indice++;
cin >> proxima;
}

int numeroUsado = indice;


cout << "Aqui estão elas na ordem inversa:\n";
for (indice = numeroUsado-1; indice >= 0; indice--)
cout << caixaDeLetras [indice];
cout << endl;
return 0;
}
19. bool busca(const int a[], int numeroUsado,
int alvo, int& onde)
{
int indice = 0;
bool encontrado = false;
while ((!encontrado) && (indice < numeroUsado))
if (alvo == a[indice])
encontrado = true;
else
indice++;
//Se alvo foi encontrado, então
//encontrado == true e a[indice] == alvo.
148 Vetores

if (encontrado)
onde = indice;
return encontrado;
}
20. 0 1 2 3
0 1 2 3
0 1 2 3
0 1 2 3
21. int a[4][5];
int indice1, indice2;
for (indice1 = 0; indice1 < 4; indice1++)
for (indice2 = 0; indice2 < 5; indice2++)
cin >> a[indice1][indice2];
22. void eco(const int a[][5], int tamanhoDea)
//Apresenta como saída os valores no vetor a em tamanhoDea linhas
//com 5 números por linha.
{
for (int indice1 = 0; indice1 < tamanhoDea; indice1++)
{
for (int indice2 = 0; indice2 < 5; indice2++)
cout << a[indice1][indice2] << " ";
cout << endl;
}
}

PROJETOS DE PROGRAMAÇÃO
1. Escreva um programa que leia a quantidade média de chuva mensal de uma cidade para cada mês do ano
e depois leia a quantidade real de chuva para cada um dos 12 meses anteriores. Assim, o programa impri-
me uma tabela bem formatada que mostra a quantidade de chuva para cada um dos 12 meses anteriores
e também quão acima ou quão abaixo da média a quantidade de chuva foi a cada mês. A média mensal é
dada pelos meses de janeiro, fevereiro e assim por diante, em seqüência. Para obter a quantidade de chuva
real nos 12 meses anteriores, o programa primeiro pergunta qual é o mês atual e depois pede as cifras da
quantidade de chuva nos 12 meses anteriores. A saída deve nomear corretamente os meses.
Existem várias formas de se lidar com nomes de meses. Um método simples é codificar os meses como
inteiros e depois fazer uma conversão antes de executar a saída. Um grande comando switch é aceitável
em uma função de saída. A entrada dos meses pode ser tratada da maneira que você desejar, desde que
seja relativamente fácil e agradável para o usuário.
Depois que houver acabado o programa acima, produza uma versão aperfeiçoada que também apresente
um gráfico exibindo a quantidade média e a quantidade real de chuva para cada um dos 12 meses ante-
riores. O gráfico deve ser similar àquele mostrado no Painel 5.4, a não ser pelo fato de que deve haver
duas barras para cada mês e estas devem receber os rótulos de quantidade média de chuva e quantidade
de chuva no último mês. Seu programa deve perguntar se o usuário deseja ver a tabela ou o gráfico de
barras, e depois deve exibir o formato requisitado. Inclua um loop que permita que o usuário veja um ou
outro formato quantas vezes desejar até requisitar que o programa se encerre.
2. Escreva uma função chamada apagaRepetidas que tenha um vetor de caracteres parcialmente preenchido
como parâmetro formal e que remova todas as letras repetidas do vetor. Como um vetor parcialmente
preenchido exige dois argumentos, a função na realidade terá dois parâmetros formais: um parâmetro ve-
torial e um parâmetro formal de tipo int que fornece o número de posições vetoriais usadas. Quando
uma letra é removida, as letras restantes são movidas para a frente para preencher as vagas. Isso criará po-
sições vazias no final do vetor, de modo que uma parte menor do vetor é utilizada. Como o parâmetro
formal é um vetor parcialmente preenchido, um segundo parâmetro formal de tipo int dirá quantas posi-
ções do vetor são preenchidas. Este segundo parâmetro formal será um parâmetro chamado por referência
Projetos de Programação 149

e será alterado para mostrar quanto do vetor é usado depois que as letras repetidas são removidas. Por
exemplo, considere o seguinte código:
char a[10];
a[0] = ’a’;
a[1] = ’b’;
a[2] = ’a’;
a[3] = ’c’;
int tamanho = 4;
apagaRepetidas(a, tamanho);
Depois que esse código é executado, o valor de a[0] é ’a’, o valor de a[1] é ’b’, o valor de a[2] é ’c’ e o
valor de tamanho é 3. (O valor de a[3] não importa, já que o vetor parcialmente preenchido não utiliza
mais essa variável indexada.) Você pode assumir que o vetor parcialmente preenchido contenha apenas le-
tras minúsculas. Insira sua função em um programa-teste adequado.
3. O desvio-padrão de uma lista de números é a medida de quanto os números se desviam da média. Se o
desvio-padrão é pequeno, os números estão aglomerados junto à média. Se o desvio-padrão é grande, os
números estão dispersos em relação à média. O desvio-padrão, S, de uma lista de N números xi é definido
da seguinte forma:

L (x;-x)2
5= _i_=l_ __
N

em que x– é a média de N números x1, x2, ... Defina uma função que tome um vetor parcialmente preen-
chido de números e seu argumento e retorne o desvio-padrão dos números no vetor parcialmente preenchido.
Como um vetor parcialmente preenchido requer dois argumentos, a função, na realidade, terá dois parâ-
metros formais: um parâmetro vetorial e um parâmetro formal de tipo int que fornece o número de po-
sições vetoriais utilizado. Os números no vetor serão de tipo double. Insira sua função em um
programa-teste adequado.
4. Escreva um programa que leia um vetor de tipo int. Você pode presumir que existam menos de 50 ele-
mentos no vetor. O programa determina quantas posições são ocupadas. A saída deve ser em uma lista de
duas colunas. A primeira coluna é uma lista dos diferentes elementos do vetor; a segunda coluna é a con-
tagem do número de ocorrências de cada elemento. A lista deve ser ordenada com base na primeira colu-
na, do maior para o menor.
Para os seguintes valores:
-12 3 -12 4 1 1 -12 1 -1 1 2 3 4 2 3 -12
a saída deve ser
N Count
4 2
3 3
2 2
1 4
-1 1
-12 4
5. Um vetor pode ser usado para armazenar grandes inteiros, um dígito de cada vez. Por exemplo, o inteiro
1234 pode ser armazenado no vetor a fixando-se a[0] como 1, a[1] como 2, a[2] como 3 e a[3] como 4.
Entretanto, para este exercício você pode achar mais útil armazenar os dígitos de trás para a frente, ou
seja, colocar o 4 em a[0], o 3 em a[1], o 2 em a[2] e o 1 em a[3]. Neste exercício você escreverá um
programa que leia dois inteiros positivos de 20 ou menos dígitos de comprimento e depois apresente a
soma dos dois números. O programa lerá os dígitos como valores do tipo char, de forma que o número
1234 é lido como os quatro caracteres ’1’, ’2’, ’3’ e ’4’. Depois de serem lidos pelo programa, os caracteres
serão alterados para valores de tipo int. Os dígitos serão lidos e inseridos em um vetor parcialmente
preenchido, e talvez você ache útil inverter a ordem dos elementos no vetor depois que este seja preenchi-
150 Vetores

do com os dados do teclado. (A opção entre inverter ou não a ordem dos elementos no vetor é sua. O
programa pode ser feito dos dois modos, e cada um tem suas vantagens e desvantagens.) O programa exe-
cutará a adição, implementando o algoritmo comum da adição. O resultado da adição é armazenado em
um vetor de tamanho 20 e, então, o resultado é escrito na tela. Se o resultado da adição é um inteiro
com mais do que o número máximo de dígitos (ou seja, mais de 20 dígitos), seu programa deve emitir
uma mensagem dizendo que encontrou um "estouro de inteiros". Você deve ser capaz de alterar o com-
primento máximo dos inteiros mudando apenas uma constante definida globalmente. Inclua um loop que
permita ao usuário continuar a fazer adições até dizer que o programa deve ser encerrado.
6. Escreva um programa que permita dois usuários jogar o jogo-da-velha. O programa deve pedir que os jo-
gadores X e O informem os lances alternadamente. O programa exibe as posições do jogo da seguinte for-
ma:
1 2 3
4 5 6
7 8 9
O jogador faz o lance informando o número da posição que deseja assinalar. Após cada lance, o programa
exibe o tabuleiro. Um exemplo de configuração de tabuleiro:
X X O
4 5 6
O 8 9
7. Escreva um programa para atribuir assentos a passageiros em um avião. Considere um avião pequeno com
assentos numerados da seguinte forma;
1 A B C D
2 A B C D
3 A B C D
4 A B C D
5 A B C D
6 A B C D
7 A B C D
O programa deve exibir o padrão dos assentos, com um ’X’ assinalando os assentos já atribuídos. Por
exemplo, depois que os assentos 1A, 2B e 4C já foram atribuídos, o padrão deve ser o seguinte:
1 X B C D
2 A X C D
3 A B C D
4 A B X D
5 A B C D
6 A B C D
7 A B C D
Depois de mostrar os assentos disponíveis, o programa pede que o usuário indique o assento desejado, o
usuário digita a informação pedida e depois o quadro de assentos disponíveis é atualizado. Isso prossegue
até que todos os assentos sejam ocupados ou até o usuário pedir que o programa termine. Se o usuário es-
colher um assento já atribuído, o programa deve dizer que o assento está ocupado e pedir que o usuário
escolha outro.
8. Escreva um programa que aceite dados de entrada como o programa no Painel 5.4 e que apresente como
saída um gráfico de barras como o daquele programa, a não ser pelo fato de que seu programa apresentará
as barras verticalmente e não horizontalmente. Um vetor bidimensional pode ser útil.
9. O matemático John Horton Conway inventou o "Jogo da Vida". Embora não seja um "jogo" no sentido
tradicional, ele apresenta um comportamento interessante, especificado com poucas regras. Esse projeto
pede que você escreva um programa que lhe permita especificar uma configuração inicial. O programa se-
gue as regras da Vida (listadas brevemente) para mostrar o comportamento contínuo da configuração.
VIDA é um organismo que vive em um mundo distinto, bidimensional. Embora esse mundo seja, na rea-
lidade, ilimitado, não temos toda essa liberdade e, assim, restringimos o vetor a 80 caracteres de largura e
22 de altura. Se você tem acesso a uma tela maior, use-a!
Projetos de Programação 151

Esse mundo é um vetor em que cada célula é capaz de abrigar uma célula da VIDA. As gerações marcam
a passagem do tempo. Cada geração traz nascimentos e mortes para a comunidade da VIDA. Os nasci-
mentos e mortes seguem o conjunto de regras:
1. Cada célula possui oito células vizinhas. As vizinhas de uma célula são as células diretamente acima,
abaixo, à direita, à esquerda, diagonalmente acima à direita ou à esquerda e diagonalmente abaixo à di-
reita ou à esquerda.
2. Se uma célula ocupada não possui vizinhas ou possui apenas uma, morre de solidão. Se uma célula
ocupada possui mais de três vizinhas, morre de superpopulação.
3. Se uma célula vazia possui exatamente três células vizinhas ocupadas, há o nascimento de uma nova cé-
lula para substituir a célula vazia.
4. Nascimentos e mortes são instantâneos e ocorrem com as mudanças de geração. Uma célula que morre
por qualquer razão pode ajudar a provocar o nascimento, mas uma célula recém-nascida não pode res-
suscitar uma célula que está morrendo, nem a morte de uma célula impede a morte de outra, digamos,
por meio da redução da população local.
*
Exemplos: *** vira * depois vira *** de novo e assim por diante.
*
Observações: algumas configurações crescem a partir de configurações iniciais bem pequenas. Outras se des-
locam pela região. Recomenda-se que, para a saída de texto, você utilize um vetor retangular de char com
80 colunas e 22 linhas para armazenar as sucessivas gerações mundiais de VIDA. Utilize um * para indicar
uma célula viva e um espaço em branco para indicar uma célula vazia (ou morta). Se você possui uma
tela com mais linhas do que isso, não hesite em utilizar a tela toda.
Sugestões: procure configurações estáveis. Ou seja, procure por comunidades que repitam os padrões conti-
nuamente. O número de configurações na repetição é chamado de período. Há configurações que são fi-
xas, ou seja, que permanecem sem mudança. Um plano possível é encontrar essas configurações.
Dicas: defina uma função void chamada geração que tome o vetor que chamamos mundo, um vetor de
tipo char de 80 colunas por 22 linhas, que contenha a configuração inicial. A função percorre o vetor e
modifica as células, assinalando as células com nascimentos e mortes de acordo com as regras listadas an-
teriormente. Isso envolve examinar uma célula de cada vez e ou matar a célula, ou deixá-la viver ou, se a
célula estiver vazia, decidir se uma célula deve nascer. Deve haver uma função mostra que aceite o vetor
mundo e exiba o vetor na tela. É preciso haver alguma espécie de intervalo de tempo entre as chamadas a
geração e a mostra. Para fazer isso, seu programa deve gerar e mostrar a próxima geração quando se aper-
ta a tecla Return. Você é livre para automatizar isso, mas a automação não é necessária para o programa.
CAPÍTULO

Estruturas e Classes
Estruturas e Classes

6Estruturas e Classes
— Chegou a hora — disse a Morsa —
De falar de muitas coisas:
De sapatos, navios, lacres,
De repolhos e de reis.
Lewis Carroll, Através do Espelho

INTRODUÇÃO
As classes talvez sejam o recurso mais importante que separa a linguagem C++ da lingua-
gem C. Uma classe é um tipo cujos valores se chamam objetos. Os objetos possuem tanto
funções de dados quanto funções-membros. As funções-membros têm acesso especial aos
dados de seu objeto. Esses objetos são os objetos de programação orientada a objetos,
uma filosofia de programação bastante popular e poderosa.
Apresentaremos as classes em duas partes. Primeiro mostraremos como fornecer uma
definição para uma estrutura. Uma estrutura (do tipo de que trataremos aqui) pode ser pen-
sada como um objeto sem nenhuma função-membro.1 A propriedade importante das estruturas
é que os dados em uma estrutura podem ser uma coleção de dados de diversos tipos. Depois
que você aprender como são as estruturas, a definição de classes virá como uma extensão natural.
Você não precisa ter lido o Capítulo 5, sobre vetores, para ler o Capítulo 6 e a maior
parte dos Capítulos 7 e 8, que tratam de classes.

6.1 Estruturas
Eu não aceitaria participar de nenhum clube que me aceitasse como membro.
Groucho Marx, The Groucho Letters

Às vezes é útil ter uma coleção de valores de tipos diferentes e tratar a coleção como
um único item. Por exemplo, considere um certificado de depósito bancário (CDB). Um
CDB é uma conta bancária que não permite retiradas por um número especificado de meses.
Um CDB naturalmente possui três espécies de dados associados a ele: o saldo bancário, a taxa
de juros e o prazo, que é o número de meses até a data do vencimento. Os primeiros dois
itens podem ser representados por valores de tipo double, e o número de meses pode ser re-
presentado como um valor de tipo int. O Painel 6.1 mostra a definição de uma estrutura
chamada CDBContaV1 que pode ser usada para esse tipo de conta. (O V1 significa "versão 1".
Apresentaremos uma versão aperfeiçoada mais adiante neste mesmo capítulo.)

1. Uma estrutura, na realidade, pode ter funções-membros em C++, mas não é este o enfoque
que utilizaremos. Este detalhe é explicado mais adiante neste capítulo. Esta nota é apenas para que
os leitores que pensaram haver encontrado um erro saibam que estamos conscientes da
definição oficial de uma estrutura. A maioria dos leitores pode ignorar esta nota.
154 Estruturas e Classes

Painel 6.1 Definição de estrutura


1 //Programa para demonstrar o tipo de estrutura CDAccountV1.
2 #include <iostream>
3 using namespace std;

4 //Estrutura para um certificado de depósito bancário:


5 struct CDAccountV1
6 { Uma versão aperfeiçoada
7 double balance; dessa estrutura será dada
8 double interestRate; posteriormente neste capítulo.
9 int term;//meses até a data de vencimento
10 };

11 void getData (CDAccountV1& theAccount);


12 //Pós-condição: theAccount.balance, theAccount.interestRate e
13 //theAccount.term receberam valores que o usuário informou ao teclado.

14 int main( )
15 {
16 CDAccountV1 account;
17 getData(account);

18 double rateFraction, interest;


19 rateFraction = account.interestRate/100.0;
20 interest = account.balance*(rateFraction*(account.term/12.0));
21 account.balance = account.balance + interest;

22 cout.setf(ios::fixed);
23 cout.setf(ios::showpoint);
24 cout.precision(2);
25 cout << "Após o prazo "
26 << account.term << " meses,\n"
27 << "você terá um saldo de $"
28 << account.balance << endl;

29 return 0;
30 }
31 //Utiliza iostream:
32 void getData( CDAccountV1& theAccount)
33 {
34 cout << "Informe o seu saldo bancário: $";
35 cin >> theAccount.balance;
36 cout << "Informe a taxa de juros da sua conta: ";
37 cin >> theAccount.interestRate;
38 cout << "Informe o número de meses até a data de vencimento: ";
39 cin >> theAccount.term;
40 }

DIÁLOGO PROGRAMA-USUÁRIO
Informe o seu saldo bancário: $100.00
Informe a taxa de juros da sua conta: 10.0
Informe o número de meses até a data de vencimento: 6
Após o prazo de 6 meses,
você terá um saldo de $105.00
Estruturas 155

■ TIPOS DE ESTRUTURAS
A definição de estrutura no Painel 6.1 é a seguinte:
struct CDBContaV1
{
double saldo;
double taxaDeJuro;
int prazo;//meses do prazo até a data de vencimento
};

A palavra-chave struct anuncia que essa é uma definição de tipo estrutura. O identificador CDBContaV1 é o
nome do tipo estrutura. O identificador de estrutura pode ser qualquer identificador que não seja uma palavra-
chave. Embora isso não seja exigido pela linguagem C++, os identificadores de estrutura normalmente são escritos
com uma letra maiúscula no início. Os identificadores declarados entre chaves, { }, são chamados membros de es-
trutura. Como ilustrado neste exemplo, uma definição de tipo estrutura termina com uma chave, }, e um ponto-
e-vírgula.
Uma definição de estrutura normalmente é colocada fora de qualquer definição de função (da mesma forma que
declarações de constantes globalmente definidas são colocadas fora de todas as definições de função). O tipo estrutura é,
então, uma definição global disponível para todo o código que se segue à definição de estrutura.
Uma vez que uma definição de tipo estrutura tenha sido dada, o tipo estrutura pode ser usado exatamente
como os tipos predefinidos int, char e assim por diante. Observe que, no Painel 6.1, o tipo estrutura CDBContaV1
é usado para declarar uma variável na função main e como nome do tipo de parâmetro para a função getDados.
Uma variável-estrutura pode guardar valores exatamente como qualquer outra variável. Um valor de estrutura
é uma coleção de valores menores chamada de valores-membros. Há um valor-membro para cada nome de mem-
bro declarado na definição de estrutura. Por exemplo, um valor do tipo CDBContaV1 é uma coleção de três valores-
membros, dois de tipo double e um de tipo int. Os valores-membros que juntos constituem o valor de estrutura
são armazenados em variáveis-membros, de que trataremos a seguir.
Cada tipo de estrutura especifica uma lista de membros de estrutura. No Painel 6.1, a estrutura CDBContaV1
possui três membros de estrutura: saldo, taxaDeJuro e prazo. Cada um desses membros de estrutura pode ser
usado para escolher uma variável menor que é parte da variável-estrutura maior. Essas variáveis menores são cha-
madas variáveis-membros. Variáveis-membros são especificadas fornecendo o nome da variável-estrutura seguido
por um ponto e, depois, o nome do membro. Por exemplo, se conta é uma variável-estrutura do tipo CDBContaV1
(como declarado no Painel 6.1), a variável-estrutura conta possui as três seguintes variáveis-membros:
conta.saldo
conta.taxaDeJuro
conta.prazo

As primeiras duas variáveis-membros são de tipo double, e a última, de tipo int. Como ilustrado no Painel
6.1, essas variáveis-membros podem ser usadas exatamente como quaisquer outras variáveis daqueles tipos. Por
exemplo, a linha seguinte do programa no Painel 6.1 acrescentará o valor contido na variável-membro conta.sal-
do e o valor contido na variável comum juros e colocará o resultado na variável-membro conta.saldo:
conta.saldo = conta.saldo + juros;

Dois ou mais tipos estrutura podem usar os mesmos membros de estrutura. Por exemplo, é perfeitamente legal
ter as duas definições de tipo seguintes no mesmo programa:
struct EstoqueDeFertilizantes
{
double quantidade;
double conteudoNitrogenio;
};

e
struct RendimentoDaColheita
{
156 Estruturas e Classes

int quantidade;
double tamanho;
};

OPERADOR PONTO
O operador ponto é utilizado para especificar uma variável-membro de uma variável-estrutura.

Sintaxe
operador ponto
Nome_Variavel_Estrutura.Nome_Variavel_Membro
EXEMPLOS
struct NotaAluno
{
int NumeroAluno;
char nota;
};
int main ( )
{
NotaAluno suaNota;
suaNota.NumeroAluno = 2001;
suaNota.nota = ’A’;
Alguns escritores chamam o operador ponto de operador de acesso aos membros da estrutura, mas não utilizaremos esse termo.

A coincidência de nomes não causará problemas. Por exemplo, se você declarar as duas variáveis-estruturas
seguintes:
EstoqueDeFertilizantes superCrescimento;
RendimentoDaColheita bananas;

então a quantidade de fertilizante superCrescimento é armazenada na variável-membro superCrescimento.quan-


tidade, e a quantidade de bananas produzida é armazenada na variável-membro bananas.quantidade. O opera-
dor ponto e a variável-estrutura especificam a que quantidade estamos nos referindo em cada exemplo.
Um valor de estrutura pode ser visto como uma coleção de valores-membros. Um valor de estrutura também
pode ser visto como um único (complexo) valor (que, por acaso, é constituído por valores-membros). Como um
valor de estrutura pode ser visto como um valor único, valores de estrutura e variáveis-estruturas podem ser usadas
da mesma forma que valores e variáveis simples dos tipos predefinidos como int. Em particular, pode-se atribuir
valores de estrutura utilizando um sinal de igual. Por exemplo, se bananas e laranjas são variáveis-estruturas do
tipo RendimentoDaColheita, já definido, então a seguinte linha é perfeitamente legal:
bananas = laranjas;

A declaração de atribuição acima é equivalente a


bananas.quantidade = laranjas.quantidade;
bananas.tamanho = laranjas.tamanho;

TIPOS ESTRUTURA SIMPLES


Define-se um tipo estrutura da forma mostrada a seguir. Identificador_Estrutura é o nome do tipo estrutura.
SINTAXE
struct Identificador_Estrutura
{
Tipo_1 Variavel_Membro_Nome_1;
Tipo_2 Variavel_Membro_Nome_2;
.
.
.
Estruturas 157

(continuação)

Tipo_Final Variavel_Membro_Nome_Final;
}; Não se esqueça deste ponto-e-vírgula.
EXEMPLO
struct Automovel
{
int ano;
int portas;
double cavalosDoMotor;
char modelo;
};
Não utilizaremos este recurso, mas você pode combinar membros de estrutura de mesmo tipo em uma lista única separada por
vírgulas. Por exemplo, a seguinte definição é equivalente à definição de estrutura acima:
struct Automovel
{
int ano, portas;
double cavalosDoMotor;
char modelo;
};
Variáveis de um tipo estrutura podem ser declaradas da mesma forma que variáveis de outros tipo. Por exemplo:
Automovel meuCarro, seuCarro;
As variáveis-membros são especificadas por meio do operador ponto. Por exemplo: meuCarro.ano, meuCarro.portas, meuCar-
ro.cavalosDoMotor e meuCarro.modelo.

ESQUECENDO UM PONTO-E-VÍRGULA EM UMA DEFINIÇÃO DE ESTRUTURA


Quando você acrescenta a chave final, }, a uma definição de estrutura, parece que a definição de estrutura
terminou, mas isso não é verdade. Você precisa colocar também um ponto-e-vírgula depois dessa chave fi-
nal. Há um motivo para isso, embora esteja ligado a um recurso que não teremos a oportunidade de utili-
zar. Uma definição de estrutura é mais do que uma definição. Pode também ser usada para declarar
variáveis-estruturas. Você pode listar nomes de variáveis-estruturas entre a chave final e o ponto-e-vírgula.
Por exemplo, a definição seguinte é de uma estrutura chamada DadosClimaticos e declara duas variáveis-es-
truturas, dadosPonto1 e dadosPonto2, ambas de tipo DadosClimaticos:
struct DadosClimaticos
{
double temperatura;
double velocidadeDoVento;
} dadosPonto1, dadosPonto2;

■ ESTRUTURAS COMO ARGUMENTOS DE FUNÇÃO


Uma função pode ter parâmetros chamados por valor de um tipo estrutura ou parâmetros chamados por refe-
rência de um tipo estrutura, ou ambos. O programa no Painel 6.1, por exemplo, inclui uma função chamada
getDados que possui um parâmetro chamado por referência com o tipo estrutura CDBContaV1.
Um tipo estrutura também pode ser o tipo para o valor retornado por uma função. Por exemplo, a definição
seguinte é de uma função que toma um argumento de tipo CDBContaV1 e retorna uma estrutura diferente de tipo
CDBContaV1. A estrutura retornada terá o mesmo saldo e prazo que o argumento, mas pagará o dobro da taxa de
juros que o argumento paga.
CDBContaV1 juroDuplo(CDBContaV1 velhaConta)
{
CDBContaV1 temp;
temp = velhaConta;
temp.taxaDeJuro = 2*velhaConta.taxaDeJuro;
return temp;
}
158 Estruturas e Classes

Observe a variável local temp de tipo CDBContaV1; temp é utilizada para construir um valor de estrutura com-
pleto da espécie desejada, que é então apresentada como saída pela função. Se minhaConta é uma variável de tipo
CDBContaV1 que recebeu valores para suas variáveis-membros, as seguintes linhas fornecerão a suaConta valores
para uma conta com o dobro da taxa de juros de minhaConta:
CDBContaV1 suaConta;
suaConta = juroDuplo(minhaConta);

IDica UTILIZE ESTRUTURAS HIERÁRQUICAS


Às vezes é interessante ter estruturas cujos membros são eles mesmos estruturas menores. Por exemplo,
um tipo estrutura chamado infoPessoal, que pode ser usado para armazenar altura, peso e data de nasci-
mento de uma pessoa, pode ser definido da seguinte forma:
struct Data
{
int dia;
int mes;
int ano;
};
struct infoPessoa;
{
double altura; //em polegadas
int peso; // em libras
Data aniversario;
};
Uma variável-estrutura de tipo infoPessoal é declarada da forma usual:
infoPessoal pessoa1;
Se a variável-estrutura pessoa1 teve seu valor fixado para registrar a data de nascimento de uma pessoa, o
ano em que a pessoa nasceu pode ser exibido na tela da seguinte forma:
cout << pessoa1.aniversario.ano;
O modo de ler tais expressões é da esquerda para a direita, e com muito cuidado. Começando da esquerda,
pessoa1 é uma variável estrutura de tipo infoPessoal. Para obter a variável-membro com o nome aniversa-
rio, utilize o operador ponto, da seguinte forma:
pessoa1.aniversario
A variável-membro é ela própria uma variável-estrutura de tipo Data. Assim, essa variável-membro possui
variáveis-membros. Uma variável-membro da variável-estrutura pessoa1.aniversario é obtida acrescentan-
do-se um ponto e o nome da variável-membro, como ano, que produz a expressão pessoa1.aniversario
exibida acima.
No Painel 6.2, reescrevemos a classe para um certificado de depósito bancário do Painel 6.1. Esta nova ver-
são possui uma variável-membro do tipo estrutura Data que abriga a data de vencimento. Também substi-
tuímos a variável-membro única saldo por duas novas variáveis-membros que fornecem o saldo inicial e o
saldo na data de vencimento.

Painel 6.2 Estrutura com um membro estrutura (parte 1 de 3)


1 //Programa para demonstrar o tipo de estrutura CDAccount.
2 #include <iostream>
3 using namespace std;

4 struct Date
Esta é a versão aperfeiçoada da estrutura
5 {
CDAccountV1 definida no Painel 6.1.
6 int month;
7 int day;
8 int year;
9 };

10 //Estrutura aperfeiçoada para certificado de depósito bancário:


11 struct CDAccount
12 {
Estruturas 159

Painel 6.2 Estrutura com um membro estrutura (parte 2 de 3)


13 double initialBalance;
14 double interestRate;
15 int term;//meses até a data de vencimento
16 Date maturity; //data de vencimento do CDB
17 double balanceAtMaturity;
18 };
19 void getCDData(CDAccount& theAccount);
20 //Pós-condição: theAccount.initialBalance, theAccount.interestRate,
21 //theAccount.term e theAccount.maturity receberam valores
22 //que o usuário informou ao teclado.
23
24 void getDate(Date& theDate);
25 //Pós-condição: theDate.month, theDate.day e theDate.year
26 //receberam valores que o usuário informou ao teclado.
27 int main( )
28 {
29 CDAccount account;
30 cout << "Informe os dados da conta no dia em que a conta foi aberta:\n";
31 getCDData(account);
32 double rateFraction, interest;
33 rateFraction = account.interestRate/100.0;
34 interest = account.initialBalance*(rateFraction*(account.term/12.0));
35 account.balanceAtMaturity = account.initialBalance + interest;
36 cout.setf(ios::fixed);
37 cout.setf(ios::showpoint);
38 cout.precision(2);
39 cout << "Na data de vencimento do CDB "
40 << account.maturity.month << "-" << account.maturity.day
41 << "-" << account.maturity.year << endl
42 << "o saldo era de $"
43 << account.balanceAtMaturity << endl;
44 return 0;
45 }
46 //utiliza iostream:
47 void getCDData(CDAccount& theAccount)
48 {
49 cout << "Informe o saldo inicial da conta: $";
50 cin >> theAccount.initialBalance;
51 cout << "Informe a taxa de juros da conta: ";
52 cin >> theAccount.interestRate;
53 cout << "Informe o número de meses até a data de vencimento: ";
54 cin >> theAccount.term;
55 cout << "Informe a data de vencimento:\n";
56 getDate(theAccount.maturity);
57 }
58 //utiliza iostream:
59 void getDate(Date& theDate)
60 {
61 cout << "Informe o mês: ";
62 cin >> theDate.month;
63 cout << "Informe o dia: ";
64 cin >> theDate.day;
65 cout << "Informe o ano: ";
66 cin >> theDate.year;
67 }
160 Estruturas e Classes

Painel 6.2 Estrutura com um membro estrutura (parte 3 de 3)


DIÁLOGO PROGRAMA-USUÁRIO
Informe os dados da conta no dia em que a conta foi aberta:
Informe o saldo inicial da conta: $100.00
Informe a taxa de juros da conta: 10.0
Informe o número de meses até a data de vencimento: 6
Informe a data de vencimento:
Informe o dia: 14
Informe o mês: 2
Informe o ano: 1899
Quando chegar a data de vencimento do CDB,
o saldo será $105.00

■ INICIALIZANDO ESTRUTURAS
Você pode inicializar uma estrutura no momento em que ela e é declarada. Para dar um valor a uma variável-
estrutura, coloque depois um sinal de igual e uma lista dos valores-membros entre chaves. Por exemplo, a seguinte
definição de uma estrutura para uma data foi apresentada na subseção anterior:
struct Data
{
int dia;
int mes;
int ano;
};

Assim que o tipo Data estiver definido, você pode declarar e inicializar uma variável-estrutura chamada data-
Pagamento da seguinte forma:
Data dataPagamento = {31, 12, 2003};

Os valores inicializantes devem ser dados na ordem que corresponda à ordem das variáveis-membros na defini-
ção do tipo estrutura. Neste exemplo, dataPagamento.dia recebe o primeiro valor inicializante, 31, dataPagamen-
to.mes recebe o segundo valor, 12, e dataPagamento.ano recebe o terceiro valor, 2003.
Se houver mais valores inicializadores do que membros struct, ocorre um erro. Se houver menos valores inicializa-
dores do que membros struct, os valores fornecidos são usados para inicializar os membros dados, em ordem. Cada
membro dado sem um inicializador é inicializado com um valor zero de um tipo apropriado para a variável.

1 Exercícios de Autoteste 1

1. Dada a seguinte estrutura e declaração de variável estrutura,


struct CDBContaV2
{
double saldo;
double taxaDeJuro;
int prazo;
char inicial1;
char inicial2;
};
CDBContaV2 conta;
qual é o tipo de cada uma das seguintes declarações? Assinale todas as que não estiverem corretas.
a. conta.saldo
b. conta.taxaDeJuro
c. CDBContaV1.prazo
d. conta.inicial2
e. conta
Estruturas 161

Exercícios de Autoteste (continuação)


2. Considere as seguintes definições de tipo:
struct TipoDeSapato
{
char estilo;
double preco;
};
Dadas as definições de tipo estrutura acima, qual deve ser a saída produzida pelo seguinte código?
TipoDeSapato sapato1, sapato2;
sapato1.estilo = ’A’;
sapato1.preco = 9.99;
cout << sapato1.estilo << " $" << sapato1.preco << endl;
sapato2 = sapato1;

sapato2.preco = sapato2.preco/9;
cout << sapato2.estilo << " $" << sapato2.preco << endl;
3. Qual é o erro na seguinte definição de estrutura?
struct Coisa
{
int b;
int c;
}
int main( )
{
Coisa x;
//outro código
}
4. Dada a seguinte definição struct,
struct A
{
int membro b;
int membro c;
};
declare que x tem esse tipo estrutura. Inicialize os membros de x, membro b e membro c, com os valo-
res 1 e 2, respectivamente.
5. Aqui está uma inicialização de um tipo estrutura. Informe o que acontece com cada inicialização. Ob-
serve quaisquer problemas com essas inicializações.
struct Data
{
int dia;
int mes;
int ano;
};
a. Data dataPagamento = {21, 12};
b. Data dataPagamento = {21, 12, 1995};
c. Data dataPagamento = {21, 12, 19, 95};
6. Escreva uma definição para um tipo estrutura para registros formados por salário, férias acumuladas (um
número inteiro de dias) e status (que pode ser horista ou assalariado). Represente o status como um
dos dois valores char ’H’ ou ’A’. Chame o tipo de RegistroDoEmpregado.
7. Dê uma definição de função correspondente à seguinte declaração de função. (O tipo TipoDeSapato foi
dado no Exercício de Autoteste 2.)
void leRegistroDeSapato(TipoDeSapato& novoSapato);
//Preenche novoSapato com valores lidos a partir do teclado.
8. Dê uma definição de função correspondente à seguinte declaração de função. (O tipo TipoDeSapato foi
dado no Exercício de Autoteste 2.)
TipoDeSapato desconto(TipoDeSapato velhoRegistro);
//Retorna uma estrutura que é a mesma de seu argumento,
//mas com o preço reduzido em 10%.
162 Estruturas e Classes

6.2 1 Classes
Todos nós sabemos — o Times sabe —, mas fingimos que não sabemos.
Virginia Woolf, Monday or Tuesday

Uma classe é, basicamente, uma estrutura com funções-membros e também dados-membros. As classes são
centrais para a metodologia de programação conhecida como programação orientada a objetos.

■ DEFININDO CLASSES E FUNÇÕES-MEMBROS


Uma classe é um tipo similar a um tipo estrutura, mas um tipo classe normalmente possui funções-membros
além de variáveis-membros. Um exemplo bastante simples, mas ilustrativo, de uma classe chamada DiaDoAno é
dado no Painel 6.3. Esta classe possui uma função-membro chamada saida, além de duas variáveis-membros dia
e mes. O termo public: é um especificador de acesso. Quer dizer apenas que não há restrições sobre os membros
que se seguem. Falaremos sobre public: e suas alternativas depois de ver este exemplo simples. O tipo DiaDoAno
definido no Painel 6.3 é uma definição de classe para objetos cujos valores são datas, como 7 de setembro ou 15
de novembro.

Painel 6.3 Classe com uma função-membro (parte 1 de 2)


1 //Programa para demonstrar um exemplo muito simples da classe.
2 //Uma versão melhor da classe DayOfYear será dada no Painel 6.4.
3 #include <iostream>
4 using namespace std;

5 class DayOfYear Normalmente, as variáveis-membros são private


6 { e não public, como neste exemplo. Isso será
7 public: discutido posteriormente neste capítulo.
8 void output( );
9 int month;
10 int day; Declaração de função-membro
11 };

12 int main( )
13 {
14 DayOfYear today, birthday;
15 cout << "Informe a data de hoje:\n";
16 cout << "Informe o dia do mês: ";
17 cin >> today.month;
18 cout << "Informe o mês com um número: ";
19 cin >> today.day;
20 cout << "Informe o dia do seu aniversário:\n";
21 cout << "Informe o mês com um número: ";
22 cin >> birthday.month;
23 cout << "Informe o dia do mês: ";
24 cin >> birthday.day;

25 cout << "Hoje é ";


26 today.output( );
27 cout << endl;
28 cout << "O seu aniversário é"; Chama a função-membro output
29 birthday.output( );
30 cout << endl;

31 if (today.month == birthday.month && today.day == birthday.day)


32 cout << "Feliz Aniversário!\n";
33 else
34 cout << "Feliz Dia de Não-Aniversário!\n";
Classes 163

Painel 6.3 Classe com uma função-membro (parte 2 de 2)


35 return 0;
36 }
37 //Utiliza iostream:
38 void DayOfYear::output( )
39 {
40 switch (month)
41 {
42 case 1:
43 cout << "Janeiro "; break;
44 case 2:
45 cout << "Fevereiro "; break;
46 case 3:
47 cout << "Março "; break;
48 case 4:
49 cout << "Abril "; break; Definição da função-membro
50 case 5:
51 cout << "Maio "; break;
52 case 6:
53 cout << "Junho "; break;
54 case 7:
55 cout << "Julho "; break;
56 case 8:
57 cout << "Agosto "; break;
58 case 9:
59 cout << "Setembro "; break;
60 case 10:
61 cout << "Outubro "; break;
62 case 11:
63 cout << "Novembro "; break;
64 case 12:
65 cout << "Dezembro "; break;
66 default:
67 cout << "Erro em DayOfYear::output. Entre em contato com o fornecedor do software.";
68 }
69
70 cout << day;
71 }

DIÁLOGO PROGRAMA-USUÁRIO
Informe a data de hoje:
Informe o dia do mês: 15
Informe o mês com um número: 10
Informe o dia do seu aniversário:
Informe o dia do mês: 21
Informe o mês com um número: 2
Hoje é 15 outubro
O seu aniversário é em 21 fevereiro
Feliz Dia de Não-Aniversário!

O valor de uma variável de um tipo classe é chamado de objeto (quando se fala de maneira imprecisa, uma
variável de um tipo classe também costuma ser chamada de objeto). Um objeto possui tanto membros dados como
membros funções. Quando se programa com classes, um programa é encarado como uma coleção de objetos inte-
ragindo. Os objetos podem interagir porque são capazes de ações, ou seja, invocações de funções-membros. As va-
riáveis de um tipo classe guardam objetos como valores. As variáveis de um tipo classe são declaradas da mesma
forma que as variáveis de tipos predefinidos e que as variáveis-estruturas.
164 Estruturas e Classes

Por enquanto, ignore a palavra public: exibida no Painel 6.3. O resto da definição da classe DiaDoAno é mui-
to semelhante a uma definição de estrutura, a não ser pelo fato de que utiliza a palavra-chave class em vez de
struct e de que lista a função-membro saida (assim como as variáveis-membros dia e mes). Observe que a fun-
ção-membro saida é listada dando-se sua declaração (protótipo). Uma definição de classe normalmente contém
apenas a declaração para suas funções-membros. As definições para as funções-membros normalmente são dadas
em outro lugar. Em uma definição de classe em C++, você pode mesclar a ordem das variáveis-membros e fun-
ções-membros da forma que desejar, mas o estilo que nós seguimos tende a listar as funções-membros antes das
variáveis-membros.
Variáveis-membros para um objeto de um tipo classe são especificadas por meio do operador ponto, da mes-
ma forma que o operador ponto é utilizado para especificar variáveis-membros de uma estrutura. Por exemplo, se
hoje é uma variável do tipo classe DiaDoAno definida no Painel 6.3, então hoje.dia e hoje.mes são as duas variá-
veis-membros do objeto hoje.
Funções-membros para classes que você define são invocadas por meio do operador ponto de uma forma simi-
lar àquela pela qual se especifica uma variável-membro. Por exemplo, o programa no Painel 6.3 declara dois obje-
tos de tipo DiaDoAno da seguinte forma:
DiaDoAno hoje, aniversario;

A função-membro saida é chamada com o objeto hoje da seguinte forma:


hoje.saida( );

e a função-membro saida é chamada com o objeto aniversario assim:


aniversario.saida( );

Quando uma função-membro é definida, a definição deve incluir o nome da classe, porque pode haver duas
ou mais classes que possuam funções-membros com o mesmo nome. No Painel 6.3, há apenas uma definição de
classe, mas em outras situações pode-se ter muitas definições de classe e mais de uma classe pode ter funções-
membros com o mesmo nome. A definição para a função-membro saida da classe DiaDoAno é mostrada na parte
2 do Painel 6.3. A definição é semelhante a uma definição de função comum, a não ser pelo fato de que é preciso
especificar o nome da classe no cabeçalho da definição de função.
O cabeçalho da definição de função para a função-membro saida é assim:
void DiaDoAno::saida( )

O operador :: é chamado operador de resolução de escopo e serve a um propósito semelhante ao do operador


ponto. Tanto o operador ponto quanto o operador de resolução de escopo são utilizados para dizer de que uma fun-
ção-membro é membro. Entretanto, o operador de resolução de escopo :: é utilizado com um nome de classe, en-
quanto o operador ponto é utilizado com objetos (ou seja, com variáveis de classe). O operador de resolução de
escopo geralmente é chamado de qualificador de tipo, porque ele especializa ("qualifica") o nome da função como
um tipo particular.
Veja a definição da função-membro DiaDoAno:: saida fornecida no Painel 6.3. Observe que, na definição de
função de DiaDoAno::saida, nós utilizamos os membros de estrutura dia e mes sozinhos, sem primeiro fornecer o
objeto e o operador ponto. Isso não é tão estranho quanto possa parecer de início. A esta altura vamos apenas de-
finir a função-membro saida. Essa definição de saida aplicar-se-á a todos os objetos de tipo DiaDoAno, mas a essa
altura não sabemos os nomes dos objetos de tipo DiaDoAno que utilizaremos, então não podemos fornecer seus
nomes. Quando a função-membro é chamada, como em
hoje.saida( );

todos os nomes dos membros na definição de função são especializados com o nome do objeto que faz a chama-
da. Assim, a função acima é equivalente a:
{
switch (hoje.mes)
{
case 1:
Classes 165

.
.
.
}
cout << hoje.dia;
}

DEFINIÇÃO DE FUNÇÃO-MEMBRO
Uma função-membro é definida de maneira similar a qualquer outra função, a não ser pelo fato de que Nome_Classe e o opera-
dor de resolução de escopo, ::, são fornecidos no cabeçalho da função.
SINTAXE
Tipo_Retornado Nome_Classe::Nome_Funcao(Lista_Parametros)
{
Comandos_Corpo_Função
}
EXEMPLO
Veja o Painel 6.3. Observe que as variáveis-membros (dia e mes) não são precedidas por um nome de objeto e ponto quando
ocorrem em uma definição de função-membro.

Na definição de função para uma função-membro, podem-se usar os nomes de todos os membros dessa classe
(tanto os membros dados como os membros funções) sem utilizar o operador ponto.

OPERADOR PONTO E OPERADOR DE RESOLUÇÃO DE ESCOPO


Tanto o operador ponto quanto o operador de resolução de escopo são usados com membros de estrutura para especificar de
que eles são membros. Por exemplo, suponha que você tenha declarado uma classe chamada DiaDoAno e você declare um objeto
chamado hoje da seguinte forma:
DiaDoAno hoje;
Emprega-se o operador ponto para especificar um membro do objeto hoje. Por exemplo, saida é uma função-membro da classe
DiaDoAno (definida no Painel 6.3) e a seguinte chamada de função produzirá como saída os valores dados armazenados no ob-
jeto hoje.
hoje.saida( );
Emprega-se o operador de resolução de escopo, ::, para especificar o nome da classe quando se fornece a definição de função
para uma função-membro. Por exemplo, o cabeçalho da definição de função para a função membro saida seria assim:
void DiaDoAno::saida( )
Lembre-se de que o operador de resolução de escopo, ::, é utilizado com um nome de classe, enquanto o operador ponto é uti-
lizado com um objeto daquela classe.

UMA CLASSE É UM TIPO COMPLETO


Uma classe é um tipo exatamente como os tipos int e double. Pode-se ter variáveis de um tipo classe, pode-se ter parâmetros
de um tipo classe, uma função pode retornar um valor de um tipo classe e, de forma geral, pode-se usar um tipo classe como
qualquer outro tipo.

1 Exercícios de Autoteste 1

9. A seguir, temos uma definição da classe DiaDoAno do Painel 6.3 de modo que, agora, há uma função-
membro adicional chamada entrada. Escreva uma definição apropriada para a função-membro entrada.
class DiaDoAno
{
public:
void entrada( );
void saida( );
int dia;
int mes;
};
166 Estruturas e Classes

Exercícios de Autoteste (continuação)


10. Dada a seguinte definição de classe, escreva uma definição apropriada para a função-membro set.
class Temperatura
{
public:
void set(double novosGraus, char novaEscala);
//Fixa as variáveis-membros para os valores dados como
//argumentos.
double graus;
char escala; //’F’ para Fahrenheit ou ’C’ para Celsius.
};
11. Com cuidado, estabeleça a distinção entre o significado e o uso do operador ponto e do operador de re-
solução de escopo, ::.

■ ENCAPSULAMENTO
Um tipo de dados, como o tipo int, possui certos valores especificados, como 0, 1, -1, 2, e assim por diante.
Tendemos a pensar no tipo de dados como sendo esses valores, mas as operações sobre esses valores são tão im-
portantes quanto os valores. Sem as operações, não se pode fazer nada de interessante com esses valores. As opera-
ções para o tipo int são +, -, *, /, % e mais alguns poucos operadores e funções de biblioteca predefinida. Não se
deve pensar no tipo de dados como sendo apenas uma coleção de valores. Um tipo de dados consiste em uma co-
leção de valores associada a um conjunto de operações básicas definidas sobre esses valores. Um tipo de dados é
chamado de um tipo de dados abstrato (ADT) se os programadores que usam o tipo não têm acesso aos detalhes
de como valores e operações são implementados. Os tipos predefinidos, como int, são tipos dados abstratos
(ADTs). Não se sabe como as operações, como as de + e as de *, são implementadas para o tipo int. Mesmo que
se saiba, não se pode utilizar essa informação em nenhum programa em C++. As classes, que são tipos definidos
pelo programador, também devem ser ADTs, ou seja, os detalhes de como as "operações" são implementadas de-
vem ser ocultos de qualquer programador que os utilize ou, pelo menos, irrelevantes para ele. As operações de
uma classe são as funções-membros (públicas) da classe. Um programador que utiliza uma classe não deve precisar
ver as definições das funções-membros. As declarações de funções-membros, dadas na definição de classe, e uns
poucos comentários devem ser tudo de que o programador precisa para utilizar a classe.
Um programador que utiliza uma classe também não deve precisar saber como os dados da classe são imple-
mentados. A implementação dos dados deve ser oculta como a implementação das funções-membros. Na verdade,
é quase impossível distinguir entre ocultar a implementação das funções-membros e a implementação dos dados.
Para um programador, a classe DiaDoAno (Painel 6.3) possui datas como dados, não números. O programador não
deve saber ou se preocupar se o mês de março é implementado como o valor int 3, a string "Março" ou de algu-
ma outra forma.
A definição de uma classe de modo que a implementação das funções-membros e dos dados nos objetos não
seja conhecida do programador que utiliza a classe, ou, pelo menos, seja irrelevante para ele, é conhecida por di-
versos termos. Os termos utilizados mais comuns são ocultação de informação, abstração de dados ou encapsula-
mento, termos que significam que os detalhes da implementação de uma classe são ocultados do programador que
utiliza a classe. Esse princípio é um dos maiores dogmas da programação orientada a objetos (OOP). Quando se
fala de OOP, o termo usado com mais freqüência é encapsulamento. Uma das formas de se aplicar esse princípio
às suas definições de classe é tornar todas as variáveis-membros privadas, assunto de que trataremos na próxima
subseção.

■ MEMBROS PÚBLICOS E PRIVADOS


Veja novamente a definição do tipo DiaDoAno, dada no Painel 6.3. A fim de utilizar essa classe, você precisa
saber que existem duas variáveis-membros de tipo int que se chamam dia e mes. Isso viola o princípio do encap-
sulamento (ocultação da informação) que abordamos na subseção anterior. O Painel 6.4 é uma versão reescrita da
classe DiaDoAno que se conforma melhor a esse princípio do encapsulamento.
Classes 167

Observe as palavras private: e public: no Painel 6.4. Diz-se que todos os itens que seguem a palavra priva-
te: (nesse caso, as variáveis membros dia e mes) são privados, o que significa que eles não podem ser referencia-
dos por nomes em nenhum lugar, exceto dentro das definições das funções-membros da classe DiaDoAno. Por
exemplo, com essa definição alterada da classe DiaDoAno, as duas atribuições seguintes e os outros códigos indica-
dos não são mais permitidos na função main do programa nem em qualquer outra definição de função, exceto as
funções-membros da classe DiaDoAno.
DiaDoAno hoje; //Esta linha está OK.
hoje.dia = 25; //ILEGAL
hoje.mes = 12; //ILEGAL
cout << hoje.dia; // ILEGAL
cout << hoje.mes; //ILEGAL
if (hoje.mes == 1) //ILEGAL
cout << "Janeiro";

Assim que uma variável-membro se torna uma variável-membro privada, não há mais como alterar seu valor
(ou fazer referência à variável-membro de qualquer forma) a não ser utilizando uma das funções-membros. Isso
quer dizer que o compilador imporá a ocultação da implementação dos dados para a classe DiaDoAno. Se você olhar

Painel 6.4 Classe com membros privados (parte 1 de 3)


1 #include <iostream>
2 #include <cstdlib> Esta é uma versão aperfeiçoada
3 using namespace std;
da classe DayOfYear que
fornecemos no Painel 6.3.
4 class DayOfYear
5 {
6 public:
7 void input( );
8 void output( );
9 void set(int newMonth, int newDay);
10 //Pré-condição: newMonth e newDay formam uma data possível.

11 void set(int newMonth);


12 //Pré-condição: 1 <= newMonth <= 12
13 //Pós-condição: A data é fixada para o primeiro dia do mês dado.

14 int getMonthNumber( ); //Retorna 1 para janeiro, 2 para fevereiro, etc.


15 int getDay( );
16 private:
17 int month;
18 int day; Membros privados
19 };

20 int main( )
21 {
22 DayOfYear today, bachBirthday;
23 cout << "Informe a data de hoje:\n";
24 today.input( );
25 cout << "A data de hoje é";
26 today.output( );
27 cout << endl;

28 bachBirthday.set(3, 21);
29 cout << "O aniversário de J. S. é";
30 bachBirthday.output( );
31 cout << endl;
32 if ( today.getMonthNumber( ) == bachBirthday.getMonthNumber( ) &&
33 today.getDay( ) == bachBirthday.getDay( ) )
34 cout << "Feliz Aniversário, Johann Sebastian!\n";
168 Estruturas e Classes

Painel 6.4 Classe com membros privados (parte 2 de 3)


35 else
36 cout << "Feliz Aniversário, Johann Sebastian!\n";
37
Observe que o nome da função set está sobrecarregado.
38 return 0;
Pode-se sobrecarregar uma função-membro exatamente como
39 }
se sobrecarrega qualquer outra função.
40 //Utiliza iostream e cstdlib:
41 void DayOfYear::set(int newMonth, int newDay)
42 {
43 if ((newMonth >= 1) && (newMonth <= 12))
44 month = newMonth;
45 else
46 { Funções mutantes
47 cout << "Valor ilegal para o mês! Programa abortado.\n";
48 exit(1);
49 }
50 if ((newDay >= 1) && (newDay <= 31))
51 day = newDay;
52 else
53 {
54 cout << "Valor ilegal para o dia! Programa abortado.\n";
55 exit(1);
56 }
57 }

58 //Utiliza iostream e cstdlib:


59 void DayOfYear::set(int newMonth)
60 {
61 if ((newMonth >= 1) && (newMonth <= 12))
62 month = newMonth;
63 else
64 {
65 cout << "Valor ilegal para o mês! Programa abortado.\n";
66 exit(1);
67 }
68 day = 1;
69 }
70
71 int DayOfYear::getMonthNumber( ) Funções de acesso
72 {
73 return month;
74 }

75 int DayOfYear::getDay( )
76 {
77 return day;
78 }

79 //Utiliza iostream e cstdlib:


80 void DayOfYear::input( )
81 {
82 cout << "Informe o mês com um número: ";
83 cin >> month; Os membros privados podem ser
84 cout << "Informe o dia do mês: "; utilizados em definições de funções-
85 cin >> day; membros (mas não em outros lugares).
86 if ((month < 1) || (month > 12) || (day < 1) || (day > 31))
87 {
88 cout << "Data ilegal! Programa abortado.\n";
Classes 169

Painel 6.4 Classe com membros privados (parte 3 de 3)


89 exit(1);
90 }
91 }

92 void DayOfYear::output( )
93 <O resto da definição de DayOfYear::output é dado no Painel 6.3.>

DIÁLOGO PROGRAMA-USUÁRIO
Informe a data de hoje:
Informe o dia do mês: 21
Informe o mês com um número: 3
A data de hoje é 21 de março
O aniversário de J. S. Bach é em 21 de março
Feliz Aniversário, Johann Sebastian!

com cuidado para o programa no Painel 6.4, verá que o único lugar em que os nomes de variáveis-membros dia
e mes são usados é na definição das funções-membros. Não há referência a hoje.dia, hoje.mes, bachAniversa-
rio.dia ou bachAniversario.mes fora das definições de funções-membros.
Todos os itens que seguem a palavra public: (nesse caso as funções-membros) são chamados de públicos, o
que significa que podem ser referenciados por nome em qualquer lugar. Não há restrições sobre o uso de mem-
bros públicos.
Quaisquer variáveis-membros podem ser públicas ou privadas. Quaisquer funções-membros podem ser públicas
ou privadas. Entretanto, a prática normal da boa programação requer que todas as variáveis-membros sejam priva-
das e que a maioria das funções-membros sejam públicas.
Pode-se ter qualquer número de ocorrências dos especificadores de acesso public e private em uma definição
de classe. Cada vez que você inserir a legenda
public:

a lista de membros muda de privada para pública. Cada vez que você inserir a legenda
private:

a lista de membros volta a ser privada. Você não precisa ter apenas um grupo de membros público e um privado.
Entretanto, é comum ter apenas uma seção pública e uma seção privada.
Não existe um entendimento universal sobre se são os membros públicos ou os privados que devem ser lista-
dos primeiro. A maioria parece preferir listar os membros públicos primeiro. Isso permite uma fácil visualização
das porções que os programadores que utilizam a classe utilizam realmente. Você pode fazer sua própria opção
quanto ao que deseja colocar primeiro, mas os exemplos no livro seguem a opinião da maioria e listam os mem-
bros públicos antes dos privados.
Em certo sentido, o C++ parece favorecer a colocação dos membros privados primeiro. Se o primeiro grupo
de membros não possuir o especificador public: nem private:, os membros desse grupo automaticamente serão
privados. Você verá esse comportamento-padrão utilizado em código e deve se familiarizar com ele. Entretanto,
não o utilizaremos neste livro.

■ FUNÇÕES DE ACESSO: ACESSOR (get) e MUTATOR (set)


Você sempre deve tornar privadas todas as variáveis-membros de uma classe. Às vezes, no entanto, você pode
precisar fazer algo com os dados em um objeto de uma classe. As funções-membros permitirão que se façam mui-
tas coisas com os dados em um objeto, porém, mais cedo ou mais tarde, você vai querer ou precisar fazer algo
com os dados para os quais não há função-membro. Como se pode fazer algo novo com os dados em um objeto?
A resposta é que você pode fazer qualquer coisa razoável que deseje, desde que equipe suas classes com funções
acessor e mutator adequadas. Essas funções-membros permitem que você tenha acesso aos dados em um objeto e
os altere de forma bastante geral. As funções acessor permitem que os dados sejam lidos. No Painel 6.4, as fun-
170 Estruturas e Classes

ções-membros getDia e getNumeroMes são funções acessor. As funções acessor não precisam retornar literalmente
os valores de cada variável-membro, mas precisam retornar algo equivalente a esses valores. Por exemplo, para uma
classe como DiaDoAno, você pode ter uma função acessor que retorne o nome do mês como algum tipo de valor
em string, em vez de retornar o mês como um número.
As funções mutator permitem que se alterem os dados. No Painel 6.4, as duas funções chamadas set são fun-
ções mutator. Faz parte da tradição utilizar nomes que incluam a palavra get para funções acessor e nomes que
incluam a palavra set para funções mutator. (As funções entrada e saida no Painel 6.4 são, na realidade, funções
mutator e acessor, respectivamente, mas E/S é um caso tão especial que elas geralmente são chamadas de funções
E/S em vez de funções acessor e mutator.)
Suas definições de classe devem sempre fornecer uma coleção adequada de funções acessor e mutator.
Pode parecer que as funções acessor e mutator destruam o objetivo de tornar as variáveis-membros privadas,
mas isso não acontece. Observe a função mutator set no Painel 6.4. Ela não permitirá que você fixe a variável-
membro dia como 13 ou qualquer número que não esteja no intervalo de 1 a 31 (inclusive). De forma similar,
ela não permitirá que você fixe a variável membro mes como 13 ou qualquer outro número que não represente
um mês. Se as variáveis fossem públicas você poderia fixar a data em valores absurdos para uma data. (Dessa for-
ma, você ainda pode fixar valores que não representam uma data real, como 31 de fevereiro, mas seria fácil excluir
essas datas. Não as excluímos para manter o exemplo simples.) Com as funções mutator, você pode controlar e
filtrar as mudanças nos dados.

1 Exercícios de Autoteste 1

12. Suponha que seu programa contenha a seguinte definição de classe:


class Automovel
{
public:
void setPreco(double novoPreco);
void setRendimento(double novoRendimento);
double getPreco( );
private:
double preco;
double rendimento;
double getRendimento( );
};
e suponha que a função main do seu programa contenha a seguinte declaração e que o programa fixe de
algum modo os valores de todas as variáveis-membros com alguns valores:
Automovel hyundai, jaguar;
Quais dos seguintes comandos são, então, permitidos na função main do seu programa?
hyundai.preco = 4999.99;
jaguar.setPreco(30000.97);
double umPreco, umRendimento;
umPreco = jaguar.getPreco( );
umPreco = jaguar.getRendimento( );
umRendimento = hyundai.getRendimento( );
hyundai = jaguar;
13. Suponha que você mude o Exercício de Autoteste 12 de forma que, na definição da classe Automovel,
todas as variáveis-membros sejam públicas em vez de privadas. Como isso alteraria sua resposta?
14. Explique o que public: e private: significam em uma definição de classe.
15. a. Quantas seções public: são necessárias em uma classe para a classe ser útil?
b. Quantas seções private: são necessárias em uma classe?
Classes 171

IDica INTERFACE E IMPLEMENTAÇÃO SEPARADAS


O princípio do encapsulamento diz que você deve definir as classes de tal forma que um programador que
utilize uma classe não precise se preocupar com detalhes de como ela é implementada. O programador que
utiliza a classe só precisa conhecer as regras de como utilizá-la. Essas regras são conhecidas como interfa-
ce ou API. Há algumas discordâncias sobre o que exatamente as iniciais API significam, mas em geral se
considera que se refiram a algo como interface de aplicação com o programador (application programmer in-
terface) ou interface abstrata de programação (abstract programming interface) ou algo similar. Neste livro,
chamaremos essas regras de interface da classe. É importante não se esquecer de que há uma clara distin-
ção entre a interface e a implementação de uma classe. Se sua classe é bem projetada, qualquer programa-
dor que a utiliza precisa conhecer apenas a interface da classe e não precisa conhecer nenhum detalhe da
implementação da classe. Uma classe cuja interface e implementação são separadas dessa forma, às vezes, é
chamada de tipo de dados abstrato (ADT) ou uma classe bem encapsulada. No Capítulo 11 mostraremos
como separar a interface e a implementação, colocando-as em arquivos diferentes, mas o importante é man-
tê-las separadas conceitualmente.
Para uma classe C++, a interface consiste em duas espécies: os comentários, normalmente no início da
definição de classe, que dizem o que os dados do objeto devem representar, como uma data, uma conta
bancária ou uma simulação de lava-carros; e as funções-membros públicas da classe com os comentários
que dizem como utilizar essas funções-membros públicas. Em uma classe bem projetada, a interface da
classe deve ser tudo o que é necessário saber a fim de utilizar a classe em seu programa.
A implementação de uma classe diz como a interface da classe é concretizada em código C++. A imple-
mentação consiste nos membros privados da classe e nas definições das funções-membros tanto públicas
quanto privadas. Embora a implementação seja necessária a fim de executar um programa que utiliza a clas-
se, você não precisa saber nada sobre a implementação para escrever o resto de um programa que utiliza a
classe; ou seja, você não precisa saber nada sobre a implementação a fim de escrever a função main do pro-
grama e escrever quaisquer funções não-membros ou outras classes utilizadas pela função main.
A vantagem mais óbvia que advém da nítida separação da interface e da implementação de suas classes é
que se pode alterar a implementação sem ter de alterar outras partes do programa. Em grandes projetos de
programação essa divisão entre a interface e a implementação facilita a divisão do trabalho entre vários pro-
gramadores. Se a interface for bem projetada, um programador pode escrever a implementação para a classe
enquanto outros programadores escrevem o código que utiliza a classe. Mesmo que você seja o único pro-
gramador trabalhando em um projeto, você tem uma tarefa maior dividida em tarefas menores, o que torna
seu programa mais fácil de projetar e depurar.

IDica TESTE PARA ENCAPSULAMENTO


Se sua definição de classe produzir um ADT (ou seja, caso separe adequadamente a interface e a implemen-
tação), você pode mudar a implementação da classe (isto é, alterar a representação dos dados e/ou alterar a
implementação de algumas funções-membros) sem precisar alterar mais nenhum código para qualquer pro-
grama que utilize a definição de classe. Eis aqui um teste seguro para verificar se você definiu um ADT ou
uma classe que não está encapsulada adequadamente.
Por exemplo, você pode alterar a implementação da classe DiaDoAno no Painel 6.4 para a seguinte e ne-
nhum programa que utilizar esta definição de classe precisará de qualquer alteração:
class DiaDoAno
{
public:
void entrada( );
void saida( );

void set(int novoDia, int novoMes);


//Pré-condição: novoDia e novoMes formam uma data possível.
//Pós-condição: A data é refixada de acordo com os argumentos.

void set(int novoMes);


//Pré-condição: 1 <= novoMes <= 12
//Pós-condição: A data é fixada para o primeiro dia do mês.

int getNumeroMes( );
//Retorna 1 para janeiro, 2 para fevereiro, etc.

int getDia( );
172 Estruturas e Classes

IDica (continuação)
private:
char primeiraLetra;//do mês
char segundaLetra;//do mês
char terceiraLetra;//do mês
int dia;
};
Nessa versão, um mês é representado pelas primeiras três letras em seu nome, como ’j’, ’a’ e ’n’ para janei-
ro. As funções-membros também devem ser reescritas, é claro, mas podem ser reescritas e se comportarem
exatamente como antes. Por exemplo, a definição da função getNumeroMes poderia começar assim:
int DiaDoAno::getNumeroMes( )
{
if (primeiraLetra == ’j’ && segundaLetra == ’a’
& terceiraLetra == ’n’)
return 1;
if (segundaLetra == ’f’ && segundaLetra == ’e’
& terceiraLetra == ’v’)
return 2;
. . .
Isso seria bastante entediante, mas nada difícil.

■ ESTRUTURAS VERSUS CLASSES


As estruturas normalmente são usadas com todas as variáveis-membros públicas e não com funções-membros.
Entretanto, em C++, uma estrutura pode ter variáveis-membros privadas e tanto funções-membros públicas quanto
privadas. Exceto por algumas diferenças de notação, uma estrutura em C++ pode fazer qualquer coisa que uma
classe pode. Agora que dissemos tudo isso e satisfizemos nosso compromisso em dizer toda a verdade, nós o aconselha-
mos a esquecer esse detalhe técnico a respeito das estruturas. Se você levar esse detalhe técnico a sério e utilizar

CLASSES E OBJETOS
Uma classe é um tipo cujas variáveis podem ser tanto variáveis-membros quanto funções-membros. A sintaxe para uma defini-
ção de classe é dada abaixo.
SINTAXE
class Nome_Classe
{
.
public:
Especificacao_MembroN+1
Especificacao_MembroN+2

private:
.
.
.

Especificacao_Membro_1
Especificacao_Membro_2
> Membros públicos

. Membros privados
.
.
Especificacao_MembroN

}; Não esqueça este ponto-e-vírgula.


Cada Especificacao_Membro_1 é ou uma declaração de variável-membro ou uma declaração de função-membro (protótipo).
Seções adicionais public: e private: são permitidas. Se o primeiro grupo de membros não possui um rótulo public: ou priva-
te:, então é o mesmo que se houvesse um private: antes do primeiro grupo.
EXEMPLO
class Bicicleta
{
Resumo do Capítulo 173

(continuação)
public:
char getCor( );
int numeroDeMarchas( );
void set(int asMarchas, char aCor);
private:
int marchas;
char cor;
};
Uma vez que uma classe é definida, uma variável objeto (variável do tipo classe) pode ser declarada da mesma forma que variá-
veis de qualquer outro tipo. Por exemplo, a linha seguinte declara duas variáveis objeto do tipo Bicicleta:
Bicicleta minhaBicicleta, suaBicicleta;

estruturas da mesma forma que utiliza classes, terá dois nomes (com diferentes regras de sintaxe) para o mesmo
conceito. Por outro lado, se você usar as estruturas como nós as descrevemos, terá uma diferença significativa en-
tre estruturas (como você as usa) e classes, e seu uso será o mesmo que o da maioria dos outros programas.
Uma diferença entre uma estrutura e uma classe é no modo como tratam um grupo inicial de membros que
não possuem especificador de acesso público nem privado. Se o primeiro grupo de membros em uma definição não
possui rótulo public: nem private:, uma estrutura presume que o grupo seja público, enquanto uma classe pre-
sumiria que o grupo fosse privado.

IDica PENSANDO OBJETOS


Se você nunca programou com classes, pode levar algum tempo até captar a sensação de programar com
elas. Quando se programa com classes, o centro do palco é ocupado pelos dados e não pelos algoritmos.
Não é que não existam algoritmos. Entretanto, os algoritmos são feitos para se adaptarem aos dados, o que
é o contrário de projetar os dados para se adaptarem ao algoritmo. É uma diferença de ponto de vista. No
caso extremo, que muitos consideram o melhor estilo, não se tem funções globais, apenas classes com fun-
ções-membros. Nesse caso, você define os objetos e como os objetos interagem, em vez de algoritmos que
atuam sobre dados. Discutiremos os detalhes de como fazer isso no decorrer do livro. É claro que você
pode ignorar as classes completamente ou relegá-las a um papel secundário, mas nesse caso você estará
programando em C, não em C++.

1 Exercícios de Autoteste 1

16. Quando você define uma classe em C++, deve tornar as variáveis-membros públicas ou privadas? Deve
tornar as funções-membros públicas ou privadas?
17. Quando você define uma classe em C++, que itens são considerados parte da interface? Que itens são
considerados parte da implementação?

Resumo do Capítulo
■ Uma estrutura pode ser usada para combinar dados de diferentes tipos em um único (composto) valor de
dados.
■ Uma classe pode ser usada para combinar dados e funções em um único (composto) objeto.
■ Uma variável-membro ou uma função-membro de uma classe pode ser pública ou privada. Se for pública,
pode ser usada fora da classe. Se for privada, só pode ser usada na definição de uma função-membro.
■ Uma função pode ter parâmetros formais de um tipo classe ou estrutura. Uma função pode retornar valo-
res de um tipo classe ou estrutura.
■ Uma função-membro de uma classe pode ser sobrecarregada da mesma forma que as funções comuns.
■ Quando se define uma classe em C++, deve-se separar a interface e a implementação, de forma que qual-
quer programador que utiliza a classe precise conhecer apenas a interface e não precise sequer olhar para a
implementação. Este é o princípio do encapsulamento.
174 Estruturas e Classes

RESPOSTAS DOS EXERCÍCIOS DE AUTOTESTE


1. a. double
b. double
c. ilegal — não se pode utilizar um identificador de estrutura em vez de uma variável-estrutura.
d. char
e. CDBContaV2
2. A $9.99
A $1.11
3. Está faltando um ponto-e-vírgula no final da definição de Coisa.
4. A x = {1,2};
5. a. Inicializadores a menos; não é um erro de sintaxe. Depois da inicialização, dia == 21, mes == 12 e ano
== 0. As variáveis-membros que não receberam um inicializador são inicializadas como um zero do tipo
apropriado.
b. Correto após inicialização. 21 == dia, 12 == mes e 1995 == ano.
c. Erro: inicializadores demais.
6. struct RegistroDoEmpregado
{
double salario;
int ferias;
char status;
};
7. void leRegistroDeSapato(TipoDeSapato& novoSapato)
{
cout << "Informe o estilo do sapato (uma letra): ";
cin >> novoSapato.estilo;
cout << "Informe preço do sapato $";
cin << novoSapato.preco;
}
8. TipoDeSapato desconto(TipoDeSapato velhoRegistro)
{
TipoDeSapato temp;
temp.estilo = velhoRegistro.estilo;
temp.preco = 0.90*velhoRegistro.preco;
return temp;
}
9. void DiaDoAno::entrada( )
{
cout << "Informe o dia do mês: ";
cin >> dia;
cout << "Informe o mês com um número: ";
cin >> mes;
}
10. void Temperatura::set(double novosGraus, char novaEscala)
{
graus = novosGraus;
escala = novaEscala;
}
11. Tanto o operador ponto quanto o operador de resolução de escopo são usados com membros de estrutura
para especificar de que classe ou estrutura o nome de membro é um membro. Se a classe DiaDoAno for
definida como no Painel 6.3 e hoje for um objeto da classe DiaDoAno, pode-se ter acesso ao membro mes
por meio do operador ponto: hoje.mes. Quando damos a definição de uma função-membro, o operador
de resolução de escopo é usado para dizer ao compilador que essa função é aquela declarada na classe.
12. hyundai.preco = 4999.99; //ILEGAL. preço é privado
jaguar.setPreco(30000.97); //LEGAL
Projetos de Programação 175

double umPreco, umRendimento; //LEGAL


umPreco = jaguar.getPreco( ); //LEGAL
umPreco = jaguar.getRendimento( ); //ILEGAL. getRendimento é
//privado.
umRendimento = hyundai.getRendimento( ); //ILEGAL. getRendimento é
//privado.
hyundai = jaguar; //LEGAL
13. Após a mudança, todos devem ser legais.
14. A todos os membros (variáveis-membros e funções-membros) que são assinalados como private: só se
pode ter acesso por nome nas definições de funções-membros (tanto públicas quanto privadas) da mesma
classe. Em relação aos membros assinalados como public:, não há restrições quanto ao local onde podem
ser usados.
15. a. Só uma. O compilador avisa se você não tiver membros public: em uma classe (ou struct).
b. Nenhuma, mas normalmente esperamos encontrar pelo menos uma seção private: em uma classe.
16. Todas as variáveis-membros devem ser privadas. As funções-membros que são parte da interface devem ser
públicas. Você também pode ter funções auxiliares que só são usadas na definição de outras funções-mem-
bros. Essas funções auxiliares devem ser privadas.
17. Todas as declarações de variáveis-membros privadas são parte da implementação. (Não deve haver variá-
veis-membros públicas.) Todas as declarações para funções-membros públicas da classe (que são listadas
nas definições de classe), assim como os comentários que explicam essas declarações, fazem parte da interface.
Todas as declarações de funções-membros privadas fazem parte da implementação. Todas as definições de fun-
ções-membros (quer a função seja pública, quer privada) fazem parte da implementação.

PROJETOS DE PROGRAMAÇÃO
1. Escreva um programa de notas para uma classe com as seguintes regras:
a. Existem duas provas, cada uma com nota máxima 10.
b. Existe um exame no meio do ano e um final, cada um com nota máxima 100.
c. O exame final vale 50% da nota final, o de meio de ano vale 25% e as duas provas juntas valem um
total de 25%. (Não se esqueça de normalizar as notas das provas. Elas devem ser convertidas em uma
porcentagem antes de se fazer a média.)
Qualquer nota de 90 ou mais equivale a A; entre 80 e 90, B; entre 70 e 80, C; entre 60 e 70, D; e me-
nos de 60, E. O programa lerá as notas dos alunos e apresentará como saída o boletim do estudante, que
consiste nas duas provas e nos dois exames, além da média numérica de todo o curso e da letra final. De-
fina e utilize uma estrutura para o boletim do aluno.
2. Defina uma classe para um tipo chamado TipoContador. Um objeto desse tipo é utilizado para contar
coisas, registrando uma contagem que é um número inteiro não-negativo. Inclua uma função mutator que
fixe o contador a uma contagem dada como argumento. Inclua funções-membros para aumentar a conta-
gem em um e diminuir a contagem em um. Assegure-se de que nenhuma função-membro permita que o
valor do contador se torne negativo. Inclua também uma função-membro que retorne o valor atual da
contagem e um que apresente a contagem como saída. Insira sua definição de classe em um programa-tes-
te.
3. O tipo Point é um tipo de dados bastante simples, mas sob outro nome (a classe modelo pair) esse tipo
de dados é definido e utilizado na Standard Template Library (Biblioteca Modelo Padrão) do C++, embo-
ra você não precise saber nada sobre a Standard Template Library para fazer este exercício. Escreva uma
definição de classe chamada Ponto que pode ser usada para armazenar e manipular a localização de um
ponto no plano. Você vai precisar declarar e implementar as seguintes funções-membros:
a. uma função-membro set que fixe os dados privados depois que um objeto dessa classe é criado.
b. uma função-membro que mova o ponto de uma certa quantidade ao longo das direções vertical e ho-
rizontal especificadas pelo primeiro e segundo argumentos.
c. uma função-membro para girar o ponto em 90 graus no sentido horário ao redor da origem.
d. duas funções inspetoras const para recuperar as coordenadas atuais do ponto.
176 Estruturas e Classes

Documente essas funções com os comentários adequados. Insira sua classe em um programa-teste que
peça ao usuário dados para vários pontos, crie os pontos e exercite as funções-membros.
4. Escreva a definição para uma classe chamada BombaDeGasolina para ser usada como modelo para uma
bomba em um posto de gasolina. Antes de começar a fazer este exercício, escreva o comportamento que
espera de uma bomba de gasolina do ponto de vista do comprador.
Abaixo, há uma lista de coisas que se espera que uma bomba de gasolina faça. Se sua lista ficar diferente
e você achar que a sua é melhor, consulte o orientador. Você e seu orientador podem decidir juntos qual
será o melhor comportamento a implementar. Então implemente e teste seu projeto para a bomba de ga-
solina.
a. Apresentação da quantidade fornecida.
b. Apresentação do preço relativo à quantidade fornecida.
c. Apresentação do custo por galão, litro ou outra unidade de volume usada no local onde você mora.
d. Antes de ser usada, a bomba de gasolina deve zerar a quantidade fornecida e o preço relativo.
e. Uma vez em funcionamento, a bomba de gasolina continua a fornecer gasolina, controlar a quantidade
fornecida e calcular o preço da quantidade fornecida até parar.
f. É necessário algum tipo de controle de parada de fornecimento.

Implemente o comportamento da bomba de gasolina como declarações de funções-membros da classe bomba


de gasolina, depois escreva implementações dessas funções-membros. Você terá de decidir se há dados sob o con-
trole da bomba de gasolina aos quais o usuário da bomba não deve ter acesso. Se este for o caso, faça com que es-
sas variáveis-membros sejam privadas.
CAPÍTULO

Construtores e Outras Ferramentas


Construtores e Outras Ferramentas

Capítulo 7Construtores e Outras Ferramentas


Dêem-nos as ferramentas e terminaremos o trabalho.
Winston Churchill, transmissão de rádio (9 de fevereiro de 1941)

INTRODUÇÃO
Este capítulo apresenta diversas ferramentas importantes para serem utilizadas quando se
programa com classes. A mais importante dessas ferramentas são os construtores de classe,
um tipo de função utilizada para inicializar objetos da classe.
A Seção 7.3 apresenta os vectors como um exemplo de classes e como uma introdução
à Standard Template Library (STL). Vectors são semelhantes a vetores, mas podem au-
mentar e encolher em tamanho. A STL é uma extensa biblioteca de classes predefinidas.
A Seção 7.3 pode ser lida agora ou depois. O conteúdo dos Capítulos 8 a 18 não requer
o conteúdo da Seção 7.3, portanto, se desejar, você pode adiar a leitura da referida seção.
As Seções 7.1 e 7.2 não utilizam o material do Capítulo 5, mas utilizam o do Capítu-
lo 6. A Seção 7.3 requer os Capítulos de 1 a 6, além da Seção 7.1.

7.1 Construtores
Um bom início já é metade do caminho.
Provérbio

Muitas vezes se quer inicializar alguma ou todas as variáveis-membros de um objeto


em sua declaração. Como veremos mais adiante neste livro, existem outras ações de inicia-
lização que você pode querer usar, mas a inicialização de variáveis-membros é a espécie
mais comum de inicialização. O C++ inclui recursos especiais para essas inicializações.
Quando se define uma classe, pode-se definir um tipo especial de função-membro chama-
do construtor. Um construtor é uma função-membro que é chamada automaticamente
quando um objeto dessa classe é declarado. Utiliza-se o construtor para inicializar os valo-
res de algumas ou de todas as variáveis-membros e para efetuar qualquer outra espécie de
inicialização que possa ser necessária.

■ DEFINIÇÕES DE CONSTRUTORES
Define-se um construtor da mesma forma que se define qualquer outra função-mem-
bro, a não ser por duas questões:
1. Um construtor deve ter o mesmo nome que a classe. Por exemplo, se a classe se cha-
mar ContaBancaria, qualquer construtor dessa classe deve se chamar ContaBancaria.
2. Uma definição de construtor não pode retornar um valor. Além disso, nenhum
tipo, nem mesmo void, pode ser dado no início da declaração da função ou no
cabeçalho da função.
178 Construtores e Outras Ferramentas

Por exemplo, suponha que desejemos acrescentar um construtor para inicializar o dia e o mês para objetos de
tipo DiaDoAno, o que apresentamos no Painel 6.4, e redefinir as linhas abaixo para incluir um construtor. (Omiti-
mos comentários para economizar espaço, mas eles devem ser incluídos em um programa real.)
class DiaDoAno
{
public:
DiaDoAno(int valorDia, int valorMes); Construtor
//Inicializa o dia e o mês como argumentos.

void entrada( );
void saida( );
void set(int novoDia, int novoMes);
void set(int novoMes);
int getNumeroMes( );
int getDia( );
private:
int dia;
int mes;
};

Observe que o construtor se chama DiaDoAno, que é o nome da classe. Note também que a declaração (protó-
tipo) do construtor DiaDoAno não começa com void ou qualquer nome de tipo. Finalmente, observe que o cons-
trutor é colocado na seção pública da definição de classe. Normalmente, os construtores devem ser funções-
membros públicas. Se você fizer todos os seus construtores-membros privados, não poderá declarar nenhum objeto
daquele tipo classe, o que tornaria a classe completamente inútil.
Com a classe redefinida DiaDoAno, dois objetos de tipo DiaDoAno podem ser declarados e inicializados da se-
guinte forma:
DiaDoAno data1(7, 4), data2(5, 5)

Presumindo que a definição do construtor executa a ação de inicializar conforme prometemos, a declaração
acima declarará o objeto data1, fixará o valor de data1.dia como 7 e de data1.mes como 9. Assim, o objeto
data1 é inicializado de modo a representar a data 7 de setembro. De forma similar, data2 é inicializado de modo
a representar a data 5 de maio. O que acontece é que o objeto data1 é declarado e o construtor DiaDoAno é cha-
mado com dois argumentos, 7 e 9. De forma similar, data2 é declarado e o construtor DiaDoAno é chamado com
os argumentos 5 e 5. O resultado é conceitualmente equivalente ao seguinte (embora não se possa escrever desta
forma em C++):
DiaDoAno data1, data2; //PROBLEMAS, MAS CORRIGÍVEIS
data1.DiaDoAno(7, 9); //MUITO ILEGAL
data2.DiaDoAno(5, 5); //MUITO ILEGAL

Como os comentários indicam, você não pode colocar as três linhas acima em seu programa. É possível tornar
a primeira linha aceitável, mas as duas chamadas ao construtor DiaDoAno são ilegais. Um construtor não pode ser
chamado da mesma forma que uma função-membro comum. Mesmo assim, o que queremos que aconteça quan-
do escrevemos as três linhas acima é claro, e acontece automaticamente quando se declaram os objetos data1 e
data2 da seguinte forma:
DiaDoAno data1(7, 9), data2(5, 5);

A definição de um construtor é dada da mesma forma que qualquer outra função-membro. Por exemplo, se
você reformular a definição da classe DiaDoAno acrescentando o construtor que acabamos de descrever, também
precisa acrescentar uma definição do construtor, que pode ser assim:
DiaDoAno::DiaDoAno(int valorDia, int valorMes)
{
dia = valorDia;
mes = valorMes;
}
Construtores 179

Como a classe e a função construtora possuem o mesmo nome, o nome DiaDoAno ocorre duas vezes no cabe-
çalho da função; o DiaDoAno antes do operador de resolução de escopo:: é o nome da classe e o DiaDoAno depois
do operador de resolução de escopo é o nome da função construtora. Observe também que não é especificado ne-
nhum tipo para a saída no cabeçalho da definição do construtor, nem mesmo o tipo void. Fora essas questões,
um construtor pode ser definido da mesma forma que uma função-membro comum.

CONSTRUTOR
Um construtor é uma função-membro de uma classe que possui o mesmo nome que a classe. O construtor é chamado auto-
maticamente quando um objeto da classe é declarado. Construtores são utilizados para inicializar objetos. O construtor deve
ter o mesmo nome que a classe de que é membro.

Como acabamos de ilustrar, um construtor pode ser definido exatamente como qualquer outra função-mem-
bro. Entretanto, há um modo alternativo e melhor de se definir construtores. A definição prévia do construtor
DiaDoAno é totalmente equivalente à versão seguinte:
DiaDoAno::DiaDoAno(int valorDia, int valorMes)
: dia(valorDia), mes(valorMes)
{/*Corpo intencionalmente vazio*/}

O novo elemento mostrado na segunda linha da definição do construtor chama-se seção de inicialização.
Como esse exemplo mostra, a seção de inicialização vem depois do parêntese que encerra a lista de parâmetros e
antes da chave de abertura do corpo da função. A seção de inicialização consiste em dois-pontos seguidos por uma
lista de algumas ou todas as variáveis-membros separadas por vírgulas. Cada variável-membro é seguida por seu
valor de inicialização entre parênteses. Observe que os valores de inicialização podem ser fornecidos em termos de
parâmetros construtores.
O corpo da função em uma definição de construtor com uma seção de inicialização não precisa ser vazio
como no exemplo anterior. Por exemplo, a seguinte versão aperfeiçoada da definição de construtor verifica se os
argumentos são adequados:
DiaDoAno::DiaDoAno(int valorDia, int valorMes)
: dia(valorDia), mes(valorMes)
{
if ((dia < 1) || (dia > 31))
{
cout << "Valor de dia ilegal!\n";
exit(1);
}
if ((mes < 1) || (mes > 12)
{
cout << "Valor de mês ilegal!\n";
exit(1);
}
}

Você pode sobrecarregar um nome de construtor como DiaDoAno::DiaDoAno, exatamente como pode sobrecar-
regar qualquer outro nome de função-membro. Na realidade, em geral os construtores são sobrecarregados para
que os objetos possam ser inicializados em mais de uma forma. Por exemplo, no Painel 7.1, redefinimos a classe
DiaDoAno de modo que tenha três versões de seu construtor. Essa redefinição sobrecarrega o nome do construtor
DiaDoAno de maneira que possa ter dois (como acabamos de explicar), um ou nenhum argumento.
Observe que, no Painel 7.1, dois construtores chamam a função-membro testaData para verificar se seus va-
lores de inicialização são adequados. A função-membro testaData é privada, já que foi projetada apenas para ser
usada por outras funções-membros e, assim, faz parte dos detalhes da implementação oculta.
Omitimos a função-membro set dessa definição de classe reformulada de DiaDoAno. Uma vez que se tenha
um bom conjunto de definições de construtor, não há necessidade de quaisquer outras funções-membros para fi-
xar as variáveis-membros da classe. Você pode usar o construtor DiaDoAno do Painel 7.1 com os mesmos objetivos
com que usaria a função-membro set (que incluímos na versão antiga da classe exibida no Painel 6.4).
180 Construtores e Outras Ferramentas

Painel 7.1 Classe com construtores (parte 1 de 2)


1 #include <iostream>
Esta definição de DayOFYear é uma versão aperfeiçoada
2 #include <cstdlib> //para saída
3 using namespace std; da classe DayOfYear dada no Painel 6.4.

4 class DayOfYear
5 {
6 public:
7 DayOfYear(int monthValue, int dayValue);
8 //Inicializa o dia e o mês com os argumentos.

9 DayOfYear(int monthValue);
10 //Inicializa a data para o primeiro dia do mês fornecido.
11 DayOfYear( ); construtor-padrão
12 //Inicializa a data como 1 de janeiro.
13 void input( );
14 void output( );
15 int getMonthNumber( );
16 //Retorna 1 para janeiro, 2 para fevereiro, etc.
17 int getDay( );
18 private:
19 int month;
20 int day;
21 void testDate( );
Isto provoca uma chamada ao construtor-padrão.
22 };
Observe que não há parênteses.
23 int main( )
24 {
25
26
DayOfYear date1(2, 21), date2(5), date3;
cout << "Datas inicializadas:\n";
/
27 date1.output( ); cout << endl;
28 date2.output( ); cout << endl;
29 date3.output( ); cout << endl;
30 date1 = DayOfYear(10, 31); uma chamada explícita ao construtor
31 cout << "date1 atualizada para a seguinte:\n"; DayOfYear::DayOfYear
32 date1.output( ); cout << endl;
33 return 0;
34 }
35
36 DayOfYear::DayOfYear(int monthValue, int dayValue)
37 : month(monthValue), day(dayValue)
38 {
39 testDate( );
40 }
41 DayOfYear::DayOfYear(int monthValue) : month(monthValue), day(1)
42 {
43 testDate( );
44 }
45 DayOfYear::DayOfYear( ) : month(1), day(1)
46 {/*Corpo intencionalmente vazio.*/}
47 //utiliza iostream e cstdlib:
48 void DayOfYear::testDate( )
49 {
50 if ((month < 1) || (month > 12))
51 {
52 cout << "Valor ilegal para o mês!\n";
53 exit(1);
Construtores 181

Painel 7.1 Classe com construtores (parte 2 de 2)


54 }
55 if ((day < 1) || (day > 31))
56 { <As definições das outras funções-membros
57 cout << "valor ilegal para o dia!\n"; são as mesmas do Painel 6.4.>
58 exit(1);
59 }
60 }
DIÁLOGO PROGRAMA-USUÁRIO
Datas inicializadas:
21 de fevereiro
1o de maio
1o de janeiro
data1 atualizada para:
31 de outubro

CONSTRUTORES SEM ARGUMENTOS


É importante não utilizar parênteses quando se declara uma variável classe e se deseja que o construtor
seja invocado sem argumentos. Por exemplo, considere a linha seguinte do Painel 7.1:
DiaDoAno data1(21, 2), data2(5), data3;
O objeto data1 é inicializado pelo construtor que requer dois argumentos, o objeto data2 é inicializado pelo
construtor que requer um argumento e o objeto data3 é inicializado pelo construtor que não requer argu-
mentos.
É tentador pensar que se devem utilizar parênteses vazios quando se declara uma variável para a qual se
deseja o construtor sem nenhum argumento invocado, mas há um motivo para isso não ser feito. Considere
a seguinte linha, que aparentemente deveria declarar a variável data3 e invocar o construtor sem argumen-
tos:
DiaDoAno data3( ); //PROBLEMA! Não é o que você pensa que é.
O problema é que, embora você pretenda que isso seja uma declaração e uma invocação ao construtor, o
compilador vê isso como uma declaração (protótipo) de uma função chamada data3 que não possui parâme-
tros e que retorna um valor de tipo DiaDoAno. Como uma função chamada data3 que não possua parâmetros e
que retorne um valor de tipo DiaDoAno é perfeitamente legal, esta notação sempre tem esse significado. Uti-
liza-se uma notação diferente (sem parênteses) quando se quer invocar um construtor sem argumentos.

CHAMANDO UM CONSTRUTOR
Um construtor é chamado automaticamente quando um objeto é declarado, mas você deve dar o argumento para o construtor
quando declara o objeto. Um construtor também pode ser chamado explicitamente, mas a sintaxe é diferente da usada para
funções-membros comuns.
SINTAXE PARA UMA DECLARAÇÃO DE OBJETO QUANDO SE TEM CONSTRUTORES
Nome_Classe Nome_Variavel(Argumentos_Para_Construtor);
EXEMPLO
DiaDoAno feriado(7, 9);
SINTAXE PARA UMA CHAMADA EXPLÍCITA AO CONSTRUTOR
Variavel = Nome_Construtor(Argumentos_Para_Construtor);
EXEMPLO
feriado = DiaDoAno(25, 12);
Um construtor deve ter o mesmo nome que a classe da qual é membro. Assim, nas descrições de sintaxe acima, Nome_Classe
e Nome_Construtor são o mesmo identificador.

■ CHAMADAS EXPLÍCITAS A CONSTRUTORES


Um construtor é chamado explicitamente sempre que se declara um objeto do tipo classe, mas também pode
ser chamado novamente depois que o objeto tenha sido declarado. Isso permite que se fixem de maneira conve-
182 Construtores e Outras Ferramentas

niente todos os membros de um objeto. Os detalhes técnicos são os seguintes: chamar o construtor cria um objeto
anônimo com novos valores. Um objeto anônimo é um objeto que não foi nomeado (ainda) por nenhuma variável. O
objeto anônimo pode ser atribuído ao objeto nomeado. Por exemplo, aqui está uma chamada ao construtor DiaDoAno
que cria um objeto anônimo para a data 25 de dezembro. Esse objeto anônimo é atribuído à variável feriado (que foi
declarada como sendo do tipo DiaDoAno) de modo que feriado também represente a data 25 de dezembro.1
feriado = DiaDoAno(25, 12);

(Como você pode concluir pela notação, um construtor às vezes se comporta como uma função que retorna
um objeto do seu tipo classe.)
Observe que, quando se invoca explicitamente um construtor sem argumentos, os parênteses devem ser incluí-
dos, da seguinte forma:
feriado = DiaDoAno( );

Os parênteses são omitidos somente quando se declara uma variável do tipo classe e se quer invocar um cons-
trutor sem argumentos como parte da declaração.

Dica SEMPRE INCLUA UM CONSTRUTOR-PADRÃO


Um construtor que não requeira argumentos é chamado de construtor-padrão. Esse nome pode provocar
confusões, porque às vezes ele é gerado automaticamente e outras não. Vamos lhe contar a história toda.
Se você definir uma classe e não incluir nenhum construtor de nenhum tipo, um construtor-padrão será au-
tomaticamente criado. Esse construtor-padrão não faz nada, mas fornece a você um objeto não-inicializado
do tipo classe, que pode ser atribuído a uma variável do tipo classe. Se sua definição de classe incluir um
ou mais construtores de qualquer tipo, nenhum construtor é gerado automaticamente. Assim, por exemplo,
suponha que você defina uma classe chamada ClasseAmostra. Se você incluir um ou mais construtores que
requeiram, cada um, um ou mais argumentos, mas não incluir um construtor-padrão em sua definição de
classe, não há construtor-padrão e qualquer declaração como a seguinte será ilegal:
ClasseAmostra umaVariavel;
O problema com a declaração acima é que ela pede ao compilador para invocar o construtor-padrão, mas
nesse caso não existe construtor-padrão.
Para tornar isso concreto, suponha que você defina uma classe assim:
class ClasseAmostra
{
public:
ClasseAmostra(int parametro1, double parametro2);
private:
int dado1;
double dado1;
};
Você deve reconhecer a seguinte linha como uma forma legal de declarar um objeto de tipo ClasseAmostra
e chamar o construtor para essa classe:
ClasseAmostra minhaVariavel(7, 7.77);
Entretanto, a linha seguinte é ilegal:
ClasseAmostra suaVariavel;
O compilador interpreta a declaração acima como a inclusão de uma chamada a um construtor sem argu-
mentos, mas não há definição para um construtor com zero argumento. Você precisa acrescentar dois argu-
mentos à declaração de suaVariavel ou acrescentar uma definição de construtor para um construtor sem
argumentos.
Se você redefinir a classe ClasseAmostra da seguinte forma, a declaração acima de suaVariavel será legal:
class ClasseAmostra
{
public:
ClasseAmostra(int parametro1, double parametro2);
ClasseAmostra( ); Construtor-padrão

1. Observe que este processo é mais complicado do que simplesmente alterar os valores das variáveis-membros. Por razões
de eficiência, portanto, você pode querer continuar usando as funções-membros chamadas set no local de uma chamada
explícita a um construtor.
Construtores 183

IDica (continuação)
void fazerAlgo( );
private:
int dado1;
double dado1;
};
Para evitar esse tipo de confusão, inclua sempre um construtor-padrão em toda a classe que definir. Se não
quiser que o construtor-padrão inicialize quaisquer variáveis-membros, simplesmente lhe dê um corpo vazio
quando o implementar. A seguinte definição de construtor é perfeitamente legal. Não faz nada, mas cria um
objeto não-inicializado:
ClasseAmostra::ClasseAmostra( )
{ /* Não faz nada.*/}

CONSTRUTORES SEM ARGUMENTOS


Um construtor que não requer argumentos é chamado de construtor-padrão. Quando se declara um objeto e se deseja que o
construtor com zero argumento seja chamado, não se incluem parênteses. Por exemplo, para declarar um objeto e passar dois
argumentos para o construtor, pode-se fazer o seguinte:
DiaDoAno data1(31, 12);
Entretanto, se você quiser que o construtor com zero argumento seja usado, declare o objeto assim:
DiaDoAno data2;
Não declare o objeto assim:
DiaDoAno data2( ); //PROBLEMA!
(O problema é que essa sintaxe declara uma função que retorna um objeto DiaDoAno e que não possui parâmetros.)
Você deve, contudo, incluir os parênteses quando invocar explicitamente um construtor sem argumentos, como mostrado abai-
xo:
data1 = DiaDoAno( );

1 Exercícios de Autoteste 1

1. Suponha que seu programa contenha a seguinte definição de classe (com definições das funções-mem-
bros):
class SuaClasse
{
public:
SuaClasse(int novaInfo, char maisNovaInfo);
SuaClasse( );
void fazerAlgo( );
private:
int informacao;
char maisInformacao;
};
Quais das seguintes linhas é ilegal?
SuaClasse umObjeto(42, ’A’);
SuaClasse outroObjeto;
SuaClasse maisOutroObjeto( );
umObjeto = SuaClasse(99, ’B’);
umObjeto = SuaClasse( );
umObjeto = SuaClasse;
2. O que é um construtor-padrão? Toda classe tem um construtor-padrão?

CLASSE ContaBancaria
O Painel 7.2 contém a definição de uma classe que representa uma conta bancária simples inserida em um
pequeno programa de demonstração. Uma conta bancária, dessa forma, possui dois conjuntos de dados: o
184 Construtores e Outras Ferramentas

saldo bancário e a taxa de juros. Observe que representamos a conta bancária como dois valores de tipo
int, um para os reais e outro para os centavos. Isso ilustra o fato de que a representação interna dos da-
dos não precisa ser simplesmente uma variável-membro para cada conjunto conceitual de dados. Talvez pa-
reça que o saldo devesse ser representado como um valor de tipo double, em vez de dois valores int.
Entretanto, uma conta abriga um número exato de reais e centavos, e um valor de tipo double é, falando na
prática, uma quantidade aproximada. Além disso, um saldo como R$ 323,52 não é um sinal de real diante
de um valor em ponto flutuante. R$ 323,52 não pode ter mais ou menos de dois dígitos depois do ponto
decimal. Não se pode ter um saldo de R$ 323,523, e uma variável-membro do tipo double permitiria que
houvesse. Não é impossível ter uma conta com frações de centavos, mas não é o que desejamos para uma
conta bancária.
Observe que o programador que utiliza a classe ContaBancaria pode pensar no saldo como um valor de
tipo double ou como dois valores de tipo int (para reais e centavos). As funções acessor e mutator permi-
tem que o programador leia e fixe o saldo como sendo um double ou dois ints. O programador que está
usando a classe não precisa e não deve pensar em quaisquer variáveis-membros subjacentes. Isso é parte da
implementação "oculta" do programador que utiliza a classe.
Note que a função mutator setSaldo bem como os nomes dos construtores estão sobrecarregados. Observe
também que todos os construtores e funções mutator verificam os valores para garantir que sejam apropria-
dos. Por exemplo, uma taxa de juros não pode ser negativa. Um saldo pode ser negativo, mas não se pode
ter um número positivo de reais e um número negativo de centavos.
Essa classe possui funções-membros privadas: dolaresParte, centavosParte, redondo e fracao. Essas fun-
ções-membros são tornadas privadas porque são projetadas somente para ser usadas nas definições de ou-
tras funções-membros.

Painel 7.2 Classe ContaBancaria (parte 1 de 5)


1 #include <iostream>
2 #include <cmath>
3 #include <cstdlib>
4 using namespace std;
5 //Os dados consistem em dois itens: uma quantia de dinheiro para o saldo
6 //e uma porcentagem para a taxa de juros.
7 class BankAccount
8 {
9 public:
10 BankAccount(double balance, double rate);
11 //Inicializa saldo e taxa de juros de acordo com os argumentos.
12 BankAccount(int dollars, int cents, double rate);
13 //Inicializa o saldo como $dollars.cents. Para um saldo negativo, tanto
14 //os dólares quanto os centavos devem ser negativos. Inicializa a taxa de juros como uma porcentagem.
15 BankAccount(int dollars, double rate);
16 //Inicializa o saldo como $dollars.00 e
17 //inicializa a taxa de juros como uma porcentagem.
18 BankAccount( );
19 //Inicializa o saldo como $0.00 e a taxa de juros como 0.0%.
20 void update( );
21 //Pós-condição: Um ano de juros simples foi acrescentado ao saldo.
22 void input( );
23 void output( );
24 double getBalance( );
25 int getDollars( );
26 int getCents( );
27 double getRate( );//Retorna a taxa de juros como uma porcentagem.
28 void setBalance(double balance);
29 void setBalance(int dollars, int cents);
30 //Verifica se os argumentos são ambos não-negativos ou não-positivos.
Construtores 185

Painel 7.2 Classe ContaBancaria (parte 2 de 5)


31 void setRate(double newRate);
32 //Se newRate é não-negativa, torna-se a nova taxa. Caso contrário, aborta o programa.
33 Membros privados
34
35
private: /
//Uma quantia negativa é representada como dólares e centavos negativos.
36 //Por examplo, $4.50 negativo fixa accountDollars como -4 e accountCents como -50.
37 int accountDollars; //de saldo
38 int accountCents; //de saldo
39 double rate;//como uma porcentagem
40 int dollarsPart(double amount);
41 int centsPart(double amount);
42 int round(double number);

43 double fraction(double percent);


44 //Converte uma porcentagem em fração. Por exemplo, fraction(50.3) retorna 0.503.
45 };
Esta declaração provoca uma chamada ao
46 int main( ) construtor-padrão. Observe que não há parênteses.
47 {
48 BankAccount account1(1345.52, 2.3),account2;
49 cout << "conta1 inicializada da seguinte forma:\n";
50 account1.output( );
51 cout << "conta2 inicializada da seguinte forma:\n";
52 account2.output( );

53 account1 = BankAccount(999, 99, 5.5); uma chamada explícita ao construtor


54 cout << "conta atualizada:\n"; BankAccount::BankAccount
55 account1.output( );

56 cout << "Forneça novos dados para a conta2:\n";


57 account2.input( );
58 cout << "conta atualizada:\n";
59 account2.output( );

60 account2.update( );
61 cout << "Em um ano a conta2 apresentará:\n";
62 account2.output( );

63 return 0;
64 }

65 BankAccount::BankAccount(double balance, double rate)


66 : accountDollars(dollarsPart(balance)), accountCents(centsPart(balance))
67 {
68 setRate(rate);
69 }

70 BankAccount::BankAccount(int dollars, int cents, double rate)


71 {
72 setBalance(dollars, cents); Essas funções verificam se os
73 setRate(rate); dados são adequados.
74 }

75 BankAccount::BankAccount(int dollars, double rate)


76 : accountDollars(dollars), accountCents(0)
77 {
78 setRate(rate);
186 Construtores e Outras Ferramentas

Painel 7.2 Classe ContaBancaria (parte 3 de 5)


79 }

80 BankAccount::BankAccount( ): accountDollars(0), accountCents(0), rate(0.0)


81 {/*Corpo propositadamente vazio.*/}

82 void BankAccount::update( )
83 {
84 double balance = accountDollars + accountCents*0.01;
85 balance = balance + fraction(rate)*balance;
86 accountDollars = dollarsPart(balance);
87 accountCents = centsPart(balance);
88 }

89 //Utiliza iostream: Para uma melhor definição de BankAccount::input veja


90 void BankAccount::input( )
o Exercício de Autoteste 3.
91 {
92 double balanceAsDouble;
93 cout << "Forneça o saldo bancário $";
94 cin >> balanceAsDouble;
95 accountDollars = dollarsPart(balanceAsDouble);
96 accountCents = centsPart(balanceAsDouble);
97 cout << "Forneça a taxa de juros (SEM o símbolo de porcentagem): ";
98 cin >> rate;
99 setRate(rate) ;
100 }
101 //Utiliza iostream e cstdlib:
102 void BankAccount::output( )
103 {
104 int absDollars = abs(accountDollars);
105 int absCents = abs(accountCents);
106 cout << "Saldo bancário: $";
107 if (accountDollars < 0)
108 cout << "-";
109 cout << absDollars;
110 if (absCents >= 10)
111 cout << "." << absCents << endl;
112 else
113 cout << "." << ’0’ << absCents << endl;

114 cout << "Taxa: " << rate << "%\n";


115 }

116 double BankAccount::getBalance( )


117 {
118 return (accountDollars + accountCents*0.01);
119 }

120 int BankAccount::getDollars( )


121 {
122 return accountDollars; O programador que utiliza a classe não se importa se
123 } o saldo é armazenado com um real ou dois ints.

124 int BankAccount::getCents( )


125 {
126 return accountCents;
127 }

128 double BankAccount::getRate( )


Construtores 187

Painel 7.2 Classe ContaBancaria (parte 4 de 5)


129 {
130 return rate;
131 }

132 void BankAccount::setBalance(double balance)


133 {
134 accountDollars = dollarsPart(balance);
135 accountCents = centsPart(balance);
136 }

137 //Utiliza cstdlib:


138 void BankAccount::setBalance(int dollars, int cents)
139 {
140 if ((dollars < 0 && cents > 0) || (dollars > 0 && cents < 0))
141 {
142 cout << "Dados inconsistentes.\n";
143 exit(1);
144 }
145 accountDollars = dollars;
146 accountCents = cents;
147 }

148 //Utiliza cstdlib:


149 void BankAccount::setRate(double newRate)
150 {
151 if (newRate >= 0.0)
152 rate = newRate;
153 else
154 {
155 cout << "Não pode haver uma taxa de juros negativa.\n";
156 exit(1);
157 }
158 }

159 int BankAccount::dollarsPart(double amount)


160 { Esta poderia ser uma função regular em vez
161 return static_cast<int>(amount); de função-membro, mas como função-
162 } membro podemos torná-la privada.
163 //Utiliza cmath:
164 int BankAccount::centsPart(double amount)
165 {
166 double doubleCents = amount*100;
167 int intCents = (round(fabs(doubleCents)))%100;//% pode se comportar mal com negativos
168 if (amount < 0)
169 intCents = -intCents; Estas funções poderiam ser regulares em vez de funções-membros,
170 return intCents; mas como funções-membros podemos torná-las privadas.
171 }

-----
172 //Utiliza cmath: Se isso não parecer claro,
173 int BankAccount::round(double number) veja a explicação sobre round
174 { no Capítulo 3, Seção 3.2.
175 return static_cast<int>(floor(number + 0.5));
176 }
177 double BankAccount::fraction(double percent)
178 {
179 return (percent/100.0);
180 }
188 Construtores e Outras Ferramentas

Painel 7.2 Classe ContaBancaria (parte 5 de 5)


DIÁLOGO PROGRAMA-USUÁRIO
conta1 inicializada da seguinte forma:
Saldo bancário: R$ 1.345,52
Taxa de juros: 2,3%
conta2 inicializada da seguinte forma:
Saldo bancário: R$ 0,00
Taxa de juros: 0%
conta1 atualizada:
Saldo bancário: R$ 999,99
Taxa de juros: 5,5%
Forneça novos dados para a conta2:
Forneça o saldo bancário: R$ 100,00
Forneça a taxa de juros (SEM o símbolo de porcentagem): 10
conta2 atualizada:
Saldo bancário: R$ 100
Taxa de juros: 10%
Em um ano a conta2 apresentará:
Saldo bancário: R$ 110
Taxa de juros: 10%

1 Exercícios de Autoteste 1

3. A função ContaBancaria::entrada no Painel 7.2 lê o saldo da conta com um valor de tipo double.
Quando o valor é armazenado na memória do computador na forma binária, isso pode criar um peque-
no erro. Este normalmente não seria notado, e a função é boa o bastante para a classe de demonstra-
ção ContaBancaria. Gastar tempo demais em análise numérica prejudicaria a mensagem em questão.
Mesmo assim, a função entrada não é suficientemente boa para as operações bancárias. Reescreva a
função ContaBancaria::entrada para que leia uma quantia como R$ 78,96 como o int 76, e três valores
char ’.’, ’9’ e ’6’. Você pode presumir que o usuário sempre forneça dois dígitos para os centavos, como
99,00 em vez de apenas 99 e nada mais. Dica: a fórmula seguinte converterá um dígito no valor int
correspondente, como ’6’ em 6.
static_cast<int>(digito) - static_cast<int>(’0’)

■ VARIÁVEIS-MEMBROS DE TIPO CLASSE


Uma classe pode ter uma variável-membro cujo tipo é de outra classe. Em geral não é preciso fazer nada de
especial para se ter uma variável-membro de classe, mas há uma notação especial para permitir a invocação do
construtor da variável-membro dentro do construtor da classe exterior. Fornecemos um exemplo no Painel 7.3.
A classe Feriado no Painel 7.3 poderia ser utilizada por algum departamento de trânsito para ajudar a contro-
lar que feriados necessitariam de reforço na verificação dos estacionamentos (envolvendo parquímetros e bilhetes
de zona azul). É uma classe bastante simplificada. Uma classe de verdade teria mais funções-membros, mas a classe
Feriado é suficiente para ilustrar a questão.
A classe Feriado possui duas variáveis-membros. A variável-membro reforcoEstacionamento é uma variável-
membro comum do tipo simples bool. A variável-membro data é do tipo classe DiaDoAno.
Reproduzimos a seguir uma definição de construtor do Painel 7.3:
Feriado::Feriado(int dia, int mes, bool oReforco)
: data(dia, mes), reforcoEstacionamento(oReforco)
{/* Propositadamente vazio*/}

Observe que fixamos a variável-membro reforcoEstacionamento na seção de inicialização da forma usual, ou


seja, com
reforcoEstacionamento(oReforco)
Construtores 189

A variável-membro data é um membro do tipo classe DiaDoAno. Para inicializar data, precisamos invocar um
construtor da classe DiaDoAno (o tipo de data). Isso é feito na seção de inicialização com a notação similar
data(dia, mes)

Painel 7.3 Variável-membro classe (parte 1 de 2)


1 #include <iostream>
2 #include<cstdlib>
3 using namespace std;

4 class DayOfYear
5 {
6 public:
7 DayOfYear(int monthValue, int dayValue);
8 DayOfYear(int monthValue);
9 DayOfYear( );
10 void input( ); A classe DayOfYear é a mesma do
11 void output( ); Painel 7.1, mas repetimos todos os
12 int getMonthNumber( ); detalhes necessários para que você
13 int getDay( ); possa compreender esta discussão.
14 private:
15 int month;
16 int day;
17 void testDate( );
18 };

19 class Holiday
20 {
21 public:
22 Holiday( );//Inicializa como 1 de janeiro sem reforço na verificação dos estacionamentos
23 Holiday(int month, int day, bool theEnforcement);
24 void output( );
25 private: variável-membro de um tipo classe
26 DayOfYear date;
27 bool parkingEnforcement;//verdadeiro se houver reforço
28 };

29 int main( )
30 {
31 Holiday h(2, 14, true);
32 cout << "Testando a classe Feriado.\n";
33 h.output( );
Invocações de construtores
34 return 0; da classe DayOfYear.
35 }
36
37 Holiday::Holiday( ) : date(1, 1),
/
. parkingEnforcement(false)
38 {/*Propositadamente vazio*/}

39 Holiday::Holiday(int month, int day, bool theEnforcement)


40 : date(month, day), parkingEnforcement(theEnforcement)
41 {/*Propositadamente vazio*/}
42 void Holiday::output( )
43 {
44 date.output( );
45 cout << endl;
46 if (parkingEnforcement)
47 cout << "As leis de estacionamento serão reforçadas.\n";
48 else
49 cout << "As leis de estacionamento serão reforçadas.\n";
190 Construtores e Outras Ferramentas

Painel 7.3 Variável-membro classe (parte 2 de 2)


50 }

51 DayOfYear::DayOfYear(int monthValue, int dayValue)


52 : month(monthValue), day(dayValue)
53 {
54 testDate( );
55 }

56 //utiliza iostream e cstdlib:


57 void DayOfYear::testDate( )
58 {
59 if ((month < 1) || (month > 12))
60 {
61 cout << "Valor ilegal para o mês!\n";
62 exit(1);
63 }
64 if ((day < 1) || (day > 31))
65 {
66 cout << "Valor ilegal para o dia!\n";
67 exit(1);
68 }
69 }
70
71 //Utiliza iostream:
72 void DayOfYear::output( )
73 {
74 switch (month)
75 {
76 case 1:
77 cout << "Janeiro "; break;
78 case 2:
79 cout << "Fevereiro "; break; As linhas omitidas estão no Painel
80 case 3: 6.3, mas não suficientemente óbvias
81 cout << "Março "; break; para que você não precise ir até lá.
.
.
.
82 case 11:
83 cout << "Novembro "; break;
84 case 12:
85 cout << "Dezembro "; break;
86 default:
87 cout << "Erro em DayOfYear::output. Entre em contato com o fornecedor do software.";
88 }

89 cout << day;


90 }

DIÁLOGO PROGRAMA-USUÁRIO
Testando a classe Feriado.
15 de novembro
As leis de estacionamento serão reforçadas.

A notação data(dia, mes) é uma invocação do construtor da classe DiaDoAno com os argumentos dia e mes
para inicializar as variáveis-membros de data. Observe que essa notação é análoga ao modo como se declara a va-
Mais Ferramentas 191

riável data de tipo DiaDoAno. Observe também que os parâmetros do construtor da classe maior Feriado podem
ser usados na invocação do construtor da variável-membro.

7.2 Mais Ferramentas


Inteligência... é a capacidade de criar objetos artificiais, especialmente ferramentas para construir ferramentas.
Henri Bergson, A Evolução Criadora

Esta seção trata de três tópicos que, embora importantes, não se encaixavam bem antes desse ponto. Os três
tópicos são parâmetros const para classes, funções inline e membros estáticos de classe.

■ MODIFICADOR DE PARÂMETROS const


Um parâmetro chamado por referência é mais eficiente que um parâmetro chamado por valor. Um parâmetro
chamado por valor é uma variável local inicializada com o valor de seu argumento, de modo que, quando a fun-
ção é chamada, há duas cópias do argumento. Com um parâmetro chamado por referência, o parâmetro é apenas
um "guardador" de lugar que é substituído pelo argumento, então há apenas uma cópia do argumento. Para parâ-
metros de tipos simples, como int ou double, a diferença na eficiência é negligenciável, mas para parâmetros de
classe a diferença pode às vezes ser importante. Assim, pode ser interessante utilizar um parâmetro chamado por
referência em vez de um parâmetro chamado por valor para uma classe, mesmo que a função não altere o parâme-
tro.
Se você está usando um parâmetro chamado por referência e sua função não altera o valor do parâmetro, você
pode marcar o parâmetro para que o compilador saiba que o parâmetro não deve ser alterado. Para fazer isso,
acrescente o modificador const diante do tipo do parâmetro. O parâmetro é, então, chamado de parâmetro cons-
tante ou parâmetro constante chamado por referência. Por exemplo, no Painel 7.2, definimos uma classe chama-
da ContaBancaria para contas bancárias simples. Em algum programa você pode querer escrever uma função de
valor booleano para testar qual das duas contas tem maior saldo. A definição da função pode ser assim:
bool maior(ContaBancaria conta1, ContaBancaria conta2)
//Retorna true se o saldo da conta1 for maior que
//o da conta2. Caso contrário, retorna false.
{
return(conta1.getSaldo( ) > conta2.getSaldo( ));
}

Isso é perfeitamente legal. Os dois parâmetros são chamados por valor. Entretanto, seria mais eficiente e mais
comum transformar os parâmetros em parâmetros constantes chamados por referência, desta forma:
bool maior(constContaBancaria& conta1,
const ContaBancaria& conta2)
//Retorna true se o saldo da conta1 for maior que
//o da conta2. Caso contrário, retorna false.
{
return(conta1.getSaldo( ) > conta2.getSaldo( ));
}

Observe que a única diferença é que transformamos o parâmetro em chamado por referência acrescentando &
e adicionamos modificadores const. Se houver uma declaração de função, a mesma mudança precisa ser feita nos
parâmetros da declaração de função.
Parâmetros constantes são uma forma automática de verificação de erros. Se sua definição de função contiver um
erro que cause uma alteração imprevista do parâmetro constante, o compilador emitirá uma mensagem de erro.
O modificador de parâmetros const pode ser usado com qualquer tipo de parâmetro; entretanto, normalmen-
te é usado apenas com parâmetros chamados por referência para classes (e com certos parâmetros cujos argumen-
tos correspondentes são extensos, como vetores).
Suponha que você invoque uma função-membro para um objeto de uma classe, como a classe ContaBancaria
do Painel 7.2. Por exemplo:
192 Construtores e Outras Ferramentas

ContaBancaria minhaConta;
minhaConta.entrada( );
minhaConta.saida( );

A invocação da função-membro entrada muda os valores das variáveis-membros no objeto que faz a chamada
minhaConta. Assim, o objeto que faz a chamada se comporta como uma espécie de parâmetro chamado por refe-
rência; a invocação da função pode alterar o objeto que faz a chamada. Às vezes não se quer alterar as variáveis-
membros do objeto que faz a chamada. Por exemplo, a função-membro saida não deve alterar os valores das
variáveis-membros do objeto que faz a chamada. Pode-se utilizar o modificador const para dizer ao compilador
que uma invocação de função-membro não deve alterar o objeto que faz a chamada.
O modificador const se aplica a objetos que fazem a chamada do mesmo modo que aos parâmetros. Caso se
tenha uma função-membro que não deva alterar o valor de um objeto que faz a chamada, pode-se marcar a fun-
ção com o modificador const; então, o compilador emitirá uma mensagem de erro se o código de sua função al-
terar inadvertidamente o valor de um objeto que faz a chamada. No caso de uma função-membro, o const vai até
o fim da declaração de função, até antes do ponto-e-vírgula final, como mostrado a seguir:
class ContaBancaria
{
public:
...
void saida( ) const;
...

O modificador const deve ser usado tanto na declaração quanto na definição de função. Portanto, a definição
de função para saida começará assim:
void ContaBancaria::saida( ) const
{
...

O restante da definição de função será o mesmo do Painel 7.2.

USO INCONSISTENTE DE const


O uso do modificador const é uma questão de tudo ou nada. Se você usar const para um parâmetro de um
tipo particular, deve usá-lo para todos os outros parâmetros que tiverem esse tipo e que não sejam altera-
dos pela chamada de função. Além disso, se o tipo for um tipo classe, você deve usar também o modifica-
dor const para toda função-membro que não alterar o valor do objeto que faz a chamada. A razão disso
tem a ver com chamadas de função dentro de chamadas de função. Por exemplo, considere a seguinte defi-
nição da função bemvindo:
void bemvindo(const ContaBancaria& suaConta)
{
cout << "Bem-vindo ao nosso banco.\n"
<< "O status da sua conta é:\n";
suaConta.saida( );
}
Se você não acrescentar o modificador const à declaração de função para a função-membro saida, a função
bemvindo produzirá uma mensagem de erro. A função membro bemvindo não altera o objeto que faz a cha-
mada preco. Entretanto, quando o compilador processa a definição de função para bemvindo, pensará que
bemvindo altera (ou pelo menos deveria alterar) o valor de suaConta. Isso acontece porque, quando está tra-
duzindo a definição de função para bemvindo, tudo o que o compilador sabe a respeito da função-membro
saida é a declaração de função para saida. Se a declaração de função não contiver um const que diga ao
compilador que o objeto que fez a chamada não será alterado, o compilador presume que o objeto que fez
a chamada será alterado. Assim, se você utilizar o modificador const com parâmetros do tipo ContaBanca-
ria, deve utilizar const também com todas as funções-membros ContaBancaria que não alterarem os valo-
res de seus objetos que fazem as chamadas. Em particular, a declaração de função para a função-membro
saida deve incluir um const.
No Painel 7.4, reescrevemos a definição da classe ContaBancaria dada no Painel 7.2, mas dessa vez utiliza-
mos o modificador const nos lugares adequados. No Painel 7.4, acrescentamos também duas funções maior
e bemvindo, das quais já tratamos, e que possuem parâmetros constantes.
Mais Ferramentas 193

MODIFICADOR DE PARÂMETROS const


Se você colocar o modificador const antes do tipo para um parâmetro chamado por referência, o parâmetro é chamado de pa-
râmetro constante. Quando você acrescenta o const está dizendo ao compilador que aquele parâmetro não deve ser alterado.
Se você cometer um erro em sua definição da função de modo que esta altere o parâmetro constante, o compilador dará uma
mensagem de erro. Parâmetros de um tipo classe que não são alterados pela função normalmente deveriam ser parâmetros
constantes chamados por referência em vez de parâmetros chamados por valor.
Se uma função-membro não alterar o valor do objeto que faz a chamada, você pode marcar a função acrescentando o modifica-
dor const à declaração de função. Se você cometer um erro em sua definição de função, de modo que esta altere o objeto que
faz a chamada e a função esteja marcada com um const, o compilador emitirá uma mensagem de erro. const é colocado ao fi-
nal da declaração de função, antes do ponto-e-vírgula final. O cabeçalho da definição de função também deve ter um const para
ficar igual à declaração da função.
EXEMPLO
class Amostra
{
public:
Amostra( );
void entrada( );
void saida( ) const;
private:
int coisa;
double outraCoisa;
};
int compare(const Amostra& s1, const Amostra& s2);
O uso do modificador const é uma questão de tudo ou nada. Você deve usar o modificador const sempre que for apropriado
para um parâmetro de classe e sempre que for apropriado para uma função-membro da classe. Se você não usar const todas as
vezes que for apropriado para uma classe, nunca deve usá-lo para essa classe.

1 Exercícios de Autoteste 1

4. Por que seria incorreto acrescentar o modificador const, como mostrado a seguir, à declaração para a
função-membro entrada da classe ContaBancaria dada no Painel 7.2?
class ContaBancaria
{
public:
void saida( ) const;
...
5. Quais são as diferenças e semelhanças entre um parâmetro chamado por valor e um parâmetro constan-
te chamado por referência? Seguem-se declarações que ilustram a questão:
void chamadoPorValor(int x);
void chamadoPorReferenciaConstante(const int& x);
6. Dadas as definições
const int x = 17
class A
{
public:
A( );
A(int n);
int f( )const;
int g(const A& x);
private:
int i;
};
Cada uma dessas três palavras-chave const é uma promessa ao compilador, e o compilador vai executá-la.
Qual é a promessa em cada caso?
194 Construtores e Outras Ferramentas

Painel 7.4 Modificador de parâmetros const (parte 1 de 2)


1 #include <iostream>
2 #include <cmath> Esta é a classe do Painel 7.2.
3 #include <cstdlib> uitilizando o modificador const.
4 using namespace std;

5 //Os dados consistem em dois itens: uma quantia de dinheiro para o saldo
6 //e uma porcentagem para a taxa de juros.
7 class BankAccount
8 {
9 public:
10 BankAccount(double balance, double rate);
11 //Inicializa saldo e taxa de juros de acordo com os argumentos.

12 BankAccount(int dollars, int cents, double rate);


13 //Inicializa o saldo como $dollars.cents. Para um saldo negativo, tanto
14 //os dólares quanto os centavos devem ser negativos. Inicializa a taxa de juros como uma porcentagem.

15 BankAccount(int dollars, double rate);


16 //Inicializa o saldo como $dollars.00 e
17 //inicializa a taxa de juros como uma porcentagem.

18 BankAccount( );
19 //Inicializa o saldo como $0.00 e a taxa de juros como 0.0%.

20 void update( );
21 //Pós-condição: Um ano de juros simples foram acrescentados ao saldo.
22 void input( );
23 void output( ) const;
24 double getBalance( ) const;
25 int getDollars( ) const;
26 int getCents( ) const;
27 double getRate( ) const;//Retorna a taxa de juros como uma porcentagem.

28 void setBalance(double balance);


29 void setBalance(int dollars, int cents);
30 //Verifica se os argumentos são ambos não-negativos ou não-positivos.

31 void setRate(double newRate);


32 //Se newRate é não-negativo, torna-se a nova taxa. Caso contrário, aborta o programa.
33
34 private:
35 //Uma quantia negativa é representada como dólares e centavos negativos.
36 //Por exemplo, $4.50 negativo fixa accountDollars como -4 e accountCents como -50.
37 int accountDollars; //de saldo
38 int accountCents; //de saldo
39 double rate;//como uma porcentagem
40 int dollarsPart(double amount) const;
41 int centsPart(double amount) const;
42 int round(double number) const;

43 double fraction(double percent) const;


44 //Converte uma porcentagem em fração. Por exemplo, fraction(50.3) retorna 0.503.
45 };

46 //Retorna true se o saldo na account1 for maior que


47 //o da account2. Caso contrário, retorna false.
48 bool isLarger(const BankAccount& account1, const BankAccount& account2);
Mais Ferramentas 195

Painel 7.4 Modificador de parâmetros const (parte 2 de 2)


49 void welcome(const BankAccount& yourAccount);

50 int main( )
51 {
52 BankAccount account1(6543.21, 4.5), account2;
53 welcome(account1);
54 cout << "Informe os dados da conta 2:\n";
55 account2.input( );
56 if (isLarger(account1, account2))
57 cout << "conta1 é maior.\n";
58 else
59 cout << "account2 is at least as large as account1.\n";

60 return 0;
61 }
62
63 bool isLarger(const BankAccount& account1, const BankAccount& account2)
64 {
65 return(account1.getBalance( ) > account2.getBalance( ));
66 }

67 void welcome(const BankAccount& yourAccount)


68 {
69 cout << "Bem-vindo ao nosso banco.\n"
70 << "O status da sua conta é:\n";
71 yourAccount.output( );
72 }

73 //Utiliza iostream e cstdlib:


74 void BankAccount::output( ) const
75 <O resto da definição da função é o mesmo do Painel 7.2.>

76 <As outras definições de função são as mesmas do Painel 7.2, a não ser pelo fato de que const
77 é acresentado quando necessário para combinar com a declaração de função.>

DIÁLOGO PROGRAMA-USUÁRIO
Bem-vindo ao nosso banco.
O status da sua conta é:
Saldo bancário: R$ 6.543,21
Taxa de juros: 4,5%
Informe os dados da conta 2:
Informe o saldo da conta: R$ 100,00
Informe a taxa de juros (sem sinal de porcentagem): 10
conta1 é maior.

■ FUNÇÕES INLINE
Pode-se dar a definição completa de uma função-membro dentro da definição de sua classe. Tais definições
são chamadas de definições de funções inline. Essas definições inline geralmente são usadas em definições de fun-
ção bem curtas. O Painel 7.5 mostra a classe do Painel 7.4 reescrita com diversas funções inline.
As funções inline são mais do que uma variante de notação do tipo de definições de funções-membros que já
vimos. O compilador trata uma função inline de forma especial. O código para uma declaração de função inline é
inserido em cada posição em que a função é invocada. Isso economiza o gasto adicional de uma invocação de função.
196 Construtores e Outras Ferramentas

Se tudo o mais for igual, uma função inline deve ser mais eficiente que uma função definida da forma usual
e, portanto, preferível a esta. Entretanto, dificilmente (ou talvez nunca) tudo o mais é igual. As funções inline têm
a desvantagem de misturar a interface e a implementação de uma classe e, assim, infringir o princípio da encapsu-
lação. Temos, portanto, vantagens e desvantagens.
Geralmente se acredita que apenas definições de função muito curtas devem ser feitas inline. Para definições
de função longas, a versão inline pode ser menos eficiente, porque grandes trechos de código são repetidos fre-
qüentemente. Fora esta regra geral, você terá de decidir por si próprio se utiliza ou não funções inline.
Qualquer função pode ser definida como inline. Para definir uma função não-membro como inline, é só colo-
car a palavra-chave inline antes da declaração e da definição de função. Não utilizaremos nem discutiremos mais
funções não-membros inline neste livro.

1 Exercícios de Autoteste 1

7. Reescreva a definição da classe DiaDoAno fornecida no Painel 7.3 de modo que as funções getNumeroMes
e getDia sejam definidas inline.

Painel 7.5 Definições de funções inline (parte 1 de 2)


1 #include <iostream>
2 #include <cmath>
3 #include <cstdlib>
Este é o Painel 7.4 reescrito
4 using namespace std;
utilizando funções-membros inline.
5 class BankAccount
6 {
7 public:
8 BankAccount(double balance, double rate);
9 BankAccount(int dollars, int cents, double rate);
10 BankAccount(int dollars, double rate);
11 BankAccount( );
12 void update( );
13 void input( );
14 void output( ) const;

15 double getBalance( ) const { return (accountDollars + accountCents*0.01);}

16 int getDollars( ) const { return accountDollars; }

17 int getCents( ) const { return accountCents; }

18 double getRate( ) const { return rate; }

19 void setBalance(double balance);


20 void setBalance(int dollars, int cents);
21 void setRate(double newRate);
22 private:
23 int accountDollars; //de saldo
24 int accountCents; //de saldo
25 double rate;//como uma porcentagem

26 int dollarsPart(double amount) const { return static_cast<int>(amount); }

27 int centsPart(double amount) const;

28 int round(double number) const


29 { return static_cast<int>(floor(number + 0.5)); }
Mais Ferramentas 197

Painel 7.5 Definições de funções inline (parte 2 de 2)


30 double fraction(double percent) const { return (percent/100.0); }
31 };
<Funções inline não têm mais definições. As outras definições de funções estão no Painel 7.4.>

■ MEMBROS ESTÁTICOS
Às vezes você quer ter uma variável que seja compartilhada por todos os objetos de uma classe. Por exemplo,
você pode querer uma variável para contar o número de vezes que uma função-membro em particular é invocada
por todos os objetos da classe. Tais variáveis são chamadas de variáveis estáticas e podem ser utilizadas para obje-
tos da classe para se comunicarem uns com os outros ou coordenar suas ações. Tais variáveis permitem algumas
das vantagens das variáveis globais sem abrir as comportas para todos os abusos a que as verdadeiras variáveis glo-
bais convidam. Em particular, uma variável estática pode ser privada, de modo que apenas objetos da classe têm
acesso a ela diretamente.
Se uma função não tem acesso aos dados de qualquer objeto e mesmo assim você quer que a função seja um
membro da classe, você pode transformá-la em uma função estática. As funções estáticas podem ser invocadas do
modo normal, por meio de um objeto da classe que faz a chamada. Entretanto, é mais comum e claro invocar
uma função estática utilizando o nome da classe e o operador de resolução de escopo, como no seguinte exemplo:
Servidor::getOrdemDeAtendimento( )

Já que uma função estática não precisa de um objeto que faz a chamada, a definição de uma função estática
não pode utilizar nada que dependa de um objeto que faz a chamada. Uma definição de função estática não pode
utilizar nenhuma variável não-estática nem funções membros não-estáticas, a não ser que a variável ou função não-
estática possua um objeto que faz a chamada que seja uma variável local ou algum objeto de outra forma criado
na definição. Se esta última sentença parece difícil de entender, utilize apenas a regra mais simples de que a defi-
nição de uma função estática não pode utilizar nada que dependa de um objeto que faz a chamada.
O Painel 7.6 é um programa de demonstração que emprega tanto variáveis estáticas quanto funções estáticas.
Observe que as variáveis estáticas são indicadas pela palavra-chave qualificadora static no início de sua declara-
ção. Observe também que todas as variáveis estáticas são inicializadas da seguinte forma:
int Servidor::ordemDeAtendimento = 0;
int Servidor::ultimoServido = 0;
bool Servidor::abertoAgora = true;

Tal inicialização requer algumas explicações. Toda variável estática deve ser inicializada fora da definição de
classe. Além disso, uma variável estática não pode ser inicializada mais de uma vez. Como no Painel 7.6, variáveis
estáticas privadas — na realidade, todas as variáveis estáticas — são inicializadas fora da classe. Isso pode parecer
contraditório à noção de privado. Entretanto, espera-se que o autor de uma classe faça as inicializações, normal-
mente no mesmo arquivo da definição de classe. Nesse caso, nenhum programador que utiliza a classe pode ini-
cializar as variáveis estáticas, já que uma variável estática não pode ser inicializada uma segunda vez.
Observe que a palavra-chave static é empregada na declaração da função-membro, mas não na definição da
função-membro.

Painel 7.6 Membros estáticos (parte 1 de 3)


1 #include <iostream>
2 using namespace std;

3 class Server
4 {
5 public:
6 Server(char letterName);
7 static int getTurn( );
8 void serveOne( );
198 Construtores e Outras Ferramentas

Painel 7.6 Membros estáticos (parte 2 de 3)


9 static bool stillOpen( );
10 private:
11 static int turn;
12 static int lastServed;
13 static bool nowOpen;
14 char name;
15 };

16 int Server:: turn = 0;


17 int Server:: lastServed = 0;
18 bool Server::nowOpen = true;

19 int main( )
20 {
21 Server s1(’A’), s2(’B’);
22 int number, count;
23 do
24 {
25 cout << "Há quantos no seu grupo? ";
26 cin >> number;
27 cout << "A sua ordem de atendimento é: ";
28 for (count = 0; count < number; count++)
29 cout <<Server::getTurn( ) << ’ ’;
30 cout << endl;
31 s1.serveOne( );
32 s2.serveOne( );
33 } while (Server::stillOpen( ));

34 cout << "Agora o serviço é encerrado.\n";

35 return 0;
36 }
37
38 Server::Server(char letterName) : name(letterName)
39 {/*Propositadamente vazio*/}

40 int Server::getTurn( )
41 {
42 turn++; Como getTurn é estático,
43 return turn; apenas membros estáticos
44 } podem ser diferenciados aqui.
45 bool Server::stillOpen( )
46 {
47 return nowOpen;
48 }

49 void Server::serveOne( )
50 {
51 if (nowOpen && lastServed < turn)
52 {
53 lastServed++;
54 cout << "Servidor " << name
55 << " Agora está servindo " << lastServed << endl;
56 }

57 if (lastServed >= turn) //Todos foram servidos


58 nowOpen = false;
59 }
Mais Ferramentas 199

Painel 7.6 Membros estáticos (parte 3 de 3)


DIÁLOGO PROGRAMA-USUÁRIO
Há quantos no seu grupo? 3
A sua ordem de atendimento é: 1 2 3
O servidor A agora está servindo 1
O servidor B agora está servindo 2
Há quantos no seu grupo? 2
A sua ordem de atendimento é: 4 5
O servidor A agora está servindo 3
O servidor B agora está servindo 4
Há quantos no seu grupo? 0
A sua ordem de atendimento é:
O servidor A agora está servindo 5
Agora o serviço será encerrado.

O programa no Painel 7.6 é o esboço de um cenário com uma fila de clientes esperando pelo serviço e dois
servidores para atender esta única fila. Você pode imaginar diversos cenários de programação de sistema para
transformar este exemplo em algo mais concreto. Para um modelo simples, só para aprender os conceitos, pense
nos números produzidos por getOrdemDeAtendimento como aquelas senhas de papel entregues aos clientes de uma
lanchonete ou sorveteria. Os dois servidores são, então, dois garçons. Um detalhe talvez peculiar desse estabeleci-
mento é que os fregueses chegam em grupos, mas são atendidos um de cada vez (talvez para pedirem um tipo de
sanduíche ou um sabor especial de sorvete).

1 Exercícios de Autoteste 1

8. A função definida da seguinte forma poderia ser acrescentada à classe Servidor no Painel 7.6 como uma
função estática? Explique sua resposta.
void Servidor::mostraSituacao( )
{
cout << "Servindo agora " << ordemDeAtendimento << endl;
cout << "o servidor de nome " << nome << endl;
}

■ DEFINIÇÕES DE CLASSE ANINHADA E LOCAL


O material desta seção foi incluído para servir de referência e, a não ser por uma rápida referência no Capítulo
17, não é utilizado em nenhuma outra parte deste livro.
Pode-se definir uma classe dentro de uma classe. Essa classe dentro de outra classe é chamada de classe ani-
nhada. O esquema geral é óbvio:
classe ClasseExterna
{
public:
...
private:
class ClasseInterna
{
...
};
...
};

Uma classe aninhada pode ser pública ou privada. Se for privada, como no nosso exemplo, não pode ser utili-
zada fora da classe externa. Quer a classe aninhada seja pública quer privada, pode ser usada em definições de fun-
ções-membros da classe externa.
200 Construtores e Outras Ferramentas

Como a classe aninhada está no escopo da classe externa, o nome da classe aninhada, como ClasseInterna
em nosso exemplo, pode ser usado para algo mais fora da classe externa. Se a classe aninhada é pública, a classe
aninhada pode ser usada como um tipo fora da classe externa. Entretanto, fora da classe externa, o nome do tipo
da classe aninhada é ClasseExterna::ClasseInterna.
Não teremos oportunidade de utilizar essas definições de classe aninhada neste livro. No entanto, no Capítulo
17 sugerimos uma possível aplicação para classes aninhadas.2
Uma definição de classe também pode ser feita dentro de uma definição de função. Em tais casos, a classe é
chamada de classe local, já que seu significado está confinado à definição de função. Uma classe local pode não
conter membros estáticos. Não teremos a oportunidade de utilizar classes locais neste livro.

7.3 Vectors — Introdução à Standard Template Library


— Tudo bem, eu vou comer — disse Alice —, e se ficar maior,
poderei alcançar a chave; se ficar menor, passarei
por baixo da porta. Assim, de qualquer forma entrarei no jardim.
Lewis Carroll, Alice no País das Maravilhas

Vectors podem ser considerados vetores que podem crescer (e encolher) em comprimento enquanto o progra-
ma é executado. Em C++, assim que o programa cria um vetor, não pode alterar seu comprimento. Os vectors
servem ao mesmo propósito que os vetores, a não ser pelo fato de que podem mudar de comprimento enquanto
o programa é executado.
Os vectors são formados a partir de uma classe modelo na Standard Template Library (STL). Falaremos de
modelos no Capítulo 16 e da STL no Capítulo 19. Entretanto, é fácil aprender alguns usos básicos dos vectores
antes de aprender sobre modelos e a STL em detalhe. Você não precisa saber muito sobre classes para utilizar vec-
tors. Pode ler esta seção sobre vectors antes de ler o Capítulo 6. Não precisa ter lido as seções anteriores deste ca-
pítulo antes de ler esta seção.

■ FUNDAMENTOS DOS VECTORS


Como um vetor, um vector possui um tipo-base e armazena uma coleção de valores de seu tipo-base. Entre-
tanto, a sintaxe para um tipo vector e uma declaração de variável vector é diferente da sintaxe dos vetores.
Declare-se uma variável, v, para um vector com tipo-base int, da seguinte forma:
vector<int> v;

A notação vector<Tipo_Base> é uma classe modelo (template), o que quer dizer que você pode conectar
qualquer tipo com o Tipo_Base e isso produzirá uma classe de vectors com esse tipo-base. Você pode pensar nisso sim-
plesmente como a especificação do tipo-base para um vector no mesmo sentido em que se especifica um tipo-base para
um vetor. Pode-se usar qualquer tipo, inclusive tipos-classe, como o tipo-base para um vector. A notação vector<int>
é um nome de classe e, assim, a declaração anterior de v como um vector de tipo vector<int> inclui uma chamada ao
construtor-padrão da classe vector<int> que cria um vector objeto vazio (sem elementos).
Os elementos dos vectors são indexados a partir do 0, como acontece com os vetores. A notação de colchetes pode
ser usada para ler ou alterar esses elementos, exatamente como com um vetor. Por exemplo, a linha seguinte altera o
valor do iésimo elemento do vector v e, depois, apresenta como saída esse valor alterado. (i é uma variável int.)
v[i] = 42;
cout << "A resposta é " << v[i];

Existe, todavia, uma restrição quanto ao uso da notação de colchetes com vectors que a torna diferente da mesma
notação utilizada com vetores. Pode-se usar v[i] para mudar o valor do iésimo elemento. Entretanto, não se pode ini-
cializar o iésimo elemento usando v[i]; só se pode mudar um elemento que já tenha recebido algum valor. Para
acrescentar um elemento a um vector pela primeira vez, normalmente se utiliza a função-membro push_back.

2. A sugestão está na subseção do Capítulo 17 intitulada "Classes Amigas e Alternativas Similares".


Vectors — Introdução à Standard Template Library 201

Acrescentam-se elementos a um vector na ordem de posições, primeiro na posição 0, depois na posição 1, de-
pois 2, e assim por diante. A função-membro push_back acrescenta um elemento na próxima posição disponível.
Por exemplo, o trecho seguinte fornece valores iniciais aos elementos 0, 1 e 2 do vector exemplo:
vector<double> exemplo;
exemplo.push_back(0.0);
exemplo.push_back(1.1);
exemplo.push_back(2.2);

O número de elementos em um vector é chamado de tamanho do vector. O tamanho da função-membro


pode ser usado para determinar quantos elementos existem em um vector. Por exemplo, depois que o código an-
teriormente exibido tiver sido executado, exemplo.size( ) apresenta como saída 3. Você pode mandar escrever
todos os elementos atualmente no vector exemplo da seguinte forma:
for (int i = 0; i < exemplo.size( ); i++)
cout << exemplo[i] << endl;

A função size retorna um valor de tipo unsigned int, não um valor de tipo int. Esse valor retornado deve
ser automaticamente convertido no tipo int quando precisar ser de tipo int, mas alguns compiladores talvez o
avisem de que você está usando um unsigned int em um local em que se exige um int. Se você quiser segurança,
pode aplicar sempre um casting de tipo para converter o unsigned int retornado em int, ou, em casos como o
deste loop for, utilizar uma variável de controle no loop de tipo unsigned int, assim:
for (unsigned int i = 0; i < exemplo.size( ); i++)
cout << exemplo[i] << endl;

O Painel 7.7 apresenta uma demonstração simples que ilustra algumas técnicas básicas de vectors.
Há um construtor de vectors que requer um argumento de número inteiro e inicializa o número de posições
dado como o argumento. Por exemplo, se você declarar v da seguinte forma:
vector<int> v(10);

os primeiros dez elementos são inicializados com o valor 0, e v.size( ) apresentará como saída 10. Então, você
pode fixar o valor do iésimo elemento utilizando v[i] para valores de i iguais a 0 até 9. Em particular, o trecho
seguinte pode se seguir imediatamente à declaração:
for (unsigned int i = 0; i < 10; i++)
v[i] = i;

Painel 7.7 Utilizando um vector (parte 1 de 2)


1 #include <iostream>
2 #include <vector>
3 using namespace std;

4 int main( )
5 {
6 vector<int> v;
7 cout << "Forneça uma lista de números positivos.\n"
8 << "Coloque um número negativo ao final.\n";

9 int next;
10 cin >> next;
11 while (next > 0)
12 {
13 v.push_back(next);
14 cout << next << " acrescentado. ";
15 cout << "v.size( ) = " << v.size( ) << endl;
16 cin >> next;
17 }

18 cout << "Você digitou:\n";


202 Construtores e Outras Ferramentas

Painel 7.7 Utilizando um vector (parte 2 de 2)


19 for (unsigned int i = 0; i < v.size( ); i++)
20 cout << v[i] << " ";
21 cout << endl;

22 return 0;
23 }

DIÁLOGO PROGRAMA-USUÁRIO
Forneça uma lista de números positivos.
Coloque um número negativo ao final.
2 4 6 8 -1
2 acrescentado. v.size = 1
4 acrescentado. v.size = 2
6 acrescentado. v.size = 3
8 acrescentado. v.size = 4
Você digitou:
2 4 6 8

Para fixar o iésimo elemento para i maior ou igual a 10, você utilizaria push_back.
Quando se usa o construtor com um inteiro como argumento, os vectors de números são inicializados com o
valor zero do tipo do número. Se o tipo-base do vector é um tipo-classe, o construtor-padrão é utilizado para a
inicialização.
A definição de vector é dada na biblioteca vector, que a coloca na std namespace. Assim, um arquivo que
utiliza vectors incluiria o seguinte (ou algo similar):
#include <vector>
using namespace std;

VECTORS
Vectors são utilizados de forma semelhante aos vetores, mas um vector não possui um tamanho fixo. Se for necessária uma ca-
pacidade maior para armazenar outro elemento, sua capacidade é aumentada automaticamente. Os vectors são definidos na bi-
blioteca vector, que os coloca no std namespace. Assim, um arquivo que utiliza vectors incluiria as seguintes linhas:
#include <vector>
using namespace std;
A classe de vector para um dado Tipo_Base é escrita como vector<Tipo_Base>. Dois exemplos de declarações de vector:
vector<int> v; //construtor-padrão produzindo um vector vazio.
vector<UmaClasse> registro(20); //construtor de vector utiliza o
//construtor-padrão para UmaClasse para inicializar 20 elementos.
Os elementos são acrescentados a um vector por meio da função-membro push_back, como ilustrado a seguir:
v.push_back(42);
Uma vez que uma posição de elemento tenha recebido seu primeiro elemento, com push_back ou com uma inicialização via
construtor, pode-se ter acesso a essa posição de elemento por meio da notação de colchetes, exatamente como com os elemen-
tos de vetores.

UTILIZANDO COLCHETES ALÉM DO TAMANHO DO VECTOR


Se v é um vector e i é maior ou igual a v.size( ), elemento v[i] ainda não existe e precisa ser criado por
meio de push_back para acrescentar elementos até incluir a posição i. Se você tentar fixar v[i] para um i
maior ou igual a v.size( ), como em
v[i] = n;
você pode receber ou não uma mensagem de erro, mas seu programa, infalivelmente, se comportará mal em
algum momento.
Vectors — Introdução à Standard Template Library 203

IDica ATRIBUIÇÃO DE VECTORS É BEM COMPORTADA


O operador de atribuição com vectors faz uma atribuição elemento-a-elemento ao vector no lado esquerdo
do operador de atribuição (aumentando a capacidade, se necessário, e atualizando o tamanho do vector no
lado esquerdo do operador de atribuição). Assim, desde que o operador de atribuição sobre o tipo-base faça
uma cópia independente de um elemento do tipo-base, o operador de atribuição sobre o vector fará uma
cópia independente, não um alias (nome alternativo), do vector no lado direito do operador de atribuição.
Observe que, para o operador de atribuição produzir uma cópia totalmente independente do vector no lado
direito do operador de atribuição, é necessário que o operador de atribuição sobre o tipo-base faça cópias
completamente independentes. O operador de atribuição sobre um vector é apenas tão bom (ou ruim)
quanto o operador de atribuição sobre seu tipo-base.

■ QUESTÕES DE EFICIÊNCIA
Em determinado momento um vector possui uma capacidade, que é o número de elementos para os quais há
memória alocada naquele momento. A função-membro capacity( ) pode ser usada para descobrir a capacidade
de um vector. Não confunda a capacidade de um vector com o tamanho de um vector. O tamanho é o número
de elementos no vector, enquanto a capacidade é o número de elementos para os quais há memória alocada. A ca-
pacidade em geral é maior que o tamanho, e a capacidade é sempre maior ou igual ao tamanho.
Sempre que um vector esgota sua capacidade e necessita de espaço para um membro adicional, a capacidade é
automaticamente aumentada. A quantidade exata do aumento depende da implementação, mas sempre proporcio-
na uma capacidade maior do que a imediatamente necessária. Um esquema de implementação comumente usado
faz com que a capacidade dobre sempre que necessitar de aumento. Como aumentar a capacidade é uma tarefa
complexa, esse método de realocação de capacidade em grandes blocos é mais eficiente que alocar diversos blocos
pequenos.
Pode-se ignorar completamente a capacidade de um vector, e isso não terá nenhum efeito sobre o que seu
programa faz. Entretanto, se a eficiência estiver em questão, você mesmo pode querer controlar a capacidade e não
simplesmente aceitar o comportamento-padrão de dobrar a capacidade sempre que se precisa aumentá-la. Você pode
utilizar a função-membro reserve para aumentar explicitamente a capacidade de um vector. Por exemplo,
v.reserve(32);

fixa a capacidade em pelo menos 32 elementos e


v.reserve(v.size( ) + 10);

fixa a capacidade em pelo menos mais 10 do que o número de elementos atualmente no vector. Observe que você
pode confiar em v.reserve para aumentar a capacidade de um vector, mas não necessariamente para diminuir a
capacidade de um vector se o argumento for menor que a capacidade atual.
Você pode alterar o tamanho de um vector utilizando a função-membro resize. Por exemplo, a linha seguin-
te altera o tamanho de um vector para 24 elementos:
v.resize(24);

Se o tamanho anterior fosse menor que 24, os novos elementos seriam inicializados como descrevemos para o
construtor com um inteiro como argumento. Se o tamanho anterior fosse maior que 24, todos, exceto os primei-
ros 24 elementos, seriam perdidos. A capacidade é automaticamente aumentada se necessário. Utilizando resize e
reserve, você pode diminuir o tamanho e a capacidade de um vector quando não houver mais necessidade de al-
guns elementos ou de alguma capacidade.

TAMANHO E CAPACIDADE
O tamanho de um vector é o número de elementos no vector. A capacidade de um vector é o número de elementos para os
quais existe memória alocada no momento. Para um vector v, o tamanho e a capacidade podem ser recuperados com as fun-
ções-membros v.size( ) e v.capacity( ).
204 Construtores e Outras Ferramentas

1 Exercícios de Autoteste 1

9. O programa seguinte é legal? Se for, qual é a saída?


#include <iostream>
#include <vector>
using namespace std;

int main( )
{
vector<int> v(10);
int i;

for (i = 0; i < v.size( ); i++)


v[i] = i;

vector<int> copia;
copy = v;
v[0] = 42;

for (i = 0; i < copia.size( ); i++)


cout << copia[i] << " ";
cout << endl;

return 0;
}
10. Qual é a diferença entre o tamanho e a capacidade de um vector?

Resumo do Capítulo
■ Um construtor é uma função-membro de uma classe chamada automaticamente quando um objeto da
classe é declarado. Um construtor deve ter o mesmo nome da classe da qual é membro.
■ Um construtor-padrão é um construtor sem parâmetros. Você deve sempre definir um construtor-padrão
para suas classes.
■ Uma variável-membro para uma classe pode ela mesma ser de um tipo-classe. Se uma classe tiver uma va-
riável-membro classe, o construtor da variável-membro pode ser invocado na seção de inicialização do
construtor da classe exterior.
■ Um parâmetro constante chamado por referência é mais eficiente que um parâmetro chamado por valor
para parâmetros de tipo-classe.
■ Transformar em inline definições de função bastante curtas pode aumentar a eficiência do seu código.
■ Variáveis-membros estáticas são variáveis compartilhadas por todos os objetos de uma classe.
■ Classes vector possuem objetos que se comportam de maneira bem semelhante a vetores cuja capacidade de
abrigar elementos aumenta automaticamente quando é necessária uma capacidade maior.

RESPOSTAS DOS EXERCÍCIOS DE AUTOTESTE


1. SuaClasse umObjeto(42, ’A’); //LEGAL
SuaClasse outroObjeto; //LEGAL
SuaClasse maisOutroObjeto( ); //PROBLEMA
umObjeto = SuaClasse(99, ’B’); //LEGAL
umObjeto = SuaClasse( ); //LEGAL
umObjeto = SuaClasse; //ILEGAL
A linha marcada com //PROBLEMA não é estritamente ilegal, mas provavelmente não significa o que você
pensa. Se você pensa que é uma declaração de um objeto chamado maisOutroObjeto, está errado. É uma
declaração correta de uma função chamada maisOutroObjeto que requer zero argumentos e retorna um
Respostas dos Exercícios de Autoteste 205

valor de tipo SuaClasse, mas normalmente este não é o significado pretendido. Para valores práticos, tal-
vez você deva considerá-la ilegal. A forma correta de declarar um objeto chamado maisOutroObjeto de
forma a inicializá-lo com o construtor-padrão é a seguinte:
SuaClasse maisOutroObjeto;
2. Um construtor-padrão é um construtor que não requer argumentos. Nem toda classe possui um constru-
tor-padrão. Se você não definir nenhum construtor para uma classe, um construtor-padrão será automati-
camente providenciado. Por outro lado, se você definir um ou mais construtores, mas não definir um
construtor-padrão, sua classe não terá construtor-padrão.
3. A definição é mais fácil de dar se você também acrescentou uma função de ajuda privada chamada Conta-
Bancaria::digitoParaInt, como a seguir, à classe ContaBancaria.
//Utiliza iostream:
void ContaBancaria::entrada( )
{
int reais;
char ponto, digito1, digito2;
cout <<
"Informe o saldo bancário (inclua centavos mesmo se for ,00) R$";
cin >> reais;
cin >> ponto >> digito1 >> digito2;
contaReais = reais;
contaCentavos = digitoParaInt(digito1)*10 + digitoParaInt(digito2);
if (contaReais < 0)
contaCentavos = -contaCentavos;
cout << "Informe a taxa de juros (SEM o sinal de porcentagem): ";
cin >> taxa;
setTaxa(taxa);
}

int ContaBancaria::digitoParaInt(char digito)


{
return (static_cast<int>(digit) - static_cast<int>(’0’));
}
4. A função-membro entrada altera o valor do objeto que faz a chamada, e assim o compilador emitirá uma
mensagem de erro se você acrescentar o modificador const.
5. Similaridades: cada método de chamada de parâmetro protege o argumento do que chama (caller) de mudanças.
Diferenças: se o tipo é uma grande estrutura ou objeto de classe, uma chamada por valor faz uma cópia do ar-
gumento daquele que chama e, assim, utiliza mais memória que uma chamada por referência constante.
6. Em const int x = 17;, a palavra-chave const promete ao compilador que o código escrito pelo autor não
alterará o valor de x.
Na declaração int f( ) const;, a palavra-chave const é uma promessa ao compilador de que o código es-
crito pelo autor para implementar a função f não alterará nada no objeto que faz a chamada.
Em int g(const A& x);, a palavra-chave const é uma promessa ao compilador de que o código escrito
pelo autor da classe não alterará o argumento conectado com x.
7. class DiaDoAno
{
public:
DiaDoAno(int valorDia, int valorMes);
DiaDoAno(int valorMes);
DiaDoAno( );
void entrada( );
void saida( );
int getNumeroMes( ) { return mes; }
int getDia( ) { return dia; }
private:
int dia;
206 Construtores e Outras Ferramentas

int mes;
void testeData( );
};
8. Não, não pode ser uma função-membro estática porque requer um objeto que faz a chamada para o nome
da variável-membro.
9. O programa é legal. A saída é
0 1 2 3 4 5 6 7 8 9
Observe que alterar v não altera copia. Uma verdadeira cópia independente é feita com a atribuição.
copia = v;
10. O tamanho é o número de elementos em um vector, enquanto a capacidade é o número de elementos
para os quais há memória alocada. Em geral, a capacidade é maior que o tamanho.

PROJETOS DE PROGRAMAÇÃO
1. Defina uma classe chamada Mes que é um tipo de dado abstrato para um mês. Sua classe terá uma variá-
vel-membro de tipo int para representar um mês (1 para janeiro, 2 para fevereiro, e assim por diante).
Inclua todas as seguintes funções-membros: um construtor para estabelecer o mês utilizando as primeiras
três letras do nome do mês como três argumentos, um construtor para estabelecer o mês utilizando um
inteiro como argumento (1 para janeiro, 2 para fevereiro, e assim por diante), um construtor-padrão, uma
função de entrada que leia o mês como um inteiro, uma função de entrada que leia o mês como as pri-
meiras três letras do nome do mês, uma função de saída que retorne o mês como um inteiro, uma função
de saída que retorne o mês como as três primeiras letras do nome do mês e uma função-membro que re-
torne o mês seguinte como um valor do tipo Mes. Insira sua definição de classe em um programa-teste.
2. Redefina a implementação da classe Mes descrita no Projeto de Programação 1 (ou faça a definição pela
primeira vez, mas faça a implementação como descrita aqui). Desta vez, o mês é implementado como três
variáveis-membros de tipo char que armazenam as três primeiras letras do nome do mês. Insira sua defi-
nição em um programa-teste.
3. Minha mãe sempre levava um pequeno contador vermelho quando ia à mercearia. O contador era usado
para calcular a quantia de dinheiro que ela deveria pagar se comprasse tudo o que havia colocado na ces-
ta. O contador possuía um painel com quatro dígitos, botões de incremento para cada dígito e um botão
de reinício. Um indicador de alerta ficava vermelho se ultrapassasse os R$ 99,99 que ele poderia registrar.
(Isso foi há muito tempo.)
Escreva e implemente as funções-membros de uma classe Contador que simule e generalize, até certo pon-
to, o comportamento desse contador. O construtor deve criar um objeto Contador que pode contar até o
argumento do construtor. Ou seja, Contador(9999) deve proporcionar um contador que pode contar até
9999. Um contador recentemente construído exibe uma leitura de 0. A função-membro void reinicia( );
faz com que o contador retorne ao 0. A função-membro void incr1( ); incrementa o dígito da unidade
em 1, void incr10( ); incrementa o dígito das dezenas em 1 e void incr100( ); e void incr1000( );
incrementam os próximos dois dígitos. O acréscimo de qualquer taxa durante o incremento não deve exigir
nenhuma outra ação além de adicionar um número adequado aos membros dados privados. Uma função-mem-
bro bool ultrapassou( ); detecta quando o máximo foi ultrapassado. (A ultrapassagem é o resultado de incre-
mentar os membros dados privados do contador além do máximo estabelecido na construção do contador.)
Utilize esta classe para fornecer uma simulação do pequeno contador vermelho da minha mãe. Embora o
display seja um inteiro, na simulação os dois dígitos mais à direita (a ordem seguida é dos menores para
os maiores) são sempre considerados centavos e dezenas de centavos, o próximo dígito é dos reais e o
quarto dígito é das dezenas de reais.
Forneça teclas para centavos, moedas de dez centavos, reais e notas de dez reais. Infelizmente, nenhuma
escolha de teclas parece especialmente mnemônica. Uma opção é utilizar as teclas asdfo: a para os centa-
vos, seguida por um dígito de 1 a 9; s para as moedas de dez centavos, seguida por um dígito de 1 a 9; d
para os reais, seguida por um dígito de 1 a 9; e f para as notas de dez reais, mais uma vez seguida por
um dígito de 1 a 9. Cada entrada (uma das asdf seguida por um dígito de 1 a 9) é seguida pelo pressio-
namento da tecla Return. Quando o valor ultrapassar o máximo, isso será informado após cada operação.
Pode-se consultar o valor de ultrapassagem com a tecla o.
CAPÍTULO Sobrecarga de
Operador, Amigos e Referências
Sobrecarga de Operador, Amigos e Referências

Capítulo 8Sobrecarga de Operador, Amigos e Referências


As verdades eternas não serão nem verdades nem eternas se não tiverem um significa-
do novo a cada nova situação social.
Franklin D. Roosevelt, Palestra na Universidade da Pensilvânia
[20 de setembro de 1940]

INTRODUÇÃO
Este capítulo trata de diversas ferramentas para serem utilizadas na definição de classes. A
primeira ferramenta é a sobrecarga de operador, que permite que se sobrecarregue opera-
dores, como + e ==, de modo que se apliquem a objetos das classes que você define. A
segunda ferramenta é a utilização de funções amigas, funções que não são membros de
uma classe, mas continuam tendo acesso a membros privados da classe. Este capítulo tam-
bém discute como fornecer conversão automática de tipo de outros tipos de dados para as
classes que você define.
Se você ainda não leu sobre vetores (Capítulo 5), deve pular a subseção da seção 8.3
intitulada Sobrecarregando o Operador Vetor [ ], pois aborda um assunto que não fará
sentido para você que ainda não conhece os fundamentos dos vetores.

8.1 Fundamentos da Sobrecarga de Operador


Ele é um operador suave.
Citação de uma canção de Sade (letra de Sade Adu e Ray St. John)

Operadores como +, -, %, == e outros não são nada além de funções utilizadas com
uma sintaxe levemente diferente. Escrevemos x + 7 em vez de +(x, 7), mas o operador
+ é uma função que requer dois argumentos (freqüentemente chamados de operandos em
vez de argumentos) e retorna um único valor. Dessa forma, os operadores não são, na
realidade, necessários. Poderíamos utilizar +(x, 7) ou mesmo add(x, 7). Os operandos
são um exemplo do que muitas vezes chamamos de açúcar sintático: uma sintaxe leve-
mente diferente e muito apreciada pelas pessoas. Entretanto, as pessoas se sentem muito à
vontade com a sintaxe normal dos operadores, x + 7, que o C++ utiliza para tipos como
int e double. E uma linguagem de alto nível, como C++, é também uma forma de tor-
nar as pessoas à vontade com a programação de computadores. Assim, esse açúcar sintáti-
co provavelmente é uma boa idéia; pelo menos, é uma idéia bastante defendida. Em C++,
podem-se sobrecarregar os operadores, como + e ==, para que funcionem com operandos
nas classes definidas. O modo de sobrecarregar um operador é bastante similar ao de se
sobrecarregar um nome de função. Os detalhes ficarão mais claros com um exemplo.
208 Sobrecarga de Operador, Amigos e Referências

■ FUNDAMENTOS DA SOBRECARGA
O Painel 8.1 contém a definição de uma classe cujos valores são quantias monetárias, como R$ 9,99 ou R$
1.567,29. A classe tem muito em comum com a classe ContaBancaria que definimos no Painel 7.2. Representa
quantias monetárias da mesma forma, com dois ints para os reais e centavos. Possui as mesmas funções de ajuda
privadas. Seus construtores e funções de acesso e mutantes são similares aos da classe ContaBancaria. O que é
realmente novo nessa classe Dinheiro é que sobrecarregamos os sinais de mais e de menos para que pudessem ser
usados para adicionar ou subtrair dois objetos da classe Dinheiro a fim de verificar se eles representam a mesma
quantia em dinheiro. Vamos dar uma olhada nesses operadores sobrecarregados.
Pode-se sobrecarregar o operador + (e muitos outros operadores) para que aceite argumentos de um tipo-clas-
se. A diferença entre sobrecarregar o operador + e definir uma função ordinária envolve apenas uma leve mudança
na sintaxe: utiliza-se o símbolo + como o nome da função e coloca-se a palavra-chave operator antes do +. A de-
claração do operador (declaração de função) para o sinal de mais é a seguinte:
const Dinheiro operator +(const Dinheiro& quantia1, const Dinheiro& quantia2);

Os dois operandos (argumentos) são parâmetros de referência constantes do tipo Dinheiro. Os operandos po-
dem ser de qualquer tipo, desde que pelo menos um seja um tipo-classe. No caso geral, os operandos podem ser
parâmetros chamados por valor ou por referência e podem ter o modificador const ou não. Entretanto, por razões
de eficiência, chamadas constantes por referência normalmente são usadas em lugar de chamadas por valor para
classes. Nesse caso, o valor retornado é do tipo Dinheiro, mas, no caso geral, o valor retornado pode ser de qual-
quer tipo, inclusive void. O const antes do tipo retornado Dinheiro será explicado posteriormente neste capítulo.
Por enquanto, você pode ignorá-lo sem problemas.
Observe que os operadores binários sobrecarregados + e - não são operadores-membros (funções-membros) da
classe Dinheiro e, portanto, não têm acesso aos membros privados da classe Dinheiro. Este é o motivo por que a
definição para os operadores sobrecarregados utiliza funções de acesso e mutantes. Neste mesmo capítulo veremos
outras formas de sobrecarregar um operando, inclusive como um operador-membro. Cada uma das diferentes for-
mas de sobrecarregar um operador tem suas vantagens e desvantagens.
As definições dos operadores binários sobrecarregados + e - talvez sejam um pouco mais complicadas do que
você poderia esperar. Os detalhes extras estão aí para lidar com o fato de que quantias monetárias podem ser ne-
gativas.
O operador unário negativo - é discutido na subseção Sobrecarregando Operadores Unários, neste mesmo ca-
pítulo.
O operador == também é sobrecarregado para poder ser usado na comparação de dois objetos da classe Di-
nheiro. Observe que o tipo retornado é bool, de modo que == possa ser usado para fazer comparações nas formas
usuais, como um comando if-else.

Painel 8.1 Sobrecarregando um operador (parte 1 de 5)


1 #include <iostream>
2 #include <cstdlib>
3 #include <cmath>
4 using namespace std;

5 //Classe para quantias de dinheiro pelo valor atual no mercado norte-americano


6 class Money
7 {
8 public:
9 Money( );
10 Money(double amount);
11 Money(int theDollars, int theCents);
12 Money(int theDollars);
13 double getAmount( ) const;
14 int getDollars( ) const;
15 int getCents( ) const;
16 void input( ); //Lê o símbolo do dólar e a quantia.
17 void output( ) const;
Fundamentos da Sobrecarga de Operador 209

Painel 8.1 Sobrecarregando um operador (parte 2 de 5)


18 private:
19 int dollars; //Uma quantia negativa é representada como dólares e centavos negativos.
20 int cents; //Por exemplo, $4.50 negativo fica accountDollars como -4 e accountCents como -50.

21 int dollarsPart(double amount) const;


22 int centsPart(double amount) const;
23 int round(double number) const;
24 };

25 const Money operator +(const Money& amount1, const Money& amount2);


Este é o operador únario
26 const Money operator -(const Money& amount1, const Money& amount2); e é discutido na subseção
Sobrecarregando
27 bool operator ==(const Money& amount1, const Money& amount2); Operadores Unários.
28 const Money operator -(const Money& amount);

29 int main( ) Para uma explicação sobre


30 { um const em um tipo
31 Money yourAmount, myAmount(10, 9); fornecido, veja a subseção
32 cout << "Informe a quantia: "; Retornando um valor const.
33 yourAmount.input( );
34 cout << "A sua quantia é ";
35 yourAmount.output( );
36 cout << endl;
37 cout << "Minha quantia é ";
38 myAmount.output( );
39 cout << endl;
40 if (yourAmount == myAmount)
41 cout << "Nós temos a mesma quantia.\n";
42 else
43 cout << "Um de nós é mais rico.\n";

44 Money ourAmount = yourAmount + myAmount;


45 yourAmount.output( ); cout << " + "; myAmount.output( );
46 cout << " equals "; ourAmount.output( ); cout << endl;

47 Money diffAmount = yourAmount - myAmount;


48 yourAmount.output( ); cout << " - "; myAmount.output( );
49 cout << " equals "; diffAmount.output( ); cout << endl;
Observe que precisamos utilizar
50 return 0; funções de acesso e mutante.

/
51 }

52 const Money operator +(const Money& amount1, const Money& amount2)


53 {
54 int allCents1 = amount1.getCents( ) + amount1.getDollars( )*100;
55 int allCents2 = amount2.getCents( ) + amount2.getDollars( )*100;
56 int sumAllCents = allCents1 + allCents2;
57 int absAllCents = abs(sumAllCents); //O dinheiro pode ser negativo.
58 int finalDollars = absAllCents/100;
59 int finalCents = absAllCents%100;

60 if (sumAllCents < 0)
61 {
62 finalDollars = -finalDollars;
63 finalCents = -finalCents;
64 }
210 Sobrecarga de Operador, Amigos e Referências

Painel 8.1 Sobrecarregando um operador (parte 3 de 5)


65 return Money(finalDollars, finalCents); Se você ficar intrigado com
66 }
as declarações de return,
67 //Utiliza cstdlib:
veja a dica com o título
68 const Money operator -(const Money& amount1, const Money& amount2)
Um Construtor Pode
69 {
Retornar um Objeto.
70 int allCents1 = amount1.getCents( ) + amount1.getDollars( )*100;
71 int allCents2 = amount2.getCents( ) + amount2.getDollars( )*100;
72 int diffAllCents = allCents1 - allCents2;
73 int absAllCents = abs(diffAllCents);
74 int finalDollars = absAllCents/100;
75 int finalCents = absAllCents%100;

76 if (diffAllCents < 0)
77 {
78 finalDollars = -finalDollars;
79 finalCents = -finalCents;
80 }

81 return Money(finalDollars, finalCents);


82 }

83 bool operator ==(const Money& amount1, const Money& amount2)


84 {
85 return ((amount1.getDollars( ) == amount2.getDollars( ))
86 && (amount1.getCents( ) == amount2.getCents( )));
87 }

88 const Money operator -(const Money& amount)


89 {
90 return Money(-amount.getDollars( ), -amount.getCents( ));
91 }

92 Money::Money( ): dollars(0), cents(0) Se você preferir, pode transformar essas


93 {/*Corpo propositadamente vazio.*/} definições de construtor curtas em definições
inline, como explicaremos no Capítulo 7.
94 Money::Money(double amount)
95 : dollars(dollarsPart(amount)), cents(centsPart(amount))
96 {/*Corpo propositadamente vazio*/}

97 Money::Money(int theDollars)
98 : dollars(theDollars), cents(0)
99 {/*Corpo propositadamente vazio*/}
100
101 //Utiliza cstdlib:
102 Money::Money(int theDollars, int theCents)
103 {
104 if ((theDollars < 0 && theCents > 0) || (theDollars > 0 && theCents < 0))
105 {
106 cout << "Dados monetários inconsistentes.\n";
107 exit(1);
108 }
109 dollars = theDollars;
110 cents = theCents;
111 }

112 double Money::getAmount( ) const


113 {
114 return (dollars + cents*0.01);
Fundamentos da Sobrecarga de Operador 211

Painel 8.1 Sobrecarregando um operador (parte 4 de 5)


115 }

116 int Money::getDollars( ) const


117 {
118 return dollars;
119 }

120 int Money::getCents( ) const


121 {
122 return cents;
123 }

124 //Utiliza iostream e cstdlib:


125 void Money::output( ) const
126 {
127 int absDollars = abs(dollars);
128 int absCents = abs(cents);
129 if (dollars < 0 || cents < 0)//trata do caso em que dólares == 0 ou centavos == 0
130 cout << "$-";
131 else
132 cout << ’$’;
133 cout << absDollars;
134
135 if (absCents >= 10)
136 cout << ’.’ << absCents;
137 else
138 cout << ’.’ << ’0’ << absCents;
139 }

140 //Utiliza iostream e cstdlib: Para uma melhor definição da função input,
141 void Money::input( ) veja Exercício de Autoteste 3 no Capítulo 7.
142 {
143 char dollarSign;
144 cin >> dollarSign; //esperamos que sim
145 if (dollarSign != ’$’)
146 {
147 cout << "Não há o símbolo do dólar na entrada Money.\n";
148 exit(1);
149 }

150 double amountAsDouble;


151 cin >> amountAsDouble;
152 dollars = dollarsPart(amountAsDouble);
153 cents = centsPart(amountAsDouble);
154 }

155 int Money::dollarsPart(double amount) const


156 <O resto da definição é como a de BankAccount::dollarsPart no Painel 7.2.>
157 int Money::centsPart(double amount) const
158 <O resto da definição é como a de BankAccount::centsPart no Painel 7.2.>

159 int Money::round(double number) const


160 <O resto da definição é como a de BankAccount::round no Painel 7.2.>
212 Sobrecarga de Operador, Amigos e Referências

Painel 8.1 Sobrecarregando um operador (parte 5 de 5)


DIÁLOGO PROGRAMA-USUÁRIO
Informe a quantia: $123.45
A sua quantia é $123.45
Minha quantia é $10.09.
Um de nós é mais rico.
$123.45 + $10.09 igual a $133.54
$123.45 - $10.09 igual a $113.36

Se você observar a função main no programa de demonstração no Painel 8.1, verá que os operadores binários
sobrecarregados +, - e ==, são usados com objetos da classe Dinheiro da mesma forma que +, - e == são usados
com os tipos predefinidos, como int e double.
Pode-se sobrecarregar a maioria dos operadores, mas não todos. Uma forte restrição sobre a sobrecarga de um
operador é que pelo menos um operando deve ser de um tipo classe. Assim, por exemplo, pode-se sobrecarregar o
operador % para aplicá-lo a dois objetos de tipo Dinheiro ou a um objeto de tipo Dinheiro e um double, mas
não se pode sobrecarregar % para combinar dois doubles.

SOBRECARGA DE OPERADOR
Um operador (binário), como +, -, /, %, etc., é apenas uma função que é chamada com uma sintaxe diferente para a listagem
de seus argumentos. Com um operador binário, os argumentos são listados antes e depois do operador; com uma função, os
argumentos são listados entre parênteses, depois do nome da função. Uma definição de operador é escrita de modo similar a
uma definição de função, a não ser pelo fato de que a definição do operador inclui a palavra reservada operator antes do nome
do operador. Os operadores predefinidos, como +, -, etc., podem ser sobrecarregados recebendo uma nova definição para um
tipo classe. Um exemplo de sobrecarga de +, - e == é fornecido no Painel 8.1.

IDica UM CONSTRUTOR PODE RETORNAR UM OBJETO


Muitas vezes pensamos em um construtor como se fosse uma função void. Entretanto, os construtores são
funções especiais com propriedades especiais, e às vezes faz mais sentido pensar neles como retornando
um valor. Observe o comando return na definição do operador sobrecarregado + no Painel 8.1, que repeti-
mos a seguir:
return Dinheiro(finalReais, finalCentavos);
A expressão fornecida é uma invocação do construtor para Dinheiro. Embora às vezes pensemos em um
construtor como em uma função void, um construtor constrói um objeto e também se pode pensar que
forneça um objeto da classe. Se você não se sente à vontade com esse uso do construtor, talvez ajude sa-
ber que esse comando return equivale ao seguinte código, mais intrincado e menos eficiente:
Dinheiro temp;
temp = Dinheiro(finalReais, finalCentavos);
return temp;
Geralmente se chama uma expressão como Dinheiro(finalReais, finalCentavos) de objeto anônimo, já
que não é nomeado por nenhuma variável. Entretanto, continua sendo um objeto completo. Você pode até
usá-lo como objeto que faz a chamada, como na seguinte expressão:
Dinheiro(finalReais, finalCentavos).getReais( )
A expressão anterior retorna o valor int de finalReais.

1 Exercícios de Autoteste 1

1. Qual é a diferença entre um operador (binário) e uma função?


2. Suponha que você deseje sobrecarregar o operador < para aplicá-lo ao tipo Dinheiro definido no Painel
8.1. O que você precisa acrescentar à definição de Dinheiro fornecida no Painel 8.1?
3. É possível utilizar a sobrecarga de operador para mudar o comportamento de + sobre inteiros? Por que
sim ou por que não?
Fundamentos da Sobrecarga de Operador 213

■ RETORNANDO UM VALOR const


Observe os tipos retornados nas declarações para operadores sobrecarregados da classe Dinheiro no Painel 8.1.
Por exemplo, a seguinte declaração do operador positivo sobrecarregado, como aparece no Painel 8.1:
const Dinheiro operator +(const Dinheiro& quantia1, const Dinheiro& quantia2);

Esta subseção explica o const no início da linha. Mas antes de falarmos deste primeiro const, vamos ter cer-
teza de que entendemos todos os outros detalhes sobre o fornecimento de um valor. Assim, vamos primeiro con-
siderar o caso em que const não aparece na declaração nem na definição do operador positivo sobrecarregado.
Suponhamos que a declaração fosse a seguinte:
Dinheiro operator +(const Dinheiro& quantia1, const Dinheiro& quantia2);

e vejamos o que podemos fazer com o valor retornado.


Quando um objeto é retornado, por exemplo, (m1 + m2), em que m1 e m2 são do tipo Dinheiro, o objeto
pode ser usado para invocar uma função-membro, que pode alterar ou não o valor das variáveis-membros no ob-
jeto (m1 + m2). Por exemplo,
(m1 + m2).saida( );

é perfeitamente legal. Nesse caso, não altera o objeto (m1 + m2). Não obstante, se omitirmos o const antes do
tipo retornado pelo operador positivo, a linha seguinte seria legal e alteraria os valores das variáveis-membros do
objeto (m1 + m2):
(m1 + m2).entrada( );

Assim, os objetos podem ser alterados, mesmo quando não estão associados a qualquer variável. Uma maneira
de se entender isso é observar que os objetos possuem variáveis-membros e, dessa forma, possuem alguns tipos de
variáveis que podem ser alteradas.
Agora vamos presumir que tudo é como mostrado no Painel 8.1; ou seja, há um const antes do tipo retorna-
do de cada operador que retorna um objeto de tipo Dinheiro. Por exemplo, a seguir está a declaração do opera-
dor positivo sobrecarregado como aparece no Painel 8.1:
const Dinheiro operator +(const Dinheiro& quantia1, const Dinheiro& quantia2);

O primeiro const na linha é um novo uso do modificador const. Isso se chama retornando um valor como
const ou retornando um valor const. O que o modificador const significa, nesse caso, é que o objeto retornado
não pode ser alterado. Por exemplo, considere o seguinte código:
Dinheiro m1(10.99), m2(23.57);
(m1 + m2).saida( );

A invocação de saida é perfeitamente legal, porque não altera o objeto (m1 + m2). No entanto, com aquele
const antes do tipo retornado, o código seguinte produzirá uma mensagem de erro do compilador:
(m1 + m2).entrada( );

Por que você iria querer retornar um valor const? Porque ele proporciona um tipo de verificação automática
de erros. Quando você constrói (m1 + m2), não deseja alterá-lo inadvertidamente.
A princípio, essa proteção contra a mudança de um objeto pode parecer exagerada, já que se pode ter
Dinheiro m3;
m3 = (m1 + m2);

e você pode muito bem querer mudar m3. Não há problema — o código seguinte é perfeitamente legal:
m3 = (m1 + m2);
m3.entrada( );

Os valores de m3 e (m1 + m2) são dois objetos diferentes. O operador de atribuição não torna m3 o mesmo
que o objeto (m1 + m2). Em vez disso, copia os valores das variáveis-membros de (m1 + m2) nas variáveis-mem-
214 Sobrecarga de Operador, Amigos e Referências

bros de m3. Com objetos de uma classe, o operador-padrão de atribuição não torna os dois objetos o mesmo, apenas co-
pia valores das variáveis-membros de um objeto para outro.
Essa distinção é sutil mas importante. Talvez ajude a compreensão dos detalhes se você se lembrar de que uma
variável de um tipo classe e um objeto de um tipo classe não são a mesma coisa. Um objeto é um valor de um
tipo classe e pode ser armazenado em uma variável de um tipo classe, mas a variável e o objeto não são a mesma
coisa. No código
m3 = (m1 + m2);

a variável m3 e seu valor (m1 + m2) são coisas diferentes, exatamente como n e 5 são coisas diferentes em
int n = 5;

ou em
int n = (2 + 3);

Talvez leve algum tempo até que você se sinta à vontade com essa idéia de retornar valores const. Enquanto
isso, uma boa regra prática é sempre retornar tipos-classes como valores const, a não ser que tenha alguma razão
explícita para não o fazer. A maioria dos programas simples não será afetada por isso, a não ser pelo fato de que
alguns erros sutis serão assinalados.
Observe que, embora legal, é inútil retornar tipos básicos, como int, como valor const. const não tem efeito
no caso de tipos básicos. Quando uma função ou operador retorna um valor de um dos tipos básicos, como int,
double ou char, retorna o valor, como 5, 5.5 ou ’A’. Não retorna uma variável ou algo como uma variável.1 Ao
contrário de uma variável, o valor não pode ser alterado — não se pode alterar 5. Valores de um tipo básico não
podem ser alterados quer haja um const antes do tipo retornado, quer não. Por outro lado, os valores de um
tipo-classe — ou seja, objetos — podem ser alterados, já que possuem variáveis-membros, e dessa forma o modi-
ficador const exerce efeito sobre o objeto retornado.

IDica RETORNANDO VARIÁVEIS-MEMBROS DE UM TIPO-CLASSE


Quando se retorna uma variável-membro de um tipo-classe, em quase todos os casos é importante retornar
o valor-membro como valor const. Para ver por que, suponha que você não faça isso, como no exemplo
que se segue:
class Funcionario
{
public:
Dinheiro getSalario( ) { return salario; }
. . .
private:
Dinheiro salario;
. . .
};
Neste exemplo, salario é uma variável-membro privada que não deveria ser alterada a não ser por meio de
uma função de acesso da classe Funcionario. Entretanto, essa privacidade é facilmente contornável, da se-
guinte forma:
Funcionario joana;
(joana.getSalario( )).entrada( );
A feliz funcionária chamada joana pode agora digitar o salário que desejar!
Por outro lado, suponha que getSalario forneça seu valor como valor const, como se segue:
class Funcionario
{
public:
const Dinheiro getSalario( ) { return salario; }

1. A não ser que o valor retornado seja retornado por referência, mas este é um tópico que será abordado mais adiante
neste capítulo. Aqui estamos pressupondo que o valor não seja retornado por referência.
Fundamentos da Sobrecarga de Operador 215

IDica (continuação)
. . .
private:
Dinheiro salario;
. . .
};
Nesse caso, a linha seguinte provocará uma mensagem de erro do compilador.
(joana.getSalario( )).entrada( );
(A declaração ideal para getSalario deveria ser
const Dinheiro getSalario( ) const { return salario; }
mas não queremos confundir os dois tipos de const.)

1 Exercícios de Autoteste 1

4. Suponha que você omita o const no início da declaração e definição do operador positivo sobrecarrega-
do para a classe Dinheiro, de modo que o valor não seja retornado como valor const. O trecho seguin-
te é legal?
Dinheiro m1(10.99), m2(23.57), m3(12.34);
(m1 + m2) = m3;
Se a definição da classe Dinheiro é como mostra o Painel 8.1, de modo que o operador positivo retorna
seu valor como valor const, isso é legal?

■ SOBRECARREGANDO OPERADORES UNÁRIOS


Além dos operadores binários, como + em x + y, o C++ possui operadores unários, como o operador - quan-
do usado para significar o oposto. Um operador unário é um operador que requer apenas um operando (um ar-
gumento). Na linha abaixo, o operador unário - é utilizado para fixar o valor de uma variável x como igual ao
valor negativo da variável y:
x = -y;

Os operadores de incremento e decremento, ++ e --, são outros exemplos de operadores unários.


Podem-se sobrecarregar tanto operadores unários quanto operadores binários. Por exemplo, sobrecarregamos o
operador do oposto - para o tipo Dinheiro (Painel 8.1) de modo que tenha tanto uma versão com operador uná-
rio quanto binário do operador de subtração/oposto -. Por exemplo, suponha que seu programa contenha essa de-
finição de classe e o seguinte código:
Dinheiro quantia1(10), quantia2(6), quantia3;

Então, a seguinte declaração fixa o valor de quantia3 como quantia1 menos quantia2:
quantia3 = quantia1 - quantia2;

A declaração seguinte apresentará a saída R$ 4,00 na tela:


quantia3.saida( );

Por outro lado, a declaração seguinte fixará quantia3 como igual ao oposto de quantia1:
quantia3 = -quantia1;

A declaração seguinte apresentará na tela -R$ 10,00:


quantia3.saida( );

Podem-se sobrecarregar os operadores ++ e -- de maneira similar a como sobrecarregamos o operador do opos-


to no Painel 8.1. Se você sobrecarregar os operadores ++ e -- seguindo o exemplo do sinal negativo - no Painel
8.1, a definição de sobrecarga se aplicará ao operador quando usado em posição de prefixo, como em ++x e --x.
Mais adiante neste capítulo falaremos detalhadamente sobre a sobrecarga de ++ e -- e explicaremos como sobrecar-
regar esses operadores para utilizá-los na posição de sufixo.
216 Sobrecarga de Operador, Amigos e Referências

■ SOBRECARREGANDO COMO FUNÇÕES-MEMBROS


No Painel 8.1, sobrecarregamos operadores como funções independentes definidas fora da classe. Também é
possível sobrecarregar um operador como um operador-membro (função-membro). Isso é ilustrado no Painel 8.2.
Observe que, quando um operador binário é sobrecarregado como um operador-membro, há apenas um parâ-
metro, não dois. O objeto que faz a chamada serve como o primeiro parâmetro. Por exemplo, considere o seguin-
te código:
Dinheiro custo(1, 50), imposto(0, 15), total;
total = custo + imposto;

Quando + é sobrecarregado como operador-membro, na expressão custo + imposto a variável custo é o ob-
jeto que faz a chamada e imposto é o argumento único de +.
A definição do operador-membro + é fornecida no Painel 8.2. Observe a seguinte linha da definição:
int totalCentavos1 = centavos + reais*100;

As expressões centavos e reais são variáveis-membros do objeto que faz a chamada, que, nesse caso, é o pri-
meiro operando. Se essa definição for aplicada a
custo + imposto

centavos significa custo.centavos e reais significa custo.reais.


Observe que, como o primeiro operando é o objeto que chama, deve-se, na maioria dos casos, acrescentar o
modificador const ao final da declaração do operador e da definição do operador. Sempre que a invocação ao
operador não alterar o objeto que faz a chamada (que é o primeiro operando), o bom estilo manda que se acrescente
const ao final da declaração do operador e da definição do operador, como ilustrado no Painel 8.2.
Sobrecarregar um operador como variável-membro pode parecer estranho, a princípio, mas é fácil se acostumar
aos novos detalhes. Muitos especialistas aconselham a sobrecarregar os operadores sempre como operadores-mem-
bros em vez de como não-membros (como no Painel 8.1): é mais do espírito da programação orientada a objetos
e um tanto mais eficiente, já que a definição pode referenciar diretamente variáveis-membros e não precisa utilizar
funções de acesso e mutantes. Entretanto, como descobriremos mais adiante neste capítulo, sobrecarregar um ope-
rador como membro também apresenta uma desvantagem importante.

Painel 8.2 Sobrecarregando operadores como membros (parte 1 de 2)


1 #include <iostream>
2 #include <cstdlib> Este é o Painel 8.1 reescrito com
3 #include <cmath> os operadores sobrecarregados
4 using namespace std; como funções-membros.

5 //Classe para quantias de dinheiro pelo valor atual no mercado norte-americano


6 class Money
7 {
8 public:
9 Money( );
10 Money(double amount);
11 Money(int dollars, int cents);
12 Money(int dollars);
13 double getAmount( ) const;
14 int getDollars( ) const;
15 int getCents( ) const;
16 void input( ); //Lê o símbolo do dólar e a quantia.
17 void output( ) const;
18 const Money operator +(const Money& amount2) const;

------
O objeto que faz a
19 const Money operator -(const Money& amount2) const;
chamada é o primeiro
20 bool operator ==(const Money& amount2) const;
21 const Money operator -( ) const; operando.
22 private:
23 int dollars; //Uma quantia negativa é representada como dólares negativos e
Fundamentos da Sobrecarga de Operador 217

Painel 8.2 Sobrecarregando operadores como membros (parte 2 de 2)


24 int cents; //centavos negativos. $4.50 negativo é representado como -4 e -50.
25 int dollarsPart(double amount) const;
26 int centsPart(double amount) const;
27 int round(double number) const;
28 };
29 int main( )
30 {
31 <Se a função main for a mesma do Painel 8.1, a tela de diálogo será a mesma mostrada no Painel 8.1.>
32 }
33
34 const Money Money::operator +(const Money& secondOperand) const
35 {
36 int allCents1 = cents + dollars*100;
37 int allCents2 = secondOperand.cents + secondOperand.dollars*100;
38 int sumAllCents = allCents1 + allCents2;
39 int absAllCents = abs(sumAllCents); //O dinheiro pode ser negativo.
40 int finalDollars = absAllCents/100;
41 int finalCents = absAllCents%100;
42 if (sumAllCents < 0)
43 {
44 finalDollars = -finalDollars;
45 finalCents = -finalCents;
46 }

47 return Money(finalDollars, finalCents);


48 }

49 const Money Money::operator -(const Money& secondOperand) const


50 <O resto da definição é o Exercício de Autoteste 5.>

51 bool Money::operator ==(const Money& secondOperand) const


52 {
53 return ((dollars == secondOperand.dollars)
54 && (cents == secondOperand.cents));
55 }

56 const Money Money::operator -( ) const


57 {
58 return Money(-dollars, -cents);
59 }

60 <As definições de todas as outras funções-membros são as mesmas do Painel 8.1.>

IDica UMA CLASSE TEM ACESSO A TODOS OS SEUS OBJETOS


Quando se define uma função ou operador-membro, pode-se ter acesso a qualquer variável (ou função)
membro privada do objeto que faz a chamada. No entanto, você tem a permissão de fazer mais do que
isso. Você tem acesso a qualquer variável-membro privada (ou função-membro privada) de qualquer objeto
da classe definida.
Por exemplo, considere as seguintes linhas que iniciam a definição do operador positivo para a classe Di-
nheiro no Painel 8.2:
const Dinheiro Dinheiro::operator +(const Dinheiro& segundoOperando) const
{
int totalCentavos1 = centavos + reais*100;
int totalCentavos2 = segundoOperando.centavos + segundoOperando.reais*100;
218 Sobrecarga de Operador, Amigos e Referências

IDica (continuação)
Nesse caso, o operador positivo é definido como um operador-membro, então as variáveis centavos e
reais, na primeira linha do corpo da função, são as variáveis-membros do objeto que faz a chamada (que é
exatamente o primeiro operando). Entretanto, também é legal ter acesso por nome às variáveis-membros do
objeto segundoOperando, como na seguinte linha:
int totalCentavos2 = segundoOperando.centavos + segundoOperando.reais*100;
Isso é legal porque segundoOperando é um objeto da classe Dinheiro e essa linha está na definição de um
operador-membro da classe Dinheiro. Muitos programadores iniciantes pensam, erroneamente, que têm
acesso direto aos membros privados do objeto que faz a chamada e não percebem que têm acesso direto a
todos os objetos da classe definida.

1 Exercícios de Autoteste 1

5. Complete a definição do operador-membro binário — no Painel 8.2.

■ SOBRECARREGANDO A APLICAÇÃO DE FUNÇÕES ( )


O operador de chamada de função ( ) deve ser sobrecarregado como uma função-membro. Isso permite que
você utilize um objeto da classe como se fosse uma função. Se a classe UmaClasse sobrecarregou o operador de
aplicação de função para ter um argumento de tipo int e umObjeto é um objeto de UmaClasse, então umObje-
to(42) invoca o operador de chamada de função sobrecarregado ( ) com o objeto que faz a chamada umObjeto e
o argumento 42. O tipo retornado pode ser void ou qualquer outro.
O operador de chamada de função ( ) é incomum, no sentido de que permite qualquer número de argumen-
tos. Assim, você pode definir diversas versões sobrecarregadas do operador de chamada de função ( ).

SOBRECARREGANDO &&, || E O OPERADOR VÍRGULA


As versões predefinidas de && e || que funcionam para o tipo bool utilizam avaliação de curto-circuito. En-
tretanto, quando sobrecarregados esses operadores executam a avaliação completa. Isso é tão contrário ao
que a maioria dos programadores espera, que causa problemas, inevitavelmente. É melhor simplesmente não
sobrecarregar esses dois operadores.
O operador vírgula também apresenta problemas. Em seu uso normal, o operador vírgula garante avaliações
da esquerda para a direita. Quando sobrecarregado, não há garantias. O operador vírgula é outro cuja so-
brecarga é melhor evitar.

8.2 1 Funções Amigas e Conversão de Tipo Automática


Confie em seus amigos.
Sabedoria popular

As funções amigas são funções não-membros que possuem todos os privilégios das funções-membros. Antes de fa-
larmos das funções amigas em detalhes, vamos falar da conversão de tipo automática por meio de construtores, pois
isso ajuda a explicar uma das vantagens de sobrecarregar operadores (ou quaisquer funções) como funções amigas.

■ CONSTRUTORES PARA CONVERSÃO DE TIPO AUTOMÁTICA


Se sua definição de classe contém os construtores apropriados, o sistema efetuará certas conversões de tipo au-
tomaticamente. Por exemplo, se o programa contiver a definição da classe Dinheiro como no Painel 8.1 ou como
no Painel 8.2, você poderia utilizar o seguinte código em seu programa:
Dinheiro quantiaBase(100, 60), quantiaTotal;
quantiaTotal = quantiaBase + 25;
quantiaTotal.saida( );
Funções Amigas e Conversão de Tipo Automática 219

A saída seria
R$ 125,60

O código apresentado pode parecer simples e natural, mas há uma sutileza. O 25 (na expressão quantiaBase
+ 25) não é do tipo apropriado. No Painel 8.1, apenas sobrecarregamos o operador + para que ele pudesse ser
utilizado com dois valores do tipo Dinheiro. Não sobrecarregamos o + para que pudesse ser utilizado com um va-
lor do tipo Dinheiro e um inteiro. A constante 25 pode ser considerada do tipo int, mas 25 não pode ser usado
como um valor do tipo Dinheiro a não ser que a definição de classe diga ao sistema, de alguma forma, como
converter um inteiro em um valor do tipo Dinheiro. O sistema só sabe que 25 significa R$ 25,00 porque incluí-
mos um construtor que requer um único argumento de tipo int. Quando o sistema vê a expressão
quantiaBase + 25

verifica primeiro se o operador + foi sobrecarregado para a combinação de um valor de tipo Dinheiro e um intei-
ro. Como não existe tal sobrecarga, o sistema a seguir verifica se há um construtor que requer um argumento úni-
co que seja um inteiro. Se encontrar, ele o utiliza para converter o inteiro 25 em um valor de tipo Dinheiro. O
construtor de argumento único diz que 25 deve ser convertido em um objeto de tipo Dinheiro cuja variável-
membro reais é igual a 25 e cuja variável-membro centavos é igual a 0. Em outras palavras, o construtor conver-
te 25 em um objeto de tipo Dinheiro que representa R$ 25,00. (A definição do construtor está no Painel 8.1.)
Observe que esse tipo de conversão não funcionará a não ser que haja um construtor adequado. Se a classe
Dinheiro não contiver um construtor com um parâmetro de tipo int (ou de algum outro tipo número, como
long ou double), então a expressão
quantiaBase + 25

produzirá uma mensagem de erro.


Essas conversões de tipo automáticas (produzidas por construtores) parecem mais comuns e atraentes com ope-
radores numéricos sobrecarregados como + e -. Não obstante, essas conversões automáticas se aplicam exatamente
da mesma forma a argumentos de funções ordinárias, argumentos de funções-membros e argumentos de outros
operadores sobrecarregados.

OPERADORES-MEMBROS E CONVERSÕES DE TIPO AUTOMÁTICAS


Quando se sobrecarrega um operador binário como um operador-membro, os dois argumentos não são mais
simétricos. Um é um objeto que faz a chamada, e apenas o segundo "argumento" é um verdadeiro argumen-
to. Isso não só é antiestético mas também apresenta uma desvantagem bastante prática. Qualquer conver-
são de tipo automática só se aplicará ao segundo argumento. Assim, por exemplo, como observamos na
subseção anterior, o código seguinte seria legal:
Dinheiro quantiaBase(100, 60), quantiaTotal;
quantiaTotal = quantiaBase + 25;
Isso acontece porque Dinheiro tem um construtor com um argumento de tipo int e, assim, o valor 25 será
considerado um valor int que é automaticamente convertido em um valor do tipo Dinheiro.
Entretanto, se você sobrecarregar + como um operador-membro (como no Painel 8.2), não pode inverter os
dois argumentos de +. A linha seguinte será ilegal
quantiaTotal = 25 + quantiaBase;
porque 25 não pode ser um objeto que faz uma chamada. A conversão de valores int para valores de tipo
Dinheiro funciona para argumentos, mas não para objetos que fazem chamadas.
Por outro lado, se você sobrecarregar + como não-membro (como no Painel 8.1), a seguinte linha será per-
feitamente legal:
quantiaTotal = 25 + quantiaBase;
Esta é a maior vantagem de sobrecarregar um operador como um não-membro.
Sobrecarregar um operador como um não-membro proporciona a você a conversão de tipo automática de
todos os argumentos. Sobrecarregar um operador como membro lhe proporciona a eficiência de contornar as
funções de acesso e mutantes e ter acesso direto às variáveis-membros. Há um modo de sobrecarregar um
operador (e certas funções) que oferece ambas as vantagens. Chama-se sobrecarregar como uma função ami-
ga, e é nosso próximo tópico.
220 Sobrecarga de Operador, Amigos e Referências

■ FUNÇÕES AMIGAS
Se sua classe tem um conjunto completo de funções de acesso e mutantes, você pode utilizar essas funções
para definir operadores não-membros sobrecarregados (como no Painel 8.1, ao contrário do Painel 8.2). No en-
tanto, embora isso possa lhe dar acesso às variáveis-membros privadas, esse acesso pode não ser eficiente. Observe
novamente a definição do operador de adição sobrecarregado + dada no Painel 8.1. Em vez de apenas ler quatro
variáveis-membros, ela obriga o gasto de duas invocações de getCentavos e duas de getReais. Isso aumenta a ine-
ficiência e também pode tornar o código difícil de entender. A alternativa de sobrecarregar + como um membro
contorna esse problema ao preço de perda da conversão de tipo automática do primeiro operando. Sobrecarregar
o operador + como amigo permitirá que tenhamos ao mesmo tempo acesso direto às variáveis-membros e conver-
são de tipo automática para todos os operandos.
Uma função amiga de uma classe não é uma função-membro da classe, mas tem acesso aos membros privados
dessa classe (tanto às variáveis-membros privadas quanto às funções-membros privadas) exatamente como uma fun-
ção-membro. Para transformar uma função em amiga, é só lhe dar o nome de amiga na definição da classe. Por
exemplo, no Painel 8.3, reescrevemos a definição da classe Dinheiro mais uma vez. Desta vez sobrecarregamos os
operadores como amigos. Um operador ou uma função é transformado em amigo de uma classe, listando-se a de-
claração do operador ou da função na definição da classe e colocando a palavra-chave friend diante da declaração
do operador ou da função.
Um operador amigo ou função amiga tem sua declaração listada na definição da classe, exatamente como se
lista a declaração de uma função-membro, a não ser pelo fato de que se antecede a declaração com a palavra-chave
friend. Entretanto, uma amiga não é uma função-membro; em vez disso, na verdade é uma função ordinária com
acesso extraordinário aos membros dados da classe. A amiga é definida exatamente como a função ordinária. Em parti-
cular, as definições de operadores mostradas no Painel 8.3 não incluem o qualificador Dinheiro:: no cabeçalho
da função. Além disso, não se usa a palavra-chave friend na definição da função (só na declaração da função). Os
operadores amigos no Painel 8.3 são invocados da mesma forma que os operadores não-amigos, não-membros, no
Painel 8.1, e possuem conversão de tipo automática de todos os argumentos exatamente como os operadores não-
amigos, não-membros, no Painel 8.1.
Os tipos mais comuns de funções amigas são operadores sobrecarregados. Entretanto, qualquer tipo de função
pode ser transformada em função amiga.
Uma função (ou operador sobrecarregado) pode ser amigo de mais de uma classe. Para transformá-lo em um
amigo de múltiplas classes, é só fornecer a declaração da função amiga em cada classe de que se deseja que seja
amiga.
Muitos especialistas consideram que as funções amigas (e os operadores amigos) sejam, em certo sentido, não
"puras". Sentem que, no verdadeiro espírito da programação orientada a objetos, todos os operadores e funções
deveriam ser funções-membros. Por outro lado, sobrecarregar operadores como amigos proporciona a vantagem
pragmática da conversão de tipo automática em todos os argumentos e, como a declaração de operador fica den-
tro das definições de classe, proporciona pelo menos um pouco mais de encapsulação do que os operadores não-
membros, não-amigos. Apresentamos três formas de sobrecarregar operadores: como não-membros e não-amigos,
como membros e como amigos. Cabe a você decidir qual técnica prefere.

Painel 8.3 Sobrecarregando operadores como amigos (parte 1 de 2)


1 #include <iostream>
2 #include <cstdlib>
3 #include <cmath>
4 using namespace std;

5 //Classe para quantias de dinheiro pelo valor atual no mercado norte-americano


6 class Money
7 {
8 public:
9 Money( );
10 Money(double amount);
11 Money(int dollars, int cents);
12 Money(int dollars);
Funções Amigas e Conversão de Tipo Automática 221

Painel 8.3 Sobrecarregando operadores como amigos (parte 2 de 2)


13 double getAmount( ) const;
14 int getDollars( ) const;
15 int getCents( ) const;
16 void input( ); //Lê o símbolo do dólar e a quantia.
17 void output( ) const;
18 friend const Money operator +(const Money& amount1, const Money& amount2);
19 friend const Money operator -(const Money& amount1, const Money& amount2);
20 friend bool operator ==(const Money& amount1, const Money& amount2);
21 friend const Money operator -(const Money& amount);
22 private:
23 int dollars; //Uma quantia negativa é representada como dólares negativos e
24 int cents; //centavos negativos. $4.50 negativo é representado como -4 e -50.

25 int dollarsPart(double amount) const;


26 int centsPart(double amount) const;
27 int round(double number) const;
28 };

29 int main( )
30 {
31 <Se a função main for a mesma do Painel 8.1, a tela de diálogo será a mesma mostrada no Painel 8.1.>

32 }
33
34 const Money operator +(const Money& amount1, const Money& amount2)
35 {
36 int allCents1 = amount1.cents + amount1.dollars*100;
37 int allCents2 = amount2.cents + amount2.dollars*100;
38 int sumAllCents = allCents1 + allCents2;
39 int absAllCents = abs(sumAllCents); //O dinheiro pode ser negativo
40 int finalDollars = absAllCents/100;
41 int finalCents = absAllCents%100;
Observe que as funções amigas têm
42 if (sumAllCents < 0) acesso direto às variáveis-membros.
43 {
44 finalDollars = -finalDollars;
45 finalCents = -finalCents;
46 }

47 return Money(finalDollars, finalCents);


48 }

49 const Money operator -(const Money& amount1, const Money& amount2)


50 <A definição completa está no Exercício de Autoteste 7.>

51 bool operator ==(const Money& amount1, const Money& amount2)


52 {
53 return ((amount1.dollars == amount2. dollars)
54 && (amount1.cents == amount2. cents ));
55 }

56 const Money operator -(const Money& amount)


57 {
58 return Money(-amount.dollars , -amount. cents );
59 }
<As definições de todas as outras funções-membros são as mesmas do Painel 8.1.>
60
222 Sobrecarga de Operador, Amigos e Referências

FUNÇÕES AMIGAS
Uma função amiga de uma classe é uma função ordinária, a não ser pelo fato de que tem acesso aos membros privados de ob-
jetos dessa classe. Para tornar uma função amiga de uma classe, você deve listar a declaração de função da função amiga na de-
finição de classe. A declaração de função é precedida pela palavra-chave friend. A declaração de função pode ser colocada na
seção privada ou na seção pública, mas será uma função pública em ambos os casos; portanto, o programa fica mais claro se
você a listar na seção pública.
SINTAXE DE UMA DEFINIÇÃO DE CLASSE COM FUNÇÕES AMIGAS
class Nome_da_Classe
{ Não é preciso listar as funções amigas
public: primeiro. Pode-se misturar a ordem das
friend Declaracao_para_Funcao_Amiga_1 declarações.
friend Declaracao_para_Funcao_Amiga_2
.
.
.
Declaracoes_da_Funcao_Membro
private:
Declaracoes_Membro_Privadas
};
EXEMPLO
class TanqueCheio
{
public:
friend void encheInferior(TanqueCheio& t1, TanqueCheio& t2);
//Enche o tanque até o nível inferior de combustível, ou t1 se os dois forem iguais.

TanqueCheio(double aCapacidade, double oNivel);


TanqueCheio( );
void input( );
void output( ) const;
private:
double capacidade; //em litros
double nivel;
};
Uma função amiga não é uma função-membro. Uma função amiga é definida e chamada da mesma forma que uma função ordi-
nária. Não se utiliza o operador ponto em uma chamada a uma função amiga, e não se utiliza um qualificador de tipo na defi-
nição de uma função amiga.

COMPILADORES SEM AMIGAS


Em alguns compiladores de C++, as funções amigas simplesmente não funcionam como deveriam. Pior ain-
da, podem funcionar algumas vezes e outras não. Nesses compiladores, as funções amigas nem sempre têm
acesso a membros privados da classe, como deveriam ter. Presumivelmente, isso será consertado em ver-
sões posteriores desses compiladores. Enquanto isso, você terá de contornar esse problema. Se você tiver
um desses compiladores em que as funções amigas não funcionam, deve utilizar funções de acesso para de-
finir funções não-membros e operadores sobrecarregados ou sobrecarregar operadores como membros.

■ CLASSES AMIGAS
Uma classe pode ser amiga de outra classe da mesma forma que uma função pode ser amiga de uma classe. Se
a classe F é amiga da classe C, então toda função-membro da classe F é uma amiga da classe C. Para transformar
uma classe em amiga de outra, você deve declarar a classe amiga como amiga dentro da outra classe.
Quando uma classe é amiga de outra classe, geralmente há uma referência de uma classe à outra em suas defi-
nições de classe. Isso requer que você inclua uma declaração antecipada para a classe definida em segundo lugar,
como ilustrado no esboço que segue este parágrafo. Observe que a declaração antecipada é apenas o cabeçalho da
definição de classe seguido de um ponto-e-vírgula.
Referências e Mais Operadores Sobrecarregados 223

Se você quer que a classe F seja amiga da classe C, deve escrever algo desse tipo:
class F; //declaração antecipada
class C
{
public:
...
friend class F;
...
};

class F
{
...

Exemplos completos utilizando classes amigas são fornecidos no Capítulo 17. Não utilizaremos classes amigas
até então.

1 Exercícios de Autoteste 1

6. Qual é a diferença entre uma função amiga de uma classe e uma função-membro de uma classe?
7. Complete a definição do operador amigo de subtração — no Painel 8.3.
8. Suponha que você deseje sobrecarregar o operador < para aplicá-lo ao tipo Dinheiro definido no Painel
8.3. O que você precisa acrescentar à definição de Dinheiro fornecida nesse painel?

8.3 1 Referências e Mais Operadores Sobrecarregados


Não confunda a lua com o dedo que a aponta.
Ditado Zen

Esta seção trata de assuntos especializados, mas importantes, a respeito da sobrecarga, inclusive a sobrecarga do
operador de atribuição e dos operadores <<, >>, [ ], ++ e --. Como é necessário entender o fornecimento de uma
referência para sobrecarregar corretamente alguns desses operadores, também tratamos desse tópico.

REGRAS A RESPEITO DA SOBRECARGA DE OPERADORES


■ Quando se sobrecarrega um operador, pelo menos um parâmetro (um operando) do operador sobrecarregado resultante
deve ser de um tipo classe.
■ A maioria dos operadores pode ser sobrecarregada como um membro da classe, ou um não-membro, não-amigo.
■ Os seguintes operadores só podem ser sobrecarregados como membros (não-estáticos) da classe: =, [ ], -> e ( ).
■ Não se pode criar um novo operador. Tudo o que se pode fazer é sobrecarregar operadores existentes, como +, -, *, /, %,
etc.
■ Não se pode alterar o número de argumentos que um operador requer. Por exemplo, não se pode mudar % de um operador
binário para um unário quando se sobrecarrega %; não se pode mudar ++ de um operador unário para um binário ao so-
brecarregá-lo.
■ Não se pode alterar a precedência de um operador. Um operador sobrecarregado tem a mesma precedência que a versão or-
dinária do operador. Por exemplo, x*y + z sempre significa (x*y) + z, mesmo que x, y e z sejam objetos e os operadores
+ e * tenham sido sobrecarregados para as classes adequadas.
■ Os seguintes operadores não podem ser sobrecarregados: o operador ponto (.), o operador de resolução de escopo (::), sizeof,
?: e o operador .*, que não será discutido neste livro.
■ Um operador sobrecarregado não pode ter argumentos-padrão.
224 Sobrecarga de Operador, Amigos e Referências

■ REFERÊNCIAS
Uma referência é o nome de uma posição de armazenamento.2 Pode-se ter uma referência independente,
como no seguinte exemplo:
int roberto;
int& beto = roberto;

Isso torna beto uma referência à posição de armazenamento da variável roberto, que transforma beto em um
nome alternativo, apelido (alias) da variável roberto. Qualquer alteração em beto também será feita em roberto.
Dito dessa forma, parece que uma referência independente não passa de uma forma de tornar seu código confuso
e colocar você em encrenca. Na maioria das vezes, uma referência independente só causa confusão, embora existam al-
guns poucos casos em que pode ser útil. Não falaremos mais em referências independentes nem as utilizaremos.
Como você deve desconfiar, as referências são utilizadas para implementar o mecanismo de parâmetros chama-
dos por referência. Assim, o conceito não é totalmente novo para você, embora o termo referência seja.
Estamos interessados em referências porque retornar uma referência permitirá que se sobrecarreguem certos
operadores de um modo mais natural. Retornar uma referência pode ser encarado como algo como retornar uma
variável ou, mais precisamente, um nome alternativo para uma variável. Os detalhes sintáticos são simples. Acres-
centa-se um & ao tipo retornado. Por exemplo:
double& amostraFuncao(double& variavel);

Já que um tipo como double& é um tipo diferente de double, você deve usar o & tanto na declaração da fun-
ção quanto na definição. A expressão fornecida deve ser algo com uma referência, como uma variável do tipo
apropriado. Não pode ser uma expressão, como X + 5. Embora muitos compiladores permitam que você o faça
(com resultados infelizes), você também não deve retornar uma variável local, porque estaria gerando um nome al-
ternativo para uma variável e imediatamente a destruindo. Um exemplo trivial da definição de função é
double& amostraFuncao(double& variavel)
{
return variavel;
}

Claro que esta é uma função bastante inútil, até mesmo perigosa, mas ilustra a idéia. Por exemplo, o código
seguinte apresentará como saída 99 e depois 42:
double m = 99;
cout << amostraFuncao(m) << endl;
amostraFuncao(m) = 42;
cout << m << endl;

Só estaremos retornando uma referência quando definirmos certos tipos de operadores sobrecarregados.

L-VALUES E R-VALUES
O termo l-value é empregado para algo que pode aparecer ao lado esquerdo de um operador de atribuição. O termo r-value é
empregado para algo que pode aparecer ao lado direito de um operador de atribuição.
Se você quiser que o objeto retornado por uma função seja um l-value, ele deve ser retornado por referência.

RETORNANDO UMA REFERÊNCIA A CERTAS VARIÁVEIS-MEMBROS


Quando uma função-membro retorna uma variável-membro e essa variável-membro é de algum tipo-classe,
normalmente ela não deveria ser fornecida por referência. Por exemplo, considere
class A

2. Se você conhece ponteiros, notará que a referência se parece com um ponteiro. Uma referência é, em essência, mas não
exatamente, um ponteiro constante. Existem diferenças entre ponteiros e referências, e os dois não são completamente
intercambiáveis.
Referências e Mais Operadores Sobrecarregados 225

(continuação)
{
public:
const AlgumaClasse getMembro( ) { return membro; }
...
private:
AlgumaClasse membro;
...
};
em que AlgumaClasse é, obviamente, um tipo classe. A função getMembro não deve retornar uma referência,
mas sim retornar um valor const, como fizemos no exemplo.
O problema de retornar uma referência a uma variável-membro de tipo classe é o mesmo que descrevemos
quanto a retornar a variável-membro como valor não-const na seção "Dica" deste capítulo intitulada Retor-
nando Variáveis-Membros de um Tipo-Classe. Quando se retorna uma variável-membro que é ela mes-
ma de um tipo-classe, normalmente ela deve ser fornecida como valor const. (Cada uma dessas regras
possui raras exceções.)

■ SOBRECARREGANDO >> E <<


Os operadores >> e << podem ser sobrecarregados de modo que sejam usados para a entrada e saída de obje-
tos das classes que você define. Os detalhes não são muito diferentes dos que já vimos para outros operadores,
mas há algumas novas sutilezas.
O operador de inserção << que utilizamos com cout é um operador binário bem semelhante a + ou -. Por
exemplo, considere o exemplo:
cout << "Ei, você aí.\n";

O operador é <<, o primeiro operando é o objeto predefinido cout (da biblioteca iostream), e o segundo
operando é o valor string "Ei, você aí.\n". O objeto predefinido cout é do tipo ostream e, portanto, você pode
sobrecarregar <<, o parâmetro que recebe cout será do tipo ostream. Pode-se alterar qualquer um dos dois ope-
randos para <<. Quando estudarmos a E/S de arquivos no Capítulo 12, você verá como criar um objeto de tipo
ostream que envie saída para um arquivo. (Esses objetos de E/S de arquivos, assim como os objetos cin e cout
são chamados de streams, e é por isso que o nome da biblioteca é ostream.) A sobrecarga que criamos, tendo cout
em mente, funcionará também para a saída de arquivos sem qualquer alteração na definição do sobrecarregado <<.
Em nossas definições anteriores da classe Dinheiro (do Painel 8.1 ao 8.3), utilizamos a função-membro saida
para enviar à saída valores do tipo Dinheiro. Isso é adequado, mas seria melhor se pudéssemos simplesmente uti-
lizar o operador de inserção << para enviar à saída valores do tipo Dinheiro, como no exemplo:
Dinheiro quantia(100);
cout << "Eu tenho " << quantia << " em minha carteira.\n";

em vez de ter de utilizar a função-membro saida, como mostrado a seguir:


Dinheiro quantia(100);
cout << "Eu tenho ";
quantia.saida( );
cout << " em minha carteira.\n";

Um problema de se sobrecarregar o operador << é decidir qual valor deve ser retornado, se é que algum deve
sê-lo, quando << é utilizado em uma expressão como a seguinte:
cout << quantia

Os dois operandos na expressão acima são cout e quantia, e avaliar a expressão fará com que o valor de
quantia seja escrito na tela. Mas, se << é um operador como + ou -, a expressão acima também deveria retornar
algum valor. Afinal, expressões com outros operandos, como n1 + n2, retornam valores. Mas qual quantia cout<<
retorna? Para obter a resposta a esta questão, precisamos olhar para uma expressão mais complexa, envolvendo <<.
Consideremos a seguinte expressão, que envolve a avaliação de cadeias de expressões utilizando <<:
226 Sobrecarga de Operador, Amigos e Referências

cout << "Eu tenho " << quantia << " em minha carteira.\n";
Se você acha que o operador << é análogo a outros operadores, como +, então a linha acima deveria ser (e de
fato é) equivalente à seguinte:
( (cout << "Eu tenho ") << quantia ) << " em minha carteira.\n";

Que valor << deveria retornar para que a expressão acima fizesse sentido? A primeira parte avaliada é a subex-
pressão:
(cout << "Eu tenho ")

Se tudo estiver funcionando, a subexpressão acima deve retornar cout, para que o cálculo possa continuar:
( cout << quantia ) << " em minha carteira.\n";

E se tudo continuar funcionando, (cout << quantia) também deve retornar cout para que o cálculo possa continuar:
cout << " em minha carteira.\n";

Isso é ilustrado no Painel 8.4. O operador << deve retornar seu primeiro argumento, que é do tipo ostream
(o tipo de cout).
Assim, a declaração para o operador sobrecarregado << (para utilizar com a classe Dinheiro) deveria ser:
class Dinheiro
{
public:
. . .
friend ostream& operator <<(ostream& outs, const Dinheiro& quantia);

Painel 8.4 << como Operador


cout << "Tenho " << amount << " em minha bolsa.\n";

significa o mesmo que

((cout << "Tenho ") << amount) << " em minha bolsa.\n";

e é calculado na seguinte forma:

Primeiro se calcula (cout << "Tenho "), que retorna cout:


((cout << "Tenho ") << amount) << " em minha bolsa.\n";

7 A string "Tenho" é apresentada à saída.


(cout << amount) << " em minha bolsa.\n";

Então se calcula (cout << amount), que retorna cout:

(cout << amount) << " em minha bolsa.\n";

7 O valor de amount é apresentado à saída.


cout << " em minha bolsa.\n";

Então se calcula cout << " em minha bolsa.\n", que retorna cout:

cout << " em minha bolsa.\n";

A string "em minha bolsa.\n" é apresentada a


cout;
Como não há operadores <<, o processo termina.
Referências e Mais Operadores Sobrecarregados 227

Uma vez que houvermos sobrecarregado o operador de inserção (saída) <<, não precisaremos mais da função-
membro saida e apagaremos saida de nossa definição da classe Dinheiro. A definição do operador sobrecarrega-
do << é bastante similar à função-membro saida. Eis um esboço da definição para o operador sobrecarregado:
ostream& operator <<(ostream& saidaStream, const Dinheiro& quantia)
{
<Esta parte é igual ao corpo de
Dinheiro::saida que está no Painel 8.1 (a não ser
pelo fato de reais ter sido substituído por quantia.reais
e centavos por quantia.centavos).>

return saidaStream;

Observe que o operador retorna uma referência.


O operador de extração >> é sobrecarregado de forma análoga à que descrevemos para o operador de inserção
<<. Entretanto, com o operador de extração (entrada) >>, o segundo argumento será o objeto que recebe o valor
de entrada, então o segundo parâmetro deve ser um parâmetro comum chamado por referência. Eis um esboço da
definição para o operador de extração sobrecarregado >>:
istream& operator >>(istream& entradaStream, Dinheiro& quantia)
{
<Esta parte é igual ao corpo de
Dinheiro::saida que está no Painel 8.1 (a não ser
pelo fato de reais ter sido substituído por quantia.reais
e centavos por quantia.centavos).>

return entradaStream;
}

As definições completas dos operadores sobrecarregados << e >> são dadas no Painel 8.5, em que reescrevemos
a classe Dinheiro mais uma vez. Dessa vez, reescrevemos a classe para que os operadores << e >> fossem sobrecar-
regados para permitir seu uso com valores de tipo Dinheiro.
Observe que não se pode realmente sobrecarregar >> ou << como operadores-membros. Se << e >> devem
funcionar como queremos, o primeiro operando (primeiro argumento) deve ser cout ou cin (ou algum stream de
arquivo de E/S). Mas se queremos sobrecarregar os operadores como membros, digamos, da classe Dinheiro, en-
tão o primeiro operando terá de ser o objeto que faz a chamada e, assim, terá de ser de tipo Dinheiro, e isso não
permitirá a definição dos operadores de modo que se comportem normalmente para >> e <<.

Painel 8.5 Sobrecarregando << e >> (parte 1 de 3)


1 #include <iostream>
2 #include <cstdlib>
3 #include <cmath>
4 using namespace std;

5 //Classe para quantias de dinheiro pelo valor atual no mercado norte-americano


6 class Money
7 {
8 public:
9 Money( );
10 Money(double amount);
11 Money(int theDollars, int theCents);
12 Money(int theDollars);
13 double getAmount( ) const;
14 int getDollars( ) const;
15 int getCents( ) const;
16 friend const Money operator +(const Money& amount1, const Money& amount2);
228 Sobrecarga de Operador, Amigos e Referências

Painel 8.5 Sobrecarregando << e >> (parte 2 de 3)


17 friend const Money operator -(const Money& amount1, const Money& amount2);
18 friend bool operator ==(const Money& amount1, const Money& amount2);
19 friend const Money operator -(const Money& amount);
20 friend ostream& operator <<(ostream& outputStream, const Money& amount);
21 friend istream& operator >>(istream& inputStream, Money& amount);
22 private:
23 int dollars; //Uma quantia negativa é representada como dólares negativos e
24 int cents; //centavos negativos. $4.50 é representado como -4 e -50.

25 int dollarsPart(double amount) const;


26 int centsPart(double amount) const;
27 int round(double number) const;
28 };

29 int main( )
30 {
31 Money yourAmount, myAmount(10, 9);
32 cout << "Digite uma quantia de dinheiro: ";
33 cin >> yourAmount;
34 cout << "A sua quantia é " << yourAmount << endl;
35 cout << "Minha quantia é " << myAmount << endl;
36
37 if (yourAmount == myAmount)
38 cout << "Nós temos a mesma quantia.\n";
39 else
40 cout << "Um de nós é mais rico.\n";

41 Money ourAmount = yourAmount + myAmount;


42 cout << yourAmount << " + " << myAmount

>
43 << " igual a " << ourAmount << endl; Como << fornece uma referência, pode-se
encadear <<. Dessa forma, >> pode ser
44 Money diffAmount = yourAmount - myAmount; encadeado de maneira semelhante.
45 cout << yourAmount << " - " << myAmount
46 << " igual a " << diffAmount << endl;

47 return 0;
48 }

<As definições das outras funções-membros são as mesmas do Painel 8.1.


As definições dos outros operadores sobrecarregados são as mesmas do Painel 8.3.>
49 ostream& operator <<(ostream& outputStream, const Money& amount)
50 {
51 int absDollars = abs(amount.dollars);
52 int absCents = abs(amount.cents); Na função main, cout é
53 if (amount.dollars < 0 || amount.cents < 0) conectada a output Stream.
54 //trata do caso em que dólares == 0 ou centavos ==Se0 desejar um outro algoritmo de entrada, veja
55 outputStream << "$-"; o Exercício de Autoteste 3 no Capítulo 7.
56 else
57 outputStream << ’$’;
58 outputStream << absDollars;

59 if (absCents >= 10)


60 outputStream << ’.’ << absCents;
61 else
62 outputStream << ’.’ << ’0’ << absCents;

63 return outputStream; Fornece uma referência.


64 }
Referências e Mais Operadores Sobrecarregados 229

Painel 8.5 Sobrecarregando << e >> (parte 3 de 3)


65
66 //Utiliza iostream e cstdlib:
67 istream& operator >>( istream& inputStream, Money& amount)
68 {
69 char dollarSign;
70 inputStream >> dollarSign; //esperamos que sim
Na função main, cin é conectado a
71 if (dollarSign != ’$’)
inputStream.
72 {
73 cout << "Não há símbolo de dólar na entrada Money.\n";
74 exit(1);
75 } Como este não é um operador-membro, é
preciso especificar um objeto de chamada
76 double amountAsDouble; para as funções-membros de Money.
77 inputStream >> amountAsDouble;
78 amount.dollars = amount.dollarsPart(amountAsDouble);
79 amount.cents = amount.centsPart(amountAsDouble);

80 return inputStream; Fornece uma referência.


81 }

DIÁLOGO PROGRAMA-USUÁRIO
Enter an amount of money: $123.45
Your amount is $123.45
My amount is $10.09.
One of us is richer.
$123.45 + $10.09 equals $133.54
$123.45 - $10.09 equals $113.36

1 Exercícios de Autoteste 1

9. No Painel 8.5, a definição do operador sobrecarregado << contém linhas como a seguinte:
saidaStream << "R$-";
Isso não é circular? Não estamos definindo << em termos de <<?
10. Por que não podemos sobrecarregar << ou >> como operadores-membros?
11. Apresentamos, a seguir, a definição de uma classe chamada Porcentagem. Objetos do tipo Porcentagem
representam porcentagens como 10% ou 99%. Dê as definições dos operadores sobrecarregados >> e
<< para que possam ser utilizados para entrada e saída com objetos da classe Porcentagem. Presuma
que a entrada sempre consiste em um inteiro seguido pelo caractere ’%’, como em 25%. Todas as por-
centagens são números inteiros e são armazenadas na variável-membro int chamada valor. Você não
precisa definir os outros operadores sobrecarregados nem o construtor. Defina apenas os operadores so-
brecarregados >> e <<.
#include <iostream>
using namespace std;
class Porcentagem
{
public:
friend bool operator ==(const Porcentagem& primeiro,
const Porcentagem& segundo);
friend bool operator <(const Porcentagem& primeiro,
const Porcentagem& segundo);
Porcentagem( );
friend istream& operator >>(istream& entradaStream,
Porcentagem& umaPorcentagem);
friend ostream& operator <<(ostream& saidaStream,
230 Sobrecarga de Operador, Amigos e Referências

Exercícios de Autoteste (continuação)


const Porcentagem& umaPorcentagem);
//Normalmente haveria outros membros e amigos.
private:
int valor;
};

SOBRECARREGANDO >> E <<


Os operadores de entrada e saída >> e << podem ser sobrecarregados como outros operadores. Se você quer que os opera-
dores se comportem como esperado com cin, cout e arquivos de E/S, então o valor retornado deve ser de tipo istream para a
entrada e ostream para a saída, e o valor deve ser retornado por referência.
DECLARAÇÕES
class Nome_Da_Classe
{
. . .
public:
. . .
friend istream& operator >>(istream& Parametro_1,
Nome_Da_Classe& Parametro_2);

friend ostream& operator <<(ostream& Parametro_3,


const Nome_Da_Classe& Parametro_4);
. . .
Os operadores não precisam ser amigos, mas não podem ser membros da classe-alvo de entrada ou saída.
DEFINIÇÕES
istream& operator >>(istream& Parametro_1,
Nome_Da_Classe& Parametro_2)
{
. . .
}
ostream& operator <<(ostream& Parametro_3,
const Nome_Da_Classe& Parametro_4)
{
. . .
}
Se você possui suficientes funções de acesso e mutantes, pode sobrecarregar >> e << como funções não-amigas. Entretanto,
é natural e mais eficiente defini-las como amigas.

IDica QUE MODO DE VALOR RETORNADO UTILIZAR


Uma função pode retornar um valor de tipo T em quatro formas diferentes:
Por valor simples, como na declaração de função T f( );
Por valor constante, como na declaração de função const T f( );
Por referência, como na declaração de função T& f( );
Por referência const, como na declaração de função const T& f( );
Não existe consenso sobre quando utilizar cada uma delas. Assim, não espere muita consistência em seu
uso. Mesmo quando um autor ou programador tem uma política clara, raramente consegue segui-la sem ex-
ceções. Ainda assim, alguns pontos são claros.
Se você vai retornar um tipo simples, como int ou char, não há razão para utilizar um const quando se re-
torna por valor ou por referência. Assim, os programadores em geral não usam um const no tipo retornado
quando é um tipo simples. Se você quer que o valor simples retornado possa ser um l-value, ou seja, possa
constar no lado esquerdo de uma declaração de atribuição, forneça por referência; de outra forma, forneça o
tipo simples por valor simples. Tipos-classe não são tão simples. O restante desta discussão se aplica ao
fornecimento de um objeto de um tipo-classe.
Referências e Mais Operadores Sobrecarregados 231

IDica (continuação)
A decisão de se retornar ou não por referência tem a ver com sua vontade ou não de poder utilizar o obje-
to retornado como um l-value. Se você quer que o valor simples retornado possa ser um l-value, ou seja,
possa ser utilizado no lado esquerdo de um operador de atribuição, você deve retornar por referência e, as-
sim, deve utilizar um caractere de "e" comercial, &, junto ao tipo retornado.
O fornecimento de uma variável local (ou outro objeto de vida curta) por referência, com ou sem um const,
pode causar problemas e deve ser evitado.
Para tipos-classe, as duas especificações de tipo retornado const T e const T& são bastante similares. Am-
bas significam que não se pode alterar o objeto retornado invocando alguma função mutante diretamente
sobre o objeto retornado, como em
f( ).mutante( );
O valor retornado ainda pode ser copiado para outra variável com um operador de atribuição e essa outra
variável pode receber a aplicação de uma função mutante. Se você estiver em dúvida entre const T& e
const T, utilize const T (sem o "e" comercial). Um const T& talvez seja um pouco mais eficiente que um
const T.3 Todavia, a diferença normalmente não é tão importante e a maioria dos programadores utiliza
const T em vez de const T& como especificação para o tipo retornado. Como já observado, const T& às
vezes causa problemas.
O resumo seguinte pode ser útil. Presume-se que T seja de tipo-classe. Só falaremos de construtores de có-
pia no Capítulo 10, mas incluímos detalhes a respeito deles como referência. Se você ainda não leu o Capí-
tulo 10, ignore todas as referências a construtores de cópia.
Se uma função-membro pública retorna uma variável-membro de classe privada, deve sempre ter um const
junto ao tipo retornado, como explicamos na seção "Armadilha" deste capítulo, intitulada Retornando Va-
riáveis-Membros de um Tipo Classe. (Uma exceção a esta regra é que os programadores normalmente
retornam um valor de tipo string por valor ordinário, não por valor const. Isso talvez porque o tipo string
é considerado semelhante a um tipo simples como int e char, embora string seja um tipo classe.)
O resumo seguinte pode ser útil. Presume-se que T seja de tipo-classe.
Fornecimento simples por valor, como na declaração de função T f( );
Não pode ser utilizado como l-value e o valor retornado pode ser alterado diretamente, como em
f( ).mutante( ). Chama o construtor de cópia.
Fornecimento por valor constante, como em const T f( );
Este caso é como o anterior, mas o valor retornado não pode ser alterado diretamente como em f( ).mu-
tante( ).
Fornecimento por referência como em T& f( );
Pode ser utilizado como um l-value, e o valor retornado pode ser alterado diretamente como em f( ).mu-
tante( ). Não chama o construtor de cópia.
Fornecimento por referência constante, como em const T& f( );
Não pode ser utilizado como um l-value, e o valor retornado não pode ser alterado diretamente como em
f( ).mutante( ). Não chama o construtor de cópia.
3

■ OPERADOR DE ATRIBUIÇÃO
Se você sobrecarregar o operador de atribuição =, deve sobrecarregá-lo como um operador-membro. Se você
não sobrecarregar o operador de atribuição =, receberá automaticamente um operador de atribuição para sua clas-
se. Esse operador de atribuição padrão copia os valores de variáveis-membros de um objeto da classe para as
variáveis-membros correspondentes de outro objeto da classe. Para classes simples, normalmente é isso o que você
quer. Quando tratarmos de ponteiros, esse operador de atribuição padrão não será o que queremos; quando che-
garmos lá, falaremos em sobrecarregar o operador de atribuição.

■ SOBRECARREGANDO OS OPERADORES DE INCREMENTO E DECREMENTO


Os operadores de incremento e decremento ++ e -- possuem, cada um, duas versões. Podem fazer coisas dife-
rentes, dependendo da utilização na notação de prefixo, ++x, ou sufixo, x++. Assim, quando sobrecarregamos esses
operadores, precisamos, de algum modo, distinguir entre as versões de prefixo e sufixo para que tenhamos duas
versões do operador sobrecarregado. Em C++, essa distinção entre as versões em prefixo e sufixo é tratada de for-

3. Isto porque const T& não chama o construtor de cópia, enquanto const T sim. Trataremos dos construtores de cópia no
Capítulo 10.
232 Sobrecarga de Operador, Amigos e Referências

ma que, à primeira leitura (e talvez até à segunda), pareça um tanto ardilosa. Se você sobrecarregar o operador ++
da forma normal (como operador não-membro com um parâmetro ou como operador-membro sem parâmetros),
você sobrecarregou a forma prefixada. Para obter a versão sufixada, x++ ou x--, acrescente um segundo parâmetro
de tipo int. É apenas um marcador para seu compilador; não se fornece um segundo argumento int quando se
invoca x++ ou x--.
Por exemplo, o Painel 8.6 contém a definição de uma classe cujos dados são pares de inteiros. O operador de
incremento ++ é definido de modo que funcione tanto na notação de prefixo quanto na de sufixo. Definimos ++
de modo que aja intuitivamente como ++ em variáveis int. Esta é a melhor forma de definir ++, mas você é livre
defini-lo a fim de retornar qualquer tipo e executar qualquer ação.
A definição da versão em sufixo ignora esse parâmetro int, como mostrado no Painel 8.6. Quando o compi-
lador vê a++, trata como uma invocação a IntPar::operator++(int), com a como o objeto que faz a chamada.
O operador de incremento e decremento em tipos simples, como int e char, retorna por referência na forma
em prefixo e por valor na forma em sufixo. Se quiser reproduzir o que acontece com tipos simples quando se so-
brecarregam esses operadores para seus tipos-classe, você retornará por referência para a forma em prefixo e por
valor para a forma em sufixo. Entretanto, descobrimos que retornar por referência com operadores de incremento
ou decremento abre a porta para inúmeros problemas e, por isso, sempre retornamos por valor para todas as ver-
sões dos operadores de incremento e decremento.

1 Exercícios de Autoteste 1

12. O trecho seguinte é correto? Explique sua resposta. (A definição de IntPar é fornecida no Painel 8.6.)
IntPar a(1,2);
(a++)++;

■ SOBRECARREGANDO O OPERADOR VETOR [ ]


Pode-se sobrecarregar os colchetes, [ ], para uma classe de modo que possam ser utilizados com objetos da classe.
Se você quer utilizar [ ] em uma expressão no lado esquerdo de um operador de atribuição, o operador deve ser defi-
nido para retornar uma referência. Quando se sobrecarrega [ ], o operador [ ] deve ser uma função-membro.

Painel 8.6 Sobrecarregando ++ (parte 1 de 3)


1 #include <iostream>
2 #include <cstdlib>
3 using namespace std;

4 class IntPair
5 {
6 public:

-----
Não é preciso dar um nome de parâmetro em
7 IntPair(int firstValue, int secondValue); uma declaração de função ou de operador.
8 IntPair operator++( ); //versão com prefixo Para ++ é interessante não dar parâmetros,
9 IntPair operator++(int); //versão com sufixo
já que o parâmetro não é utilizado.
10 void setFirst(int newValue);
11 void setSecond(int newValue);
12 int getFirst( ) const;
13 int getSecond( ) const;
14 private:
15 int first;
16 int second;
17 };
18 int main( )
19 {
20 IntPair a(1,2);
21 cout << "Sufixo a++: Valor inicial do objeto a: ";
22 cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
23 IntPair b = a++;
Referências e Mais Operadores Sobrecarregados 233

Painel 8.6 Sobrecarregando ++ (parte 2 de 3)


24 cout << "Valor retornado: ";
25 cout << b.getFirst( ) << " " << b.getSecond( ) << endl;
26 cout << "Objeti alterado: ";
27 cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
28 a = IntPair(1, 2);
29 cout << "Prefixo a++: Valor inicial do objeto a: ";
30 cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
31 IntPair c = ++a;
32 cout << "Valor retornado: ";
33 cout << c.getFirst( ) << " " << c.getSecond( ) << endl;
34 cout << "Objeto alterado: ";
35 cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
36 return 0;
37 }
38
39 IntPair::IntPair(int firstValue, int secondValue)
40 : first(firstValue), second(secondValue)
41 {/*Corpo propositadamente vazio*/}
42 IntPair IntPair::operator++(int ignoreMe) //Versão com sufixo
43 {
44 int temp1 = first;
45 int temp2 = second;
46 first++;
47 second++;
48 return IntPair(temp1, temp2);
49 }

50 IntPair IntPair::operator++( ) //Versão com prefixo


51 {
52 first++;
53 second++;
54 return IntPair(first, second);
55 }
56 void IntPair::setFirst(int newValue)
57 {
58 first = newValue;
59 }
60 void IntPair::setSecond(int newValue)
61 {
62 second = newValue;
63 }
64 int IntPair::getFirst( ) const
65 {
66 return first;
67 }
68 int IntPair::getSecond( ) const
69 {
70 return second;
71 }

DIÁLOGO PROGRAMA-USUÁRIO
Sufixo a++: Valor inicial do objeto a: 1 2
Valor retornado: 1 2
Objeto alterado: 2 3
234 Sobrecarga de Operador, Amigos e Referências

Painel 8.6 Sobrecarregando ++ (parte 3 de 3)

Prefixo a++: Valor inicial do objeto a: 1 2


Valor retornado: 2 3
Objeto alterado: 2 3

É interessante rever a sintaxe para o operador [ ], já que é diferente de todos os outros operadores que vimos.
Lembre-se de que [ ] é sobrecarregado como um operador-membro; portanto, o componente da expressão que
utilize [ ] deve ser o objeto que faz a chamada. Na expressão a[2], a é o objeto que faz a chamada e 2 é o argu-
mento do operador-membro [ ]. Quando se sobrecarrega [ ], este parâmetro "índice" pode ser de qualquer tipo.
Por exemplo, no Painel 8.7 definimos uma classe chamada Par, cujos objetos se comportam como vetores de
caracteres com os dois índices 1 e 2 (não 0 e 1). Observe que as expressões a[1] e a[2] se comportam exatamente
como variáveis indexadas de vetor. Se você observa a definição do operador sobrecarregado [ ], verá que uma refe-
rência é fornecida e que é uma referência a uma variável-membro, não ao objeto Par inteiro. Isso porque a variável-
membro é análoga a uma variável indexada de um vetor. Quando se altera a[1] (no código-exemplo no Painel
8.7), deseja-se que esta seja uma alteração na variável-membro primeiro. Observe que isso dá a qualquer progra-
ma acesso às variáveis-membros privadas, por exemplo, via a[1] e a[2] na função main do exemplo no Painel 8.7.
Embora primeiro e segundo sejam membros privados, o código é legal porque não referencia primeiro e segundo
por nome, e sim indiretamente, utilizando os nomes a[1] e a[2].

Painel 8.7 Sobrecarregando [ ] (parte 1 de 2)


1 #include <iostream>
2 #include <cstdlib>
3 using namespace std;

4 class CharPair
5 {
6 public:
7 CharPair( ){/*Corpo propositadamente vazio*/}
8 CharPair(char firstValue, char secondValue)
9 : first(firstValue), second(secondValue)
10 {/*Corpo propositadamente vazio*/}
11
12 char& operator[](int index);
13 private:
14 char first;
15 char second;
16 };

17 int main( )
18 {
19 CharPair a;
20 a[1] = ’A’;
21 a[2] = ’B’;
22 cout << "a[1] e a[2] são:\n";
23 cout << a[1] << a[2] << endl;

24 cout << "Digite duas letras (sem espaços):\n";


25 cin >> a[1] >> a[2];
26 cout << "Você digitou:\n";
27 cout << a[1] << a[2] << endl;
28 return 0;
29 }
30
Referências e Mais Operadores Sobrecarregados 235

Painel 8.7 Sobrecarregando [ ] (parte 2 de 2)


31 //Utiliza iostream e cstdlib:
32 char & CharPair::operator[](int index)
33 {
34 if (index == 1) Observe que o que é fornecido é a variável-membro,
35 return first; não o objeto entire Pair, porque a variável-membro
36 else if (index == 2) é análoga a uma variável indexada de um vetor.
37 return second;
38 else
39 {
40 cout << "Valor de índice ilegal.\n";
41 exit(1);
42 }
43 }

DIÁLOGO PROGRAMA-USUÁRIO
a[1] e a[2] são:
AB
Digite duas letras (sem espaços):
CD
Você digitou:
CD

■ SOBRECARGA COM BASE EM L-VALUE VERSUS R-VALUE


Não faremos isto neste livro, mas você pode sobrecarregar um nome de função (ou operador) para que se
comporte de modo diferente quando utilizado como l-value e quando utilizado como r-value. (Lembre-se de que
l-value é o que pode ser utilizado no lado esquerdo de uma declaração de atribuição.) Por exemplo, se você quer
que uma função f se comporte de modo diferente dependendo de sua utilização como um l-value ou um r-value,
pode fazer o seguinte:
class AlgumaClasse
{
public:
int& f( ); // será usado em qualquer invocação a l-value
const int& f( ) const; // usado em qualquer invocação a r-value
...
};

As duas listas de parâmetros não precisam ser vazias, mas devem ser iguais (senão você obtém a sobrecarga
simples). Não deixe de notar que a segunda declaração de f apresenta duas ocorrências de const. Você deve in-
cluir ambas as ocorrências. O sinal de "e" comercial, &, também é exigido, é claro.

[ Resumo do Capítulo j
■ Operadores, como + e ==, podem ser sobrecarregados para ser utilizados com objetos de um tipo-classe
que você define.
■ Um operador é apenas uma função que utiliza uma sintaxe diferente para as invocações.
■ Uma função amiga de uma classe é uma função ordinária, a não ser pelo fato de ter acesso aos membros
privados da classe, exatamente como as funções-membros.
■ Quando um operador é sobrecarregado como membro de uma classe, o primeiro operando é o objeto que
faz a chamada.
236 Sobrecarga de Operador, Amigos e Referências

■ Se suas classes possuem cada uma um conjunto completo de funções de acesso e mutantes, a única razão
para tornar uma função amiga é fazer com que a função amiga seja mais simples e eficiente, mas em geral
esta é uma razão suficiente.
■ Uma referência é uma forma de nomear uma variável. É, essencialmente, um apelido (alias) para a variável.
■ Quando se sobrecarregam os operadores >> ou<<, o tipo retornado deve ser stream e deve ser uma referên-
cia, o que é indicado pelo acréscimo de um & ao nome do tipo retornado.

RESPOSTAS DOS EXERCÍCIOS DE AUTOTESTE


1. A diferença entre um operador (binário), como +, * ou /) e uma função envolve a sintaxe de chamada.
Em uma chamada de função, os argumentos são dados entre parênteses depois do nome da função. Com
um operador, os argumentos são dados antes e depois do operador. Além disso, você deve utilizar a pala-
vra reservada operator na declaração do operador e na definição de um operador sobrecarregado.
2. Acrescente as seguintes declaração e definição de função:
bool operator <(const Dinheiro& quantia1, const Dinheiro& quantia2);
bool operator <(const Dinheiro& quantia1, const Dinheiro& quantia2)
{
int reais1 = quantia1.getReais( );
int reais2 = quantia2.getReais( );
int centavos1 = quantia1.getCentavos( );
int centavos2 = quantia2.getCentavos( );
return ((reais1 < reais2) ||
((reais1 == reais2) && (centavos1 < centavos2)));
}
3. Quando se sobrecarrega um operador, pelo menos um dos argumentos do operador deve ser do tipo-
classe. Isso impede a alteração do comportamento do + para inteiros.
4. Se você omitir o const no início da declaração e definição do operador de positivo sobrecarregado para a
classe Dinheiro, a linha seguinte é legal:
(m1 + m2) = m3;
Se a definição da classe Dinheiro for como mostrada no Painel 8.1, de forma que o operador positivo
forneça por valor const, então não é legal.
5. const Dinheiro
Dinheiro::operator -(const Dinheiro& segundoOperando) const
{
int totalCentavos1 = centavos + reais*100;
int totalCentavos2 = segundoOperando.centavos
+ segundoOperando.reais*100;
int difTotalCentavos = totalCentavos1 - totalCentavos2;
int absTotalCentavos = abs(difTotalCentavos);
int finalReais = absTotalCentavos/100;
int finalCentavos = absTotalCentavos%100;

if (difTotalCentavos < 0)
{
finalReais = -finalReais;
finalCentavos = -finalCentavos;
}

return Dinheiro(finalReais, finalCentavos);


}
6. Uma função amiga e uma função-membro são semelhantes, no sentido de que ambas utilizam qualquer
membro (público ou privado) em sua definição de função. Entretanto, uma função amiga é definida e uti-
lizada como uma função ordinária; não se usa o operador ponto nem qualificadores de tipo quando se
chama uma função amiga. Uma função-membro, por outro lado, é chamada por meio de um nome de
Respostas dos Exercícios de Autoteste 237

objeto e do operador ponto. Além disso, uma definição de função-membro inclui um qualificador de
tipo, que consiste no nome da classe e no operador de resolução de escopo, ::.
7. //Utiliza cstdlib:
const Dinheiro operator -(const Dinheiro& quantia1,
const Dinheiro& quantia2)
{
int totalCentavos1 = quantia1.centavos + quantia1.reais*100;
int totalCentavos2 = quantia2.centavos + quantia2.reais*100;
int difTotalCentavos = totalCentavos1 - totalCentavos2;
int absTotalCentavos = abs(difTotalCentavos);

int finalReais = absTotalCentavos/100;


int finalCentavos = absTotalCentavos%100;

if (difTotalCentavos < 0)
{
finalReais = -finalReais;
finalCentavos = -finalCentavos;
}

return Dinheiro(finalReais, finalCentavos);


}
8. Acrescente as seguintes declaração e definição de função:
friend bool operator <(const Dinheiro& quantia1,
const Dinheiro& quantia2);

bool operator <(const Dinheiro& quantia1,


const Dinheiro& quantia2)
{
return ((quantia1.reais < quantia2.reais) ||
((quantia1.reais == quantia2.reais) &&
(quantia1.centavos < quantia2.centavos)));
}
9. Para entender por que não é circular, você precisa pensar na mensagem básica da sobrecarga: um único
nome de função ou operador pode ter duas ou mais definições. Isso significa que dois ou mais operadores
(ou funções) diferentes podem compartilhar um único nome. Na linha
saidaStream << "R$-";
o operador << é o nome de um operador definido na biblioteca iostream para ser usado quando o segun-
do argumento é uma string entre aspas duplas. O operador chamado <<, que definimos no Painel 8.5, é
um operador diferente que atua quando o segundo argumento é do tipo Dinheiro.
10. Se << e >> devem funcionar como queremos, o primeiro operando (primeiro argumento) deve ser cout
ou cin (ou algum stream de arquivo de E/S). Mas se queremos sobrecarregar os operadores como mem-
bros, digamos, da classe Dinheiro, o primeiro operando terá de ser o objeto que faz a chamada e, assim,
terá de ser do tipo Dinheiro, e não é o que desejamos.
11. //Utiliza iostream:
istream& operator >>(istream& entradaStream,
Porcentagem& umaPorcentagem)
{
char sinalDePorcentagem;
entradaStream >> umaPorcentagem.valor;
entradaStream >> sinalDePorcentagem;//Descarta o sinal %.
return entradaStream;
}

//Utiliza iostream:
ostream& operator <<(ostream& saidaStream,
238 Sobrecarga de Operador, Amigos e Referências

const Porcentagem& umaPorcentagem)


{
saidaStream << umaPorcentagem.valor << ’%’;
return saidaStream;
}
12. É legal, mas o significado não é o que você poderia desejar. (a++) incrementa o valor das variáveis-membros
em um, mas (a++)++ aumenta o valor das variáveis-membros em a++ em um, e a++ é um objeto diferente de
a. (É possível definir o operador de incremento para que (a++)++ tenha o valor das variáveis-membros incremen-
tado em dois, mas isso requer o uso do ponteiro this, que só será discutido no Capítulo 10.)

PROJETOS DE PROGRAMAÇÃO
1. Modifique a definição da classe Dinheiro mostrada no Painel 8.5 para acrescentar o seguinte:
a. Os operadores <, <=, > e >= foram todos sobrecarregados para ser aplicados ao tipo Dinheiro.
(Dica: veja Exercício de Autoteste 8.)
b. A seguinte função-membro foi acrescentada à definição de classe. (Mostramos a declaração de função como
deve aparecer na definição de classe. A definição da própria função incluirá o qualificador Dinheiro::.)
const Dinheiro porcentagem(int cifraPorcentagem) const;
//Retorna uma porcentagem da quantia de dinheiro no objeto que faz a chamada.
//Por exemplo, se cifraPorcentagem for 10, então o valor retornado é
//10% da quantia de dinheiro representada pelo objeto que faz a chamada.
Por exemplo, se carteira é um objeto de tipo Dinheiro cujo valor representa a quantia R$ 100,10, então
a chamada
carteira.porcentagem(10);
retorna 10% de R$ 100,10; ou seja, retorna um valor de tipo Dinheiro que representa a quantia R$ 10,01.
2. Defina uma classe para números racionais. Um número racional é um número que pode ser representado
como o quociente de dois inteiros. Por exemplo, 1/2, 3/4 e 64/2 são números racionais. (Com 1/2, etc.,
queremos dizer as frações comuns, não a divisão inteira que essa expressão produziria em um programa
em C++.) Represente números racionais como dois valores de tipo int, um para o numerador e outro
para o denominador. Chame a classe de Racional. Inclua um construtor com dois argumentos que possa
ser usado para fixar as variáveis-membros de um objeto como qualquer valor legítimo. Inclua também um
construtor que possua um único parâmetro de tipo int; chame esse único parâmetro de numeroInteiro e
defina o construtor de modo que o objeto seja inicializado com o número racional numeroInteiro/1. In-
clua um construtor-padrão que inicialize um objeto como 0 (ou seja, 0/1). Sobrecarregue os operadores de
entrada e saída >> e <<. Os números devem entrar e sair na forma 1/2, 15/32, 300/401, e assim por
diante. Observe que o numerador, o denominador ou ambos podem conter um sinal de menos, de modo
que -1/2, 15/-32 e -300/-401 também são entradas possíveis. Sobrecarregue todos os seguintes operadores
para que se apliquem corretamente ao tipo Racional: ==, <, <=, >, >=, +, -, * e /. Escreva um programa-
teste para testar sua classe. Dicas: dois números racionais a/b e c/d são iguais se a*d é igual a c*b. Se b e d
são números racionais positivos, a/b é menor que c/d desde que a*d seja menor que c*b. Inclua uma função
para normalizar os valores armazenados de forma que, após a normalização, o denominador seja positivo e
o numerador e o denominador sejam tão pequenos quanto possível. Por exemplo, depois da normalização,
4/-8 deve ser representado como -1/2.
3. Defina uma classe para números complexos. Um número complexo é um número da forma
a + b*i
em que, para nossos propósitos, a e b são números de tipo double, e i é um número que representa a
quantidade √ −1
 . Represente um número complexo como dois valores de tipo double. Chame as variáveis-
membros de real e imaginaria. (A variável para o número que é multiplicado por i é a que é chamada
imaginaria.) Chame a classe de Complexo. Inclua um construtor com dois parâmetros de tipo double que
possa ser usado para fixar em qualquer valor as variáveis-membros de um objeto. Inclua um construtor
que possua apenas um parâmetro único de tipo double; chame esse parâmetro de parteReal e defina o
construtor para que o objeto seja inicializado como parteReal + 0*i. Inclua um construtor-padrão que
inicialize um objeto como 0 (ou seja, to 0 + 0*i ). Sobrecarregue todos os seguintes operadores para que
Projetos de Programação 239

se apliquem corretamente ao tipo Complexo: ==, +, -, *, >> e <<. Escreva, também, um programa-teste
para testar sua classe. Dicas: para adicionar ou subtrair dois números complexos, adicione ou subtraia as
duas variáveis-membros de tipo double. O produto de dois números complexos é dado pela seguinte fórmula:
(a + b*i)*(c + d*i) == (a*c - b*d) + (a*d + b*c)*i
No arquivo de interface, defina uma constante i da seguinte forma:
const Complexo i(0, 1);
Esta constante definida i será a mesma que o i de que falamos anteriormente.
4. Modifique cumulativamente o exemplo do Painel 8.7 da seguinte forma:
a. No Painel 8.7, substitua os membros privados char primeiro e segundo por um vetor de char de ta-
manho 100 e um membro de dados privado chamado tamanho.
Inclua um construtor-padrão que inicialize tamanho como 10 e fixe as primeiras 10 posições de char
como ’#’. (Utiliza apenas 10 das 100 posições possíveis.)
Inclua uma função de acesso que forneça o valor do membro privado tamanho.
Teste.
b. Acrescente um operador[ ] membro que forneça um char& que permita ao usuário ter acesso a qual-
quer membro do vetor de dados privado ou o estabeleça utilizando um índice não-negativo que seja
menor que tamanho.
Teste.
c. Acrescente um construtor que requeira um argumento int, tm, que fixe os primeiros tm membros do
vetor char como ’#’.
Teste.
d. Acrescente um construtor que requeira um argumento int, tm, e um vetor de char de tamanho tm.
O construtor deve fixar os primeiros tm membros do vetor de dados privado como os tm membros do
vetor argumento de char.
Teste.
OBSERVAÇÕES: quando testar, utilize bons valores conhecidos, valores no limite e valores delibera-
damente ruins. Não exigimos que você inclua verificações para índices fora dos limites em seu código, mas
isso seria interessante. Alternativas para lidar com erros: envie uma mensagem de erro e depois "caia fora"
(ou seja, chame exit(1)) ou dê ao usuário outra oportunidade de fornecer uma entrada correta.
CAPÍTULO

Strings
Strings

Capítulo 9Strings
Polonius: O que o senhor está lendo?
Hamlet: Palavras, palavras, palavras
William Shakespeare, Hamlet

INTRODUÇÃO
Este capítulo trata de dois tipos cujos valores representam strings de caracteres, como
"Olá". Um tipo é apenas um vetor com tipo-base char que armazena strings de caracteres
no vetor e assinala o fim da string com o caractere nulo, ’\0’. Este é o modo antigo de se
representar strings, que o C++ herdou da linguagem de programação C. Essas strings são
chamadas strings C. Embora as strings C sejam um modo antigo de se representar strings,
é difícil fazer qualquer espécie de processamento de strings em C++ sem um conhecimen-
to mínimo de strings C. Por exemplo, strings de citação, como "Olá", são implementadas
como strings C em C++.
O padrão ANSI/ISO de C++ inclui um recurso mais moderno de se lidar com
strings, na forma da classe string. A classe string é o segundo tipo string de que trata-
remos neste capítulo. A classe string plena utiliza templates (modelos) e é muito parecida
com as classes templates da Standard Template Library (STL). Os templates serão aborda-
dos no Capítulo 16, e a STL, no Capítulo 19. Este capítulo trata dos usos básicos da
classe string, que não requerem o conhecimento de templates.
Este material não exige um conhecimento profundo de vetores, mas você deve estar
habituado à notação vetorial básica, como a[i]. A Seção 5.1 do Capítulo 5 contém mais
do que o necessário para que você leia este capítulo. Este material também não requer um
conhecimento profundo de classes. A Seção 9.1, sobre strings C, e a Seção 9.2, sobre ma-
nipulação de caracteres, podem ser lidas antes dos Capítulos 6, 7 e 8, que tratam de clas-
ses. Entretanto, antes de ler a Seção 9.3, sobre a classe string padrão, você deve ler o
Capítulo 6 e as seguintes partes do Capítulo 7: Seção 7.1 e a subseção da Seção 7.2, inti-
tulada "Modificador de Parâmetros const" com a seção Armadilha que a acompanha.

9.1 Tipo Vetor para Strings


Em tudo se deve levar em consideração o fim.
Jean de La Fontaine, Fábulas, livro III (1668)

Esta seção descreve um modo de representar strings de caracteres, que o C++ herdou
da linguagem C. A Seção 9.3 descreve uma classe string que é um modo mais moderno
de se representar strings. Embora o tipo string descrito aqui possa ser um pouco "antiqua-
do", ainda é amplamente utilizado e faz parte da linguagem C++.
242 Strings

■ VALORES STRING C E VARIÁVEIS STRING C


Uma forma de representar uma string é como um vetor com tipo-base char. Entretanto, se a string for "Olá",
é útil representá-la como um vetor de caracteres com quatro variáveis indexadas: três para as três letras de "Olá"
mais uma para o caractere ’\0’, que serve como sinalizador de final. O caractere ’\0’ é chamado de caractere nulo
e é usado como sinalizador de final porque se distingue de todos os caracteres "reais". O sinalizador de final per-
mite que o programa leia o vetor, um caractere de cada vez, e saiba que deve parar de ler quando lê o ’\0’. Uma
string armazenada dessa forma (como um vetor de caracteres terminado em ’\0’) é chamada de string C.
Escrevemos ’\0’ com dois símbolos em um programa, mas, assim como o caractere de nova linha,’\n’, o carac-
tere ’\0’ é, na verdade, um único valor de caractere. Como qualquer outro valor de caractere, ’\0’ pode ser arma-
zenado em uma variável de tipo char ou uma variável indexada de um vetor de caracteres.

O CARACTERE NULO, ’\0’


O caractere nulo, ’\0’, é utilizado para assinalar o final de uma string C armazenada em um vetor de caracteres. Quando um
vetor de caracteres é usado dessa forma, costuma-se chamá-lo de variável string C. Embora o caractere nulo ’\0’ seja escrito
com dois símbolos, é um caractere único que cabe em uma variável de tipo char ou uma variável indexada de um vetor de ca-
racteres.

Você já vem utilizando strings C. Em C++, uma string literal, como "Olá", é armazenada como uma string C,
embora você quase nunca precise ter consciência desse detalhe.
Uma variável string C é apenas um vetor de caracteres. Assim, a seguinte declaração de vetor nos proporciona
uma variável string C capaz de armazenar um valor string C com nove ou menos caracteres:
char s[10];

O 10 é para as 9 letras na string mais o caractere nulo ’\0’ para assinalar o final da string.
Uma variável string C é um vetor de caracteres parcialmente preenchido. Como qualquer outro vetor parcial-
mente preenchido, uma variável string C utiliza posições a começar da variável indexada 0 até quantas forem ne-
cessárias. Entretanto, uma variável string C não utiliza uma variável int para controlar quanto do vetor é usado no
momento. Em vez disso, coloca o símbolo especial ’\0’ no vetor imediatamente após o último caractere da string C.
Assim, se s contiver a string "Oi, mamãe!", os elementos do vetor são preenchidos como mostrado abaixo:

S[o] s[l] s[2] s[3] s[4] s[S] s[6] s[7] s[8] s[9]
O I I 1 1 M ã e 1 \o ? ?

O caractere ’\0’ é utilizado com um valor de sentinela para marcar o final da string C. Se você ler os caracte-
res na string C começando da variável indexada s[0], seguindo para s[1], depois para s[2], e assim por diante,
saberá que, ao encontrar o símbolo ’\0’, terá chegado ao fim da string C. Como o símbolo ’\0’ sempre ocupa um ele-
mento do vetor, o comprimento da string mais longa que o vetor pode abrigar é o tamanho do vetor menos um.
O que distingue uma variável string C de um vetor de caracteres comum é que uma variável string C deve
conter o caractere nulo ’\0’ ao final do valor string C. Isso é uma distinção em relação a como o vetor é utilizado
e não em relação ao que é o vetor. Uma variável string C é um vetor de caracteres, mas é usado de forma diferente.
Pode-se inicializar uma variável string C na declaração, como ilustrado a seguir:
char minhaMensagem[20] = "Olá, pessoal.";

Observe que a string C atribuída à variável string C não precisa preencher todo o vetor.
Quando se inicializa uma variável string C, pode-se omitir o tamanho do vetor e o C++, automaticamente,
fará o tamanho da variável string C com a extensão da string entre aspas mais um. (A variável indexada extra é
para o ’\0’.) Por exemplo:
char stringCurta[] = "abc";

é equivalente a
char stringCurta[4] = "abc";
Tipo Vetor para Strings 243

DECLARAÇÃO DE VARIÁVEL STRING C


Uma variável string C é o mesmo que um vetor de caracteres, mas é usada de forma diferente. Uma variável string C é declara-
da como um vetor de caracteres da forma usual.
SINTAXE
char Nome_Do_Vetor[Tamanho_Maximo_string_C + 1];
EXEMPLO
char minhaStringC[11];
O + 1 abre espaço para o caractere nulo ’\0’, que termina qualquer string C armazenada no vetor. Por exemplo, a variável
string C minhaStringC, no exemplo acima, pode abrigar uma string C com dez ou menos caracteres de extensão.

Não confunda as inicializações:


char stringCurta[] = "abc";

e
char stringCurta[] = {’a’, ’b’, ’c’};

Não são equivalentes. A primeira dessas duas possíveis inicializações coloca o caractere nulo ’\0’ no vetor após
os caracteres ’a’, ’b’ e ’c’. A segunda não coloca um ’\0’ em nenhum lugar do vetor.

INICIALIZANDO UMA VARIÁVEL STRING C


Uma variável string C pode ser inicializada quando declarada, como ilustrado pelo seguinte exemplo:
char suaString[11] = "La Ra Ra";
Inicializar desta forma coloca automaticamente o caractere nulo, ’\0’, no vetor ao final da string C especificada.
Se você omitir o número dentro dos colchetes, [ ], a variável string C receberá o tamanho do comprimento da string C mais um.
Por exemplo, a seguinte declaração de minhaString apresenta nove variáveis indexadas (oito para os caracteres da string C "La
Ra Ra" e um para o caractere nulo ’\0’):
char minhaString[] = "La Ra Ra";

Uma variável string C é um vetor, então possui variáveis indexadas que podem ser usadas como as de qual-
quer outro vetor. Por exemplo, suponha que seu programa contenha a seguinte declaração de variável string C:
char nossaString[5] = "Oi";

Com nossaString declarada como acima, seu programa possui as seguintes variáveis indexadas:
nossaString[0], nossaString[1], nossaString[2], nossaString[3] e nossaString[4]. Por exemplo, o seguinte
trecho alterará o valor string C em nossaString para uma string C de mesmo comprimento formada só de carac-
teres ’X’:
int indice = 0;
while (nossaString[indice] != ’\0’)
{
nossaString[indice] = ’X’;
indice++;
}

Quando se manipulam essas variáveis indexadas, deve-se ter muito cuidado para não substituir o caractere nulo
’\0’ por algum outro valor. Se o vetor perder o valor ’\0’, não se comportará mais como uma variável string C.
No exemplo a seguir, o vetor felizString será alterado de modo que não contenha mais uma string C:
char felizString[7] = "LaRaRa";
felizString[6] = ’Z’;

Depois que o código acima é executado, o vetor felizString conterá ainda as seis letras na string C "LaRa-
Ra", mas felizString não conterá mais o caractere nulo ’\0’ para assinalar o fim da string C. Muitas funções de
manipulação de strings dependem radicalmente da presença de ’\0’ para assinalar o final do valor string C.
Como outro exemplo, considere o loop while acima, que muda caracteres na variável string C nossaString.
Esse loop while muda caracteres até encontrar um ’\0’. Se o loop nunca encontrar um ’\0’, poderá alterar um
244 Strings

grande bloco de memória para valores indesejados, e o programa poderá começar a fazer coisas estranhas. Como
medida de segurança, seria melhor reescrever o loop while acima da seguinte forma, para que, se o caractere nulo
’\0’ for perdido, o loop não altere inadvertidamente posições de memória além do final do vetor:
int indice = 0;
while ( (nossaString[indice] != ’\0’) && (indice < TAMANHO) )
{
nossaString[indice] = ’X’;
indice++;
}

TAMANHO é uma constante definida igual ao tamanho declarado do vetor nossaString.

BIBLIOTECA <cstring>
Você não precisa de nenhuma instrução de include nem utilizar um comando para declarar e inicializar strings C. Todavia,
quando se processa strings C, é inevitável o uso de algumas das funções string predefinidas da biblioteca <cstring>. Assim,
quando utilizar strings C, normalmente você fornecerá a seguinte instrução de include perto do início do arquivo que contém
seu código:
#include <cstring>
As definições em <cstring> estão colocadas no namespace global, não no std namespace, por isso não é necessária nenhuma
instrução de using.

UTILIZANDO = E == COM STRINGS C


Valores e variáveis string C não são como valores e variáveis de outros tipos de dados, e muitas das opera-
ções usuais não funcionam com strings C. Não se pode utilizar uma variável string C em uma declaração de
atribuição utilizando =. Se você utilizar == para testar as strings C quanto à igualdade, não obterá o re-
sultado esperado. O motivo desses problemas é que as strings C e as variáveis string C são vetores.
Atribuir um valor a uma variável string C não é tão simples como com outros tipos de variáveis. O código
que se segue é ilegal:
char umaString[10];
umaString = "Olá"; Ilegal!
Embora se possa usar o sinal de igual para atribuir um valor a uma variável string C quando a variável é de-
clarada, não se pode fazer isso em nenhum outro lugar do programa. Tecnicamente, o uso do sinal de igual
em uma declaração, como em
char felizString[7] = "LaRaRa";
é uma inicialização, não uma atribuição. Se você quiser atribuir um valor a uma variável string C, deve fazer
algo diferente.
Existem diversas formas de se atribuir um valor a uma variável string C. O jeito mais fácil é usar a função
predefinida strcpy, como mostrado a seguir:
strcpy(umaString, "Olá");
Isso fixará o valor de umaString como igual a "Olá". Infelizmente, esta versão da função strcpy não faz ve-
rificações para assegurar que a cópia não ultrapasse o tamanho da variável string que é o primeiro argu-
mento. Muitas versões de C++, mas não todas, também possuem uma versão de strcpy que requer um
terceiro argumento que dá o número máximo de caracteres a serem copiados. Se esse terceiro parâmetro é
fixado na posição do primeiro argumento como o tamanho da variável vetor menos um, então você obtém
uma versão segura de strcpy (desde que a sua versão de C++ permita esse terceiro argumento). Por
exemplo:
char outraString[10];
strcpy(outraString, umaStringVariavel, 9);
Com esta versão de strcpy, no máximo nove caracteres (deixando espaço para o ’\0’) serão copiados da va-
riável string C umaStringVariavel, independentemente de quão longa seja a string em umaStringVariavel.
Também não se pode utilizar o operador == em uma expressão para testar se duas strings C são a mes-
ma. (Na verdade, é pior do que isso. Pode-se utilizar == com strings C, mas isso não serve para testar se
as strings C são iguais. Assim, se você usar == para testar duas strings C quanto à igualdade, corre o ris-
co de obter resultados incorretos, e sem mensagem de erro!) Para testar se duas strings C são a mesma,
pode-se usar a função predefinida strcmp. Por exemplo:
if (strcmp(stringC1, stringC2))
Tipo Vetor para Strings 245

(continuação)
cout << "As strings NÃO são iguais.";
else
cout << "As strings são iguais.";
Observe que a função strcmp atua de modo diferente do que você poderia supor. A comparação é verdadei-
ra se as strings não são iguais. A função strcmp compara os caracteres nos argumentos da string C um de
cada vez. Se em algum ponto a codificação numérica do caractere de stringC1 é menor que a codificação
numérica do caractere correspondente de stringC2, o teste pára nesse ponto e um número negativo é re-
tornado. Se o caractere de stringC1 for maior que o caractere de stringC2, um número positivo é retorna-
do. (Algumas implementações de strcmp retornam a diferença da codificação do caractere, mas não conte
muito com isso.) Se as strings C forem iguais, um 0 é retornado. O relacionamento de ordem utilizado para
comparar caracteres se chama ordem lexicográfica. É importante observar que se ambas as strings conti-
verem apenas letras maiúsculas ou apenas letras minúsculas, a ordem lexicográfica é a própria ordem alfa-
bética.
Como vimos, strcmp retorna um valor negativo, positivo ou zero, dependendo de as strings C comparadas
lexicograficamente serem menores, maiores ou iguais. Se você utilizar strcmp como uma expressão booleana
em um comando if ou em um looping para testar strings C quanto à igualdade, o valor não-zero será con-
vertido em true se as strings forem diferentes, e o zero será convertido em false. Não se esqueça dessa
lógica invertida quando for testar strings C quanto à igualdade.
Os compiladores de C++ que obedecem ao padrão dispõem de uma versão mais segura de strcmp, que
possui um terceiro argumento que dá o número máximo de caracteres a serem comparados.
As funções strcpy e strcmp estão na biblioteca com o arquivo de cabeçalho <cstring>. Portanto, para utili-
zá-las você deve inserir a seguinte linha junto ao início do arquivo:
#include <cstring>
As definições de strcpy e strcmp estão colocadas no namespace global, não no std namespace; por isso, a
instrução de using não é necessária.

■ OUTRAS FUNÇÕES EM <cstring>


O Painel 9.1 contém algumas das funções mais usadas da biblioteca com o arquivo de cabeçalho <cstring>.
Para utilizá-las, insira a seguinte linha junto ao início do arquivo:
#include <cstring>

Observe que <cstring> coloca todas essas definições no namespace global, não no std namespace; por isso,
não é necessária nenhuma instrução de using.
Já falamos a respeito de strcpy e strcmp. A função strlen é fácil de entender e de usar. Por exemplo,
strlen("larara") apresenta como saída 6, porque há seis caracteres em "larara".
A função strcat é empregada para concatenar duas strings C; ou seja, para formar uma string mais longa colocan-
do duas strings C mais curtas uma depois da outra. O primeiro argumento deve ser uma variável string C. O segundo
argumento pode ser qualquer coisa que, avaliada, produza um valor string C, como uma string entre aspas duplas. O
resultado é colocado na variável string C que é o primeiro argumento. Por exemplo, considere o seguinte código:
char varString[20] = "O rato";
strcat(varString, "roeu");

Este código alterará o valor de varString para "O ratoroeu". Como este exemplo ilustra, é preciso ter o cui-
dado de levar em conta os espaços em branco quando se concatena strings C. Na tabela do Painel 9.1, você verá
que existe uma versão mais segura, de três argumentos, da função strcat disponível em muitas, mas não todas, as
versões de C++.

Painel 9.1 Algumas funções string C predefinidas em <cstring> (parte 1 de 2)


FUNÇÃO DESCRIÇÃO PRECAUÇÕES
strcpy(Var_String_Alvo, Copia o valor string C Src_String na variável Não verifica se Var_String_Alvo é grande o
Src_String) string C Var_String_Alvo. bastante para abrigar o valor Src_String.
strncpy(Var_String_Alvo, Semelhante à strcpy de dois argumentos, Se Limite for escolhido com cuidado, esta
Src_String, Limite) exceto pelo fato de que no máximo Limite versão é mais segura que a strcpy de
caracteres são copiados. dois argumentos. Nem todas as versões de
C++ têm essa função implementada.
246 Strings

Painel 9.1 Algumas funções string C predefinidas em <cstring> (parte 2 de 2)


FUNÇÃO DESCRIÇÃO