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

Tamanho: px
Começar a partir da página:

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

Transcrição

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

2 2 Pesquisa e Desenvolvimento Tecnológico Essa página foi deixada em branco intencionalmente.

3 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 3 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 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.

4 4 Pesquisa e Desenvolvimento Tecnológico Conteúdos PUBLICADO POR... 3 PARTE 1 NOÇÕES BÁSICAS SOBRE C Introdução a C Uma breve história do C Interpretadores e Compiladores... 9 Programação Procedural, Estruturada e Orientada a Objetos... 9 Programação Orientada a Objetos e C Como C++ tem evoluído O padrão ANSI/ISO Preparação para Programar Seu ambiente de desenvolvimento Seu primeiro programa C Erros de compilação Perguntas e Respostas Teste Exercícios A Anatomia de um Programa C Um programa simples Uma rápida olhada em cout Usando a namespace Standard Comentando seus programas Funções Usando funções Conclusão Usando Variáveis e Declarando Constantes Que é uma variável? Armazenando dados na memória Reservando memória Tipos fundamentais de variáveis Definindo variáveis Convenção de nomes Palavras Chaves Determinando o tamanho de uma variável Criando muitas variáveis ao mesmo tempo Atribuindo valores a variáveis Criando pseudônimos com typedef Estourando os limites de um inteiro unsigned Estourando os limites de um inteiro signed Trabalhando com caracteres Caracteres especiais de impressão Constantes Perguntas e respostas Teste Exercícios... 36

5 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 5 PARTE 2 MECÂNICA BÁSICA DE DADOS E EXPRESSÕES Trabalhando Com Arrays e Strings Que é um array? Acessando elementos do array Usando um array além de seus limites Inicializando arrays Declarando arrays Arrays multidimensionais Arrays de caracter e strings Usando os métodos strcpy() e strncpy() Classes string Perguntas e respostas Teste Exercícios Trabalhando com Expressões, Instruções e Operadores Iniciando com instruções Usando espaços em branco Blocos e instruções compostas Expressões Trabalhando com operadores A natureza do verdadeiro Avaliando com operadores relacionais A instrução if A instrução else Instruções if avançadas Usando operadores lógicos Procedência relacional Mais sobre verdade e falsidade O operador condicional ternário Perguntas e respostas Teste Exercícios Organizando o Código com Funções Que é uma função? Valores de retorno, parâmetros e argumentos Declarando e definindo funções Protótipo de funções Definindo a função Execução de funções Determinando o escopo de variáveis Parâmetros são variáveis locais Variáveis globais Variáveis globais: uma palavra de precaução Considerações para criar instruções em funções Mais sobre argumento de funções Mais sobre valores de retorno Parâmetros default Sobrecarga de funções Tópicos especiais sobre funções Como funções funcionam - uma olhada sob o capô 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.

6 6 Pesquisa e Desenvolvimento Tecnológico Perguntas e respostas Testes Exercícios... 90

7 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 7 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 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.

8 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

9 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 9 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 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.

10 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 Abrir o arquivo de empregados Ir para o registro correto 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

11 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 11 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 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.

12 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.

13 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 13 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 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.

14 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.

15 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 15 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> 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> 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? 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.

16 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> 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

17 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 17 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 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.

18 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;

19 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 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> using namespace std; void FuncaoDemonstracao() 6. { 7. cout << "Na funcao demonstracao" << endl; 8. } 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. } 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.

20 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> using namespace std; void Pausa() 6. { 7. char resposta; 8. cin >> resposta; 9. } int Soma(int primeiro, int segundo) 12. { 13. return (primeiro + segundo); 14. } 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;

21 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens } Saída Entrei em main() Digite dois numeros: 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? 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.

22 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.

23 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 23 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 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.

24 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 signed char unsigned char 1 Byte 1 Byte 1 Byte 1 Byte short short int signed short signed short int unsigned short unsigned short int wchar_t char16_t int signed signed int unsigned unsigned int long long int signed long signed long int unsigned long unsigned long int long long long long int signed long long signed long long int unsigned long long unsigned long long int short int unsigned short int int unsigned int long int unsigned long int long long int (C++11) unsigned long long int (C++11) 2 Bytes 2 Bytes 2 Bytes 2 Bytes 2 Bytes 4 Bytes 4 Bytes 4 Bytes 4 Bytes 4 Bytes 4 Bytes 8 Bytes 8 Bytes 8 Bytes 8 Bytes 8 Bytes

25 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 25 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 ± à não-sinalizado 0 to à bytes sinalizado ± ,147,483,648 à 2,147,483,647 não-sinalizado 0 to to 4,294,967,295 8 bytes sinalizado ± ,223,372,036,854,775,808 à 9,223,372,036,854,775,807 não-sinalizado 0 to to 18,446,744,073,709,551,615 Ponto Flutuante 4 bytes IEEE-754 ± ± 38 (~7 digits) 8 bytes IEEE-754 ± ± 308 (~15 digits) min subnormal: ± 1.401,298, min normal: ± 1.175,494, max: ± 3.402,823, min subnormal: ± 4.940,656,458, min normal: ± 2.225,073,858,507,201, max: ± 1.797,693,134,862,315, 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. } 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.

26 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; 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;

27 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 27 Saída 13. cout << "bool: " << sizeof(bool) << endl; 14. char resposta; 15. cin >> resposta; 16. return 0; 17. } 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: 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.

28 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.

29 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 29 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: Saída 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. } Numero pequeno: 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 (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: 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.

30 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: Numero pequeno: Numero pequeno: 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 (seu limite negativo). Na linha 10 incrementamos novamente, o que resulta no próximo valor negativo, 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.

31 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 31 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: Saída 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. }!!!"#$%&'()*+,-./ :;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] ^_`abcdefghijklmnopqrstuvwxyz{ }~ ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø Ø ƒáíóúññªº ½¼ ÁÂÀ ãã ððêëèıíîï Ì ÓßÔÒ õõµþþúûùýý ± ¾ ¹³²!"#$%&'()*+,-./ :;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno pqrstuvwxyz{ }~ Análise 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.

32 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

33 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 33 \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 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.

34 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: Saída 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. } 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;

35 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 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 , um unsigned long int (com 4 bytes) só faz isto após 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 (ou 0xFF em hexa), é acessado por um inteiro sem sinal como 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? 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.

36 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, Declare uma variável float e inicialize-a com sua constante PI.

37 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 37 Análise Parte 2 Mecânica Básica de Dados e Expressões Lição 4 Trabalhando com Arrays e Strings Lição 5 Lição 6 Trabalhando com Expressões, Instruções e Operadores Organizando o Código com Funções 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.

38 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];

39 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 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; 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.

40 40 Pesquisa e Desenvolvimento Tecnológico Saída 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. } 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};

41 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 41 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]; 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.

42 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:

43 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 43 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 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.

44 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: Saída 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. } 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!

45 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 45 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: Saída 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. } String1: Nenhum homem eh uma ilha String2: Nenhum homem eh uma ilha Análise 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.

46 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>

47 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 47 Saída 2. #include <string> using namespace std; int main() 7. { 8. string string1 = "Uma string C++"; 9. cout << "string1 = " << string1 << endl; string string2; 12. string2 = string1; 13. cout << "string2 = " << string2 << endl; string2 = "Alo, strings"; 16. cout << "Agora string2 contem: " << string2 << endl; string somastring = string1 + " - " + string2; 19. cout << "Resultado da soma de strings: " << somastring << endl; return 0; 22. } 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 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.

48 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 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;

49 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 49 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 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.

50 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 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 SEGUNDOS_POR_MINUTO // Constante inteira que retorna 60 Presumindo que PI foi definido como uma constante float e inicializado com o valor 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()

51 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens { 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: 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.

52 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 = 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 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;

53 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 53 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 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.

54 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 = * 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 = * * 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 = ; Por fim são feitas as adições, da esquerda para a direita: = 8; = 80; = 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;

55 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 55 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 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.

56 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: Saída 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. }

57 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 57 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; 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.

58 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: Saída 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. } Informe dois numeros

59 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 59 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; } 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.

60 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.

61 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 61 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: 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.

62 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: Saída 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. } 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

63 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 63 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 = é 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 * 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 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.

64 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ê?

65 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 65 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 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.

66 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.

67 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 67 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 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.

68 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(); Saída 1. #include <iostream> int Area(int comprimento, int largura); int main() 6. { 7. using std::cout; 8. using std::cin; int comprimentodoquintal = 0; 11. int larguradoquintal = 0; 12. int areadoquintal = 0; cout << "Qual a largura do seu quintal? "; 15. cin >> larguradoquintal; 16. cout << "Qual o comprimento do seu quintal? "; 17. cin >> comprimentodoquintal; areadoquintal = Area(comprimentoDoQuintal, larguradoquintal); cout << "Seu quintal tem "; 22. cout << areadoquintal; 23. cout << " metros quadrados\n\n"; 24. return 0; 25. } int Area(int comp, int larg) 28. { 29. return (comp * larg); 30. } 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

69 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 69 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> float Converte(float); int main() 6. { 7. using namespace std; float tempfar; 10. float tempcel; 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.

70 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. } 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: 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> void MinhaFuncao(); int main() 6. { 7. int x = 5; 8. std::cout << "Em main, x = " << x; MinhaFuncao(); std::cout << "\nde volta a main, x = " << x << std::endl; 13. return 0; 14. }

71 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 71 Saída void MinhaFuncao() 17. { 18. int x = 8; 19. std::cout << "\nem MinhaFuncao, x local = " << x; { 22. std::cout << "\nno bloco dentro da funcao, x = " << x; 23. int x = 9; 24. std::cout << "\nvariavel x declarada no bloco = " << x; 25. } std::cout << "\nfora do bloco, em MinhaFuncao, x = " << x; 28. } 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> using namespace std; void Trocar(int x, int y); 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.

