Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e...

161
Fascículo III Abril 2012 Introdução a C++ Pesquisa e Desenvolvimento Tecnológico

Transcript of Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e...

Page 1: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

Fascículo III

Abril 2012

Introdução a C++

Pesquisa e

Desenvolvimento

Tecnológico

Page 2: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

2 Pesquisa e Desenvolvimento Tecnológico

Essa página foi deixada em branco intencionalmente.

Page 3: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

3 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

PUBLICADO POR

Atual Sistemas – Departamento de Desenvolvimento

A informação contida neste documento representa a visão então sustentada pela Atual Sistemas sobre os assuntos

abordados na data de publicação do mesmo. Uma vez que a Atual Sistemas reagirá à mudanças nas condições do

mercado, este documento não deve ser interpretado como sob compromisso por parte da Atual Sistemas, e a Atual

Sistemas não garante exatidão de qualquer informação apresentada após a data dessa publicação.

As informações aqui contidas são apenas para propósito informativo. A Atual Sistemas NÃO FORNECE NENHUMA

GARANTIA OU RESPONSABILIDADE QUANTO A INFOMAÇÃO NESSE DOCUMENTO.

Esse documento é uma obra intelectual de uso restrito ao departamento de desenvolvimento da Atual Sistemas não

podendo ser reproduzido, armazenado, alterado, distribuído em quaisquer que sejam os meios (eletrônico, mecânico,

fotocopiado, gravado ou qualquer que seja), quer completo, quer parcial, para qualquer propósito, sem uma expressa

autorização escrita da Atual Sistemas.

A violação desse acordo por posse ou uso não autorizado em quaisquer das situações citadas acima representa um ato

direto contra os direitos de cópia e direitos de intelectualidade e será respondida conforme o rigor da lei.

Page 4: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

4 Pesquisa e Desenvolvimento Tecnológico

Conteúdos

PUBLICADO POR .......................................................................................................................................................... 3

PARTE 1 – NOÇÕES BÁSICAS SOBRE C++ .......................................................................... 7

Introdução a C++ ......................................................................................................................................................... 8

Uma breve história do C++ ................................................................................................................................................... 8

Interpretadores e Compiladores .......................................................................................................................................... 9

Programação Procedural, Estruturada e Orientada a Objetos ............................................................................................. 9

Programação Orientada a Objetos e C++ ........................................................................................................................... 10

Como C++ tem evoluído .................................................................................................................................................... 11

O padrão ANSI/ISO ............................................................................................................................................................. 11

Preparação para Programar ............................................................................................................................................... 12

Seu ambiente de desenvolvimento .................................................................................................................................... 12

Seu primeiro programa C++ ............................................................................................................................................... 13

Erros de compilação ........................................................................................................................................................... 14

Perguntas e Respostas ....................................................................................................................................................... 14

Teste ................................................................................................................................................................................... 15

Exercícios ............................................................................................................................................................................ 15

A Anatomia de um Programa C++ .......................................................................................................................... 16

Um programa simples ........................................................................................................................................................ 16

Uma rápida olhada em cout ............................................................................................................................................... 17

Usando a namespace Standard .......................................................................................................................................... 18

Comentando seus programas ............................................................................................................................................ 19

Funções .............................................................................................................................................................................. 19

Usando funções .................................................................................................................................................................. 20

Conclusão ........................................................................................................................................................................... 21

Usando Variáveis e Declarando Constantes ........................................................................................................ 23

Que é uma variável? ........................................................................................................................................................... 23

Armazenando dados na memória ...................................................................................................................................... 23

Reservando memória ......................................................................................................................................................... 23

Tipos fundamentais de variáveis ........................................................................................................................................ 24

Definindo variáveis ............................................................................................................................................................. 25

Convenção de nomes ......................................................................................................................................................... 26

Palavras Chaves .................................................................................................................................................................. 26

Determinando o tamanho de uma variável ....................................................................................................................... 26

Criando muitas variáveis ao mesmo tempo ....................................................................................................................... 27

Atribuindo valores a variáveis ............................................................................................................................................ 27

Criando pseudônimos com typedef .................................................................................................................................... 28

Estourando os limites de um inteiro unsigned ................................................................................................................... 29

Estourando os limites de um inteiro signed ....................................................................................................................... 29

Trabalhando com caracteres .............................................................................................................................................. 31

Caracteres especiais de impressão ..................................................................................................................................... 32

Constantes .......................................................................................................................................................................... 33

Perguntas e respostas ........................................................................................................................................................ 35

Teste ................................................................................................................................................................................... 35

Exercícios ............................................................................................................................................................................ 36

Page 5: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

5 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

PARTE 2 – MECÂNICA BÁSICA DE DADOS E EXPRESSÕES .......................................... 37

Trabalhando Com Arrays e Strings ........................................................................................................................ 38

Que é um array? ................................................................................................................................................................. 38

Acessando elementos do array .......................................................................................................................................... 38

Usando um array além de seus limites .............................................................................................................................. 39

Inicializando arrays ............................................................................................................................................................. 40

Declarando arrays .............................................................................................................................................................. 41

Arrays multidimensionais ................................................................................................................................................... 41

Arrays de caracter e strings ................................................................................................................................................ 44

Usando os métodos strcpy() e strncpy() ............................................................................................................................. 45

Classes string ...................................................................................................................................................................... 46

Perguntas e respostas ........................................................................................................................................................ 47

Teste ................................................................................................................................................................................... 48

Exercícios ............................................................................................................................................................................ 48

Trabalhando com Expressões, Instruções e Operadores .................................................................................. 49

Iniciando com instruções.................................................................................................................................................... 49

Usando espaços em branco................................................................................................................................................ 49

Blocos e instruções compostas .......................................................................................................................................... 50

Expressões .......................................................................................................................................................................... 50

Trabalhando com operadores ............................................................................................................................................ 51

A natureza do verdadeiro ................................................................................................................................................... 55

Avaliando com operadores relacionais .............................................................................................................................. 55

A instrução if ...................................................................................................................................................................... 56

A instrução else .................................................................................................................................................................. 57

Instruções if avançadas ...................................................................................................................................................... 58

Usando operadores lógicos ................................................................................................................................................ 60

Procedência relacional ....................................................................................................................................................... 61

Mais sobre verdade e falsidade .......................................................................................................................................... 61

O operador condicional ternário ........................................................................................................................................ 62

Perguntas e respostas ........................................................................................................................................................ 62

Teste ................................................................................................................................................................................... 63

Exercícios ............................................................................................................................................................................ 64

Organizando o Código com Funções .................................................................................................................... 65

Que é uma função? ............................................................................................................................................................ 65

Valores de retorno, parâmetros e argumentos.................................................................................................................. 66

Declarando e definindo funções ........................................................................................................................................ 66

Protótipo de funções .......................................................................................................................................................... 67

Definindo a função ............................................................................................................................................................. 67

Execução de funções .......................................................................................................................................................... 69

Determinando o escopo de variáveis ................................................................................................................................. 69

Parâmetros são variáveis locais.......................................................................................................................................... 71

Variáveis globais ................................................................................................................................................................. 72

Variáveis globais: uma palavra de precaução .................................................................................................................... 74

Considerações para criar instruções em funções ............................................................................................................... 74

Mais sobre argumento de funções ..................................................................................................................................... 74

Mais sobre valores de retorno ........................................................................................................................................... 75

Parâmetros default............................................................................................................................................................. 76

Sobrecarga de funções ....................................................................................................................................................... 78

Tópicos especiais sobre funções ........................................................................................................................................ 81

Como funções funcionam - uma olhada sob o capô .......................................................................................................... 86

Page 6: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

6 Pesquisa e Desenvolvimento Tecnológico

Perguntas e respostas ........................................................................................................................................................ 89

Testes ................................................................................................................................................................................. 90

Exercícios ............................................................................................................................................................................ 90

Page 7: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

7 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Conteúdo

Parte 1 – Noções

Básicas Sobre C++

Lição 1 Introdução a C++

Lição 2 A Anatomia de um Programa C++

Lição 3 Usando Variáveis e Declarando Constantes

Page 8: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

8 Pesquisa e Desenvolvimento Tecnológico

Lição 1

Introdução a C++

Essa lição prover a introdução necessária para que você se torne um Desenvolvedor C++ eficiente. Nessa lição

você verá os seguintes tópicos.

Porque C++ é um padrão em desenvolvimento de software.

Passos para desenvolver um programa C++.

Como escrever, compilar e linkar seu primeiro programa funcional em C++.

Uma breve história do C++

As linguagens de programação sofreram uma dramática evolução desde os primeiros computadores eletrônicos.

No início, programadores trabalhavam com o tipo mais primitivo de instruções de computador: linguagem de

máquina. Estas instruções eram representadas por sequências de uns e zeros. O Assembly logo se tornou padrão

na programação por substituir ou mapear as complexas sequências binárias em mnemônicos legíveis e

manejáveis por humanos, como add, mov, jmp.

Entretanto, à medida que as aplicações de software foram se tornando mais complexas (por exemplo, a

computação de trajetória de mísseis), os programadores sentiram a necessidade de uma linguagem que poderia

executar instruções matemáticas relativamente complexas e que por sua vez fossem uma combinação de vários

códigos Assembly, ou seja, muitas instruções em linguagem de máquina em um único comando. Foi quando o

FORTRAN nasceu, como a primeira linguagem de programação de alto nível otimizada para computação

numérica e científica, que introduziu, entre outras coisas, sub-rotinas, funções e loops no cenário da

programação. Com o tempo, outras linguagens de alto nível surgiram e evoluíram, permitindo que

programadores trabalhassem com palavras e sentenças (conhecido como código fonte), como em Let i = 100

(em Basic).

A linguagem C surgiu como uma melhoria de uma versão prévia chamada B, que por sua vez foi uma melhoria

da BCPL (Basic Combined Programming Language). Apesar de C ter sido criado especificamente para auxiliar

programadores a usarem características oferecidas pelo novo hardware (nos anos 70), ela deve sua grande parte

de sua popularidade a sua portabilidade e velocidade. C é uma linguagem procedural, e com as linguagens de

programação evoluindo para o domínio da orientação a objeto, Bjarne Stroustrup criou o C++, em 1981, que

continua sendo uma das mais evoluídas e utilizadas linguagens do mundo. Além de introduzir características

como sobrecarga de operadores e funções em linha, C++ também implementou conceitos orientados a objetos,

como herança (inclusive herança múltipla), encapsulamento, abstração e polimorfismo - termos que serão

explicados posteriormente nesta lição. A implementação de templates (classes genéricas ou funções) em C++ e

a sofisticação destes conceitos até recentemente não estavam disponíveis em linguagens muito mais modernas

como Java e C#.

Após o C++, Java foi a próxima revolução no mundo da programação. Ela se tornou popular sobre a promessa

que uma aplicação Java poderia executar na maioria das plataformas populares. A popularidade de Java

também se baseou em sua simplicidade, que foi conseguida não implementando muitas características que

tornam o C++ uma poderosa ferramenta. Além de não permitir ponteiros, Java também administra a memória e

executa a coleta de lixo (garbage collection) para o usuário. Após Java, C# foi uma das primeiras linguagens

criadas baseadas num framework (a Microsoft .Net Framework). C# deriva ideologicamente e sintaticamente de

Java e C++, além de se diferenciar em alguns aspectos de ambas. Uma versão gerenciada de C++ (chamada de

Managed C++) é o equivalente do C++ original na plataforma .Net, que trás as vantagens da plataforma (como

Page 9: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

9 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

gerenciamento automático de memória e coleta de lixo) e a promessa de uma execução mais rápida que outras

linguagens baseadas no framework, como C#.

C++ continua a ser preferida para muitas aplicações não apenas porque novas linguagens ainda não atendem a

muitas exigências de certas aplicações, bem como pela flexibilidade e poder oferecidos ao programador. C++ é

regulada pelo padrão ANSI e continua a evoluir como linguagem.

Interpretadores e Compiladores

Um interpretador traduz e executa um programa à medida que é lido, transformando instruções ou código

fonte, diretamente em ações. Um compilador traduz o código fonte para uma forma intermediária. Este passo é

chamado compilação e produz um arquivo objeto. Um programa de vinculação ou ligação, chamado linker,

executa após o compilador e combina o arquivo objeto num programa executável contendo instruções em

código de máquina que podem ser executadas diretamente pelo processador.

Pelo fato de interpretadores lerem o código fonte à medida que são escritos e executarem o código na máquina

local, eles se tornam mais fáceis para o programador trabalhar. Atualmente, a maioria dos programas

interpretados são conhecidos como scripts, e o interpretador também é conhecido como motor de script (script

engine).

Compiladores introduzem uma faze extra de compilação de código fonte (legível por humanos) para código

objeto (legível pela máquina). Esta fase extra pode parecer inconveniente, mas programas compilados executam

muito rápido, porque a tarefa demorada de traduzir o código fonte em linguagem de máquina é feito uma só

vez, em tempo de compilação. Outra vantagem de linguagens compiladas como C++ é que você pode distribuir

o programa executável para quem não tem o compilador, diferente de uma linguagem interpretada que deve

ter o interpretador instalado para executar o programa.

Algumas linguagens de alto nível, como Visual Basic 6, chamam o interpretador de biblioteca em tempo de

execução (runtime library), normalmente composto por um conjunto de DLL's. Outras, comoC#, Visual Basic .Net

e Java, têm outros componentes, referenciados como máquina virtual (virtual machine ou VM) ou runtime. Uma

máquina virtual também é um interpretador, mas não traduz um código fonte em linguagem de máquina – ele

utiliza um código previamente compilado, independente de plataforma, conhecido como código intermediário.

Estas linguagens entretanto ainda precisam de um compilador, que gera um código interpretável pela máquina

virtual ou biblioteca de execução.

C++ é tipicamente uma linguagem compilada, apesar de haver alguns interpretadores C++, e tem a reputação

de produzir programas rápidos e poderosos.

Pode parecer meio confuso, mas é fácil fazer a distinção: qualquer linguagem que não possua compilador, ou

que o programa compilado dependa de máquinas virtuais ou DLL's, é uma linguagem interpretada. Uma

linguagem compilada produz um executável puro, nativo do processador.

Programação Procedural, Estruturada e Orientada a Objetos

Até recentemente, programas de computador era uma série de procedimentos que agiam sobre dados. Um

procedimento (procedure), também chamado uma função ou método, é um conjunto específico de instruções

executadas uma após outra. Os dados são separados dos procedimentos e a estratégia da programação era

controlar quais funções eram chamadas por outras funções e quais dados eram alterados. Para melhorar esta

situação potencialmente perigosa foi criada a programação estruturada.

Page 10: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

10 Pesquisa e Desenvolvimento Tecnológico

A principal ideia por trás da programação estruturada é "dividir e conquistar". Um programa de computador

pode ser considerado um conjunto de tarefas. Qualquer tarefa que é muito complexa para ser descrita de forma

simples, é subdividida em um conjunto de tarefas menores, até estas tarefas serem suficientemente pequenas e

independentes para sua fácil compreensão.

Por exemplo, calcular a média de salários dos empregados de uma empresa é uma tarefa complexa, entretanto

você pode subdividi-la em várias subtarefas:

1. Contar quantos empregados existe

2. Descobrir quanto cada empregado ganha

3. Totalizar todos os salários

3.1. Obter cada registro de empregado

3.1.1. Abrir o arquivo de empregados

3.1.2. Ir para o registro correto

3.1.3. Ler os dados

3.2. Acessar o salário

3.3. Somar o salário ao total

3.4. Obter o próximo registro de empregado

4. Dividir pela quantidade total de empregados

A programação estruturada se tornou um enorme sucesso para lidar com problemas complexos, porém nos

anos 80 algumas de suas deficiências começaram a se tornar claras. Primeiro, uma vontade natural é pensar em

dados (registros de empregados, por exemplo) e o que você pode fazer com eles (classificar, editar, etc.) como

uma única ideia. Infelizmente, a programação estruturada separa as estruturas de dados das funções que as

manipulam, e não há nenhuma forma original em agrupar os dados e suas funções. Programação estruturada é

também conhecida como programação procedural, porque seu foco está nos procedimentos, e não nos objetos.

Segundo, programadores precisam reutilizar funções, mas funções que trabalham com um tipo de dado

normalmente não podem ser usadas com outros tipos, limitando os benefícios obtidos.

A programação orientada a objetos resolve estas necessidades, fornecendo técnicas para trabalhar com enorme

complexidade, obtendo reutilização de componentes de software e acoplando dados com as tarefas que os

manipulam. A essência da programação orientada a objetos é modelar objetos (coisas ou conceitos) em vez de

dados. Os objetos podem ser componentes de interfaces gráficas (widgets), como botões e caixas de listagem,

ou coisas do mundo real, como clientes, bicicletas, aviões, etc.

Objetos têm características, também chamadas de propriedades ou atributos, como idade, velocidade, espaço,

preto ou molhado. Eles também têm capacidades, também chamadas operações ou funções, como comprar,

acelerar ou voar.

Programação Orientada a Objetos e C++

C++ suporta totalmente a programação orientada a objetos, incluindo seus três pilares básicos:

encapsulamento, herança e polimorfismo.

Encapsulamento

Quando um engenheiro necessita acrescentar um resistor ao dispositivo que está criando, normalmente ele não

constrói um do zero, mas ele recorre a uma caixa de resistores, examina suas listas coloridas (que definem suas

propriedades) e escolhe o que necessita. O resistor é uma "caixa-preta" na visão do engenheiro, ele não se

Page 11: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

11 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

preocupa como ele funciona, desde que esteja de acordo com suas especificações, ele não precisa olhar o

interior da peça para utilizá-la em seu projeto.

A propriedade de ser uma unidade independente é chamada encapsulamento. Com esta técnica você pode

ocultar os dados, ou seja, um objeto pode ser usado sem que o usuário saiba ou se preocupe como ele trabalha

internamente.

C++ suporta encapsulamento através da criação de tipos definidos pelo usuário, chamados classes. Depois de

ser criada, uma classe bem definida atua como uma entidade totalmente encapsulada - é usada como uma

unidade inteira. O trabalho real interno da classe pode ser oculto e usuários de uma classe bem definida não

precisam saber como ela funciona, mas apenas como usá-la.

Herança e Reutilização

Quando os engenheiros da Acme Motors querem construir um novo carro, tem duas opções: podem começar

do zero ou podem modificar um modelo existente chamado Star. Talvez seu modelo Star seja quase perfeito,

mas eles querem acrescentar um motor turbinado e um câmbio de seis marchas. O engenheiro principal decide

não começar do zero, mas sim adicionar estas características ao Star, e batizá-lo com um novo nome: Quasar. O

Quasar será um tipo de Star, mas um especializado, com novas características.

C++ suporta herança e com ela você pode declarar um novo tipo que é uma extensão de um tipo já existente.

Esta nova subclasse é dita derivada do tipo existente, e às vezes chamada tipo derivado. Se o Quasar é derivado

de Star, e por isto herda todas suas características, os engenheiros podem acrescentar ou mudar estas

características como necessitarem.

Polimorfismo

Um novo Quasar pode responder diferente de um Star quando você pisa no acelerador. O Quasar tem um

motor turbinado, enquanto o do Star é aspirado. Um usuário, entretanto, não precisa saber destas diferenças,

ele quer apenas se movimentar e as coisas certas acontecem, dependendo de qual modelo está dirigindo.

C++ suporta a idéia que diferentes objetos podem ser tratados de forma similar, e ainda assim fazer a coisa

correta, pelo que é chamado de polimorfismo de funções e polimorfismo de classes. Poli significa muitos/as e

morfi significa forma, assim polimorfismo se refere ao mesmo nome assumindo formas diferentes.

Como C++ tem evoluído

À medida que a análise, projeto e programação orientados a objetos começaram a se popularizar, Bjarne

Stroustrup pegou o C, a linguagem mais popular para desenvolvimento de software comercial, e o estendeu

para fornecer as características necessárias para o desenvolvimento orientado a objetos. Apesar de ser verdade

que C++ é um superconjunto de C e que virtualmente qualquer código válido em C também é valido em C++, o

salto de um para o outro é muito significante. C++ se beneficia de seu relacionamento com C porque

programadores C tem facilidade de usar C++, mas para obter todo o benefício de C++ muitos descobriram que

tinham que esquecer muito do que sabiam e aprender uma nova forma de conceituar e resolver problemas de

programação.

O padrão ANSI/ISO

O Accredited Standards Committee, operando sob as regras do AmericanNational Standards Institute (ANSI),

criou um padrão internacional para C++, também conhecido como padrão ISO (International Organization for

Page 12: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

12 Pesquisa e Desenvolvimento Tecnológico

Standardization), padrãoNCITS (National Committee for Information Technology Standards), padrãoX3 (antigo

nome para NCITS) e padrão ANSI/ISO, sendo o mais comum a expressão padrão ANSI.

Este padrão é uma tentativa de garantir que C++ é portável, garantindo, por exemplo, que um código dentro

das normas ANSI escrito para o compilador da Microsoft compilará sem erros em outros compiladores e outros

ambientes, como Apple e Linux. Mas lembre-se que nem todos os compiladores são totalmente compatíveis.

Preparação para Programar

C++, talvez mais que outras linguagens, necessita que o programador projete o programa antes de escrevê-lo.

Os problemas e cenários descritos neste treinamento são genéricos e não necessitam de maiores projetos, mas

a situação é muito diferente na programação profissional do dia a dia, em que um projeto é essencial. Quanto

melhor o projeto, melhor a solução do problema, dentro dos prazos e orçamentos estabelecidos. Um bom

projeto também faz um programa relativamente sem erros e fácil de manter. Já foi estimado que 90 % do custo

de um software está na depuração e manutenção, e desta forma, em última análise, um bom projeto significa

redução de custos.

A primeira questão a responder na preparação de qualquer projeto é "qual o problema que estou tentando

resolver?" Todo programa deve ter um objetivo claro e bem articulado.

A segunda questão que todo bom programador faz é "isto pode ser realizado sem escrever um software

específico?" Reutilizar um programa antigo, usar lápis e papel ou comprar um software de terceiros às vezes é

uma solução melhor que escrever algo novo. O programador que pode oferecer estas alternativas nunca ficará

sem trabalho, pois encontrando soluções menos caras para os problemas de hoje sempre gerará novas

oportunidades mais tarde.

Considerando que você entende o problema e necessita escrever um novo programa, você está pronto para

começar seu projeto. A completa compreensão do problema (análise) e a criação de um plano para a solução

(projeto) formam a base necessária para escrever uma aplicação comercial de alto nível.

Seu ambiente de desenvolvimento

Você pode fazer programas C++ usando apenas um editor de textos simples, como o Notepad, criar um arquivo

com a extensão .cpp, .cp ou .c para o programa fonte, compilar e executar numa janela de comando. A maioria

dos compiladores não se importa com a extensão do nome do arquivo, mas por padrão, é usado .cpp para C++

e .c para C. Os passos para criar um arquivo executável são:

1. Crie um arquivo de código fonte com a extensão .cpp e digite seu código

2. Compile o fonte gerando um arquivo objeto com a extensão .obj ou .o

3. Vincule (Link) seu arquivo objeto com as bibliotecas necessárias para produzir um executável

Todos os compiladores C++ vêm pelo menos com uma biblioteca de funções e classes que podem ser usadas

em seus programas (a STL ou Standard Templates Library) além de aceitarem que você inclua quaisquer outras.

Neste treinamento utilizaremos o ambiente Windows e o Visual Studio 2010, que se encarrega de fazer a

compilação e vinculação automaticamente, gerando todos os arquivos necessários.

Todos os códigos aqui mostrados estão dentro do padrão ANSI e podem ser compilados e executados em

qualquer ambiente que respeite esta padronização.

Page 13: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

13 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Inicialmente, instale o Visual Studio e lembre-se de selecionar "C++" nas opções de linguagens; todas as demais

opções não serão necessárias e podem ser desmarcadas. O Visual Studio tem o conceito de solução (solution) e

projeto (project). Um projeto é um conjunto de pastas e códigos que, após a compilação, resultam num único

programa executável (ou DLL); uma solução é um conjunto de projetos que podem compartilhar recursos entre

si.

Execute o Visual Studio e crie uma solução vazia:

1. No menu principal, selecione File, New, Project

2. A esquerda, em Instaled Templates, selecione Other Project Types, Visual Studio Solution e no painel central,

Blank Solution

3. Em Name: substitua Solution1 por algo mais compreensível, como TreinamentoC++

4. Em Location: selecione a pasta onde ficará sua solução e clique em Ok

5. Verifique se o painel Solution Explorer está sendo exibido com a solução criada; se não estiver, selecione o

menu View, Solution Explorer

Para facilitar, criaremos um projeto para cada lição. Crie o primeiro projeto:

1. No Solution Explorer clique com o direito no nome da solução e selecione Add, New Project

2. A esquerda, em Instaled Templates, selecione Visual C++ e no painel central, Empty Project

3. Em Name: substitua <Enter_name> por Licao01 e clique Ok

4. Agora o Solution Explorer exibe a solução (TreinamentoC++) e abaixo dela o projeto (Licao01), composto

por quatro pastas: External Dependencies, Header Files, Resource Files e Source Files

Seu primeiro programa C++

Nosso primeiro programa será o famoso "Alô, Mundo". Vamos criá-lo:

1. No Solution Explorer, clique com o direito na pasta Source Files, do projeto Licao01 e selecione Add, New

Item

2. No painel central, selecione C++ File (.cpp)

3. Em Name: digite Alo e clique Ok

4. O arquivo Alo.cpp será criado e aberto no editor do Visual Studio

Digite o seguinte código:

1. #include <iostream> 2. int main() 3. { 4. std::cout << "Alo, Mundo\n"; 5. return 0; 6. }

Observação: as linhas foram numeradas para facilitar a explicação, mas obviamente isto não faz parte do código

fonte. O editor do Visual Studio numera as linhas, inclusive em branco, o que não fazemos aqui. Se o número de

linhas não estiver sendo exibido, vá ao menu Tools, Options, no painel a esquerda selecione Text Editor, All

Languages e marque a caixa Line Numbers.

Certifique-se de digitar exatamente como mostrado.Preste atenção na pontuação: as linhas 4 e 5 terminam com

";". Um detalhe importante: C++ é sensível a maiúsculas e minúsculas, ou seja, nomeVariavel, NomeVariavel,

nomevariavel e NOMEVARIAVEL são quatro coisas distintas.Linhas em branco, espaços e tabulações não

significam nada para o compilador, apenas ajudam a organizar o código fonte.

Page 14: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

14 Pesquisa e Desenvolvimento Tecnológico

Para compilar e executar este código no Visual Studio, vá ao menu Debug, escolha Run without Debugging ou

use as teclas de atalho Ctrl+F5. Você verá uma tela do command prompt surgir e desaparecer rapidamente; isto

ocorre apenas dentro do Visual Studio, porque o programa que criamos não tem nenhum ponto de parada.

Faça o seguinte teste:

1. No Windows, clique em Iniciar,Todos os Programas, Microsoft Visual Studio 2010, Visual Studio Tools,

Visual Studio Command Prompt (2010)

2. Na janela do prompt, vá para a pasta onde está sua solução, por exemplo, cd /TreinamentoC++; dentro

desta pasta, o Visual Studio criou outra chamada Debug, onde fica o programa executável

3. Entre nesta pasta (cd Debug) e execute o programa gerado, Licao01.exe, e você verá a saída normal do

programa

Para evitar este problema dentro do Visual Studio, acrescente o seguinte código antes de return 0:

1. char resposta; 2. std::cin >> resposta;

Execute novamente o programa e agora você verá uma janela do prompt aguardando uma entrada de dados;

tecle Ctrl+C para fechar a janela.

Erros de compilação

Vamos forçar um erro de compilação para ver o que acontece no Visual Studio: exclua a última linha do

programa, que contém uma chave direita(}) e compile o programa novamente; será exibido um diálogo

informando que existe um erro e se você deseja executar a versão compilada anteriormente sem erros; clique

em No e será exibida a janela Error List; se ela não for exibida, vá ao menu View, Error List.Por padrão, o Visual

Studio exibe a janela Output, que mostra o resultado da compilação, e não a Error List; para alterar este

comportamento:

1. Selecione o menu Tools, Options

2. No painel à esquerda, selecione Projects and Solutions, General

3. Marque a opção Always show Error List if build finishes with erros e desmarque a opção Show Output

window when build starts

Observe que a janela Error List tem três abas, Erros, Warnings e Messages; quando existem Erros, o programa não

pode ser compilado; Warnings (advertências) não impedem a compilação, mas indicam que algo está errado

com seu código e é melhor verificar; Messages costumam exibir informações complementares ou sugestões.

Perguntas e Respostas

Posso ignorar as mensagens de advertência (warnings) do compilador?

- Os compiladores geralmente exibem erros e advertências; se ocorrem erros, o programa não será totalmente

construído e se forem advertências a compilação é concluída. Muitos são vagos nesta questão, mas a resposta é

não. Adquira o hábito, desde o primeiro dia, de tratar as mensagens de advertência como se fossem erros. C++

usa o compilador para advertir quando você está fazendo alguma coisa que não pretendia. Observe estes avisos

e faça as correções para que eles desapareçam.

O que é tempo de compilação?

- Tempo de compilação (compile time) é o momento em que você está executando o compilador, em contraste

com tempo de vinculação (link time) e tempo de execução (runtime). São apenas termos de programação para

identificar que existem três momentos em que os erros podem surgir.

Page 15: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

15 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Teste

1 - Qual a diferença entre um interpretador e um compilador?

2 - Como você compila seu código fonte com seu compilador?

3 - O que faz o linker?

Exercícios

1 - Observe o programa abaixo e tente adivinhar o que ele faz sem executá-lo:

1. #include <iostream> 2. 3. int main() 4. { 5. int x = 5; 6. int y = 7; 7. std::cout << std::endl; 8. std::cout << x + y << " " << x * y; 9. std::cout << std::endl; 10. return 0; 11. }

2 - Digite e execute o programa do exercício 1. O que ele faz? O que você imaginava?

3 - Digite e compile o programa a seguir. Qual erro ocorre?

1. include <iostream> 2. 3. int main() 4. { 5. std::cout << "Alo, Mundo\n"; 6. return 0; 7. }

4 - Corrija o erro no programa do exercício 3 e o execute. O que ele faz?

Page 16: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

16 Pesquisa e Desenvolvimento Tecnológico

Lição 2

A Anatomia de um Programa C++

Programas C++ consistem de classes, funções, variáveis e outros componentes. Muito desse treinamento é

dedicado em explicar essas partes a fundo. No entanto, para se ter uma boa noção como sobre como um

programa se estrutura, você terá de observar um programa completo.

Nessa lição, você aprenderá:

As partes de um programa C++

Como essas partes trabalham conjuntamente.

O que é uma função e o que ela faz.

Um programa simples

Antes de tudo, crie um novo projeto dentro da nossa solução TreinamentoC++ e nomeie como Licao02. No

Solution Explorer, arraste o arquivo Alo.cpp da pasta Source Files do projeto Licao01 para a pasta Source Files do

projeto Licao02.

Mesmo um programa simples, como o Alo.cpp da Lição 1, tem muitas partes interessantes. Nesta sessão vamos

rever o programa em detalhes:

1. #include <iostream> 2. 3. int main() 4. { 5. std::cout << "Alo, Mundo\n"; 6. char resposta; 7. std::cin >> resposta; 8. return 0; 9. }

Saída

Alo, Mundo

Análise

Na primeira linha, foi incluído o arquivo iostream. Como funciona: o primeiro caractere é o símbolo #, que é um

sinal para um programa chamado pré-processador. Cada vez que o compilador inicia, o pré-processador é

executado antes. Ele lê todo o código fonte, procurando por linhas que começam com o símbolo de tralha (#) e

atua nestas linhas antes do compilador executar. O pré-processador será discutido em mais detalhes nas lições

posteriores.

O comando #include é uma instrução do pré-processador que significa, "o que vem em seguida é um nome de

arquivo; encontre este arquivo, leia-o e coloque seu conteúdo aqui". Os sinais de ângulo (<>) em torno do

nome do arquivo dizem ao pré-processador para procurar este arquivo em todos os locais usuais, que

normalmente é a pasta include onde o compilador está instalado. O arquivo iostream (input-output stream ou

canal de entrada e saída) é usado pelos objetos cout (saída para o vídeo) e cin (entrada do teclado), que são

fornecidos pela biblioteca padrão (standard library). Uma biblioteca é uma coleção de classes e a standard library

é fornecida com todo compilador C++ padrão ANSI. Como você pode ter objetos com o mesmo nome, de

Page 17: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

17 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

origens diferentes, C++ divide o mundo em namespaces. Você informa ao compilador que o objeto cout faz

parte da standard library informando a que namespace ele pertence (std) com o especificador(::). Assim std::cout

significa "objeto cout do namespace std".

O programa realmente começa com a função main(), que todo programa C++ tem. Normalmente, funções são

chamadas por outras funções, mas main() é chamada automaticamente quando o programa inicia. Uma função

pode retornar um valor, e neste caso, main() está retornando um int (inteiro) valendo 0 (zero). Um valor pode

ser retornado para o sistema operacional indicando se a execução do programa foi bem sucedida (por padrão,

0) ou se houve problemas (por padrão, qualquer outro valor). Isto pode ser importante quando um programa é

chamado por outro programa. O programa que chamou pode usar este valor de retorno para tomar alguma

decisão.

Toda função começa com uma chave de abertura “{“ e termina com uma chave de fechamento “}” e todo o

código entre as chaves é considerado parte da função.

A parte mais importante do programa é o uso de std::cout e std::cin. O objeto cout é usado para exibir uma

mensagem na saída padrão (tela) e cin para obter um valor da entrada padrão (teclado). Os argumentos a serem

exibidos por cout são informados através do operador de extração <<, e tudo o que vem a seguir será exibido

na tela. Os caracteres /n dentro da string "Alo Mundo" dizem a cout para exibir uma nova linha após o texto. O

objeto cout aceita vários argumentos na mesma linha, desde que cada um tenha o operador de extração <<

antes.

Uma rápida olhada em cout

No Solution Explorer do Visual Studio clique com o botão direito no nome do projeto Licao02 e selecione Set as

Startup Project, assim este será o projeto a ser compilado e executado por padrão. Na pasta Source Files de

Licao02 acrescente um novo arquivo Cout.cpp e inclua o código abaixo:

1. #include <iostream> 2. int main() 3. { 4. std::cout << "Oi, pessoal\n"; 5. std::cout << "Veja o numero 5: " << 5 << '\n'; 6. std::cout << "Usando o manipulador std::endl" << std::endl; 7. std::cout << "Um numero grande:\t" << 7000 << std::endl; 8. std::cout << "O resultado de uma operacao:\t\t" << (8 + 5) << std::endl; 9. char resposta; 10. std::cin >> resposta; 11. return 0; 12. }

Saída

Oi, pessoal

Veja o numero 5: 5

Usando o manipulador std::endl

Um numero grande: 7000

O resultado de uma operacao: 13

Observação: se o arquivo Alo.cpp está no mesmo projeto, você receberá um erro de compilação, informando

que main já está definida neste arquivo. Dentro de um projeto só pode haver um ponto de entrada (apenas um

arquivo com a função main). Para contornar este problema, no arquivo Alo.cpp altere main para Main (lembre-se

que C++ distingue maiúsculas e minúsculas) e compile novamente.

Page 18: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

18 Pesquisa e Desenvolvimento Tecnológico

Análise

Na linha 1 incluímos o arquivo iostream, necessário para usar cout e suas funções relacionadas.

Na linha 4 exibimos a mensagem "Oi, pessoal" e em seguida uma linha em branco, usando \n dentro da string.

Na linha 5 exibimos uma mensagem, um número e uma nova linha, usando vários operadores <<. Observe que

a string está entre aspas (") e o \n entre apóstrofos ('). Esta é a forma do C++ distinguir entre uma string e um

único caractere. Apesar de /n ser composto por dois símbolos, ele é um único caractere na tabela ASCII (new

line ou nova linha).

Na linha 6 usamos o manipulador std::endl para exibir uma nova linha, ao invés de /n. É preferível usar endl no

lugar de /n, pois endl é adaptado ao sistema operacional e isto faz diferença, por exemplo, ao executar o

programa no ambiente Linux, Unix ou Mac. Porém, caso /n funcione de maneira apropriada nos cenários que

você deseja e envolva várias operações seqüenciais, nesse caso, dê preferência ao /n porque toda vez que endl é

chamado, é executado um flush no buffer do canal em questão, gerando overhead.

Na linha 7 usamos o caractere especial \t, para inserir uma tabulação.

Na linha 8 usamos \t duas vezes, ou seja, duas tabulações, e exibimos o resultado da operação entre parênteses.

No caso de uma operação simples como esta os parênteses não são necessários, mas em operações mais

complexas isto pode causar resultados diferentes, então é uma boa prática sempre utilizá-los.

Usando a namespace Standard

No exemplo anterior usamos std:: na frente de cada cout, endl e cin. Apesar da designação da namespace ser

uma boa forma, neste caso torna-se um trabalho tedioso para digitar. O padrão ANSI permite duas soluções

para este caso. A primeira é informar ao compilador que você usará estas classes que estão na standard library,

no início do código, como abaixo:

1. #include <iostream> 2. using std::cout; 3. using std::endl; 4. using std::cin; 5. int main() 6. { 7. cout << "Oi, pessoal\n"; 8. cout << "Veja o numero 5: " << 5 << '\n'; 9. cout << "Usando o manipulador std::endl" << endl; 10. cout << "Um numero grande:\t" << 7000 << endl; 11. cout << "O resultado de uma operacao:\t\t" << (8 + 5) << endl; 12. char resposta; 13. cin >> resposta; 14. return 0; 15. }

A segunda forma evita o inconveniente de escrever std:: para cada instrução, e indica que usaremos a standard

library completa, como abaixo:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. cout << "Oi, pessoal\n"; 6. cout << "Veja o numero 5: " << 5 << '\n'; 7. cout << "Usando o manipulador std::endl" << endl; 8. cout << "Um numero grande:\t" << 7000 << endl; 9. cout << "O resultado de uma operacao:\t\t" << (8 + 5) << endl; 10. char resposta;

Page 19: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

19 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

11. cin >> resposta; 12. return 0; 13. }

A vantagem é que você não precisa especificar cada objeto que será utilizado; a desvantagem é que se corre o

risco de inadvertidamente, usar objetos de uma biblioteca errada, se contiverem os mesmos nomes.

Comentando seus programas

Comentar seu código fonte é extremamente importante, e o C++ fornece duas formas para isto: utilizar // para

comentar todo o restante da linha ou utilizar o par /* e */ para comentar uma ou mais linhas. Veja o exemplo

abaixo:

1. #include <iostream> 2. using namespace std; 3. /* 4. Nome: main 5. Objetivo: Ponto de entrada do programa. 6. Parâmetros: nenhum 7. Retorno: 0 se execução bem sucedida 8. */ 9. int main() 10. { 11. cout << "Programador de alto nivel"; 12. cout << " - Codigo bem comentado" << endl; 13. char resposta; // Variável que receberá um caracter 14. cin >> resposta; // Captura caracter do teclado 15. // Execução bem sucedida 16. return 0; 17. }

No editor do Visual Studio, você pode comentar várias linhas de uma vez, selecionando o bloco de código e

teclando Ctrl+K+C (as três teclas ao mesmo tempo) ou Ctrl+K, Ctrl+C (primeiro Ctrl K, depois Ctrl C); para

desfazer os comentários, use Ctrl+K+U ou Ctrl+K, Ctrl+U.

Funções

Apesar de main() ser uma função, ela se comporta diferente. Para ser útil, uma função deve ser chamada ou

invocada durante a execução do programa, e main() é chamada automaticamente pelo sistema operacional.

Detalhe: um programa C++ pode ser composto de muitos arquivos fonte, mas apenas um deles pode conter

main(). No Visual Studio, crie um novo arquivo Funcao.cpp e digite o código abaixo:

1. #include <iostream> 2. 3. using namespace std; 4. 5. void FuncaoDemonstracao() 6. { 7. cout << "Na funcao demonstracao" << endl; 8. } 9. 10. int main() 11. { 12. cout << "Em main()" << endl; 13. FuncaoDemonstracao(); 14. cout << "De volta a main()" << endl; 15. char resposta; 16. cin >> resposta; 17. return 0; 18. }

Page 20: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

20 Pesquisa e Desenvolvimento Tecnológico

Saída

Em main()

Na funcao demonstracao

De volta a main()

Análise

Nas linhas 5 a 8 definimos a função FuncaoDemonstracao(), que apenas exibe uma mensagem e retorna.

A linha 12 exibe uma mensagem em main(), a linha 13 chama a FuncaoDemonstracao() e a linha 14 exibe outra

mensagem após o retorno da função.

Usando funções

Funções podem retornar um valor ou retornar void, que significa apenas executar sem retornar nada. Uma

função que soma dois inteiros pode retornar esta soma, e é definida para retornar um inteiro (int).

Uma função consiste de um cabeçalho e um corpo. O cabeçalho contém o tipo de retorno, o nome da função e

os parâmetros para esta função (se existirem). Estes parâmetros permitem que valores sejam passados para a

função. Assim, se uma função deve somar dois números, estes números serão seus parâmetros. Veja um

exemplo de um típico cabeçalho de função de soma:

int Soma(int primeiro, int segundo)

Um parâmetro é uma declaração sobre o tipo de valor que será passado, e o valor passado quando a função é

chamada é conhecido como argumento. Os termos parâmetro e argumento são sinônimos, no caso de funções.

O corpo de uma função consiste de uma chave de abertura {, zero ou mais comandos e uma chave de

fechamento }.

Uma função pode retornar um valor usando o comando return e este valor deve ser do mesmo tipo declarado

no cabeçalho. Este comando também causa a saída da função e pode ser colocado em qualquer parte dentro do

corpo. Veja um exemplo de uma função que retorna um valor:

1. #include <iostream> 2. 3. using namespace std; 4. 5. void Pausa() 6. { 7. char resposta; 8. cin >> resposta; 9. } 10. 11. int Soma(int primeiro, int segundo) 12. { 13. return (primeiro + segundo); 14. } 15. 16. int main() 17. { 18. cout << "Entrei em main()" << endl; 19. int a, b, c; 20. cout << "Digite dois numeros: "; 21. cin >> a; 22. cin >> b; 23. c = Soma(a, b); 24. cout << "Resultado: " << c << endl; 25. Pausa(); 26. return 0;

Page 21: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

21 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

27. }

Saída

Entrei em main()

Digite dois numeros: 10 20

Resultado: 30

Análise

Nas linhas 5 a 9 definimos uma função Pausa(), que não retorna nenhum valor (void) e em seu corpo define uma

variável tipo char para obter o resultado da entrada padrão (cin). O objetivo é apenas parar a execução do

programa e aguardar o usuário teclar alguma coisa.

Nas linhas 11 a 14 definimos outra função, Soma(), que retorna um inteiro (int) e recebe dois inteiros como

parâmetros (primeiro e segundo). Seu retorno é a soma dos dois argumentos.

Na linha 19 declaramos três variáveis inteiras, a, b e c. Nas linhas 21 e 22 capturamos a entrada do teclado para

atribuir os valores às variáveis a e b. Os valores são digitados com um espaço entre eles. Na linha 23, a função

Soma() é chamada e seu retorno é atribuído a c. A linha 24 exibe o valor de c e na linha 25 chamamos a função

Pausa() antes de encerrar o programa.

Conclusão

Perguntas e respostas

O que #include faz?

- Isto é uma diretiva para o pré-processador, que é executado antes do compilador. Esta diretiva específica faz

com que o arquivo entre sinais de ângulo <> seja incluído no arquivo fonte.

Qual a diferença entre os comentários do tipo // e /*...*/?

- Os comentários com barra dupla (//) terminam no fim da linha. O estilo /*...*/comenta todo o conteúdo entre

eles, não importando quantas linhas tenham.

Qual a diferença entre um comentário bom e um ruim?

- Um bom comentário explica ao leitor por que um código está fazendo aquilo ou o que uma seção de código

fará. Um comentário ruim explica o que o código está fazendo. Linhas de código devem ser escritas de maneira

que falem por si mesmas. Um código bem escrito lhe diz o que está fazendo sem necessidade de comentários.

Questionário

1 - Qual a diferença entre o compilador e o pré-processador?

2 - Por que a função main() é especial?

3 - Quais são os dois tipos de comentários e como diferem?

4 - Comentários podem ser aninhados, um dentro do outro?

5 - Comentários podem ser maiores que uma linha?

Page 22: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

22 Pesquisa e Desenvolvimento Tecnológico

Exercícios

1 - Faça um programa que escreva "Eu amo C++" na tela.

2 - Escreva o menor programa possível que possa ser compilado e executado.

3 - Caça-Erros: digite o programa abaixo e compile. O que falhou? Como pode ser corrigido?

1. #include <iostream> 2. main() 3. { 4. std::cout << "Existe um bug aqui ?"; 5. }

4 - Corrija e compile o programa acima.

5 - Modifique o programa e inclua uma função de subtração, chame esta função de Subtrai e use da mesma

maneira que Soma, passando os mesmos parâmetros.

Page 23: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

23 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Lição 3

Usando Variáveis e Declarando Constantes

Programas precisam de uma maneira de armazenar os dados que eles usam ou criam de modo que esses dados

possam ser usados em várias partes durante a execução do programa. Variáveis e constantes oferecem vários

modos de representar, armazenar e manipular esses dados.

Nessa lição, você aprenderá à:

Como declarar e definir variáveis e constantes.

Como atribuir valores a variáveis e manipular esses valores.

Como escrever o valor de uma variável na tela.

Que é uma variável?

Em programação, uma variável é um local na memória do computador para armazenar valores e recuperá-los

quando necessário. Variáveis são usadas para armazenamento temporário, pois quando você encerra o

programa ou desliga o computador, suas informações são perdidas. Para uma armazenagem permanente, uma

variável deve ser definida num arquivo ou banco de dados.

Armazenando dados na memória

A memória do computador pode ser vista como uma série de cubículos, milhões ou bilhões, alinhados em

sequência. Cada cubículo, ou posição de memória, é numerado sequencialmente e este número é conhecido

como endereço de memória. Uma variável reserva uma ou mais posições de memória para o seu conteúdo.

O nome da variável (por exemplo, meuInteiro) é um rótulo ou etiqueta para estes cubículos, de maneira que

você possa encontrá-los facilmente, sem saber seu real endereço de memória. A figura a seguir é uma

representação esquemática desta idéia; a variável myVariable inicia do endereço de memória 103 e dependendo

do seu tamanho, pode ocupar uma ou mais posições de memória.

Reservando memória

Quando você define uma variável, você deve informar de que espécie esta variável é (isto é conhecido como tipo

da variável): um inteiro, ponto flutuante, caracter, etc. Esta informação diz ao compilador qual espaço deve ser

reservado para esta variável e o tipo de valores que ela pode conter. Isto também permite ao compilador gerar

Page 24: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

24 Pesquisa e Desenvolvimento Tecnológico

um erro se você acidentalmente tentar armazenar um valor de tipo diferente daquele permitido na variável (esta

característica de programação é chamada tipagem forte).

Cada posição de memória é do tamanho de um byte e o tipo da variável define quantos bytes ela ocupa,

dizendo ao compilador qual espaço reservar para a variável.

Tipos fundamentais de variáveis

O espaço que uma variável ocupa na memória depende do seu tipo, da arquitetura do computador (8, 16, 32 e

64 bits) e do sistema operacional. Uma variável do tipo char (pronuncia-se "car", de character, em inglês), que

armazena um único caracter (letra, número ou símbolo) ocupa um byte de memória. Os tipos inteiros têm

muitas variações, como short int (inteiro curto), long int (inteiro longo), signed (com sinal), unsigned (sem sinal),

ou simplesmente int. Os tipos signed armazenam a metade da faixa de valores de um unsigned, pois precisam

reservar uma posição de memória para o sinal. A tabela a seguir mostra os tipos fundamentais de C++, com

seus nomes, tamanho na memória e faixa de valores suportados:

Tipo Tipo Equivalente Capacidade

Win32 Unix

32bits

Win64 Unix 64bits

bool bool 1 Byte 1 Byte 1 Byte 1 Byte

char char

1 Byte 1 Byte 1 Byte 1 Byte signed char

unsigned char

short short int

2 Bytes 2 Bytes 2 Bytes 2 Bytes

short int

signed short

signed short int

unsigned short unsigned short int

unsigned short int

wchar_t

char16_t

int int

2 Bytes 4 Bytes 4 Bytes 4 Bytes

signed

signed int

unsigned unsigned int

unsigned int

long long int

4 Bytes 4 Bytes 4 Bytes 8 Bytes

long int

signed long

signed long int

unsigned long unsigned long int

unsigned long int

long long long long int (C++11)

8 Bytes 8 Bytes 8 Bytes 8 Bytes

long long int

signed long long

signed long long int

unsigned long long unsigned long long int (C++11) unsigned long long int

Page 25: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

25 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Como você pode notar, muitos tipos acabam sendo correspondentes entre si e, portanto se excluem

mutuamente. Entretanto, existem algumas leves nuances entre tipos dependendo do sistema operacional.

Abaixo, segue uma tabela com os valores que podem ser atribuídos a esses tipos de acordo com o tamanho

deles.

Tipo Tamanho Formato Alcance

Aproximado Exato

Caracter 1 byte sinalizado -127 à 127

sinalizado -128 à 127

não-sinalizado 0 to 255

Integral 2 bytes sinalizado ± 3.27 · 104 -32768 à 32767

não-sinalizado 0 to 6.55 · 104 0 à 65535

4 bytes sinalizado ± 2.14 · 109 -2,147,483,648 à 2,147,483,647

não-sinalizado 0 to 4.29 · 109 0 to 4,294,967,295

8 bytes sinalizado ± 9.22 · 1018

-9,223,372,036,854,775,808 à

9,223,372,036,854,775,807

não-sinalizado 0 to 1.84 · 1019

0 to 18,446,744,073,709,551,615

Ponto

Flutuante

4 bytes IEEE-754 ± 3.4 · 10± 38

(~7 digits)

min subnormal: ± 1.401,298,4 · 10-47

min normal: ± 1.175,494,3 · 10-38

max: ± 3.402,823,4 · 1038

8 bytes IEEE-754 ± 1.7 · 10± 308

(~15 digits)

min subnormal: ± 4.940,656,458,412 · 10-

324

min normal: ± 2.225,073,858,507,201,4 ·

10-308

max: ± 1.797,693,134,862,315,7 · 10308

Definindo variáveis

Uma variável é criada declarando-se seu tipo e nome. Esta ação apenas reserva o espaço de memória necessário

para o tipo, mas não inicializa a variável com nenhum valor, a não ser lixo. Para ser utilizada, uma variável

precisa ser inicializada, o que pode ser feito na sua declaração ou posteriormente. O nome da variável pode ser

qualquer combinação de letras, números e alguns símbolos. Nunca se esqueça: C++ diferencia maiúsculas de

minúsculas! Ao criar um nome, evite coisas sem sentido, como x, variavel01 e coisas do tipo. Procure definir um

nome que explique o conteúdo da variável, como largura, índice, nomeCliente, etc. Observe os dois exemplos

abaixo:

1. int main() 2. { 3. unsigned short x = 10; 4. unsigned short y = 11; 5. unsigned short z = x * y; 6. return 0; 7. }

1. int main() 2. { 3. unsigned short largura = 10; 4. unsigned short comprimento = 11; 5. unsigned short area = largura * comprimento; 6. return 0; 7. }

Page 26: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

26 Pesquisa e Desenvolvimento Tecnológico

Obviamente, o segundo código é muito mais fácil de entender que o primeiro. Observe que as variáveis tiveram

seus valores atribuídos em sua criação, que é chamado de inicialização de variável.

Convenção de nomes

Existem vários padrões para convenção de nomes, e isto não faz diferença para o compilador; o importante é

que você escolha um e use-o em todos os programas.

Em C++ é comum o nome da variável ser em minúsculas. Quando um nome é formado de várias palavras,

pode-se usar o caracter _ (sublinha) para separar os nomes (como em nome_Cliente) ou a chamada notação

camelo, onde as primeiras letras de cada palavra começam com maiúsculas (como em nomeCliente). Existe

também a notação húngara, onde o prefixo de um nome indica seu tipo (como em intValor). Nesse livro,

usaremos a notação camelo para o nome de variáveis.

Palavras Chaves

Algumas palavras são reservadas em C++ com um significado e funcionalidade específica. Não utilize essas

palavras chaves para nomes de variáveis ou funções. Alguns compiladores acrescentam algumas palavras

reservadas a lista de palavras reservardas. Veja abaixo uma relação destas palavras para o C++ padrão:

asm else new this auto enum operator throw bool explicit private true break export protected try case extern public typedef catch false register typeid char float reinterpret_cast typename class for return union const friend short unsigned

const_cast goto signed using continue if sizeof virtual default inline static void delete int static_cast volatile do long struct wchar_t double mutable switch while dynamic_cast namespace template And bit or not_eq xor and_eq compl or xor_eq bit and not or_eq

Determinando o tamanho de uma variável

Normalmente o programador não precisa se preocupar com o espaço da memória ocupado por uma variável.

Alguns tipos inclusive, como int, podem variar de um computador para outro dependendo do processador,

sistema operacional ou mesmo do compilador. Se for necessário saber o tamanho de uma variável, C++ fornece

o operador sizeof, que retorna este tamanho em tempo de execução. Veja o exemplo abaixo:

1. #include <iostream> 2. using namespace std; 3. 4. int main() 5. { 6. cout << "Tamanho de tipos" << endl << endl; 7. cout << "int: " << sizeof(int) << endl; 8. cout << "short: " << sizeof(short) << endl; 9. cout << "long: " << sizeof(long) << endl; 10. cout << "char: " << sizeof(char) << endl; 11. cout << "float: " << sizeof(float) << endl; 12. cout << "double: " << sizeof(double) << endl;

Page 27: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

27 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

13. cout << "bool: " << sizeof(bool) << endl; 14. char resposta; 15. cin >> resposta; 16. return 0; 17. }

Saída

Tamanho de tipos

int: 4

short: 2

long: 4

char: 1

float: 4

double: 8

bool: 1

Análise

O programa é extremamente simples, apenas demonstra o uso de sizeof para exibir o tamanho dos tipos

principais. Se seu programa apresentou resultados diferentes, provavelmente você está com outra arquitetura

de hardware ou software.

Criando muitas variáveis ao mesmo tempo

Você pode criar diversas variáveis numa única instrução, apenas separando seus nomes por vírgulas, como

abaixo:

1. int largura, altura;

Variáveis declaradas em uma linha devem ser do mesmo tipo, caso contrário haverá um erro de compilação,

como na linha abaixo:

1. float valor, char letra;

Atribuindo valores a variáveis

Você atribui um valor a uma variável usando o operador de atribuição =.

1. int largura; 2. largura = 5;

Um valor também pode ser atribuído na declaração da variável:

1. int largura = 5;

Da mesma forma que você cria diversas variáveis numa linha, também pode atribuir seus valores:

1. int largura = 5, altura = 10;

Também é possível misturar as duas formas, como abaixo:

1. int largura = 5, altura = 10, peso; 2. peso = 20;

Lembre-se que toda variável deve ser inicializada (ter um valor atribuído) antes de sua utilização. C++, ao

contrário de outras linguagens, não atribui um valor padrão quando a variável é criada. Veja o exemplo a seguir:

Page 28: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

28 Pesquisa e Desenvolvimento Tecnológico

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. unsigned short int largura = 5, altura = 10, area, indefinido; 6. area = altura * largura; 7. cout << "Largura: " << largura << endl; 8. cout << "Altura: " << altura << endl; 9. cout << "Area = " << area << endl; 10. cout << "Indefinido: " << indefinido << endl; 11. char resposta; 12. cin >> resposta; 13. return 0; 14. }

O programa será compilado, mas causará um erro em tempo de execução, pois nenhum valor foi atribuído a

indefinido.

Observação: no Visual Studio, o menu Debug, Start Debugging (F5) ou Debug, Start Without Debuggin (Ctrl+F5)

compila e executa o programa automaticamente. Se você quiser apenas compilar, sem executar, no menu Build

selecione Build Solution ou Build NomeDoProjeto.

No exemplo anterior, o Build não dará nenhum erro, mas a execução será interrompida na linha 10, pois não

atribuímos nenhum valor para indefinido.

Na linha 12 criamos uma variável char sem nenhum valor inicial, pois na linha seguinte o comando cin se

encarrega desta atribuição. Isto funciona sem problemas, mas em programas grandes, com muitas variáveis,

alguma pode ficar sem atribuição, e este erro só será percebido quando o programa for executado. Por isto, é

uma boa prática sempre atribuir um valor inicial a uma variável.

Criando pseudônimos com typedef

Torna-se tedioso, repetitivo e passivo de erros digitar constantemente algo como unsigned shor int para definir

uma variável. C++ permite que você crie um pseudônimo (apelido, sinônimo) para esta frase usando a palavra

chave typedef, abreviatura de type definition. Observe que você não estará criando um novo tipo, mas apenas

dando outro nome, normalmente mais curto, a um já existente. Veja o mesmo programa do exemplo anterior,

reescrito para utilizar typedef:

1. #include <iostream> 2. using namespace std; 3. typedef unsigned short int USHORT; 4. int main() 5. { 6. USHORT largura = 5, altura = 10; 7. USHORT area = altura * largura; 8. cout << "Largura: " << largura << endl; 9. cout << "Altura: " << altura << endl; 10. cout << "Area = " << area << endl; 11. char resposta; 12. cin >> resposta; 13. return 0; 14. }

Análise

Na linha 3 usamos typedef para criar um sinônimo para unsigned short int chamado USHORT. O uso de

maiúsculas não é obrigatório, mas uma convenção do C++. Nas linhas 6 e 7 utilizamos simplesmente USHORT

ao invés de unsigned short int, o que torna o código mais compacto e menos sensível a erros.

Page 29: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

29 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Estourando os limites de um inteiro unsigned

O tipo unsigned long int tem uma faixa de valores que dificilmente causarão problemas, mas o que acontece se

você ultrapassar este limite? Quando um inteiro unsigned ultrapassa seu limite, ele reinicia do zero, como o

odômetro de um carro. O exemplo a seguir mostra o que acontece quando tentamos colocar um valor muito

grande num tipo short int:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. unsigned short int numeroPequeno; 6. numeroPequeno = 65535; 7. cout << "Numero pequeno: " << numeroPequeno << endl; 8. numeroPequeno++; 9. cout << "Numero pequeno: " << numeroPequeno << endl; 10. numeroPequeno++; 11. cout << "Numero pequeno: " << numeroPequeno << endl; 12. char resposta; 13. cin >> resposta; 14. return 0; 15. }

Saída

Numero pequeno: 65535

Numero pequeno: 0

Numero pequeno: 1

Análise

Na linha 5 declaramos uma variável do tipo unsigned short int e na linha 6 atribuímos a ela o valor 65535 (seu

limite máximo). Na linha 8, incrementamos o valor desta variável usando o operador ++, que será visto

posteriormente. O novo valor deveria ser 65536, mas como isto está fora dos limites deste tipo, ele reinicia para

seu primeiro valor: 0. Quando o incrementamos novamente na linha 10, ele passa para o valor 1.

Estourando os limites de um inteiro signed

Um inteiro signed se comporta de forma diferente de um unsigned, pois metade de sua faixa de valores é usada

para números negativos e a outra metade para números positivos. Ao invés de imaginar o odômetro de um

carro, tente visualizar um relógio como o da figura abaixo:

Page 30: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

30 Pesquisa e Desenvolvimento Tecnológico

Neste caso, quando o relógio ultrapassar as seis horas (o limite do tempo positivo), ele passará para o maior

número negativo, e irá decrescendo seus valores. O exemplo a seguir mostra o que acontece quando você

ultrapassa os limites de um número com sinal (signed):

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. signed short int numeroPequeno; 6. numeroPequeno = 32767; 7. cout << "Numero pequeno: " << numeroPequeno << endl; 8. numeroPequeno++; 9. cout << "Numero pequeno: " << numeroPequeno << endl; 10. numeroPequeno++; 11. cout << "Numero pequeno: " << numeroPequeno << endl; 12. char resposta; 13. cin >> resposta; 14. return 0; 15. }

Saída

Numero pequeno: 32767

Numero pequeno: -32768

Numero pequeno: -32767

Análise

Na linha 5 declaramos um tipo signed short int (ou short int, pois o signed é padrão) e na linha 6 atribuímos seu

valor positivo máximo (32767). Na linha 8 incrementamos seu valor, que não vai para 32768, mas sim para -

32768 (seu limite negativo). Na linha 10 incrementamos novamente, o que resulta no próximo valor negativo, -

32767.

Resumindo, quando ultrapassamos os limites de um tipo, um inteiro sem sinal (unsigned) vai para 0 e um inteiro

com sinal (signed) vai para seu valor máximo negativo. Isto é conhecido como overflow.

Page 31: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

31 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Trabalhando com caracteres

Variáveis caracter (tipo char) ocupam um byte, suficiente para comportar 256 valores. Um char pode ser

interpretado como um número pequeno (0 a 255) ou um caracter da tabela ASCII. O conjunto de caracteres

ASCII e seu equivalente ISO são uma forma de codificar todas as letras, numerais e sinais de pontuação.

Nota: Computadores não conhecem letras, pontuações ou sentenças, na verdade tudo que eles conhecem é se

existe ou não certa quantidade de energia numa junção de circuitos. Estes dois estados (existe energia ou não)

são representados simbolicamente por 0 e 1 (a linguagem binária). Agrupando estes zeros e uns, o computador

é capaz de criar padrões que são interpretados como números e estes, por sua vez, podem ser atribuídos a

símbolos (letras, números, pontuações).

Na tabela ASCII, a letra a minúscula é atribuída ao valor 97. Todas as letras, maiúsculas e minúsculas, todos os

números e todos os sinais de pontuação são atribuídos a valores entre 0 e 128. Outros 128 símbolos foram

reservados para os fabricantes de computador, mas a IBM estendeu este conjunto adicional para se tornar um

padrão.

Nota: Os primeiros 128 caracteres são suficientes para representar todos os símbolos da língua inglesa, apenas.

Caracteres acentuados ou ç, por exemplo, fazem parte dos 128 símbolos extras.

Quando você coloca um caracter numa variável char o que vai realmente é um número entre 0 e 255. O

compilador sabe como traduzir este símbolo (que está entre apóstrofos) para seu correspondente numérico

ASCII. Observe que existe uma grande diferença entre o valor 5 e o caracter 5 (que tem o valor ASCII de 53),

como a letra a, que tem o valor de 97. Veja o exemplo a seguir:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. for (int i = 0; i < 128; i++) 6. cout << (char) i; 7. cout << endl << endl; 8. for (int i = 128; i < 256; i++) 9. cout << (char) i; 10. cout << endl << endl; 11. for (unsigned char i = 32; i < 128; i++) 12. cout << i; 13. char resposta; 14. cin >> resposta; 15. return 0; 16. }

Saída

☺☻♥♦♣♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]

^_`abcdefghijklmnopqrstuvwxyz{|}~⌂

ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈıÍÎÏ┘┌█▄¦Ì▀ÓßÔÒ

õÕµþÞÚÛÙýݯ´­±‗¾¶§÷¸°¨·¹³²■

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno

pqrstuvwxyz{|}~⌂

Análise

Page 32: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

32 Pesquisa e Desenvolvimento Tecnológico

Na linha 5 declaramos um loop for (estudaremos em lições posteriores) que executa 128 vezes (de 0 a 127) e na

linha 6 exibe (char) i, ou seja, o valor de i convertido para char (também veremos nas próximas lições). Isto exibe

os primeiros 128 caracteres da tabela ASCII, muitos dos quais não fazem parte do teclado.

Na linha 8 declaramos outro loop for, também executando 128 vezes, mas começando em 128 e terminando em

255. Isto exibe os símbolos restantes da tabela ASCII (observe que os caracteres acentuados estão nesta

sequência).

Por fim, na linha 11 fazemos outro loop for, desta vez com os caracteres de 32 a 128, que estão presentes em

qualquer teclado.

Observe que nos primeiros dois loops declaramos a variável i como int, que aceita valores positivos e negativos,

e vai muito além do valor 255. Por isto, para exibir os caracteres tivemos que fazer uma conversão (ou cast)

usando (char) i. Na linha 11, porém, declaramos i como unsigned char e neste caso exibimos i sem precisar de

nenhuma conversão.

Caracteres especiais de impressão

O compilador C++ reconhece alguns caracteres especiais para formatação, e você os coloca em seu código

usando a contra barra (\) seguida de um caracter, ambos entre apóstrofos ('). Por exemplo:

1. char tabulacao = '\t';

Estes caracteres são válidos para qualquer saída padrão do C++ (tela, impressora, arquivo ou qualquer outro

dispositivo). A contra barra, também conhecida como caracter de saída (escape caracter) muda o significado do

símbolo que o acompanha. Por exemplo, o caracter n significa a letra n, mas se for precedido de contra barra

significa new line (nova linha ou quebra de linha). Veja abaixo os principais caracteres de saída:

Caracter Significado

\a Bell (sinal sonoro)

\b Backspace (retorna uma posição)

\f Form Feed (salto de página)

\n New Line (nova linha ou quebra de linha)

\r Carriage Return (Enter)

\t Tab (tabulação horizontal)

\v Vertical Tab (tabulação vertical)

\' Apóstrofo (aspas simples)

\" Aspas

\? Ponto de interrogação

\\ Contrabarra

\000 Notação octal

Page 33: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

33 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

\xhhh Notação hexadecimal

Constantes

Constante é uma variável como outra qualquer, com a grande diferença que seu valor não pode ser alterado

depois que é atribuído.

Uma constante literal é um valor digitado diretamente em seu programa, como no exemplo abaixo:

1. int minhaIdade = 24;

Aqui minhaIdade é uma variável inteira e 24 é uma constante literal. Você não pode atribuir outro valor a 24,

como em 24 = 25.

Uma constante simbólica é representada por um nome, como uma variável, o que muda é que seu valor

permanece inalterado após ser atribuído pela primeira vez.

Definindo constantes

Muitos programas usam a diretiva de preprocessador #define para definir uma constante, como abaixo:

1. #define NUMERO_ESTUDANTES 40;

Usar apenas letras maiúsculas para nomear uma constante é uma convenção da linguagem C, mas não é

obrigatório. Note que este comando não está definindo uma variável de qualquer tipo, apenas dando outro

nome para o valor 40 (semelhante ao que typedef faz), ou seja, em qualquer parte do programa que usarmos

NUMERO_ESTUDANTES, o compilador simplesmente substituirá este nome por 40. Apesar de #define ser muito

simples de usar, deve ser evitado, pois foi considerada uma construção obsoleta pelo padrão ANSI.

C++ tem uma maneira bem mais funcional de se definir constantes, como no exemplo abaixo:

1. const unsigned short int NUMERO_ESTUDANTES = 24;

Aqui também definimos uma constante, com a grande diferença que declaramos seu tipo (unsigned short int).

Isto torna o código muito mais compreensível e menos propenso a erros, pois o compilador exigirá seu uso com

tipos compatíveis, o que não ocorre com #define.

Constantes enumeradas

Constantes enumeradas permitem criar novos tipos e definir variáveis deste novo tipo, que ficarão restritas ao

conjunto de valores da constante. Por exemplo, podemos declarar COR como uma constante com vários valores

da seguinte forma:

1. enum Cor {Vermelho, Azul, Amarelo, Verde};

Este comando faz duas coisas:

- Cria um novo tipo Cor, que é o nome da enumeração;

- Cria Vermelho como uma constante simbólica valendo 0, Azul como uma constante simbólica valendo 1, e

assim sucessivamente.

Page 34: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

34 Pesquisa e Desenvolvimento Tecnológico

Toda constante enumerada tem um valor inteiro. Se não for especificada outra coisa, o primeiro valor será 0 e os

demais serão incrementados de um em um. Qualquer das constantes pode ser inicializada com um valor

diferente, e as seguintes continuarão a ser incrementadas. Por exemplo:

1. enum Cor {Vermelho=100, Azul, Amarelo=500, Verde};

Aqui a constante Vermelho terá o valor 100, a Azul que vem em seguida terá 101, Amarelo terá 500 e Verde em

seguida terá 501.

Você pode definir uma variável do tipo Cor, mas ela só poderá conter os valores da enumeração. Perceba que

uma enumeração normalmente é do tipo unsigned int e as constantes enumeradas são variáveis inteiras. Veja o

exemplo abaixo:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. enum Dia {Domingo, Segunda, Terca, Quarta, Quinta, Sexta, Sabado}; 6. Dia hoje; 7. hoje = Segunda; 8. if (hoje == Domingo || hoje == Sabado) 9. cout << "Fim de semana é show!" << endl; 10. else 11. cout << "De volta ao trabalho!" << endl; 12. char resposta; 13. cin >> resposta; 14. return 0; 15. }

Saída

De volta ao trabalho!

Análise

Na linha 5 criamos uma constante enumerada (ou enum) chamada Dia, que contém os sete valores entre

chaves. Cada valor é um inteiro, começando a contagem do zero, ou seja, Domingo=0, Segunda=1, e assim por

diante. Na linha 6 criamos uma variável do tipo Dia chamada hoje e na linha 7 atribuímos a ela um valor. Note

que apesar de hoje ser realmente uma variável int, ela não pode conter nenhum valor além daqueles definidos

no enum. Nas linhas 8 a 11 fazemos um teste (veremos isto adiante) e exibimos o resultado.

A constante enumerada poderia ser substituída por uma série de constantes inteiras, como abaixo:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. const int Domingo = 0; 6. const int Segunda = 1; 7. const int Terca = 2; 8. const int Quarta = 3; 9. const int Quinta = 4; 10. const int Sexta = 5; 11. const int Sabado = 6; 12. int hoje; 13. hoje = Segunda; 14. if (hoje == Domingo || hoje == Sabado) 15. cout << "Adoro o fim de semana" << endl; 16. else 17. cout << "De volta ao trabalho" << endl; 18. char resposta;

Page 35: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

35 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

19. cin >> resposta; 20. return 0; 21. }

O resultado seria o mesmo, mas claramente não faz sentido usar esta construção quando se tem enum.

Perguntas e respostas

Se um tipo short int pode ficar sem espaço e reiniciar seu valor, porque não usar sempre long int?

- Qualquer inteiro pode ficar sem espaço e reiniciar seu valor, mas um inteiro longo faz isto com valores muito

mais altos. Por exemplo, um unsigned short int (com 2 bytes) reinicia seu valor após 65.535, um unsigned long int

(com 4 bytes) só faz isto após 4.294.967.295. Entretanto um inteiro longo ocupa duas vezes mais memória que

um inteiro curto. É claro que isto não é um problema nas máquinas atuais, com milhões ou bilhões de bytes de

memória, mas pode ser significativo se o programa contiver milhares de variáveis. Um número maior também

toma mais tempo de processamento que um pequeno.

Que acontece se for atribuído um número com ponto decimal a um inteiro, em vez de um float, como em

int numero = 2.5?

- O compilador costuma enviar um aviso, mas a instrução é perfeitamente válida. Apenas o valor armazenado

será 2, e a parte decimal será perdida. Se posteriormente você atribuir o valor desta variável a um float, o valor

recebido será 2.

Por que não usar constantes literais e ter o incômodo de usar constantes simbólicas?

- Se você usa um valor em várias partes do programa, é muito mais simples alterar este valor apenas na

declaração da constante que em todo código fonte. Também é mais difícil de entender por que um número é

multiplicado por 360 do que multiplicado por GRAUS.

Que acontece se eu atribuir um número negativo a uma variável unsigned?

- Se você digita algo do tipo unsigned int positivo = -1, o compilador poderá enviar um aviso, mas a instrução é

legal. O detalhe é que o número é avaliado no padrão binário e atribuído à variável. Assim, -1, que em binário é

representado por 11111111 11111111 (ou 0xFF em hexa), é acessado por um inteiro sem sinal como 65535.

Posso trabalhar com C++ sem entender de padrão de bits, linguagem binária ou hexadecimal?

- Sim, mas não tão efetivamente que se você tiver conhecimento destes tópicos. C++ não faz um bom trabalho

como outras linguagens em proteger o programador do que o computador está realmente fazendo. Isto

realmente é um benefício porque lhe fornece um tremendo poder que outras linguagens não têm. Mas como

qualquer ferramenta poderosa, para obter o máximo do C++ você precisa entender como ele funciona.

Programadores que tentam usar o C++ sem uma noção básica do sistema binário ocasionalmente podem ficar

confusos com seus resultados.

Teste

1 - Qual a diferença entre uma variável inteira e uma de ponto flutuante?

2 - Qual a diferença entre um unsigned short int e um long int?

3 - Qual a vantagem de usar constantes simbólicas no lugar de constantes literais?

4 - Qual a vantagem de usar a palavra chave const ao invés de #define?

5 - O que torna um nome de variável bom ou ruim?

Page 36: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

36 Pesquisa e Desenvolvimento Tecnológico

6 - No enum a seguir, qual o valor de AZUL?

enum COR {BRANCO, PRETO=100, VERMELHO, AZUL, VERDE=300}

7 - Quais dos seguintes nomes de variáveis são bons, ruins ou inválidos?

a) idade

b) !ex

c) R79J

d) rendimentoTotal

e) __Invalido

Exercícios

1 - Qual deve ser o tipo de variável correta para armazenar as seguintes informações?

a) Sua idade

b) A área do seu quintal

c) A quantidade de estrelas de uma galáxia

d) A média de chuva para o mês de Janeiro

2 - Crie bons nomes de variáveis para esta informação.

3 - Declare uma constante para PI com 3,14159.

4 - Declare uma variável float e inicialize-a com sua constante PI.

Page 37: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

37 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Análise

Parte 2 – Mecânica

Básica de Dados e

Expressões

Lição 4 Trabalhando com Arrays e Strings

Lição 5 Trabalhando com Expressões, Instruções e

Operadores

Lição 6 Organizando o Código com Funções

Page 38: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

38 Pesquisa e Desenvolvimento Tecnológico

Lição 4

Trabalhando Com Arrays e Strings

Na lição anterior, você declarou variáveis int, char e outras. No entanto, em alguns casos, você precisa declarar

uma coleção de variáveis ou objetos, como no caso de 20 ints.

Nessa lição você aprenderá:

O que são arrays e como declará-los.

O que são string e como usar arrays de caracteres para declará-las.

Que é um array?

Um array é uma coleção sequencial de locais de armazenagem de dados, cada qual comportando o mesmo tipo

de dados. Cada local de armazenagem é chamado elemento do array. Você declara um array informando o tipo,

nome e um subscrito. Subscrito é o número de elementos do array, colocado entre colchetes. Por exemplo:

int meuArray[25];

A linha acima declara um array chamado meuArray de 25 inteiros. Quando o compilador encontra esta

declaração, ele reserva espaço na memória suficiente para armazenar os 25 elementos. Se cada inteiro ocupa 4

bytes, esta declaração reservará 100 bytes contínuos de memória, como na figura abaixo:

Acessando elementos do array

Você acessa um elemento do array referindo-se ao seu deslocamento a partir do início. Este deslocamento é

contado a partir de zero, assim o primeiro elemento será meuArray[0], a segundo meuArray[1] e a último

meuArray[24]. Isto pode causar alguma confusão, pois algumArray[3] tem três elementos que são

algumArray[0], algumArray[1] e algunArray[2]. Generalizando, em um array de n elementos umArray[n], o

primeiro será sempre umArray[0] e o último umArray[n-1]. Isto ocorre porque o índice é um deslocamento,

então o primeiro elemento não tem deslocamento nenhum, o segundo tem um deslocamento, e assim por

diante.

O exemplo a seguir mostra a criação e utilização de um array de cinco elementos:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. int meuArray[5];

Page 39: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

39 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

6. int i; 7. cout << "Informe os valores do array:" << endl; 8. for (i = 0; i < 5; i++) 9. { 10. cout << "Valor de meuArray[" << i << "] = "; 11. cin >> meuArray[i]; 12. } 13. for (i = 0; i < 5; i++) 14. cout << i << " = " << meuArray[i] << endl; 15. return 0; 16. }

Saída

Valor de meuArray[0] = 1

Valor de meuArray[1] = 3

Valor de meuArray[2] = 5

Valor de meuArray[3] = 7

Valor de meuArray[4] = 9

0 = 1

1 = 3

2 = 5

3 = 7

4 = 9

Análise

Na linha 5 criamos um array de cinco inteiros. Na linha 8 iniciamos um laço for (veremos futuramente) que

executará cinco vezes usando a variável i, começando de 0 e terminando em 4, e dentro deste laço você informa

o valor de cada elemento. Na linha 13 iniciamos outro laço for apenas para exibir os elementos informados.

Observe que cada elemento do array é acessado usando-se um índice (neste caso, a variável i) e é tratado como

uma variável normal do tipo definido no array.

Reforçando: arrays contam a partir de zero, e não de um, pois o índice representa o deslocamento de cada

elemento a partir do início do array. Como o primeiro elemento não tem deslocamento nenhum, seu índice é

zero. Portanto, ao usar um array, se ele foi declarado com 10 elementos, o primeiro será nomeArray[0] e o

último, nomeArray[9]; nomeArray[10] irá gerar um erro, e isto é muito comum com programadores iniciantes em

C++.

Usando um array além de seus limites

Quando você coloca um valor num elemento de array, o compilador calcula onde o valor será armazenado

baseado no tamanho de cada elemento e no índice informado. Imagine que você quer armazenar um valor em

meuArray[5], que é o sexto elemento. O compilador multiplicará o índice ou deslocamento (5) pelo tamanho de

um inteiro (4 bytes), se posicionará 20 bytes a partir do início do array, e gravará este valor ali. A maioria dos

compiladores não dará nenhum erro se você for além dos limites definidos para o array, mas ele simplesmente

fará o cálculo do deslocamento e acessará aquela posição da memória, não importando o que esteja

armazenado lá. Isto dará um resultado imprevisível, pois não se sabe o que ou qual programa realmente está

usando este endereço. O exemplo a seguir propositalmente extrapola os limites de um array e na compilação

não apresenta nenhum erro, mas sua execução é imprevisível:

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

Page 40: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

40 Pesquisa e Desenvolvimento Tecnológico

4. int main() 5. { 6. long arrayTeste[25]; 7. int i; 8. // Loop correto, dentro dos limites do array 9. for (i = 0; i < 25; i++) 10. arrayTeste[i] = 10; 11. cout << "Primeiro teste" << endl; 12. cout << "arrayTeste[0] = " << arrayTeste[0] << endl; 13. cout << "arrayTeste[24] = " << arrayTeste[24] << endl; 14. // Loop errado, além dos limites do array 15. for (i = 0; i <= 25; i++) 16. arrayTeste[i] = 20; 17. cout << "Segundo teste" << endl; 18. cout << "arrayTeste[0] = " << arrayTeste[0] << endl; 19. cout << "arrayTeste[24] = " << arrayTeste[24] << endl; 20. cout << "arrayTeste[25] = " << arrayTeste[25] << endl; // Além do limite 21. return 0; 22. }

Saída

Primeiro teste

arrayTeste[0] = 10

arrayTeste[24] = 10

Segundo teste

arrayTeste[0] = 20

arrayTeste[24] = 20

arrayTeste[25] = 20

Análise

Na linha 6 criamos um array do tipo long com 25 elementos, ou seja, de arrayTeste[0] a arrayTeste[24]. Nas

linhas 9 e 10 fizemos um loop e armazenamos o valor 10 em cada elemento. Observe o teste que foi feito na

contagem do item, dentro do comando for: i < 25. Nas linhas 15 e 16 fizemos outro loop para armazenar o valor

20 em cada elemento do array. Porém, o teste no comando for está errado: i <= 25, ou seja, de 0 a 25! A

compilação e execução deste programa parecem completamente normais, inclusive exibindo o elemento que

não existe (arrayTeste[25]) com o valor 20, mas na verdade foi acessada uma posição de memória que poderia

conter qualquer coisa, e inclusive travar o sistema operacional. Este erro é chamado de buffer overflow e o

resultado é imprevisível.

Inicializando arrays

Você pode inicializar um array simples de tipos embutidos, como inteiros ou caracteres, quando ele é declarado,

colocando após o nome do array o sinal de = e uma lista de inicialização onde os valores ficam entre chaves,

como abaixo:

int arrayInteiros[5] = {1, 2, 3, 4, 5};

Lembre de não colocar mais valores que o array comporta, ou você obterá um erro do compilador. Você,

entretanto, pode colocar menos valores que o limite do array:

int arrayInteiros[5] = {1, 2};

Neste caso, o array tem cinco elementos, mas apenas os dois primeiros são inicializados. Se você quiser que

todos os elementos sejam inicializados com o mesmo valor, basta colocar este valor uma única vez entre chaves:

int arrayInteiros[5] = {0};

Page 41: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

41 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Isto é o mesmo que fazer:

int arrayInteiros[5] = {0, 0, 0, 0, 0};

Se você omitir o tamanho de um array e inicializá-lo com um conjunto de valores, a quantidade destes valores

será o tamanho do array:

int arrayInteiros[] = {1, 2, 3, 4, 5};

O array acima, apesar de não declarado entre os colchetes, terá cinco elementos.

Assim como qualquer variável, é uma boa prática sempre inicializar um array antes de sua utilização, caso

contrário ele conterá valores totalmente aleatórios ou inválidos.

Declarando arrays

Arrays podem ter qualquer nome válido para uma variável, mas não podem ter o mesmo nome de uma variável

já declarada, ou seja, você não pode ter um array chamado meusGatos[5] se já houver uma variável chamada

meusGatos. No exemplo anterior usamos um número para declarar o tamanho do array (25), mas é muito

comum se usar constantes. Criar um array usando uma enumeração fica um pouco diferente, como no exemplo

abaixo:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. enum diasDaSemana {Dom, Seg, Ter, Qua, Qui, Sex, Sab, QuantidaDeDias}; 6. int arraySemana[quantidadeDias] = {10, 20, 30, 40, 50, 60, 70}; 7. cout << "Valor em Terca = " << arraySemana[Ter] << endl; 8. }

Saída

Valor em Terca = 30

Análise

Na linha 5 declaramos uma enumeração diasDaSemana com oito elementos. Na linha 6 declaramos um array de

inteiros cujo tamanho é o valor da constante quantidadeDias (neste caso, 7). Lembre-se que a contagem das

constantes de uma enumeração também começa do zero. Como cada constante da enumeração representa um

inteiro, podemos usar qualquer um deles para acessar um elemento do array, como na linha 7 (aqui a constante

Ter vale 2, ou seja, o terceiro elemento do array).

Arrays multidimensionais

Os exemplos de arrays que vimos até agora são todos unidimensionais, ou seja, apenas uma dimensão. Pode-se

dizer que o tamanho de um array é o mesmo que o tamanho de uma string (na verdade, uma string é um array

unidimensional, como veremos adiante).

Arrays, entretanto, podem ser multidimensionais. Assim como os caracteres de uma string podem ser

representados por array unidimensional, os pontos de uma área retangular podem ser representados por um

multidimensional. Como cada ponto é uma coordenada do tipo x e y, o array terá dois subscritos ou índices,

como abaixo:

int coordenada[5][5];

Page 42: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

42 Pesquisa e Desenvolvimento Tecnológico

Se você for representar os pontos em uma esfera, precisará de um array de três dimensões, x, y e z:

int coordenada[5][5][5];

Os índices separados num array multidimensional são para facilitar seu acesso, mas na prática um array

multidimensional equivale a um unidimensional, cujo tamanho é o produto dos índices, ou seja, um array com

[5][5] elementos equivale a um com [25] elementos (na verdade, os elementos são armazenados em sequência

na memória).

Para melhor entendimento, vamos imaginar um array para armazenar as posições num tabuleiro de xadrez,

onde o primeiro índice representa as linhas e o segundo as colunas:

int tabuleiro[8][8];

Nunca esqueça: a numeração dos índices começa em zero!

Obviamente, as dimensões de um array não precisam ser iguais, podemos ter algo como:

int meuArray[5][3];

Um array multidimensional pode ser inicializado da mesma forma que um unidimensional, com todos os

elementos entre chaves:

int meuArray[5][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

Aqui meuArray[0][0] vale 1, meuArray[0][2] vale 3, meuArray[1][2] vale 6 e meuArray[4][2] vale 15. Esta

inicialização pode parecer meio confusa, por isto é preferível identificar os elementos de cada dimensão em seu

próprio conjunto de chaves:

Page 43: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

43 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

int meuArray[5][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}, {13, 14, 15} };

Desta forma fica bem mais claro o conjunto de valores que pertencem à primeira dimensão e a segunda

dimensão. Um array multidimensional também pode ter todos os seus elementos inicializados com o mesmo

valor:

int meuArray[5][3] = {0};

Veja o exemplo a seguir:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. int algumArray[2][5] = { {0,1,2,3,4}, {0,2,4,6,8} }; 6. for (int i = 0; i < 2; i++) 7. { 8. for (int j = 0; j < 5; j++) 9. { 10. cout << "algumArray[" << i << "][" << j << "]: "; 11. cout << algumArray[i][j]<< endl; 12. } 13. } 14. }

Saída

algumArray[0][0]: 0

algumArray[0][1]: 1

algumArray[0][2]: 2

algumArray[0][3]: 3

algumArray[0][4]: 4

algumArray[1][0]: 0

algumArray[1][1]: 2

algumArray[1][2]: 4

algumArray[1][3]: 6

algumArray[1][4]: 8

Análise

Na linha 5 criamos um array de duas dimensões, a primeira inicializada com os números de 0 a 4, e a segunda

com o dobro destes números. Na linha 6 iniciamos um loop for que contará os elementos da primeira dimensão

e na linha 8, dentro deste loop, iniciamos outro que contará a segunda dimensão. O resultado é exibido nas

linhas 10 e 11. Este array pode ser representado pela figura abaixo:

Quando você declara um array, você informa ao compilador exatamente quantos objetos pretende armazenar

nele e o compilador reserva a memória necessária, mesmo que você nunca a utilize. Mas se você precisar de um

array cujas dimensões você não sabe no momento da declaração, você deve usar estruturas de dados mais

avançadas e dinâmicas, como vector ou deque, que veremos posteriormente.

Page 44: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

44 Pesquisa e Desenvolvimento Tecnológico

Arrays de caracter e strings

Existe um tipo de array que merece atenção especial, que é um array de caracteres terminado com nulo. Este

array é considerado uma string, no estilo C, e todos os que usamos até agora com o comando cout são deste

tipo, como:

cout << "Alo, Strings";

Você pode declarar e inicializar uma string estilo C da mesma maneira que faz com qualquer outro array:

char cumprimento[] = {'A', 'l', 'o', ',', ' ', 'S', 't', 'r', 'i', 'n', 'g', '\0'}

Neste caso, cumprimento[] é uma string do tipo char inicializada com caracteres entre apóstrofos. O último

elemento, '\0', é o caracter null, que muitas funções do C++ reconhecem como o terminador para uma string

estilo C. Apesar de esta forma caracter a caracter funcionar, obviamente não é nada prática. C++ permite que

você use um atalho para o código anterior:

char cumprimento[] = "Alo, String";

Observe duas coisas nesta sintaxe:

1. Ao invés de usar caracteres entre apóstrofos separados por vírgulas e entre chaves, você utiliza um texto

entre aspas;

2. Você não precisa acrescentar o caracter null no final da string, pois o compilador se encarrega disto;

O tamanho de uma string estilo C inclui a quantidade de caracteres mais o caracter null, assim a string de nosso

exemplo tem o tamanho de 12: três para "Alo", um para ",", um para " ", seis para "String" e mais um para null. É

muito importante que você nunca se esqueça deste conceito de string do C/C++, que termina com null; este

desconhecimento é uma fonte constante de erros e problemas, principalmente para programadores de outras

linguagens que utilizam DLL's ou programas escritos em C/C++.

Você também pode criar array de caracteres não inicializados (apesar de não ser indicado), e é muito

importante que você não ultrapasse os limites deste array. Veja o exemplo:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. //char tamanhoErrado[10] = "Alo, Mundo"; 6. char buffer[80] = {'\0'}; 7. cout << "Digite a string: "; 8. cin >> buffer; 9. cout << endl << "Aqui esta o buffer: " << buffer; 10. return 0; 11. }

Saída

Digite a string: Tentando aprender

Aqui esta o buffer: Tentando

Análise

A linha 5 está comentada, caso contrário gerará um erro de compilação. Declaramos um array de char com

tamanho 10 (que é o tamanho de "Alo, Mundo"), mas esquecemos que precisamos mais um elemento para o

caracter null. O tamanho correto é 11!

Page 45: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

45 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Na linha 6 criamos outro array de char chamado buffer com capacidade de 80 elementos e inicializamos todos

estes elementos com null. Na linha 8 capturamos a entrada do teclado para este array e na linha 9 estamos

exibindo o que foi digitado. Observe que usamos o nome do array buffer sem nenhum índice.

Dois problemas podem ocorrer com este programa: primeiro, se o usuário digitar mais de 79 caracteres, cin

escreverá além do limite da variável (buffer overflow); segundo, se for digitado um espaço, cin vai entender que a

string terminou e não aceitará outros valores, apesar de você estar vendo a digitação. Para resolver estes

problemas, você deve usar um método (função) especial de cin chamado get(), que tem três parâmetros:

1. A variável a ser preenchida

2. O número máximo de caracteres

3. O delimitador que encerra a entrada

Veja o exemplo abaixo:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. char buffer[80] = {'\0'}; 6. cout << "Digite a string: "; 7. cin.get(buffer, 79); // Aceita até 79 caracteres ou enter 8. cout << endl << "Aqui esta o buffer: " << buffer; 9. }

Análise

Agora você pode digitar qualquer caracter, inclusive espaços e tabulações. Não há necessidade de especificar o

caracter terminador, pois o valor default de enter é suficiente.

Usando os métodos strcpy() e strncpy()

Existem muitas funções na biblioteca do C++ para trabalhar com strings, e muitas delas são para tratar strings

estilo C. Entre outras, existem duas funções para copiar uma string em outra: strcpy() e strncpy(). A primeira

copia uma string inteira para outra e a segunda copia uma determinada quantidade de caracteres de uma string

para outra. Veja o exemplo:

1. #include <iostream> 2. #include <string> 3. using namespace std; 4. int main() 5. { 6. char string1[] = "Nenhum homem eh uma ilha"; 7. char string2[80] = {'\0'}; 8. strcpy(string2, string1); 9. cout << "String1: " << string1 << endl << "String2: " << string2; 10. }

Saída

String1: Nenhum homem eh uma ilha

String2: Nenhum homem eh uma ilha

Análise

Page 46: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

46 Pesquisa e Desenvolvimento Tecnológico

Para trabalhar com strings, na linha 2 incluímos o arquivo string, que faz parte da biblioteca padrão. Nas linhas 6

e 7 declaramos as duas variáveis e na linha 8 usamos a função strcpy() para copiar string1 para string2 (observe

que a string destino vem antes da string fonte, nos parâmetros da função).

Você deve ter cuidado ao usar strcpy(), pois se a string de origem é maior que a de destino, strcpy() causará um

buffer overflow (gravará além do limite do array). Para evitar este erro, a biblioteca fornece a função strncpy(). A

diferença é que strncpy() aceita como parâmetro a quantidade de caracteres que deve ser copiada, evitando um

erro.

Vamos alterar nosso programa anterior para ver como funciona:

1. #include <iostream> 2. #include <string> 3. using namespace std; 4. int main() 5. { 6. const int TAMANHO_MAXIMO = 80; 7. char string1[] = "Nenhum homem eh uma ilha"; 8. char string2[TAMANHO_MAXIMO+1] = {'\0'}; 9. strncpy(string2, string1, TAMANHO_MAXIMO); // Mais seguro que strcpy() 10. cout << "String1: " << string1 << endl << "String2: " << string2; 11. }

Análise

Aqui a saída é a mesma do programa anterior, mas temos alguns detalhes a mais. Na linha 6 definimos uma

constante inteira para armazenar o tamanho máximo da string. Esta constante foi usada na declaração da string,

mas incrementada de um, exatamente para armazenar o caracter terminador null. Na linha 9 ela foi usada para

especificar quantos caracteres serão copiados por strncpy().

Classes string

C++ herda do C o estilo de string terminado com null e a biblioteca de funções que inclue strcpy e strncpy, mas

estas funções não estão integradas no framework orientado a objetos. Como todos os arrays, os arrays de

caracteres também são estáticos. Você define seu tamanho e não pode alterar depois da declaração. Eles

sempre reservarão a memória necessária para todos os elementos, mesmo que você não os utilize. Ultrapassar

os limites do array pode ser desastroso em todos os casos.

Como vimos nos exemplos anteriores, usando as funções estilo C, como strcpy e strncpy , deixam o ônus de

gerenciamento da memória para o programador. Por exemplo, antes de usar strcpy você deve ter certeza que a

variável destino tem capacidade suficiente para armazenar toda a string que será copiada, senão você cairá na

armadilha do buffer overflow. Esta limitação apresenta grandes desvantagens ao armazenar digitação do

usuário, por exemplo, que pode ser de tamanhos variados. O programador precisará alocar dinamicamente o

buffer de destino, seja determinando o tamanho da string digitada, ou usando um buffer estaticamente alocado,

como um array, cujo tamanho é uma estimativa otimista - que pode ser inadequada em tempo de execução.

Uma atividade como copiar string, que deveria ser trivial, é, entretanto perigosamente sujeita a falhas, que

podem abortar a aplicação em certos casos.

Para atender a estas frequentes necessidades, a biblioteca padrão do C++ inclui uma classe string, que torna o

trabalho mais fácil, fornecendo um conjunto encapsulado de dados e funções. Esta classe se encarrega dos

detalhes de alocação de memória e torna fáceis as tarefas de atribuição de valores, copia e outras funções com

strings. Veja o exemplo abaixo:

1. #include <iostream>

Page 47: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

47 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

2. #include <string> 3. 4. using namespace std; 5. 6. int main() 7. { 8. string string1 = "Uma string C++"; 9. cout << "string1 = " << string1 << endl; 10. 11. string string2; 12. string2 = string1; 13. cout << "string2 = " << string2 << endl; 14. 15. string2 = "Alo, strings"; 16. cout << "Agora string2 contem: " << string2 << endl; 17. 18. string somaString = string1 + " - " + string2; 19. cout << "Resultado da soma de strings: " << somaString << endl; 20. 21. return 0; 22. }

Saída

string1 = Uma string C++

string2 = Uma string C++

Agora string2 contem: Alo, strings

Resultado da soma de strings: Uma string C++ - Alo, strings

Análise

Sem entrar em conceitos mais profundos sobre o uso da classe string, uma rápida olhada neste programa

mostra como é muito mais fácil e intuitivo trabalhar com strings, seja na declaração, atribuição de valores, cópia

ou concatenação (soma). Aqui uma string é tratada como se fosse um tipo nativo do C++, apesar de

internamente continuar sendo um array de caracteres terminado com null. Não se esqueça do #include <string>

e observe que se não estivéssemos declarando using namespace std, a sintaxe completa da classe string seria

std::string. Em lições posteriores, trabalharemos bem mais com strings.

Perguntas e respostas

O que existe num elemento de array não inicializado?

- Qualquer coisa que estiver na memória naquele momento. O resultado de usar membros de array não

inicializados é imprevisível.

Posso combinar arrays?

- Sim, com arrays simples você pode usar pointers para combiná-los num único array, novo e maior. Com

strings, você pode utilizar alguma das funções embutidas, como strcat.

Qual a vantagem de classes de arrays dinâmicos, como vector?

- Usando arrays dinâmicos o programador não precisa saber da quantidade de itens que o array conterá, em

tempo de compilação. Arrays dinâmicos redimensionam-se automaticamente para atender os requisitos da

aplicação. Além disto, estas classes possuem muitas funções que não estão disponíveis para arrays estáticos.

A classe string deve usar um buffer interno de char para reter o conteúdo da string?

- Não, ela pode usar qualquer meio de armazenagem que o programador achar melhor.

Page 48: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

48 Pesquisa e Desenvolvimento Tecnológico

Teste

1 - Qual é o primeiro e último elemento do array algumArray[25]?

2 - Como se declara um array multidimensional?

3 - Inicialize todos os membros do array algumArray[2][3][2] para zero.

4 - Quantos elementos existem no array algumArray[10][5][20]?

5 - Quantos caracteres são armazenados na string "Eu conheço C++"?

6 - Qual o último caractere na string "Andrin é um cara legal"?

Exercícios

1 - Declare um array multidimensional que represente o jogo da velha.

2 - Escreva o código que inicializa todos os elementos do exercício anterior para 0.

3 - Escreva um programa que contenha quatro arrays. Três destes arrays devem conter o primeiro nome, nome

do meio e sobrenome de alguém. Use a função de copia de strings para concatenar estes três arrays no quarto,

nome completo.

4 - Caça-Erros: o que está errado neste fragmento de código?

unsigned short algumArray[5][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 5; j++) algumArray[i][j] = i + j;

Page 49: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

49 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Lição 5

Trabalhando com Expressões, Instruções e Operadores

Em sua essência, um programa é um conjunto de comando executados em sequência. O poder de um programa

vê da sua capacidade de executar um ou outro conjunto de comandos, levando-se em conta se uma condição

em particular é verdadeira ou não.

Nessa lição você aprenderá:

O que são instruções

O que são blocos

O que são expressões

Como ramificar seu código baseado em condições.

O que é verdade e como agir baseando-se nela.

Iniciando com instruções

Em C++ uma instrução controla a sequência de execução, avalia uma expressão ou não faz nada (a instrução

null). Todas as instruções C++ terminam com um ponto e vírgula (;) e mais nada. Uma das instruções mais

comuns é a seguinte atribuição:

x = a + b;

Ao contrário da álgebra, esta instrução não significa que x é igual a a + b. Ao invés disto, esta expressão é lida

como "atribua o valor da soma de a mais b à x" ou "atribua a x, a + b" ou "faça x igual a mais b". Esta instrução

está fazendo duas coisas: primeiro, somando a e b e depois atribuindo o resultado a x usando o operador de

atribuição (=). Apesar de estar fazendo duas coisas, é apenas uma instrução e tem somente um ponto e vírgula.

Usando espaços em branco

Espaços em branco são caracteres invisíveis como tabulações, espaços e quebras de linha, normalmente

ignorados em instruções. A instrução anterior poderia ser escrita como:

x=a+b; x = a + b ;

Poderia ser escrita dessa forma ou usando qualquer outra forma que você imaginar. O que importa aqui é o

ponto e vírgula encerrando a instrução. Espaços em branco podem ser usados para tornar seu programa mais

legível ou para criar horríveis e indecifráveis códigos, fica a seu critério.

No Visual Studio você pode escolher as opções de formatação automáticas do código acessando o menu Tools,

Options; no painel à esquerda, abra o grupo Text Editor, abra All Languages e clique em Tabs. No painel à direita

você pode escolher o tipo de indentação no grupo Indenting e definir as opções de tabulação no grupo Tabs.

Em nossos exemplos, usamos Tab Size = 2, Indenting Size = 2 e marcamos a opção Insert Spaces. Esta opção

Page 50: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

50 Pesquisa e Desenvolvimento Tecnológico

transforma os espaços de tabulação em espaços em branco, o que pode facilitar bastante quando você acessa o

código com outro editor.

Blocos e instruções compostas

Em qualquer lugar que você pode colocar uma instrução, também pode pôr uma instrução composta, também

chamada bloco de instruções ou simplesmente bloco. Um bloco começa com uma chave de abertura { e termina

com uma chave de fechamento }. Apesar de todas as instruções dentro do bloco terminarem com ponto e

vírgula, o bloco por si só não precisa disto, como abaixo:

{ temp = a; a = b; b = temp; }

Este bloco age como uma única instrução e alterna os valores entre as variáveis a e b. Observe que as linhas do

bloco começam com uma tabulação ou espaços em branco, que não são obrigatórios, obviamente, mas uma

convenção da linguagem. É muito comum se iniciar um bloco de comandos, usando {, e esquecer-se de fechá-lo

com }. Apesar do editor do Visual Studio e de outras IDE's normalmente indicarem isto, é uma boa prática, ao

criar um bloco, já colocar as chaves de abertura e fechamento, e escrever o código entre elas.

Expressões

Qualquer coisa que se torna um valor é uma expressão em C++. Costuma-se dizer que uma expressão retorna

um valor. Assim, a instrução 3 + 2 que retorna o valor 5 é uma expressão. Todas as expressões são instruções. A

quantidade de partes de código que são considerados expressões pode surpreendê-lo, como os exemplos

abaixo:

3.2 // Retorna o valor 3.2 PI // Constante float que retorna 3.1415 SEGUNDOS_POR_MINUTO // Constante inteira que retorna 60

Presumindo que PI foi definido como uma constante float e inicializado com o valor 3.1415 e que

SEGUNDOS_POR_MINUTO foi definido como uma constante int e inicializado com 60, as três linhas do exemplo

são expressões. Uma expressão mais complicada como x = a + b não apenas soma a e b e atribui o resultado a

x, mas também retorna o valor de x. Então esta instrução de atribuição é também uma expressão.

Cabe notar que qualquer expressão pode ser usada a direita do sinal de atribuição (=), e o código abaixo é

perfeitamente válido em C++:

y = x = a + b;

Esta linha é avaliada na seguinte ordem:

1. Somar a e b

2. Atribuir o retorno da expressão a + b à x

3. Atribuir o retorno da expressão x = a + b à y

Veja no exemplo abaixo vários tipos de atribuição:

1. #include <iostream> 2. using namespace std; 3. int main()

Page 51: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

51 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

4. { 5. int a = 0, b = 0, x = 0, y = 35; 6. cout << "a:" << a << " b:" << b 7. << " x:" << x << " y:" << y << endl; 8. a = 9; 9. b = 7; 10. x = y = a + b; 11. cout << "a:" << a << " b:" << b; 12. cout << " x:" << x << " y:" << y << endl; 13. return 0; 14. }

Saída

a:0 b:0 x:0 y:35

a:9 b:7 x:16 y:16

Análise

Na linha 5 declaramos e inicializamos quatro variáveis. Nas linhas 6 e 7 estamos exibindo o valor destas

variáveis. Observe que a linha 6 não termina com ; o que indica que a instrução continua na linha seguinte. Nas

linhas 8 e 9 atribuímos novos valores às variáveis a e b e na linha 10 atribuímos o retorno de expressões a x e y.

Trabalhando com operadores

Um operador é um símbolo que faz o compilador tomar uma ação. Operadores agem sobre operandos, e em

C++ qualquer expressão pode ser um operando. C++ tem várias categorias de operadores e inicialmente

veremos dois:

Operadores de atribuição

Operadores matemáticos

Operadores de atribuição

Já vimos o operador de atribuição (=), que atribui ao operando à sua esquerda o valor da expressão à sua

direita.

Um operando que pode estar do lado esquerdo do operador de atribuição é conhecido como l-value

(abreviatura de left-value, valor à esquerda) e o que pode estar do lado direito como r-value (abreviatura de

right-value, valor à direita). Note que todos os l-values podem ser r-values, mas a recíproca não é verdadeira.

Você pode escrever x = 5, mas obviamente não pode usar 5 = x. Constantes são r-values após a primeira

atribuição, pois não poderão mais estar à esquerda (receber a atribuição).

Operadores matemáticos

Os cinco operadores matemáticos são soma (+), subtração (-), multiplicação (*), divisão (/) e módulo (%), que é o

resto da divisão de dois números.

Problemas com subtração

A subtração de números inteiros sem sinal (unsigned int) pode causar surpresas se o resultado for negativo. Já

vimos este assunto na lição 4 quando falamos de overflow (estouro) de variáveis. A listagem abaixo mostra o

que acontece quando subtraímos um número grande de um pequeno, ambos sem sinal:

Page 52: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

52 Pesquisa e Desenvolvimento Tecnológico

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. unsigned int diferença; 6. unsigned int numeroGrande = 100, numeroPequeno = 50; 7. diferença = numeroGrande - numeroPequeno; 8. cout << "Diferenca = " << diferença << endl; 9. cout << "Outra diferenca = " << (numeroPequeno - numeroGrande) << endl; 10. return 0; 11. }

Saída

Diferenca = 50

Outra diferenca = 4294967246

Análise

O resultado da primeira operação é normal, mas a segunda pode surpreender. O valor deveria ser -50, mas

como os números não aceitam números negativos (unsigned), ocorre um overflow, como foi visto na lição 3.

Divisão de inteiros e módulo

Quando você divide 21 por 4, usando números inteiros, a resposta é 5 com um resto de 1. O operador módulo

(%) retorna exatamente este resto. Assim, se você usar 21 % 4 o resultado será 1 (o resto da divisão). Quando o

resto é igual a zero, os números são divisíveis entre si.

Por exemplo, se o módulo da divisão de qualquer número por 2 é zero, significa que o número é par (divisível

por 2). Observe que o resultado de 5 / 3 é 1, e o resultado de 5 % 3 é 2. Se você precisar de divisão com

resultados exatos, use os tipos float ou double, assim a divisão de 5 por 3 daria 1.66667.

Combinando atribuições e operadores matemáticos

É comum você precisar adicionar um valor a uma variável e atribuir o resultado à mesma variável. Normalmente,

se faz uma construção do tipo:

int minhaIdade = 20;

minhaIdade = minhaIdade + 4;

C++ permite um método mais simples:

minhaIdade += 4;

Aqui usamos o operador de auto atribuição (+=) que retorna o mesmo resultado que minhaIdade = minhaIdade

+ 4. A auto atribuição também existe para os demais operadores, subtração (-=), multiplicação (*=), divisão (/=)

e módulo (%=).

Incrementando e decrementando

Incrementar (somar 1) e decrementar (subtrair 1) são operações muito comuns em programação. Normalmente,

você usa uma construção do tipo:

minhaIdade = minhaIdade + 1;

Page 53: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

53 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Ou poderia usar o operador de auto atribuição:

minhaIdade += 1;

C++ permite ainda uma terceira forma, mais simples e muito comum, usando o operador de incremento (++)

ou decremento (--):

minhaIdade++;

minhaIdade--;

Agora você sabe o porquê do nome C++: é um C incrementado!

Prefixando ou posfixando

Os operadores de incremento e decremento podem ser usados de duas formas: como prefixo (antes do nome

da variável, como ++minhaIdade) ou como sufixo (depois do nome da variável, como em minhaIdade++). Numa

instrução simples não faz diferença a posição do operador, mas em operações mais complexas a diferença é

grande. Veja o exemplo:

int x = 10;

int a = ++x;

Na segunda linha, o valor da variável a será 11 e o valor de x também, ou seja, antes de atribuir o valor de a, x

será incrementado. Entretanto se você inverter a posição do operador, o resultado será diferente:

int a = x++;

Aqui o valor de a será 10 e o valor de x será 11, pois x será incrementado após ser atribuído à a.

Ao usar os operadores de incremento ou decremento, certifique-se que estão na posição correta para o

resultado esperado. Vejamos um exemplo:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. int minhaIdade = 39; 6. int suaIdade = 39; 7. cout << "Eu tenho " << minhaIdade << " anos" << endl; 8. cout << "Voce tem " << suaIdade << " anos" << endl; 9. ++minhaIdade; 10. suaIdade++; 11. cout << "Um ano depois..." << endl; 12. cout << "Eu tenho " << minhaIdade << " anos" << endl; 13. cout << "Voce tem " << suaIdade << " anos" << endl; 14. cout << "Mais um ano..." << endl; 15. cout << "Eu tenho " << minhaIdade++ << " anos" << endl; 16. cout << "Voce tem " << ++suaIdade << " anos" << endl; 17. cout << "Mostrando novamente..." << endl; 18. cout << "Eu tenho " << minhaIdade << " anos" << endl; 19. cout << "Voce tem " << suaIdade << " anos" << endl; 20. return 0; 21. }

Saída

Eu tenho 39 anos

Voce tem 39 anos

Page 54: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

54 Pesquisa e Desenvolvimento Tecnológico

Um ano depois...

Eu tenho 40 anos

Voce tem 40 anos

Mais um ano...

Eu tenho 40 anos

Voce tem 41 anos

Mostrando novamente...

Eu tenho 41 anos

Voce tem 42 anos

Análise

Nas linhas 5 e 6 as duas variáveis são declaradas e inicializadas com o mesmo valor e nas linhas 7 e 8 estes

valores são exibidos. Na linha 9 minhaIdade é pré-incrementada e na linha 10 suaIdade é pos-incrementada. As

linhas 12 e 13 tornam a exibir seus valores, que estão corretamente incrementados. Na linha 15 minhaIdade é

exibida novamente, mas com o operador de pos-incremento. Neste caso, o valor atual é exibido (40) e depois

incrementado. Na linha 16 suaIdade é exibida novamente, mas com o operador de pre-incremento. Aqui a

variável primeiro é incrementada, depois exibida. As linhas 18 e 19 mostram o resultado final.

Entendendo a precedência de operadores

Observe a instrução abaixo.

x = 5 + 3 * 8;

Qual a operação é executada primeira, a adição ou a multiplicação? Se a adição for executada antes, a resposta

é 64, se a multiplicação for executada antes, a resposta é 29.

O padrão do C++ não deixa esta ordem aleatória, ao contrário, cada operador tem um valor de precedência.

Multiplicação tem precedência maior que adição, então a resposta será 29. Quando dois operadores têm a

mesma precedência, sua execução é da esquerda para a direita. Por exemplo, observe a próxima expressão:

x = 5 + 3 + 8 * 9 + 6 * 4;

As multiplicações são avaliadas primeiro, da esquerda para a direita, ou seja, 8 * 9 = 72 e 6 * 4 = 24. Com isso, a

expressão se torna:

x = 5 + 3 + 72 + 24;

Por fim são feitas as adições, da esquerda para a direita: 5 + 3 = 8; 8 + 72 = 80; 80 + 24 = 104.

Cuidado com isto: alguns operadores, como o de atribuição, são avaliados da direita para a esquerda.

Em todo caso, qual ordem de precedência satisfaz suas necessidades? Considere a seguinte expressão:

totalSegundos = numeroMinutosPensar + numeroMinutosDigitar * 60;

Nesta expressão você não quer multiplicar numeroMinutosDigitar por 60 e depois adicionar à

numeroMinutosPensar. Na realidade, você quer adicionar as duas variáveis e multiplicar o resultado por 60.

Para isto usam-se parênteses para mudar a ordem de precedência, então o exemplo deve ser reescrito como a

expressão a seguir.

totalSegundos = (numeroMinutosPensar + numeroMinutosDigitar) * 60;

Page 55: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

55 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Parênteses aninhados

Para expressões complexas, você precisa aninhar parênteses, um dentro do outro. Por exemplo, você computar

o total de segundos e então computar a quantidade de pessoas que estão envolvidas antes de multiplicar

segundos por pessoas:

totalPessoasSegundos = (((numeroMinutosPensar + numeroMinutosDigitar) * 60) * (pessoasTrabalhando + pessoasFerias));

Esta expressão complicada é lida de dentro para fora, ou seja, primeiro numeroMinutosPensar é adicionada a

numeroMinutosDigitar, pois são os parênteses mais internos – esta soma então é multiplicada por 60; depois,

pessoasTrabalhando é adicionada a pessoasFerias; finalmente, o total de pessoas é multiplicado pelo total de

segundos.

Esta expressão é fácil para um computador entender, mas difícil para um humano ler, entender e modificar. Veja

a mesma expressão reescrita, usando variáveis temporárias:

totalMinutos = numeroMinutosPensar + numeroMinutosDigitar; totalSegundos = totalMinutos * 60; totalPessoas = pessoasTrabalhando + pessoasFerias; totalPessoasSegundos = totalPessoas * totalSegundos;

Este exemplo é maior e usa mais variáveis, mas é muito mais fácil de entender. Se você acrescenta um

comentário para explicar o que o código faz e muda o valor 60 para uma constante simbólica, o código se torna

fácil de entender e manter.

A natureza do verdadeiro

Toda expressão pode ser avaliada como verdadeira ou falsa. Expressões que avaliam matematicamente para

zero retornam false. Qualquer outro valor retornado é true. Em versões anteriores do C++, true e false eram

representados por inteiros, mas o padrão ANSI introduziu o tipo bool, que só pode ter dois valores: true ou false.

Avaliando com operadores relacionais

Operadores relacionais são usados para comparar duas expressões e avaliar se são iguais, diferentes, maior ou

menor uma em relação a outra. Toda instrução relacional avalia para true ou false e todos os operadores

relacionais retornam um valor bool. Em versões anteriores do C++, estes operadores retornavam 0 para falso e

qualquer outro valor para verdadeiro.

Se uma variável inteira minhaIdade tem o valor 45 e outra variável suaIdade tem o valor 50, você pode

determinar se elas são iguais usando o operador relacional de igualdade (==):

minhaIdade == suaIdade; // retorna false minhaIdade < suaIdade; // retorna true

Cuidado: muitos programadores iniciantes em C++ confundem o operador de atribuição (=) com o operador de

igualdade (==) e isto pode causar um erro desagradável em seu programa.

A tabela abaixo mostra os operadores relacionais com exemplos:

Nome Operador Exemplo Retorno

Igual == 100 == 50 false

Diferente != 100 != 50 true

Page 56: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

56 Pesquisa e Desenvolvimento Tecnológico

Maior que > 50 > 100 false

Maior ou igual a >= 100 >= 50 true

Menor que < 50 < 100 true

Menor ou igual a <= 100 <= 50 false

A instrução if

A instrução if permite que você teste uma condição e execute diferentes partes do código dependendo do

resultado. A forma mais simples de uma instrução if é:

if (expressão) instrução;

A expressão entre parênteses pode ser qualquer expressão, mas normalmente contém um ou mais operadores

relacionais. Se a expressão retornar true, a instrução a seguir será executada, caso contrário será ignorada.

if (numeroGrande > numeroPequeno) numeroGrande = numeroPequeno;

Como um bloco de comandos entre chaves equivale a uma instrução, podemos ter um código como:

if (expressão) { instrução1; instrução2; instrução3; }

Como no exemplo a seguir:

if (numeroGrande > numeroPequeno) { numeroGrande = numeroPequeno; std::cout << "Numero grande:" << numeroGrande << endl; std::cout << "Numero pequeno:" << numeroPequeno << endl; }

Veja um exemplo de ramificação do código baseado em operadores relacionais:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. int placarFlamengo, placarBotafogo; 6. cout << "Informe o placar do Flamengo:"; 7. cin >> placarFlamengo; 8. cout << "Informe o placar do Botafogo:"; 9. cin >> placarBotafogo; 10. cout << endl; 11. if (placarBotafogo > placarFlamengo) 12. cout << "Vamos la, Botafogo!" << endl; 13. if (placarBotafogo < placarFlamengo) 14. cout << "Vai, Flamengo!" << endl; 15. if (placarBotafogo == placarFlamengo) 16. { 17. cout << "Jogo empatado!" << endl; 18. } 19. }

Saída

Page 57: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

57 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Informe o placar do Flamengo:5

Informe o placar do Botafogo:10

Vamos la, Botafogo!

Análise

O programa pede que o usuário digite o placar para dois times e faz três testes. Observe que as linhas 11 e 12

formam uma única instrução (o ponto e vírgula está no final da linha 12). Se você colocar ponto e vírgula após a

expressão de if, o resultado será completamente diferente:

if (algumValor < 10); // Veja o ponto e vírgula! algumValor = 10;

Aqui a instrução if acaba na mesma linha e a linha seguinte será sempre executada, não importando o resultado

do teste. O compilador C++ não detecta isto como erro.

Estilos de indentação

Existem muitas variações possíveis para a indentação de uma instrução if, mas as mais usadas são:

if (expressão) { instruções } if (expressão) { instruções } if (expressão) { instruções }

Neste livro usamos a segunda opção, indentando as instruções dentro de chaves.

A instrução else

Normalmente, um programa toma um caminho se uma instrução for verdadeira ou outro se a instrução for

falsa. No programa anterior, por exemplo, usamos duas instruções if quando poderíamos usar apenas uma,

utilizando a instrução else:

if (expressão) instrução; else instrução;

Observe as duas formas de instrução if:

if (expressão) instrução; próximaInstrução; if (expressão) instrução1; else instrução2;

Page 58: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

58 Pesquisa e Desenvolvimento Tecnológico

próximaInstrução;

Veja que não existe ponto e vírgula na linha do if nem na linha do else. Em todos os casos, a instrução pode ser

um único comando como um bloco entre chaves.

Instruções if avançadas

Qualquer instrução pode ser usada numa cláusula if ou else, inclusive outros if ou else. Veja o exemplo abaixo:

if (expressão1) { if (expressão2) instrução1; else { if (expressão3) instrução2; else instrução3; } } else instrução4;

Observe que estas construções if...else são complexas, mas a indentação do código e o uso de chaves deixa

claro sua lógica. Veja o exemplo a seguir:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. /* 6. Pede por dois números. 7. Atribui estes números a primeiroNumero e segundoNumero. 8. Se primeiroNumero é maior que segundoNumero, 9. verifica se eles são divisíveis. 10. Se forem, verifica se são iguais. 11. */ 12. int primeiroNumero, segundoNumero; 13. cout << "Informe dois numeros\nPrimeiro:"; 14. cin >> primeiroNumero; 15. cout << "Segundo:"; 16. cin >> segundoNumero; 17. if (primeiroNumero >= segundoNumero) 18. { 19. if ((primeiroNumero % segundoNumero) == 0) // São divisíveis? 20. { 21. if (primeiroNumero == segundoNumero) 22. cout << "Numeros iguais\n"; 23. else 24. cout << "Sao divisiveis\n"; 25. } 26. else 27. cout << "Nao sao divisiveis\n"; 28. } 29. else 30. cout << "Segundo numero eh maior\n"; 31. }

Saída

Informe dois numeros

Page 59: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

59 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Primeiro:10

Segundo:5

Sao divisiveis

Análise

Considerando este código complexo, a primeira coisa que fizemos foi um comentário. Observe como vários

if...else estão aninhados um dentro do outro. Este é um caso típico que devemos abrir e fechar as chaves

correspondentes e depois incluir o código dentro delas. O editor do Visual Studio ajuda muito, fazendo a

indentação e destacando qual chave fecha a outra e a qual if pertence um else.

Este código também poderia ser escrito sem o uso de chaves:

if (primeiroNumero >= segundoNumero) if ((primeiroNumero % segundoNumero) == 0) // São divisíveis? if (primeiroNumero == segundoNumero) cout << "Numeros iguais\n"; else cout << "Sao divisiveis\n"; else cout << "Nao sao divisiveis\n"; else cout << "Segundo numero eh maior\n";

Apesar de a indentação deixar claro o aninhamento, isto não é uma boa construção, pois fica muita exposta a

erros. Sem as chaves, é muito fácil confundir um else com um if errado, e o resultado ser completamente

diferente. Veja um caso típico, em que o programador confiou apenas na indentação:

if (x >= 100) if (x > 100) cout << "Maior que 100"; else cout << "Menor que 100";

Aqui, apesar do else estar aninhado com o primeiro if, na verdade ele pertence ao segundo! O resultado correto

é obtido com o código abaixo:

if (x >= 100) { if (x > 100) cout << "Maior que 100"; } else cout << "Menor que 100";

Para minimizar muitos problemas que surgem com instruções if...else, procure usar sempre as chaves, mesmo

que em instruções simples:

if (expressão) { instrução1; } else { instrução2; }

Page 60: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

60 Pesquisa e Desenvolvimento Tecnológico

Usando operadores lógicos

Frequentemente, você precisa de mais de uma questão relacional de uma só vez: "É verdade que x é maior que

y, e também é verdade que y é maior que z?" ou "Se o alarme disparou E ainda não são 6:00 h E NÃO é feriado

OU é fim de semana, chame a polícia".

Os três operadores lógicos de C++ são usados para resolver estas questões:

Operador Símbolo Exemplo

AND && expressão1 && expressão2

OR || expressão1 || expressão2

NOT ! !expressão

O operador lógico AND

A instrução lógica AND usa o operador lógico AND (E) para conectar e avaliar duas expressões. Se ambas as

instruções são verdadeiras, a instrução lógica é verdadeira (retorna true):

if ((x == 5) && (y == 5))

Retorna true apenas se x E y forem iguais a 5. Note que o operador lógico AND são dois símbolos &&. O

símbolo & usado isoladamente com apenas uma ocorrência é outro operador, conhecido como AND binário,

que discutiremos posteriormente.

O operador lógico OR

O operador lógico OR (OU) avalia duas expressões e retorna true se uma das duas for verdadeira:

if ((x == 5) || (y == 5))

Retorna true se uma ou mais expressões forem verdadeiras. Assim como o &&, o operador OR são dois

símbolos ||. O símbolo | usado isoladamente com apenas uma ocorrência também é outro operador, conhecido

como OR binário.

O operador lógico NOT

A instrução lógica NOT retorna true se a expressão for falsa e false se a expressão for true, ou seja, ela inverte

(ou nega) a lógica da expressão:

if !(x == 5)

Retorna true se x NÃO for igual a 5. Equivale a expressão x != 5.

Atalho para avaliação lógica

De uma olhada na seguinte expressão AND.

if ((x == 5) && (y == 5))

Quando o compilador está avaliando uma expressão como a citada aqui, ele primeiro avalia a primeira

expressão, e se ela não for verdadeira, a segunda nem será executada, pois AND necessita que ambas sejam

verdadeiras.

Algo semelhante ocorre com a expressão OR a seguir.

Page 61: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

61 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

if ((x == 5) || (y == 5))

O compilador avaliará a primeira expressão e, se ela for verdadeira, a segunda não será avaliada, pois OR precisa

de que apenas uma das expressões seja verdadeira para retornar true.

Apesar deste conceito parecer sem importância, observe o exemplo a seguir:

if ((x == 5) && (++y == 3)

Aqui, se x não for igual a 5, a segunda expressão não será avaliada, e consequentemente y não será

incrementado.

Procedência relacional

Como todas as expressões C++, o uso de operadores relacionais ou operadores lógicos também retornam um

valor, true ou false. Como todas as expressões, eles também tem uma ordem de precedência que determina

qual relacionamento é avaliado primeiro. Isto é importante quando se está determinando o valor de uma

instrução como esta:

if (x > 5 && y > 5 || z > 5)

Parece que o programador quer que esta instrução retorne true se x e y fossem maiores que 5 ou z fosse maior

que 5. Ou talvez o programador queira que a instrução retorne true apenas se x for maior que 5 e y for maior

que 5 ou z for maior que 5.

Se x é 3 e y e z são 10, a primeira interpretação está correta (z é maior que 5, então ignore x e y), mas para a

segunda interpretação está errada (x não é maior que 5, então ignore o restante após o símbolo &&, pois os

dois lados devem ser verdadeiros).

Apesar da precedência determinar qual relação será avaliada primeiro, o uso de parênteses pode mudar esta

precedência e deixar a instrução muito mais clara:

if ( (x > 5) && (y > 5 || z > 5) )

Usando os valores estabelecidos, o resultado será false, porque x não é maior que 5 e se o lado esquerdo da

instrução AND falhou, o que está à direita de && nem será avaliado. É uma boa prática sempre usar parênteses

extras para deixar claro o que se quer agrupar.

Mais sobre verdade e falsidade

Em C++, zero é avaliado como falso, e qualquer outro valor como verdadeiro. Como uma expressão sempre tem

um valor, muitos programadores tiram vantagem desta característica. Um exemplo é a instrução a seguir:

if (x) x = 0;

A instrução é lida como "se x for diferente de zero, atribua-o zero". Isto é uma pequena trapaça. Seria mais claro

e correto escrever da seguinte maneira.

if (x != 0) x = 0;

As duas instruções são válidas, mas é uma boa prática de programação reservar a primeira forma para

verdadeiros testes de lógica e não para testar se um valor é zero ou não. As duas instruções a seguir são

equivalentes:

Page 62: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

62 Pesquisa e Desenvolvimento Tecnológico

if (!x) if (x == 0)

O segundo, entretanto, é mais fácil de entender se você está testando valores matemáticos e não estados

lógicos.

O operador condicional ternário

O operador condicional ?: é o único operador ternário do C++ (ou seja, que usa três termos). Ele possui a

seguinte sintaxe.

(expressão1) ? (expressão2) : (expressão3);

Nesse caso, se lê: "se expressão1 for verdadeira, retorne o valor de expressão2, senão retorne o valor de

expressão3". Tipicamente, este valor é atribuído a uma variável, como mostra o exemplo a seguir:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. int x, y, z; 6. cout << "Digite dois numeros.\n" << "Primeiro:"; 7. cin >> x; 8. cout << "Segundo:"; 9. cin >> y; 10. if (x > y) 11. z = x; 12. else 13. z = y; 14. cout << "Apos o teste, z=" << z << endl; 15. z = (x > y) ? x : y; 16. cout << "Apos o operador ternario, z=" << z << endl; 17. char resposta; 18. cin >> resposta; 19. return 0; 20. }

Saída

Digite dois numeros.

Primeiro:10

Segundo:20

Apos o teste, z=20

Apos o operador ternario, z=20

Análise

Este programa não tem nada de novo, a não ser a linha 15. Aqui, o operador ternário é usado para testar (x > y);

se for verdadeiro, retorna x, senão, retorna y. O retorno é atribuído a z. Ou seja, resumimos as instruções das

linhas 10 a 13 a apenas uma instrução.

Atenção: Os possíveis retornos passados como resultados no operador ternário devem ser do mesmo tipo. Uma

expressão ternária não pode, por exemplo, ter o retorno de uma string em um caso, ou de um int em outro.

Perguntas e respostas

Page 63: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

63 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Por que usar parênteses desnecessários quando a precedência dos operadores irá determinar qual será

avaliado primeiro?

- É verdade que o compilador saberá a precedência, e o programador pode sempre consultar a ordem de

precedência. Usando parênteses, entretanto, torna o código mais fácil de entender e manter.

Se os operadores relacionais sempre retornam true ou false, por que um valor diferente de zero é

considerado true?

- Esta convenção foi herdada da linguagem C, que frequentemente era usado para escrever código de baixo

nível, como sistemas operacionais, drivers ou programas de controle em tempo real. Provavelmente seu uso

desenvolveu-se como um atalho para testar se todos os bits são zero.

Os operadores relacionais retornam true ou false, mas qualquer expressão retorna um valor e este valor pode

ser avaliado numa instrução if. Veja o exemplo:

if ((x = a + b) == 35)

Isto é uma instrução C++ perfeitamente válida. Ela retornará um valor mesmo se a soma de a mais b não for 35,

ao mesmo tempo em que esta soma é atribuída a x.

Qual efeito que tabulações, espaços em branco e quebras de linha tem no programa?

- Nenhum, embora o seu uso inteligente torne o programa mais fácil de entender.

Números negativos são verdadeiros ou falsos?

- Qualquer número diferente de zero, positivo ou negativo, é verdadeiro.

Teste

1. Que é uma expressão?

2. x = 5 + 7 é uma expressão? Qual seu valor?

3. Qual o valor de 201 / 4?

4. Qual o valor de 201 % 4?

5. Se minhaIdade, a e b são todas variáveis inteiras, quais seus valores após esta sequência?

minhaIdade = 39; a = minhaIdade++; b = ++minhaIdade;

6. Qual o valor de 8 + 2 * 3?

7. Qual a diferença entre if (x = 3) e if (x == 3)?

8. Quais dos seguintes valores retornam true ou false?

A. 0

B. 1

C. -1

D. x = 0

E. x == 0 // Presumindo que x vale zero

Page 64: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

64 Pesquisa e Desenvolvimento Tecnológico

Exercícios

1 - Escreva uma instrução if que examina duas variáveis inteiras e troca o valor da maior pela menor, usando

apenas um else.

2 - Examine o programa abaixo, imagine digitar três números e escreva a saída que você espera:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. int a, b, c; 6. cout << "Digite tres numeros\n"; 7. cout << "a: "; 8. cin >> a; 9. cout << "\nb: "; 10. cin >> b; 11. cout << "\nc: "; 12. cin >> c; 13. if (c = (a-b)) 14. cout << "a: " << a << " menos b: " << b << " igual a c: " << c; 15. else 16. cout << "a-b diferente de c: "; 17. return 0; 18. }

3 - Digite o programa do exercício anterior, compile, e entre com os números 20, 10 e 50. Você obteve a saída

que esperava? Por que não?

4 - Examine este programa e antecipe seu resultado:

1. #include <iostream> 2. using namespace std; 3. int main() 4. { 5. int a = 2, b = 2, c; 6. if (c = (a-b)) 7. cout << "Valor de c: " << c; 8. return 0; 9. }

5 - Digite e execute o programa do exercício anterior. Qual foi a saída? Por quê?

Page 65: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

65 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Lição 6

Organizando o Código com Funções

Embora programação orientada a objetos tenha retirado o foco que era dado a funções e o tenha levado para

objetos, ainda assim, funções continuam sendo o componente centra de qualquer programa. Funções globais

podem existir fora do escopo das classes e dos objetos, e funções membros (algumas vezes chamadas métodos

membros) existem dentro de uma classe e fazem o que representa as funcionalidades dessa classe.

Que é uma função?

Uma função é, efetivamente, um subprograma que atua sobre dados e retorna um valor. Todo programa C++

tem pelo menos uma função: main(). Quando o programa inicia, main() é chamada automaticamente e pode

chamar outras funções e algumas destas chamar outras funções.

Como estas funções não são parte de um objeto, são chamadas funções globais, ou seja, podem ser acessadas

em qualquer parte do programa. Cada função tem seu próprio nome, e quando este nome é encontrado, a

execução do programa se desvia para o corpo da função. Isto é referido como chamar uma função. Quando a

função encerra (seja encontrando a instrução return ou a chave de fechamento final), a execução reinicia na

próxima linha após a chamada da função. Este fluxo é ilustrado abaixo:

Funções bem projetadas executam uma tarefa simples, específicas e fáceis de entender, identificadas pelo nome

de suas funções. Tarefas complicadas devem ser subdivididas em múltiplas funções.

Funções têm duas variedades: construídas pelo usuário ou embutidas. Funções embutidas fazem parte do

pacote do compilador e as construídas pelo usuário são aquelas que você escreve.

Page 66: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

66 Pesquisa e Desenvolvimento Tecnológico

Valores de retorno, parâmetros e argumentos

Como foi visto na lição 2, "A anatomia de um programa C++", uma função pode receber e retornar um valor.

Quando você chama uma função, ela executa e retorna o resultado de seu trabalho. Isto é chamado um valor de

retorno. O tipo deste retorno deve ser declarado previamente. Assim, se você escreve:

int MinhaFuncao();

Você está declarando uma função chamada MinhaFuncao que retornará um valor inteiro. Agora, considere a

seguinte declaração:

int MinhaFuncao(int algumValor, float outroValor);

Esta declaração indica que MinhaFuncao retornará um inteiro, mas também receberá dois valores.

Quando você envia valores para uma função, estes valores agem como variáveis que você pode utilizar dentro

da função. A descrição dos valores que você envia é chamada lista de parâmetros. No exemplo anterior, a lista

de parâmetros contém algumValor, que é uma variável do tipo inteira (int) e outroValor, que é do tipo ponto

flutuante (float). Um parâmetro descreve o tipo de valor que será passado à função quando ela é chamada e os

valores reais que são passados chamam-se argumentos. Considere o seguinte:

int valorRetornado = MinhaFuncao(5, 6.7);

Aqui a variável inteira valorRetornado é criada e inicializada com o valor retornado por MinhaFuncao, e os

valores 5 e 6.7 são passados como argumentos. Os tipos dos argumentos devem coincidir com os tipos

declarados nos parâmetros. Neste caso, 5 é um valor inteiro e 6.7 um ponto flutuante, seguindo o requerimento

da lista de parâmetros.

Declarando e definindo funções

Usar funções requer que você primeiro declare e depois defina a função. A declaração da função é chamada de

protótipo. Existem três maneiras de declarar uma função:

Escrever o protótipo num arquivo e usar a diretiva #include para incluir este arquivo em seu programa.

Escrever o protótipo no mesmo arquivo em que a função será usada.

Definir a função antes dela ser chamada por outra função. Neste caso, a definição age como seu próprio

protótipo.

Apesar de você poder definir a função antes de usá-la, evitando a necessidade de criar o protótipo da função,

isto não é uma boa prática de programação por três razões. Primeiro, é uma má idéia querer que as funções

apareçam numa ordem específica num arquivo. Isto torna difícil manter o programa quando os requisitos

mudam.

Segundo, é possível que a função A() seja capaz de chamar a função B(), mas a função B() também precisa ser

capaz de chamar a função A(), em certas circunstâncias. Neste caso, não é possível definir a função A() antes de

B(), nem definir a função B() antes da A(), então pelo menos uma delas deve ser declarada antes de definida.

Terceiro, protótipos de função são uma boa e poderosa técnica de depuração. Se seu protótipo declara que sua

função pega um conjunto particular de parâmetros, ou que retorna um tipo particular de valor, e a definição da

função não coincide com o protótipo, o compilador pode sinalizar seu erro ao invés de esperar que ele se

apresente em tempo de execução. Isto é como um lançamento bruto em contabilidade. O protótipo e a

definição verificam-se entre si, reduzindo a possibilidade de um simples erro de digitação causar um erro em

seu programa.

Page 67: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

67 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Apesar disto, a grande maioria dos programadores escolhe a terceira opção. Isto porque a redução de linhas de

código, a simplificação da manutenção (alterações no cabeçalho da função também requerem alterações no

protótipo), e a ordem das funções num arquivo são estabelecidas corretamente. Mesmo assim, protótipos são

necessários em algumas situações.

Protótipo de funções

Muitas das funções embutidas que você usa terão seus protótipos já escritos. Eles aparecem em arquivos que

você inclui em seu programa usando #include. Para funções escritas por você mesmo, você deve incluir o

protótipo.

O protótipo de uma função é uma instrução, significando que ele termina com um ponto e vírgula. Ele consiste

do tipo de retorno da função e sua assinatura. A assinatura de uma função é seu nome e sua lista de

parâmetros.

A lista de parâmetros é uma lista com todos os parâmetros e seus tipos, separados por vírgula. A figura abaixo

ilustra as partes de uma função:

O protótipo e a definição da função devem concordar corretamente sobre o tipo de retorno e a assinatura, caso

contrário você receberá um erro em tempo de compilação. Observe, entretanto, que o protótipo da função não

precisa conter os nomes dos parâmetros, apenas seus tipos. Um protótipo como este é perfeitamente válido:

long Area(int, int);

Este protótipo declara uma função chamada Area, que retorna um long e aceita dois int como parâmetros.

Apesar de válido, não é uma boa idéia. Acrescentando nomes aos parâmetros tornam seu protótipo mais claro.

A mesma função com parâmetros nomeados pode ser:

long Area(int comprimento, int largura);

Agora é muito mais claro o que a função faz e o que são os parâmetros.

Veja que todas as funções têm um tipo de retorno. Se nada for estabelecido explicitamente, o retorno padrão é

int. Seu programa será mais compreensível, entretanto, se você declarar explicitamente o tipo de retorno de

cada função, inclusive main().

Quando você não quer que uma função retorne um valor, você declara seu tipo de retorno como void (vazio),

como mostrado aqui:

void ImprimirNumero(int meuNumero);

Isto declara uma função chamada ImprimirNumero que recebe um parâmetro inteiro e não retorna nada.

Definindo a função

A definição de uma função consiste de seu cabeçalho e corpo. O cabeçalho é como o protótipo da função,

exceto que os parâmetros precisam ser nomeados e nenhum terminador ponto e vírgula é usado.

Page 68: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

68 Pesquisa e Desenvolvimento Tecnológico

O corpo de uma função é um conjunto de instruções dentro de chaves. A figura abaixo mostra o cabeçalho e

corpo de uma função:

O exemplo a seguir demonstra um programa que inclui o protótipo para a função Area();

1. #include <iostream> 2. 3. int Area(int comprimento, int largura); 4. 5. int main() 6. { 7. using std::cout; 8. using std::cin; 9. 10. int comprimentoDoQuintal = 0; 11. int larguraDoQuintal = 0; 12. int areaDoQuintal = 0; 13. 14. cout << "Qual a largura do seu quintal? "; 15. cin >> larguraDoQuintal; 16. cout << "Qual o comprimento do seu quintal? "; 17. cin >> comprimentoDoQuintal; 18. 19. areaDoQuintal = Area(comprimentoDoQuintal, larguraDoQuintal); 20. 21. cout << "Seu quintal tem "; 22. cout << areaDoQuintal; 23. cout << " metros quadrados\n\n"; 24. return 0; 25. } 26. 27. int Area(int comp, int larg) 28. { 29. return (comp * larg); 30. }

Saída

Qual a largura do seu quintal? 20

Qual o comprimento do seu quintal? 30

Seu quintal tem 600 metros quadrados

Análise

O protótipo para a função Area() está na linha 3. Compare o protótipo com a definição da função na linha 27.

Note que o nome, tipo de retorno e tipos de parâmetros são iguais. Se forem diferentes, será gerado um erro de

Page 69: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

69 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

compilação. Na verdade, a única diferença necessária é que o protótipo termine com um ponto e vírgula e não

tenha nenhum corpo.

Observe também que os nomes dos parâmetros no protótipo e na definição são diferentes. Como vimos os

nomes de parâmetros no protótipo não são usados, são como informações para o programador. É uma boa

prática de programação coincidir os nomes de parâmetros do protótipo com os da implementação, mas isto

não é exigido.

Os argumentos são passados para a função na ordem em que os parâmetros são declarados e definidos, mas

não ocorre nenhuma coincidência de nomes. Se você tivesse passado larguraDoQuintal seguido de

comprimentoDoQuintal, a função teria usado o valor em larguraDoQuintal como comprimento e

comprimentoDoQuintal como largura.

O corpo da função sempre está contido em chaves, mesmo que seja apenas uma instrução, como a do exemplo.

Execução de funções

Quando você chama uma função, a execução começa na primeira instrução após a chave de abertura. Desvios

podem ocorrer usando-se a instrução if. Funções também podem chamar outras funções e podem inclusive

chamar a si mesmas.

Quando uma função termina sua execução, o fluxo retorna para a função que a chamou. Quando a função

main() termina, o controle retorna para o sistema operacional.

Determinando o escopo de variáveis

Uma variável tem um escopo, que determina quando ela está disponível para o programa e onde pode ser

acessada. Variáveis declaradas dentro de um bloco são limitadas àquele bloco. Elas podem ser acessadas apenas

dentro das chaves do bloco, e não existem mais quando o bloco termina. Variáveis globais têm um escopo

global e estão disponíveis em qualquer parte do programa.

Variáveis locais

Você pode não apenas passar variáveis para uma função, mas também pode declarar variáveis dentro do corpo

da função. Variáveis declaradas dentro do corpo de uma função são chamadas locais porque existem apenas

localmente, dentro do corpo da função. Quando a função retorna, estas variáveis não estão mais disponíveis;

elas são marcadas para destruição pelo compilador.

Variáveis locais são declaradas da mesma maneira que outras variáveis. Os parâmetros passados para a função

também são considerados variáveis locais e são usadas exatamente como se tivessem sido definidas dentro do

corpo da função. O Exemplo a seguir mostra o uso de parâmetros e variáveis locais definidas dentro de uma

função:

1. #include <iostream> 2. 3. float Converte(float); 4. 5. int main() 6. { 7. using namespace std; 8. 9. float tempFar; 10. float tempCel; 11.

Page 70: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

70 Pesquisa e Desenvolvimento Tecnológico

12. cout << "Informe a temperatura em Fahrenheit: "; 13. cin >> tempFar; 14. tempCel = Converte(tempFar); 15. cout << "Temperatura em Celsius: " << tempCel << endl; 16. return 0; 17. } 18. 19. float Converte(float tempFar) 20. { 21. float tempCel; 22. tempCel = ((tempFar - 32) * 5) / 9; 23. return tempCel; 24. }

Saída

Informe a temperatura em Fahrenheit: 100

Temperatura em Celsius: 37.7778

Análise

Nas linhas 9 e 10 são declaradas duas variáveis float, uma para conter a temperatura em Fahrenheit e outra para

a temperatura em Celsius. O usuário entra com o valor em Fahrenheit na linha 13 e este valor é passado para a

função Converte() na linha 14. Com a chamada da função, a execução salta para a linha 21, onde uma variável

local também chamada tempCel é declarada. Veja que esta variável local não é a mesma declarada na linha 10;

esta existe apenas dentro da função Converte(). O valor passado como parâmetro, tempFar, também é apenas

uma cópia local da variável declarada em main().

Esta função poderia ter nomeado o parâmetro e a variável local em qualquer outro lugar e o programa

funcionaria igualmente bem. farTemp no lugar de tempFar ou celTemp no lugar de tempCel seriam válidas e a

função funcionaria da mesma forma. Você pode digitar estes nomes diferentes e testar a execução do

programa.

A variável local é atribuída com o valor que resulta de subtrair 32 do parâmetro tempFar, multiplicar por 5 e

dividir por 9. Este valor é então devolvido como valor de retorno da função. Na linha 14, este valor de retorno é

atribuído à variável tempCel na função main() e o valor é exibido na linha 15.

Variáveis locais dentro de blocos

Você pode definir variáveis em qualquer lugar de uma função, não apenas no seu início. O escopo da variável é

o bloco onde ela é definida. Assim se você define uma variável dentro de um conjunto de chaves interno a uma

função, a variável está disponível apenas neste bloco. O exemplo abaixo ilustra esta idéia:

1. #include <iostream> 2. 3. void MinhaFuncao(); 4. 5. int main() 6. { 7. int x = 5; 8. std::cout << "Em main, x = " << x; 9. 10. MinhaFuncao(); 11. 12. std::cout << "\nDe volta a main, x = " << x << std::endl; 13. return 0; 14. }

Page 71: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

71 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

15. 16. void MinhaFuncao() 17. { 18. int x = 8; 19. std::cout << "\nEm MinhaFuncao, x local = " << x; 20. 21. { 22. std::cout << "\nNo bloco dentro da funcao, x = " << x; 23. int x = 9; 24. std::cout << "\nVariavel x declarada no bloco = " << x; 25. } 26. 27. std::cout << "\nFora do bloco, em MinhaFuncao, x = " << x; 28. }

Saída

Em main, x = 5

Em MinhaFuncao, x local = 8

No bloco dentro da funcao, x = 8

Variavel x declarada no bloco = 9

Fora do bloco, em MinhaFuncao, x = 8

De volta a main, x = 5

Análise

O programa começa declarando uma variável local x, na linha 7, em main(). A saída na linha 8 exibe que x foi

inicializada com 5. Na linha 10, MinhaFuncao() é chamada.

Na linha 18, dentro de MinhaFuncao(), uma variável local também chamada x, é inicializada com o valor 8 e este

valor é exibido na linha 19.

A chave de abertura na linha 21 inicia um novo bloco. A variável x da função é exibida novamente na linha 22.

Uma nova variável também chamada x, mas local ao bloco, é criada na linha 23 e inicializada com 9. O valor da

mais nova variável x é exibido na linha 24. O bloco local termina na linha 25, e a variável criada na linha 23 sai do

escopo e não é mais visível.

Quando x é exibido na linha 27, este é o x que foi declarado na linha 18, dentro de MinhaFuncao(). Este x não foi

afetado pelo x definido na linha 23 do bloco, seu valor ainda é 8.

Na linha 28 MinhaFuncao() sai do escopo, e sua variável local x torna-se indisponível. A execução retorna para

linha 12, que exibe o valor da variável x declarada na linha 7. Ela não foi afetada por nenhuma das variáveis

declaradas em MinhaFuncao(). Desnecessário dizer que este programa seria bem menos confuso se as três

variáveis tivessem nomes diferentes!

Parâmetros são variáveis locais

Os argumentos passados para uma função são locais à função. Alterações feitas nestes argumentos não afetam

os valores na função de chamada. Isto é conhecido como passando por valor, que significa uma cópia local de

cada argumento é feita na função. Estas cópias locais são tratadas da mesma forma que qualquer outra variável

local. O exemplo abaixo ilustra este importante ponto:

1. #include <iostream> 2. 3. using namespace std; 4. 5. void Trocar(int x, int y);

Page 72: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

72 Pesquisa e Desenvolvimento Tecnológico

6. 7. int main() 8. { 9. int x = 5, y = 10; 10. 11. cout << "main. Antes da troca, x: " << x << " y: " << y << endl; 12. Trocar(x, y); 13. cout << "main. Depois da troca, x: " << x << " y: " << y << endl; 14. return 0; 15. } 16. 17. void Trocar(int x, int y) 18. { 19. int temp; 20. 21. cout << "Trocar. Antes da troca, x: " << x << " y: " << y << endl; 22. 23. temp = x; 24. x = y; 25. y = temp; 26. 27. cout << "Trocar. Depois da troca, x: " << x << " y: " << y << endl; 28. }

Saída

main. Antes da troca, x: 5 y: 10

Trocar. Antes da troca, x: 5 y: 10

Trocar. Depois da troca, x: 10 y: 5

main. Depois da troca, x: 5 y: 10

Análise

O programa inicializa duas variáveis em main() e as passa para a função Trocar(), que parece trocar seus valores.

Quando elas são examinadas novamente em main(), entretanto, permanecem inalteradas!

As variáveis são inicializadas na linha 9 e seus valores exibidos na linha 11. A função Trocar() é chamada na linha

12, e as variáveis passadas a ela.

A execução do programa vai para a função Trocar(), onde, na linha 21, seus valores são exibidos novamente. Elas

estão na mesma ordem que estavam em main(), como era esperado. Nas linhas 23 a 25, os valores são trocados,

e esta ação é confirmada pela exibição na linha 27. Na verdade, dentro da função Trocar() os valores foram

trocados. A execução então retorna à linha 13, de volta a main(), onde os valores não estão trocados.

Como você descobriu os valores passados para função Trocar() foram passados por valor, significando que

cópias dos valores foram feitas para serem locais a Trocar(). Estas variáveis locais foram trocadas nas linhas 23 a

25, mas as variáveis de main() não foram afetadas.

Na lição 8, "Pointers explicados", você verá alternativas à passagem por valor que permitirão os valores em

main() serem mudados.

Variáveis globais

Variáveis definidas fora de qualquer função têm o escopo global, e estão disponíveis para qualquer função do

programa, incluindo main(). Variáveis locais com os mesmos nomes das globais não mudam as variáveis globais.

Uma variável local com o mesmo nome de uma global, entretanto, oculta o valor da variável global. Se uma

Page 73: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

73 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

função tem uma variável com o mesmo nome de uma variável global, o nome refere-se à variável local - não à

global - quando usado dentro da função. Vamos ilustrar estes pontos:

1. #include <iostream> 2. 3. void MinhaFuncao(); // Protótipo 4. 5. int x = 5, y = 7; // Variáveis globais 6. 7. int main() 8. { 9. using namespace std; 10. 11. cout << "x em main: " << x << endl; 12. cout << "y em main: " << y << endl; 13. MinhaFuncao(); 14. cout << "De volta da funcao" << endl; 15. cout << "x em main: " << x << endl; 16. cout << "y em main: " << y << endl; 17. return 0; 18. } 19. 20. void MinhaFuncao() 21. { 22. using std::cout; 23. 24. int y = 10; 25. 26. cout << "x em MinhaFuncao: " << x << std::endl; 27. cout << "y em MinhaFuncao: " << y << std::endl; 28. }

Saída

x em main: 5

y em main: 7

x em MinhaFuncao: 5

y em MinhaFuncao: 10

De volta da funcao

x em main: 5

y em main: 7

Análise

Este programa simples ilustra uns poucos pontos chave, e potencialmente confusos, sobre variáveis locais e

globais. Na linha 5 duas variáveis globais, x e y, são declaradas. A variável global x é iniciada com o valor 5, e a

variável global y inicializada com o valor 7.

Nas linhas 11 e 12 na função main(), estes valores são exibidos. Veja que a função main() não definiu nenhuma

variável. Como elas são globais, estão disponíveis para main().

Quando MinhaFuncao() é chamada na linha 13, a execução do programa passa para a linha 20. Na linha 24, uma

variável local y é definida e inicializada com o valor 10. Na linha 26, MinhaFuncao() exibe o valor da variável x, e

a variável global x é usada, como foi em main(). Na linha 27, entretanto, quando a variável y é usada, a variável

local y é exibida, ocultando a variável global com o mesmo nome.

A chamada à função termina e o controle retorna para main(), que exibe novamente os valores das variáveis

globais. Veja que a variável global y não foi afetada pelo valor atribuído à variável local y, dentro de

MinhaFuncao().

Page 74: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

74 Pesquisa e Desenvolvimento Tecnológico

Variáveis globais: uma palavra de precaução

Em C++, variáveis globais são válidas, mas quase nunca são usadas. C++ cresceu a partir de C, e em C, variáveis

globais são uma ferramenta perigosa, mas necessária. São necessárias porque há caso em que o programador

precisa disponibilizar dados para muitas funções, e é incômodo passar estes dados como parâmetros de função

para outra função, especialmente quando muitas das funções na sequência de chamada somente recebem

parâmetros para passá-los a outras funções.

Variáveis globais são perigosas porque elas compartilham dados, e uma função pode alterar uma variável global

de uma forma invisível para outra função. Isto gera erros muito difíceis de encontrar.

Considerações para criar instruções em funções

Virtualmente não existe limite para a quantidade e tipos de instruções que podem ser colocadas no corpo de

uma função. Apesar de não se poder definir outra função dentro de uma função, você pode chamar uma função,

e naturalmente, main() faz exatamente isto em praticamente todos os programas C++. Funções podem inclusive

chamar elas mesmas, que discutiremos posteriormente em recursão.

Apesar de não existir limite para o tamanho de uma função em C++, funções bem projetadas tendem a serem

pequenas. Muitos programadores tomam o cuidado de manterem suas funções curtas o suficiente para

caberem inteiras numa única tela. Isto é uma regra de ouro frequentemente quebrada por muitos bons

programadores, mas a verdade é que uma função menor é mais fácil de manter e entender que uma maior.

Cada função deve cumprir uma tarefa simples e fácil de entender. Se suas funções começam a se tornar muito

grandes, procure por lugares onde você pode dividi-las em tarefas menores.

Mais sobre argumento de funções

Qualquer expressão C++ válida pode ser um argumento de função, incluindo constantes, expressões

matemáticas e lógicas e outras funções que retornam um valor. O importante é que o resultado da expressão

coincida com o tipo de argumento esperado pela função.

É valido, inclusive, uma função ser passada como argumento. Afinal, a função resultará em seu tipo de retorno.

Usar uma função como argumento, entretanto, pode criar código difícil de ler e de depurar.

Como exemplo, suponha que você tenha as funções Dobro(), Triplo(), Quadrado() e Cubo(), cada qual com seu

valor de retorno. Você pode escrever:

resposta = (Dobro(Triplo(Quadrado(Cubo(meuValor)))));

Você pode olhar esta instrução de duas maneiras. Primeiro você pode ver a função Dobro() pegando a função

Triplo() como argumento. Triplo(), por sua vez, pega a função Quadrado(), que pega a função Cubo() como seu

argumento. A função Cubo() pega a variável meuValor como argumento.

Olhando de outra forma, você pode ver a instrução pegando a variável meuValor e passando-o como

argumento para a função Cubo(), cujo retorno é passado como argumento para a função Quadrado(), cujo

retorno é passado para a função Triplo() e cujo retorno é passado para a função Dobro(). O valor de retorno

deste número duplo, triplo, quadrado e cúbico é atribuído a resposta.

Page 75: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

75 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

É difícil saber com certeza o que este código faz (o valor foi triplicado antes ou depois de ser quadrado?), e se a

resposta for errada, será difícil descobrir qual função falhou. Uma alternativa é atribuir cada passo a seu próprio

valor intermediário:

unsigned long meuValor = 2; unsigned long cubico = Cubo(meuValor); // cubico = 8 unsigned long quadrado = Quadrado(cubico); // quadrado = 64 unsigned long triplo = Triplo(quadrado); // triplo = 192 unsigned long resposta = Dobro(triplo); // resposta = 384

Agora cada valor intermediário pode ser examinado e a ordem de execução é explícita.

C++ torna fácil escrever código compacto como do exemplo que combinou as quatro funções. Só porque você

pode, não quer dizer que você deva. É melhor fazer seu código fácil de ler e manter, que fazê-lo o mais

compacto possível.

Mais sobre valores de retorno

Funções retornam um valor ou retornam void. void sinaliza ao compilador que nenhum valor será retornado.

Para retornar um valor de uma função, escreva a palavra chave return seguida do valor que você quer retornar.

Este valor pode ser uma expressão que retorna um valor. Por exemplo:

return 5; // retorna um número return (x > 5); // retorna o resultado de uma comparação return (MinhaFuncao()); // retorna o valor retornado por outra função

Todas são instruções return válidas, supondo que a função MinhaFuncao() retorne um valor. O valor na segunda

instrução, return (x > 5), será false se x não for maior que 5, caso contrário será true. O que será retornado é o

valor da expressão, false ou true, não o valor de x.

Quando a instrução return é encontrada, a expressão seguinte a return é devolvida como o valor da função. A

execução do programa retorna imediatamente para a função de chamada, e qualquer outra instrução após o

return não será executada.

É válido ter mais de uma instrução return numa única função. O exemplo abaixo ilustra esta idéia:

1. #include <iostream> 2. 3. int Duplo(int valorADobrar); 4. 5. int main() 6. { 7. using std::cout; 8. 9. int resultado = 0; 10. int entrada; 11. 12. cout << "Informe um numero entre 0 e 10.000 para dobrar: "; 13. std::cin >> entrada; 14. 15. cout << "\nAntes de Duplo() ser chamado..."; 16. cout << "\nentrada: " << entrada << " dobrado: " << resultado << "\n"; 17. 18. resultado = Duplo(entrada); 19. 20. cout << "\nVoltando de Duplo()...\n"; 21. cout << "\nentrada: " << entrada << " dobrado: " << resultado << "\n"; 22. 23. return 0;

Page 76: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

76 Pesquisa e Desenvolvimento Tecnológico

24. } 25. 26. int Duplo(int original) 27. { 28. if (original <= 10000) 29. return original * 2; 30. else 31. return -1; 32. std::cout << "Voce nao pode chegar aqui!\n"; 33. }

Saída

Informe um numero entre 0 e 10.000 para dobrar: 255

Antes de Duplo() ser chamado...

entrada: 255 dobrado: 0

Voltando de Duplo()...

entrada: 255 dobrado: 510

Análise

Um número é solicitado nas linhas 12 e 13 e exibido nas linhas 15 e 16 junto com a variável local resultado. A

função Duplo() é chamada na linha 18 e o valor entrada é passado como parâmetro. O resultado é atribuído à

variável resultado e os valores são re-exibidos nas linhas 20 e 21.

Na linha 28, dentro da função Duplo(), o parâmetro é testado para ver se é menor ou igual a 10.000. Se for, a

função retorna o dobro do valor original, caso contrário, retorna -1 como um valor de erro.

A instrução da linha 32 nunca será executada, pois independente se o número é menor ou igual a 10.000 ou

maior que 10.000, a função retorna na linha 29 ou 31 - antes de atingir a linha 32.

Parâmetros default

Para todo parâmetro que você declara no protótipo e definição de uma função, a chamada da função deve

passar um valor. O valor passado deve ser do tipo declarado. Assim, considere o protótipo de função abaixo.

long MinhaFuncao(int);

Para que você possa chamar essa função, a função precisa, de fato, receber uma variável inteira. Se a definição

da função difere, ou se você não passar um valor inteiro, receberá um erro do compilador.

A única exceção para esta regra é se o protótipo da função declara um valor default para o parâmetro. Um valor

default (ou por omissão) é o valor a ser usado se nenhum for fornecido. A declaração anterior pode ser reescrita

como:

long MinhaFuncao(int x = 50);

Este protótipo diz: "MInhaFuncao() retorna um long e toma um parâmetro int. Se um argumento não for

fornecido, use o valor default de 50". Como o nome do parâmetro não é exigido no protótipo da função, esta

declaração poderia ser escrita como:

long MinhaFuncao(int = 50);

Page 77: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

77 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

A definição da função não muda pela declaração de um parâmetro default. O cabeçalho na definição da função

seria:

long MinhaFuncao(int x)

Se a função de chamada não inclui um parâmetro, o compilador fixará x com o valor default de 50. O nome do

parâmetro default no protótipo não precisa ser o mesmo do cabeçalho da função, pois o valor default é

atribuído à posição, não ao nome.

Quaisquer dos parâmetros de função podem ter valores default atribuídos. A única restrição é: se algum

parâmetro não tem um valor default, nenhum parâmetro anterior pode ter.

Por exemplo, considere o caso de um protótipo como o seguinte:

long MinhaFuncao(int param1, int param2, int param3);

Nesse protótipo você pode atribuir um valor default para param2 somente se tiver atribuído para param3. Você

pode atribuir um valor default para param1 apenas se tiver atribuído valores default para param2 e param3. O

programa a seguir demonstra o uso de valores default:

1. #include <iostream> 2. 3. int AreaCubo(int comprimento, int largura = 25, int altura = 1); 4. 5. int main() 6. { 7. int comprimento = 100; 8. int largura = 50; 9. int altura = 2; 10. int area; 11. 12. area = AreaCubo(comprimento, largura, altura); 13. std::cout << "Primeira area = " << area << "\n"; 14. 15. area = AreaCubo(comprimento, largura); 16. std::cout << "Segunda area = " << area << "\n"; 17. 18. area = AreaCubo(comprimento); 19. std::cout << "Terceira area = " << area << "\n"; 20. return 0; 21. } 22. 23. int AreaCubo(int comprimento, int largura, int altura) 24. { 25. return (comprimento * largura * altura); 26. }

Saída

Primeira area = 10000

Segunda area = 5000

Terceira area = 2500

Análise

Na linha 3, o protótipo da função AreaCubo() recebe três parâmetros inteiros. O dois último tem valores default.

Page 78: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

78 Pesquisa e Desenvolvimento Tecnológico

Esta função calcula a área do cubo cujas dimensões são passadas a ela. Se nenhuma largura é passada, é usada

a largura de 25 e a altura de 1. Se a largura, mas não a altura, é passada, é usada a altura de 1. Não é possível

passar uma altura sem passar uma largura.

Nas linhas 7 a 9, as dimensões comprimento, largura e altura são inicializadas, e passadas à AreaCubo() na linha

12. Os valores são computados e o resultado exibido na linha 13.

A execução continua na linha 15, onde AreaCubo() é chamada novamente, mas sem um valor para altura. O

valor default é usado e novamente as dimensões são calculadas e exibidas.

A execução continua na linha 18, e desta vez nem largura nem altura são passados. Com esta chamada à

AreaCubo() a execução se desvia pela terceira vez para a linha 23. Os valores default são usados e a área é

computada. A execução à função main() onde o valor final é exibido.

Faça Não faça

Lembre-se que parâmetros de função

atuam como variáveis locais dentro da

função

Não tente criar um valor default para o

primeiro parâmetro se nenhum valor default

foi definido para o segundo

Lembre-se que alterações de uma variável

global numa função alteram esta variável

para todas as funções

Não esqueça que argumentos passados por

valor não podem afetar as variáveis na função

de chamada

Sobrecarga de funções

C++ permite que você crie mais de uma função com o mesmo nome. Isto é chamado sobrecarga (overload) de

função. A função deve diferenciar em sua lista de parâmetros, com tipos diferentes, quantidades diferentes ou

ambos. Aqui está um exemplo:

int MinhaFuncao(int, int); int MinhaFuncao(long, long); int MinhaFuncao(long);

MinhaFuncao() é sobrecarregada com três listas de parâmetros. A primeira e segunda versões diferem no tipo

do parâmetro e a terceira no número de parâmetros.

O tipo de retorno pode ser igual ou diferente em funções sobrecarregadas. Entretanto, funções sobrecarregadas

não podem diferenciar apenas no tipo do retorno; elas também devem aceitar um conjunto de parâmetros

diferenciado:

int MinhaFuncao (int); void MinhaFuncao (int); // inválido - diferem apenas no tipo de retorno void MinhaFuncao (long); // OK! void MinhaFuncao (long, long); // OK! int MinhaFuncao (long, long); // inválido - diferem apenas no tipo de retorno int MinhaFuncao (long, int); // OK! int MinhaFuncao (int, long); // OK!

Como você pode ver, é importante que versões sobrecarregadas de funções apresentem uma assinatura única

em termos de tipos de argumentos que elas aceitam.

Observação: duas funções com o mesmo nome e lista de parâmetros, mas diferentes tipos de retorno geram um

erro de compilação. Para alterar o retorno, você também deve alterar a assinatura (nome e/ou lista de

parâmetros).

Page 79: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

79 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Sobrecarga de função também é conhecida como polimorfismo de função. Poli significa muitos e morfo significa

forma. Assim, uma função polimórfica possui muitas formas.

Polimorfismo de função refere-se à capacidade de sobrecarregar a função com mais de uma maneira. Mudando

a quantidade ou tipo de parâmetros, você pode ter duas ou mais funções com o mesmo nome, e a correta é

chamada automaticamente de acordo com os parâmetros usados. Isto permite criar uma função que calcula a

média de inteiros, duplos ou outros tipos de valores sem ter que criar nomes individuais para cada uma, como

MediaInteiros(), MediaDuplos(), e assim por diante.

Suponha que você escreva uma função que duplica qualquer entrada fornecida. Você gostaria de poder passar

um int, um long, um float ou um double. Sem a sobrecarga de função você teria que criar quatro nomes de

função:

int DuploInt(int); long DuploLong(long); float DuploFloat(float); double DuploDouble(double);

Com sobrecarga de função, você faz esta declaração:

int Duplo(int); long Duplo(long); float Duplo(float); double Duplo(double);

A segunda versão é mais fácil de ler e usar. Você não precisa se preocupar com qual delas é chamada; você

simplesmente passa uma variável, e a função correta é chamada automaticamente. Veja o exemplo:

1. #include <iostream> 2. 3. int Duplo(int); 4. long Duplo(long); 5. float Duplo(float); 6. double Duplo(double); 7. 8. using namespace std; 9. 10. int main() 11. { 12. int meuInt = 6500; 13. long meuLong = 65000; 14. float meuFloat = 6.5F; 15. double meuDouble = 6.5e20; 16. 17. int intDobrado; 18. long longDobrado; 19. float floatDobrado; 20. double doubledDobrado; 21. 22. cout << "meuInt: " << meuInt << "\n"; 23. cout << "meuLong: " << meuLong << "\n"; 24. cout << "meuFloat: " << meuFloat << "\n"; 25. cout << "meuDouble: " << meuDouble << "\n"; 26. 27. intDobrado = Duplo(meuInt); 28. longDobrado = Duplo(meuLong); 29. floatDobrado = Duplo(meuFloat); 30. doubledDobrado = Duplo(meuDouble); 31. 32. cout << "intDobrado: " << intDobrado << "\n"; 33. cout << "longDobrado: " << longDobrado << "\n"; 34. cout << "floatDobrado: " << floatDobrado << "\n";

Page 80: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

80 Pesquisa e Desenvolvimento Tecnológico

35. cout << "doubledDobrado: " << doubledDobrado << "\n"; 36. return 0; 37. } 38. 39. int Duplo(int original) 40. { 41. cout << "Em Duplo(int)\n"; 42. return 2 * original; 43. } 44. 45. long Duplo(long original) 46. { 47. cout << "Em Duplo(long)\n"; 48. return 2 * original; 49. } 50. 51. float Duplo(float original) 52. { 53. cout << "Em Duplo(float)\n"; 54. return 2 * original; 55. } 56. 57. double Duplo(double original) 58. { 59. cout << "Em Duplo(double)\n"; 60. return 2 * original; 61. }

Saída

meuInt: 6500

meuLong: 65000

meuFloat: 6.5

meuDouble: 6.5e+020

Em Duplo(int)

Em Duplo(long)

Em Duplo(float)

Em Duplo(double)

intDobrado: 13000

longDobrado: 130000

floatDobrado: 13

doubledDobrado: 1.3e+021

Análise

A função Duplo() é sobrecarregada com int, long, float e double. Os protótipos estão nas linhas 3 a 6 e as

definições nas linhas 39 a 61.

No corpo da função main() são declaradas oito variáveis locais. Nas linhas 12 a 15 quatro destes valores são

inicializados e nas linhas 27 a 30 os outros quatro são atribuídos com os resultados da passagem dos primeiros

quatro para a função Duplo(). Quando Duplo() é chamada, a função de chamada não distingue qual chamar, ela

simplesmente passa um argumento, e a correta é invocada.

O compilador examina os argumentos e escolhe qual das quatro funções Duplo() chamar. A saída revela que as

quatro são chamadas, como esperado.

Page 81: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

81 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Tópicos especiais sobre funções

Como funções são fundamentais em programação, surgem alguns tópicos especiais que devem ser de interesse

quando você se depara com problemas incomuns. Usadas corretamente, funções em linha podem ajudar a

obter o máximo de desempenho.

Recursão de funções é um destes maravilhosos e esotéricos tipos de programação, os quais, de vez em quando,

podem tornar apenas trabalhosos problemas que seriam difíceis de resolver.

Funções em linha

Quando você define uma função, normalmente o compilador cria apenas um conjunto de instruções na

memória. Quando você chama a função, o programa desvia para estas instruções, e quando a função retorna, a

execução desvia novamente para a próxima linha após a chamada da função. Se você chamar a função dez

vezes, o programa desvia para o conjunto de instruções cada vez. Isto significa que existe apenas uma cópia da

função, e não dez.

Uma pequena perda de desempenho ocorre nestes desvios para dentro e fora de funções. Acontece que

algumas funções são muito pequenas, apenas uma ou duas linhas de código, e pode ser obtida mais eficiência

se o programa evitar estes saltos apenas para executar uma ou duas instruções. Quando programadores falam

de eficiência, normalmente querem dizer velocidade. O programa executa mais rápido se a chamada de função

puder ser evitada.

Se uma função for declarada com a palavra chave inline (em linha), o compilador não cria uma função real, mas

ele copia o código da função em linha diretamente para a chamada da função. Nenhum desvio é feito. De modo

simples, é como se você tivesse escrito as instruções da função diretamente na chamada da função.

Observe que funções em linha podem trazer um custo alto. Se a função é chamada dez vezes, o código em linha

será copiado para a chamada da função cada uma destas vezes. A pequena melhoria de velocidade que você

pode conseguir pode ser perdida pelo aumento do tamanho do executável, que pode realmente tornar o

programa mais lento.

A realidade hoje em dia é que compiladores otimizados podem quase certamente tomar uma decisão melhor

que a sua, desta forma não é uma boa idéia declarar funções em linha, a menos que seja uma, no máximo duas,

instruções. Na dúvida, deixe inline de fora. Alguns compiladores podem deliberadamente não acatar a marcação

inline feita pelo programador se a função for muito grande, e torná-la inline causaria um aumento significante

no executável.

Observação: otimização de desempenho é um desafio difícil, e a maioria dos programadores não são bons o

suficiente para identificar os locais de problemas de desempenho em seus programas sem uma ajuda.

A maneira correta de otimizar o desempenho é estudando o comportamento da aplicação usando profilers, que

podem apresentar uma variedade de estatísticas, desde o tempo gasto numa função específica até o número de

vezes que ela é chamada. Estas estatísticas ajudam ao programador focar seus esforços em partes do código

que realmente precisam de atenção, ao invés de usar a intuição e gastar tempo em artefatos que trazem pouco

ganho.

Por isto, é sempre melhor escrever código claro e compreensível do que escrever código que contém suas

suposições sobre executar mais rápido ou mais lento, mas é difícil de entender. Normalmente é mais fácil fazer

código compreensível executar mais rápido.

O exemplo a seguir demonstra o uso de funções em linha:

Page 82: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

82 Pesquisa e Desenvolvimento Tecnológico

1. #include <iostream> 2. 3. inline int Dobro(int); 4. 5. int main() 6. { 7. int alvo; 8. using std::cout; 9. using std::cin; 10. using std::endl; 11. 12. cout << "Digite um numero para trabalhar: "; 13. cin >> alvo; 14. cout << endl; 15. 16. alvo = Dobro(alvo); 17. cout << "Alvo:" << alvo << endl; 18. 19. alvo = Dobro(alvo); 20. cout << "Alvo:" << alvo << endl; 21. 22. alvo = Dobro(alvo); 23. cout << "Alvo:" << alvo << endl; 24. 25. return 0; 26. } 27. 28. int Dobro(int alvo) 29. { 30. return 2 * alvo; 31. }

Saída

Digite um numero para trabalhar: 10

Alvo:20

Alvo:40

Alvo:80

Análise

Na linha 3 Dobro() é declarado para ser uma função em linha que recebe um parâmetro int e retorna um int. A

declaração é como qualquer outro protótipo, exceto que a palavra chave inline é colocada antes do valor de

retorno. Isto resulta num código que é como se escrever

alvo = 2 * alvo;

cada vez que você digita

alvo = Dobro(alvo);

Quando seu programa executa, as instruções já estão no lugar, compiladas num arquivo .obj. Isto economiza um

desvio e retorno na execução, ao custo de um código maior.

Observação: a palavra chave inline é uma dica para o compilador que você quer que esta função seja em linha. O

compilador tem a liberdade de ignorar esta linha e fazer uma chamada real de função.

Page 83: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

83 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Recursão

Uma função pode chamar ela mesma. Isto é chamado recursão, e pode ser direta ou indireta. Uma recursão é

direta quando uma função chama a si mesma. A recursão indireta acontece quando uma função chama outra

função, que então chama a primeira função.

Alguns problemas são mais facilmente solucionados com recursão, normalmente aqueles em que você atua

sobre dados e estes dados atuam da mesma forma no resultado. Os tipos de recursão têm duas variedades:

aqueles que eventualmente terminam e produzem um resultado e aqueles que nunca terminam e produzem um

erro de execução. Programadores acham que o último é engraçado (quando acontece com os outros).

É importante observar que quando uma função chama a si mesma, uma nova cópia da função está executando.

As variáveis locais da segunda versão são independentes da primeira, e elas não podem afetar outras

diretamente mais que variáveis locais em main() podem afetar variáveis locais que ela chama.

Para ilustrar a solução de um problema usando recursão, considere a sequência de Fibonacci:

1, 1, 2, 3, 5, 8, 13, 21, 34...

Cada número, após o segundo, é a soma dos dois anteriores. Uma questão pode ser, por exemplo, determinar

qual o décimo segundo número da série.

Para resolver este problema, você deve examinar a sequência cuidadosamente. Os dois primeiros números são

1. Cada número subsequente é a soma dos dois números anteriores. Então, o sétimo número é a soma do sexto

com o quinto. Mais genericamente, o enésimo número é a soma de n-2 e n-1, desde que n > 2.

Funções recursivas precisam de uma condição de parada. Alguma coisa deve acontecer para que o programa

encerre a recursão ou ela nunca terminará. Na sequência de Fibonacci, n < 3 é a condição de parada (ou seja,

quando n for menor que 3, o programa para de trabalhar no problema).

Um algoritmo é um conjunto de passos que você segue para resolver um problema. O algoritmo para a

sequência de Fibonacci é:

1. Peça ao usuário uma posição na sequência.

2. Chame a função Fib() com esta posição, passando o valor informado.

3. A função Fib() examina o argumento (n). Se n < 3, a função retorna 1; senão, Fib() chama a si mesma

(recursivamente) passando n - 2. Então ela se chama novamente passando n - 1, e retorna a soma da

primeira chamada com a segunda.

Se você chama Fib(1), ela retorna 1. Se você chama Fib(2), ela retorna 1. Se você chama Fib(3), ela retorna a

soma de Fib(2) com Fib(1). Como Fib(2) e Fib(1) retornam 1, Fib(3) retorna 2 (a soma de 1 + 1).

Se você chama Fib(4), ela retorna a soma de Fib(3) e Fib(2). Você já viu que Fib(3) retorna 2 e que Fib(2) retorna

1, então Fib(4) retorna a soma dos dois, 3, que é o quarto número da sequência.

Fazendo mais um passo, se você chama Fib(5) ela retorna a soma de Fib(4) com Fib(3). Como você viu que Fib(4)

retorna 3 e Fib(3) retorna 2, a soma retornada será 5.

Este método não é a forma mais eficiente de resolver este problema (em Fib(20) a função será chamada 13.529

vezes!), mas funciona. Cuidado: se você informar um número muito grande, ficará sem memória. Cada vez que

Fib() é chamada, uma memória é reservada e quando ela retorna, esta memória é liberada. Com recursão, a

memória continua reservada antes de ser liberada e este sistema pode consumir rapidamente a memória. O

exemplo a seguir implementa a função Fib().

Page 84: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

84 Pesquisa e Desenvolvimento Tecnológico

Cuidado: quando executar este exemplo, observe que a entrada de um número muito grande resultará em mais

chamadas de função recursiva, e consequentemente, grande consumo de memória.

1. #include <iostream> 2. 3. using namespace std; 4. 5. int Fib(int n); 6. 7. int main() 8. { 9. int n, resposta; 10. cout << "Entre o numero a encontrar: "; 11. cin >> n; 12. cout << "\n\n"; 13. 14. resposta = Fib(n); 15. 16. cout << resposta << " fica na posicao " << n; 17. cout << " da sequencia de Fibonacci\n"; 18. return 0; 19. } 20. 21. int Fib(int n) 22. { 23. cout << "Processando Fib(" << n << ")..."; 24. if (n < 3) 25. { 26. cout << "Retorna 1!\n"; 27. return 1; 28. } 29. else 30. { 31. cout << "Chamando Fib(" << n - 2 << ") "; 32. cout << "e Fib(" << n - 1 << ")\n"; 33. return (Fib(n - 2) + Fib(n - 1)); 34. } 35. }

Saída

Entre o numero a encontrar: 6

Processando Fib(6)...Chamando Fib(4) e Fib(5)

Processando Fib(4)...Chamando Fib(2) e Fib(3)

Processando Fib(2)...Retorna 1!

Processando Fib(3)...Chamando Fib(1) e Fib(2)

Processando Fib(1)...Retorna 1!

Processando Fib(2)...Retorna 1!

Processando Fib(5)...Chamando Fib(3) e Fib(4)

Processando Fib(3)...Chamando Fib(1) e Fib(2)

Processando Fib(1)...Retorna 1!

Processando Fib(2)...Retorna 1!

Processando Fib(4)...Chamando Fib(2) e Fib(3)

Processando Fib(2)...Retorna 1!

Processando Fib(3)...Chamando Fib(1) e Fib(2)

Processando Fib(1)...Retorna 1!

Processando Fib(2)...Retorna 1!

8 fica na posicao 6 da sequencia de Fibonacci

Análise

Page 85: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

85 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

O programa pede pelo número a encontrar na linha 10 e atribui este número a n. Ele então chama Fib() com n. A

execução desvia para a função Fib() onde, na linha 23, ela exibe o argumento passado.

O argumento é testado para verificar se é menor que 3, na linha 24. Se for, Fib() retorna 1, senão, ela retorna a

soma dos valores retornados pela chamada de Fib() com n - 2 e n - 1.

Os valores não podem ser retornados até as chamadas (a Fib()) serem resolvidas. Assim, você pode visualizar o

programa mergulhando em Fib() repetidamente até ele chegar a uma chamada a Fib() que retorna um valor. As

únicas chamadas que retornam um valor são as chamadas para Fib(2) e Fib(1). Estes valores são então passados

às chamadas em espera, que por sua vez, somam o valor retornado ao seu próprio e eles retornam. As figuras a

seguir mostram respectivamente a sequência de chamadas e a sequência de retornos de Fib():

No exemplo, n é 6, então Fib(6) é chamada de main(). A execução desvia para a função Fib(), e n é testado para

um valor menor que 3 na linha 24. O teste falha, então Fib(6) retorna na linha 33 a soma dos valores retornados

por Fib(4) e Fib(5). Veja a linha 33:

return (Fib(n - 2) + Fib(n - 1));

Desta instrução de retorno uma chamada é feita a Fib(4) (por n == 6, Fib(n - 2) é o mesmo que Fib(4)) e outra

chamada é feita a Fib(5) (Fib(n - 1)), então a função que você está (Fib(6)) aguarda até que as chamadas

retornem um valor. Quando isto acontece, a função pode retornar o resultado da soma destes dois valores.

Page 86: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

86 Pesquisa e Desenvolvimento Tecnológico

Como Fib(5) passa um valor que não é menor que 3, Fib() é chamada novamente com Fib(4) e Fib(3). Fib(4) por

sua vez chama Fib(3) e Fib(2). A saída rastreia estas chamadas e seus valores de retorno. Execute o programa

informando primeiro 1, depois 2, depois 3, até 6, e veja cuidadosamente o resultado.

Este pode ser um bom momento para começar a experimentar seu depurador (debugger). Coloque um ponto

de parada (break point) na linha 21 e execute passo a passo (step into ou F11, no Visual Studio) cada chamada a

Fib(), controlando o valor de n a medida que você avança recursivamente em Fib().

Recursão não é muito comum em programação C++, mas pode ser uma ferramenta poderosa e elegante para

certas necessidades.

Observação: recursão é uma parte complicada de programação avançada. Ela é apresentada aqui porque pode

ser útil para entender os fundamentos de como funciona, mas não se preocupe muito se você não entendeu

completamente todos os detalhes.

Como funções funcionam - uma olhada sob o capô

Quando você chama uma função, o código se desvia para a função chamada, parâmetros são passados e o

corpo da função é executado. Quando a função se conclui, um valor é retornado (a menos que a função retorne

void) e o controle retorna para a função de chamada.

Como esta tarefa é realizada? Como o código sabe para onde ir? Onde as variáveis ficam quando são passadas?

O que acontece com variáveis declaradas no corpo da função? Como o valor de retorno é passado de volta?

Como o código sabe onde reiniciar?

Esta explicação requer uma breve tangência na discussão sobre memória de computador. Você pode optar por

voltar a esta seção mais tarde e continuar com a próxima lição.

Níveis de abstração

Um dos principais obstáculos para novos programadores é o conflito com os vários níveis de abstração

intelectual. Computadores, é claro, são máquinas eletrônicas. Eles não sabem sobre janelas e menus, não sabem

sobre programas ou instruções e sequer sabem sobre zeros e uns. Tudo que está acontecendo é que a voltagem

é medida em vários locais de um circuito integrado. Mesmo isto é uma abstração: eletricidade por si mesma é

apenas um conceito intelectual representando o comportamento de partículas subatômicas, que também são

sem dúvida, abstrações intelectuais!

Poucos programadores se incomodam com o nível de detalhes abaixo de valores na RAM. Afinal, você não

precisa entender partículas físicas para dirigir um carro, fazer torradas ou jogar futebol, e não precisa entender

de eletrônica de computadores para programar um.

Você precisa entender como a memória é organizada, entretanto. Sem uma razoável imagem mental de onde

suas variáveis estão quando são criadas e como valores são passados entre funções, tudo permanecerá como

um intratável mistério.

Particionamento da RAM

Quando você inicia seu programa, o sistema operacional (seja DOS, Linux/Unix, ou Windows) configura várias

áreas da memória baseado nos requisitos do compilador. Como um programador C++, frequentemente você

estará preocupado com a namespace global, a área livre, os registradores, o espaço do código (code space) e a

pilha (stack).

Page 87: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

87 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Variáveis globais ficam na namespace global. Você aprenderá mais sobre namespaces e área livre nos próximos

dias, mas aqui o foco é nos registradores, no espaço do código e na pilha.

Registradores é uma área especial da memória construída dentro do processador. Eles cuidam da organização

interna. Muito do que vai para os registradores está fora do escopo deste livro, mas o que você deve se

preocupar é com o conjunto de registradores responsáveis por apontar, em certo momento, para a próxima

linha de código. Estes registradores juntos podem ser chamados de ponteiro de instrução (instruction pointer).

Sua tarefa é controlar qual linha de código será executada em seguida.

O código propriamente está no espaço de código, que é uma parte da memória reservada para armazenar a

forma binária das instruções que você criou em seu programa. Cada linha de código fonte é traduzida para uma

série de instruções, e cada uma destas instruções está num endereço de memória específico. O ponteiro de

instrução tem o endereço da próxima instrução a ser executada. A figura abaixo ilustra a idéia:

A pilha (stack) é uma área especial da memória alocada por seu programa para conter os dados necessários

para cada função. Ela é chamada pilha porque é uma fila do tipo último-a-entrar, primeiro-a-sair, como uma

pilha de pratos de um restaurante:

Último-a-entrar, primeiro a sair (UEPS) ou last in, first out (LIFO), significa que o que for adicionado à pilha será a

primeira coisa a ser retirada. Isto difere da maioria das filas, onde o primeiro a entrar normalmente é o primeiro

a sair, como numa fila de cinema. Uma pilha é como uma pilha de moedas: se você empilha dez moedas sobre a

mesa e então pega algumas de volta, as últimas três colocadas em cima serão as primeiras três que você pegará.

Quando dados são empurrados (pushed) para a pilha, ela cresce; quando são tirados (popped) da pilha, ela

encolhe. Não é possível tirar um prato de uma pilha sem primeiro tirar todos os que foram colocados em cima

dele.

Uma pilha de pratos é uma analogia comum. Está correto de certa forma, mas está errado de uma maneira

fundamental. Uma figura mental mais precisa seria uma série cubículos alinhados de cima para baixo. O topo da

Page 88: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

88 Pesquisa e Desenvolvimento Tecnológico

pilha é qualquer cubículo que o ponteiro da pilha (stack pointer, o qual é outro registrador) está apontando.

Cada cubículo tem um endereço sequencial e um destes endereços é mantido no registrador ponteiro da pilha.

Tudo abaixo deste endereço mágico, conhecido como topo da pilha, é considerado que está na pilha. Tudo que

estiver acima da pilha é considerado fora da pilha e inválido. A figura abaixo mostra isto:

Quando dados são colocados na pilha, ficam um cubículo acima do ponteiro de pilha e este ponteiro é movido

para o novo dado. Tudo que realmente acontece quando dados são retirados da pilha é que o endereço do

ponteiro de pilha é alterado, movendo-se para baixo na pilha. A próxima figura deixa esta regra clara. Os dados

acima do ponteiro da pilha podem ou não serem alterados a qualquer momento. Estes valores são referidos

como lixo (garbage), por que não são mais confiáveis.

A pilha e as funções

O que segue é uma aproximação do que acontece quando seu programa ramifica para uma função (os detalhes

serão diferentes dependendo do sistema operacional e compilador).

1. O endereço do ponteiro de instrução é incrementado para a próxima instrução depois da chamada da

função. Este endereço é colocado na pilha e será o endereço de retorno quando a função terminar.

2. É criado um espaço na pilha para o tipo de retorno que você declarou. Num sistema de inteiros com

dois bytes, se o retorno é declarado como int, outros dois bytes são adicionados à pilha, mas nenhum

valor é colocado neles (significando que qualquer lixo que estiver nestes bytes permanecerá até que a

variável seja inicializada).

Page 89: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

89 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

3. O endereço da chamada de função, que é mantido numa área especial da memória reservada para isto,

é carregado no ponteiro de instrução, assim a próxima instrução a ser executada será na função

chamada.

4. O topo atual da pilha é anotado e armazenado num ponteiro especial chamado estrutura da pilha (stack

frame). A partir deste momento, tudo que for acrescentado à pilha será considerado pertencente à

função (local).

5. Todos os argumentos para a função são colocados na pilha.

6. A instrução que está no ponteiro é executada, ou seja, a primeira instrução da função.

7. Variáveis locais são colocadas na pilha à medida que são definidas.

Quando a função está pronta para retornar, o valor de retorno é colocado na área da pilha reservada no passo

2. A pilha então é toda esvaziada para cima, até o ponteiro de estrutura da pilha, que efetivamente joga fora

todas as variáveis locais e argumentos da função.

O valor de retorno é retirado da pilha e atribuído como o próprio valor da função, e o endereço armazenado no

passo 1 é recuperado e colocado no ponteiro de instrução. O programa então continua imediatamente após a

chamada da função, com seu valor recuperado.

Alguns dos detalhes deste processo mudam de um compilador para o outro, ou entre sistemas operacionais ou

processadores, mas a idéia essencial é consistente através dos ambientes. Em geral, quando você chama uma

função, o endereço de retorno e os parâmetros são colocados na pilha. Durante o tempo de vida da função,

variáveis locais são adicionadas à pilha. Quando a função retorna, todas são removidas, esvaziando a pilha.

Na lição 8 você aprenderá sobre outros lugares da memória que são usados para conter dados que devem

persistir durante a vida da função.

Perguntas e respostas

Por que não fazer todas as variáveis globais?

- Antigamente, era exatamente assim que a programação era feita. À medida que programas se tornaram mais

complexos, entretanto, ficou muito difícil encontrar erros porque os dados poderiam ter sido corrompidos por

qualquer função - dados globais podem ser acessados em qualquer parte do programa. Anos de experiência

convenceram os programadores que os dados devem ficar o mais local possível e os dados devem ser

rigorosamente definidos.

Qual a diferença entre int main() e void main()? Qual devo usar? Ambas funcionam bem, então por que

usar int main() {return 0;}?

- Ambas funcionam na maioria dos compiladores, mas somente int main() é padrão ANSI, assim, somente ela

tem garantia de continuar funcionando. A diferença é que int main() retorna um valor ao sistema operacional.

Quando seu programa termina, este valor pode ser capturado, por exemplo, para programas em lote ou uma

aplicação que invoca seu programa e verifica o valor de retorno para saber se a execução teve sucesso ou não.

Apesar dos nossos exemplos não usarem o valor de retorno de main(), o padrão ANSI exige que ele seja

declarado e conscientemente optamos por permanecer compatíveis.

Quando a palavra chave inline deve ser usada num protótipo de função?

- Se a função é muito pequena, não mais que uma ou duas linhas, e não for chamada em muitos locais do

programa, é uma candidata para ser em linha.

Por que as alterações nos valores de argumentos de funções não são refletidas na função de chamada?

Page 90: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

90 Pesquisa e Desenvolvimento Tecnológico

- Argumentos passados a uma função são passados por valor. Isto significa que o argumento na função é na

verdade uma cópia do valor original. Este conceito é explicado em profundidade na sessão "Como funções

funcionam - uma olhada sob o capô".

Se o argumento é passado por valor, o que eu faço se precisar trazer as alterações de volta a função de

chamada?

- Na lição 8, discutiremos ponteiros e na lição 9, "Explorando referências", você aprenderá sobre referências. O

uso de ponteiros ou referências resolverá este problema, bem como fornece um meio de contornar a limitação

de uma função retornar apenas um valor.

Que acontece se eu tiver as duas funções abaixo?

int Area (int largura, int comprimento = 1); int Area (int tamanho);

Serão sobrecarregadas? Existe uma quantidade diferente de parâmetros, mas a primeira tem um valor

default.

- A declaração vai compilar, mas se você invocar Area com um parâmetro receberá o erro: ambiguity between

Area(int, int) and Area(int).

Testes

1 - Qual a diferença entre protótipo de função e definição de função?

2 - Os nomes dos parâmetros devem coincidir no protótipo, declaração e chamada da função?

3 - Se uma função não retorna um valor, como declará-la?

4 - Se você não declara um tipo de valor, qual é assumido?

5 - Que é uma variável local?

6 - Que é escopo?

7 - Que é recursão?

8 - Quando devo usar variáveis globais?

9 - Que é sobrecarga de funções?

Exercícios

1 - Escreva o protótipo para uma função chamada Perimetro(), que retorna um unsigned long e recebe dois

parâmetros, ambos unsigned short int.

2 - Escreva a definição da função Perimetro() conforme descrita no exercício 1. Os dois parâmetros representam

o comprimento e largura do retângulo. A função deve retornar o perímetro (o dobro da largura vezes o dobro

do comprimento).

3 - Caça-Erros: Que está errado com a função do código abaixo?

1 #include <iostream> 2 3 void MinhaFuncao(unsigned short int x); 4

Page 91: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

91 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

5 int main() 6 { 7 unsigned short int x, y; 8 y = MinhaFuncao(int); 9 std::cout << "x: " << x << " y: " << y << "\n"; 10 return 0; 11 } 12 13 void MinhaFuncao(unsigned short int x) 14 { 15 return (4*x); 16 }

4 - Caça-Erros: Que está errado com a função do código abaixo?

1 #include <iostream> 2 3 int MinhaFuncao(unsigned short int x); 4 5 int main() 6 { 7 unsigned short int x, y; 8 x = 7; 9 y = MinhaFuncao(x); 10 std::cout << "x: " << x << " y: " << y << "\n"; 11 return 0; 12 } 13 14 int MinhaFuncao(unsigned short int x); 15 { 16 return (4*x); 17 }

5 - Escreva uma função que pega dois inteiros unsigned short e retorna o resultado dividindo o primeiro pelo

segundo. Não faça a divisão se o segundo número for zero, mas retorne -1.

6 - Escreva um programa que pede dois números e chama a função que você escreveu no exercício 5. Exiba a

resposta ou uma mensagem de erro, se você obteve -1.

7 - Escreva um programa que pede dois números e uma potência. Escreva uma função recursiva que eleve o

número a esta potência. Por exemplo, se o número é 2 e a potência é 4, a função retornará 16.

Page 92: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

92 Pesquisa e Desenvolvimento Tecnológico

Parte 3 – Controle de

Fluxo e Ponteiros

Lição 7 Controlando o Fluxo do Programa

Lição 8 Ponteiros Explicados

Lição 9 Explorando Referências

Page 93: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

93 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Lição 7

Controlando o Fluxo do Programa

Muitas tarefas que os programas realizam são feitas por meio de técnicas de ramificação e loop. Na lição 5,

“Trabalhando com Expressões, Instruções e Operadores,” você aprendeu como ramificar seu programa usando a

instrução if.

Nessa lição, você aprenderá:

O que são loops e como eles são usados.

Como construir vários loops.

Uma alternativa para instruções if ... else profundamente aninhadas.

Loops de programação

Muitos problemas de programação são resolvidos atuando repetidamente sobre o mesmo dado. Duas maneiras

para fazer isto são recursão (discutida na lição 6, "Organizando código com funções") e iteração. Iteração (ou

repetição) significa fazer a mesma coisa várias vezes. O principal método de iteração é o laço (loop).

As raízes dos loops: goto

Nos primeiros dias da nova ciência da computação, programas eram mal feitos, brutos e curtos. Loops

consistiam de um rótulo (label), alguns comandos e um salto para o rótulo.

Em C++, um rótulo é simplesmente um nome seguido de (:). O rótulo é colocado à esquerda de uma instrução.

Um salto é efetuado escrevendo goto seguido de um nome de rótulo. A listagem 7.1 ilustra esta forma primitiva

de laço:

1. // Listagem 7.1 2. // Laço com goto 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. int contador = 0; // Inicializa contador 9. laco: 10. contador++; // Topo do laço 11. cout << "Contador: " << contador << endl; 12. if (contador < 5) // Testa valor 13. goto laco; // Salta para o topo 14. cout << "Completo. Contador: " << contador << endl; 15. return 0; 16. }

Saída

Contador: 1

Contador: 2

Contador: 3

Contador: 4

Contador: 5

Completo. Contador: 5

Page 94: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

94 Pesquisa e Desenvolvimento Tecnológico

Análise

Na linha 8 contador é inicializado para zero. Um rótulo chamado laco está na linha 9, marcando o início do laço.

contador é incrementado e na linha 11 seu valor é exibido. Este valor é testado na linha 12. Se o valor é menor

que 5, a instrução if é verdadeira e a instrução goto é executada. Isto faz a execução do programa saltar para o

rótulo laco na linha 9. O programa continua neste laço até que contador seja igual a 5, e nesta hora ele sai do

laço e a saída final é exibida.

Por que goto é evitado

Como regra, programadores evitam goto e com uma boa razão. Instruções goto causam um salto para qualquer

local do seu código, para trás ou para frente. Seu uso indiscriminado causou programas pobres, emaranhados e

impossíveis de ler conhecidos como código espaguete.

A instrução goto

Para usar a instrução goto, você escreve goto seguido de um nome de rótulo. Isto causa um salto

incondicional para o rótulo.

Exemplo

If (valor > 10) goto fim; if (valor < 10) goto fim; cout << "valor é 10"; fim: cout << "feito";

Para evitar o uso de goto, foram introduzidos comandos de loop mais sofisticados e rigidamente controlados:

for, while e do...while.

Usando loops while

Um loop while faz seu programa repetir uma sequência de instruções enquanto a condição inicial permanece

verdadeira. No exemplo de goto da listagem 7.1, o contador foi incrementado até ser igual a 5. A listagem 7.2

mostra o mesmo programa reescrito para tirar vantagem de um loop while:

1. // Listagem 7.2 2. // Laço com while 3. #include <iostream> 4. int main() 5. { 6. using namespace std; 7. int contador = 0; // inicializa a condição 8. while(contador < 5) // testa a condição enquanto verdadeira 9. { 10. contador++; // corpo do laço 11. cout << "contador: " << contador << endl; 12. } 13. cout << "Completo. Contador: " << contador << endl; 14. return 0; 15. }

Page 95: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

95 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Saída

contador: 1

contador: 2

contador: 3

contador: 4

contador: 5

Completo. Contador: 5

Análise

Este simples programa demonstra os fundamentos do laço while. Na linha 7, uma variável inteira chamada

contador é criada e inicializada para zero. Isto então é usado como parte de uma condição. A condição é

testada, e se for verdadeira, o corpo do loop while é executado. Neste caso, a condição testada na linha 8 é se o

contador é menor que 5. Se a condição é verdadeira, o corpo do loop é executado. Na linha 10 o contador é

incrementado e na linha 11 é exibido. Quando a instrução condicional da linha 8 falha (o contador não é menor

que 5), todo o corpo do loop while (linhas 9 a 12) é saltado. A execução do programa continua na linha 13.

Deve-se notar aqui que é uma boa idéia sempre usar chaves envolvendo o bloco executado por um loop,

mesmo quando for uma única linha de código. Isto evita o erro comum de inadvertidamente colocar um ponto

e vírgula no final do loop e causar sua repetição infinita - por exemplo:

int contador = 0; while (contador < 5); contador++;

Neste exemplo, contador++ nunca é executado.

A instrução while

A sintaxe da instrução while é:

while (condição) instrução;

condição é qualquer expressão C++, e instrução é qualquer instrução ou bloco de instruções C++ válido.

Quando condição avaliada resulta em true, instrução é executada e condição é testada novamente. Isto

continua até o teste de condição ser false, quando então o loop while termina e a execução continua na

primeira linha após instrução.

Exemplo:

// Conte até 10 int x = 0; while (x < 10) cout << "X: " << x++;

Explorando instruções while mais complicadas

A condição testada por um loop while pode ser tão complexa quanto qualquer expressão C++ válida. Isto pode

incluir expressões usando os operadores lógicos && (and), || (or) e ! (not). Na listagem 7.3 há uma instrução

while um pouco mais complicada:

1. // Listagem 7.3

Page 96: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

96 Pesquisa e Desenvolvimento Tecnológico

2. // Instruções while complexas 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. unsigned short pequeno; 9. unsigned long grande; 10. const unsigned short MAXPEQUENO=65535; 11. 12. cout << "Digite um numero pequeno: "; 13. cin >> pequeno; 14. cout << "Digite um numero grande: "; 15. cin >> grande; 16. 17. cout << "Pequeno: " << pequeno << "..."; 18. 19. // Para cada iteração, testa duas condições 20. while (pequeno < grande && pequeno < MAXPEQUENO) 21. { 22. if (pequeno % 5000 == 0) // Escreve um . a cada 5K linhas 23. cout << "."; 24. 25. pequeno++; 26. grande-=2; 27. } 28. 29. cout << "\nPequeno: " << pequeno << " Grande: " << grande << endl; 30. return 0; 31. }

Saída

Digite um numero pequeno: 2

Digite um numero grande: 10000

Pequeno: 2...

Pequeno: 3335 Grande: 3334

Análise

Este programa é um jogo. Informe dois números, um pequeno e um grande. O menor será incrementado por

um e o maior será decrementado por dois. O objetivo do jogo é adivinhar quando eles se encontram.

Nas linhas 12 a 15 os números são digitados. A linha 20 inicia um loop while que continuará enquanto as duas

condições acontecerem:

1. pequeno não é maior que grande.

2. pequeno não ultrapassa o tamanho de um small int (MAXPEQUENO).

Na linha 22, o módulo de pequeno por 5000 é calculado. Isto não altera o valor em pequeno; entretanto, retorna

o valor zero apenas quando pequeno é um múltiplo exato de 5000. Cada vez que isto acontece, um ponto é

exibido na tela para mostrar o progresso. Na linha 25, pequeno é incrementado, e na linha 26, grande é

decrementado de 2. Quando ambas as condições no loop while falharem, o loop termina e a execução continua

após a chave de fechamento na linha 27.

Observação: o operador módulo(%) e condições compostas são abordados na lição 5, "Trabalhando com

expressões, instruções e operadores".

Page 97: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

97 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Apresentando continue e break

Às vezes você pode querer retornar para o início de um loop while antes que seu conjunto inteiro de instruções

seja executado. A instrução continue salta de volta ao início do loop.

Outras vezes você pode querer sair do loop antes que sua condição de saída seja atendida. A instrução break sai

imediatamente do loop while e a execução continua após a chave de fechamento.

A listagem 7.4 demonstra o uso destas instruções. Desta vez o jogo se tornou mais complicado. O usuário é

convidado a informar um número pequeno e um grande, um número de salto e um número alvo. O número

pequeno será incrementado por um e o número grande será decrementado por dois. O decremento será

saltado cada vez que o número pequeno for múltiplo do salto. O jogo termina se pequeno se torna maior que

grande. Se o número maior atinge exatamente o número alvo, uma instrução é exibida e o jogo para. O objetivo

do usuário é colocar um número alvo para o número grande que parará o jogo.

1. // Listagem 7.4 - Demonstra break e continue 2. #include <iostream> 3. 4. int main() 5. { 6. using namespace std; 7. 8. unsigned short pequeno; 9. unsigned long grande; 10. unsigned long salto; 11. unsigned long alvo; 12. const unsigned short MAXPEQUENO=65535; 13. 14. cout << "Informe um numero pequeno: "; 15. cin >> pequeno; 16. cout << "Informe um numero grande: "; 17. cin >> grande; 18. cout << "Informe um numero de salto: "; 19. cin >> salto; 20. cout << "Informe o numero alvo: "; 21. cin >> alvo; 22. 23. cout << "\n"; 24. 25. // Configura duas posições de parada para o loop 26. while (pequeno < grande && pequeno < MAXPEQUENO) 27. { 28. pequeno++; 29. 30. if (pequeno % salto == 0) // salta o decremento? 31. { 32. cout << "Saltando " << pequeno << endl; 33. continue; 34. } 35. 36. if (grande == alvo) // combinação exata para o alvo? 37. { 38. cout << "Alvo alcancado!"; 39. break; 40. } 41. 42. grande-=2; 43. } // fim do loop while 44. 45. cout << "\nPequeno: " << pequeno << " Grande: " << grande << endl; 46. return 0; 47. }

Page 98: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

98 Pesquisa e Desenvolvimento Tecnológico

Saída:

Informe um numero pequeno: 2

Informe um numero grande: 20

Informe um numero de salto: 4

Informe o numero alvo: 6

Saltando 4

Saltando 8

Pequeno: 10 Grande: 8

Análise

Neste jogo o usuário perde. pequeno se torna maior que grande antes que o número alvo 6 seja alcançado. Na

linha 26 as condições de while são testadas. Se pequeno continua a ser menor que grande e se pequeno não

ultrapassou o valor máximo para um small int, o corpo do laço while é iniciado.

Na linha 30 é pego o módulo do valor de pequeno pelo valor de salto. Se pequeno é um múltiplo de salto, a

instrução continue é alcançada e a execução do programa salta para o início do laço, na linha 26.

Na linha 36, alvo é testado contra o valor de grande. Se forem os mesmos, o usuário ganhou. Uma mensagem é

exibida e a instrução break é alcançada e executada. Isto causa a saída imediata do loop while e a execução do

programa continua na linha 44.

Observação: break e continue devem ser usados cautelosamente. Eles são os comandos mais perigosos depois

de goto, pela mesma razão. Programas que subitamente mudam de direção são difíceis de entender e o uso

indiscriminado de continue e break podem tornar inteligível mesmo um loop while pequeno. A necessidade de

sair de um loop normalmente indica que a condição de término não foi configurada com a expressão booleana

apropriada. Sempre é melhor usar uma instrução if dentro de um laço para saltar algumas linhas que usar uma

instrução de quebra.

A instrução continue

continue faz com que um loop while, do...while ou for volte ao início. Veja a listagem 7.4 para um

exemplo do uso de continue.

A instrução break

break faz com que um loop while, do...while ou for seja imediatamente encerrado. A execução salta para

a chave de fechamento.

Exemplo while (condição) { if (condição2) break; // instruções }

Page 99: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

99 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Examinando laços while (true)

A condição testada num loop while pode ser qualquer expressão C++ válida. Enquanto a condição permanece

verdadeira, o loop while continua. Você pode criar um loop que nunca termina usando o valor true para a

condição a ser testada. A listagem 7.5 demonstra a contagem até 10 usando esta construção:

1. // Listagem 7.5 2. // Demonstra o loop while true 3. #include <iostream> 4. 5. int main() 6. { 7. int contador = 0; 8. 9. while (true) 10. { 11. contador ++; 12. if (contador > 10) 13. break; 14. } 15. std::cout << "Contador: " << contador << std::endl; 16. return 0; 17. }

Saída

Contador: 11

Análise

Na linha 9 um loop while é configurado com uma condição que jamais será falsa. O loop incrementa a variável

contador na linha 11, e na linha 12 ele testa para verificar se contador passou de 10. Se não passou, o loop while

continua. Se contador é maior que 10, o break na linha 13 encerra o loop e a execução do programa cai na linha

15, onde o resultado é exibido.

Este programa funciona, mas não está elegante. Isto é um bom exemplo do uso de uma ferramenta errada para

o trabalho. A mesma coisa pode ser alcançada colocando o teste do valor de contador onde ele pertence - na

condição do while.

Cuidado: loops eternos como while (true) podem fazer seu computador travar se a condição de saída nunca for

alcançada. Use isto com cuidado e teste muito.

C++ lhe dá muitas maneiras de efetuar a mesma tarefa. O verdadeiro truque é pegar a ferramenta certa para

um trabalho em particular.

Faça Não faça

Use loops while com uma instrução condicional.

Seja cauteloso ao usar instruções continue e break.

Assegure-se que seu loop vai eventualmente

terminar.

Usar a instrução goto.

Esquecer a diferença entre continue e break.

continue volta ao início, ao passo que break sai do

laço.

Page 100: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

100 Pesquisa e Desenvolvimento Tecnológico

Implementando laços do...while

É possível que o corpo de um laço while nunca seja executado. A instrução while verifica a condição antes de

executar qualquer instrução, e se a condição for falsa, salta todo o corpo do laço. A listagem 7.6 ilustra isto:

1. // Listagem 7.6 2. // Demonstra pular o corpo de um 3. // laço while quando a condição é falsa 4. 5. #include <iostream> 6. 7. int main() 8. { 9. int contador; 10. std::cout << "Quantos alos?: "; 11. std::cin >> contador; 12. while (contador > 0) 13. { 14. std::cout << "Alo!\n"; 15. contador--; 16. } 17. std::cout << "Contador: " << contador; 18. return 0; 19. }

Saída

Quantos alos?: 2

Alo!

Alo!

Contador: 0

Quantos alos?: 0

Contador: 0

Análise

O usuário é solicitado a por um valor inicial na linha 10. Este valor inicial é armazenado na variável inteira

contador. O valor de contador é testado na linha 12 e decrementado no corpo do loop while. Na saída você

pode ver que na primeira vez o contador foi definido para 2 e o corpo do loop while executou duas vezes. Na

segunda vez, entretanto, foi informado zero. O valor do contador foi testado na linha 12 e a condição foi testada

como falsa. Com isso, contador não era maior que zero. Todo o corpo do laço while foi saltado e Alo nunca foi

exibido.

E se você quiser que Alo seja exibido pelo menos uma vez? O loop while não pode realizar isto porque a

condição é testada antes que qualquer saída seja exibida. Você pode forçar o fluxo com uma instrução if

exatamente antes de entrar no loop while.

if (contador < 1) // Força um valor mínimo contador = 1;

Porém, isto é o que os programadores chamam de "gato" ou "bacalhau", uma solução feia e deselegante.

Usando do...while

O loop do...while executa o corpo do loop antes da condição ser testada, assegurando sua execução pelo menos

uma vez. A listagem 7.7 reescreve a listagem 7.6 usando o loop do...while:

Page 101: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

101 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

1. // Listagem 7.7 2. // Demonstra do while 3. 4. #include <iostream> 5. 6. int main() 7. { 8. using namespace std; 9. int contador; 10. cout << "Quantos alos? "; 11. cin >> contador; 12. do 13. { 14. cout << "Alo\n"; 15. contador--; 16. } while (contador >0 ); 17. cout << "Contador: " << contador << endl; 18. return 0; 19. }

Saída

Quantos alos?: 2

Alo!

Alo!

Contador: 0

Quantos alos?: 0

Alo!

Contador: -1

Análise

Como o programa anterior, este exibe a palavra Alo no console por um determinado número de vezes. Ao

contrário do anterior, entretanto, este sempre irá exibir pelo menos uma vez.

O usuário é solicitado a por um valor inicial na linha 10, que é armazenado na variável inteira contador. No loop

do...while, o corpo do loop é executado antes da condição ser testada, então é certo que ele execute pelo menos

uma vez. Na linha 14, a palavra Alo é exibida. Na linha 15 o contador é decrementado. A execução salta para o

início do loop na linha 13 – caso contrário cai na linha 17.

As instruções continue e break funcionam num loop do...while exatamente da mesma forma que num loop while.

A única diferença entre while e do...while é onde a condição é testada.

A instrução do...while

A sintaxe para a instrução do...while é:

do instrução; while (condição);

instrução é executada e então condição é avaliada. Se a condição for true, o loop é repetido, caso

contrário, o loop termina. As instruções e condições, fora isto, são idênticas ao loop while.

Exemplo 1

// Conta até 10 int x = 0; do cout << "X: " << x++ << endl;

Page 102: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

102 Pesquisa e Desenvolvimento Tecnológico

while (x < 10);

Exemplo 2

// Exibe o alfabeto minúsculo char ch = 'a'; do { cout << ch << ' '; ch++; } while ( ch <= 'z');

Faça Não faça

Use do...while quando você quer que o loop execute

pelo menos uma vez.

Use loops while quando quiser saltar o loop se a

condição for falsa.

Teste todos os loops para certificar-se que eles

fazem o que você espera.

Usar break e continue com loops, a menos que

esteja claro o que seu código está fazendo. Sempre

existem melhores maneiras de realizar a mesma

tarefa.

Usar a instrução goto.

Loops com a instrução for

Ao trabalhar com loops while você frequentemente encontrará três passos: configurar uma condição inicial,

testar para verificar se a condição é verdadeira e incrementar ou alterar uma variável a cada volta. A listagem 7.8

demonstra isto:

1. // Listagem 7.8 2. // Laços com while 3. 4. #include <iostream> 5. 6. int main() 7. { 8. int contador = 0; 9. 10. while(contador < 5) 11. { 12. contador++; 13. std::cout << "Contando! "; 14. } 15. 16. std::cout << "\nContador: " << contador << std::endl; 17. return 0; 18. }

Saída

Contando! Contando! Contando! Contando! Contando!

Contador: 5

Análise

Na listagem você pode ver os três passos ocorrendo. Primeiro, a condição inicial é configurada na linha 8:

contador é inicializado para zero. Na linha 10 ocorre o teste da condição, quando contador é testado para

Page 103: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

103 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

verificar se é menor que 5. Finalmente, a variável contador é incrementada na linha 12. O loop exibe uma simples

mensagem na linha 13. Como você pode imaginar, trabalhos mais importantes podem ser feitos para cada

incremento do contador.

Um laço for combina os três passos em uma instrução. Os três passos são inicialização, teste e incremento. Uma

instrução for consiste da palavra chave for seguida de um par de parênteses. Dentro dos parênteses há três

instruções separadas por ponto e vírgula:

for (inicialização; teste; ação) { ... }

A primeira expressão, inicialização, é a condição inicial ou inicialização. Qualquer instrução C++ válida pode ser

colocada aqui, mas tipicamente é usada para criar e inicializar uma variável. A segunda expressão, teste, é o teste

e qualquer expressão C++ válida pode ser usada aqui. O teste obedece à mesma regra que a condição de um

laço while. A terceira expressão, ação, é a ação que será tomada. Esta ação tipicamente é o incremento ou

decremento de um valor, apesar de qualquer instrução C++ válida poder ser colocada aqui. A listagem 7.9

demonstra um laço for reescrevendo a listagem 7.8:

1. // Listagem 7.9 2. // Laços com for 3. 4. #include <iostream> 5. 6. int main() 7. { 8. int contador; 9. for (contador = 0; contador < 5; contador++) 10. std::cout << "Contando! "; 11. 12. std::cout << "\nContador: " << contador << std::endl; 13. return 0; 14. }

Saída

Contando! Contando! Contando! Contando! Contando!

Contador: 5

Análise

A instrução for na linha 9 combina em uma linha a inicialização de contador, o teste se contador é menor que 5 e

o incremento de contador. O corpo da instrução for está na linha 10. Obviamente, um bloco de código pode ser

colocado aqui.

A instrução for

A sintaxe da instrução for é:

for (inicialização; teste; ação) instrução;

A instrução inicialização é usada para iniciar o estado de um contador ou preparar para o loop. teste é

qualquer expressão C++ e é avaliada a cada iteração do laço. Se teste é verdadeiro, o corpo do loop for

é executado e então a ação é tomada (tipicamente, o contador é incrementado).

Exemplo 1

Page 104: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

104 Pesquisa e Desenvolvimento Tecnológico

// Exibe Alo 10 vezes for (int i = 0; i < 10; i++) cout << "Alo!" << endl;

Exemplo 2 for (int i = 0; i < 10; i++) { cout << "Alo!" << endl; cout << "Valor de i: " << i << endl; }

Laços for avançados

Instruções for são poderosas e flexíveis. As três instruções independentes (inicialização, teste e ação) lhes dão

muitas variações.

Múltiplas inicializações e incrementos

É comum inicializar mais de uma variável, testar uma expressão lógica composta e executar mais de uma

instrução. A inicialização e a ação podem ser substituídas por múltiplas instruções C++, cada uma separada com

vírgula. A listagem 7.10 demonstra a inicialização e incremento de duas variáveis:

1. //Listagem 7.10 2. // Demonstra múltiplas instruções em 3. // laços for 4. #include <iostream> 5. 6. int main() 7. { 8. 9. for (int i=0, j=0; i<3; i++, j++) 10. std::cout << "i: " << i << " j: " << j << std::endl; 11. return 0; 12. }

Saída

i: 0 j: 0

i: 1 j: 1

i: 2 j: 2

Análise

Na linha 9, duas variáveis, i e j, são inicializadas com o valor zero. Uma vírgula é usada para separar as duas

expressões. Você também pode ver que estas inicializações estão separadas da condição de teste pelo esperado

ponto e vírgula.

Quando o programa executa, o teste (i < 3) é avaliado, e porque é verdadeiro, o corpo do laço for é executado,

onde os valores são exibidos. Finalmente, a terceira cláusula na instrução for é executada. Como você pode ver,

duas expressões estão aqui também. Neste caso, ambos i e j são incrementados.

Após a linha 10 completar, a condição é avaliada novamente, e se permanece verdadeira, as ações são repetidas

(i e j são novamente incrementados) e o corpo do loop é executado de novo. Isto continua até o teste falhar,

neste caso a instrução de ação não é executada e o controle sai do laço.

Page 105: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

105 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Instruções nulas em loops for

Toda ou qualquer instrução num laço for pode ser deixada de fora. Para permitir isto, você utiliza uma instrução

nula, que é simplesmente o uso do ponto e vírgula para marcar onde a instrução deveria estar. Usando uma

instrução nula você pode criar um loop for que age exatamente como um while deixando fora a primeira e

terceira instruções. A listagem 7.11 ilustra esta idéia:

1. // Listagem 7.11 2. // Laços for com instruções nulas 3. 4. #include <iostream> 5. 6. int main() 7. { 8. int contador = 0; 9. 10. for( ; contador < 5; ) 11. { 12. contador++; 13. std::cout << "Contando! "; 14. } 15. 16. std::cout << "\nContador: " << contador << std::endl; 17. return 0; 18. }

Saída

Contando! Contando! Contando! Contando! Contando!

Contador: 5

Análise

Você deve reconhecer que isto é exatamente como o laço while mostrado na listagem 7.8. Na linha 8 a variável

contador é inicializada. A instrução for na linha 10 não inicializa nenhum valor, mas inclui um teste para contador

< 5. Não existe nenhuma instrução de incremento, e por isso este laço se comporta exatamente como se fosse

escrito como abaixo.

while (contador < 5)

Você pode ver novamente que C++ permite muitas maneiras de realizar a mesma coisa. Nenhum programador

C++ experiente usaria um laço for como mostrado na listagem 7.11, mas isto ilustra a flexibilidade da instrução.

De fato é possível, usando break e continue, criar um laço for sem nenhuma das três instruções. A listagem 7.12

mostra como:

1. //Listagem 7.12 ilustrando 2. //uma instrução for vazia 3. 4. #include <iostream> 5. 6. int main() 7. { 8. int contador=0; // inicialização 9. int max; 10. std::cout << "Quantos Alos? "; 11. std::cin >> max; 12. for (;;) // um laço for que nunca termina 13. { 14. if (contador < max) // teste 15. { 16. std::cout << "Alo! " << std::endl;

Page 106: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

106 Pesquisa e Desenvolvimento Tecnológico

17. contador++; // incremento 18. } 19. else 20. break; 21. } 22. return 0; 23. }

Saída

Quantos Alos? 3

Alo!

Alo!

Alo!

Análise

O loop for agora foi levado ao seu limite absoluto. Inicialização, teste e ação foram tirados da instrução na linha

12. A inicialização é feita na linha 8, antes do loop for começar. O teste é feito numa instrução if separada, na

linha 14, e se for bem-sucedido, a ação, um incremento em contador, é executado na linha 17. Se o teste falhar,

a quebra do loop ocorre na linha 20.

Apesar deste programa em particular algo bem absurdo, às vezes um laço for (; ;) ou um while (true) é tudo que

você quer. Você verá um exemplo de uso mais razoável destes loops quando instruções switch forem discutidas

posteriormente nesta lição.

Laços for vazios

Visto que muitas coisas podem ser feitas no cabeçalho de uma instrução for, às vezes você não precisa do corpo

para fazer nada mais. Neste caso, certifique-se de colocar uma instrução nula (;) como corpo do laço. O ponto e

vírgula pode ficar na mesma linha do cabeçalho, mas é fácil de esquecer. A listagem 7.13 ilustra uma forma

apropriada de usar um corpo nulo num laço for:

1. // Listagem 7.13 2. // Demonstra instrução nula 3. // como corpo do laço 4. 5. #include <iostream> 6. int main() 7. { 8. for (int i = 0; i<5; std::cout << "i: " << i++ << std::endl) 9. ; 10. return 0; 11. }

Saída

i: 0

i: 1

i: 2

i: 3

i: 4

Análise

O laço for na linha 8 contém três instruções: a inicialização estabelece o contador i e o inicializa para zero. A

condição testa se i < 5 e a ação mostra o valor de i e o incrementa.

Page 107: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

107 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Nada precisa ser feito no corpo do laço for, então a instrução nula (;) é usada. Observe que isto não é um loop

for bem projetado: a instrução de ação está fazendo muita coisa. Isto ficaria melhor se reescrito como

for (int i = 0; i < 5; i++) cout << "i: " << i << endl;

Apesar de ambos fazerem a mesma coisa, este exemplo é mais fácil de ler.

Loops aninhados

Qualquer loop pode estar aninhado dentro do corpo de outro loop. O loop mais interno será executado

totalmente a cada execução do loop mais externo. A listagem 7.14 ilustra a escrita de um símbolo numa matriz

usando loops aninhados:

1. //Listagem 7.14 2. //Ilustra laços for aninhados 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. int linhas, colunas; 9. char simbolo; 10. cout << "Quantas linhas? "; 11. cin >> linhas; 12. cout << "Quantas colunas? "; 13. cin >> colunas; 14. cout << "Qual simbolo? "; 15. cin >> simbolo; 16. for (int i = 0; i<linhas; i++) 17. { 18. for (int j = 0; j<colunas; j++) 19. cout << simbolo; 20. cout << endl; 21. } 22. return 0; 23. }

Saída

Quantas linhas? 4

Quantas colunas? 12

Qual simbolo? X

XXXXXXXXXXXX

XXXXXXXXXXXX

XXXXXXXXXXXX

XXXXXXXXXXXX

Análise

Na listagem, o usuário é solicitado pelo número de linhas e colunas e um símbolo. O primeiro loop for, na linha

16, inicializa o contador i para zero e o corpo do loop externo é executado.

Na linha 18, a primeira linha do corpo do loop mais externo, outro loop for é estabelecido. Um segundo

contador, j, é inicializado para zero e o corpo do loop mais interno é executado. Na linha 19 o símbolo escolhido

é exibido e o controle retorna para o cabeçalho do loop mais interno. Observe que o loop mais interno é apenas

uma instrução (a exibição do símbolo). A condição é testada (j < colunas) e se retorna true, j é incrementado e o

próximo símbolo é exibido. Isto continua até que j seja igual ao número de colunas.

Page 108: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

108 Pesquisa e Desenvolvimento Tecnológico

Quando o teste do laço interno falha, neste caso após doze X's terem sido exibidos, a execução cai na linha 20 e

uma nova linha é exibida. O laço mais externo agora retorna a seu cabeçalho, onde a condição (i < linhas) é

testada. Se retorna true, i é incrementado e o corpo do loop executado.

Na segunda iteração do loop mais externo, o loop interno começa novamente. Assim, j é reiniciado para zero e

todo o loop interno é executado novamente.

A idéia importante aqui é que usando loops aninhados, o loop mais interno é executado a cada iteração do mais

externo. Assim, o símbolo é exibido colunas vezes para cada linhas.

Observação: muitos programadores C++ usam as letras i e j como variáveis contadoras. Esta tradição vem dos

tempos do FORTRAN, onde as letras i, j, k, l, m e n eram as únicas variáveis contadoras disponíveis. Apesar de

não ser prejudicial, leitores de seu programa podem ficar confusos com o objetivo do contador e usá-lo de

forma imprópria. Você mesmo pode ficar confuso num programa complexo com loops aninhados. É melhor

indicar o uso da variável índice no seu nome - por exemplo, indiceCliente ou contadorEntrada.

Escopo em loops for

Antigamente, variáveis declaradas em loops for tinham escopo fora do laço. O padrão ANSI mudou o escopo

destas variáveis restringindo-as ao bloco do loop. Entretanto, nem todo compilador implementa esta alteração.

Você pode testar seu compilador com o seguinte código:

1. #include <iostream> 2. int main() 3. { 4. // i tem escopo fora do laço? 5. for (int i = 0; i<5; i++) 6. { 7. std::cout << "i: " << i << std::endl; 8. } 9. i = 7; // inteiro ‘i’ não deve estar no escopo! 10. return 0; 11. }

Se isto for compilado sem queixas, seu compilador ainda não suporta este aspecto do padrão ANSI. Se o

compilador reclamar que i não está definido (na linha i = 7), ele suporta o novo padrão. Você pode escrever

código que compilará em qualquer compilador declarando i fora do laço, como mostrado aqui:

1. #include <iostream> 2. int main() 3. { 4. int i; //declara fora do laço 5. for (i = 0; i<5; i++) 6. { 7. std::cout << "i: " << i << std::endl; 8. } 9. i = 7; // agora está no escopo para todos os compiladores 10. return 0; 11. }

Observação: no Visual Studio, você nem precisa compilar, o Inllisence do editor já marca a variável i com um

erro se ela estiver fora do escopo.

Page 109: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

109 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Resumindo loops

Na lição 6, "Organizando código com funções", você aprendeu como resolver o problema da sequência de

Fibonacci usando recursão. Revendo rapidamente, a sequência de Fibonacci começa com 1, 1, 2, 3 e todos os

números subsequentes são a soma dos dois anteriores:

1, 1, 2, 3, 5, 8, 13, 21, 34...

O enésimo número na sequência é a soma de n - 1 e n - 2. O problema resolvido na lição 6 era encontrar o valor

do enésimo número da sequência, e isto foi feito com recursão. A listagem 7.15 oferece a solução usando

iteração:

1. // Listagem 7.15 - Demonstra a solução do enésimo 2. // número de Fibonacci usando iteração 3. 4. #include <iostream> 5. 6. unsigned int Fib(unsigned int posicao); 7. int main() 8. { 9. using namespace std; 10. unsigned int resposta, posicao; 11. cout << "Qual posicao? "; 12. cin >> posicao; 13. cout << endl; 14. 15. resposta = Fib(posicao); 16. cout << resposta << " esta na posicao "; 17. cout << posicao << " da sequencia de Fibonacci. " << endl; 18. return 0; 19. } 20. 21. unsigned int Fib(unsigned int n) 22. { 23. unsigned int menosDois=1, menosUm=1, resposta=2; 24. 25. if (n < 3) 26. return 1; 27. 28. for (n -= 3; n != 0; n--) 29. { 30. menosDois = menosUm; 31. menosUm = resposta; 32. resposta = menosUm + menosDois; 33. } 34. 35. return resposta; 36. }

Saída

Qual posicao? 4

3 esta na posicao 4 da sequencia de Fibonacci.

Qual posicao? 5

5 esta na posicao 5 da sequencia de Fibonacci.

Qual posicao? 20

6765 esta na posicao 20 da sequencia de Fibonacci.

Qual posicao? 100

3314859971 esta na posicao 100 da sequencia de Fibonacci.

Análise

Page 110: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

110 Pesquisa e Desenvolvimento Tecnológico

A listagem 7.15 resolve a sequência de Fibonacci usando iteração ao invés de recursão. Esta abordagem é mais

rápida e usa menos memória que a recursão.

Na linha 11, o usuário é solicitado a fornecer a posição a ser verificada. A função Fib() é chamada, para calcular a

posição. Se a posição é menor que 3, a função retorna o valor 1. Começando com a posição 3, a função interage

usando o seguinte algoritmo:

1. Estabelecer a posição inicial: preencha a variável resposta com 2, menosDois com 1 e menosUm com 1.

Decremente a posição por 3, porque os primeiros dois números são tratados pela posição inicial.

2. Para cada número, conta-se a sequência de Fibonacci. Isto é feito por

a. Colocar o valor atual de menosUm em menosDois

b. Colocar o valor atual de resposta em menosUm

c. Somar menosUm e menosDois e colocar a soma em resposta

d. Decrementar n

3. Quando n chega a zero, retorna a resposta.

Isto é exatamente como você resolveria este problema com lápis e papel. Se for pedido o quinto número de

Fibonacci, você normalmente começaria a escrever a seguinte sequencia:

1, 1, 2,

Em seguida, você soma 2 + 1 e escreve 3, e pensa "encontrar mais um". Por fim, você escreve 3 + 2 e a resposta

será 5. Na verdade, você está alternando sua atenção para um número de cada vez e decrementando o número

que resta ser encontrado.

Observe a condição testada na linha 28 (n != 0). Muitos programadores C++ usam o seguinte para esta linha:

for (n -= 3; n; n--)

Você pode ver que ao invés de usar uma condição relacional, apenas o valor n é usado como condição no loop

for. Isto é um dialeto C++, e n é considerado equivalente a n != 0. Usar apenas n baseia-se no fato que quando

n atinge zero, ele será avaliado como false, pois zero é considerado falso em C++. Para ficar com o padrão C++

atual, é melhor depender de uma condição para avaliar o valor de false, do que usar um valor numérico.

Execute este programa e a solução oferecida pela lição 6. Tente encontrar a posição 25 e compare o tempo que

cada programa usa. Recursão é elegante, mas como chamadas de funções trazem sobrecarga de desempenho, e

são usadas muitas vezes, o desempenho é claramente melhor com iteração. Computadores tendem a ser

otimizados para operações matemáticas, assim soluções iterativas são muito mais rápidas.

Cuidado com o tamanho do número informado. Fib() cresce rapidamente e mesmo um unsigned long int

estourará após um tempo.

Controlando o fluxo com instruções switch

Na lição 5, "Trabalhando com expressões, instruções e operadores", você viu como escrever instruções if e

if...else. Elas podem se tornar um pouco confusas quando aninhadas muito profundamente, e C++ oferece uma

alternativa. Diferente de if, que avalia um valor, instruções switch lhe permitem escolher um entre vários valores.

A forma geral da instrução switch é:

switch (expressão) { case valorUm: instrução; break; case valorDois: instrução;

Page 111: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

111 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

break; .... case valorN: instrução; break; default: instrução; }

expressão é qualquer expressão C++ válida e instrução é qualquer instrução, ou bloco de instruções, que

avaliam (ou podem ser inequivocamente convertidas para) um valor inteiro. Observe, entretanto, que a avaliação

é apenas por igualdade – nenhum operador relacional ou operação booleana pode ser usado aqui.

Se um dos valores case coincide com a expressão, a execução salta para estas instruções e continua no final do

bloco switch, a menos que uma instrução break seja encontrada. Se nada coincide, a execução vai para a

instrução opcional default. Se nenhum default ou valor coincidente existe, a execução sai da instrução switch.

Dica: é sempre uma boa idéia ter uma opção default numa instrução switch. Se você não tem outras

necessidades para default, use-o para testar possíveis casos impossíveis, e exiba uma mensagem de erro. Isto

pode ser uma tremenda ajuda em depuração.

É importante observar que se nenhuma instrução break aparece no fim de um case, a execução continua na

próxima opção case. Isto as vezes é necessário, mas normalmente é um erro. Se você decide deixar a execução

prosseguir, certifique-se de colocar um comentário indicando que você não esqueceu o break. A listagem 7.16

ilustra o uso da instrução switch:

1. // Listagem 7.16 2. // Demonstra a instrução switch 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. unsigned short int numero; 9. cout << "Informe um numero entre 1 e 5: "; 10. cin >> numero; 11. switch (numero) 12. { 13. case 0: cout << "Muito pequeno, desculpe!"; 14. break; 15. case 5: cout << "Bom trabalho!! " << endl; // prossegue 16. case 4: cout << "Boa escolha!" << endl; // prossegue 17. case 3: cout << "Excelente!" << endl; // prossegue 18. case 2: cout << "Magistral!" << endl; // prossegue 19. case 1: cout << "Incrivel!" << endl; 20. break; 21. default: cout << "Muito grande!" << endl; 22. break; 23. } 24. cout << endl << endl; 25. return 0; 26. }

Saída

Informe um numero entre 1 e 5: 3

Excelente!

Magistral!

Incrivel!

Informe um numero entre 1 e 5: 8

Muito grande!

Page 112: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

112 Pesquisa e Desenvolvimento Tecnológico

Análise

O usuário é solicitado por um número nas linhas 9 e 10. O número é passado à instrução switch na linha 11. Se o

número é zero, a instrução case na linha 13 coincide, a mensagem "Muito pequeno, desculpe!" é exibida e a

instrução break na linha 14 encerra o switch. Se o valor é 5, a execução desvia para a linha 15, onde uma

mensagem é exibida, continua na linha 16, outra mensagem é exibida, e assim por diante até encontrar um

break na linha 20, quando então o switch termina.

O efeito ruim destas instruções é que para um número entre 1 e 5, muitas mensagens são exibidas. Se o valor

do número não está entre 0 e 5, supõe-se que seja muito grande e a instrução default é invocada na linha 21.

A instrução switch

A sintaxe da instrução switch é:

switch (expressão) { case valorUm: instrução; break; case valorDois: instrução; break; .... case valorN: instrução; break; default: instrução; }

A instrução switch permite ramificar em múltiplos valores de expressão. A expressão é avaliada e se

coincide com algum dos valores em case, a execução salta para aquela linha e continua até o fim do

bloco switch ou até encontrar uma instrução break.

Se expressão não coincide com nenhuma das instruções case, e se existe uma instrução default, a

execução salta para a default, caso contrário o bloco switch é encerrado.

Exemplo 1 switch (escolha) { case 0: cout << “Zero!” << endl; break; case 1: cout << “Um!” << endl; break; case 2: cout << “Dois!” << endl; default: cout << “Default!” << endl; }

Exemplo 2

switch (escolha) { case 0: case 1: case 2: cout << “Menor que 3!”; break; case 3:

Page 113: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

113 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

cout << “Igual a 3!”; break; default: cout << “Maior que 3!”; }

Usando uma instrução switch com um menu

A listagem 7.17 retorna ao laço for(; ;) discutido anteriormente. Estes loops são também chamados loops eternos

ou infinitos porque ele executará eternamente se um break não for encontrado. Na listagem, um loop eterno é

usado para exibir um menu, solicitar uma escolha do usuário, atuar sobre a escolha e retornar ao menu. Isto

continua até o usuário escolher sair.

Observação: alguns programadores gostam de escrever:

#define EVER; for (EVER) { // instruções; }

Numa alusão à palavra inglesa forever (para sempre).

1. //Listagem 7.17 2. //Usando um laço infinito para manusear iteração com usuário 3. #include <iostream> 4. 5. // protótipos 6. int menu(); 7. void FacaTarefaUm(); 8. void FacaTarefaMuitas(int); 9. 10. using namespace std; 11. 12. int main() 13. { 14. bool sair = false; 15. for (;;) 16. { 17. int escolha = menu(); 18. switch(escolha) 19. { 20. case (1): 21. FacaTarefaUm(); 22. break; 23. case (2): 24. FacaTarefaMuitas(2); 25. break; 26. case (3): 27. FacaTarefaMuitas(3); 28. break; 29. case (4): 30. continue; // redundante! 31. break; 32. case (5): 33. sair=true; 34. break; 35. default: 36. cout << "Por favor, selecione novamente! " << endl; 37. break; 38. } // termina switch 39. 40. if (sair == true)

Page 114: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

114 Pesquisa e Desenvolvimento Tecnológico

41. break; 42. } // encerra infinito 43. return 0; 44. } // encerra main() 45. 46. int menu() 47. { 48. int escolha; 49. 50. cout << " **** Menu **** " << endl << endl; 51. cout << "(1) Escolha um. " << endl; 52. cout << "(2) Escolha dois. " << endl; 53. cout << "(3) Escolha tres. " << endl; 54. cout << "(4) Reexibir menu. " << endl; 55. cout << "(5) Sair. " << endl << endl; 56. cout << ": "; 57. cin >> escolha; 58. return escolha; 59. } 60. 61. void FacaTarefaUm() 62. { 63. cout << "Tarefa um! " << endl; 64. } 65. 66. void FacaTarefaMuitas(int qual) 67. { 68. if (qual == 2) 69. cout << "Tarefa dois! " << endl; 70. else 71. cout << "Tarefa tres! " << endl; 72. }

Saída

**** Menu ****

(1) Escolha um.

(2) Escolha dois.

(3) Escolha tres.

(4) Reexibir menu.

(5) Sair.

: 1

Tarefa um!

**** Menu ****

(1) Escolha um.

(2) Escolha dois.

(3) Escolha tres.

(4) Reexibir menu.

(5) Sair.

: 3

Tarefa tres!

**** Menu ****

(1) Escolha um.

(2) Escolha dois.

(3) Escolha tres.

(4) Reexibir menu.

(5) Sair.

: 5

Page 115: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

115 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Análise

Este programa trás junto vários conceitos desta e de lições anteriores. Também mostra um uso comum de uma

instrução switch.

O laço infinito começa na linha 15. A função Menu() é chamada, que exibe o menu na tela e retorna a seleção do

usuário. A instrução switch, que começa na linha 18 e termina na linha 38, comuta a escolha do usuário.

Se o usuário digitar 1, a execução salta para case (1) na linha 20. A linha 21 executa a função FaçaTarefaUm(),

que exibe uma mensagem e retorna. No retorno, a execução continua na linha 22, onde o break termina a

instrução switch e a execução cai na linha 39. Na linha 40 a variável sair é avaliada para verificar se é verdadeira.

Se for, o break na linha 41 é executado e o laço for (; ;) termina, mas se sair for false, a execução continua no

início do laço, na linha 15.

Observe que a instrução continue na linha 30 é redundante. Se ela não fosse usada e a instrução break fosse

encontrada, o switch terminaria, sair seria avaliada como false, o laço iria reiterar e o menu exibido novamente.

O continue, entretanto, ignora o teste de sair.

Faça Não faça

Documente cuidadosamente todos os case sem

instrução.

Coloque um case default na instrução switch, se for

somente para detectar situações aparentemente

impossíveis.

Não use instruções complexas if...else se uma

instrução switch mais clara funcionar.

Não esqueça do break no fim de cada case, a menos

que você queira continuar.

Perguntas e respostas

Como escolho entre for...else e switch?

- Se mais que uma ou duas cláusulas else são usadas, e todas são testadas com o mesmo valor, considere usar

um switch.

Como escolho entre while e do...while?

- Se o corpo do loop deve executar pelo menos uma vez, considere o laço do...while, senão, tente usar o laço

while.

Como escolho entre while e for?

- Se você está inicializando uma variável contadora, testando e incrementando a variável a cada laço, escolha

um for.Se a variável já está inicializada e não é incrementada a cada loop, um while pode ser uma escolha

melhor. Programadores experientes procuram por esta utilização e acharão seu código difícil de ler se suas

expectativas forem violadas.

É melhor usar while (true) ou for (; ;)?

- Não existe diferença, entretanto é melhor evitar os dois.

Por que não devo usar uma variável como uma condição, como em while (n)?

- No padrão C++ atual, uma expressão é avaliada para um booleano true ou false. Apesar de poder igualar false

a zero e true a qualquer outro valor, é melhor - e mais afinado com os padrões atuais - usar uma expressão que

avalia para true ou false. Entretanto, uma variável tipo bool pode ser usada numa condição, sem nenhum

problema aparente.

Page 116: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

116 Pesquisa e Desenvolvimento Tecnológico

Teste

1 - Como você inicializa mais de uma variável num laço for?

2 - Por que goto é evitado?

3 - É possível escrever um laço for com um corpo que nunca é executado?

4 - Qual o valor de x quando o laço for completar?

for (int x = 0; x < 100; x++)

5 - É possível aninhar laços while dentro de laços for?

6 - É possível criar um laço que nunca termina? Dê um exemplo.

7 - O que acontece se você cria um laço que nunca termina?

Exercícios

1 - Escreva um laço for aninhado que exiba uma matriz de 10 x 10 zeros.

2 - Escreva uma instrução for que conte de 100 a 200 de 2 em 2.

3 - Escreva um laço while que conte de 100 a 200 de 2 em 2.

4 - Escreva um laço do...while que conte de 100 a 200 de 2 em 2.

5 - Caça-Erros: o que está errado neste código?

int contador = 0; while (contador < 10) { cout << "contador: " << contador; }

6 - Caça-Erros: o que está errado neste código?

for (int contador = 0; contador < 10; contador ++); cout << contador << " ";

7 - Caça-Erros: o que está errado neste código?

int contador = 100; while (contador < 10) { cout << " contador agora: " << contador; counter--; }

8 - Caça-Erros: o que está errado neste código?

cout << "Informe um numero entre 0 e 5: "; cin >> numero; switch (numero) { case 0: FacaZero();

Page 117: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

117 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

case 1: // prossiga case 2: // prossiga case 3: // prossiga case 4: // prossiga case 5: FacaUmACinco(); break; default: FacaDefault(); break; }

Page 118: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

118 Pesquisa e Desenvolvimento Tecnológico

Lição 8

Ponteiros Explicados

Uma das poderosas ferramentas disponível para programadores C++ com características de programação de

baixo-nível é a habilidade de manipular a memória do computador diretamente por meio do uso de ponteiros.

Essa é uma vantagem que C++ tem sobre outras linguagens, tais como Java, C# e Visual Basic.

Quando se está aprendendo C++, ponteiros apresentam dois desafios em especial: Eles podem ser um pouco

confusos, e não é imediatamente obvia a razão de termos de usar ponteiros. Essa lição explica como ponteiros

funcionam, passo a passo. Porém, você só entenderá a necessidade de ponteiros à medida que avançarmos

nesse livro.

Nesta lição você aprendera:

O que são ponteiros.

Como declarar e usar ponteiros.

O que é o free store e como manipular a memória.

O que é um ponteiro?

Um ponteiro é uma variável que contém um endereço de memória. Só isto. Se você entende esta simples frase,

você conhece o núcleo do que se deve saber sobre ponteiros.

Uma palavra sobre memória

Para entender ponteiros, você precisa conhecer um pouco sobre memória de computador. A memória do

computador é dividida em locações de memória sequencialmente numeradas. Cada variável é colocada numa

única locação na memória, conhecida como seu endereço. A figura abaixo mostra uma representação

esquemática da armazenagem de um unsigned long int chamada theAge.

Observação: a capacidade de usar ponteiros e manipular a memória em baixo nível é um dos fatores que

tornam C++ a linguagem escolhida para aplicações embarcadas e de tempo real.

Page 119: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

119 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Obtendo o endereço de memória de variáveis

Computadores diferentes numeram sua memória usando esquemas complexos diferentes. Normalmente, como

programador, você não precisa conhecer o endereço específico de uma variável por que o compilador lida com

estes detalhes. Se você quer esta informação, entretanto, pode usar o operador endereço-de &, que retorna o

endereço de um objeto na memória. A listagem 8.1 é usada para ilustrar o uso deste operador.

1. // Listagem 8.1 Demonstra o operador endereço-de 2. // e o endereço de uma variável local 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. unsigned short varShort=5; 9. unsigned long varLong=65535; 10. long sVar = -65535; 11. 12. cout << "varShort:\t" << varShort; 13. cout << "\tEndereco de varShort:\t"; 14. cout << &varShort << endl; 15. 16. cout << "varLong:\t" << varLong; 17. cout << "\tEndereco de varLong:\t" ; 18. cout << &varLong << endl; 19. 20. cout << "sVar:\t\t" << sVar; 21. cout << "\tEndereco de sVar:\t" ; 22. cout << &sVar << endl; 23. 24. return 0; 25. }

Saída

varShort: 5 Endereco de varShort: 0041F824

varLong: 65535 Endereco de varLong: 0041F818

sVar: -65535 Endereco de sVar: 0041F80C

(Seu resultado poderá ser diferente, principalmente a última coluna)

Análise

Três variáveis são declaradas e inicializadas: um unsigned short int na linha 8, um unsigned long int na linha 9 e

um long na linha 10. Seus valores e endereços são exibidos nas linhas 12 a 22. Você pode ver nas linhas 14, 18 e

22 que o operador endereço-de (&) é usado para obter o endereço da variável. Este operador simplesmente é

colocado na frente do nome da variável, para ter o endereço retornado.

A linha 12 exibe o valor de varShort como 5, como esperado. Na primeira linha de saída, você pode ver o

endereço 0041F824. Este endereço é especifico de cada computador e pode variar até mesmo de uma execução

para outra do mesmo programa. Seus resultados com certeza serão diferentes destes.

Quando você declara uma variável, o compilador determina a memória necessária baseado no tipo da variável.

Ele cuida da alocação de memória e automaticamente atribui um endereço para ela. Para um inteiro long que

tipicamente ocupa quatro bytes, esta será a memória usada.

Observação: seu compilador pode insistir em alocar quatro bytes para novas variáveis. Assim, varLong foi

alocada num endereço quatro bytes após varShort, mesmo que varShort precise apenas de dois bytes!

Page 120: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

120 Pesquisa e Desenvolvimento Tecnológico

Armazenando o endereço de uma variável num ponteiro

Cada variável tem um endereço. Mesmo não sabendo este endereço específico, você pode armazená-lo num

ponteiro.

Suponha por exemplo que idade seja um inteiro. Para declarar um ponteiro chamado pIdade para armazenar seu

endereço, você escreve:

int *pIdade = nullptr;

Isto declara pIdade para ser um ponteiro para um inteiro, ou seja, pIdade é declarada para armazenar o

endereço de um inteiro.

Observe que pIdade é uma variável. Quando você declara um tipo inteiro (int), o compilador reserva memória

suficiente para guardar um endereço (normalmente, quatro bytes). Um ponteiro é apenas um tipo diferente de

variável, e isso se aplica para pIdade.

Nomes de ponteiros

Sendo ponteiros apenas outras variáveis, você pode usar qualquer nome válido para variáveis. As mesmas regras

de nomenclatura e sugestões se aplicam. Muitos programadores seguem a convenção de nomear todos os

ponteiros iniciando com p, como em pIdade e pNumero.

int *pIdade = 0;

No exemplo acima, pIdade é inicializada para zero. Um ponteiro cujo valor é zero é chamado um ponteiro nulo

(use nullptr para ponteiros nulos). Todos os ponteiros, quando criados, devem ser inicializados para algo. Se

você não sabe o que atribuir a um ponteiro, atribua nullptr (o mesmo que 0). Um ponteiro não inicializado é

chamado wild pointer ou dangling pointer (ponteiro selvagem ou pendente) por que você não tem nenhuma

idéia do que ele está apontando - que pode ser qualquer coisa! Wild pointers são muito perigosos.

Dica: pratique a computação segura: inicialize todos seus ponteiros!

Para um ponteiro conter um endereço, este endereço deve ser atribuído a ele. No exemplo anterior, você deve

especificamente atribuir o endereço de idade para pIdade, como mostrado abaixo:

unsigned short int idade = 50; // cria uma variável unsigned short int * pIdade = 0; // cria um ponteiro pIdade = &idade; // coloca o endereço de idade em pIdade

A primeira linha cria a variável idade, do tipo unsigned short int e a inicializa com o valor de 50. A segunda linha

declara pIdade como um ponteiro para o tipo unsigned short int e o inicializa para zero. Você sabe que pIdade é

um ponteiro pelo (*) após o tipo de variável e antes do seu nome.

A terceira e última linha atribui o endereço de idade para o ponteiro pIdade. Você pode dizer que o endereço de

idade está sendo atribuído pelo uso do operador endereço-de (&). Se este operador não tivesse sido usado, o

valor de idade teria sido atribuído, que poderia ou não ter um endereço válido.

Neste ponto, pIdade tem como seu valor o endereço de idade, que por sua vez tem o valor de 50. Você poderia

ter chegado nisto com um passo a menos, como segue:

unsigned short int idade = 50; // cria uma variável unsigned short int * pIdade = &idade; // cria um ponteiro para idade

Page 121: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

121 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Obtendo o valor de uma variável

Usando pIdade você pode realmente determinar o valor de idade, que neste caso é 50. Acessar o valor de uma

variável usando um ponteiro é chamado indireção por que você está acessando a variável via uma referência,

por meio do endereço de memória no ponteiro. Por exemplo, você pode usar indireção com o ponteiro pIdade

para acessar o valor em idade.

indireção significa acessar o valor que está contido no endereço armazenado por um ponteiro. O ponteiro

fornece um meio indireto de obter o valor guardado naquele endereço.

Observação: Com uma variável normal, o tipo de variável diz ao compilador quanta memória é necessária para

armazenar o valor. Com um ponteiro, o tipo de variável não faz isto. Todos os ponteiros armazenam endereços

na memória, que têm o mesmo tamanho - normalmente quatro bytes num processador 32 bits e oito num

processador 64 bits.

O tipo informa ao compilador quanta memória é necessária para o objeto naquele endereço, que o ponteiro

armazena.

Observe a seguinte declaração:

unsigned short int * pIdade = nullptr; // cria um ponteiro

pIdade é declarado para ser um ponteiro para um unsigned short int. Isto informa ao compilador que o ponteiro

(que precisa de quatro bytes para armazenar um endereço) conterá o endereço de um objeto do tipo unsigned

short int, que por si só requer dois bytes.

Desreferência com o operador de indireção

O operador de indireção (*) também é chamado operador de desreferência. Quando um ponteiro é

desreferenciado, obtem-se o valor que está no endereço armazenado pelo ponteiro.

Variáveis normais fornecem acesso direto aos seus próprios valores. Se você cria uma nova variável do tipo

unsigned short int chamada suaIdade, e quer atribuir o valor em idade para esta nova variável, você escreve

unsigned short int suaIdade; suaIdade = idade;

Um ponteiro fornece um acesso indireto ao valor da variável cujo endereço ele contém. Para atribuir o valor em

idade para a nova variável suaIdade através do ponteiro pIdade, você escreve

unsigned short int suaIdade; suaIdade = *pIdade;

O operador de indireção (*) na frente de pIdade significa "Pegue o valor armazenado no endereço em pIdade e

atribua a suaIdade". É necessário que você desreferencie o ponteiro, e tome cuidado para não cometer o

seguinte erro.

suaIdade = pIdade; // errado!

Nesse caso, você estará tentando atribuir o valor em pIdade, um endereço de memória, para suaIdade. Seu

compilador provavelmente lhe dará um aviso que você está cometendo um erro.

Diferentes usos do asterisco

Page 122: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

122 Pesquisa e Desenvolvimento Tecnológico

O asterisco (*) é usado de formas diferentes com ponteiros: como parte da declaração do ponteiro e

também como operador de desreferência.

Quando você declara um ponteiro, o * faz parte da declaração e segue o tipo do objeto apontado. Por

exemplo:

// cria um ponteiro para um unsigned short unsigned short * pIdade = nullptr;

Quando o ponteiro é desreferenciado, o operador de desreferência (ou indireção) indica que o valor no

local de memória armazenado pelo ponteiro deve ser acessado, ao invés do endereço propriamente.

// atribui 5 ao valor de pIdade *pIdade = 5;

Observe também que o mesmo caracter * é usado para o operador de multiplicação. O compilador sabe

qual operador chamar baseado em como você o está usando (contexto).

Ponteiros, endereços e variáveis

É importante distinguir entre um ponteiro, o valor que ele armazena e o valor no endereço armazenado no

ponteiro. Isto é fonte de muita confusão sobre ponteiros. Considere o seguinte fragmento de código:

int theVariable = 5; int * pPointer = &theVariable;

theVariable é declarada como inteira e inicializada com o valor 5. pPointer é declarado como ponteiro para um

inteiro, e é inicializado com o endereço de theVariable. O valor no endereço que pPointer contém é 5. A figura

abaixo mostra o diagrama esquemático de theVariable e pPointer.

Na figura, o valor 5 é armazenado no endereço 101. Isto é mostrado no número binário

0000 0000 0000 0101

Isto são dois bytes (16 bits) cujo valor decimal é 5. A variável ponteiro está no endereço 106 e seu valor é:

000 0000 0000 0000 0000 0000 0110 0101

Isto é a representação binária do valor 101, que é o endereço de theVariable, cujo valor é 5. O esboço de

memória aqui é esquemático, mas ilustra a idéia de como ponteiros armazenam um endereço.

Page 123: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

123 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Manuseando dados usando ponteiros

Além de usar a desreferenciação para ver qual dado está armazenado no local apontado pela variável, você

também pode manusear este dado. Após o ponteiro ser atribuído a um endereço, você pode usá-lo para

acessar os dados na variável que está sendo apontada.

A listagem 8.2 junta o que você aprendeu até aqui sobre ponteiros. Nela você vê como um endereço de uma

variável local é atribuído a um ponteiro e como o ponteiro pode ser usado com o operador de indireção para

manusear os valores nesta variável.

1. // Listagem 8.2 Usando ponteiros 2. #include <iostream> 3. 4. typedef unsigned short int USHORT; 5. 6. int main() 7. { 8. 9. using namespace std; 10. 11. USHORT minhaIdade; // uma variável 12. USHORT * pIdade = 0; // um ponteiro 13. 14. minhaIdade = 5; 15. 16. cout << "minhaIdade: " << minhaIdade << endl; 17. pIdade = &minhaIdade; // atribuindo endereço de minhaIdade para pIdade 18. cout << "*pIdade: " << *pIdade << endl << endl; 19. 20. cout << "Atribuindo *pIdade = 7... " << endl; 21. *pIdade = 7; // atribui minhaIdade para 7 22. 23. cout << "*pIdade: " << *pIdade << endl; 24. cout << "minhaIdade: " << minhaIdade << endl << endl; 25. 26. cout << "Atribuindo minhaIdade = 9... " << endl; 27. minhaIdade = 9; 28. 29. cout << "minhaIdade: " << minhaIdade << endl; 30. cout << "*pIdade: " << *pIdade << endl; 31. 32. return 0; 33. }

Saída

minhaIdade: 5

*pIdade: 5

Atribuindo *pIdade = 7...

*pIdade: 7

minhaIdade: 7

Atribuindo minhaIdade = 9...

minhaIdade: 9

*pIdade: 9

Análise

Page 124: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

124 Pesquisa e Desenvolvimento Tecnológico

O programa declara duas variáveis: uma unsigned short, minhaIdade, e um ponteiro para um unsigned short,

pIdade. minhaIdade é atribuída ao valor 5 na linha 14. Em seguida, isto é verificado pela saída exibida na linha

16.

Na linha 17, pIdade é atribuída ao endereço de minhaIdade. Na linha 18 pIdade é desreferenciada - usando o

operador de indireção * - e exibida, mostrando que o valor no endereço que pIdade armazena é o 5

armazenado por minhaIdade.

Na linha 21 o valor 7 é atribuído à variável no endereço armazenado em pIdade. Isto configura minhaIdade para

7, e as saídas das linhas 23 e 24 confirmam isto. Novamente, você deve observar que o acesso indireto à variável

foi obtido usando um asterisco - o operador de indireção, neste contexto – ou seja, o ponteiro sempre tem que

ser desreferenciado para ter seu valor acesso para leitura e alteração.

Na linha 27 o valor 9 é atribuído à variável minhaIdade. Este valor é obtido diretamente na linha 29 e

indiretamente (desreferenciando pIdade) na linha 30.

Examinando o endereço

Ponteiros lhe permitem manusear endereços sem saber seus valores reais. A partir de agora, você pode

acreditar que quando atribui o endereço de uma variável a um ponteiro, ele realmente tem o endereço desta

variável como seu valor. Mas só desta vez, por que não verificar, para ter certeza? A listagem 8.3 ilustra a idéia.

1. // Listagem 8.3 2. // O que é armazenado por um ponteiro. 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. 9. unsigned short int minhaIdade = 5, suaIdade = 10; 10. 11. // um ponteiro 12. unsigned short int * pIdade = &minhaIdade; 13. 14. cout << "minhaIdade:\t" << minhaIdade 15. << "\t\tsuaIdade:\t" << suaIdade << endl; 16. 17. cout << "&minhaIdade:\t" << &minhaIdade 18. << "\t&suaIdade:\t" << &suaIdade << endl; 19. 20. cout << "pIdade:\t" << pIdade << endl; 21. cout << "*pIdade:\t" << *pIdade << endl; 22. 23. 24. cout << "\nReatribuindo: pIdade = &suaIdade..." << endl << endl; 25. pIdade = &suaIdade; // reatribuindo o ponteiro 26. 27. cout << "minhaIdade:\t" << minhaIdade << 28. "\t\tsuaIdade:\t" << suaIdade << endl; 29. 30. cout << "&minhaIdade:\t" << &minhaIdade 31. << "\t&suaIdade:\t" << &suaIdade << endl; 32. 33. cout << "pIdade:\t" << pIdade << endl; 34. cout << "*pIdade:\t" << *pIdade << endl; 35. 36. cout << "\n&pIdade:\t" << &pIdade << endl; 37. 38. return 0;

Page 125: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

125 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

39. }

Saída

minhaIdade: 5 suaIdade: 10

&minhaIdade: 0040FAAC &suaIdade: 0040FAA0

pIdade: 0040FAAC

*pIdade: 5

Reatribuindo: pIdade = &suaIdade...

minhaIdade: 5 suaIdade: 10

&minhaIdade: 0040FAAC &suaIdade: 0040FAA0

pIdade: 0040FAA0

*pIdade: 10

&pIdade: 0040FA94

Análise

Na linha 9, minhaIdade e suaIdade são declaradas como variáveis do tipo unsigned short. Na linha 12, pIdade é

declarada como ponteiro para um unsigned short, e é inicializado com o endereço da variável minhaIdade.

As linhas 14 a 18 mostram os valores e endereços de minhaIdade e suaIdade. A linha 20 exibe o conteúdo de

pIdade, que é o endereço de minhaIdade. Observe que a saída confirma que o valor de pIdade coincide com o

endereço de minhaIdade. A linha 21 mostra o resultado de desreferenciar pIdade, que exibe o valor em pIdade, o

valor de minhaIdade, 5.

Isto é a essência de ponteiros. A linha 20 mostra que pIdade armazena o endereço de minhaIdade e a linha 21

mostra como obter o valor armazenado em minhaIdade desreferenciando o ponteiro pIdade. Certifique-se de

ter entendido isto completamente, antes de prosseguir. Estude o código e veja o resultado.

Na linha 25 pIdade é atribuído novamente para apontar o endereço de suaIdade. Os valores e endereços são

exibidos novamente. A saída mostra que agora pIdade tem o endereço de suaIdade e que a desreferência obtém

o valor em suaIdade.

A linha 36 exibe o próprio endereço de pIdade. Como uma variável, ele tem um endereço, e este endereço pode

ser armazenado num ponteiro (atribuir o endereço de um ponteiro a outro ponteiro será discutido em breve).

Ponteiros e nomes de arrays

Em C++, um nome de array é um ponteiro constante para o primeiro elemento do array. Para entendermos, dê

uma olhada na declaração de array abaixo.

int numeros[5];

Nesse caso, numeros é um ponteiro para &numero[0], que é o endereço do primeiro elemento do array.

É valido usar nomes de arrays como ponteiros constantes e vice-versa. Portanto, numeros + 4 é uma forma

legítima de acessar o inteiro em numero[4].

O compilador faz toda a aritmética quando você soma, incrementa ou decrementa ponteiros. O endereço

acessado quando você escreve numeros + 4 não está quatro bytes após o endereço de numeros, ele está a

quatro objetos, ou seja, quatro inteiros. Como um inteiro tipicamente tem 4 bytes de comprimento, numeros + 4

está a 16 bytes do início do array. Esta relação entre ponteiros e arrays é ilustrada na listagem 8.4, que usa

ponteiros para exibir o conteúdo de um array.

Page 126: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

126 Pesquisa e Desenvolvimento Tecnológico

1. #include <iostream> 2. const int TAMANHO_ARRAY = 5; 3. 4. int main () 5. { 6. using namespace std; 7. 8. // Um array de 5 inteiros inicializado com 5 valores 9. int numeros [TAMANHO_ARRAY] = {0, 100, 200, 300, 400}; 10. 11. // pInt aponta para o primeiro elemento 12. const int *pInt = numeros; 13. 14. cout << "Usando um ponteiro que exibe o conteudo do array: " << endl; 15. 16. for (int indice = 0; indice < TAMANHO_ARRAY; ++ indice) 17. cout << "Elemento [" << indice << "] = " << *(pInt + indice) << endl; 18. 19. return 0; 20. }

Saída

Usando um ponteiro que exibe o conteudo do array:

Elemento [0] = 0

Elemento [1] = 100

Elemento [2] = 200

Elemento [3] = 300

Elemento [4] = 400

Análise

Criamos um array de cinco inteiros chamado numeros e acessamos seus elementos usando o ponteiro pInt.

Como um nome de array é um ponteiro para o primeiro elemento, pInt faz o mesmo. Assim (pInt + 1) é um

ponteiro para o segundo elemento, (pInt + 2) um ponteiro para o terceiro e assim por diante. Desreferenciando

estes ponteiros ajuda a trazer os valores que estão apontando. Assim, *pInt retorna o primeiro valor, *(pInt + 1)

o segundo valor, e assim por diante.

O exemplo anterior demonstra a similaridade entre ponteiros e arrays. De fato, na linha 16, simplesmente

usamos pInt[indice] ao invés de *(pInt + indice) e ainda assim recebemos a mesma saída.

Um ponteiro para um array versus um array de ponteiros

Examine as seguintes declarações:

int numerosUm[500]; int * numerosDois[500]; int * numerosTres[500] = new int[500];

numerosUm é um array de 500 objetos int. numerosDois é um array de 500 ponteiros para objetos int.

numerosTres é um ponteiro para um array de 500 objetos int.

Você já viu que um nome de array como numerosUm é na verdade um ponteiro para o primeiro elemento no

array. Portanto torna-se claro que a primeira e última declaração são mais similares. numerosDois, entretanto,

destaca-se por ser um array de ponteiros que apontam para inteiros, ou seja, ele contém 500 endereços que

podem apontar para inteiros na memória.

Faça Não faça

Use o operador de indireção (*) para Não confunda o endereço em um ponteiro com o valor que

Page 127: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

127 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

acessar os dados armazenados no endereço

que está no ponteiro.

Inicialize todos os ponteiros, para um valor

válido ou para nullptr (0).

ele endereça

Usando ponteiros

Para declarar um ponteiro, escreva o tipo da variável ou objeto cujo endereço será armazenado no

ponteiro, seguido pelo operador de ponteiro (*) e o nome do ponteiro. Por exemplo:

unsigned short int * pPonteiro = 0;

Para atribuir ou inicializar um ponteiro, prefixe o nome da variável cujo endereço está sendo atribuído,

com o operador endereço-de (&). Por exemplo:

unsigned short int theVariable = 5; unsigned short int * pPointer = &theVariable;

Para desreferenciar um ponteiro, prefixe o nome do ponteiro com o operador de desreferência (*). Por

exemplo:

unsigned short int theValue = *pPointer;

Porque você usaria ponteiros

Até agora, você viu passo a passo detalhes de atribuir o endereço de uma variável a um ponteiro. Na prática,

você nunca faz isto. Afinal, porque se incomodar com um ponteiro quando você já tem uma variável com acesso

aquele valor? A única razão para este tipo de manuseio de ponteiro de uma variável é demonstrar como

ponteiros funcionam. Agora que você está confortável com a sintaxe dos ponteiros, você pode usá-los para

boas causas. Ponteiros são usados mais frequentemente para três tarefas:

Manuseio de dados na área livre da memória (heap ou free store)

Acessar dados e funções de membros de classes

Passar valores por referência para funções

O restante desta lição é focado nas duas primeiras tarefas.

A pilha (stack) e a área livre (heap)

Na sessão "Como funções funcionam - Uma olhada sob o capô" na lição 6 "Organizando código com funções",

cinco áreas da memória são mencionadas:

namespace Global

área livre (heap)

registradores

área do código

pilha (stack)

Variáveis locais estão na pilha, junto com parâmetros de funções. Código está na área do código, é claro e

variáveis globais estão na namespace Global. Registradores são usados para funções de trabalhos internos,

Page 128: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

128 Pesquisa e Desenvolvimento Tecnológico

como controlar o topo da pilha e o ponteiro de instrução. Quase toda a memória restante é dada para a área

livre, que normalmente é chamada de heap.

Variáveis locais não são persistentes. Quando uma função retorna, suas variáveis locais são destruídas. Isto é

bom por que o programador não tem que fazer nada mais para gerenciar esta parte da memória. Porém, isso

também é ruim por que torna difícil para funções criarem objetos para uso de outros objetos ou funções sem

gerar a carga extra de copiar objetos da pilha para objetos destinados na chamada de função. Variáveis globais

resolvem este problema ao custo de prover acesso irrestrito destas variáveis em todo o programa, que leva a

criação de código difícil de entender e manter. Colocar dados na área livre resolve ambos os problemas se os

dados forem manuseados apropriadamente.

Você pode imaginar esta área livre como uma grande sessão da memória onde milhares de cubículos

numerados sequencialmente ficam aguardando seus dados. Você não pode rotular estes cubículos, entretanto,

como pode com a pilha. Você deve solicitar o endereço do cubículo que você reservou e armazenar este

endereço num ponteiro.

Uma maneira de pensar sobre isto é com uma analogia: um amigo lhe deu o número 0800 da Acme Pedidos por

Correio. Você vai para casa e programa seu telefone com este número e então joga fora o pedaço de papel com

o número anotado nele. Se pressionar o botão, um telefone toca em algum lugar e a Acme Pedidos por Correio

responde. Você não se lembra do número e não sabe onde o telefone de pedidos está localizado, mas o botão

lhe dá acesso à Acme Pedidos por Correio. Acme Pedidos por Correio é sua informação na área livre. Você não

sabe onde está, mas sabe como obtê-la. Você a acessa através de seu endereço - neste caso, o número do

telefone. Você não precisa conhecer este número, precisa apenas colocá-lo num ponteiro (o botão). O ponteiro

dá acesso á sua informação sem incomodar com os detalhes.

A pilha é limpa automaticamente quando a função retorna. Todas as variáveis locais saem do escopo e são

removidas da pilha. A área livre não é limpa até que seu programa termine, e você é responsável por liberar

qualquer memória reservada quando não a utiliza mais. Aqui é onde destrutores são absolutamente críticos: eles

fornecem um local onde qualquer memória heap alocada na classe pode ser recuperada.

A vantagem da área livre também é que a memória que você reserva permanece disponível até que você

explicitamente declara que terminou com ela, liberando-a. Se você se descuidar de liberar esta memória, ela

pode crescer com o tempo e causar uma pane no sistema.

A vantagem de acessar memória desta maneira, no lugar de variáveis globais, é que apenas funções com acesso

ao ponteiro (que tem o endereço apropriado) têm acesso aos dados. Isto exige que o objeto contendo o

ponteiro para os dados, ou que o ponteiro em si mesmo, seja passado para funções que causam mudanças,

reduzindo assim as chances de uma função alterar os dados sem que a alteração seja rastreável.

Para isto funcionar, você deve ser capaz de criar um ponteiro na área livre e passar este ponteiro entre funções.

A próxima sessão descreve como fazer isto.

Alocando espaço com a palavra chave new

Você aloca memória na área livre usando a palavra chave new, seguida pelo tipo de objeto a ser alocado, assim

o compilador sabe quanta memória é necessária. Desta forma, new unsigned short int aloca dois bytes e new

long aloca quatro bytes.

O valor retornado por new é o endereço de memória. Como você sabe que endereços são armazenados em

ponteiros, não será nenhuma surpresa que o valor de retorno de new deverá ser atribuído a um ponteiro. Para

criar um unsigned short na área livre, você deve escrever

Page 129: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

129 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

unsigned short int * pPonteiro; pPonteiro = new unsigned short int;

Você pode, é claro, fazer isto em apenas uma linha, inicializando o ponteiro ao mesmo tempo em que o declara:

unsigned short int * pPonteiro = new unsigned short int;

Nos dois casos, pPonteiro agora aponta para um unsigned short int na área livre. Você usa isto como qualquer

outro ponteiro para uma variável e atribui um valor a esta área usando a forma ilustrada abaixo.

*pPonteiro = 72;

Que significa "Coloque 72 no valor em pPonteiro" ou "Atribua o valor 72 ao local na área livre para onde

pPonteiro aponta".

Observação: se new não consegue criar espaço na área livre (memória, afinal, é um recurso limitado), ele lança

uma exception (veja lição 28, "Manuseio de exceções").

Colocando memória de volta: a palavra chave delete

Quando você terminar com a área de memória, você deve devolvê-la liberada ao sistema, chamando delete no

ponteiro, que retorna a memória para área livre.

É crítico lembrar que memória alocada com new não é liberada automaticamente. Se um ponteiro indica uma

memória na área livre e este ponteiro perde o escopo, a memória não é devolvida para a área livre. Ao contrário,

ela é considerada alocada e como o ponteiro não está mais disponível, ela não pode mais ser acessada. Isto

acontece, por exemplo, se o ponteiro é uma variável local. Quando a função onde este ponteiro está declarado

retorna, o ponteiro sai do escopo e é perdido. A memória alocada com new não é liberada, apesar de se tornar

inacessível.

Esta situação é chamada vazamento de memória (memory leak), pois esta memória não pode ser recuperada até

que o programa termine. É como se a memória tivesse vazado de seu computador.

Para prevenir vazamento de memória, você deve recuperar toda memória alocada de volta para a área livre,

usando a palavra chave delete. Por exemplo:

delete pPonteiro;

Quando você deleta o ponteiro, você está realmente liberando a memória cujo endereço é armazenado no

ponteiro. Você está dizendo "Retorne para a área livre a memória que este ponteiro aponta". O ponteiro ainda é

um ponteiro, e pode ser realocado. A listagem 8.5 demonstra alocação de uma variável na heap, utilização desta

variável e sua exclusão.

Mais frequentemente, você alocará itens na memória heap num construtor e liberará num destrutor. Em outros

casos, você inicializará ponteiros num construtor, alocará memória para estes ponteiros à medida que o objeto

for usado e então, no destrutor, testará o ponteiro para nulo e desalocará se ele for diferente de nulo.

Cuidado: quando você chama delete num ponteiro, a memória que ele aponta será liberada. Chamando delete

novamente neste ponteiro causará uma falha no seu programa! Quando você apagar um ponteiro, atribua-o a

zero (null). Chamar delete num ponteiro nulo é garantido ser seguro. Por exemplo:

Animal *pCachorro = new Animal // aloca memória delete pCachorro; // libera memória pCachorro = nullptr; // configura ponteiro para nulo delete pCachorro; // inofensivo

Page 130: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

130 Pesquisa e Desenvolvimento Tecnológico

1. // Listagem 8.5 2. // Alocando e apagando um ponteiro 3. #include <iostream> 4. int main() 5. { 6. using namespace std; 7. int variavelLocal = 5; 8. int * pLocal= &variavelLocal; 9. int * pHeap = new int; 10. *pHeap = 7; 11. cout << "localVariable: " << variavelLocal << endl; 12. cout << "*pLocal: " << *pLocal << endl; 13. cout << "*pHeap: " << *pHeap << endl; 14. delete pHeap; 15. pHeap = new int; 16. *pHeap = 9; 17. cout << "*pHeap: " << *pHeap << endl; 18. delete pHeap; 19. return 0; 20. }

Saída

variavelLocal: 5 *pLocal: 5

*pHeap: 7

*pHeap: 9

Análise

A linha 7 declara e inicializa uma variável local ironicamente chamada de variavelLocal. A linha 8 declara um

ponteiro chamado pLocal e o inicializa com o endereço da variável local. Na linha 9, um segundo ponteiro

chamado pHeap é declarado, contudo, ele é inicializado com o resultado obtido de uma chamada a new int. Isto

aloca espaço na área livre para um int, que pode ser acessado usando o ponteiro. A esta memória alocada é

atribuído o valor 7, na linha 10.

A linha 11 mostra o valor de variavelLocal, linha 12 mostra o valor apontado pelo ponteiro pLocal e a linha 13

mostra o valor apontado por pHeap. Você pode observar que, como esperado, os valores exibidos nas linhas 11

e 12 coincidem. Além disto, a linha 13 confirma que o valor atribuído na linha 10 está, de fato, acessível.

Na linha 14 a memória alocada na linha 9 é retornada à área livre pela chamada de delete. Isto libera a memória

e desassocia o ponteiro daquela memória. pHeap agora está liberado para apontar para outro endereço. Ele é

retribuído nas linhas 15 e 16, e na linha 17 exibe o resultado. A linha 18 restaura a memória para a área livre.

Apesar da linha 18 ser redundante (o fim do programa vai liberar aquela memória), é uma boa idéia liberar a

memória explicitamente. Se o programa mudar ou for extendido, ter cuidado neste passo é benéfico.

Outra olhada em vazamento de memória

Vazamentos de memória são uma das questões mais sérias e uma das principais reclamações sobre ponteiros.

Você viu uma maneira que vazamento de memória pode ocorrer. Outra maneira é inadvertidamente reatribuir

seu ponteiro antes de excluir a memória que ele aponta. Considere o código:

1. unsigned short int * pPonteiro = new unsigned short int; 2. *pPonteiro = 72; 3. pPonteiro = new unsigned short int; 4. *pPonteiro = 84;

Page 131: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

131 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

A linha 1 cria pPonteiro e o atribui ao endereço de um local na área livre. A linha 2 armazena o valor 72 neste

endereço. A linha 3 reatribui pPonteiro a outra área de memória. A linha 4 coloca 84 nesta área. A área original,

onde o valor 72 está guardado, agora está indisponível por que o ponteiro para esta área foi atribuído com um

novo valor. Não existe nenhuma maneira de acessar a área original da memória, nem de liberar esta área, até

que o programa encerre. Este código deveria ser escrito como:

1. unsigned short int * pPonteiro = new unsigned short int; 2. *pPonteiro = 72; 3. delete pPonteiro; 4. pPonteiro = new unsigned short int; 5. *pPonteiro = 84;

Agora a memória originalmente apontada por pPonteiro foi excluída, e por isto liberada, na linha 3.

Observação: para cada vez em seu programa que você chama new, deve haver uma chamada a delete. É

importante controlar a qual ponteiro pertence uma área da memória e assegurar-se que a memória é retornada

para a área livre quando você terminar com ela.

Criando objetos na área livre

Da mesma forma que você cria um ponteiro para um inteiro, você pode criar para qualquer tipo de dados,

incluindo classes. Se você declarou um objeto do tipo Gato, pode declarar um ponteiro para aquela classe e

instanciar um objeto Gato na área livre, assim como pode fazer na pilha. A sintaxe é a mesma que para os

inteiros:

Gato *pGato = new Gato;

Isto chama o construtor default - aquele que não recebe nenhum parâmetro. O construtor é chamado não

importando onde o objeto é criado (na pilha ou na área livre). Fique ciente, entretanto, que você não está

limitado ao construtor default ao usar new, qualquer construtor pode ser usado.

Excluindo objetos da área livre

Quando você chama delete num ponteiro para um objeto na área livre, o destrutor do objeto é chamado antes

que a memória seja liberada. Isto lhe dá a chance de limpar (geralmente desalocando memória do heap), da

mesma forma que faz para objetos destruídos na pilha. A listagem 8.6 ilustra a criação e exclusão de objetos da

área livre.

1. // Listagem 8.6 - Criando objetos na área livre 2. // usando new e delete 3. 4. #include <iostream> 5. 6. using namespace std; 7. 8. class Gato 9. { 10. public: 11. Gato(); 12. ~Gato(); 13. private: 14. int _idade; 15. }; 16. 17. Gato::Gato() 18. { 19. cout << "Construtor chamado. " << endl;

Page 132: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

132 Pesquisa e Desenvolvimento Tecnológico

20. _idade = 1; 21. } 22. 23. Gato::~Gato() 24. { 25. cout << "Destrutor chamado. " << endl; 26. } 27. 28. int main() 29. { 30. cout << "Gato Frisky... " << endl; 31. Gato Frisky; 32. cout << "Gato *pRags = new Gato..." << endl; 33. Gato * pRags = new Gato; 34. cout << "delete pRags... " << endl; 35. delete pRags; 36. cout << "Saindo, veja Frisky... " << endl; 37. return 0; 38. }

Saída

Gato Frisky...

Construtor chamado.

Gato *pRags = new Gato...

Construtor chamado.

delete pRags...

Destrutor chamado.

Saindo, veja Frisky...

Destrutor chamado.

Análise

As linhas 8 a 15 declaram a classe despojada Gato. A linha 11 declara o construtor de Gato e as linhas 17 a 21

contém sua definição. A linha 12 declara o destrutor de Gato e as linhas 23 a 26 contém sua definição. Como

você pode ver tanto o construtor quanto o destrutor simplesmente exibem uma mensagem para que você saiba

que foram chamados.

Na linha 31, Frisky é criado como uma variável local comum, assim ela é criada na pilha. Esta criação faz que o

construtor seja chamado e a memória que foi alocada para o objeto seja retornada. Quando a função termina

na linha 38, Frisky sai do escopo e o destrutor é chamado.

Ponteiros selvagens, pendentes ou perdidos

Novamente, problemas com ponteiros estão sendo criados. Isto porque os erros criados em seu programa com

ponteiros podem estar entre os mais difíceis de achar e entre os mais problemáticos. Uma fonte de erros que

são especialmente desagradáveis e difíceis de encontrar em C++ são os ponteiros perdidos. Um ponteiro

perdido (wild pointer) é criado quando você chama delete num ponteiro - liberando assim a memória que ele

aponta - e não o atribui para nulo. Se tentar usar aquele ponteiro novamente sem reatribuição, o resultado é

imprevisível e, se você tiver sorte, seu programa vai falhar.

É como se a Acme Pedidos por Correio se mudasse, mas você ainda utilizasse o botão programado em seu

telefone. É possível que nada terrível aconteça - um telefone vai tocar num armazém deserto. Por outro lado,

talvez o número do telefone tenha sido atribuído para uma fábrica de munições, e sua chamada detone um

explosivo e destrua toda a cidade!

Resumindo, tenha cuidado para não usar um ponteiro depois que tiver chamado delete sobre ele. O ponteiro

ainda aponta para uma antiga área na memória, mas o compilador tem liberdade de colocar outros dados lá;

Page 133: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

133 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

usar o ponteiro sem realocar uma nova memória para ele pode causar uma falha no programa. Pior, seu

programa pode meramente prosseguir e falhar muitos minutos depois. Isto é chamado uma bomba relógio e

não é engraçado. Para sua segurança, após excluir um ponteiro configure-o para nulo (0). Isto desarma o

ponteiro. A listagem 8.7 ilustra a criação de um ponteiro selvagem.

Cuidado: este programa intencionalmente cria um ponteiro selvagem. Não o execute - ele pode causar uma

falha, se você tiver sorte.

1. // Listagem 8.7 - Demonstra um ponteiro selvagem 2. 3. typedef unsigned short int USHORT; 4. #include <iostream> 5. 6. int main() 7. { 8. USHORT * pInt = new USHORT; 9. *pInt = 10; 10. std::cout << "*pInt: " << *pInt << std::endl; 11. delete pInt; 12. 13. long * pLong = new long; 14. *pLong = 90000; 15. std::cout << "*pLong: " << *pLong << std::endl; 16. 17. *pInt = 20; // opa, isto foi excluído! 18. 19. std::cout << "*pInt: " << *pInt << std::endl; 20. std::cout << "*pLong: " << *pLong << std::endl; 21. delete pLong; 22. return 0; 23. }

Saída

*pInt: 10 *pLong: 90000 *pInt: 20 *pLong: 65556

Não tente recriar esta saída. A sua pode ser diferente, se estiver com sorte, ou seu computador pode travar se

não estiver.

Análise

Repetindo: isto é um exemplo que você deve evitar executar, pois ele pode travar sua máquina. Na linha 8 pInt é

declarada como um ponteiro para USHORT e é apontada para a nova memória alocada. Na linha 9, o valor 10 é

colocado nesta memória, e é exibido na linha 10. Após isto, delete é chamado no ponteiro. Após a execução da

linha 11, pInt é um ponteiro selvagem, pendente ou perdido.

A linha 13 declara um novo ponteiro, pLong, que aponta para a memória alocada por new. Na linha 14 o valor

90000 é atribuído a pLong e na linha 15 este valor é exibido.

É na linha 17 que o problema começa. Nela, o valor 20 é atribuído à memória que pInt aponta, mas agora pInt

não aponta para nada válido, pois esta memória foi liberada pela chamada de delete na linha 11. Atribuir um

valor a esta memória certamente é um desastre.

Na linha 19 o valor em pInt é exibido. Com certeza, ele é 20. A linha 20 exibe o valor em pLong e ele

subitamente mudou para 65556. Surgem duas questões:

Como o valor de pLong mudou se ele nem foi tocado?

Page 134: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

134 Pesquisa e Desenvolvimento Tecnológico

Onde foi o 20 usado em pInt na linha 17?

Como pode imaginar, são questões relacionadas. Quando um valor foi colocado em pInt na linha 17, o

compilador alegremente colocou o valor 20 na locação de memória para onde pInt apontava anteriormente.

Entretanto, como esta memória foi liberada na linha 11, o compilador tem liberdade de reutilizá-la. Quando

pLong foi criado na linha 13, ele obteve a antiga locação de memória de pInt (em alguns computadores isto

pode não acontecer, dependendo de onde na memória estes valores foram armazenados). Quando o valor 20

foi atribuído para a locação que pInt apontava, ele foi escrito sobre o valor apontado por pLong. Isto é chamado

de pisar num ponteiro e normalmente é o resultado infeliz de usar um ponteiro selvagem.

Este erro é especialmente desagradável por que o valor que ele alterou não estava associado com o ponteiro

selvagem. A mudança no valor de pLong foi um efeito colateral do uso incorreto de pInt. Num programa grande,

seria muito difícil de rastrear.

Só para diversão

Aqui está em detalhes como 65556 foi parar no endereço de pLong na listagem 8.7:

1. pInt estava apontando para uma locação específica de memória e o valor 10 tinha sido atribuído.

2. delete foi chamada em pInt, que disse ao compilador que ele poderia colocar qualquer outra

coisa naquela locação. pLong foi atribuída para a mesma locação.

3. O valor 90000 foi atribuído a *pLong. O computador usado para o exemplo armazenou o valor

de quatro bytes 90000 (00 01 5F 90) na ordem byte reversa, ou seja, foi armazenado como 5F 90

00 01.

4. pInt tinha atribuído o valor 20 ou 00 14 em hexadecimal. Como pInt ainda aponta para o mesmo

endereço, os primeiros dois bytes de pLong foram sobrescritos, deixando 00 14 00 01.

5. O valor em pLong foi exibido, revertendo os bytes para sua ordem correta de 00 01 00 14, que

no DOS foi traduzido como 65556.

Usando ponteiros const

Você pode usar a palavra chave const para ponteiros antes do tipo, depois do tipo ou em ambos os locais. Por

exemplo, todas as declarações abaixo são válidas:

const int * pUm; int * const pDois; const int * const pTres;

Cada uma, entretanto, faz uma coisa diferente:

pUm é um ponteiro para um inteiro constante. O valor que ele aponta não pode ser alterado.

pDois é um ponteiro constante para um inteiro. O inteiro pode mudar, mas pDois não pode apontar

para nada mais.

pTres é um ponteiro constante para um inteiro constante. O valor que ele aponta não pode ser alterado

e pTres não pode apontar para nada mais.

O truque para entender isto é olhar para a direita da palavra chave const e descobrir o que está sendo declarado

constante. Se o tipo está a direita da palavra chave, seu valor é constante. Se a variável está a direita, é a variável

ponteiro que é constante. O código abaixo ajuda a ilustrar isto:

const int * p1; // o inteiro apontado é constante int * const p2; // p2 é constante, não pode apontar para nada diferente

Faça Não faça

Proteja objetos passados por referência usando Não use um ponteiro que foi excluído.

Page 135: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

135 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

const, se eles não devem ser alterados.

Configure ponteiros para nulo ao invés de deixá-los

pendentes ou não inicializados.

Não exclua ponteiros mais de uma vez.

Perguntas e respostas

Porque ponteiros são tão importantes?

- Ponteiros são importantes por várias razões. Isto inclui a capacidade de usar ponteiros para guardar o

endereço de objetos e passar argumentos por referência. Na lição 12, "Polimorfismo" você verá como ponteiros

são usados no polimorfismo de classes. Além disto, muitos sistemas operacionais e bibliotecas de classes criam

seus objetos e retornam ponteiros para eles.

Porque devo me preocupar em declarar qualquer coisa na área livre?

- Objetos na área livre persistem após o retorno de uma função. Além do mais, a habilidade de armazenar

objetos na área livre lhe permite decidir em tempo de execução quantos objetos você precisa, ao invés de ter

que declará-los antecipadamente.

Porque devo declarar um objeto const se ele limita o que posso fazer com ele?

- Como programador, você deve recrutar o compilador para ajudar na localização de erros. Um sério erro difícil

de achar é uma função que altera um objeto de uma maneira que não está clara para a função de chamada.

Declarando um objeto como const evita esta alteração.

Qual a diferença entre um ponteiro nulo e um ponteiro selvagem?

- Quando você exclui um ponteiro, diz ao compilador para liberar a memória, mas o ponteiro continua a existir,

a partir desse ponto ele é considerado um ponteiro selvagem. Quando você escreve meuPonteiro = nullptr você

muda sua condição de ponteiro selvagem para ponteiro nulo.

Normalmente, se você exclui um ponteiro e exclui novamente, seu programa está indefinido. Ou seja, qualquer

coisa pode acontecer - com sorte, o programa vai interromper. Se você exclui um ponteiro nulo, nada acontece

– é completamente seguro.

Usar um ponteiro selvagem ou nulo (por exemplo, meuPonteiro = 5;) é ilegal, e pode parar seu sistema. Se o

ponteiro é nulo, ele vai parar, outro benefício de nulo sobre selvagem. Erros previsíveis são preferíveis, por

serem mais fáceis de depurar.

Teste

1 - Qual operador é usado para determinar o endereço de uma variável?

2 - Qual operador é usado para encontrar o valor armazenado no endereço guardado num ponteiro?

3 - O que é um ponteiro?

4 - Qual a diferença entre o endereço armazenado num ponteiro e o valor naquele endereço?

5 - Qual a diferença entre o operador de indireção e o operador endereço-de?

6 - Qual a diferença entre const int * pUm e int * const pDois?

Page 136: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

136 Pesquisa e Desenvolvimento Tecnológico

Exercícios

1 - O que estas declarações fazem?

A. int * pUm; B. int vDois; C. int * pTres = &vDois;

2 - Se você tem uma variável unsigned short int chamada suaIdade, como você declara um ponteiro para

manusear suaIdade?

3 - Atribua o valor 50 para a variável suaIdade usando o ponteiro declarado no exercício 2.

4 - Escreva um pequeno programa que declara um inteiro e um ponteiro para um inteiro. Atribua o endereço do

inteiro para o ponteiro. Use o ponteiro para configurar o valor na variável inteira.

5 - Caça-Erros: o que está errado neste código?

#include <iostream> using namespace std; int main() { int *pInt; *pInt = 9; cout << “O valor em pInt: “ << *pInt; return 0; }

6 - Caça-Erros: o que está errado neste código?

#include <iostream> using namespace std; int main() { int algumaVariavel = 5; cout << "algumaVariavel: " << algumaVariavel << endl; int *pVar = & algumaVariavel; pVar = 9; cout << "algumaVariavel: " << *pVar << endl; return 0; }

Page 137: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

137 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Lição 9

Explorando Referências

Na lição anterior, você aprendeu sobre como usar ponteiros e a manipular objetos no heap e também aprendeu

como se referir a esses objetos indiretamente. Referências lhe dão quase todo o poder de ponteiros, mas com

uma sintaxe mais flexível, e esse é o tópico da nossa lição.

Nessa lição, você aprenderá:

O que são referências

Como referências são diferentes de ponteiros

Como criar e usar referências

Quais são as limitações no uso de referências

Como passar valores e objetos para dentro de funções e retirá-los, tudo com o uso de referências

O que é uma referência?

Uma referência é um apelido ou pseudônimo. Ao criar uma referência, você a inicializa com o nome de outro

objeto, o alvo. A partir deste momento, a referência age como um nome alternativo para o alvo e tudo que for

feito com a referência é feito realmente com o alvo.

Você cria uma referência escrevendo o tipo do objeto alvo, seguido pelo operador de referência &, mais o nome

da referência, o sinal de atribuição e o nome do objeto alvo.

Referências podem ter qualquer nome de variável válido, mas alguns programadores preferem prefixar o nome

com a letra r. Assim, se você tem uma variável inteira chamada algumInt, você cria uma referência para ela com

o código:

int &rAlgumInt = algumInt;

Esta instrução é lida como "rAlgumInt é uma referência para um inteiro. A referência é inicializada para se referir

a algumInt". Referências diferem de outras variáveis por precisarem ser inicializadas na declaração. Se você

tentar criar uma referência sem uma atribuição, receberá um erro do compilador. A listagem 9.1 mostra como

referências são criadas e usadas.

Observações: o operador de referência & é o mesmo usado pelo operador endereço-de. Eles não são o mesmo

operador, apesar de estarem relacionados. O espaço antes do operador é obrigatório. Já o espaço entre o

operador e o nome da referência é opcional.

int &rAlgumaRef = algumInt; // Ok int & rAlgumaRef = algumInt; // Ok

No exemplo acima, os dois casos de declaração de referências estão corretos.

1. //Listagem 9.1 - Demonstra o uso de referências 2. 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. int intUm;

Page 138: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

138 Pesquisa e Desenvolvimento Tecnológico

9. int &rAlgumaRef = intUm; 10. 11. intUm = 5; 12. cout << "intUm: " << intUm << endl; 13. cout << "rAlgumaRef: " << rAlgumaRef << endl; 14. 15. rAlgumaRef = 7; 16. cout << " intUm: " << intUm << endl; 17. cout << " rAlgumaRef: " << rAlgumaRef << endl; 18. 19. return 0; 20. }

Saída

intUm: 5

rAlgumaRef: 5

intUm: 7

rAlgumaRef: 7

Análise

Na linha 8, uma variável local inteira, intUm, é declarada. Na linha 9, uma referência para um inteiro (int),

rAlgumaRef, é declarada e inicializada para se referir a intUm. Como já vimos, se você declara uma referência,

mas não a inicializa, recebe um erro em tempo de compilação. Referências devem ser inicializadas.

Na linha 11 intUm é atribuída ao valor 5. Nas linhas 12 e 13 o valor em intUm e rAlgumaRef é exibido e, é claro,

são os mesmos.

Na linha 15 é atribuído 7 à rAlgumaRef. Como ela é uma referência, um apelido para intUm, 7 na verdade é

atribuído a intUm, como mostrado nas linhas 16 e 17.

Usando o operador endereço-de (&) em referências

Você já viu que o símbolo & é usado tanto para o endereço de uma variável como para declarar uma referência.

Mas e se você pegar o endereço de uma referência? Se você pedir o endereço a uma referência, ela devolve o

endereço do alvo. Esta é a natureza de referências, elas são apelidos para o alvo. A listagem 9.2 demonstra a

captura do endereço de uma referência chamada rAlgumaRef.

1. //Listagem 9.2 - Demonstra o uso de referências 2. 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. int intUm; 9. int &rAlgumaRef = intUm; 10. 11. intUm = 5; 12. cout << "intUm: " << intUm << endl; 13. cout << "rAlgumaRef: " << rAlgumaRef << endl; 14. 15. cout << "&intUm: " << &intUm << endl; 16. cout << "&rAlgumaRef: " << &rAlgumaRef << endl; 17. 18. return 0; 19. }

Saída

Page 139: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

139 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

intUm: 5

rAlgumaRef: 5

&intUm: 002EFD68

&rAlgumaRef: 002EFD68

Análise

rAlgumaRef novamente é inicializada como uma referência para intUm. Desta vez, o endereço das duas variáveis

é exibido nas linhas 15 e 16, e são idênticos.

C++ não oferece uma maneira de acessar o endereço da própria referência por que não é significativo como

seria se você estivesse usando um ponteiro ou outra variável. Referências são inicializadas na criação e sempre

agem como sinônimos para seus alvos, mesmo quando o operador endereço-de é aplicado.

Por exemplo, se você tem uma classe chamada Presidente, pode declarar uma instância dela como:

Presidente GetulioVargas;

Você pode então declarar uma referência à Presidente e inicializá-la com este objeto:

Presidente &PaiDosPobres = GetulioVargas;

Apenas um Presidente existe. Ambos os identificadores se referem ao mesmo objeto da mesma classe. Qualquer

ação tomada em PaiDosPobres é tomada também em GetulioVargas.

Note que é preciso distinguir entre o símbolo & na linha 9 da listagem 9.2, que declara uma referência para um

inteiro chamado rAlgumaRef, e os símbolos & nas linhas 15 e 16, que retornam o endereço da variável intUm e

da referência rAlgumaRef. O compilador sabe distinguir entre os dois pelo contexto onde são usados.

Tentativa de reatribuir uma referência (não faça!)

Variáveis referência não podem ser reatribuídas. Mesmo programadores C++ experientes podem ser

confundidos com o que acontece quando você tenta reatribuir uma referência. Variáveis referência são sempre

apelidos para seus alvos. O que parece ser uma reatribuição se torna a atribuição para um novo valor para o

alvo. A listagem 9.3 ilustra este fato.

1. //Listagem 9.3 - //Reatribuindo a referencia 2. 3. #include <iostream> 4. 5. int main() 6. { 7. using namespace std; 8. int intUm; 9. int &rAlgumaRef = intUm; 10. 11. intUm = 5; 12. cout << "intUm: " << intUm << endl; 13. cout << "rAlgumaRef: " << rAlgumaRef << endl; 14. cout << "&intUm: " << &intUm << endl; 15. cout << "&rAlgumaRef: " << &rAlgumaRef << endl; 16. 17. int intDois = 8; 18. rAlgumaRef = intDois; // não é o que você pensa! 19. cout << "\nintUm: " << intUm << endl; 20. cout << "intDois: " << intDois << endl; 21. cout << "rrAlgumaRef: " << rAlgumaRef << endl; 22. cout << "&intUm: " << &intUm << endl; 23. cout << "&intDois: " << &intDois << endl; 24. cout << "&rAlgumaRef: " << &rAlgumaRef << endl;

Page 140: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

140 Pesquisa e Desenvolvimento Tecnológico

25. return 0; 26. }

Saída

intUm: 5

rAlgumaRef: 5

&intUm: 003DFBF4

&rAlgumaRef: 003DFBF4

intUm: 8

intDois: 8

rrAlgumaRef: 8

&intUm: 003DFBF4

&intDois: 003DFBDC

&rAlgumaRef: 003DFBF4

Análise

Nas linhas 8 e 9 uma variável inteira e uma referência são declaradas. Ao inteiro é atribuído o valor 5 na linha 11,

e os valores e seus endereços são exibidos nas linhas 12 a 15.

Na linha 17, uma nova variável, intDois, é criada e inicializada com o valor 8. Na linha 18, o programador tenta

reatribuir rAlgumaRef para ser um apelido de intDois, mas não é isto que acontece. O que realmente acontece é

que rAlgumaRef continua a agir como um apelido para intUm, então a atribuição é equivalente a:

intUm = intDois;

Com certeza, quando os valores de intUm e rAlgumaRef são exibidos (linhas 19 a 21), eles são os mesmos de

intDois. De fato, quando os endereços são exibidos nas linhas 22 a 24, você vê que rAlgumaRef continua a se

referir a intUm e não a intDois.

Faça Não faça

Use referências para criar um apelido para

um objeto.

Inicialize todas as referências.

Não tente reatribuir uma referência.

Não confunda o operador endereço-de com o

operador de referência.

Ponteiros nulos e referências nulas

Quando ponteiros não são inicializados ou são excluídos, eles devem ser atribuídos a nulo (0). Isto não é

verdade para referências por que elas devem ser inicializadas para aquilo que elas referenciam quando são

declaradas.

Entretanto, como C++ precisa ser utilizável por drivers de dispositivos, sistemas embarcados e sistemas em

tempo real que podem chegar diretamente no hardware, a habilidade de referenciar endereços específicos é

valiosa e necessária. Por isto, muitos compiladores suportam um nulo ou valor numérico para atribuição de uma

referência, sem muita reclamação, falhando apenas se você tentar usar o objeto de alguma maneira que a

referência seria inválida.

Tirar vantagem disto na programação normal, entretanto, não é uma boa idéia. Quando você move seu

programa para outra máquina ou compilador, erros misteriosos podem surgir se você tem referências nulas.

Page 141: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

141 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Passando argumentos de funções por referência

Na lição 6, "Organizando código com funções", você aprendeu que funções têm duas limitações: argumentos

são passados por valor e a instrução return retorna apenas um valor.

Passando valores por referência pode superar estas limitações. Em C++, isto é conseguido de duas maneiras:

usando ponteiros e usando referências. Observe a diferença: você passa por referência usando um ponteiro, ou

você passa a referência usando uma referência.

A sintaxe de usar um ponteiro é diferente de usar uma referência, mas o efeito é o mesmo. Ao invés de uma

cópia ser criada no escopo da função, o valor original real é (efetivamente) disponibilizado para a função.

Passando um objeto por referência permite a função alterar este objeto. Na lição 6 você aprendeu que funções

passam seus parâmetros na pilha. Quando um valor é passado por referência (usando ponteiros ou referências),

o endereço do objeto original é colocado na pilha e não o objeto inteiro. De fato, em alguns compiladores, o

endereço realmente é guardado num registrador e nada é colocado na pilha. Em ambos os casos, como o

endereço está sendo passado, o compilador agora sabe como obter o objeto original e as alterações são feitas

lá, e não em cópias locais.

Lembre-se da listagem 6.4 da lição 6 demonstrou que uma chamada à função Trocar() não afeta os valores na

função de chamada. A listagem 6.4 é reproduzida aqui como listagem 9.4:

29. // Listagem 9.4 - Demonstra a passagem por valor 30. #include <iostream> 31. 32. using namespace std; 33. void Trocar(int x, int y); 34. 35. int main() 36. { 37. int x = 5, y = 10; 38. 39. cout << "main. Antes da troca, x: " << x << " y: " << y << endl; 40. Trocar(x, y); 41. cout << "main. Depois da troca, x: " << x << " y: " << y << endl; 42. return 0; 43. } 44. 45. void Trocar(int x, int y) 46. { 47. int temp; 48. 49. cout << "Trocar. Antes da troca, x: " << x << " y: " << y << endl; 50. 51. temp = x; 52. x = y; 53. y = temp; 54. 55. cout << "Trocar. Depois da troca, x: " << x << " y: " << y << endl; 56. }

Saída

main. Antes da troca, x: 5 y: 10

Trocar. Antes da troca, x: 5 y: 10

Trocar. Depois da troca, x: 10 y: 5

main. Depois da troca, x: 5 y: 10

Análise

Page 142: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

142 Pesquisa e Desenvolvimento Tecnológico

Este programa inicializa duas variáveis em main() e passa seus valores para Trocar(), que parece trocá-los.

Quando eles são examinados novamente em main(), estão inalterados!

O problema aqui é que x e y são passados para Trocar() por valor, ou seja, uma cópia é feita na função. Estas

cópias locais são trocadas e descartadas quando a função retorna e sua armazenagem local é desalocada. É

preferível passar os valores por referência, que altera os valores originais das variáveis, não suas cópias locais.

Duas maneiras de resolver este problema são possíveis em C++: você pode fazer os parâmetros de Trocar()

como ponteiros dos valores originais ou passá-los como referência para os valores originais.

Fazendo Trocar() trabalhar com ponteiros

Quando você passa um ponteiro, está passando o endereço do objeto, assim a função pode manipular os

valores neste endereço. Para fazer Trocar() alterar os valores reais de x e y, a função deve ser alterada para

aceitar dois ponteiros inteiros. Desreferenciando os ponteiros, os valores serão realmente acessados e alterados.

A listagem 9.5 demonstra esta idéia.

1. //Listagem 9.5 - Demonstra passagem por referencia 2. #include <iostream> 3. 4. using namespace std; 5. void Trocar(int *x, int *y); 6. 7. int main() 8. { 9. int x = 5, y = 10; 10. 11. cout << "Main. Antes da troca, x: " << x << " y: " << y << endl; 12. Trocar(&x,&y); 13. cout << "Main. Depois da troca, x: " << x << " y: " << y << endl; 14. return 0; 15. } 16. 17. void Trocar (int *px, int *py) 18. { 19. int temp; 20. 21. cout << "Trocar. Antes da troca, *px: " << *px << 22. " *py: " << *py << endl; 23. 24. temp = *px; 25. *px = *py; 26. *py = temp; 27. 28. cout << "Trocar. Depois da troca, *px: " << *px << 29. " *py: " << *py << endl; 30. 31. }

Saída

Main. Antes da troca, x: 5 y: 10

Trocar. Antes da troca, *px: 5 *py: 10

Trocar. Depois da troca, *px: 10 *py: 5

Main. Depois da troca, x: 10 y: 5

Análise

Page 143: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

143 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Sucesso! Na linha 5 o protótipo de Trocar() é alterado para indicar que seus dois parâmetros são ponteiros para

inteiros e não variáveis inteiras. Quando Trocar() é chamada na linha 12, os endereços de x e y são passados

como argumentos. Você pode ver isto porque o operador endereço-de (&) está sendo usado.

Na linha 19, uma variável local temp é declarada na função Trocar(). Ela não precisa ser um ponteiro, pois vai

armazenar o valor de *px (ou seja, o valor de x na função de chamada) apenas durante a vida da função. Depois

que a função retornar, temp não é mais necessária.

Na linha 24 temp é atribuída ao valor em px. Na linha 25 o valor em px é atribuído ao valor em py. Na linha 26 o

valor armazenado em temp (que é o valor original em px) é colocado em py. O efeito final disto é que os valores

na função de chamada, cujos endereços foram passados para Trocar(), são alterados.

Implementando Trocar() com referências

O programa anterior funciona, mas a sintaxe de Trocar() é incômoda de duas maneiras: primeiro, a necessidade

repetitiva de desreferenciar os ponteiros em Trocar() a torna propensa a erros. Por exemplo, se você esquece-se

de desreferenciar o ponteiro, o compilador ainda permitirá atribuir um inteiro ao ponteiro e um usuário

subsequente vai experimentar um erro. Esta forma também é difícil de ler. Finalmente, a necessidade de passar

endereços de variáveis torna o trabalho interno da função muito aparente.

Um objetivo de uma linguagem orientada a objetos como C++ é evitar que o usuário de uma função se

preocupe como ela funciona. Passando ponteiros põe o ônus na função de chamada e não onde ele pertence -

na função sendo chamada. A listagem 9.6 reescreve a função Trocar() usando referências.

1. //Listagem 9.6 - Demonstra passagem por referencia 2. // usando referências! 3. #include <iostream> 4. 5. using namespace std; 6. void Trocar(int &x, int &y); 7. 8. int main() 9. { 10. int x = 5, y = 10; 11. 12. cout << "Main. Antes da troca, x: " << x << " y: " 13. << y << endl; 14. 15. Trocar(x,y); 16. 17. cout << "Main. Depois da troca, x: " << x << " y: " 18. << y << endl; 19. 20. return 0; 21. } 22. 23. void Trocar (int &rx, int &ry) 24. { 25. int temp; 26. 27. cout << "Trocar. Antes da troca, rx: " << rx << 28. " ry: " << ry << endl; 29. 30. temp = rx; 31. rx = ry; 32. ry = temp; 33. 34. 35. cout << "Trocar. Depois da troca, rx: " << rx << 36. " ry: " << ry << endl;

Page 144: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

144 Pesquisa e Desenvolvimento Tecnológico

37. 38. }

Saída

Main. Antes da troca, x: 5 y: 10

Trocar. Antes da troca, rx: 5 ry: 10

Trocar. Depois da troca, rx: 10 ry: 5

Main. Depois da troca, x: 10 y: 5

Análise

Como no exemplo com ponteiros, duas variáveis são declaradas na linha 10 e seus valores são exibidos na linha

12. Na linha 15 a função Trocar() é chamada, mas observe que são passados x e y e não seus endereços. A

função de chamada simplesmente passa as variáveis.

Quando Trocar() é chamada, a execução do programa salta para alinha 23, onde as variáveis são identificadas

como referências. Os valores das variáveis são exibidos na linha 27, mas observe que nenhum operador especial

é necessário. As variáveis são apelidos para as originais e podem ser usadas como se fossem elas.

Nas linhas 30 a 32 os valores são trocados e exibidos na linha 35. A execução retorna para a função de chamada

e na linha 17 os valores são exibidos em main(). Como os parâmetros são declarados como referências, as

variáveis de main() são passadas por referência e seus valores alterados também são vistos em main(). Como

você pode ver nesta listagem, referências fornecem a conveniência e facilidade de uso das variáveis, mas com o

poder e a capacidade de passar por referência dos ponteiros!

Retornando múltiplos valores

Como discutido, funções podem retornar apenas um valor. E se você precisar de dois valores no retorno da

função? Uma maneira é passar dois objetos por referência, e a função pode preencher estes objetos com o valor

correto. Como a passagem por referência permite uma função mudar os valores originais, isto efetivamente

retorna duas informações. Esta abordagem contorna o problema. O retorno, por sua vez, pode ser reservado

para reportar erros.

Novamente, isto pode ser feito com referências ou ponteiros. A listagem 9.7 demonstra uma função que retorna

três valores: dois como parâmetros ponteiros e um como o valor de retorno da função.

1. //Listagem 9.7 - Retornando múltiplos valores de uma função 2. 3. #include <iostream> 4. 5. using namespace std; 6. short Fator(int n, int* pQuadrado, int* pCubico); 7. 8. int main() 9. { 10. int numero, quadrado, cubico; 11. short erro; 12. 13. cout << "Entre um numero (0 - 20): "; 14. cin >> numero; 15. 16. erro = Fator(numero, &quadrado, &cubico); 17. 18. if (!erro) 19. { 20. cout << "numero: " << numero << endl; 21. cout << "quadrado: " << quadrado << endl;

Page 145: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

145 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

22. cout << "cubico: " << cubico << endl; 23. } 24. else 25. cout << "Erro encontrado!" << endl; 26. return 0; 27. } 28. 29. short Fator(int n, int *pQuadrado, int *pCubico) 30. { 31. short valor = 0; 32. if (n > 20) 33. valor = 1; 34. else 35. { 36. *pQuadrado = n*n; 37. *pCubico = n*n*n; 38. valor = 0; 39. } 40. return valor; 41. }

Saída

Entre um numero (0 - 20): 3

numero: 3

quadrado: 9

cubico: 27

Análise

Na linha 10, numero, quadrado e cubico são definidos como inteiros curtos e a numero é atribuído o valor que o

usuário informou na linha 14. Na linha 16, este número e os endereços de quadrado e cubico são passados para

a função Fator().

Na linha 32, Fator() examina o primeiro parâmetro, que é passado por valor. Se ele é maior

que 20 (o máximo que esta função pode trabalhar), ela configura seu valor de retorno, valor, para um valor de

erro. Observe que o valor de retorno da função é reservado para este valor de erro ou zero, indicando como foi

a execução, e este valor é retornado na linha 40.

Os valores realmente necessários, o quadrado e cubo do número, não são retornados pelo mecanismo de

retorno, mas sim alterando os ponteiros que foram passados para a função. Nas linhas 36 e 37 os ponteiros são

atribuídos com seus valores de retorno, que são atribuídos aos valores originais usando indireção. Você sabe

disto pelo uso do operador de desreferência (*) com os nomes de ponteiros. Na linha 38, valor é atribuído para

um valor de sucesso e na linha 40 ele é retornado.

Dica: como passar por referência ou por ponteiro permite acesso total a atributos e métodos de objetos, você

deve passar o mínimo necessário para a função fazer seu trabalho. Isto ajuda a garantir que a função é segura

para usar e mais compreensível.

Retornando valores por referência

Apesar da listagem 9.7 funcionar, ela pode se tornar mais fácil de ler e manter usando referências ao invés de

ponteiros. A listagem 9.8 mostra o mesmo programa reescrito para usar referências. A listagem também inclui

uma segunda melhoria. Um enum foi acrescentado para tornar o valor de retorno mais fácil de entender. Ao

invés de retornar 0 ou 1, usando um enum o programa pode retornar SUCESSO ou FALHA.

Page 146: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

146 Pesquisa e Desenvolvimento Tecnológico

1. // Listagem 9.8 2. // Retornando múltiplos valores de uma função 3. // usando referências 4. #include <iostream> 5. 6. using namespace std; 7. 8. enum CODIGO_ERRO { SUCESSO, ERRO }; 9. 10. CODIGO_ERRO Fator(int, int&, int&); 11. 12. int main() 13. { 14. int numero, quadrado, cubico; 15. CODIGO_ERRO resultado; 16. 17. cout << "Entre um numero (0 - 20): "; 18. cin >> numero; 19. 20. resultado = Fator(numero, quadrado, cubico); 21. 22. if (resultado == SUCESSO) 23. { 24. cout << "numero: " << numero << endl; 25. cout << "quadrado: " << quadrado << endl; 26. cout << "cubico: " << cubico << endl; 27. } 28. else 29. cout << "Erro encontrado!" << endl; 30. return 0; 31. } 32. 33. CODIGO_ERRO Fator(int n, int &rQuadrado, int &rCubico) 34. { 35. if (n > 20) 36. return ERRO; // simples código de erro 37. else 38. { 39. rQuadrado = n*n; 40. rCubico = n*n*n; 41. return SUCESSO; 42. } 43. }

Saída

Entre um numero (0 - 20): 3

numero: 3

quadrado: 9

cubico: 27

Análise

A listagem 9.8 é idêntica a 9.7, com duas exceções. A enumeração CODIGO_ERRO torna o relatório de erros um

pouco mais explícito que nas linhas 36 e 41, bem como o manuseio do erro na linha 22.

A grande mudança, entretanto, é que Fator() agora é declarado para pegar referências de quadrado e cubico no

lugar de ponteiros. Isto torna o manuseio destes parâmetros mais simples e fácil de entender.

Page 147: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

147 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Passando por referência para eficiência

Sempre que você passa um objeto para uma função por valor, uma cópia do objeto é feita. Cada vez que

retorna um objeto de uma função, outra cópia é feita. Estas fases de cópias resultam em zero ou pouca perda de

desempenho para pequenos objetos como inteiros.

Entretanto, com objetos grandes criados pelo usuário, como estruturas ou classes, o custo da cópia pode ser

bem alto. O tamanho de um objeto criado pelo usuário na pilha é a soma de cada variável membro. Estas, por

sua vez, podem ser objetos criados pelo usuário, e a passagem de uma estrutura pesada como esta pela cópia

na pilha pode ser bem caro em consumo de memória e desempenho.

Outro custo também ocorre. Com as classes que você cria, cada uma destas cópias temporárias é criada quando

o compilador chama um construtor especial: o construtor de cópia. Mais tarde você vai aprender como

construtores de cópias funcionam e como fazer os seus próprios, mas por hora é suficiente saber que o

construtor de cópia é chamado cada vez que uma cópia temporária do objeto é colocada na pilha.

Quando o objeto temporário é destruído, o que acontece quando a função retorna, o destrutor do objeto é

chamado. Se um objeto é retornado pela função por valor, uma cópia deste objeto também deve ser feita e

destruída.

Com grandes objetos, estas chamadas de construtor e destrutor podem ser caras em velocidade e uso de

memória. Para ilustrar a idéia, a listagem 9.9 cria um objeto despojado, definido pelo usuário: Gato. Um objeto

real será maior e mais caro, mas este é suficiente para mostrar quão frequentemente o construtor de cópia e

destrutor são chamados quando um objeto é passado por valor. Por você ainda não ter estudado o conceito de

classes, não preste atenção na sintaxe. Ao invés disto, entenda como passando por referência reduz as

chamadas de função focando o resultado.

1. //Listagem 9.9 - Passando ponteiros para objetos 2. 3. #include <iostream> 4. 5. using namespace std; 6. class Gato 7. { 8. public: 9. Gato (); // construtor 10. Gato(Gato&); // construtor de cópia 11. ~Gato(); // destrutor 12. }; 13. 14. Gato::Gato() 15. { 16. cout << "Gato Construtor..." << endl; 17. } 18. 19. Gato::Gato(Gato&) 20. { 21. cout << "Gato Construtor de copia..." << endl; 22. } 23. 24. Gato::~Gato() 25. { 26. cout << "Gato Destrutor..." << endl; 27. } 28. 29. Gato FuncaoUm (Gato umGato); 30. Gato* FuncaoDois (Gato *umGato); 31. 32. int main()

Page 148: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

148 Pesquisa e Desenvolvimento Tecnológico

33. { 34. cout << "Criando um gato..." << endl; 35. Gato Frisky; 36. cout << "Chamando FuncaoUm..." << endl; 37. FuncaoUm(Frisky); 38. cout << "Chamando FuncaoDois..." << endl; 39. FuncaoDois(&Frisky); 40. return 0; 41. } 42. 43. // FuncaoUm, passa por valor 44. Gato FuncaoUm(Gato umGato) 45. { 46. cout << "Funcao um. Retornando... " << endl; 47. return umGato; 48. } 49. 50. // FuncaoDois, passa por referência 51. Gato* FuncaoDois (Gato *umGato) 52. { 53. cout << "Funcao dois. Retornando... " << endl; 54. return umGato; 55. }

Saída

Criando um gato...

Gato Construtor...

Chamando FuncaoUm...

Gato Construtor de copia...

Funcao um. Retornando...

Gato Construtor de copia...

Gato Destrutor...

Gato Destrutor...

Chamando FuncaoDois...

Funcao dois. Retornando...

Gato Destrutor...

Análise

A listagem 9.9 cria o objeto Gato e chama duas funções. A primeira recebe Gato por valor e retorna por valor. A

segunda recebe um ponteiro para o objeto e retorna um ponteiro para o objeto.

A classe bem simples Gato é declarada nas linhas 6 a 12. O construtor, construtor de cópia e destrutor, exibem

uma mensagem informativa para você ver quando são chamados.

Na linha 34, main() exibe uma mensagem mostrada na primeira linha da saída. Na linha 35 um objeto Gato é

instanciado. Isto causa uma chamada ao construtor, e a saída do construtor é vista na segunda linha da saída.

Na linha 36 main() relata que está chamando FuncaoUm, que cria a terceira linha na saída. Como FuncaoUm() é

chamada passando o objeto Gato por valor, uma cópia do objeto Gato é feita na pilha como um objeto local

para a função chamada. Isto causa a chamada do construtor de cópia, que cria a quarta linha da saída.

A execução do programa salta para a linha 46 na função chamada, que exibe uma mensagem informativa, a

quinta linha da saída. A função então retorna, retornando o objeto Gato por valor. Isto cria outra cópia do

objeto, pela chamada do construtor de cópia, e produz a sexta linha da saída.

O valor retornado de FuncaoUm() não é atribuído a nenhum objeto, assim o objeto temporário criado para

retorno é descartado, chamando o destrutor, que produz a sétima linha de saída. Como FuncaoUm() terminou,

sua cópia local sai do escopo e é destruída, chamando o destrutor e produzindo a oitava linha de saída.

Page 149: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

149 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

A execução do programa retorna a main() e FuncaoDois() é chamada, mas o parâmetro é passado por referência.

Nenhuma cópia é produzida, então nada é exibido. FuncaoDois() exibe a mensagem que aparece na décima

linha de saída e retorna o objeto Gato novamente por referência, que também não faz nenhuma chamada ao

construtor ou destrutor.

Finalmente, o programa termina e Frisky sai do escopo, causando uma última chamada ao destrutor e

produzindo a linha final de saída. O efeito final disto é que a chamada para FuncaoUm(), que recebe Frisky por

valor, produz duas chamadas ao construtor de cópia e duas ao destrutor, enquanto a chamada de FuncaoDois()

não produz nenhuma.

Passando um ponteiro const

Apesar da passagem de um ponteiro para FuncaoDois() ser mais eficiente, é mais perigoso. FuncaoDois() não

pretende ter permissão para alterar o objeto Gato passada, apesar de ser lhe dada seu endereço. Isto expõe

seriamente o objeto original a mudanças e desmonta a proteção oferecida pela passagem por valor.

Passar por valor é como dar a um museu uma fotografia de sua obra-prima, ao invés da coisa real. Se vândalos a

estragarem, não haverá nenhum dano ao original. Passar por referência é como enviar o endereço de sua casa

para o museu e chamar os convidados para entrar e olhar a coisa real.

A solução é passar um ponteiro para uma constante Gato. Fazendo isto previne a chamada de qualquer método

não constante sobre Gato, protegendo-o de alterações.

Passar uma referência constante permite seus convidados verem a pintura original, mas não modificá-la por

qualquer forma. A listagem 9.10 demonstra a idéia.

1. //Listagem 9.10 - Passando ponteiros para objetos 2. 3. #include <iostream> 4. 5. using namespace std; 6. class Gato 7. { 8. public: 9. Gato(); 10. Gato(Gato&); 11. ~Gato(); 12. 13. int GetIdade() const { return suaIdade; } 14. void SetIdade(int idade) { suaIdade = idade; } 15. 16. private: 17. int suaIdade; 18. }; 19. 20. Gato::Gato() 21. { 22. cout << "Gato Construtor..." << endl; 23. suaIdade = 1; 24. } 25. 26. Gato::Gato(Gato&) 27. { 28. cout << "Gato Construtor de copia..." << endl; 29. } 30. 31. Gato::~Gato() 32. { 33. cout << "Gato Destrutor..." << endl;

Page 150: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

150 Pesquisa e Desenvolvimento Tecnológico

34. } 35. 36. const Gato * const FuncaoDois 37. (const Gato * const gato); 38. 39. int main() 40. { 41. cout << "Criando um gato..." << endl; 42. Gato Frisky; 43. cout << "Frisky tem " ; 44. cout << Frisky.GetIdade(); 45. cout << " anos" << endl; 46. int age = 5; 47. Frisky.SetIdade(age); 48. cout << "Frisky tem " ; 49. cout << Frisky.GetIdade(); 50. cout << " anos" << endl; 51. cout << "Chamando FuncaoDois..." << endl; 52. FuncaoDois(&Frisky); 53. cout << "Frisky tem " ; 54. cout << Frisky.GetIdade(); 55. cout << " anos" << endl; 56. return 0; 57. } 58. 59. // FuncaoDois, passa um ponteiro constante 60. const Gato * const FuncaoDois 61. (const Gato * const gato) 62. { 63. cout << "Funcao dois. Retornando..." << endl; 64. cout << "Frisky agora tem " << gato->GetIdade(); 65. cout << " anos " << endl; 66. // gato->SetIdade(8); constante! 67. return gato; 68. }

Saída

Criando um gato...

Gato Construtor...

Frisky tem 1 anos

Frisky tem 5 anos

Chamando FuncaoDois...

Funcao dois. Retornando...

Frisky agora tem 5 anos

Frisky tem 5 anos

Gato Destrutor...

Análise

Gato acrescentou duas funções de acesso, GetIdade() na linha 13, que é uma função constante, e SetIdade() na

linha 14, que não é constante. Também acrescentou a variável membro suaIdade, na linha 17.

O construtor, construtor de cópia e destrutor ainda estão definidos para exibir suas mensagens. O construtor de

cópia nunca é chamado, entretanto, por que o objeto é passado por referência, assim nenhuma cópia é feita. Na

linha 42 um objeto é criado e sua idade padrão é exibida, começando na linha 43.

Na linha 47 suaIdade é configurada usando a função SetIdade e o resultado é exibido na linha 48. FuncaoUm não

é usada neste programa, mas FuncaoDois() é chamada e foi levemente modificada: o parâmetro e valor de

retorno agora são declarados na linha 36, para pegar e retornar um ponteiro constante para um objeto

constante.

Page 151: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

151 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Como o parâmetro e valor de retorno ainda são passados por referência, nenhuma cópia é feita e o construtor

de cópia nunca é chamado. O objeto sendo apontado na FuncaoDois(), entretanto, agora é constante e não

pode chamar o método não constante SetIdade(). Se a chamada a SetIdade() na linha 66 não for comentada, o

programa não compilará.

Observe que o objeto criado em main() não é constante, e Frisky pode chamar SetIdade(). O endereço deste

objeto não constante foi passado para FuncaoDois(), mas como sua declaração define o ponteiro como

constante, de um objeto constante, ele pode ser tratado como constante.

Referências como uma alternativa

A listagem 9.10 resolve o problema de fazer cópias extras, e economiza as chamadas ao construtor de cópia e

destrutor. Usa ponteiros constantes e desta forma resolve o problema de alteração de objetos na função. Mas

de certa forma ainda é incômodo, pois os objetos passados são ponteiros.

Como você sabe que os objetos nunca serão nulos, é mais fácil trabalhar com a função se forem passadas

referências, no lugar de ponteiros. A listagem 9.11 ilustra esta abordagem.

1. //Listagem 9.11 - Passando referências de objetos 2. 3. #include <iostream> 4. 5. using namespace std; 6. class Gato 7. { 8. public: 9. Gato(); 10. Gato(Gato&); 11. ~Gato(); 12. 13. int GetIdade() const { return suaIdade; } 14. void SetIdade(int idade) { suaIdade = idade; } 15. 16. private: 17. int suaIdade; 18. }; 19. 20. Gato::Gato() 21. { 22. cout << "Gato Construtor..." << endl; 23. suaIdade = 1; 24. } 25. 26. Gato::Gato(Gato&) 27. { 28. cout << "Gato Construtor de copia..." << endl; 29. } 30. 31. Gato::~Gato() 32. { 33. cout << "Gato Destrutor..." << endl; 34. } 35. 36. const Gato & FuncaoDois (const Gato & gato); 37. 38. int main() 39. { 40. cout << "Criando um gato..." << endl; 41. Gato Frisky; 42. cout << "Frisky tem " << Frisky.GetIdade() << " anos" << endl; 43. int age = 5;

Page 152: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

152 Pesquisa e Desenvolvimento Tecnológico

44. Frisky.SetIdade(age); 45. cout << "Frisky tem " << Frisky.GetIdade() << " anos" << endl; 46. cout << "Chamando FuncaoDois..." << endl; 47. FuncaoDois(Frisky); 48. cout << "Frisky tem " << Frisky.GetIdade() << " anos" << endl; 49. return 0; 50. } 51. 52. // FuncaoDois, passa uma referência para um objeto const 53. const Gato & FuncaoDois (const Gato & gato) 54. { 55. cout << "Funcao Dois. Retornando..." << endl; 56. cout << "Frisky agora tem " << gato.GetIdade(); 57. cout << " anos " << endl; 58. // gato.SetIdade(8); constante! 59. return gato; 60. }

Saída

Criando um gato...

Gato Construtor...

Frisky tem 1 anos

Frisky tem 5 anos

Chamando FuncaoDois...

Funcao Dois. Retornando...

Frisky agora tem 5 anos

Frisky tem 5 anos

Gato Destrutor...

Análise

A saída é idêntica à produzida pela listagem 9.10. A única mudança significativa é que FuncaoDois() agora

recebe e retorna referências para objetos constantes. Novamente, trabalhar com referências é mais simples que

trabalhar com ponteiros, e a mesma economia e eficiência é alcançado, bem como a segurança provida pelo uso

de const.

Referências const

Programadores C++ normalmente não diferem entre "referência constante para um

objeto Gato" e "referência para um objeto Gato constante". Referências por si só nunca

podem ser reatribuídas para referenciar outro objeto, assim são sempre constantes. Se a

palavra chave const é aplicada a uma referência, é para fazer constante o objeto a que se

refere.

Sabendo quando usar referências ou ponteiros

Programadores C++ experientes preferem muito mais referências que ponteiros. Referências são claras e fáceis

de usar, e fazem um trabalho melhor de ocultar informações, como visto no exemplo anterior.

Referências não podem ser reatribuídas, entretanto. Se você precisa primeiro apontar para um objeto e depois

para outro, deve usar ponteiros. Referências não podem ser usadas para referenciar objetos anônimos. Além

disso, referências não podem ser nulas, então se existe alguma chance do objeto em questão ser nulo, você não

deve usar referências.

Faça Não faça

Passe parâmetros por referência sempre

que possível.

Não use ponteiros se referências vão

funcionar.

Page 153: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

153 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Use const para proteger referências e

ponteiros sempre que possível.

Não tente reatribuir uma referência para outra

variável. Você não pode.

Misturando referências e ponteiros

É perfeitamente válido declarar ponteiros e referências na mesma lista de parâmetros de uma função, além de

objetos passados por valor. Aqui está um exemplo:

Gato * AlgumaFuncao(Pessoa &outra, Casa *casa, int idade);

Esta declaração diz que AlgumaFuncao pega três parâmetros. O primeiro é uma referência a um objeto Pessoa, o

segundo um ponteiro para um objeto Casa e o terceiro um inteiro. Ela retorna um ponteiro para um objeto

Gato.

A questão de onde colocar o operador de referência (&) ou de indireção (*) quando declarar estas variáveis é

uma grande controvérsia. Quando declara uma referência, você pode escrever qualquer das seguintes formas:

1. Gato& rFrisky; 2. Gato & rFrisky; 3. Gato &rFrisky;

Espaços em branco são completamente ignorados, assim em qualquer lugar que você ver um espaço aqui você

pode colocar muitos espaços, tabulações e novas linhas como desejar.

Deixando de lado questões de liberdade de expressão, qual é a melhor? Aqui estão os argumentos para as três:

O argumento para o primeiro caso é que rFrisky é uma variável cujo nome é rFrisky e o tipo pode ser pensado

como uma "referência para um objeto Gato". Assim, este argumento diz que o & deve estar com o tipo.

O contra argumento é que o tipo é Gato. O & é parte do "declarador", que inclui o nome da variável e o e

comercial (&). Mais importante, tendo o & próximo de Gato pode conduzir ao seguinte erro:

Gato& rFrisky, rBotas;

Um exame descuidado desta linha o induzirá a pensar que tanto rFrisky como rBotas são referências a objetos

Gato, mas estaria errado. Isto realmente diz que rFrisky é uma referência para um Gato e rBotas (apesar do

nome), não é uma referência, mas uma simples variável Gato. Isto pode ser reescrito como abaixo:

Gato &rFrisky, rBotas;

A resposta para esta objeção é que a declaração de referências e variáveis não devem ser combinadas desta

forma. A forma certa seria:

Gato& rFrisky; Gato botas;

Finalmente, muitos programadores escolhem sair desta argumentação e vão para a posição do meio - colocar &

no meio de dois, como mostrado no caso 2.

Naturalmente, tudo que foi dito até agora sobre o operador de referência (&) se aplica igualmente ao operador

de indireção (*). O importante é reconhecer que pessoas razoáveis divergem em suas opiniões sobre o caminho

correto. Escolha um estilo que funciona para você e seja consistente com ele em todos os programas. Clareza é,

e continua sendo, o objetivo.

Observação: muitos programadores gostam da seguinte convenção para declarar referências e ponteiros:

Page 154: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

154 Pesquisa e Desenvolvimento Tecnológico

Colocar o e-comercial e asterisco no meio, com um espaço em cada lado.

Nunca declarar referências, ponteiros e variáveis na mesma linha.

Retornando referências a objetos fora do escopo

Depois dos programadores C++ aprenderem a passar por referência, eles têm tendência a generalizar. É

possível, entretanto, exagerar nisto. Lembre-se que uma referência é sempre um apelido para algum outro

objeto. Se você passa uma referência de ou para uma função, certifique-se de perguntar "Qual o objeto que

estou apelidando e ele ainda existirá sempre que for usado?". A listagem 9.12 ilustra o perigo de retornar uma

referência para um objeto que não existe mais.

1. #include <iostream> 2. 3. int& GetInt (); 4. 5. int main() 6. { 7. int & rInt = GetInt (); 8. std::cout << "rInt = " << rInt << std::endl; 9. 10. return 0; 11. } 12. 13. int & GetInt () 14. { 15. int nLocalInt = 25; 16. 17. return nLocalInt; 18. }

Saída

rInt = 25

Atenção: este programa não compila em alguns compiladores. Ele parece funcionar corretamente com o

compilador Microsoft, mas, entretanto, deve-se observar que é uma prática de programação pobre.

Análise

O corpo de GetInt() declara um objeto local do tipo int e inicializa seu valor para 25. Então ele retorna este

objeto como referência. Alguns compiladores são espertos o bastante para capturar este erro e não executarem

o programa. Outros deixam o programa ser executado com resultados imprevisíveis.

Quando GetInt() retorna, o objeto local nLocalInt é destruído. A referência retornada por esta função é um

apelido para um objeto que não existe, e isto é mau.

O problema de retornar uma referência para um objeto na área livre

(heap)

Você pode ser tentado a resolver o problema na listagem 9.12 fazendo GetInt() criar nLocalInt na heap. Desta

forma, quando você retorna da função a locação chamada nLocalInt ainda existe.

O problema com esta abordagem é este: o que você fará com a memória alocada para nLocalInt quando você

terminar de usar? A listagem 9.13 ilustra este problema.

1. #include <iostream> 2.

Page 155: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

155 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

3. int& GetInt (); 4. 5. int main() 6. { 7. int & rInt = GetInt (); 8. std::cout << "rInt = " << rInt << std::endl; 9. 10. return 0; 11. } 12. 13. int & GetInt () 14. { 15. // Instancia um objeto inteiro na área livre / heap 16. int* pInteger = new int (25); 17. 18. return *pInteger; 19. }

Saída

rInt = 25

Cuidado: isto compila e executa aparentemente normal, mas é uma bomba relógio aguardando para explodir.

Análise

GetInt() nas linhas 13 a 19 foi alterada para não mais retornar uma referência a uma variável local. A memória é

alocada na área livre e atribuída a um ponteiro na linha 16. Este ponteiro é desreferenciado e o objeto apontado

por ele é retornado por referência. Na linha 7 o retorno de GetInt() é atribuído a uma referência e este objeto é

usado para obter o valor inteiro, que é exibido na linha 8.

Até aqui, tudo bem. Mas como a memória será liberada? Você não pode chamar delete na referência. Uma

solução é criar outro ponteiro, inicializá-lo com o endereço obtido de rInt e então invocar delete neste ponteiro.

Isto exclui a memória e fecha o vazamento de memória. Um pequeno problema, porém: ao que rInt se referirá

após uma ação destas? Como estabelecido anteriormente, uma referência deve sempre apelidar um objeto real.

Se ela referencia um objeto nulo (como faz agora), o programa é inválido. Assim, tanto quanto possível, estas

construções devem ser evitadas, por que há maneiras de fazer a mesma coisa de forma mais simples e clara.

Observação: nunca é demais enfatizar que um programa com uma referência para um objeto nulo pode

compilar, mas é inválido e sua execução é imprevisível.

No caso visto, o problema pode ser resolvido por uma das seguintes formas, que ajudariam a fazer o trabalho

melhor:

int GetInt(); int* GetInt(); void GetInt(int & nInt);

Faça Não faça

Passe parâmetros por valor quando

necessário.

Retorne por valor quando necessário.

Não passe por referência se o item

referenciado pode ficar fora do escopo.

Não perca o controle de quando e onde a

memória é alocada, assim você se certifica

que ela é liberada.

Page 156: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

156 Pesquisa e Desenvolvimento Tecnológico

Perguntas e respostas

Porque ter referências se ponteiros podem fazer tudo que referências fazem?

- Referências são mais fáceis de usar e entender. A indireção é oculta e não há necessidade de repetidamente

desreferenciar a variável.

Porque ter ponteiros se referências são mais fáceis?

- Referências não podem ser nulas e não podem ser reatribuídas. Ponteiros oferecem grande flexibilidade, mas

são um pouco mais difíceis de usar.

Porque você precisaria retornar uma função por valor?

- Se o objeto sendo retornado é local, você deve retornar por valor ou estará retornando uma referência para

um objeto que não existe.

Dado o perigo de retornar por referência, porque não sempre retornar por valor?

- Uma eficiência muito maior é obtida retornando por referência. A memória é economizada e o programa

executa mais rápido.

Teste

1 - Qual a diferença entre uma referência e um ponteiro?

2 - Quando devo usar um ponteiro ao invés de uma referência?

3 - O que new retorna se houver memória insuficiente para criar o novo objeto?

4 - Que é uma referência constante?

5 - Qual a diferença entre passar por valor e passar por referência?

6 - Ao declarar uma referência, qual está correto:

A. int& minhaRef = meuInt;

B. int & minhaRef = meuInt;

C. int &minhaRef = meuInt;

Exercícios

1 - Escreva um programa que declare um int, uma referência para um int e um ponteiro para um int. Use o

ponteiro e a referência para manusear o valor no int.

2 - Escreva um programa que declare um ponteiro constante para um inteiro constante. Inicialize o ponteiro

para uma variável inteira, varUm. Atribua 6 a varUm. Use o ponteiro para atribuir 7 a varUm. Crie uma segunda

variável inteira, varDois. Reatribua o ponteiro para varDois. Não compile este exercício ainda.

3 - Agora compile o programa do exercício 2. O que produz erros? O que produz avisos?

4 - Escreva um programa que produza um ponteiro selvagem.

5 - Corrija o programa do exercício 4.

Page 157: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

157 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

6 - Escreva um programa que produza um vazamento de memória.

7 - Corrija o programa do exercício 6.

8 - Caça-Erros: o que está errado neste programa?

1. #include <iostream> 2. using namespace std; 3. class Gato 4. { 5. public: 6. Gato(int idade) { suaIdade = idade; } 7. ~Gato(){} 8. int GetIdade() const { return suaIdade;} 9. private: 10. int suaIdade; 11. }; 12. 13. Gato & CriaGato(int idade); 14. 15. int main() 16. { 17. int idade = 7; 18. Gato Botas = CriaGato(idade); 19. cout << "Botas tem " << Botas.GetIdade() 20. << " anos" << endl; 21. return 0; 22. } 23. 24. Gato & CriaGato(int idade) 25. { 26. Gato * pGato = new Gato(idade); 27. return *pGato; 28. }

9 - Corrija o programa do exercício 8.

Page 158: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

158 Pesquisa e Desenvolvimento Tecnológico

Apêndice – Aplicando o

Conhecimento

Apêndice I Resolva os Problemas

Page 159: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

159 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Apêndice I

Resolva os problemas

Muito do que você aprendeu já pode ser prontamente usado para resolver problemas reais do mundo da

computação.

Haverá agora alguns desafios que já podem ser facilmente resolvidos aplicando o conhecimento já obtido e

com uma breve pesquisa na Internet para auxílio. Tente resolver os problemas, e caso não consiga, anote quais

impencílios foram encontrados.

Problema 1

Se listarmos todos os números naturais menores que 10 que são múltiplos de 3 ou de 5, nós ficaremos com os

números 3, 5, 6 e 9. A soma desses múltiplos é 23.

Com base nesse raciocínio, escreva um programa que encontre a soma de todos os múltiplos de 3 ou de 5

menores que 1000.

Problema 2

Se pegarmos 122 e somarmos todos os seus dígitos, sendo isso 1+2+2, nós termos o valor total de 5 como

resultado. Descubra a soma de todos os dígitos de n!. Contudo, para conseguirmos realizar a soma de todos os

dígitos de !n, nós precisamo descobrir primeiro quanto vale n!.

Podemo definir n! como n x (n – 1). Assim, caso n! valesse 10, para chegarmos ao seu valor final, teríamos de

calcular 10! = 10 x 9 x 7 x 6 x 5 x 4 x 3 x 2 x 1 = 3628800. Com esse resultado, podemos somar os dígitos de 10!

como 3+6+2+8+8+0+0 = 27.

Encontra a soma de todos os dígitos do número 100!

Problema 3

Encontre o maior produto de cinco dígitos consecutivos no seguinte número abaixo de 1000 dígitos.

73167176531330624919225119674426574742355349194934

96983520312774506326239578318016984801869478851843

85861560789112949495459501737958331952853208805511

12540698747158523863050715693290963295227443043557

66896648950445244523161731856403098711121722383113

62229893423380308135336276614282806444486645238749

30358907296290491560440772390713810515859307960866

70172427121883998797908792274921901699720888093776

65727333001053367881220235421809751254540594752243

52584907711670556013604839586446706324415722155397

53697817977846174064955149290862569321978468622482

83972241375657056057490261407972968652414535100474

82166370484403199890008895243450658541227588666881

16427171479924442928230863465674813919123162824586

17866458359124566529476545682848912883142607690042

24219022671055626321111109370544217506941658960408

07198403850962455444362981230987879927244284909188

Page 160: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

160 Pesquisa e Desenvolvimento Tecnológico

84580156166097919133875499200524063689912560717606

05886116467109405077541002256983155200055935729725

71636269561882670428252483600823257530420752963450

Assim, ache o produto do 1º x 2º x 3º x 4º x 5º. Em seguida, faça o mesmo para o 6º x 7º x 8º x 9º x 10º e assim

consecutivamente até ter verificado todos os produtos de cada cinco número consecultivos. Em seguida,

imprima o maior produto achado.

Problema 4

Usando names.txt (para baixar, basta clicar em names.txt segurando a tecla ctrl.), um arquivo de texto de

46K contendo mais de cinco mil nomes, carregue os nomes no conteúdo do arquivo e organize-os (em

memória) em ordem alfabética. Em seguida, em cada nome, some cada letra presente no nome de acordo

com a posição que ela ocupa no alfabeto e em seguida multiplique pela posição que o nome ficou na lista

alfabética de nomes para obter uma pontuação.

Por exemplo, quando a lista estiver organizada em ordem alfabética, COLIN, o qual vale 3(C) + 15(O) + 12(L) +

9(I) + 14(N) = 53, será o 938º nome na lista. Assim, a pontuação de COLIN seria 938 x 53 = 49714.

Qual é o total da soma de todas as pontuações de todos os nomes?

Page 161: Pesquisa e Desenvolvimento Tecnológico - Atual Sistemas do Desenvolvedor - Fasciculo III... · e Java, têm outros componentes, referenciados como máquina virtual (virtual machine

© 2011 Atual Sistemas. Todos os direitos Reservados.

Ao fazer uso desse material você está automaticamente concordando com o termo de licença na página 3.

161 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens

Por que C++? Ora, por que não?

Equipe Editora

Traduzido por Luiz Kohl

Revisado por Claudio M. Souza Junior

Primeiro, resolva o problema. Então, começe a programar.

Só há dois tipos de linguagem de programação: as que todo mundo

reclama e as que ninguém usa.