72 72 Pesquisa e Desenvolvimento Tecnológico int main() 8. { 9. int x = 5, y = 10; 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. } void Trocar(int x, int y) 18. { 19. int temp; cout << "Trocar. Antes da troca, x: " << x << " y: " << y << endl; temp = x; 24. x = y; 25. y = temp; 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

73 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 73 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> void MinhaFuncao(); // Protótipo int x = 5, y = 7; // Variáveis globais int main() 8. { 9. using namespace std; 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. } void MinhaFuncao() 21. { 22. using std::cout; int y = 10; 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() 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.

74 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.

75 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 75 É 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> int Duplo(int valoradobrar); int main() 6. { 7. using std::cout; int resultado = 0; 10. int entrada; cout << "Informe um numero entre 0 e para dobrar: "; 13. std::cin >> entrada; cout << "\nantes de Duplo() ser chamado..."; 16. cout << "\nentrada: " << entrada << " dobrado: " << resultado << "\n"; resultado = Duplo(entrada); cout << "\nvoltando de Duplo()...\n"; 21. cout << "\nentrada: " << entrada << " dobrado: " << resultado << "\n"; return 0; 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.

76 76 Pesquisa e Desenvolvimento Tecnológico 24. } 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 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 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 ou maior que , 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);

77 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 77 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> int AreaCubo(int comprimento, int largura = 25, int altura = 1); int main() 6. { 7. int comprimento = 100; 8. int largura = 50; 9. int altura = 2; 10. int area; area = AreaCubo(comprimento, largura, altura); 13. std::cout << "Primeira area = " << area << "\n"; area = AreaCubo(comprimento, largura); 16. std::cout << "Segunda area = " << area << "\n"; area = AreaCubo(comprimento); 19. std::cout << "Terceira area = " << area << "\n"; 20. return 0; 21. } int AreaCubo(int comprimento, int largura, int altura) 24. { 25. return (comprimento * largura * altura); 26. } Saída Primeira area = 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 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.

78 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 Lembre-se que parâmetros de função atuam como variáveis locais dentro da função Lembre-se que alterações de uma variável global numa função alteram esta variável para todas as funções Não faça Não tente criar um valor default para o primeiro parâmetro se nenhum valor default foi definido para o segundo 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).

79 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 79 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> int Duplo(int); 4. long Duplo(long); 5. float Duplo(float); 6. double Duplo(double); using namespace std; int main() 11. { 12. int meuint = 6500; 13. long meulong = 65000; 14. float meufloat = 6.5F; 15. double meudouble = 6.5e20; int intdobrado; 18. long longdobrado; 19. float floatdobrado; 20. double doubleddobrado; cout << "meuint: " << meuint << "\n"; 23. cout << "meulong: " << meulong << "\n"; 24. cout << "meufloat: " << meufloat << "\n"; 25. cout << "meudouble: " << meudouble << "\n"; intdobrado = Duplo(meuInt); 28. longdobrado = Duplo(meuLong); 29. floatdobrado = Duplo(meuFloat); 30. doubleddobrado = Duplo(meuDouble); cout << "intdobrado: " << intdobrado << "\n"; 33. cout << "longdobrado: " << longdobrado << "\n"; 34. cout << "floatdobrado: " << floatdobrado << "\n"; 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.

80 80 Pesquisa e Desenvolvimento Tecnológico 35. cout << "doubleddobrado: " << doubleddobrado << "\n"; 36. return 0; 37. } int Duplo(int original) 40. { 41. cout << "Em Duplo(int)\n"; 42. return 2 * original; 43. } long Duplo(long original) 46. { 47. cout << "Em Duplo(long)\n"; 48. return 2 * original; 49. } float Duplo(float original) 52. { 53. cout << "Em Duplo(float)\n"; 54. return 2 * original; 55. } double Duplo(double original) 58. { 59. cout << "Em Duplo(double)\n"; 60. return 2 * original; 61. } Saída meuint: 6500 meulong: meufloat: 6.5 meudouble: 6.5e+020 Em Duplo(int) Em Duplo(long) Em Duplo(float) Em Duplo(double) intdobrado: longdobrado: 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.

81 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 81 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: 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.

82 82 Pesquisa e Desenvolvimento Tecnológico 1. #include <iostream> inline int Dobro(int); int main() 6. { 7. int alvo; 8. using std::cout; 9. using std::cin; 10. using std::endl; cout << "Digite um numero para trabalhar: "; 13. cin >> alvo; 14. cout << endl; alvo = Dobro(alvo); 17. cout << "Alvo:" << alvo << endl; alvo = Dobro(alvo); 20. cout << "Alvo:" << alvo << endl; alvo = Dobro(alvo); 23. cout << "Alvo:" << alvo << endl; return 0; 26. } 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.

83 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 83 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, 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 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() 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.

84 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> using namespace std; int Fib(int n); int main() 8. { 9. int n, resposta; 10. cout << "Entre o numero a encontrar: "; 11. cin >> n; 12. cout << "\n\n"; resposta = Fib(n); cout << resposta << " fica na posicao " << n; 17. cout << " da sequencia de Fibonacci\n"; 18. return 0; 19. } 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

85 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 85 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 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.

86 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).

87 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 87 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 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.

88 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).

89 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 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? 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.

90 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

91 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 91 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 } 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 } 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 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 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á 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.

92 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

93 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 93 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 // Laço com goto 3. #include <iostream> 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: 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.

94 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 // 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. }

95 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 95 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 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.

96 96 Pesquisa e Desenvolvimento Tecnológico 2. // Instruções while complexas 3. #include <iostream> int main() 6. { 7. using namespace std; 8. unsigned short pequeno; 9. unsigned long grande; 10. const unsigned short MAXPEQUENO=65535; cout << "Digite um numero pequeno: "; 13. cin >> pequeno; 14. cout << "Digite um numero grande: "; 15. cin >> grande; cout << "Pequeno: " << pequeno << "..."; // 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 << "."; pequeno++; 26. grande-=2; 27. } cout << "\npequeno: " << pequeno << " Grande: " << grande << endl; 30. return 0; 31. } Saída Digite um numero pequeno: 2 Digite um numero grande: 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 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".

97 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 97 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 Demonstra break e continue 2. #include <iostream> int main() 5. { 6. using namespace std; unsigned short pequeno; 9. unsigned long grande; 10. unsigned long salto; 11. unsigned long alvo; 12. const unsigned short MAXPEQUENO=65535; 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; cout << "\n"; // Configura duas posições de parada para o loop 26. while (pequeno < grande && pequeno < MAXPEQUENO) 27. { 28. pequeno++; if (pequeno % salto == 0) // salta o decremento? 31. { 32. cout << "Saltando " << pequeno << endl; 33. continue; 34. } if (grande == alvo) // combinação exata para o alvo? 37. { 38. cout << "Alvo alcancado!"; 39. break; 40. } grande-=2; 43. } // fim do loop while cout << "\npequeno: " << pequeno << " Grande: " << grande << endl; 46. return 0; 47. } 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.

98 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 }

99 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 99 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 // Demonstra o loop while true 3. #include <iostream> int main() 6. { 7. int contador = 0; 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 Use loops while com uma instrução condicional. Não faça Usar a instrução goto. Seja cauteloso ao usar instruções continue e break. Assegure-se que seu loop vai eventualmente terminar. Esquecer a diferença entre continue e break. continue volta ao início, ao passo que break sai do laço 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.

100 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: Saída 1. // Listagem // Demonstra pular o corpo de um 3. // laço while quando a condição é falsa #include <iostream> 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. } 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:

101 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 101 Saída 1. // Listagem // Demonstra do while #include <iostream> 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. } 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; 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.

102 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 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. Não faça 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: Saída 1. // Listagem // Laços com while #include <iostream> int main() 7. { 8. int contador = 0; while(contador < 5) 11. { 12. contador++; 13. std::cout << "Contando! "; 14. } std::cout << "\ncontador: " << contador << std::endl; 17. return 0; 18. } 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

103 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 103 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: Saída 1. // Listagem // Laços com for #include <iostream> int main() 7. { 8. int contador; 9. for (contador = 0; contador < 5; contador++) 10. std::cout << "Contando! "; std::cout << "\ncontador: " << contador << std::endl; 13. return 0; 14. } 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 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.

104 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: Saída 1. //Listagem // Demonstra múltiplas instruções em 3. // laços for 4. #include <iostream> int main() 7. { for (int i=0, j=0; i<3; i++, j++) 10. std::cout << "i: " << i << " j: " << j << std::endl; 11. return 0; 12. } 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.

105 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 105 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: Saída 1. // Listagem // Laços for com instruções nulas #include <iostream> int main() 7. { 8. int contador = 0; for( ; contador < 5; ) 11. { 12. contador++; 13. std::cout << "Contando! "; 14. } std::cout << "\ncontador: " << contador << std::endl; 17. return 0; 18. } 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 #include <iostream> 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; 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.

106 106 Pesquisa e Desenvolvimento Tecnológico Saída 17. contador++; // incremento 18. } 19. else 20. break; 21. } 22. return 0; 23. } 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: Saída i: 0 i: 1 i: 2 i: 3 i: 4 1. // Listagem // Demonstra instrução nula 3. // como corpo do laço #include <iostream> 6. int main() 7. { 8. for (int i = 0; i<5; std::cout << "i: " << i++ << std::endl) 9. ; 10. return 0; 11. } 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.

107 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 107 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: Saída 1. //Listagem //Ilustra laços for aninhados 3. #include <iostream> 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. } 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 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.

108 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.

109 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 109 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, 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: Saída 1. // Listagem Demonstra a solução do enésimo 2. // número de Fibonacci usando iteração #include <iostream> 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; resposta = Fib(posicao); 16. cout << resposta << " esta na posicao "; 17. cout << posicao << " da sequencia de Fibonacci. " << endl; 18. return 0; 19. } unsigned int Fib(unsigned int n) 22. { 23. unsigned int menosdois=1, menosum=1, resposta=2; if (n < 3) 26. return 1; for (n -= 3; n!= 0; n--) 29. { 30. menosdois = menosum; 31. menosum = resposta; 32. resposta = menosum + menosdois; 33. } return resposta; 36. } 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? esta na posicao 20 da sequencia de Fibonacci. Qual posicao? esta na posicao 100 da sequencia de Fibonacci. Análise 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.

110 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 e escreve 3, e pensa "encontrar mais um". Por fim, você escreve 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;

111 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 111 } 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: Saída 1. // Listagem // Demonstra a instrução switch 3. #include <iostream> 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. } Informe um numero entre 1 e 5: 3 Excelente! Magistral! Incrivel! Informe um numero entre 1 e 5: 8 Muito grande! 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.

112 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:

113 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 113 } 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 //Usando um laço infinito para manusear iteração com usuário 3. #include <iostream> // protótipos 6. int menu(); 7. void FacaTarefaUm(); 8. void FacaTarefaMuitas(int); using namespace std; 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 if (sair == true) 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.

114 114 Pesquisa e Desenvolvimento Tecnológico Saída 41. break; 42. } // encerra infinito 43. return 0; 44. } // encerra main() int menu() 47. { 48. int escolha; 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. } void FacaTarefaUm() 62. { 63. cout << "Tarefa um! " << endl; 64. } void FacaTarefaMuitas(int qual) 67. { 68. if (qual == 2) 69. cout << "Tarefa dois! " << endl; 70. else 71. cout << "Tarefa tres! " << endl; 72. } **** 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

115 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 115 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 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 faça 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 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.

116 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 Escreva um laço while que conte de 100 a 200 de 2 em Escreva um laço do...while que conte de 100 a 200 de 2 em 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();

117 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 117 } case 1: // prossiga case 2: // prossiga case 3: // prossiga case 4: // prossiga case 5: FacaUmACinco(); break; default: FacaDefault(); break; 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.

118 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.

119 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 119 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. Saída 1. // Listagem 8.1 Demonstra o operador endereço-de 2. // e o endereço de uma variável local 3. #include <iostream> int main() 6. { 7. using namespace std; 8. unsigned short varshort=5; 9. unsigned long varlong=65535; 10. long svar = ; cout << "varshort:\t" << varshort; 13. cout << "\tendereco de varshort:\t"; 14. cout << &varshort << endl; cout << "varlong:\t" << varlong; 17. cout << "\tendereco de varlong:\t" ; 18. cout << &varlong << endl; cout << "svar:\t\t" << svar; 21. cout << "\tendereco de svar:\t" ; 22. cout << &svar << endl; return 0; 25. } varshort: 5 Endereco de varshort: 0041F824 varlong: Endereco de varlong: 0041F818 svar: 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! 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.

120 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

121 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 121 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 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.

122 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 Isto são dois bytes (16 bits) cujo valor decimal é 5. A variável ponteiro está no endereço 106 e seu valor é: 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.

123 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 123 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. Saída 1. // Listagem 8.2 Usando ponteiros 2. #include <iostream> typedef unsigned short int USHORT; int main() 7. { using namespace std; USHORT minhaidade; // uma variável 12. USHORT * pidade = 0; // um ponteiro minhaidade = 5; cout << "minhaidade: " << minhaidade << endl; 17. pidade = &minhaidade; // atribuindo endereço de minhaidade para pidade 18. cout << "*pidade: " << *pidade << endl << endl; cout << "Atribuindo *pidade = 7... " << endl; 21. *pidade = 7; // atribui minhaidade para cout << "*pidade: " << *pidade << endl; 24. cout << "minhaidade: " << minhaidade << endl << endl; cout << "Atribuindo minhaidade = 9... " << endl; 27. minhaidade = 9; cout << "minhaidade: " << minhaidade << endl; 30. cout << "*pidade: " << *pidade << endl; return 0; 33. } minhaidade: 5 *pidade: 5 Atribuindo *pidade = 7... *pidade: 7 minhaidade: 7 Atribuindo minhaidade = 9... minhaidade: 9 *pidade: 9 Análise 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.

124 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 // O que é armazenado por um ponteiro. 3. #include <iostream> int main() 6. { 7. using namespace std; unsigned short int minhaidade = 5, suaidade = 10; // um ponteiro 12. unsigned short int * pidade = &minhaidade; cout << "minhaidade:\t" << minhaidade 15. << "\t\tsuaidade:\t" << suaidade << endl; cout << "&minhaidade:\t" << &minhaidade 18. << "\t&suaidade:\t" << &suaidade << endl; cout << "pidade:\t" << pidade << endl; 21. cout << "*pidade:\t" << *pidade << endl; cout << "\nreatribuindo: pidade = &suaidade..." << endl << endl; 25. pidade = &suaidade; // reatribuindo o ponteiro cout << "minhaidade:\t" << minhaidade << 28. "\t\tsuaidade:\t" << suaidade << endl; cout << "&minhaidade:\t" << &minhaidade 31. << "\t&suaidade:\t" << &suaidade << endl; cout << "pidade:\t" << pidade << endl; 34. cout << "*pidade:\t" << *pidade << endl; cout << "\n&pidade:\t" << &pidade << endl; return 0;

125 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 125 Saída 39. } 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 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.

126 126 Pesquisa e Desenvolvimento Tecnológico Saída 1. #include <iostream> 2. const int TAMANHO_ARRAY = 5; int main () 5. { 6. using namespace std; // Um array de 5 inteiros inicializado com 5 valores 9. int numeros [TAMANHO_ARRAY] = {0, 100, 200, 300, 400}; // pint aponta para o primeiro elemento 12. const int *pint = numeros; cout << "Usando um ponteiro que exibe o conteudo do array: " << endl; for (int indice = 0; indice < TAMANHO_ARRAY; ++ indice) 17. cout << "Elemento [" << indice << "] = " << *(pint + indice) << endl; return 0; 20. } 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. numerostres é um ponteiro para um array de 500 objetos int. numerosdois é um array de 500 ponteiros para 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 Use o operador de indireção (*) para Não faça Não confunda o endereço em um ponteiro com o valor que

127 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 127 acessar os dados armazenados no endereço que está no ponteiro. ele endereça Inicialize todos os ponteiros, para um valor válido ou para nullptr (0). 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, 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.

128 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

129 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 129 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 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.

130 130 Pesquisa e Desenvolvimento Tecnológico Saída 1. // Listagem // 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. } 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;

131 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 131 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 Criando objetos na área livre 2. // usando new e delete #include <iostream> using namespace std; class Gato 9. { 10. public: 11. Gato(); 12. ~Gato(); 13. private: 14. int _idade; 15. }; Gato::Gato() 18. { 19. cout << "Construtor chamado. " << endl; 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.

132 132 Pesquisa e Desenvolvimento Tecnológico Saída 20. _idade = 1; 21. } Gato::~Gato() 24. { 25. cout << "Destrutor chamado. " << endl; 26. } 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. } 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á;

133 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 133 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. Saída 1. // Listagem Demonstra um ponteiro selvagem typedef unsigned short int USHORT; 4. #include <iostream> int main() 7. { 8. USHORT * pint = new USHORT; 9. *pint = 10; 10. std::cout << "*pint: " << *pint << std::endl; 11. delete pint; long * plong = new long; 14. *plong = 90000; 15. std::cout << "*plong: " << *plong << std::endl; *pint = 20; // opa, isto foi excluído! std::cout << "*pint: " << *pint << std::endl; 20. std::cout << "*plong: " << *plong << std::endl; 21. delete plong; 22. return 0; 23. } *pint: 10 *plong: *pint: 20 *plong: 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 é 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 Surgem duas questões: Como o valor de plong mudou se ele nem foi tocado? 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.

134 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 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 foi atribuído a *plong. O computador usado para o exemplo armazenou o valor de quatro bytes ( F 90) na ordem byte reversa, ou seja, foi armazenado como 5F pint tinha atribuído o valor 20 ou em hexadecimal. Como pint ainda aponta para o mesmo endereço, os primeiros dois bytes de plong foram sobrescritos, deixando O valor em plong foi exibido, revertendo os bytes para sua ordem correta de , que no DOS foi traduzido como 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 Proteja objetos passados por referência usando Não faça Não use um ponteiro que foi excluído.

135 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 135 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? 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.

136 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 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; }

137 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 137 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 Demonstra o uso de referências #include <iostream> int main() 6. { 7. using namespace std; 8. int intum; 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.

138 138 Pesquisa e Desenvolvimento Tecnológico Saída 9. int &ralgumaref = intum; intum = 5; 12. cout << "intum: " << intum << endl; 13. cout << "ralgumaref: " << ralgumaref << endl; ralgumaref = 7; 16. cout << " intum: " << intum << endl; 17. cout << " ralgumaref: " << ralgumaref << endl; return 0; 20. } 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. Saída 1. //Listagem Demonstra o uso de referências #include <iostream> int main() 6. { 7. using namespace std; 8. int intum; 9. int &ralgumaref = intum; intum = 5; 12. cout << "intum: " << intum << endl; 13. cout << "ralgumaref: " << ralgumaref << endl; cout << "&intum: " << &intum << endl; 16. cout << "&ralgumaref: " << &ralgumaref << endl; return 0; 19. }

139 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 139 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 //Reatribuindo a referencia #include <iostream> int main() 6. { 7. using namespace std; 8. int intum; 9. int &ralgumaref = intum; intum = 5; 12. cout << "intum: " << intum << endl; 13. cout << "ralgumaref: " << ralgumaref << endl; 14. cout << "&intum: " << &intum << endl; 15. cout << "&ralgumaref: " << &ralgumaref << endl; 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; 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.

140 140 Pesquisa e Desenvolvimento Tecnológico Saída 25. return 0; 26. } 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 Use referências para criar um apelido para um objeto. Inicialize todas as referências. Não faça 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.

141 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 141 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: Saída 29. // Listagem Demonstra a passagem por valor 30. #include <iostream> using namespace std; 33. void Trocar(int x, int y); int main() 36. { 37. int x = 5, y = 10; 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. } void Trocar(int x, int y) 46. { 47. int temp; cout << "Trocar. Antes da troca, x: " << x << " y: " << y << endl; temp = x; 52. x = y; 53. y = temp; cout << "Trocar. Depois da troca, x: " << x << " y: " << y << endl; 56. } 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 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.

142 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. Saída 1. //Listagem Demonstra passagem por referencia 2. #include <iostream> using namespace std; 5. void Trocar(int *x, int *y); int main() 8. { 9. int x = 5, y = 10; 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. } void Trocar (int *px, int *py) 18. { 19. int temp; cout << "Trocar. Antes da troca, *px: " << *px << 22. " *py: " << *py << endl; temp = *px; 25. *px = *py; 26. *py = temp; cout << "Trocar. Depois da troca, *px: " << *px << 29. " *py: " << *py << endl; } 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

143 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 143 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 Demonstra passagem por referencia 2. // usando referências! 3. #include <iostream> using namespace std; 6. void Trocar(int &x, int &y); int main() 9. { 10. int x = 5, y = 10; cout << "Main. Antes da troca, x: " << x << " y: " 13. << y << endl; Trocar(x,y); cout << "Main. Depois da troca, x: " << x << " y: " 18. << y << endl; return 0; 21. } void Trocar (int &rx, int &ry) 24. { 25. int temp; cout << "Trocar. Antes da troca, rx: " << rx << 28. " ry: " << ry << endl; temp = rx; 31. rx = ry; 32. ry = temp; cout << "Trocar. Depois da troca, rx: " << rx << 36. " ry: " << ry << endl; 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.

144 144 Pesquisa e Desenvolvimento Tecnológico 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 Retornando múltiplos valores de uma função #include <iostream> using namespace std; 6. short Fator(int n, int* pquadrado, int* pcubico); int main() 9. { 10. int numero, quadrado, cubico; 11. short erro; cout << "Entre um numero (0-20): "; 14. cin >> numero; erro = Fator(numero, &quadrado, &cubico); if (!erro) 19. { 20. cout << "numero: " << numero << endl; 21. cout << "quadrado: " << quadrado << endl;

145 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens cout << "cubico: " << cubico << endl; 23. } 24. else 25. cout << "Erro encontrado!" << endl; 26. return 0; 27. } 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 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.

146 146 Pesquisa e Desenvolvimento Tecnológico Saída 1. // Listagem // Retornando múltiplos valores de uma função 3. // usando referências 4. #include <iostream> using namespace std; enum CODIGO_ERRO { SUCESSO, ERRO }; CODIGO_ERRO Fator(int, int&, int&); int main() 13. { 14. int numero, quadrado, cubico; 15. CODIGO_ERRO resultado; cout << "Entre um numero (0-20): "; 18. cin >> numero; resultado = Fator(numero, quadrado, cubico); 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. } 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. } 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.

147 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 147 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 Passando ponteiros para objetos #include <iostream> using namespace std; 6. class Gato 7. { 8. public: 9. Gato (); // construtor 10. Gato(Gato&); // construtor de cópia 11. ~Gato(); // destrutor 12. }; Gato::Gato() 15. { 16. cout << "Gato Construtor..." << endl; 17. } Gato::Gato(Gato&) 20. { 21. cout << "Gato Construtor de copia..." << endl; 22. } Gato::~Gato() 25. { 26. cout << "Gato Destrutor..." << endl; 27. } Gato FuncaoUm (Gato umgato); 30. Gato* FuncaoDois (Gato *umgato); int main() 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.

148 148 Pesquisa e Desenvolvimento Tecnológico Saída 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. } // FuncaoUm, passa por valor 44. Gato FuncaoUm(Gato umgato) 45. { 46. cout << "Funcao um. Retornando... " << endl; 47. return umgato; 48. } // FuncaoDois, passa por referência 51. Gato* FuncaoDois (Gato *umgato) 52. { 53. cout << "Funcao dois. Retornando... " << endl; 54. return umgato; 55. } 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.

149 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 149 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 Passando ponteiros para objetos #include <iostream> using namespace std; 6. class Gato 7. { 8. public: 9. Gato(); 10. Gato(Gato&); 11. ~Gato(); int GetIdade() const { return suaidade; } 14. void SetIdade(int idade) { suaidade = idade; } private: 17. int suaidade; 18. }; Gato::Gato() 21. { 22. cout << "Gato Construtor..." << endl; 23. suaidade = 1; 24. } Gato::Gato(Gato&) 27. { 28. cout << "Gato Construtor de copia..." << endl; 29. } Gato::~Gato() 32. { 33. cout << "Gato Destrutor..." << endl; 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.

150 150 Pesquisa e Desenvolvimento Tecnológico Saída 34. } const Gato * const FuncaoDois 37. (const Gato * const gato); 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. } // 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. } 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.

151 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 151 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 Passando referências de objetos #include <iostream> using namespace std; 6. class Gato 7. { 8. public: 9. Gato(); 10. Gato(Gato&); 11. ~Gato(); int GetIdade() const { return suaidade; } 14. void SetIdade(int idade) { suaidade = idade; } private: 17. int suaidade; 18. }; Gato::Gato() 21. { 22. cout << "Gato Construtor..." << endl; 23. suaidade = 1; 24. } Gato::Gato(Gato&) 27. { 28. cout << "Gato Construtor de copia..." << endl; 29. } Gato::~Gato() 32. { 33. cout << "Gato Destrutor..." << endl; 34. } const Gato & FuncaoDois (const Gato & gato); int main() 39. { 40. cout << "Criando um gato..." << endl; 41. Gato Frisky; 42. cout << "Frisky tem " << Frisky.GetIdade() << " anos" << endl; 43. int age = 5; 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.

152 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. } // 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 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 Passe parâmetros por referência sempre que possível. Não faça Não use ponteiros se referências vão funcionar.

153 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 153 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: 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.

154 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. Saída 1. #include <iostream> int& GetInt (); int main() 6. { 7. int & rint = GetInt (); 8. std::cout << "rint = " << rint << std::endl; return 0; 11. } int & GetInt () 14. { 15. int nlocalint = 25; return nlocalint; 18. } 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.

155 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 155 Saída 3. int& GetInt (); int main() 6. { 7. int & rint = GetInt (); 8. std::cout << "rint = " << rint << std::endl; return 0; 11. } int & GetInt () 14. { 15. // Instancia um objeto inteiro na área livre / heap 16. int* pinteger = new int (25); return *pinteger; 19. } 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 Passe parâmetros por valor quando necessário. Não faça Não passe por referência se o item referenciado pode ficar fora do escopo. Retorne por valor quando necessário. Não perca o controle de quando e onde a memória é alocada, assim você se certifica que ela é liberada 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.

156 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.

157 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens Escreva um programa que produza um vazamento de memória. 7 - Corrija o programa do exercício 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. }; Gato & CriaGato(int idade); 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. } Gato & CriaGato(int idade) 25. { 26. Gato * pgato = new Gato(idade); 27. return *pgato; 28. } 9 - Corrija o programa do exercício 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.

158 158 Pesquisa e Desenvolvimento Tecnológico Apêndice Aplicando o Conhecimento Apêndice I Resolva os Problemas

159 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 159 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 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 = Com esse resultado, podemos somar os dígitos de 10! como = 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 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.

160 160 Pesquisa e Desenvolvimento Tecnológico 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 = Qual é o total da soma de todas as pontuações de todos os nomes?

161 Pesquisa e Desenvolvimento Tecnológico - Painel de Linguagens 161 Por que C++? Ora, por que não? 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. Equipe Editora Traduzido por Luiz Kohl Revisado por Claudio M. Souza Junior 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.

Componentes da linguagem C++

Componentes da linguagem C++ Componentes da linguagem C++ C++ é uma linguagem de programação orientada a objetos (OO) que oferece suporte às características OO, além de permitir você realizar outras tarefas, similarmente a outras

Leia mais

Introdução à Programação

Introdução à Programação Introdução à Programação Introdução a Linguagem C Construções Básicas Programa em C #include int main ( ) { Palavras Reservadas } float celsius ; float farenheit ; celsius = 30; farenheit = 9.0/5

Leia mais

Orientação a Objetos

Orientação a Objetos 1. Domínio e Aplicação Orientação a Objetos Um domínio é composto pelas entidades, informações e processos relacionados a um determinado contexto. Uma aplicação pode ser desenvolvida para automatizar ou

Leia mais

CURSO DE PROGRAMAÇÃO EM JAVA

CURSO DE PROGRAMAÇÃO EM JAVA CURSO DE PROGRAMAÇÃO EM JAVA Introdução para Iniciantes Prof. M.Sc. Daniel Calife Índice 1 - A programação e a Linguagem Java. 1.1 1.2 1.3 1.4 Linguagens de Programação Java JDK IDE 2 - Criando o primeiro

Leia mais

INTRODUÇÃO À PROGRAMAÇÃO BCC 201 TURMAS 31, 32 E 33 2015-2 AULA TEÓRICA 2 PROF. MARCELO LUIZ SILVA (R E D)

INTRODUÇÃO À PROGRAMAÇÃO BCC 201 TURMAS 31, 32 E 33 2015-2 AULA TEÓRICA 2 PROF. MARCELO LUIZ SILVA (R E D) Universidade Federal de Ouro Preto - UFOP Instituto de Ciências Exatas e Biológicas - ICEB Departamento de Computação - DECOM INTRODUÇÃO À PROGRAMAÇÃO BCC 201 TURMAS 31, 32 E 33 2015-2 1 AULA TEÓRICA 2

Leia mais

Algoritmos e Programação Estruturada

Algoritmos e Programação Estruturada Algoritmos e Programação Estruturada Virgínia M. Cardoso Linguagem C Criada por Dennis M. Ritchie e Ken Thompson no Laboratório Bell em 1972. A Linguagem C foi baseada na Linguagem B criada por Thompson.

Leia mais

Criando um script simples

Criando um script simples Criando um script simples As ferramentas de script Diferente de muitas linguagens de programação, você não precisará de quaisquer softwares especiais para criar scripts de JavaScript. A primeira coisa

Leia mais

LINGUAGEM C UMA INTRODUÇÃO

LINGUAGEM C UMA INTRODUÇÃO LINGUAGEM C UMA INTRODUÇÃO AULA 1 Conceitos muito básicos 1 Introdução O C nasceu na década de 70. Seu inventor, Dennis Ritchie, implementou-o pela primeira vez usando um DEC PDP-11 rodando o sistema operacional

Leia mais

Fluxo de trabalho do Capture Pro Software: Indexação de código de barras e separação de documentos

Fluxo de trabalho do Capture Pro Software: Indexação de código de barras e separação de documentos Este procedimento corresponde ao fluxo de trabalho de Indexação de código de barras e de separação de documentos no programa de treinamento do Capture Pro Software. As etapas do procedimento encontram-se

Leia mais

Iniciação à Informática

Iniciação à Informática Meu computador e Windows Explorer Justificativa Toda informação ou dado trabalhado no computador, quando armazenado em uma unidade de disco, transforma-se em um arquivo. Saber manipular os arquivos através

Leia mais

Fluxo de trabalho do Capture Pro Software: Indexação de OCR e separação de documentos de código de correção

Fluxo de trabalho do Capture Pro Software: Indexação de OCR e separação de documentos de código de correção Este procedimento corresponde ao fluxo de trabalho de Indexação de OCR com separação de código de correção no programa de treinamento do Capture Pro Software. As etapas do procedimento encontram-se na

Leia mais

Guia e Utilização do Visual Studio 6.0

Guia e Utilização do Visual Studio 6.0 Guia e Utilização do Visual Studio 6.0 Objectivo Este guia acompanha-o através do processo de criação de uma aplicação C++ utilizando o ambiente de desenvolvimento Visual Studio 6.0. Passo 1: Criação de

Leia mais

INFORMÁTICA APLICADA AULA 02 LINGUAGEM DE PROGRAMAÇÃO C++

INFORMÁTICA APLICADA AULA 02 LINGUAGEM DE PROGRAMAÇÃO C++ UNIVERSIDADE FEDERAL RURAL DO SEMI-ÁRIDO CURSO: Bacharelado em Ciências e Tecnologia INFORMÁTICA APLICADA AULA 02 LINGUAGEM DE PROGRAMAÇÃO C++ Profª ª Danielle Casillo COMPILADORES Toda linguagem de programação

Leia mais

INTRODUÇÃO ÀS LINGUAGENS DE PROGRAMAÇÃO

INTRODUÇÃO ÀS LINGUAGENS DE PROGRAMAÇÃO Capítulo 1 INTRODUÇÃO ÀS LINGUAGENS DE PROGRAMAÇÃO 1.1 Histórico de Linguagens de Programação Para um computador executar uma dada tarefa é necessário que se informe a ele, de uma maneira clara, como ele

Leia mais

Nota de Aula: Utilização da IDE Code::Blocks

Nota de Aula: Utilização da IDE Code::Blocks INSTITUTO FEDERAL DE EDUCAÇÃO, CIÊNCIA E TECNOLOGIA DO MARANHÃO CAMPUS SÃO LUÍS MONTE CASTELO DEPARTAMENTO ACADÊMICO DE INFORMÁTICA SISTEMAS DE INFORMAÇÃO DISCIPLINA: LINGUAGEM DE PROGRAMAÇÃO I PROFESSOR:

Leia mais

Na disciplina de Cálculo Numérico, vamos trabalhar com a linguagem C++ e o compilador que vamos usar é o Dev C++.

Na disciplina de Cálculo Numérico, vamos trabalhar com a linguagem C++ e o compilador que vamos usar é o Dev C++. Data: 14/8 Página 1 de 9 Primeiros passos Introdução Na disciplina de Cálculo Numérico, vamos trabalhar com a linguagem C++ e o compilador que vamos usar é o Dev C++. No tutorial anterior, mostramos como

Leia mais

LP II Estrutura de Dados. Introdução e Linguagem C. Prof. José Honorato F. Nunes honorato.nunes@ifbaiano.bonfim.edu.br

LP II Estrutura de Dados. Introdução e Linguagem C. Prof. José Honorato F. Nunes honorato.nunes@ifbaiano.bonfim.edu.br LP II Estrutura de Dados Introdução e Linguagem C Prof. José Honorato F. Nunes honorato.nunes@ifbaiano.bonfim.edu.br Resumo da aula Considerações Gerais Introdução a Linguagem C Variáveis e C Tipos de

Leia mais

Conceitos básicos da linguagem C

Conceitos básicos da linguagem C Conceitos básicos da linguagem C 2 Em 1969 Ken Thompson cria o Unix. O C nasceu logo depois, na década de 70. Dennis Ritchie, implementou-o pela primeira vez usando o sistema operacional UNIX criado por

Leia mais

Lazarus pelo SVN Linux/Windows

Lazarus pelo SVN Linux/Windows Lazarus pelo SVN Linux/Windows Sei que não faltam artigos sobre como obter e compilar o Lazarus e o FPC pelo SVN, mas sei também que nunca é de mais divulgar um pouco mais e talvez escrever algo diferente.

Leia mais

O Primeiro Programa em Visual Studio.net

O Primeiro Programa em Visual Studio.net O Primeiro Programa em Visual Studio.net Já examinamos o primeiro programa escrito em C que servirá de ponto de partida para todos os demais exemplos e exercícios do curso. Agora, aprenderemos como utilizar

Leia mais

2. OPERADORES... 6 3. ALGORITMOS, FLUXOGRAMAS E PROGRAMAS... 8 4. FUNÇÕES... 10

2. OPERADORES... 6 3. ALGORITMOS, FLUXOGRAMAS E PROGRAMAS... 8 4. FUNÇÕES... 10 1. TIPOS DE DADOS... 3 1.1 DEFINIÇÃO DE DADOS... 3 1.2 - DEFINIÇÃO DE VARIÁVEIS... 3 1.3 - VARIÁVEIS EM C... 3 1.3.1. NOME DAS VARIÁVEIS... 3 1.3.2 - TIPOS BÁSICOS... 3 1.3.3 DECLARAÇÃO DE VARIÁVEIS...

Leia mais

Algoritmos e Programação (Prática) Profa. Andreza Leite andreza.leite@univasf.edu.br

Algoritmos e Programação (Prática) Profa. Andreza Leite andreza.leite@univasf.edu.br (Prática) Profa. Andreza Leite andreza.leite@univasf.edu.br Introdução O computador como ferramenta indispensável: Faz parte das nossas vidas; Por si só não faz nada de útil; Grande capacidade de resolução

Leia mais

Aula 2. Objetivos Conceitos; Instalação do Text Pad; Entendendo o código java do AloMundo1 Codificação do AloMundo2,AloMundo3 e AloMundo4.

Aula 2. Objetivos Conceitos; Instalação do Text Pad; Entendendo o código java do AloMundo1 Codificação do AloMundo2,AloMundo3 e AloMundo4. Aula 2 Objetivos Conceitos; Instalação do Text Pad; Entendendo o código java do AloMundo1 Codificação do AloMundo2,AloMundo3 e AloMundo4. Conceitos O software controla os computadores(freqüentemente conhecido

Leia mais

Barra de ferramentas padrão. Barra de formatação. Barra de desenho Painel de Tarefas

Barra de ferramentas padrão. Barra de formatação. Barra de desenho Painel de Tarefas Microsoft Power Point 2003 No Microsoft PowerPoint 2003, você cria sua apresentação usando apenas um arquivo, ele contém tudo o que você precisa uma estrutura para sua apresentação, os slides, o material

Leia mais

TUTORIAL DO ACCESS PASSO A PASSO. I. Criar um Novo Banco de Dados. Passos: 1. Abrir o Access 2. Clicar em Criar um novo arquivo

TUTORIAL DO ACCESS PASSO A PASSO. I. Criar um Novo Banco de Dados. Passos: 1. Abrir o Access 2. Clicar em Criar um novo arquivo TUTORIAL DO ACCESS PASSO A PASSO I. Criar um Novo Banco de Dados Passos: 1. Abrir o Access 2. Clicar em Criar um novo arquivo 3. Clicar em Banco de Dados em Branco 4. Escrever um nome na caixa de diálogo

Leia mais

Banco de Dados Microsoft Access: Criar tabelas

Banco de Dados Microsoft Access: Criar tabelas Banco de Dados Microsoft Access: Criar s Vitor Valerio de Souza Campos Objetivos do curso 1. Criar uma no modo de exibição Folha de Dados. 2. Definir tipos de dados para os campos na. 3. Criar uma no modo

Leia mais

Banco de Dados Microsoft Access: Criar tabelas. Vitor Valerio de Souza Campos

Banco de Dados Microsoft Access: Criar tabelas. Vitor Valerio de Souza Campos Banco de Dados Microsoft Access: Criar tabelas Vitor Valerio de Souza Campos Objetivos do curso 1. Criar uma tabela no modo de exibição Folha de Dados. 2. Definir tipos de dados para os campos na tabela.

Leia mais

Lição 1 - Criação de campos calculados em consultas

Lição 1 - Criação de campos calculados em consultas 1 de 5 21-08-2011 22:15 Lição 1 - Criação de campos calculados em consultas Adição de Colunas com Valores Calculados: Vamos, inicialmente, relembrar, rapidamente alguns conceitos básicos sobre Consultas

Leia mais

3. No painel da direita, dê um clique com o botão direito do mouse em qualquer espaço livre (área em branco).

3. No painel da direita, dê um clique com o botão direito do mouse em qualquer espaço livre (área em branco). Permissões de compartilhamento e NTFS - Parte 2 Criando e compartilhando uma pasta - Prática Autor: Júlio Battisti - Site: www.juliobattisti.com.br Neste tópico vamos criar e compartilhar uma pasta chamada

Leia mais

INTRODUÇÃO AO WINDOWS

INTRODUÇÃO AO WINDOWS INTRODUÇÃO AO WINDOWS Paulo José De Fazzio Júnior 1 Noções de Windows INICIANDO O WINDOWS...3 ÍCONES...4 BARRA DE TAREFAS...5 BOTÃO...5 ÁREA DE NOTIFICAÇÃO...5 BOTÃO INICIAR...6 INICIANDO PROGRAMAS...7

Leia mais

Operador de Computador. Informática Básica

Operador de Computador. Informática Básica Operador de Computador Informática Básica Instalação de Software e Periféricos Podemos ter diversos tipos de software que nos auxiliam no desenvolvimento das nossas tarefas diárias, seja ela em casa, no

Leia mais

Prof. Esp. Adriano Carvalho

Prof. Esp. Adriano Carvalho Prof. Esp. Adriano Carvalho O que é um Programa? Um arquivo contendo uma sequência de comandos em uma linguagem de programação especifica Esses comandosrespeitam regras de como serem escritos e quais

Leia mais

Aula 1. // exemplo1.cpp /* Incluímos a biblioteca C++ padrão de entrada e saída */ #include <iostream>

Aula 1. // exemplo1.cpp /* Incluímos a biblioteca C++ padrão de entrada e saída */ #include <iostream> Aula 1 C é uma linguagem de programação estruturada desenvolvida por Dennis Ritchie nos laboratórios Bell entre 1969 e 1972; Algumas características: É case-sensitive, ou seja, o compilador difere letras

Leia mais

SUMÁRIO 1. AULA 6 ENDEREÇAMENTO IP:... 2

SUMÁRIO 1. AULA 6 ENDEREÇAMENTO IP:... 2 SUMÁRIO 1. AULA 6 ENDEREÇAMENTO IP:... 2 1.1 Introdução... 2 1.2 Estrutura do IP... 3 1.3 Tipos de IP... 3 1.4 Classes de IP... 4 1.5 Máscara de Sub-Rede... 6 1.6 Atribuindo um IP ao computador... 7 2

Leia mais

ARRAYS. Um array é um OBJETO que referencia (aponta) mais de um objeto ou armazena mais de um dado primitivo.

ARRAYS. Um array é um OBJETO que referencia (aponta) mais de um objeto ou armazena mais de um dado primitivo. Cursos: Análise, Ciência da Computação e Sistemas de Informação Programação I - Prof. Aníbal Notas de aula 8 ARRAYS Introdução Até agora, utilizamos variáveis individuais. Significa que uma variável objeto

Leia mais

1. Apresentação. 1.1. Objetivos

1. Apresentação. 1.1. Objetivos 1.1. Objetivos 1. Apresentação Neste capítulo estão descritos os objetivos gerais do livro, os requisitos desejáveis do estudante para que possa utilizá-lo eficientemente, e os recursos necessários em

Leia mais

Turma. PowerPoint 2003

Turma. PowerPoint 2003 PowerPoint 2003 Apresentação O Power Point é um aplicativo do Microsoft Office direcionado à criação de apresentações. Com ele você poderá criar rapidamente slides com esquemas, textos animados, sons e

Leia mais

Como acessar o novo webmail da Educação? Manual do Usuário. 15/9/2009 Gerencia de Suporte, Redes e Novas Tecnologias Claudia M.S.

Como acessar o novo webmail da Educação? Manual do Usuário. 15/9/2009 Gerencia de Suporte, Redes e Novas Tecnologias Claudia M.S. Como acessar o novo webmail da Educação? Manual do Usuário 15/9/2009 Gerencia de Suporte, Redes e Novas Tecnologias Claudia M.S. Tomaz IT.002 02 2/14 Como acessar o Webmail da Secretaria de Educação? Para

Leia mais

Agente Administrativo do MTE

Agente Administrativo do MTE PowerPoint 2003 Apresentação O Power Point é um aplicativo do Microsoft Office direcionado à criação de apresentações. Com ele você poderá criar rapidamente slides com esquemas, textos animados, sons e

Leia mais

Noções de. Microsoft SQL Server. Microsoft SQL Server

Noções de. Microsoft SQL Server. Microsoft SQL Server Noções de 1 Considerações Iniciais Basicamente existem dois tipos de usuários do SQL Server: Implementadores Administradores 2 1 Implementadores Utilizam o SQL Server para criar e alterar base de dados

Leia mais

TUTORIAL III: ADICIONANDO AJUDA. Adicionando Ajuda

TUTORIAL III: ADICIONANDO AJUDA. Adicionando Ajuda Adicionando Ajuda Para construir arquivos de ajuda do Windows, é necessário saber quais são os componentes de um arquivo de ajuda. Você tem três arquivos básicos que são parte de cada arquivo de ajuda:

Leia mais

Memória Flash. PdP. Autor: Tiago Lone Nível: Básico Criação: 11/12/2005 Última versão: 18/12/2006. Pesquisa e Desenvolvimento de Produtos

Memória Flash. PdP. Autor: Tiago Lone Nível: Básico Criação: 11/12/2005 Última versão: 18/12/2006. Pesquisa e Desenvolvimento de Produtos TUTORIAL Memória Flash Autor: Tiago Lone Nível: Básico Criação: 11/12/2005 Última versão: 18/12/2006 PdP Pesquisa e Desenvolvimento de Produtos http://www.maxwellbohr.com.br contato@maxwellbohr.com.br

Leia mais

Aula 01 - Formatações prontas e condicionais. Aula 01 - Formatações prontas e condicionais. Sumário. Formatar como Tabela

Aula 01 - Formatações prontas e condicionais. Aula 01 - Formatações prontas e condicionais. Sumário. Formatar como Tabela Aula 01 - Formatações prontas e Sumário Formatar como Tabela Formatar como Tabela (cont.) Alterando as formatações aplicadas e adicionando novos itens Removendo a formatação de tabela aplicada Formatação

Leia mais

Introdução. Capítulo 1. 1.1. Breve sinopse

Introdução. Capítulo 1. 1.1. Breve sinopse Capítulo 1 Introdução 1.1. Breve sinopse O C é uma linguagem de programação criada por Dennis Ritchie no início da década de 70 do século XX. É uma linguagem de complexidade baixa, estruturada, imperativa

Leia mais

Microsoft Access XP Módulo Um

Microsoft Access XP Módulo Um Microsoft Access XP Módulo Um Neste primeiro módulo de aula do curso completo de Access XP vamos nos dedicar ao estudo de alguns termos relacionados com banco de dados e as principais novidades do novo

Leia mais

Desenvolvendo Websites com PHP

Desenvolvendo Websites com PHP Desenvolvendo Websites com PHP Aprenda a criar Websites dinâmicos e interativos com PHP e bancos de dados Juliano Niederauer 19 Capítulo 1 O que é o PHP? O PHP é uma das linguagens mais utilizadas na Web.

Leia mais

atube Catcher versão 3.8 Manual de instalação do software atube Catcher

atube Catcher versão 3.8 Manual de instalação do software atube Catcher atube Catcher versão 3.8 Manual de instalação do software atube Catcher Desenvolvido por: Clarice Mello, Denis Marques Campos Dezembro de 2014 Sumario 1. Objetivo deste manual...3 2. Requisitos para instalação...3

Leia mais

Manual SAGe Versão 1.2 (a partir da versão 12.08.01)

Manual SAGe Versão 1.2 (a partir da versão 12.08.01) Manual SAGe Versão 1.2 (a partir da versão 12.08.01) Submissão de Relatórios Científicos Sumário Introdução... 2 Elaboração do Relatório Científico... 3 Submissão do Relatório Científico... 14 Operação

Leia mais

Microsoft Access: Criar relatórios para um novo banco de dados. Vitor Valerio de Souza Campos

Microsoft Access: Criar relatórios para um novo banco de dados. Vitor Valerio de Souza Campos Microsoft Access: Criar relatórios para um novo banco de dados Vitor Valerio de Souza Campos Conteúdo do curso Visão geral: O produto final Lição: Inclui oito seções Tarefas práticas sugeridas Teste Visão

Leia mais

Lógica de Programação

Lógica de Programação Lógica de Programação Unidade 4 Ambiente de desenvolvimento Java QI ESCOLAS E FACULDADES Curso Técnico em Informática SUMÁRIO A LINGUAGEM JAVA... 3 JVM, JRE, JDK... 3 BYTECODE... 3 PREPARANDO O AMBIENTE

Leia mais

Prática 6 ActionScript

Prática 6 ActionScript Prática 6 ActionScript 1. Objetivos Se familiarizar com o ActionScript. Usar comandos e funções básicas. 2. Recursos Necessários Computador com o programa Macromedia Flash MX ou superior. 3. Conceitos

Leia mais

Disciplina: INF1005 - Programação I. 1 a aula prática Introdução ao ambiente do Microsoft Visual Studio 2010

Disciplina: INF1005 - Programação I. 1 a aula prática Introdução ao ambiente do Microsoft Visual Studio 2010 1 a aula prática Introdução ao ambiente do Microsoft Visual Studio 2010 1. Execute o MS-Visual Studio 2010. Experimente o caminho: Start All Programs Microsoft Visual Studio 2010 Microsoft Visual Studio

Leia mais

20 Caracteres - Tipo char

20 Caracteres - Tipo char 0 Caracteres - Tipo char Ronaldo F. Hashimoto e Carlos H. Morimoto Até agora vimos como o computador pode ser utilizado para processar informação que pode ser quantificada de forma numérica. No entanto,

Leia mais

Google Drive: Acesse e organize seus arquivos

Google Drive: Acesse e organize seus arquivos Google Drive: Acesse e organize seus arquivos Use o Google Drive para armazenar e acessar arquivos, pastas e documentos do Google Docs onde quer que você esteja. Quando você altera um arquivo na web, no

Leia mais

Linguagem e Técnicas de Programação I Programação estruturada e fundamentos da linguagem C

Linguagem e Técnicas de Programação I Programação estruturada e fundamentos da linguagem C Linguagem e Técnicas de Programação I Programação estruturada e fundamentos da linguagem C Prof. MSc. Hugo Souza Material desenvolvido por: Profa. Ameliara Freire Continuando as aulas sobre os fundamentos

Leia mais

Introdução à Lógica de Programação

Introdução à Lógica de Programação Introdução à Lógica de Programação Sistemas Numéricos As informações inseridas em um computador são traduzidos em dados, ou seja, em sinais que podem ser manipulados pelo computador. O computador trabalha

Leia mais

Eclipse com c++11 e boost Etapa 1- Download da IDE Eclipse c++ e configuração do MinGW

Eclipse com c++11 e boost Etapa 1- Download da IDE Eclipse c++ e configuração do MinGW Eclipse com c++11 e boost Etapa 1- Download da IDE Eclipse c++ e configuração do MinGW Primeiro passo: download Primeiramente devemos baixar o eclipse para c++, sugiro a ultima versão o Mars M4 https://eclipse.org/downloads/packages/release/mars/m4

Leia mais

Framework.NET, Microsoft Visual C# 2010 Express e Elementos da Linguagem C#

Framework.NET, Microsoft Visual C# 2010 Express e Elementos da Linguagem C# Linguagem de Programação 3 Framework.NET, Microsoft Visual C# 2010 Express e Elementos da Linguagem C# Prof. Mauro Lopes 1-31 35 Objetivos Nesta aula iremos apresentar a tecnologia.net, o ambiente de desenvolvimento

Leia mais

AMBIENTE. FORMULÁRIO: é a janela do aplicativo apresentada ao usuário. Considere o formulário como a sua prancheta de trabalho.

AMBIENTE. FORMULÁRIO: é a janela do aplicativo apresentada ao usuário. Considere o formulário como a sua prancheta de trabalho. DELPHI BÁSICO VANTAGENS Ambiente de desenvolvimento fácil de usar; 1. Grande Biblioteca de Componentes Visuais (VCL - Visual Component Library), que são botões, campos, gráficos, caixas de diálogo e acesso

Leia mais

Capacidade = 512 x 300 x 20000 x 2 x 5 = 30.720.000.000 30,72 GB

Capacidade = 512 x 300 x 20000 x 2 x 5 = 30.720.000.000 30,72 GB Calculando a capacidade de disco: Capacidade = (# bytes/setor) x (méd. # setores/trilha) x (# trilhas/superfície) x (# superfícies/prato) x (# pratos/disco) Exemplo 01: 512 bytes/setor 300 setores/trilha

Leia mais

Introdução a Java. Hélder Nunes

Introdução a Java. Hélder Nunes Introdução a Java Hélder Nunes 2 Exercício de Fixação Os 4 elementos básicos da OO são os objetos, as classes, os atributos e os métodos. A orientação a objetos consiste em considerar os sistemas computacionais

Leia mais

MANUAL DE FTP. Instalando, Configurando e Utilizando FTP

MANUAL DE FTP. Instalando, Configurando e Utilizando FTP MANUAL DE FTP Instalando, Configurando e Utilizando FTP Este manual destina-se auxiliar os clientes e fornecedores da Log&Print na instalação, configuração e utilização de FTP O que é FTP? E o que é um

Leia mais

PROGRAMAÇÃO ESTRUTURADA. CC 2º Período

PROGRAMAÇÃO ESTRUTURADA. CC 2º Período PROGRAMAÇÃO ESTRUTURADA CC 2º Período PROGRAMAÇÃO ESTRUTURADA Aula 07: Funções O comando return Protótipo de funções O tipo void Arquivos-cabeçalho Escopo de variáveis Passagem de parâmetros por valor

Leia mais

ÍNDICE... 2 INTRODUÇÃO... 4

ÍNDICE... 2 INTRODUÇÃO... 4 Mic crosoft Excel 201 0 ÍNDICE ÍNDICE... 2 INTRODUÇÃO... 4 Interface... 4 Guias de Planilha... 5 Movimentação na planilha... 6 Entrada de textos e números... 7 Congelando painéis... 8 Comentários nas Células...

Leia mais

Gerenciamento de Arquivos e Pastas. Professor: Jeferson Machado Cordini jmcordini@hotmail.com

Gerenciamento de Arquivos e Pastas. Professor: Jeferson Machado Cordini jmcordini@hotmail.com Gerenciamento de Arquivos e Pastas Professor: Jeferson Machado Cordini jmcordini@hotmail.com Arquivo Todo e qualquer software ou informação gravada em nosso computador será guardada em uma unidade de disco,

Leia mais

1 Code::Blocks Criação de projetos

1 Code::Blocks Criação de projetos Programação MEEC Índice 1Code::Blocks Criação de projetos...1 2Code::Blocks Localização do projeto...5 3Code::Blocks Abertura de projetos já existentes...7 4Code::Blocks Funcionamento...8 5Code::Blocks

Leia mais

ArpPrintServer. Sistema de Gerenciamento de Impressão By Netsource www.netsource.com.br Rev: 02

ArpPrintServer. Sistema de Gerenciamento de Impressão By Netsource www.netsource.com.br Rev: 02 ArpPrintServer Sistema de Gerenciamento de Impressão By Netsource www.netsource.com.br Rev: 02 1 Sumário INTRODUÇÃO... 3 CARACTERÍSTICAS PRINCIPAIS DO SISTEMA... 3 REQUISITOS DE SISTEMA... 4 INSTALAÇÃO

Leia mais

Organização de programas em Python. Vanessa Braganholo vanessa@ic.uff.br

Organização de programas em Python. Vanessa Braganholo vanessa@ic.uff.br Organização de programas em Python Vanessa Braganholo vanessa@ic.uff.br Vamos programar em Python! Mas... } Como um programa é organizado? } Quais são os tipos de dados disponíveis? } Como variáveis podem

Leia mais

NetBeans. Conhecendo um pouco da IDE

NetBeans. Conhecendo um pouco da IDE NetBeans Conhecendo um pouco da IDE Professor: Edwar Saliba Júnior Sumário Apresentação:...1 Criando Um Novo Projeto de Software:...1 Depurando Um Código-fonte:...4 Entendendo o Código-fonte:...7 Dica

Leia mais

Dicas para usar melhor o Word 2007

Dicas para usar melhor o Word 2007 Dicas para usar melhor o Word 2007 Quem está acostumado (ou não) a trabalhar com o Word, não costuma ter todo o tempo do mundo disponível para descobrir as funcionalidades de versões recentemente lançadas.

Leia mais

MDaemon GroupWare. Versão 1 Manual do Usuário. plugin para o Microsoft Outlook. Trabalhe em Equipe Usando o Outlook e o MDaemon

MDaemon GroupWare. Versão 1 Manual do Usuário. plugin para o Microsoft Outlook. Trabalhe em Equipe Usando o Outlook e o MDaemon MDaemon GroupWare plugin para o Microsoft Outlook Trabalhe em Equipe Usando o Outlook e o MDaemon Versão 1 Manual do Usuário MDaemon GroupWare Plugin for Microsoft Outlook Conteúdo 2003 Alt-N Technologies.

Leia mais

MANUAL C R M ÍNDICE. Sobre o módulo de CRM... 2. 1 Definindo a Campanha... 3

MANUAL C R M ÍNDICE. Sobre o módulo de CRM... 2. 1 Definindo a Campanha... 3 ÍNDICE Sobre o módulo de CRM... 2 1 Definindo a Campanha... 3 1.1 Incluir uma campanha... 3 1.2 Alterar uma campanha... 4 1.3 Excluir... 4 1.4 Procurar... 4 2 Definindo os clientes para a campanha... 4

Leia mais

Resumo da Matéria de Linguagem de Programação. Linguagem C

Resumo da Matéria de Linguagem de Programação. Linguagem C Resumo da Matéria de Linguagem de Programação Linguagem C Vitor H. Migoto de Gouvêa 2011 Sumário Como instalar um programa para executar o C...3 Sintaxe inicial da Linguagem de Programação C...4 Variáveis

Leia mais

SISTEMAS OPERACIONAIS ABERTOS Prof. Ricardo Rodrigues Barcelar http://www.ricardobarcelar.com

SISTEMAS OPERACIONAIS ABERTOS Prof. Ricardo Rodrigues Barcelar http://www.ricardobarcelar.com - Aula 2-1. PRINCÍPIOS DE SOFTWARE DE ENTRADA E SAÍDA (E/S) As metas gerais do software de entrada e saída é organizar o software como uma série de camadas, com as mais baixas preocupadas em esconder as

Leia mais

CONVENÇÃO DE CÓDIGO JAVA

CONVENÇÃO DE CÓDIGO JAVA CONVENÇÃO DE CÓDIGO JAVA Eligiane Ceron - Abril de 2012 Versão 1.0 Conteúdo Considerações iniciais... 2 Introdução... 2 Extensão de arquivos... 2 Arquivos de código Java... 2 Comentários iniciais... 2

Leia mais

APOSTILA DE EXEMPLO. (Esta é só uma reprodução parcial do conteúdo)

APOSTILA DE EXEMPLO. (Esta é só uma reprodução parcial do conteúdo) APOSTILA DE EXEMPLO (Esta é só uma reprodução parcial do conteúdo) 1 Índice Aula 1 - Área de trabalho e personalizando o sistema... 3 A área de trabalho... 3 Partes da área de trabalho.... 4 O Menu Iniciar:...

Leia mais

CAPÍTULO 7 NÍVEL DE LINGUAGEM DE MONTAGEM

CAPÍTULO 7 NÍVEL DE LINGUAGEM DE MONTAGEM CAPÍTULO 7 NÍVEL DE LINGUAGEM DE MONTAGEM 71 Introdução Difere dos níveis inferiores por ser implementado por tradução A tradução é usada quando um processador está disponível para uma mensagem fonte mas

Leia mais

Usando o simulador MIPS

Usando o simulador MIPS Usando o simulador MIPS O objetivo desta aula prática será a utilização do simulador MipsIt para executar programas escritos em linguagem de máquina do MIPS. 1 Criando um projeto Cada programa a ser executado

Leia mais

Tabela de Símbolos. Análise Semântica A Tabela de Símbolos. Principais Operações. Estrutura da Tabela de Símbolos. Declarações 11/6/2008

Tabela de Símbolos. Análise Semântica A Tabela de Símbolos. Principais Operações. Estrutura da Tabela de Símbolos. Declarações 11/6/2008 Tabela de Símbolos Análise Semântica A Tabela de Símbolos Fabiano Baldo Após a árvore de derivação, a tabela de símbolos é o principal atributo herdado em um compilador. É possível, mas não necessário,

Leia mais

2 de maio de 2014. Remote Scan

2 de maio de 2014. Remote Scan 2 de maio de 2014 Remote Scan 2014 Electronics For Imaging. As informações nesta publicação estão cobertas pelos termos dos Avisos de caráter legal deste produto. Conteúdo 3 Conteúdo...5 Acesso ao...5

Leia mais

Aspectos técnicos do desenvolvimento baseado em componentes

Aspectos técnicos do desenvolvimento baseado em componentes Aspectos técnicos do desenvolvimento baseado em componentes Um novo processo de desenvolvimento O uso de componentes traz mudanças no processo de desenvolvimento Além de desenvolver um produto, queremos

Leia mais

Dadas a base e a altura de um triangulo, determinar sua área.

Dadas a base e a altura de um triangulo, determinar sua área. Disciplina Lógica de Programação Visual Ana Rita Dutra dos Santos Especialista em Novas Tecnologias aplicadas a Educação Mestranda em Informática aplicada a Educação ana.santos@qi.edu.br Conceitos Preliminares

Leia mais

AULA 06 CRIAÇÃO DE USUÁRIOS

AULA 06 CRIAÇÃO DE USUÁRIOS AULA 06 CRIAÇÃO DE USUÁRIOS O Windows XP fornece contas de usuários de grupos (das quais os usuários podem ser membros). As contas de usuários são projetadas para indivíduos. As contas de grupos são projetadas

Leia mais

Novell. Novell Teaming 1.0. novdocx (pt-br) 6 April 2007 EXPLORAR O PORTLET BEM-VINDO DESCUBRA SEU CAMINHO USANDO O NOVELL TEAMING NAVIGATOR

Novell. Novell Teaming 1.0. novdocx (pt-br) 6 April 2007 EXPLORAR O PORTLET BEM-VINDO DESCUBRA SEU CAMINHO USANDO O NOVELL TEAMING NAVIGATOR Novell Teaming - Guia de início rápido Novell Teaming 1.0 Julho de 2007 INTRODUÇÃO RÁPIDA www.novell.com Novell Teaming O termo Novell Teaming neste documento se aplica a todas as versões do Novell Teaming,

Leia mais

15. OLHA QUEM ESTÁ NA WEB!

15. OLHA QUEM ESTÁ NA WEB! 7 a e 8 a SÉRIES / ENSINO MÉDIO 15. OLHA QUEM ESTÁ NA WEB! Sua home page para publicar na Internet SOFTWARES NECESSÁRIOS: MICROSOFT WORD 2000 MICROSOFT PUBLISHER 2000 SOFTWARE OPCIONAL: INTERNET EXPLORER

Leia mais

Esta dissertação apresentou duas abordagens para integração entre a linguagem Lua e o Common Language Runtime. O objetivo principal da integração foi

Esta dissertação apresentou duas abordagens para integração entre a linguagem Lua e o Common Language Runtime. O objetivo principal da integração foi 5 Conclusão Esta dissertação apresentou duas abordagens para integração entre a linguagem Lua e o Common Language Runtime. O objetivo principal da integração foi permitir que scripts Lua instanciem e usem

Leia mais

AULA 5 Sistemas Operacionais

AULA 5 Sistemas Operacionais AULA 5 Sistemas Operacionais Disciplina: Introdução à Informática Professora: Gustavo Leitão Email: gustavo.leitao@ifrn.edu.br Sistemas Operacionais Conteúdo: Partições Formatação Fragmentação Gerenciamento

Leia mais

Montar planilhas de uma forma organizada e clara.

Montar planilhas de uma forma organizada e clara. 1 Treinamento do Office 2007 EXCEL Objetivos Após concluir este curso você poderá: Montar planilhas de uma forma organizada e clara. Layout da planilha Inserir gráficos Realizar operações matemáticas 2

Leia mais

Sistemas Operacionais. Curso Técnico Integrado Profa: Michelle Nery

Sistemas Operacionais. Curso Técnico Integrado Profa: Michelle Nery Sistemas Operacionais Curso Técnico Integrado Profa: Michelle Nery Conteúdo Programático Virtual Box Instalação do Virtual Box Instalação do Extension Pack Criando uma Máquina Virtual Instalando o Windows

Leia mais

15/8/2007 Gerencia de Tecnologia da Informação Claudia M.S. Tomaz

15/8/2007 Gerencia de Tecnologia da Informação Claudia M.S. Tomaz 15/8/2007 Gerencia de Tecnologia da Informação Claudia M.S. Tomaz MANUAL DE UTILIZAÇÃO DO WEBMAIL GETEC 01 2/13 Como acessar o Webmail da Secretaria de Educação? Para utilizar o Webmail da Secretaria de

Leia mais

Computadores XXI: Busca e execução Final

Computadores XXI: Busca e execução Final Computadores XXI: Busca e execução Final A6 Texto 6 http://www.bpiropo.com.br/fpc20060123.htm Sítio Fórum PCs /Colunas Coluna: B. Piropo Publicada em 23/01/2006 Autor: B.Piropo Na coluna anterior, < http://www.forumpcs.com.br/viewtopic.php?t=146019

Leia mais

Curso de Programação Computadores

Curso de Programação Computadores 3 O Primeiro Programa em C Unesp Campus de Guaratinguetá Curso de Programação Computadores Prof. Aníbal Tavares Profa. Cassilda Ribeiro 3 O Primeiro Programa em C 3.1 - Introdução Depois dos conceitos

Leia mais

Pesquisa e organização de informação

Pesquisa e organização de informação Pesquisa e organização de informação Capítulo 3 A capacidade e a variedade de dispositivos de armazenamento que qualquer computador atual possui, tornam a pesquisa de informação um desafio cada vez maior

Leia mais

Programação científica C++

Programação científica C++ Programação científica C++ NIELSEN CASTELO DAMASCENO Slide 1 Linguagens de Programação Uma linguagem de programação é um método padronizado para expressar instruções para um computador. É um conjunto

Leia mais

INTRODUÇÃO AO C++ SISTEMAS DE INFORMAÇÃO DR. EDNALDO B. PIZZOLATO

INTRODUÇÃO AO C++ SISTEMAS DE INFORMAÇÃO DR. EDNALDO B. PIZZOLATO INTRODUÇÃO AO C++ SISTEMAS DE INFORMAÇÃO DR. EDNALDO B. PIZZOLATO Tópicos Estrutura Básica B de Programas C e C++ Tipos de Dados Variáveis Strings Entrada e Saída de Dados no C e C++ INTRODUÇÃO O C++ aceita

Leia mais

Programação Estruturada e Orientada a Objetos. Fundamentos Orientação a Objetos

Programação Estruturada e Orientada a Objetos. Fundamentos Orientação a Objetos Programação Estruturada e Orientada a Objetos Fundamentos Orientação a Objetos 2013 O que veremos hoje? Introdução aos fundamentos de Orientação a Objetos Transparências baseadas no material do Prof. Jailton

Leia mais

Apostilas OBJETIVA Escrevente Técnico Judiciário TJ Tribunal de Justiça do Estado de São Paulo - Concurso Público 2015. Índice

Apostilas OBJETIVA Escrevente Técnico Judiciário TJ Tribunal de Justiça do Estado de São Paulo - Concurso Público 2015. Índice Índice Caderno 2 PG. MS-Excel 2010: estrutura básica das planilhas, conceitos de células, linhas, colunas, pastas e gráficos, elaboração de tabelas e gráficos, uso de fórmulas, funções e macros, impressão,

Leia mais

LIÇÃO 1 - USANDO O GRAVADOR DE MACROS

LIÇÃO 1 - USANDO O GRAVADOR DE MACROS 1_15 - ADS - PRO MICRO (ILM 001) - Estudo dirigido Macros Gravadas Word 1/35 LIÇÃO 1 - USANDO O GRAVADOR DE MACROS No Microsoft Office Word 2007 é possível automatizar tarefas usadas frequentemente criando

Leia mais