apostila de programação i

140
APOSTILA DE PROGRAMAÇÃO I PARA ENGENHARIA DE PRODUÇÃO Prof. ANDRÉ CARLOS SILVA FASAR 2005 Versão 1.1

Upload: marcos-silva

Post on 14-Aug-2015

66 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Apostila de Programação I

APOSTILA DE PROGRAMAÇÃO I PARA ENGENHARIA DE PRODUÇÃO

Prof. ANDRÉ CARLOS SILVA

FASAR 2005

Versão 1.1

Page 2: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 3

SUMÁRIO 1 – INTRODUÇÃO AO C++............................................................................................. 11

1.1. Aspectos gerais da linguagem C++ ........................................................................... 11 1.1.1. Comentários........................................................................................................ 12 1.1.2. Identificadores .................................................................................................... 12 1.1.3. Case Sensitive..................................................................................................... 13 1.1.4. Palavras-Chave ................................................................................................... 13 1.1.5. Pontuação ........................................................................................................... 13 1.1.6. Separadores......................................................................................................... 13 1.1.7. Arquivos de cabeçalho........................................................................................ 14

1.2. Variáveis.................................................................................................................... 15 1.2.1. Inicializando variáveis com definições............................................................... 15 1.2.2. Escopo de uma variável...................................................................................... 16 1.2.3. Inicializando variáveis globais e locais .............................................................. 17

1.3. Entrada e saída de dados............................................................................................ 18 1.3.1. Fluxos de saída de dados .................................................................................... 18 1.3.2. Saída de dados no estilo C.................................................................................. 19 1.3.3. Saída de dados formatada................................................................................... 19 1.3.4. Fluxos de entrada de dados................................................................................. 20

1.4. Constantes.................................................................................................................. 21 1.4.1. Constantes literais............................................................................................... 22 1.4.2. Constantes definidas ........................................................................................... 24 1.4.3. Constantes declaradas......................................................................................... 25 1.4.4. Constantes enumeradas ...................................................................................... 27 1.4.5. Atribuição de valores a elementos enumerados ................................................. 29

1.5. Operadores................................................................................................................. 29 1.5.1. Operadores e precedência................................................................................... 30 1.5.2. Operadores de incremento e decremento............................................................ 30 1.5.3. Expressões .......................................................................................................... 32

1.6. Exercícios do capítulo ............................................................................................... 34 2 – INSTRUÇÕES E OPERADORES AVANÇADOS ................................................... 36

2.1. Operadores avançados ............................................................................................... 36 2.1.1. Entendendo os operadores relacionais................................................................ 36 2.1.2. O comando If, Else If e Else ............................................................................... 38 2.1.3. Entendendo os operadores lógicos ..................................................................... 39 2.1.4. Avaliando o resultado de operadores.................................................................. 41 2.1.5. Entendendo o operador condicional ternário...................................................... 42

2.2. Operadores de Bits .................................................................................................... 43 2.2.1. O operador binário E .......................................................................................... 43 2.2.2. O operador binário OU ....................................................................................... 45 2.2.3. O operador binário OU exclusivo ....................................................................... 47

Page 3: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 4

2.2.4. Aplicações dos operadores binários ................................................................... 48 2.2.5. Operadores de deslocamento de bits .................................................................. 49 2.2.6. O operador binário NOT ..................................................................................... 51 2.2.7. Atribuições combinadas ..................................................................................... 52

2.3. Instruções de fluxo de programa ............................................................................... 53 2.3.1. O comando EXIT ................................................................................................ 53 2.3.2. O comando WHILE ............................................................................................ 55 2.3.3. O comando SWITCH .......................................................................................... 55 2.3.4. O comando DO/WHILE...................................................................................... 58 2.3.5. O comando FOR ................................................................................................. 59 2.3.6. Elementos de um loop FOR múltiplo ................................................................. 60 2.3.7. Loop eterno usando o comando FOR ................................................................. 61 2.3.8. O comando BREAK ............................................................................................ 61 2.3.9. O comando CONTINUE ..................................................................................... 62

2.9. Exercícios do capítulo ............................................................................................... 63 3 – ESTRUTURAS DE DADOS........................................................................................ 64

3.1. Estruturas confiáveis para o armazenamento de dados ............................................. 64 3.2. Estruturas aninhadas .................................................................................................. 70 3.3. As uniões ................................................................................................................... 71 3.4. Vetores....................................................................................................................... 73

3.4.1. Inicializando vetores........................................................................................... 77 3.5. Matrizes ..................................................................................................................... 78 3.6. Strings........................................................................................................................ 81

3.6.1. Inicializando vetores de caracteres ..................................................................... 82 3.7. Vetores e ponteiros .................................................................................................... 82 3.8. Campos de bits .......................................................................................................... 83 3.9. O operador SIZEOF .................................................................................................. 86 3.10. Exercícios do capítulo ............................................................................................. 87

4 – FUNÇÕES: PROGRAMANDO POR PARTES........................................................ 88

4.1. Chamando uma função .............................................................................................. 88 4.2. Funções simples......................................................................................................... 89 4.3. Protótipo de funções .................................................................................................. 90

4.3.1. Protótipo externo e local..................................................................................... 90 4.3.2. Eliminando o protótipo de funções..................................................................... 91

4.4. Tipos de funções........................................................................................................ 92 4.4.1. O comando RETURN ......................................................................................... 92 4.4.2. Limitações do comando RETURN...................................................................... 93

4.5. Definição de funções ................................................................................................. 93 4.5.1. Parâmetros da função.......................................................................................... 93 4.5.2. Passagem de argumentos por valor .................................................................... 93 4.5.3. Funções do tipo VOID ........................................................................................ 94 4.5.4. Funções que não recebem nada e não retornam nada......................................... 95 4.5.5. Passagem de vários argumentos ......................................................................... 96

Page 4: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 5

4.5.6. Escrevendo várias funções em um mesmo programa......................................... 96 4.5.7. Funções usadas como argumento de outras funções .......................................... 97

4.6. O operador unário de referência &............................................................................ 97 4.6.1. Passagem de argumentos por referência............................................................. 98 4.6.2. Referências constantes...................................................................................... 100 4.6.3. Considerações sobre referências....................................................................... 101

4.7. Valores default para os argumentos de uma função ................................................ 101 4.8. Sobrecarga de funções ............................................................................................. 103 4.9. Funções inline.......................................................................................................... 105 4.10 Funções recursivas.................................................................................................. 106

4.10.1. Como trabalha uma função recursiva? ........................................................... 107 4.10.2. O jogo das torres de Hanói ............................................................................. 108

4.11. Classes de armazenamento .................................................................................... 110 4.11.1. A classe de armazenamento auto.................................................................... 110 4.11.2. A classe de armazenamento extern................................................................. 111 4.11.3. A classe de armazenamento static .................................................................. 115 4.11.4. A classe de armazenamento static extern ...................................................... 121 4.11.5. A classe de armazenamento register ............................................................. 122

4.12. O pré-processador C++.......................................................................................... 124 4.12.1. A diretiva #define ........................................................................................... 125 4.12.2. A diretiva #undef ............................................................................................ 130 4.12.3. A diretiva #include ......................................................................................... 131 4.12.4. As diretivas #if, #ifdef, #ifndef, #elif, #else e #endif ...................................... 131 4.12.5. A diretiva #error............................................................................................. 134

4.13. Exercícios do capítulo ........................................................................................... 135 5 – MANIPULAÇÃO DE ARQUIVOS EM DISCO ..................................................... 138 6 – PONTEIROS............................................................................................................... 139 APÊNDICES ..................................................................................................................... 140

A.1. BASES NUMÉRICAS ........................................................................................... 140 A.1.1. A BASE BINÁRIA ......................................................................................... 140 A.1.2. A BASE OCTAL............................................................................................. 141 A.1.3. A BASE HEXADECIMAL............................................................................. 141

Page 5: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 6

ÍNDICE DE FIGURAS Figura 1.1 - Exemplo do escopo de uma variável. .............................................................. 17 Figura 3.1 - Os bytes de cada campo de uma variável struct são armazenados na memória

consecutivamente.......................................................................................................... 66 Figura 3.2 - Os bytes de cada campo de uma variável union são armazenados na mesma

posição da memória. ..................................................................................................... 72 Figura 3.3 - Representação esquemática de um vetor de 100 números inteiros................... 74 Figura 3.4 - Um vetor bidimensional é um vetor de vetores, mas também pode-se encará-lo

como sendo uma matriz. ............................................................................................... 78 Figura 4.1 - Esquema de funcionamento do jogo das torres de Hanói. .............................. 109

Page 6: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 7

ÍNDICE DE TABELAS Tabela 1.1 - Tipos usuais de dados em C++......................................................................... 15 Tabela 1.2 - Códigos de escape utilizados em C++............................................................. 23 Tabela 1.3 - Operadores usados em C++ e sua respectiva precedência. ............................. 30 Tabela 2.1 - Operadores relacionais em C++. ...................................................................... 37 Tabela 2.2 - Modo de avaliação de operandos e resultados produzidos por operadores

aritméticos, relacionais e lógicos.................................................................................. 42 Tabela 2.3 - Operadores de Bits. .......................................................................................... 43 Tabela 2.4 - Possíveis resultados para o operador E binário. ............................................... 43 Tabela 2.5 - Possíveis resultados para o operador OU binário............................................. 45 Tabela 2.6 - Possíveis resultados para o operador OU exclusivo binário............................. 47 Tabela 4.1 - Analogia entre as diretivas de compilação condicional e operador if. ........... 132

Page 7: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 8

ÍNDICE DE CÓDIGOS Código 1.1 - Um programa típico em C++........................................................................... 11 Código 1.2 - Exemplo de cabeçalho..................................................................................... 12 Código 1.3 - Outra forma de se escrever um programa....................................................... 14 Código 1.4 - Exemplos de usos de arquivos de cabeçalho................................................... 14 Código 1.5 - Inicialização de variáveis no momento de sua declaração. ............................. 16 Código 1.6 - Programa que retorna a data e a hora atual...................................................... 18 Código 1.7 - O comando PRINTF. ....................................................................................... 19 Código 1.8 - Exemplo de conversão de formato de dados. .................................................. 19 Código 1.9 - Programa para cálculo da média de três números. .......................................... 20 Código 1.10 - Método para evitar uma entrada incorreta do tipo de dado. .......................... 20 Código 1.11 - Uso de #define para a declaração de constantes. ...................................... 24 Código 1.12 - Uso do comando const para a declaração constantes................................. 25 Código 1.13 - Uso dos comandos #define e #undef. .................................................... 26 Código 1.14 - Uso de constantes enumeradas. ..................................................................... 28 Código 1.15 - Uso de operadores de incremento e decremento. .......................................... 31 Código 1.16 - Programa para cálculo do imposto incidente sobre mercadorias. ................ 32 Código 2.1 - Programa para a conversão de milhas para quilômetros. ................................ 37 Código 2.2 - A estrutura if / else if / else........................................................ 38 Código 2.3 - Programa para averiguação do desempenho de automóveis. .......................... 40 Código 2.4 - Uso do operador condicional ternário. ............................................................ 42 Código 2.5 - Uso do operador E binário............................................................................... 44 Código 2.6 - Uso do operador OU binário. .......................................................................... 46 Código 2.7 - Uso do operador OU exclusivo binário. .......................................................... 47 Código 2.8 - Uso dos operadores de deslocamento de bits. ................................................. 49 Código 2.9 - Uso do operador NOT binário. ........................................................................ 51 Código 2.10 - Uso do comando EXIT................................................................................... 54 Código 2.11 - Uso do camando EXIT (versão simplificada). ............................................... 54 Código 2.12 - Uso do comando WHILE e do comando SWITCH. ....................................... 57 Código 2.13 - Uso do comando DO/WHILE. ....................................................................... 58 Código 2.14 - Uso do comando FOR. .................................................................................. 59 Código 2.15 - Programa para exibição dos caracteres da Tabela ASCII. ............................ 60 Código 2.16 - Uso do comando BREAK............................................................................... 61 Código 2.17 - Uso do comando CONTINUE. ...................................................................... 62 Código 3.1 - Uso do comando STRUCT para os principais tipos de dados em C++. .......... 65 Código 3.2 - Arquivo de cabeçalho (.h) típico de um banco de dados................................. 67 Código 3.3 - Uso do comando STRUCT em banco de dados. .............................................. 68 Código 3.4 - Declaração e inicilaização conjunta de uma variável STRUCT. ..................... 69 Código 3.5 - Uso do comando UNION. ............................................................................... 72 Código 3.6 - Uso de VETORES em C++.............................................................................. 74 Código 3.7 - Programa para cálculo da média de 18 números utilizando vetores. .............. 76 Código 3.8 - Programa para teste da inicialização de vetores. ............................................. 77 Código 3.9 - Exemplo do uso de matrizes em C++.............................................................. 79

Page 8: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 9

Código 3.10 - Exemplo do uso de um CAMPO DE BITS. ................................................... 84 Código 3.11 - Uso do operador SIZEOF. ............................................................................. 86 Código 4.1 - Exemplo do uso de uma FUNÇÃO simples. ................................................... 89 Código 4.2 - Uso de funções de protótipo local. .................................................................. 90 Código 4.3 - Uso de funções sem protótipo. ........................................................................ 91 Código 4.4 - Uso de passagem de argumentos por valor em funções. ................................. 94 Código 4.5 - Uso de funções do tipo VOID.......................................................................... 94 Código 4.6 - Uso de funções que não recebem e nem retornam nada.................................. 95 Código 4.7 - Uso de funções com mais de um argumento. .................................................. 96 Código 4.8 - Uso de funções como argumento de outras funções. ...................................... 97 Código 4.9 - Uso de passagem de argumentos por referência em funções. ......................... 98 Código 4.10 - Uso de passagem de argumentos por referência para ordenar um vetor. ...... 99 Código 4.11 - Uso de passagem de argumentos por referências constantes. ..................... 101 Código 4.12 - Uso de valores default para argumentos de funções. .................................. 102 Código 4.13 - Uso de sobrecarga (overload) de funções. .................................................. 103 Código 4.14 - Código 4.12 modificado para o uso de sobrecarga de funções. .................. 104 Código 4.15 - Uso de funções INLINE............................................................................... 105 Código 4.16 - Uso de funções recursivas. .......................................................................... 106 Código 4.17 - Programa que imprime uma frase digitada pelo usuário ao contrário......... 108 Código 4.18 - Resolução do problema das torres de Hanói. .............................................. 109 Código 4.19 - O operador de escopo de variáveis “::”. ................................................... 113 Código 4.20 – Exemplo de uma função que retorna mais de um valor usando referências.

.................................................................................................................................... 114 Código 4.21 - Exemplo de uma função que retorna um valor à uma variável por referência.

.................................................................................................................................... 115 Código 4.22 - Uso de variáveis da classe static. ................................................................ 116 Código 4.23 - Uso da função RAND para a simulação de um lançamento de um dado ou de

uma moeda.................................................................................................................. 117 Código 4.24 - Gerador de números pseudoaleatórios......................................................... 119 Código 4.25 - Gerador de números aleatórios que usa a função _dos_gettime(). .............. 120 Código 4.26 - Gerador de números aleatórios que utiliza uma variável da classe static

extern como semente. ................................................................................................. 121 Código 4.27 - Uso de variáveis da classe register.............................................................. 123 Código 4.28 - Uso de macros. ............................................................................................ 125 Código 4.29 - Uso de uma macro na definição de outra macro. ........................................ 127 Código 4.30 - Problemas no uso de macros. ...................................................................... 128 Código 4.31 - Uso de funções inline no lugar de macros................................................... 128 Código 4.32 - Outro exemplo do uso incorreto de uma macro. ......................................... 129 Código 4.33 - Outro exemplo do uso de funções inline no lugar de macros. .................... 129

Page 9: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 10

PREFÁCIO A cada dia que se passa a informática se torna mais e mais parte do nosso dia a dia. Seja como ferramental para a resolução de problemas matemáticos ou como um poderoso e polivalente laboratório de simulações a informática tem revolucionado a forma como os engenheiros resolvem seus problemas. Nesta apostila o aluno terá contato com a linguagem de programação mais difundida entre os programadores de todo o mundo, o C e seu sucessor o C++. Tópicos mais avançados, tais como programação orientada a objetos, arvores, grafos, etc são deixados para os cursos posteriores a este. Neste curso espera-se que o aluno adquira conhecimento para criar os seus primeiros programas, computacionalmente simples, mas que na prática já resolvem alguns problemas do dia a dia. Esta apostila destina-se aos alunos do segundo semestre do curso de Engenharia de Produção da Faculdade Santa Rita (FASAR), os quais tenho o prazer de ter como meus alunos. Por fim, estou a disposição de todos que lerem esta apostila para tirar dúvidas, assim como para receber críticas.

Page 10: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 11

1 – INTRODUÇÃO AO C++

1.1. Aspectos gerais da linguagem C++

Código 1.1 - Um programa típico em C++.

//Programa welcome to C++ #include <stream.h> main () { cout << "Bem Vindo ao mundo do C++!\n"; } O exemplo acima mostra como é o aspecto geral de um programa escrito em C++. O leitor não deve se ater, por enquanto, à sintaxe dos comandos, mas sim à morfologia do corpo do programa. Note que algumas linhas terminam em ponto-e-vírgula (;) e que outras não. A primeira linha começa com o caractere #. Aos poucos abordaremos todos os conceitos que estão envolvidos neste pequeno programa e que tem por finalidade apenas mostrar na tela a mensagem: Bem Vindo ao mundo do C++!. Assim sendo, comecemos com o comando cout (do inglês character out) que tem por finalidade imprimir na tela uma seqüência de caracteres, aqui representada pela frase: Bem Vindo ao mundo do C++! O comando cout é usado juntamente com o sinal de duplo menor-que (<<), que tem por finalidade mostrar como é o fluxo do comando (neste caso a instrução da direita será passada ao comando da esquerda). O sinal ‘\n’ (do inglês to a new-line) indica que naquele ponto deverá ser inserida uma quebra de linha pelo compilador. Uma propriedade interessante da linguagem C++ é que se pode escrever instruções simples em uma ou mais linhas sem problema algum. Assim sendo, o bloco de instruções abaixo é tratado pelo compilador como se fosse uma linha apenas. cout << "Esta é apenas" << "uma instrução" << "dividida em três linhas...\n"; Que na tela será exibida a mensagem: Esta é apenas uma instrução dividida em três linhas...

Page 11: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 12

1.1.1. Comentários Há duas formas de se inserir um comentário em C++. Uma delas é uma herança direta da linguagem C e a outra é nativa do C++. As duas formas são: /* Comentário no estilo C */ // Comentário no estilo C++ Há uma pequena, porém sutil diferença nas duas formas de se escrever um comentário, e esta se deve ao fato de que um comentário no estilo C++ torna toda a linha após as duas barras (//) uma linha de comentário, assim sendo tudo o que se encontra à direita das duas barras até o início da próxima linha é ignorado pelo compilador. Já o comentário escrito em linguagem C tem um símbolo de início (/*) e um de fim (*/), o que permite que sejam escritos comentários desta forma no meio de linhas de comando1. A maioria dos programadores costuma fazer uma “moldura” inicial em seus programas, tal moldura é útil a nível de comparação entre versões, direitos autorais, etc. Um exemplo de moldura pode ser visto no Código 1.2.

Código 1.2 - Exemplo de cabeçalho.

/*********************************************************/ /* PROGRAMA Exemplo de Cabeçalho */ /* */ /* ESCRITO por André Carlos Silva VERSÃO 1.00.00 */ /* DATA: 08/09/01 TODOS OS DIREITOS RESERVADOS */ /*********************************************************/

1.1.2. Identificadores Identificadores são símbolos singulares (ou únicos) utilizados nos programas. O nome da função main no Código 1.1 é um identificador, porque identifica o início do programa principal (ou o programa propriamente dito). Os identificadores podem conter apenas letras (maiúsculas e/ou minúsculas), algarismos e traços de sublinha (em inglês underscore, é o caractere “_”), e se comporem de até 127 caracteres. Os identificadores devem começar com letras de A a Z (como fn1 ou Catch21). O identificador 123abc não é válido, pois começa com um caractere numérico, já o identificador abc123 é válido. Identificadores do sistema freqüentemente começam com traço de sublinha, o que torna o seu uso como caractere inicial não muito recomendável. Além do traço de sublinha, outros sinais de pontuação, tais como ponto final (.), aspas (“ ”), etc., são usados pelo compilador

1 Linha de comando é toda instrução dada ao compilador. Pode ser uma declaração ou um comando propriamente dito, apesar de alguns autores não pensarem assim.

Page 12: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 13

e não devem ser incorporados em identificadores para que não causem nenhum efeito inesperado ao funcionamento do programa.

1.1.3. Case Sensitive Tanto a linguagem C quanto a linguagem C++ são case sensitive, que quer dizer que o compilador diferencia identificadores escritos em maiúsculas e em minúsculas, ou seja, para o compilador o identificador x1 é diferente do identificador X1. Essa propriedade vale para qualquer letra em qualquer posição de um identificador e não apenas para a primeira letra do identificador. Mediante isso é altamente recomendável que um padrão para a criação e utilização de identificadores seja adotado pelo programador.

1.1.4. Palavras-Chave Palavras-Chave (ou keywords) são identificadores que o compilador reserva para o seu uso próprio. As palavras auto, float, signed, void e while são exemplos de palavras chave, uma vez que tais palavras têm um significado próprio previamente definido para o compilador, significado este que não pode ser mudado.

1.1.5. Pontuação Uma linha de instrução deverá ser terminada por ponto-e-vírgula (;), os parentes (( )) após o identificador main no Código 1.1 delimitam a passagem de parâmetros entre o programa e outras funções e as chaves ({}) logo após o identificador main delimitam um bloco de instruções2, sendo análogas aos comandos BEGIN e END do Pascal. Estes são exemplos de sinais de pontuação que já foram usados previamente e que serão, sem dúvida alguma, usados novamente. O conjunto completo de sinais de pontuação usados pelo compilador pode ser visto abaixo.

# ( ) [ ] { } , : ; . ... À medida que novos sinais forem sendo apresentados os seus significados serão explicados.

1.1.6. Separadores Diferem dos sinais de pontuação por serem invisíveis. São descritos pelo termo espaço em branco (ou blank space). Os separadores em C++ incluem espaços, tabulações, carriage return e line feeds incluídas no texto. Usualmente o C++ ignora os espaços em branco que não são utilizados para separar identificadores, números e outras construções que devam permanecer unidas. Assim sendo,

2 Quando temos instruções que são executadas em dentro de um mesmo nível de um programa o seu conjunto recebe o nome de bloco de instruções e diz-se que as instruções estão aninhadas.

Page 13: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 14

o Código 1.1 pode ser reescrito como no Código 1.3, sem que o conteúdo do programa seja afetado3.

Código 1.3 - Outra forma de se escrever um programa.

#include <stream.h> main () {cout << "Bem Vindo ao mundo do C++!\n";}

1.1.7. Arquivos de cabeçalho Arquivos de cabeçalho são arquivos incluídos no programa através do comando #include, localizado sempre no começo do programa. O resultado de se incluir um arquivo de cabeçalho é o mesmo que de se incluir o conteúdo de todo o arquivo na mesma posição em que se incluiu a chamada ao arquivo. O que significa que uma alteração no arquivo de cabeçalho acarretará em mudanças em todos os programas que fazem menção a este. Os arquivos de cabeçalho têm a terminação .h ou .hpp, ao passo que o código-fonte escrito em C++ tem a terminação .cpp. Os símbolos < > delimitam o nome do arquivo a ser incluído e indicam que o arquivo está armazenado em um diretório default chamado include, dentro da pasta onde o compilador foi instalado. Se desejarmos adicionar um arquivo de cabeçalho que não se encontra no diretório include, podemos utilizar aspas (“ ”) para indicar que o arquivo se encontra no mesmo diretório onde o código-fonte do programa se encontra. O Código 1.4 apresenta um exemplo onde o arquivo de cabeçalho stream.h se encontra no diretório include e o arquivo stdio.h se encontra no mesmo diretório do código-fonte.

Código 1.4 - Exemplos de usos de arquivos de cabeçalho.

#include <stream.h> // Esta declaração inclui todo o conteúdo do arquivo // stream.h neste programa. #include "stdio.h" // Esta declaração inclui todo o conteúdo do arquivo // stdio.h neste programa. É altamente recomendável que sempre se use os sinais de < > para inserir um arquivo de cabeçalho fornecido junto com o compilador e “ ” para inserir um arquivo de cabeçalho que não venha incluso neste. O ponto mais importante que se deve ressaltar sobre os arquivos de cabeçalho é que são estes arquivos que possuem todos os comandos passados ao compilador. O compilador C, bem como o C++, não reconhecem quase comando algum (ao contrário de compiladores tais como o PASCAL, o FORTRAN, o DELPHI, etc.). Um programador em C++ deve conhecer os arquivos de cabeçalho, tanto a sua estrutura quanto os seus comandos. 3 O código descrito em 1.3 não é de uso aconselhável, pois torna o algoritmo do programa extremamente complexo para a leitura.

Page 14: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 15

1.2. Variáveis Em C++ uma variável é uma posição da memória que recebeu um nome. Assim sendo, as variáveis podem armazenar todo o tipo de informação, ou de dados. Dentro de um programa podemos usar o nome de uma variável livremente como se fosse o seu valor. Todas as variáveis apresentam, além de um nome, um tipo declarado, que é a informação que o compilador requer para distinguir qual o tipo de dado que será armazenado pela variável. A Tabela 1.1 apresenta os principais tipos de variáveis existentes no C++ e os seus limites de aplicabilidade.

Tabela 0.1 - Tipos usuais de dados em C++.

Range Tipo Exemplo Tamanho (bytes) Mínimo Máximo

Char ‘C’ 1 - 128 ... 127 Short -7 2 - 32768 ... 32767 Int 1024 2 - 32768 ... 32767 Long 262144 4 - 2147483648 ... 2147483647 Float 10,5 4 1,5e-45 ... 3,4e38* Double 0,00045 8 5,0e-324 ... 1,7e308* Long double 1e-8 8 5,0e-324 ... 1,7e308*

* Valores aproximados. Para criar uma variável em um programa deve-se começar com uma declaração de seu tipo e terminar com o seu identificador (ou nome da variável), seguido de ponto-e-vírgula. Abaixo segue o exemplo da declaração de uma variável do tipo caractere chamada sim_ou_nao. char sim_ou_nao; Tal comando é denominado definição, porque define o espaço na memória para o armazenamento de uma variável. Pode-se inserir definições de variáveis em qualquer lugar do programa, porém o local da definição de uma variável muda o seu escopo, o que pode afetar o modo como o programa será executado. Voltaremos a falar em escopo de variáveis no item 1.2.2.

1.2.1. Inicializando variáveis com definições Existem duas formas de se inicializar4 uma variável, isto é, de atribuir-lhe um valor inicial. O primeiro, e provavelmente o melhor, é visto no Código 1.5. Desta forma a variável é inicializada logo após ter sido declarada.

4 A necessidade de se inicializar uma variável se deve ao fato do compilador C, e também o C++, guardarem lixo, que é o fato de uma variável não inicializada começar com um valor esdrúxulo, oriundo de dados

Page 15: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 16

Código 1.5 - Inicialização de variáveis no momento de sua declaração.

char sim_ou_não = ‘S’; int contador = 1; float peso = 155.5; A outra forma é inicializar a variável no decorrer do programa. Isso é feito mediante um simples comando de atribuição, da seguinte forma: int contador; ... contador = 1; Um programa pode mudar o valor de uma variável quantas vezes forem necessárias e toda vez que o programa atribui um novo valor para uma variável, este substitui o valor antigo, sendo este último perdido.

1.2.2. Escopo de uma variável Instruções podem se referir a variáveis apenas quando estas variáveis se encontram dentro do mesmo escopo, ou nível, das instruções. O escopo de uma variável se estende até as fronteiras do bloco que lhe define. A Figura 1.1 apresenta um código-fonte escrito em C++ de caráter didático onde as variáveis vão sendo declaradas em diferentes níveis do programa. A cada nível uma nova variável é declarada e sua cor corresponde à cor do quadro que delimita o escopo desta variável. Note que a variável Q é de escopo global, ou seja sua declaração é valida para todo o programa, já as demais variáveis são locais.

previamente armazenados na memória do computador, o que pode causar sérios transtornos na vida do programador.

Page 16: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 17

Figura 1.1 - Exemplo do escopo de uma variável.

Definições que são realizadas dentro das chaves abertas logo após main são locais à função main. Tal aspecto deve ficar bem claro desde já, uma vez que logo serão apresentados os conceitos de funções similares à função main.

1.2.3. Inicializando variáveis globais e locais Ainda que estas se apresentem de maneira muito semelhante nos programas, variáveis globais e locais são armazenadas na memória de maneiras muito diferentes. O compilador reserva um espaço fixo na memória para cada variável global, denominado segmento de dados. Ao fazer isso o compilador inicializa todas as variáveis globais com o valor zero (no caso das variáveis do tipo char o seu conteúdo é nulo, do inglês void). Já para as variáveis locais o compilador cria um espaço temporário denominado de pilha (do inglês stack). Conforme já mencionado, todas as variáveis globais são inicializadas automaticamente com o valor nulo quando o programa é executado, mas o mesmo não ocorre com as variáveis locais, que podem ser inicializadas com algum lixo da memória.

Page 17: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 18

1.3. Entrada e saída de dados As instruções de entrada e saída de dados (I/O, do inglês Input/Output) de um programa se responsabilizam por todas as trocas de dados entre o computador e meio externo, como por exemplo a apresentação de textos em vídeo, a impressão de relatórios, o recebimento de um dado emitido diretamente pelo usuário via teclado ou mesmo a comunicação com sistemas remotos através de uma linha telefônica ou porta serial.

1.3.1. Fluxos de saída de dados Códigos-fonte já mostrados usaram instruções de fluxo de saída para apresentar strings e valores de variáveis na tela. O código 1.6 mostra um exemplo mais prático desta tarefa, exibindo na tela do computador a data e a hora atual, fornecida pelo relógio do sistema.

Código 1.6 - Programa que retorna a data e a hora atual.

//Programa para ler a data e a hora do sistema #include <stream.h> #include <dos.h> main () { struct dosdate_t hoje; struct dostime_t agora; _dos_getdate(&hoje); cout << "A data de hoje e: " << (int)hoje.day << "/" << (int)hoje.month << "/" << (int)hoje.year << "\n"; _dos_gettime(&agora); cout << "A hora atual e: " << (int)agora.hour << ":"; if (agora.minute < 10) cout << '0'; cout << (int)agora.minute << ":"; if (agora.second < 10) cout << '0'; cout << (int)agora.second << "\n"; } Existem alguns itens ainda não estudados no Código 1.6, como nas linhas 8 e 9, onde são definidas duas variáveis do tipo struct, denominadas hoje e agora. Já as linhas 11 e 16 usam funções para ler e armazenar a data e a hora atual do sistema nestas variáveis. Basta, por enquanto, saber que o comando struct é um tipo de definição. O comando (int)agora.second é denominado de molde de tipo (do inglês type cast). Neste caso, a variável agora.second foi definida previamente no arquivo de cabeçalho dos.h como um char de um único byte, que normalmente armazena pequenos valores ou caracteres ASCII5. O programa usa uma variável char para armazenar pequenos números

5 Vide capítulo 1.4.1.1. Constantes literais do tipo caractere para mais informações.

Page 18: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 19

inteiros, e não caracteres. Assim sendo, para que sejam exibidos na tela números inteiros deve-se utilizar o comando (int), que molda a variável char em um tipo inteiro (int).

1.3.2. Saída de dados no estilo C

Código 1.7 - O comando PRINTF.

printf("Entre com o seu nome"); printf("Seu saldo é $ %d\n", saldo); A função printf6, freqüentemente encontrada em programas escritos em C, transfere argumentos encontrados entre os parênteses para a saída de vídeo padrão, que normalmente é o monitor de vídeo. A primeira instrução do Código 1.7 imprime no vídeo uma string simples. A segunda instrução também imprime uma string na tela, mas usa notação especial (%d) que é usada para acrescentar elementos aos caracteres entre aspas. Nesse caso deseja-se acrescentar uma variável, denominada saldo, a qual printf converte seu conteúdo em caracteres e os insere na posição indicada na string de saída. A execução do Código 1.7 traz como resultado no vídeo algo como: Entre com o seu nome Seu saldo é $ 75.68

1.3.3. Saída de dados formatada A formatação de dados é muito usada quando se têm variáveis de um determinado tipo e se deseja imprimir o seu conteúdo como outro tipo, ou com um determinado número de casas decimais. Suponha o caso em que um programa realiza determinadas contas com números inteiros e, em seu final, deseja-se que o resultado seja impresso nas bases octal, hexadecimal e decimal. Neste caso pode-se utilizar uma função de formatação de dados para que a saída dos dados seja como a desejada. O Código 1.8 mostra um exemplo da formatação de dados para a impressão.

Código 1.8 - Exemplo de conversão de formato de dados.

cout <<"A conta em octal é:" << oct << conta; cout <<"A conta em hexadecimal é:" << hex << conta; cout <<"A conta em decimal é:" << dec << conta; Uma outra razão para se usar funções de formatação de dados é para alinhar ou justificar colunas de números. Para fazê-lo basta que se acrescente um número inteiro após a variável que se deseja exibir o conteúdo, número este que indica o mínimo de caracteres que a variável irá ocupar, por exemplo: cout <<"A conta é:" << setw(10) << conta;

6 Para usar o comando printf em programas escritos em C++ deve-se usar o cabeçalho stdio.h.

Page 19: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 20

O comando acima exibe na tela o valor da variável conta, acrescentando espaços à sua esquerda para completar um mínimo de 10 colunas de caracteres. Se o número de colunas reservado for menor que o necessário para a correta exibição da variável, esta será exibida corretamente, porém não estará alinhada como o esperado.

1.3.4. Fluxos de entrada de dados Para que um dado flua para fora do computador usamos a instrução cout, já agora para que um dado flua para dentro do computador usaremos a instrução cin7 (do inglês character input). O comando cin é um comando de atribuição no qual é atribuído a uma variável um dado. O Código 1.9 apresenta um programa que solicita ao usuário que digite três números reais e retorna o valor da média destes três valores.

Código 1.9 - Programa para cálculo da média de três números.

//Programa para calcular a media de três números reais #include <stream.h> main () { float a, b, c; cout << "Digite três números.\n"; cin >> a >> b >> c; cout << "A media dos três números e: " << (a+b+c)/3 << '\n'; } Fluxos de entrada de dados partem do pressuposto que a informação que lhes será passada será condizente com o tipo da variável que lhe receberá. Muitas vezes isso não ocorre, seja por um erro de digitação do usuário ou outro motivo qualquer, o que ocorre comumente quando os dados a serem lidos são de tipos numéricos e, ao invés disso, o dado lido é um dado não-numérico. Quando erros como o do exemplo como o anterior ocorre, quase sempre ocorrem erros internos no programa, o que pode causar a sua falha geral. Uma boa forma de lidar com estes tipos de erros é ler todas as entradas de dados armazenando-as em strings de caracteres e converte-las, posteriormente, para o tipo de dado desejado. O Código 1.10 apresenta o mesmo programa do Código 1.9, porém caso o dado lido não seja numérico o programa continua sua execução normalmente.

Código 1.10 - Método para evitar uma entrada incorreta do tipo de dado.

//Programa para calcular a media de três números reais #include <stream.h>

7 Programas em C utilizam tipicamente a função scanf para a entrada de dados. O C++ também reconhece esta função exatamente como faz com a função printf.

Page 20: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 21

#include <stdlib.h> main () { float a, b, c; char d[20], e[20], f[20]; cout << "Digite três números.\n"; cin >> d >> e >> f; a = atof(d); cout <<"O primeiro valor digitado foi: " << a << '\n'; b = atof(e); cout <<"O segundo valor digitado foi: " << b << '\n'; c = atof(f); cout <<"O terceiro valor digitado foi: " << c << '\n'; cout << "A media dos três números e: " << (a+b+c)/3 << '\n'; } No programa apresentado no Código 1.9, caso o usuário digite um caractere não-numérico o programa automaticamente pararia de ler as demais variáveis e saltaria para linha de comando posterior. O mesmo já não acontece com programa apresentando no Código 1.10, pois os dados lidos são armazenados em variáveis do tipo char, podendo ter cada uma vinte posições8. A função atof (ASCII to Float Point) se encarrega de converter a string lida para um valor real e caso o conteúdo da string seja um caractere não-numérico, o programa simplesmente atribuirá o valor zero para a variável, evitando assim maiores problemas. As funções atol (ASCII to Long) e atoi (ASCII to Ingeter) são análogas à função atof, porém a primeira converte a string para um valor do tipo inteiro longo, a segunda para um valor do tipo inteiro e a terceira para um valor do tipo real.

1.4. Constantes Constantes9 são valores que não se alteram durante a execução de um programa, como por exemplo o valor de π, ou se alteram raramente, como no caso de um programa que armazena as notas de exames de alunos. Pode-se criar uma constante chamada NOTA_MAXIMA e associar a esta o valor 10. Se a nota máxima mais tarde for alterada para 100 bastará revisar o valor da constante e recompilar o programa, não havendo a necessidade de se revisar todo o programa alterando todo 10 por 100. Em programas escritos em C++ existem quatro tipos de constantes, que são:

• Constantes literais • Constantes definidas

8 O número de posições de uma string é definido pelo número entre colchetes após o nome da variável. 9 Constantes em C e C++ são tipicamente escritas com letras maiúsculas, ainda que isso não seja obrigatório.

Page 21: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 22

• Constantes declaradas • Constantes enumeradas

1.4.1. Constantes literais São a forma mais comum de constantes e se compõem de valores numéricos ou alfanuméricos digitados diretamente no código-fonte do programa. A forma de uma constante literal determina o seu tipo de dados, que pode ser qualquer um dos tipos listados na Tabela 1.1 referentes a variáveis, uma vez que uma constante é uma variável na qual o programa não pode alterar o seu valor durante a sua execução.

1.4.1.1. Constantes literais do tipo caractere (char) Usualmente uma constante do tipo char representa símbolos constantes no conjunto de caracteres ASCII e que são envolvidos por apóstrofos (‘ ’). Exemplos de constantes do tipo char são ‘$’, ‘U’, ‘z’, ‘?’. Como esses símbolos são representados internamente por números inteiros de 0 a 255 (que são a tabela ASCII propriamente dita), constantes do tipo char também podem ser usadas para armazenar números dentro dessa mesma faixa. Isso torna o tipo char conveniente para o armazenamento de pequenos valores ocupando apenas um byte. Deve-se tomar atenção quanto ao fato da constante char estar ou não declarada como uma constante dotada de sinal (signed). Constantes, e/ou variáveis, signed representam grandezas positivas e negativas, mas constantes, e/ou variáveis, unsigned representam apenas valores positivos10. Assim sendo, apesar de constantes unsigned char representares valores entre 0 e 255, constantes signed char representam valores entre –128 e 127. O mesmo é válido para os outros tipos de constantes/variáveis, uma constante unsigned int pode variar entre 0 e 65535, já constantes signed int podem variar entre –32768 e 32767. Os quatro tipos de inteiros (char, short, int e long) são signed por default. Para imprimir na tela caracteres que não podem ser digitados diretamente via teclado pode-se usar comandos, tal como '\n' para representar um caractere de new-line, que são caracteres denominados de códigos de escape. A Tabela 1.2 lista outros códigos de escape que o compilador C++ reconhece. Pode-se usar os códigos de escape como constantes do tipo char individuais, ou seja, dentro de apóstrofes, ou como de strings, ou seja, dentro de aspas.

10 As faixas de valores signed e unsigned possuem os mesmos “comprimentos”, porém representam diferentes faixas de valores. Apenas tipos de dados signed podem representar valores negativos.

Page 22: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 23

Tabela 0.2 - Códigos de escape utilizados em C++.

Valores em ASCII Código de escape Significado Decimal Hexadecimal Símbolo(s) '\a' Campainha 7 07 BEL '\b' Backspace 8 08 BS '\f' Form feed 12 0C FF '\n' New-line 1310 0D0A CRLF '\r' Carriage return 13 0D CR '\t' Tab horizontal 9 09 HT '\v' Tab vertical 11 0B VT '\\' Barra reversa 92 5C \ '\'' Apóstrofo 39 27 ’ '\"' Aspas 34 22 “ '\?' Ponto de interrogação 63 3F ? '\000' Octal em ASCII Todos todos todos '\x00' Hexadecimal em ASCII Todos todos todos

1.4.1.2. Constantes do tipo string Em C++ uma string pode ter qualquer tamanho e pode conter quaisquer caracteres que se possa digitar entre duas aspas, bem como quaisquer dos códigos de escape da Tabela 1.2. Na memória do computador, as strings são representadas por uma série de valores de caracteres ASCII além do 0 ou NULL. O caractere NULL marca o fim de uma string e é inserido automaticamente pelo C++ ao final das constantes do tipo string. Como strings terminam com NULL, não se deve utilizar aspas para representar caracteres simples. Se isso for feito, pode-se estar desperdiçando espaço da memória. Por exemplo, "J" é uma string composta por dois caracteres (um caractere para representar a letra J e outro para o NULL, sendo este invisível ao final da string), já 'J' é um caractere simples, ocupando apenas um byte de espaço na memória e não demandando um valor NULL para marcar o seu fim.

1.4.1.3. Constantes do tipo inteiro (int) Quando se trabalha com constantes do tipo inteiro deve-se tomar os seguintes cuidados: a. Nunca use virgula ou outra pontuação em números inteiros. Entre com 123456 e não

com 123,456 ou 123.456. b. Para forçar que o valor seja do tipo long, coloque em seu final a letra L maiúscula. Por

exemplo, 1024 é do tipo int, mas 1024L é do tipo long. c. Para forçar que o valor seja do tipo unsigned, coloque em seu final um U maiúsculo.

Page 23: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 24

1.4.1.4. Constantes do tipo real (float) Valores do tipo real são sempre do tipo signed. Pode-se entrar com valores reais de maneira usual (como por exemplo 1.2 ou –555.9), ou então usando notação científica, colocando-se após o número um E seguido de um expoente, positivo ou negativo. Assim, o número 10.589,25 pode ser representado por 1.058E4 (sendo que aqui a precisão escolhida foi de três casas decimais).

1.4.2. Constantes definidas As constantes literais são comuns e úteis, mas lhes falta clareza. Por exemplo, o que significa a constante literal 51? Trata-se da idade de alguém? Do número de batatas em ma lata? De um número sorteado na loteria? “51” pode ser quaisquer destes valores ou um infinito de outros. Como constantes literais podem confundir a pessoa que está lendo o programa, deve-se dar nomes às constantes. Um modo de se dar nome a uma constante é usar o comando #define. Como exemplo tem-se: #define NUMERO 51 #define NOVALINHA '\n' As linhas acima não são instruções, por isso não terminam em ponto-e-vírgula. Elas são linhas de controle e não ocupam qualquer espaço no código compilado. No corpo de um programa o compilador substitui um símbolo definido por #define com o texto associado. O programa apresentado no Código 1.11 define duas constantes e imprime o valor de cada uma delas na tela.

Código 1.11 - Uso de #define para a declaração de constantes.

#include <stream.h> #define NUMERO 51 #define CARACTERE '@' #define STRING "Aprendendo C++." #define OCTAL 0233 #define DECIMAL 155 #define HEXADECIMAL 0x9b #define REAL 3.14159 #define NOVALINHA '\n' main () { cout << STRING << NOVALINHA << CARACTERE << NOVALINHA << NUMERO << NOVALINHA << DECIMAL << NOVALINHA << OCTAL << NOVALINHA

Page 24: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 25

<< HEXADECIMAL << NOVALINHA << REAL << NOVALINHA; } O C++ substitui o NUMERO e NOVALINHA com o texto associado a estes símbolos, 51 e '\n', respectivamente. Nota-se que um espaço simples é suficiente para se separar um identificador de seu valor. O C++ ignora os espaços extras que por ventura existam.

1.4.3. Constantes declaradas Um segundo modo, freqüentemente preferido, de se criar uma constante em programas é anteceder uma definição normal de variável com a palavra-chave const. Uma vez feito isso, o compilador rejeita quaisquer instruções que tentem alterar os valores definidos desta forma. O programa apresentado no Código 1.12 é o mesmo programa do Código 1.11, mas com a diferença deste declarar todas as suas constantes com o comando const e não com o comando #define. Observe que as definições com const especificam tipos de dados, terminam com ponto-e-vírgula e são inicializadas como variáveis comuns. O comando #define não especifica tipo de dados, não uso o símbolo de atribuição (=) e não termina com ponto-e-vírgula.

Código 1.12 - Uso do comando const para a declaração constantes.

#include <stream.h> const int NUMERO = 51; const char CARACTERE = '@'; const char STRING[] = "Aprendendo C++."; const int OCTAL = 0233; const int DECIMAL = 155; const int HEXADECIMAL = 0x9b; const float REAL = 3.14159; const char NOVALINHA = '\n'; main () { cout << STRING << NOVALINHA << CARACTERE << NOVALINHA << NUMERO << NOVALINHA << DECIMAL << NOVALINHA << OCTAL << NOVALINHA << HEXADECIMAL << NOVALINHA << REAL << NOVALINHA; }

Page 25: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 26

Exceto para a constante STRING na linha 5, os identificadores são declarados da mesma maneira nos Códigos 1.11 e 1.12. Na linha 5 do Código 1.12 a constante STRING termina com um par de colchetes ([ ]). Os colchetes após STRING dizem ao C++ para criar um vetor de caracteres, em outras palavras, uma string de mais de um caractere. Sem os colchetes o compilador iria considerar a linha 5 como definindo um único char, e não um vetor deles. Uma outra diferença significativa entre constantes #definidas e constantes criadas com o comando const é quando o compilador avalia um valor. Por exemplo, suponhamos que um programa contivesse o código abaixo: #define X 5 #define Y X + 10 Como era de se esperar, X está associado ao valor 5 e Y está associado ao valor X + 10. Todavia deve-se ter muito cuidado com ais tipos de definições, pois estas podem conduzir a bugs no programa. Apenas o texto X + 10 está associado à constante Y, e não o resultado numérico dessa expressão. Isso pode causar resultados surpreendentes em instruções como a abaixo: cout << "X = " << X << "Y = " << Y; Quando o C++ compila essa instrução, ele insere o texto 5 e o texto X + 10 no lugar das constantes X e Y. A expressão X + 10 é então avaliada quando esta instrução for compilada e não antes quando a constante Y foi definida. Por isso, se o programa mencionado acima executar as linhas a seguir antes da instrução de fluxo de saída de dados, X terá um valor diferente quando a instrução for compilada e, portanto, a expressão X + 10 também terá. #undef X #define X 10 O comando de controle #undef desfaz a definição de um símbolo previamente criado com o comando #define. Quando usamos o comando #undef destruímos a constante da memória e, através de um novo #define podemos definir uma nova constante com o mesmo nome da anterior, porém com um conteúdo diferente. O Código 1.13 apresenta um programa exemplo de como se pode usar os comandos #define e #undef e as conseqüências de tais usos.

Código 1.13 - Uso dos comandos #define e #undef.

#include <stream.h> #define X 5 #define Y X+5 main () {

Page 26: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 27

cout << "X = " << X << " Y = " << Y << '\n'; #undef X #define X 10 cout << "X = " << X << " Y = " << Y << '\n'; #undef X #define X 100 cout << "X = " << X << " Y = " << Y << '\n'; } O que acontece no Código 1.13 não acontece com constantes do tipo const, pois estas nunca podem ser alteradas de forma semelhante e portanto são mais seguras, ou pelo menos “mais constantes”. Existem duas outras vantagens de se usar constantes do tipo const em um programa ao invés de usar constantes do tipo #define. A primeira é que o compilador, na maioria das vezes, pode criar um código mais eficiente com constantes do tipo const. Em segundo lugar, como as definições especificam tipos de dados, o compilador pode verificar na compilação se as constantes do tipo const estão no formato correto. Com #define o compilador não pode fazer isso até que uma instrução utilize o identificador da constante, tornando quaisquer erros na constante mais difíceis de serem identificados.

1.4.4. Constantes enumeradas Pode-se usar constantes enumeradas para criar listas de categorizadas. O exemplo clássico de constantes enumeradas é o de uma lista de cores, onde cada cor recebe um valor inteiro, começando em zero. Tal lista é útil devido ao fato de podermos trabalhar com o nome da cor ao invés de seu respectivo código. Em C++ pode-se gerar uma lista enumerada da seguinte maneira: enum cores {VERMELHO, LARANJA, AMARELO, VERDE, AZUL, INDIGO, VIOLETA}; O comando acima associa os elementos constantes listados entre chaves (VERMELHO, LARANJA, ... , VIOLETA) ao identificador cores, que é um nome atribuído a este novo tipo de dados. O comando enum diz ao compilador que os itens entre chaves são constantes e devem ser enumeradas, isto é, a estes itens devem ser associados números inteiros seqüenciais. Quando o compilador processa esse comando ele atribui valores, começando de 0, para cada elemento enumerado de forma que VERMELHO seja igual a 0, LARANJA igual a 1 e VIOLETA igual a 6. Após um novo tipo enumerado de dados ser declarado pode-se criar variáveis desse mesmo tipo, assim como se faz com variáveis de outros tipos de dados. Por exemplo, pode-se definir uma variável do tipo cores e faze-se isso da seguinte maneira:

Page 27: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 28

cores corFavorita = AZUL; O código acima cria uma variável chamada corFavorita do tipo cores e atribui a esta variável o valor inicial AZUL11. Como corFavorita é uma variável, uma instrução de atribuição pode lhe dar um novo valor, como por exemplo: corFavorita = LARANJA; Pode-se definir uma constante to tipo cores usando o comando const. De qualquer forma que se utilize constante enumeradas, como os exemplos acima mostram, o seu uso aumenta enormemente a legibilidade de um programa. Se uma pessoa ao ler um programa se depara com uma instrução não comentada semelhante a: cor = 4; terá que buscar o significado de 4 em uma lista de referências para descobrir qual a cor atribuída pelo programa à variável cor. Já a atribuição: cor = AZUL; é clara por si só. O Código 1.14 mostra alguns outros exemplos de constantes enumeradas e ilustra alguns dos modos dos quais os programas podem utiliza-las.

Código 1.14 - Uso de constantes enumeradas.

#include <stream.h> enum LINGUAGENS {ASSEMBY, BASIC, C, CPP, FORTRAN, PASCAL} linguagens; enum escala {DO, RE, MI, FA, SOL, LA, SI}; enum {FALSE, TRUE}; main () { int nota_da_escala = LA; int operacao = TRUE; linguagens = CPP; cout << "Linguagem = " << (int)linguagens << '\n' << "Nota musical = " << (int)nota_da_escala << '\n'

11 Na realidade o compilador inicializa a variável corFavorita com o valor numérico da variável AZUL, que é igual a 4.

Page 28: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 29

<< "Resultado da operacao = " << (int)operacao << '\n'; } A linha 3 do Código 1.14 declara o tipo enumerado LINGUAGENS, usando letras maiúsculas para o nome do tipo dos dados. No final da mesma linha de comando existe a declaração de uma variável chamada linguagens, usando letras minúsculas. A variável linguagens é uma variável do tipo enumerado LINGUAGENS e a linha 13 atribui um dos elementos listados entre chaves de LINGUAGENS para esta variável. A linha 6 demonstra como criar um tipo de dado enumerado sem dar-lhe um nome, o que ocasionalmente é útil. O C++ permite isso para simplificar o trabalho de se dar nomes a listas enumeradas que serão criadas apenas para se usar os itens contidos nas listas ao invés de seus valores do tipo int.

1.4.5. Atribuição de valores a elementos enumerados Normalmente é melhor deixar o compilador atribuir os valores seqüenciais aos elementos de tipos enumerados de dados. Porém, em caso de necessidade, pode-s alterar os valores que o compilador associa a cada elemento. Para atribuir um valor específico a um elemento enumerado basta que se coloque após o elemento o sinal de igual e o valor desejado. Por exemplo: enum semáforo {VERDE, AMARELO = 10, VERMELHO}; No exemplo acima o elemento VERDE é igual a 0, o elemento AMARELO é igual a 10 e o elemento VERMELHO é igual a 11. Elementos enumerados que por ventura viessem após o elemento VERMELHO teriam valores crescentes a partir do número 12. Pode-se atribuir valores explícitos para mais de um elemento enumerado. Quando precisamos criar tipos enumerados de dados com elementos cujos valores coincidam com outras constantes (tais como números de referentes a erros no sistema operacional, portas de I/O e outras grandezas constantes), a possibilidade de iniciar novas seqüências dentro da declaração de tipo enum é extremamente útil.

1.5. Operadores O C++ é repleto de operadores, que são símbolos que executam vários tipos de operações sobre os seus argumentos. O sinal de mais (+) é um operador. Em uma expressão, como era de se esperar, este operador soma dois valores, como por exemplo: a = b + c; As variáveis a, b e c poderiam ser de quaisquer tipos numéricos de dados e o efeito do exemplo acima é a atribuição à variável a o valor da soma da variável b com a variável c.

Page 29: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 30

O sinal de igual neste exemplo é também um operador, com a função de atribuir o valor da sua direita para a posição da memória representada pela variável à esquerda.

1.5.1. Operadores e precedência O C++ possui mais operadores que os operadores matemáticos básicos (+, –, *, /). Existem operadores que criam funções, operadores para vetores, operadores que incrementam e decrementam valores e operadores complexos que executam mais de uma tarefa de uma só vez. A Tabela 1.3 apresenta os operadores da linguagem C++ e também mostra os seus níveis de precedência. Em sua utilização normal, operadores de menor precedência executam as suas operações em expressões antes de operadores de maior precedência.

Tabela 0.3 - Operadores usados em C++ e sua respectiva precedência.

Nível de precedência Operador12 1 ( ) . [ ] -> :: ->* this & 2 * & nem delete ! ~ ++ -- - sizeof 3 * / % 4 + - 5 << >> 6 < <= > >= 7 == != 8 & 9 ^ 10 | 11 && 12 || 13 ?: 14 = += -= *= /= %= <<= >>= &= ^= |= 15 '

1.5.2. Operadores de incremento e decremento Dois dos mais usados operadores da linguagem C e C++ são os operadores de incremento (++) e decremento (--). O operador ++ incrementa o seu argumento em uma unidade, ao passo que o operador – decrementa o seu argumento em uma unidade. Os exemplos abaixo mostram a grande utilidade que os operadores de incremento e decremento podem ter: i = i + 1; //Soma 1 à variável do tipo int i i++; //Soma 1 à variável do tipo int i j = j – 1; //Subtrai 1 da variável do tipo int j

12 O símbolo * no nível 2 é o operador de referenciamento de ponteiros. O mesmo símbolo no nível 3 é o operador matemático de multiplicação.

Page 30: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 31

j--; //Subtrai 1 da variável do tipo int j Normalmente, quando não se faz nada com o valor de uma expressão, este valor é perdido. Mas as expressões i++ e j-- são especiais. Elas executam uma ação sobre os seus argumentos (incrementam i e decrementam j) e possuem valor próprio. O Código 1.15 mostra um exemplo do uso dos operadores ++ e --.

Código 1.15 - Uso de operadores de incremento e decremento.

#include <stream.h> main () { int i = 100, j = 100; cout << "i++ = " << i++ << '\n'; cout << "i = " << i << '\n'; cout << "j-- = " << j-- << '\n'; cout << "j = " << j; } Quando se executa o Código 1.15, o vídeo mostrará o seguinte resultado: i++ = 100 i = 101 j-- = 100 j = 99 Isso pode parecer estranho à primeira vista, mas não o é. Deve-se entender que quando se usa o operador ++, bem como --, este possui um valor próprio (que é o valor do seu argumento antes da operação) e passa ao seu argumento o valor relativo ao seu incremento, ou decremento. Assim sendo, para i = 100, a expressão: j = i++; Atribuirá à j o valor de 100 e não o valor de i + 1 como pode-se ser levado a pensar. A expressão anterior é equivalente à expressão: j = i; i = i + 1; Pensando-se apenas na expressão expandida nunca se esquece que o valor de i++ é igual ao valor de i antes do incremento. Porém, se os operadores ++ e -- aparecerem à frente de seus argumentos, eles passam a ter efeito antes que o valor da expressão seja formado. Uma vez mais assumindo que i = 100, a expressão abaixo incrementa a variável i em uma unidade: j = ++i;

Page 31: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 32

Entretanto, como o operador ++ aparece antes de i, o operador ++ incrementa a variável i antes do resultado ser atribuído à variável j. Assim sendo, a expressão anterior atribui o valor 101 tanto para j quanto para i. A expressão anterior é equivalente à expressão: i = i + 1; j = i; Quando não se deseja atribuir o resultado do incremento ou decremento de uma variável a uma outra variável não há diferença em se colocar o operador antes ou depois do seu argumento. Assumindo que as variáveis do exemplo a seguir são do tipo int, as instruções abaixo incrementam cada uma destas variáveis de 1: peso++; //peso = peso + 1 saldo++; //saldo = saldo + 1 nota++; //nota = nota + 1 Posicionando os operadores em primeiro lugar têm-se os mesmos resultados: ++peso; //peso = peso + 1 ++saldo; //saldo = saldo + 1 ++nota; //nota = nota + 1

1.5.3. Expressões Expressões são os processadores numéricos de uma linguagem de programação, são um modo de se passar instruções a um programa sobre o que ele deve fazer com vários valores. O Código 1.16 apresenta um programa muito simples de álgebra. Este consiste em, dado o preço de custo de uma mercadoria em dólares e uma taxa de impostos, qual o deveria ser o preço de venda desta mercadoria e quanto seria o imposto pago, ambos em dólares?

Código 1.16 - Programa para cálculo do imposto incidente sobre mercadorias.

#include <stream.h> #include <stdlib.h> char resposta[80]; float preco_custo, taxa, preco_venda, impostos; main () { cout << "Digite o preco de custo da mercadoria (em US$): "; cin >> resposta; preco_custo = atof(resposta); cout << "Digite a taxa de impostos sobre a mercadoria (em %): "; cin >> resposta; taxa = atof(resposta);

Page 32: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 33

taxa = taxa / 100; preco_venda = preco_custo * ( 1 + taxa); impostos = preco_venda - preco_custo; cout << "\nO preco de venda da mercadoria devera ser de: US$ " << preco_venda ; cout << "\nO valor dos impostos sobre a mercadoria e de: US$ " << impostos; } O Código 1.16 não possui nenhum comando ainda não visto e, além de muito simples, mostra como é possível a elaboração de programas úteis com recursos simples e de fácil aplicação.

Page 33: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 34

1.6. Exercícios do capítulo 1.1. Escreva um programa que solicite o seu primeiro nome e imprima uma mensagem na

tela incluindo o nome fornecido. 1.2. Explique o significado dos símbolos //, /* e */ nas linhas abaixo: /*Prompt para a entrada de dados*/ cout << "Entre com o valor: "; //Prompt para um valor cin >> valor; //Lê o valor 1.3. Escreva o menor programa em C++ possível de ser compilado e executado. 1.4. Quais destes identificadores são válidos: myMoney, 2por1, _max, _max_Value,

$Balance, A_L_F_A_B_E_T_O_? Prove a sua resposta. 1.5. Escreva um programa que exiba os sinais de pontuação da linguagem C++. 1.6. Explique o que são espaços em branco e para que são usados. 1.7. Que tipo ou tipos de dados podem armazenar os valores 145540, 145.543 e 10? Qual é

o menor tipo que pode armazenar estes valores? 1.8. Com uma única linha de código, defina uma variável de denominada alfa do tipo

char e atribua o caractere ‘A’ à variável. 1.9. Quais são as duas principais diferenças entre uma variável local e um global? 1.10. Escreva um programa que exiba na tela a seguinte linha: Idade = 70, Aniversário = 01/10/1931, Saldo = $1300.76 Onde os valores 70, 01, 10, 1931 e 1300.76 fiquem armazenados em variáveis do tipo int denominadas idade, dia, mês e ano em uma variável real denominada saldo, respectivamente. 1.11. Escreva dois programas, sendo que ambos devem apenas ler um valor real digitado

pelo usuário, armazena-lo em uma constante real e imprimi-lo na tela. No primeiro programa use uma instrução de fluxo de entrada de dados para armazenar o valor digitado diretamente em uma variável real. No segundo armazene o valor primeiramente em uma string e depois o atribua para uma variável real. Por que o segundo método é mais seguro?

1.12. Escreva um programa que exiba na tela a seguinte frase, incluindo todas as aspas e o

apóstrofo:

Page 34: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 35

"It’s raining "dogs and cats" in here!"

1.13. Escreva um programa que atribua um apóstrofo (‘) a uma variável do tipo char e,

então, exiba essa variável na tela. 1.14. Escreva um programa que declare duas constantes denominadas MIN, com valor

igual a 1 e MAX com valor igual a 999. Exiba as constantes na tela. Faça o exercício utilizando constantes declaradas e definidas.

1.15. Crie um tipo enumerado de dados denominado flores com os elementos ROSA,

CARMELIA, ORQUIDEA e GARDENIA. 1.16. Se i = 98, qual é o resultado de cada uma das operações que se seguem,

executadas na ordem que se segue: i++, ++i, i-- e --i? Qual é o valor final de i? Faça um programa para ajudar nas respostas.

Page 35: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 36

2 – INSTRUÇÕES E OPERADORES AVANÇADOS

Programas de computador operam de forma bastante semelhante a motores de combustão interna. Queimam combustível (usam dados) e executam trabalho (instruções). Sem combustível o motor não funciona e, sem motor, o combustível não teria que ser retirado do subsolo. A maior parte dos programas precisa tanto de combustível quanto de um motor que funcione, isto é, de dados e de instruções, para desempenhar tarefas úteis. Como seria de se esperar, o C++ possui uma ampla variedade de tipos de estruturas de dados para o armazenamento de informações na memória. Também possui alguns tipos de instruções que, entre outras funções, podem tomar decisões, repetir ações e selecionar elementos de algum padrão de um conjunto de valores. Aprender os tipos fundamentais de estruturas de dados e instruções da linguagem C++ não é difícil. De fato, quando alguém se depara com tais estruturas e instruções pela primeira vez estas talvez lhe pareçam até mesmo simples demais para qualquer uso prático. Isso é natural e ilustra uma das razões pelas quais nunca avançam além de um conhecimento simples de programação em uma linguagem de propósito genérico como o C++, ou C ou mesmo o Pascal. Elas memorizam o básico da linguagem, do modo como elas talvez o fizessem com uma lista de peças em uma loja de equipamentos, mas deixam de aprender como combinar estas peças para criarem programas que fazem os computadores renderem o máximo possível. Por essa razão em vez de documentar cada estrutura de dados e tipos de instrução uma a uma, como o manual de um compilador talvez o faça, neste capítulo serão introduzidos os fundamentos sobre as instruções e tipos de dados do C++ em situações práticas, pelo menos na maioria dos casos. Aprenderemos não apenas o que é uma estrutura, mas também como aplicá-la na resolução de problemas.

2.1. Operadores avançados No Capítulo 1 aprendemos sobre os operadores da linguagem C++, que são símbolos que executam ações sobre seus argumentos. Por exemplo, o sinal de mais (+) é um operador com um propósito óbvio, somar dois valores. Nesta seção, porém, encontraremos alguns dos operadores do C++ menos óbvios. Operadores que podem comparar valores, avaliar expressões e manipular bits na memória. Iremos também aprender como usar abreviações práticas para escrever expressões.

2.1.1. Entendendo os operadores relacionais Os operadores relacionais do C++ permitem que se escrevam instruções para comparar valores. Quando um programa executa uma expressão relacional ele avalia seus

Page 36: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 37

argumentos para produzir um resultado, que pode ser verdadeiro ou falso. De posse desse resultado o programa pode então decidir o que fazer a seguir13. Pode-se usar qualquer operador da Tabela 2.1 em expressões para se comparar dois valores, numéricos ou não. Comparar duas strings requer mais trabalho e uma abordagem vetorial, o que será abordado em capítulos mais à frente.

Tabela 0.1 - Operadores relacionais em C++.

Operador Descrição Exemplo < Menor que (a < b) <= Menor ou igual a (a <= b) > Maior que (a > b) >= Maior ou igual a (a >= b) == Igual a (a == b) != Diferente de (a != b)

A expressão (a == b) é avaliada como verdadeira se, e somente se, a e b possuírem o mesmo valor. Já a expressão (a = b) atribui o valor de b à a. O primeiro programa deste capítulo, apresentado no Código 2.1 usa operadores relacionais de um modo típico, possibilitar ao usuário escolher entre várias opções de um menu exibido no vídeo. As opções apresentadas neste programa são a conversão de milhas em quilômetros, quilômetros em milhas ou abandonar o programa sem executar nenhum cálculo.

Código 2.1 - Programa para a conversão de milhas para quilômetros.

#include <stream.h> const float mil_por_km = 0.6214, km_por_mil = 1.6093; const char linha[] = "\n_______________________________\n"; main () { int escolha; float km, mil; cout << linha; cout << "Conversao de MILHAS para QUILOMETROS" << linha; cout << "Digite:\n"; cout << "1 : Milhas para Quilometros\n"; cout << "2 : Quilometros para Milhas\n"; cout << "3 : Quit\n"; cout << "RESPOSTA: "; cin >> escolha; if (escolha == 1)

13 Internamente o compilador C++ reduz uma expressão relacional em um valor inteiro 0, para falso, ou 1 para verdadeiro. Tais números são conhecidos como variáveis lógicas ou Booleanas.

Page 37: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 38

{ cout << linha; cout << "Digite o valor em milhas: "; cin >> mil; km = mil * km_por_mil; cout << mil << " milhas = " << km << " km" << linha; } else if (escolha == 2) { cout << linha; cout << "Digite o valor em quilometros: "; cin >> km; mil = km * mil_por_km; cout << km << " km = " << mil << " milhas" << linha; } else cout << linha << "Fim do programa" << linha; }

2.1.2. O comando If, Else If e Else O uso de expressões relacionais e instruções if/else permite que o programa mude o seu curso tendo como base a sua entrada de dados. O Código 2.1 utiliza um tipo de instrução denominada if/else, que é a principal estrutura tomadora de decisões no C++. Sua utilização é bastante simples, basta apenas acrescentar uma expressão relacional entre parênteses após a palavra-chave if e então o bloco de instruções que se deseja realizar se (do inglês if) a expressão relacional seja avaliada como verdadeira. A estrutura else if funciona da mesma maneira que o if e equivale à pergunta ou se. O uso da estrutura else, como o da estrutura else if, é sempre opcional e este não precisa de expressão relacional, uma vez que a sua finalidade é abranger todos os casos que não foram tratados pelas estruturas if/else if anteriores. Basicamente um bloco if/else if/else funciona com mostrado no código 2.2.

Código 2.2 - A estrutura if / else if / else.

if (expressão relacional 1) {Bloco de comandos a serem executados caso a expressão relacional 1 seja verdadeira} else if (expressão relacional 2) {Bloco de comandos a serem executados caso a expressão relacional 2 seja verdadeira} else if (expressão relacional n) {Bloco de comandos a serem executados caso a n-ésima expressão relacional seja verdadeira}

Page 38: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 39

else {Bloco de comandos a serem executados caso nenhuma expressão relacional seja verdadeira} Seja então o exemplo em que se deseja exibir o valor de uma variável chamada conta, apenas se este for maior que 100, pode-se proceder da seguinte maneira: if (conta > 100) cout << "Conta = " << conta; Quando se deseja que uma instrução if execute um bloco de comandos e não apenas uma linha de comando deve-se delimitar tal bloco por chaves.

2.1.3. Entendendo os operadores lógicos Como já foi visto na seção anterior, os operadores relacionais nos capacitam a escrever expressões que executam ações baseadas na veracidade ou falsidade de várias condições. Os operadores lógicos expandem essa idéia, fornecendo-nos os meios para avaliar expressões relacionais múltiplas (ou interdependentes) de modo a se obter um único resultado, verdadeiro ou falso. Por exemplo, suponha o caso em que se deseja exibir o valor de uma variável chamada conta, apenas se este se encontrar entre 1 e 100.

2.1.3.1. O operador lógico E Uma forma de se fazer isso é usando o operador lógico E, representado em C++ por && (ou and, de acordo com o inglês) que combina o resultado de duas expressões relacionais. O operador lógico && retorna um valor verdadeiro se ambas as expressões relacionais as quais este está associado forem verdadeiras, ou seja, se a primeira e a segunda expressão relacional forem verdadeiras. Para este caso pode-se proceder da seguinte maneira: if ((conta >= 1) && (conta <= 100)) cout << "Conta = " << conta; Poderíamos também escrever a expressão acima de forma a parecer com a expressão matemática 1 < conta < 100 da seguinte forma: if ((1 <= conta) && (conta <= 100)) cout << "Conta = " << conta; Desta forma, um programa que contivesse o código acima só exibiria a mensagem acima caso o valor da variável conta estivesse compreendido entre 1 e 100, inclusive.

2.1.3.2. O operador lógico OU Um outro operador lógico é o OU, representado em C++ por ||. Assim como com &&, pode-se usar o operador || para criar expressões relacionais complexas que executem um

Page 39: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 40

bloco de comandos se a primeira operação relacional ou a segunda forem verdadeiras. Por exemplo: if ((1 == conta) || (conta == 100)) cout << "Conta é igual a 1 ou a 100."; O efeito deste exemplo é a execução da instrução de fluxo de saída apenas se conta for igual ou 1 a 100. Quaisquer outros valores fazem com que a expressão relacional inteira seja avaliada como falsa, o que ocasiona a não execução da instrução de saída. Pode-se criar expressões ainda mais complexas, usando criteriosamente parênteses para combinar && e ||, como por exemplo: if (((1 <= conta) && (conta <= 100)) || (conta == -1)) cout << "Conta é igual a -1 ou esta entre 1 e 100."; Neste caso a instrução de fluxo de saída é executada apenas se conta for um valor entre 1 e 100 ou se for igual a –1. O Código 2.3 apresenta um programa que utiliza os conceitos de operadores lógicos para calcular o consumo de gasolina de veículo e relatar se o seu desempenho é insatisfatório, bom ou excelente.

Código 2.3 - Programa para averiguação do desempenho de automóveis.

#include <stream.h> #include <stdlib.h> const char linha[] = "\n_______________________________\n"; float inicio, fim, litros, km, consumo; main () { cout << linha; cout << setw(30) << "Programa para a averiguacao do desempenho de automoveis." << linha; cout << "\nDigite a leitura inicial do odometro:"; cin >> inicio; cout << "Digite a leitura final do odometro:"; cin >> fim; cout << "Digite o consumo de combustivel (em L):"; cin >> litros; if (litros == 0) { cout << "\aO consumo deve diferente de zero!"; exit(1); } km = fim - inicio; consumo = km/litros; cout << "O automovel apresenta um consumo de " << setprecision(3) << consumo << " km/L."; cout << linha << "O que indica um desempenho ";

Page 40: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 41

if (consumo < 8.503 cout << "INSATISFATORIO!!!" << linha; else if ((consumo >= 8.503) && (consumo < 10.629)) cout << "BOM!!!" << linha; else cout << "EXCELENTE!!!" << linha; } Após solicitar as leituras iniciais e finais do odômetro, o programa apresentado no Código 2.3 usa uma instrução if para verificar se o consumo de combustível informado pelo usuário é igual a zero, o que ocasionaria um erro fatal na linha 22 (ocasionaria uma divisão por zero, o que representa um erro na execução do programa). Caso o usuário informe um consumo de combustível igual a zero a instrução if será executada e após uma mensagem ao usuário o programa tem sua execução interrompida pelo comando exit(1)14. O uso do comando exit para interromper um programa faz com que este envie para o DOS o valor dentro dos parênteses, neste caso 1.

2.1.3.3. O operador lógico NOT Além dos operadores lógicos que funcionam com dois argumentos (ditos binários), o C++ também possui um operador lógico unário denominado NOT e representado por um ponto de exclamação (!). O operador ! reverte o resultado de uma expressão relacional, mudando-a de verdadeira para falsa, ou vice-versa. Para executar uma instrução se uma condição não for verdadeira, pode-se escrever algo do tipo: if (!(conta < 100)) cout << "A conta é >= 100"; A expressão if (!(conta < 100)) consiste na verdade de duas expressões. A primeira é a expressão relacional mais interna (conta < 100), que gera um resultado verdadeiro ou falso dependendo do valor da variável conta. A segunda expressão é a aplicação do operador ! sobre o resultado da primeira expressão, mudando o seu valor. O efeito final é a execução da instrução de saída apenas se conta não for menor que 100.

2.1.4. Avaliando o resultado de operadores Todo operador opera sobre um, ou mais, operandos e a operação por eles executada gera um resultado. Os operandos são avaliados como operandos numéricos ou como operandos lógicos, dependendo do operador em questão. Da mesma forma, o resultado de uma operação pode ser um valor numérico ou um valor lógico. O modo de avaliação dos operandos e o resultado produzido pelos operadores aritméticos, relacionais e lógicos estão resumidos na Tabela 2..

14 A função exit está declarada no arquivo de cabeçalho stdlib.h.

Page 41: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 42

Tabela 0.2 - Modo de avaliação de operandos e resultados produzidos por operadores aritméticos, relacionais e lógicos.

Operadores Operandos Resultado Aritméticos Numéricos Numérico Relacionais Numéricos Lógico Lógicos Lógicos Lógico

2.1.5. Entendendo o operador condicional ternário O operador condicional ternário é o único operador em C++ que opera sobre três expressões. Sua sintaxe geral possui a seguinte construção: exp1 ? exp2 : exp3; A primeira expressão, ou exp1, é sempre avaliada primeiro. Se o seu valor for diferente de zero esta é considerada verdadeira15 e a segunda expressão, a exp2, é avaliada. O resultado da segunda expressão será então o resultado da expressão condicional como um todo. Se a primeira expressão for igual a zero a terceira expressão, a exp3, é avaliada e o seu resultado será o resultado da expressão condicional como um todo. O Código 2.4 apresenta um programa que solicita ao usuário dois números inteiros e, de posse destes, utiliza o operador condicional ternário para verificar qual dos dois é o maior. Esta é uma aplicação dentre muitas deste operador que, apesar de simples, é uma poderosa ferramenta de programação.

Código 2.4 - Uso do operador condicional ternário.

#include <stream.h> main () { int a, b, max; cout << "Digite o valor de a: "; cin >> a; cout << "Digite o valor de b: "; cin >> b; max = (a > b) ? a : b; cout << "O maior valor digitado foi: " << max; }

15 Em C++ não existe o tipo Booleano de variáveis. Assim sendo, o C++ adota como sendo uma expressão verdadeira se o seu resultado numérico for igual a 1 e falsa se este for igual a 0.

Page 42: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 43

2.2. Operadores de Bits Em muitas linguagens computacionais é difícil manipular bits na memória do computador, mas isso não se aplica ao C++. O C++ é uma linguagem de alto nível, mas que não impede que o programador escreva códigos que operem no nível do sistema operacional. Uma razão para se trabalhar diretamente com bits na memória é o armazenamento de informações no menor espaço possível. Por exemplo, um certo bit em um registro de banco de dados poderia representar o status corrente de um fato, sendo 1 para sim e 0 para não, ou 0 para masculino e 1 para feminino. Uma série de bits também poderia representar um conjunto de valores: se o bit 1 estiver ligado, o valor de A é considerado como pertencendo ao conjunto, se o bit 2 estiver ligado, o valor B pertence ao conjunto e assim por diante. Muitos programadores preferem utilizar a linguagem assembler (ou assembly) para programação em baixo nível, uma vez que esta manipula tanto memória quanto hardware bit a bit. Porém, é provavelmente mais fácil usar os operadores de bits do C++ listados na Tabela 2.3. Com estes operadores pode-se manipular bits como se estes fossem valores inteiros, com resultados comparáveis em velocidade e eficiência aos resultados da linguagem assembler.

Tabela 0.3 - Operadores de Bits.

Operador Descrição & E binário | OU binário ^ OU EXCLUSIVO binário

<< Desloca bits para a esquerda >> Desloca bits para a direita ~ Calcula o complemento dos bits

Em expressões, os três primeiros operadores de bits da Tabela 2.3 combinam dois valores conforme as regras para os seus operadores lógicos equivalentes.

2.2.1. O operador binário E O primeiro operador da Tabela 2.3 é o operador binário E. Tal operador só retorna 1 se os dois bits originais comparados forem iguais a 1. Em todos os outros casos o resultado é sempre zero. A Tabela 2.4 mostra os possíveis resultados para o uso do operador E binário.

Tabela 0.4 - Possíveis resultados para o operador E binário.

A & B = C 0 & 0 = 0 0 & 1 = 0 1 & 0 = 0 1 & 1 = 1

Page 43: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 44

O Código 2.5 apresenta um programa que combina dois valores unsigned long v1 e v2 utilizando o operador E binário e atribui o resultado dessa operação à variável v3, de mesmo tipo das anteriores.

Código 2.5 - Uso do operador E binário.

#include <stream.h> unsigned long v1, v2, v3; long double inttobin (unsigned long v); void main () { cout << setw(55) << "Programa exemplo do uso do operador E binario"; cout << "\n\nDigite dois valores inteiros\nv1: "; cin >> v1; cout << "v2: "; cin >> v2; v3 = v1 & v2; cout << setw(20) << dec << v1 << setw(6) << hex << v1 << setprecision(20) << setw(20) << inttobin(v1) << '\n'; cout << 'E' << setw(19) << dec << v2 << setw(6) << hex << v2 << setprecision(20) << setw(20) << inttobin(v2) << '\n'; cout << "============================================\n"; cout << "Igual a:" << setw(12) << dec << v3 << setw(6) << hex << v3 << setprecision(20) << setw(20) << inttobin(v3) << '\n'; } long double inttobin(unsigned long x) { unsigned long resto; long double y, i; y = 0; if (x == 0) y = 0; else if (x == 1) y = 1; else { i = 1; while (x > 1) { resto = x%2; x = (x - resto)/2; y = y + (i * resto); i = i * 10; if (x == 1) y = y + i;

Page 44: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 45

} } return y; } A declaração na linha 3 do Código 2.5 apresenta a declaração de uma função em C++. Os conceitos sobre o uso de funções serão vistos mais à frente mas, por enquanto, note que uma função em C++ tem que ser declarada e a sua declaração é muito semelhante à declaração de uma variável. O resultado da execução do programa apresentado no Código 2.5 é um programa que solicita ao usuário a entrada de dois valores numéricos inteiros, por exemplo 1021 e 15. O resultado do programa será:

1021 3fd 1111111101 E ............................................................................................................................................................................................................................................................. 15 f 1111

======================================= Igual a: 13 d 1101

Assim, o resultado da operação 1021 & 15 é igual a 13. Pode parecer estranho à primeira vista tal operação, mas se lembramos que o operador & é um operador binário fica mais fácil entender qual operação é realizada por este. Note que no resultado apresentado acima o operador & comparou bit a bit os números 1021 e 15, e gerou o resultado 13 de acordo com as regras da Tabela 2.4, assim sendo o resultado da combinação de dois valores A e B com o operador E binário é 1 apenas se os dois bits comparados forem iguais a 1. Um uso típico para o operador E binário é mascarar uma parte de um valor, permitindo apenas que alguns bits do valor original sejam passados para o resultado. Em qualquer posição que aparece um bit 1 na máscara, o resultado conterá o mesmo bit do valor original. Em qualquer posição que aparece um bit 0 na máscara, o resultado conterá o bit 0. O 0 na máscara bloqueia o bit do valor original, impedindo que ele passe adiante.

2.2.2. O operador binário OU O segundo operador da Tabela 2.3 é o operador binário OU. Tal operador retorna 1 se um, ou ambos, bits originais comparados forem iguais a 1. Em todos os outros casos o resultado é sempre zero. A Tabela 2.5 mostra os possíveis resultados para o uso do operador OU binário.

Tabela 0.5 - Possíveis resultados para o operador OU binário.

A | B = C 0 | 0 = 0 0 | 1 = 1 1 | 0 = 1 1 | 1 = 1

Page 45: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 46

O Código 2.6 apresenta o mesmo programa do Código 2.5 modificado apenas para utilizar o operador OU binário16.

Código 2.6 - Uso do operador OU binário.

#include <stream.h> unsigned long v1, v2, v3; void main () { cout << setw(55) << "Programa exemplo do uso do operador OU binario"; cout << "\n\nDigite dois valores inteiros\nv1: "; cin >> v1; cout << "v2: "; cin >> v2; v3 = v1 | v2; cout << setw(20) << dec << v1 << setw(6) << hex << v1 << setprecision(20) << setw(20) << inttobin(v1) << '\n'; cout << "OU" << setw(18) << dec << v2 << setw(6) << hex << v2 << setprecision(20) << setw(20) << inttobin(v2) << '\n'; cout << "============================================\n"; cout << "Igual a:" << setw(12) << dec << v3 << setw(6) << hex << v3 << setprecision(20) << setw(20) << inttobin(v3) << '\n'; } O resultado da execução do programa apresentado no Código 2.6 é um programa que solicita ao usuário a entrada de dois valores numéricos inteiros, por exemplo 1021 e 15. O resultado do programa será:

1021 3fd 1111111101 OU .......................................................................................................................................................................................................................................................... 15 f 1111

======================================= Igual a: 1023 3ff 1111111111

Assim, o resultado da operação 1021 | 15 é igual a 1023. O operador OU binário é usado freqüentemente para inserir bits dentro de valores. Suponha que uma variável contenha o valor 152, ou 10011000 em binário. Para mudar o primeiro e o terceiro bit mais à direita para 1, basta usarmos uma máscara de valor 5, ou 101 em binário. Se entrarmos com tais valores no programa apresentado no Código 2.6, o resultado será:

16 Apesar dos programas apresentados nos Códigos 2.6 e 2.7 usarem a mesma função apresentada no Código 2.5, esta foi suprimida nos dois últimos apenas por uma questão de inteligibilidade dos códigos.

Page 46: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 47

152 98 10011000

OU ........................................................................................................................................................................................................................................................... 5 5 101

======================================= Igual a: 157 9d 10011101

O resultado acima contém todos os bits do valor original acrescidos dos bits da máscara inseridos onde zeros apareciam anteriormente. Essa técnica é útil para ligar bits em registradores e em outras variáveis sem perturbar outros bits existentes.

2.2.3. O operador binário OU exclusivo O terceiro operador da Tabela 2.3 é o operador binário OU exclusivo17. Tal operador retorna 1 apenas se os bits originais comparados forem diferentes entre si. Em todos os outros casos o resultado é sempre zero. A Tabela 2.6 mostra os possíveis resultados para o uso do operador OU exclusivo binário.

Tabela 0.6 - Possíveis resultados para o operador OU exclusivo binário.

A ^ B = C 0 ^ 0 = 0 0 ^ 1 = 1 1 ^ 0 = 1 1 ^ 1 = 0

O Código 2.7 apresenta o mesmo programa do Código 2.5 e do Código 2.6, modificado apenas para utilizar o operador OU exclusivo binário.

Código 2.7 - Uso do operador OU exclusivo binário.

#include <stream.h> unsigned long v1, v2, v3; void main () { cout << setw(55) << "Programa exemplo do uso do operador OU binario"; cout << "\n\nDigite dois valores inteiros\nv1: "; cin >> v1; cout << "v2: "; cin >> v2; v3 = v1 ^ v2; cout << setw(20) << dec << v1 << setw(6) << hex << v1 << setprecision(20) << setw(20) << inttobin(v1) << '\n'; cout << "XOR" << setw(17) << dec << v2 << setw(6) << hex

17 O operador binário E é usualmente tratado por AND binário, o operador binário OU por OR binário e o operador binário OU exclusivo por XOR.

Page 47: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 48

<< v2 << setprecision(20) << setw(20) << inttobin(v2) << '\n'; cout << "============================================\n"; cout << "Igual a:" << setw(12) << dec << v3 << setw(6) << hex << v3 << setprecision(20) << setw(20) << inttobin(v3) << '\n'; } O resultado da execução do programa apresentado no Código 2.7 é um programa que solicita ao usuário a entrada de dois valores numéricos inteiros, por exemplo 1021 e 15. O resultado do programa será:

1021 3fd 1111111101 XOR ....................................................................................................................................................................................................................................................... 15 f 1111

======================================= Igual a: 1010 3f2 1111110010

Assim, o resultado da operação 1021 ^ 15 é igual a 1010.

2.2.4. Aplicações dos operadores binários Saber como, quando e por que usar os operadores &, | e ^ exige prática. Como já mencionado, & é freqüentemente usado para mascarar ou para “desligar” (ou zerar) determinados bits permitindo que outros bits sigam inalterados. Seja o exemplo de aplicarmos o operador E aos números 45001 e 15, obtendo o seguinte resultado:

45001 afc9 1010111111001001 E ........................................................................................................................................................................................................................................................... 15 f 1111

======================================== Igual a: 9 9 1001

O operador | é usado tipicamente para o propósito oposto do operador &, ou seja, para “ligar” (ou mudar o valor para 1) de determinados bits permitindo que outros bits sigam inalterados. Seja o exemplo de aplicarmos o operador OU aos números 45001 e 15, obtendo o seguinte resultado:

45001 afc9 1010111111001001 OU ........................................................................................................................................................................................................................................................ 15 f 1111

======================================== Igual a: 45007 afcf 1010111111001111

O operador ^ é freqüentemente utilizado como uma chave para tornar bits 1 em 0 e 0 em 1. Uma característica do comando XOR é o fato de, ao se aplicar este operador a um argumento e a uma máscara, obtêm-se um resultado tal que, se aplicado novamente o mesmo operador e a mesma máscara ao resultado obtido restaura-se o valor original. Seja o

Page 48: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 49

exemplo de aplicarmos o operador XOR aos números 45001 e 15, obtendo o seguinte resultado:

45001 afc9 1010111111001001 XOR ..................................................................................................................................................................................................................................................... 15 f 1111

======================================== Igual a: 44998 afc6 1010111111000110

Seja agora aplicarmos o operador XOR aos números 44998 e 15, obtendo o seguinte resultado:

44998 afc6 1010111111000110 XOR ..................................................................................................................................................................................................................................................... 15 f 1111

======================================== Igual a: 45001 afc9 1010111111001001

Programas gráficos freqüentemente fazem uso dessa propriedade do operador XOR para fazer formas se moverem no vídeo sem perturbar as imagens de fundo. Um outro uso para XOR é a criptografia, em que uma máscara serve de argumento para XOR, que opera sobre todos os bits de um arquivo, e, assim, disfarça o seu conteúdo. Para restaurar o conteúdo original do arquivo basta aplicar a mesma operação.

2.2.5. Operadores de deslocamento de bits Até agora examinamos apenas três dos seis operadores de bits da linguagem C++ apresentados na Tabela 2.3. Os outros três operadores permitem que expressões desloquem bits para a esquerda e para a direita e façam o complemento de todos os bits de valores inteiros, mudando os zeros para uns e vice-versa. Os operadores de deslocamento de bits se dividem em: i. Operador de deslocamento de bits para a esquerda (<<), utilizado em dois argumentos,

como por exemplo V1 << 2, faz com que o valor de V1 seja deslocado para a esquerda em dois bits.

ii. Operador de deslocamento de bits para a direita (>>), utilizado em dois argumentos,

como por exemplo V1 >> 2, faz com que o valor de V1 seja deslocado para a direita em dois bits.

O Código 2.8 apresenta um programa exemplo da utilização dos operadores de deslocamento de bits, tanto à direita quanto à esquerda. O programa solicita ao usuário que este lhe informe dois números inteiros, de posse destes o programa desloca o primeiro valor de acordo com o segundo valor, para a esquerda e para a direita.

Código 2.8 - Uso dos operadores de deslocamento de bits.

#include <stream.h>

Page 49: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 50

unsigned long v1, v2, v3; void main () { cout << setw(55) << "Programa exemplo do uso dos operadores de deslocamento de bits\n(V1 >> V2 e V1 << V2)"; cout << "\n\nDigite dois valores inteiros\nv1: "; cin >> v1; cout << "v2: "; cin >> v2; v3 = v1 << v2; cout << "\n\nV1 e igual a:"; cout << setw(8) << "INT" << setw(6) << "HEX" << setw (20) << "BIN\n"; cout << setw(20) << dec << v1 << setw(6) << hex << v1 << setprecision(20) << setw(20) << inttobin(v1) << '\n'; cout << "Deslocado de " << v2 << " bits a esquerda resulta em:\n"; cout << setw(20) << dec << v3 << setw(6) << hex << v3 << setprecision(20) << setw(20) << inttobin(v3) << '\n'; v3 = v1 >> v2; cout << "Deslocado de " << v2 << " bits a direita resulta em:\n"; cout << setw(20) << dec << v3 << setw(6) << hex << v3 << setprecision(20) << setw(20) << inttobin(v3) << '\n'; } O resultado do Código 2.8 para, por exemplo, 4 e 1 é:

V1 é igual a: INT HEX BIN 4 4 100

Deslocado de 1 bit à esquerda resulta em: 8 8 1000 Deslocado de 1 bit à direita resulta em: 2 2 10 Note que o resultado para o deslocamento à esquerda de 1 bit do número 4 é 8, e o resultado para o deslocamento à direita de 1 bit do número 4 é 2. Ou seja, quando se desloca um número x de y bits à esquerda e atribui-se este resultado a uma variável z, o compilador faz a seguinte conta:

z = x * 2y

Já quando se desloca um número x de y bits à direita e atribui-se este resultado a uma variável z, o compilador faz a seguinte conta:

z = x / 2y

Page 50: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 51

O resultado do Código 2.8 para, por exemplo, 10 e 4 é:

V1 é igual a: INT HEX BIN 10 a 1010

Deslocado de 1 bit à esquerda resulta em: 160 a0 10100000 Deslocado de 1 bit à direita resulta em: 0 0 0 Note que apesar de 10 / 24 não ser igual a zero este foi o resultado retornado pelo computador. O que acontece é que o compilador desloca quatro bits do número 10, começando do bit mais à direta para o bit mais à esquerda, como o número 10 só possui quatro bits o resultado deverá ser 0. Como os computadores podem deslocar bits muito mais rapidamente do que podem realizar uma multiplicação, o que é verdade mesmo com a presença de um co-processador matemático, quando se deseja multiplicar um valor por potências de 2, normalmente é mais eficiente empregar os deslocamentos de bits do que multiplicações.

2.2.6. O operador binário NOT O operador de bits restante em C++ é o operador NOT binário, representado pelo símbolo ~. Este operador funciona um pouco diferentemente do que os outros operadores de bits, em vez de utilizar dois argumentos, o operador NOT binário é um operador unário que se aplica ao valor localizado à sua esquerda (semelhantemente ao operador NOT lógico (!)). Por exemplo, a expressão ~conta complementa o valor de conta, chaveando todos os bits 1 para 0 e os bits 0 para 1. Como acontece com outros operadores (exceto ++ e --) o uso do operador NOT binário não altera diretamente o valor da variável, no exemplo conta, e deve-se atribuir o resultado desta operação a uma variável quando se deseja armazená-lo. O Código 2.9 mostra um programa exemplo do uso do operador NOT binário.

Código 2.9 - Uso do operador NOT binário.

#include <stream.h> unsigned long v1, v2; void main () { cout << setw(55) << "Programa exemplo do uso do operador NOT binario"; cout << "\n\nDigite um valor inteiro\nv1: "; cin >> v1; v2 = ~v1; cout << "\n\nV1 e igual a:";

Page 51: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 52

cout << setw(8) << "INT" << setw(6) << "HEX" << setw (20) << "BIN\n"; cout << setw(20) << dec << v1 << setw(6) << hex << v1 << setprecision(20) << setw(20) << inttobin(v1) << '\n'; cout << "~V1 e igual a:\n"; cout << setw(20) << dec << v2 << setw(6) << hex << v2 << setprecision(20) << setw(20) << inttobin(v2) << '\n'; } Combinar o operador de complemento com outros operadores, com freqüência o &, pode ser, algumas vezes, útil. Por exemplo, para “desligar” o primeiro bit (torná-lo igual a zero) em uma variável conta, podemos escrever: conta = conta & ~1;

2.2.7. Atribuições combinadas Examine a instrução: conta = conta & ~1; Tal instrução pega o valor da variável conta, aplica-lhe o operador & sobre o complemento do número 1 e, então, atribui o resultado à variável conta. Para esse, e outros casos similares, em que um operador age sobre uma variável e, então, o resultado da operação é atribuído de volta à variável original, o C++ possui um operador especial que abrevia a expressão, realizando o que é conhecido como atribuição combinada. Para utilizar um operador de atribuição combinada basta que se acrescente um sinal de igual a qualquer um dos operadores: +, -, *, /, %, <<, >>, &, ^ ou |. Por exemplo, em vez de se escrever conta = conta & ~1, pode-se escrever: conta &= ~1; O C++ interpreta o operador combinado como uma instrução para executar uma operação com o valor inicial (a variável conta neste caso) e, então, atribuir o resultado de volta a essa mesma variável. A seguir são listados alguns outros exemplos que mostram usos de atribuições combinadas. conta += 10; //Equivale a: conta = conta + 10; conta -= 2; //Equivale a: conta = conta - 2; conta *= 5; //Equivale a: conta = conta * 5; conta *= conta; //Equivale a: conta = conta * conta; conta %= 16; //Equivale a: conta = conta % 16;

Page 52: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 53

Algumas vezes o compilador será capaz de gerar um código mais eficiente para um operador de atribuição combinada do que para uma expressão equivalente não abreviada. Esta é, talvez, a maior vantagem de se usar um operador de atribuição combinada.

2.3. Instruções de fluxo de programa Talvez você já tenha notado uma deficiência em todos os códigos-fonte mostrados nesta apostila. Em todos os casos (exceto nos códigos onde foi utilizada a função inttobin, criada por nós), os programas começam no seu topo e correm em linha reta até o seu término. Obviamente, a maioria dos programas de computador não funcionam assim, pelo contrário, eles percorrem loops18, saltam de uma seção para outra e não terminam até que o usuário lhe dê um comando específico. Nas próximas páginas desta apostila explicaremos como adicionar estas, e outras, características aos seus programas. Programas em C++ têm seu início na primeira instrução dentro do bloco da função main e continuam a executar uma instrução após a outra até alcançarem as chaves que fecham tal bloco. Isso é verdade a menos que uma instrução altere o fluxo normal de um programa. Chamo as instruções desta categoria de instruções de fluxo de programa, apesar desta não ser uma expressão oficial. Elas também são conhecidas por estruturas de controle. Uma instrução de fluxo de programa controla a ordem na qual outras instruções são executadas. Estas podem interromper os programas, tomar decisões, escolher elementos de um conjunto de acordo com alguma condição e repetir uma ou mais instruções. Com instrução de fluxo de programa pode-se escrever programas que rodem até que um determinado evento planejado ocorra ou até que o usuário deseje que o programa pare.

2.3.1. O comando EXIT Em condições normais os programas escritos em C++ se interrompem após a execução da última instrução de main, ou seja, quando se atinge as chaves que fecham o bloco de main. Depois disso, o sistema operacional recupera o controle de modo que se possa dar novos comandos ou executar outros programas. Uma maneira de interromper um programa é através da execução de uma instrução denominada exit. Conforme já mencionado, a instrução exit retorna um valor inteiro, sem sinal, de volta para o sistema operacional, valor este que um arquivo batch pode recuperar, examinando a variável errorlevel. Antes de usar a instrução exit um programa deverá incluir o arquivo cabeçalho stdlib.h. O Código 2.10 mostra um programa em que, de acordo com a vontade do usuário, o programa se encerra e retorna um valor diferente para o sistema operacional.

18 Loop é uma expressão em inglês, sem boas traduções, mas que significa uma estrutura de repetição na qual, após uma série de comandos serem executados, o fluxo do programa volta ao ponto onde este começou, como se estivesse se movendo sobre um círculo.

Page 53: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 54

Código 2.10 - Uso do comando EXIT.

#include <stream.h> #include <stdlib.h> #include <ctype.h> main () { char resposta; cout << "Digite S para sim ou N para não\nRESPOSTA: "; cin >> resposta; resposta = toupper(resposta); if (resposta == 'S') exit(1); else exit(0); cout << "Esta linha do programa NUNCA é executada..."; } O Código 2.10 apresenta uma função ainda não vista, a função toupper, que tecnicamente é uma macro, mas que destina a converter um caractere minúsculo em maiúsculo. Outros símbolos passados a toupper, tais como números, sinais de pontuação ou mesmo letras já em maiúsculas, permanecem inalterados. Para usar a função toupper um programa deverá incluir deverá incluir o arquivo cabeçalho ctype.h. Existe uma outra maneira de se escrever o Código 2.10 que não requer o uso da instrução if e que reduz o tamanho do texto do programa. A expressão exit((toupper(resposta) == 'S')) compara o resultado de toupper(resposta) com o caractere 'S', retornando o valor 0 se a expressão for falsa e 1 se esta for verdadeira, valor este que é passado imediatamente à instrução exit. O Código 2.11 apresenta o mesmo programa do Código 2.10, porém agora em sua versão mais “curta”.

Código 2.11 - Uso do camando EXIT (versão simplificada).

#include <stream.h> #include <stdlib.h> #include <ctype.h> main () { char resposta; cout << "Digite S para sim ou N para nao\nRESPOSTA: "; cin >> resposta; exit(toupper(resposta) == 'S'); }

Page 54: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 55

2.3.2. O comando WHILE O comando while, também denominado de loop while, tem por finalidade a repetição de uma ou mais instruções enquanto uma determinada condição for verdadeira. Por exemplo, se conta for uma variável inteira, o loop while que segue abaixo exibirá na tela do computador uma contagem de 1 até 10. conta = 1; while (conta <= 10) cout << conta++ << '\n'; Pode-se também executar um bloco de instruções dentro de um loop while, para isso basta delimitar o bloco de instruções com chaves. Em geral, um loop while tem a seguinte forma: while (expressão) { //Instruções a serem executadas enquanto a expressão for //verdadeira } Sendo que a (expressão) pode ser qualquer expressão que gere um resultado verdadeiro ou falso, ou seja lógico.

2.3.3. O comando SWITCH Quando se elabora um programa em que este precisa selecionar uma de várias ações durante a sua execução, pode-se usar uma série de instruções if/else. Por exemplo, suponha que um programa deverá executar determinadas ações, de acordo com uma variável resposta do tipo caractere, quando esta for igual a 'A', 'B' ou 'C'. Para fazer isso poderia-se usar uma estrutura do tipo: if (resposta == 'A') { //Instruções a serem executadas se resposta == A;} else if (resposta == 'B') { //Instruções a serem executadas se resposta == B;} else if (resposta == 'C') { //Instruções a serem executadas se resposta == C;} else { //Instruções a serem executadas quando resposta possuir //outro valor que não A, B ou C;} Não há nada de errado com a construção anterior, a não ser que esta é muito longa e que pode facilmente levar a erros. Uma alternativa à construção anterior é o uso do comando switch, que permite que se atinja os mesmos objetivos da construção anterior, mas de uma forma mais inteligível, se assemelhando a uma tabela. A seguir é apresentada uma versão da construção anterior, porém usando o comando switch. Nesta construção, os cases (ou casos – 'A', 'B' e 'C' neste exemplo) marcam as seções que devem ser

Page 55: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 56

executadas se o seletor (neste exemplo a variável resposta) for igual a um dos valores indicados após os cases. switch (resposta) { case 'A': { // Instruções a serem executadas se resposta == A; break; } case 'B': { // Instruções a serem executadas se resposta == B; break; } case 'C': { // Instruções a serem executadas se resposta == C; break; } default: { //Instruções a serem executadas quando resposta //possuir outro valor que não A, B ou C; } } A instrução começa com a palavra-chave switch e uma expressão entre parênteses, expressão esta que deverá retornar um valor inteiro ou um caractere. Os seletores case devem ser constantes ou expressões constantes. O resultado da expressão do switch é comparado com todas as constantes dos seletores case subseqüentes, cada um dos quais devendo terminar com dois-pontos (:). Os valores apresentados nos seletores case devem ser únicos, podendo estar dispostos em qualquer ordem. Somente as instruções abaixo do primeiro case cuja constante seletora for igual ao resultado da expressão é que serão executadas. As várias instruções break marcam o fim das instruções de cada case. Sem um break o programa começaria a executar as instruções a partir do primeiro case aceito e continuaria até o final da instrução switch. se nenhum dos valores case for igual ao resultado da expressão, as instruções após o case default serão executadas, sendo que o uso deste é de caráter opcional. De modo diferente de outras instruções em C++, instruções múltiplas em cases dentro de instruções switch não precisam ser obrigatoriamente cercadas por chaves. Uma instrução switch torna-se prática sempre que um programa necessita selecionar uma ação dentre as diversas possíveis tendo como base o resultado de uma expressão ou o valor de uma variável, equivalentes a um valor inteiro ou a um caractere. Um exemplo típico onde uma instrução switch funciona bem é a criação de menus de opções em programas, onde o usuário digita uma letra (ou número) para escolher uma dentre várias opções. O Código 2.12 apresenta um programa que exibe um menu de cinco opções ao usuário, A de adicionar, S de subtrair, M de multiplicar, D de dividir e Q de quit (do inglês sair do programa). Apesar deste exemplo não fazer nenhum cálculo, ele demonstra a utilização do

Page 56: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 57

comando switch para fazer a seleção das opções do menu e do comando while para forçar o programa a só terminar a sua execução quando o usuário solicitar.

Código 2.12 - Uso do comando WHILE e do comando SWITCH.

#include <stream.h> #include <stdlib.h> #include <ctype.h> const char linha[40] = "\n*****************************\n"; char resposta; main () { while ((toupper(resposta)) != 'Q') { cout << linha << "* *\n" << "* Calculadora de 4 operacoes feita em C++ *\n" << "* *\n" << "*Digite: *\n" << "* A para adicao *\n" << "* S para subtracao *\n" << "* M para multiplicacao *\n" << "* D para divisao *\n" << "* Q para sair do programa *\n" << "* *\n" << "*RESPOSTA: "; cin >> resposta; cout << linha; switch (toupper(resposta)) { case 'A': cout << "*Voce selecionou o modulo de adicao *"; break; case 'S': cout << "*Voce selecionou o modulo de subtracao *"; break; case 'M': cout << "*Voce selecionou o modulo de multiplicao*"; break; case 'D': cout << "*Voce selecionou o modulo de divisao *"; break;

Page 57: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 58

case 'Q': cout << "*Voce selecionou sair do programa!!! *"; break; default: cout << "*Opcao invalida. Tente de novo!!! *"; } cout << linha; }

2.3.4. O comando DO/WHILE A instrução do/while executa uma, ou mais instruções enquanto uma expressão seja verdadeira. A diferença do comando do/while para o comando while é que o teste da expressão relacional não é feito no começo do loop, mas sim em seu término. Em geral, o loop do/while se apresenta como mostrado no Código 2.13.

Código 2.13 - Uso do comando DO/WHILE.

do { //Instruções a serem executadas enquanto a expressão for //verdadeira; } while (expressão); A grande diferença entre um loop while e um loop do/while é que o primeiro pode nunca ser executado, caso a expressão de controle seja falsa, ao passo que o último é sempre executado pelo menos uma vez, pois o programa não avalia a sua expressão de controle até que o loop tenha sido executado uma vez. Seja o exemplo em que deseja-se contar até 10, poderia-se usar um loop do/while da seguinte maneira: int conta = 0; do { cout << conta++ << '\n'; } while (conta <= 10); Mas se a variável conta fosse inicializada com o valor conta = 12, e não conta = 0, o programa apresentado acima imprimiria na tela o valor 12 na tela, ou seja, o programa seria executado uma vez, o que não aconteceria se o loop fosse feito usando o comando while. Na prática os loops while tendem a ser mais comuns que os loops do/while, apesar de ambos possuírem usos distintos. Uma boa maneira de decidir qual dos dois loops usar é

Page 58: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 59

respondendo à seguinte pergunta: “Existe alguma condição passível de ocorrer, na qual este loop não devesse ser executado nem mesmo uma única vez?”. Se a resposta for sim, o uso de um loop while é mais apropriado. Se a resposta for não, utilize um loop do/while.

2.3.5. O comando FOR Quando se sabe, ou quando o programa pode calcular previamente o número de vezes que um bloco de instruções deve ser executado, o uso do comando for é normalmente a melhor escolha para a construção de um loop. Todo comando for em C++ apresentam os seguintes elementos:

� A palavra-chave for � Três expressões, delimitadas por parênteses e separadas por ponto-e-vírgulas � Uma instrução, ou um bloco de instruções a serem executados

Um loop for combina os três elementos acima tendo como formato geral o Código 2.14.

Código 2.14 - Uso do comando FOR.

for (instrução1; expressão; instrução2) { //Instruções a serem executadas; } Dentro dos parênteses após a palavra-chave for existem três elementos que controlam a execução do loop for. Em primeiro lugar há uma instrução (instrução1), que é executada uma única vez, antes do início do loop. Normalmente esta instrução inicializa uma variável que será utilizada pelos próximos dois elementos. O próximo elemento, a expressão, é uma expressão relacional que o comando for testa sempre que o fluxo do programa se encontrar no início do loop. O último elemento, a instrução2, é executada no final do loop, após a execução do bloco de instruções internas do loop e antes que o fluxo do programa retorne ao início do loop. A descrição precedente faz mais sentido quando comparada a um loop while que executa as mesmas instruções do loop for mostrado no Código 2.14. instrução1; while (expressão) { //Instruções a serem executadas; instrução2; } Para criar um programa que usasse o comando for para exibir uma contagem de 0 a 10 bastaria poucas linhas. Por exemplo: int conta; for (conta = 0; conta <= 10; conta++)

Page 59: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 60

cout << conta << '\n'; O que equivale exatamente ao loop while abaixo: int conta = 0; while (conta <= 10) { cout << conta << '\n'; conta++; } Apesar do efeito dos dois loops apresentados acima serem idênticos, o loop for é mais conciso e após ter aprendido como construí-lo o leitor provavelmente o achará mais legível e inteligível que os loops while equivalentes. Um programa exemplo do uso do comando for é apresentado no Código 2.15, que exibe na tela uma tabela dos caracteres ASCII.

Código 2.15 - Programa para exibição dos caracteres da Tabela ASCII.

#include <stream.h> unsigned char caractere; int i; main () { for (caractere = 0; caractere <= 254; caractere++) { cout << setw(6) << i << " = " << caractere; i++; if ((caractere%7) == 0) cout << '\n'; } }

2.3.6. Elementos de um loop FOR múltiplo Pode-se separar elementos de um loop for múltiplo por vírgulas, para que sejam executadas mais de uma inicialização e expressões dentro do mesmo loop. Por exemplo, para contar uma variável interia i de 0 a 9 e, ao mesmo tempo, contar uma outra variável inteira j de 9 a 0, pode-se escrever: for (i = 0, j = 9;((i <= 9)&&(j >= 0));i++, j--) cout << "\ni = " << i << " j = " << j;

Page 60: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 61

2.3.7. Loop eterno usando o comando FOR Quando a expressão existente no comando for nunca seja falsa, o que ocasiona no fim do loop, dizemos que o loop é um loop eterno, pois este nunca terá fim19, ou simplesmente que este é um loop FOREVER. O exemplo mais simples de um loop for infinito é o caso: for (;;) { //Instruções a serem realizadas "eternamente"; } O loop acima não inicializa nada, não testa nada e não incrementa nenhuma variável de controle. Os dois ponto-e-vírgulas dentro dos parênteses são necessários para marcar o lugar dos elementos que foram omitidos.

2.3.8. O comando BREAK Algumas vezes é útil interromper o progresso de um loop while, do/while ou for. Para faze-lo basta que se insira uma instrução break no ponto onde se deseja parar o loop. O Código 2.16 mostra um programa exemplo do comando break.

Código 2.16 - Uso do comando BREAK.

#include <stream.h> int i; main () { cout << "Loop for:\n"; for (i = 1;i <= 10;i++) { if (i > 5) break; cout << i << '\n'; } ................................................................................................... cout << "\nLoop while:\n"; i = 1; while (i <= 10) { if (i > 5) break; cout << i << '\n'; i++; } cout << "\nLoop do/while:\n"; i = 1;

19 Evidentemente que o loop será finalizado se o usuário desligar o PC, mas este não é o caso, quando digo que o loop não terá fim me refiro a um fim causado pelo próprio programa.

Page 61: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 62

do { if (i > 5) break; cout << i << '\n'; i++; } while (i <= 10); } O Código 2.16 contém um loop for, um loop while e um loop do/while, todos os quais tem a mesma finalidade, contar de 1 até 10 usando uma variável do tipo inteiro i. Entretanto, em cada caso, o comando break interrompe o loop assim que a variável atinge o valor 5. Uma instrução break é útil para o tratamento de erros dentro de loops complexos e para fornecer uma saída de um loop em execução, seja ele eterno ou não.

2.3.9. O comando CONTINUE Enquanto um comando break encerra imediatamente um loop, uma instrução semelhante, mas oposta em sua concepção é o comando continue, que força um loop a começar a sua próxima iteração, voltando ao início do bloco de instruções. O Código 2.17 demonstra a diferença entre os dois comandos.

Código 2.17 - Uso do comando CONTINUE.

#include <stream.h> int i; main () { cout << "Início do loop usando continue...\n"; for (i = 1;i <= 10;i++) { if (i > 5) continue; cout << i << '\n'; } cout << "\nApós o loop o contador será igual a: " << i << "\n\nInício do loop usando break...\n"; for (i = 1;i <= 10;i++) { if (i > 5) break; cout << i << '\n'; } cout <<"\nApós o loop o contador será igual a: " << i; } O resultado do Código 2.17 é que ao final do primeiro loop a variável i será igual a 11 e ao final do segundo loop esta será igual a 6.

Page 62: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 63

2.9. Exercícios do capítulo 2.1.Em expressões relacionais, quais valores são utilizados internamente pelo compilador

C++ para representar verdadeiro e falso? 2.2. Explique a diferença entre == e =. 2.3. Escreva um programa em C++ utilizando um loop que solicite ao usuário um valor

entre 1 e 100. Se o usuário digitar um valor fora dessa faixa o programa deverá exibir uma mensagem de erro e solicitar o valor novamente, só encerrando o loop quando o usuário digitar um valor entre 1 e 100.

2.4. Escreva a instrução while equivalente ao loop for que se segue: for (int contador = 0; contador > -8; contador--) { cout << "\nValor do contador: " << contador; cout << "\n--"; } 2.5. Usando operadores de bits, escreva um programa que limite a faixa de valores de uma

variável inteira qualquer entre 0 e 31. 2.6. Escreva um programa que criptografe uma string após a entrada de uma senha e, então,

recupere a mesma string. Use o operador XOR em sua resposta. 2.7. Escreva um programa que determine se um número inteiro digitado pelo usuário é par

ou ímpar.

Page 63: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 64

3 – ESTRUTURAS DE DADOS

“Os chips de memória do computador são feitos de silício, isto é, de areia, literalmente.

Como partículas de areia, a memória é apenas um monte de pequenos pedaços sem forma...”

TOM SWAN Para dar forma à memória o C++ possui diversas características que impõem estruturas na memória do mesmo modo que os castelos de areia impõem uma estrutura a esta. Além disso as estruturas de dados são compostas de pedaços (bits) de memória, assim como os castelos de areia são compostos de partículas de areia. Nesta seção você conhecerá as estruturas de dados do C++, as quais lhe permitem reunir campos de variáveis, armazenar informações em vetores e manipular bits com a utilização de técnicas normalmente mais convenientes que os operadores de bits descritos no início deste capítulo.

3.1. Estruturas confiáveis para o armazenamento de dados Pode-se reunir variáveis, até mesmo aquelas de tipos diferentes, com o uso da palavra-chave struct20, a fim de criar um novo tipo de dados que reserve memória suficiente para guardar diversas variáveis relacionadas de alguma maneira no mesmo lugar. Um exemplo típico onde struct é útil é em um programa de banco de dados voltado para o armazenamento de registros que contenham uma variedade de campos. Por exemplo, suponha que você seja o presidente de um clube de emagrecimento e precise registrar os nomes, pesos e números telefônicos dos membros do clube. Assim sendo, provavelmente você precisará de uma série de variáveis, tais quais: char nome1[30] = "Marta"; float peso1 = 110.0; char telefone1[15] = "031-3561-1524"; char nome2[30] = "Jose"; float peso2 = 119.0; char telefone2[15] = "031-3561-1525"; char nome3[30] = "Pedro"; float peso3 = 130.0; char telefone3[15] = "031-3561-1526"; 20 A palavra-chave struct já foi usada nesta apostila no Código 1.6.

Page 64: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 65

Digitar separadamente cada nome, peso e número telefônico é monótono e entope o programa com variáveis confusas. Pior ainda, se houver a necessidade de se inserir ou apagar um nome, você terá que escrever novas instruções que tratarão cada variável, o que não é nada prático. Um método mais indicado é criar um novo tipo de dados que possa armazenar nomes, pesos e números telefônicos conjuntamente. Você poderá, então, escrever instruções que operem sobre este tipo de estrutura e, portanto, sobre as informações de qualquer membro do seu clube. Para fazê-lo comece declarando um novo tipo de dados com a palavra-chave struct, conforme é mostrado a seguir: struct membro { char nome[30]; float peso; char telefone[15]; }; Esta construção declara um novo tipo de dado chamado membro, com a capacidade de armazenar três campos, que são o nome (uma string de 30 caracteres), o peso (um número real) e o telefone (uma string de 15 caracteres). Não se pode esquecer do ponto-e-vírgula necessário no final da declaração após o fecha-chaves21. Um tipo de dados struct não reserva nenhuma parte da memória para o armazenamento de informações, o comando struct apenas cria um molde que diz ao compilador a natureza da estrutura. Os campos podem até parecer definir variáveis, mas como as declarações estão dentro da estrutura de um struct não o fazem. Após definir um tipo struct, pode-se criar variáveis desse tipo do mesmo modo como se faz para outros tipos de dados, ou seja: membro membro1; A declaração precedente cria uma variável chamada membro1 do tipo membro. Tal declaração reserva espaço na memória para todos campos declarados dentro da struct, neste caso uma string de 30 caracteres, um valor real e uma string de 15 caracteres. O Código 2.18 apresenta um exemplo onde os principais tipos de dados do C++ são declarados dentro de uma mesma struct.

Código 3.1 - Uso do comando STRUCT para os principais tipos de dados em C++.

struct ExemploDeStruct { float UmFloat;

21 Ele é fácil de ser esquecido porque blocos de instruções com aparência similar às declarações do tipo struct não terminam com ponto-e-vírgula.

Page 65: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 66

int UmInt; char UmaString[8]; char UmChar; long UmLong; } Em alguns casos o tamanho de uma variável struct pode ser maior que a soma dos campos declarados. Isso ocorre porque o C++ “recheia” os campos que contenham um número ímpar de bytes de modo que todos os campos principiem num endereço de número par (veja o byte rotulado de “enchimento” na Figura 2.1). A razão para tal é manter maior rapidez na execução de programas em sistemas que possuam processadores 80x86 de 16 bits, que podem ler dados mais rapidamente em endereços pares do que em endereços ímpares.

Figura 3.1 - Os bytes de cada campo de uma variável struct são armazenados na memória

consecutivamente.

Para criar uma variável chamada Exe, do tipo ExemploDeStruct (vide Código 2.18) e atribuir valores a cada um dos seus campos, pode-se proceder da seguinte maneira: ExemploDeStruct Exe; //Declara a variável Exe. Exe.UmFloat = 4.3541; //Atribui um valor ao campo

//UmFloat. Exe.UmInt = 2001; //Atribui um valor ao campo UmInt Exe.UmaString = "Abc"; //Atribui uma string ao campo

//UmaString. Exe.UmChar = 'M'; //Atribui um caractere ao campo

//UmChar. Exe.UmLong = 2147483640; //Atribui um valor longo ao campo

//UmLong. Assim sendo, a variável Exe é uma variável do tipo ExemploDeStruct e possui cinco campos declarados em ExemploDeStruct, cada um dos quais é acessado escrevendo-se o nome da variável seguido de um ponto final e do nome do campo. Esta forma de referência é algumas vezes chamada de tipo de notação de ponto.

Page 66: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 67

As expressões Exe.UmLong, Exe.UmChar e Exe.UmFloat, por exemplo, são inteiramente qualificadas, significando que elas contêm informações suficientes para que o compilador possa tratá-las como se fossem de um tipo simples. Não se pode escrever algo como Exe = 15 porque Exe é do tipo ExemploDeStruct. Como pode ser feito com outras variáveis, pode-se declarar e inicializar uma struct com uma instrução única. Para fazê-lo basta que se insira os valores dos campos entre chaves e separados por vírgulas, após a definição da variável. Note que os valores deverão estar ordenados da mesma maneira que os campos. Por exemplo, para se criar uma variável chamada exemplo, do tipo ExemploDeStruct e inicializá-la, pode-se proceder da seguinte maneira: ExemploDeStruct exemplo = {4.3541, 2001, "Abc", 'M', 2147}; Cada campo deverá ser separado do próximo por uma vírgula e, ao final da declaração da variável, deve-se terminar com um ponto-e-vírgula. Além disso, não se pode “pular” nenhum campo, apesar de se poder parar a inicialização antes de se atribuir valores a todos os campos. Por exemplo, para se atribuir apenas UmFloat e UmInt, deixando de lado os outros campos de uma variável do tipo ExemploDeStruct, poder-se-ia escrever ExemploDeStruct exemplo2 = {4.3541, 2001}; Após declarar um tipo de dados struct e definir uma ou mais variáveis desse tipo, pode-se atribuir valores para essas variáveis, exibir os seus valores no vídeo, utilizá-las em expressões, etc. Para demonstrar algumas dessas possibilidades, suponha que você precise pesquisar os modelos, as velocidades, a capacidade do HD e outros detalhes dos computadores da sua empresa. Você decide então armazenar essas informações em uma struct chamada inventario. Os Códigos 2.19 e 2.20 mostram algumas das instruções necessárias para se escrever um programa de banco de dados. Compile apenas o Código 2.20, que contém o código anterior. Estes dois códigos são os primeiros de muitos exemplos de programas armazenados em mais de um arquivo. O Código 2.19 é o arquivo de cabeçalho22 que declara alguns elementos que serão usados no programa do Código 2.20, inclusive a struct que irá armazenar a descrição dos computadores da sua empresa. Outros programas que precisarem criar variáveis do tipo pc podem simplesmente incluir o arquivo de cabeçalho pcdb.h, ou seja, não precisam declarar novamente o mesmo tipo de dados. Isso economiza tempo de digitação e mantém as declarações comuns a vários programas em um único lugar, de modo que apenas um único arquivo precise ser editado se for necessário modificar os campos de inventario.

Código 3.2 - Arquivo de cabeçalho (.h) típico de um banco de dados.

//Arquivo de cabecalho tipico para um banco de dados const char linha[80] = "______________________________\n"; enum boolean {FALSE, TRUE}; struct inventario

22 Para maiores informações sobre arquivos de cabeçalho consulte o Capítulo 1.1.7.

Page 67: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 68

{ char *modelo; //Nome do modelo do PC char *cpu; //Tipo da CPU do PC char *monitor; //Tipo do Monitor do PC int velocidade; //Velocidade do PC em MHz int numfloppy; //Numero de floppy drives int capacidadehd; //Capacidade do HD em GB int ram; //Quantidade de RAM instalada boolean modem; //PC possui Modem instalado? char *modelomodem //Modelo do Modem boolean cdrom; //PC possui drive de CDROM? int velcdrom; //Velocidade da cdrom em x boolean cdrw; //PC possui drive CDRW? char *velcdrw; //Velocidade da cdrw boolean placa3d; //PC possui placa 3D? char *modelo3d; //Modelo da placa de video 3D boolean dvd; //PC possui drive de DVD? char *veldvd; //Velocidade do DVD };

Código 3.3 - Uso do comando STRUCT em banco de dados.

#include <stream.h> #include "pcdb.h" main () { inventario pc; pc.modelo = "AMD K6II 3D NOW"; pc.cpu = "80686"; pc.monitor = "Sansung SyncMaster 450b"; pc.velocidade = 450; pc.numfloppy = 1; pc.capacidadehd = 6; pc.ram = 128; pc.modem = TRUE; pc.modelomodem = "US ROBOTICS 56K WinModem"; pc.cdrom = TRUE; pc.velcdrom = 56; pc.cdrw = TRUE; pc.velcdrw = "6x4x32"; pc.placa3d = TRUE; pc.modelo3d = "Diammond Viper RIVA 128 de 4 MB"; pc.dvd = FALSE; cout << linha << setw(53) << "Meu computador atualmente e:\n\n";

Page 68: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 69

cout << setfill('.') << "Modelo" << setw(63) << pc.modelo << "\nCPU" << setw(66) << pc.cpu << "\nMonitor" << setw(62) << pc.monitor << "\nVelocidade" << setw(50) << pc.velocidade << "\nNumero de floppy drives" << setw(46) << pc.numfloppy << "\nCapacidade do HD (em GB)" << setw(45) << pc.capacidadehd << "\nMemoria RAM (em MB)" << setw(50) << pc.ram;

if (pc.modem) cout << "\nModelo do modem" << setw(54)

<< pc.modelomodem; if (pc.cdrom) cout << "\nVelocidade do CDROM" << setw(50)

<< pc.velcdrom; if (pc.cdrw) cout << "\nVelocidade do CDRW" << setw(51)

<< pc.velcdrw; if (pc.placa3d) cout << "\nModelo da Placa 3D" << setw(51)

<< pc.modelo3d; if (pc.dvd) cout << "\nVelocidade do DVD" << setw(52)

<< pc.veldvd; } O Código 3.2 utiliza a declaração char *modelo e declarações similares. Estas são declarações de ponteiros, tópico que será abordado no Capítulo 4 desta apostila. Por enquanto, entenda-os como sendo uma outra forma de se criar variáveis do tipo string, por exemplo modelo. Adiante, ainda neste capítulo será explicado mais sobre esse formato. A linha 6 do Código 3.3 define uma variável chamada pc do tipo inventario. Isso pode ser feito porque a declaração da struct inventario foi incluída previamente na linha 2, na forma do arquivo de cabeçalho pcdb.h. As linhas 8 a 23 atribuem valores aos campos da variável pc, fazendo isso individualmente. O Código 3.4 é semelhante ao Código 3.3, mas usa uma única instrução para declarar e inicializar a variável pc do tipo inventario. Esse é um método conveniente para a inicialização de estruturas e o leitor deve-se familiar com tal formato. O resto do Código 3.4 é idêntico ao Código 3.3.

Código 3.4 - Declaração e inicilaização conjunta de uma variável STRUCT.

#include <stream.h> #include "pcdb.h" main ()

Page 69: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 70

{ inventario pc = { "AMD K6II 3D NOW", "80686", "Sansung SyncMaster 450b", 450, 1, 6, 128, TRUE, "US ROBOTICS 56K WinModem", TRUE, 56, TRUE, "6x4x32", TRUE, "Diammond Viper RIVA 128 de 4 MB", FALSE}; cout << linha << setw(53) << "Meu computador atualmente e:\n\n"; cout << setfill('.')

<< "Modelo" << setw(63) << pc.modelo << "\nCPU" << setw(66) << pc.cpu << "\nMonitor" << setw(62) << pc.monitor << "\nVelocidade" << setw(50) << pc.velocidade << "\nNumero de floppy drives" << setw(46) << pc.numfloppy << "\nCapacidade do HD (em GB)" << setw(45) << pc.capacidadehd << "\nMemoria RAM (em MB)" << setw(50) << pc.ram;

if (pc.modem) cout << "\nModelo do modem" << setw(54)

<< pc.modelomodem; if (pc.cdrom) cout << "\nVelocidade do CDROM" << setw(50)

<< pc.velcdrom; if (pc.cdrw) cout << "\nVelocidade do CDRW" << setw(51)

<< pc.velcdrw; if (pc.placa3d) cout << "\nModelo da Placa 3D" << setw(51)

<< pc.modelo3d; if (pc.dvd) cout << "\nVelocidade do DVD" << setw(52)

<< pc.veldvd; }

3.2. Estruturas aninhadas Uma struct pode residir dentro de outra, criando um tipo de dados complexo, mas com inúmeras possibilidades de utilização para o armazenamento de todos os tipos de informações. Por exemplo, em um banco de dados de nomes e endereços de clientes de uma seguradora, talvez se deseje incluir informações sobre o computador pessoal de cada cliente da seguradora. Ao invés de declarar novamente todos os campos do Código 3.1, poder-se-ia apenas introduzir a estrutura inventario dentro da nova estrutura criada, como por exemplo:

Page 70: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 71

struct cliente { char *nome; char *endereco; int idade; inventario pc; } Declarado dessa forma, um cliente é uma estrutura com quatro campos: nome, endereço, idade e pc da pessoa. Os primeiros três campos são idênticos, no formato, àqueles já estudados. O último campo, no entanto, é do tipo inventario, a struct declarada no cabeçalho pcdb.h. Para criar uma variável chamada cliente1 e atribuir a esta variável um nome, endereço e idade, pode-se proceder da seguinte maneira: cliente cliente1; cliente1.nome = "Arthur of Pendragon"; cliente1.endereco = "Camelot"; cliente1.idade = 32; Entretanto, atribuir valores ao campo pc requer um pouco mais de trabalho. Como esse campo é uma estrutura do tipo inventario, introduzida dentro de outra estrutura do tipo cliente, deve-se utilizar a notação de ponto, juntamente com ambos os nomes dos tipos de dados referentes aos campos da estrutura inventario e da estrutura cliente. cliente1.pc.modelo = "PENTIUM IV"; cliente1.pc.cpu = "80886"; cliente1.pc.monitor = "Sansung SyncMaster 550b";

3.3. As uniões As uniões são quase idênticas às estruturas, e, de fato, se não for tomado o devido cuidado, pode-se facilmente confundir as duas, ocasionando em resultados desastrosos. Apesar de estruturas e uniões possuírem formatos gêmeos, estas devem ser encaradas mais como primas que como gêmeas idênticas. Como já visto, as estruturas reúnem vários campos em um pacote conveniente, ou seja, uma struct permite armazenar variáveis relacionadas conjuntamente, o que é conveniente quando se precisa manter registros de bancos de dados. Já as uniões, declaradas com a palavra-chave union, também podem armazenar campos múltiplos em um único pacote. Porém, em vez de encontramos esses campos uns após os outros na memória, como acontece com uma struct, em uma union todos os campos se sobrepõem na mesma localidade da memória. O uso mais comum das uniões é na criação de um tipo de dados que pode ser usado como dois ou mais tipos de dados diferentes. Por exemplo, podemos criar uma união de um tipo long com um tipo float, mas trata-lo, também, como se fosse um tipo char. Isso não

Page 71: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 72

significa que as uniões do C++ convertem campos de um tipo para outro. O que acontece é que os bits na memória, que armazenam as informações para cada campo, são apenas interpretados como sendo de um determinado tipo de dados para algumas instruções e como um outro tipo de dados para outras instruções. Considere uma união declarada como: union ExemploDeUnion { float UmFloat; long UmLong; } Em uma variável do tipo ExemploDeUnion, os campos UmFloat e UmLong ficam armazenados no mesmo endereço da memória do computador (vide Figura 2.2). Isso significa que se um programa atribui um valor ao campo UmFloat, ele mudará também o valor do campo UmLong, o que não significa que existem duas variáveis na união. Existe apenas um valor representado de dois modos. O tamanho da union é igual ao tamanho do maior elemento nesta declarado.

Figura 3.2 - Os bytes de cada campo de uma variável union são armazenados na mesma posição da

memória.

Um exemplo prático ajuda, e muito, a entender melhor o funcionamento das uniões em C++. O Código 3.5 apresenta um programa que declara uma união com dois campos, um campo do tipo char e outro do tipo int. Quando se compila e executa o Código 3.5 o resultado é a exibição do alfabeto na tela do computador.

Código 3.5 - Uso do comando UNION.

#include <stream.h> union charint { char c; int i; }; charint ci; main () { int i; for (i = 65; i < 91; i++) { ci.i = i;

Page 72: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 73

cout << ci.c; } } Para entender como o Código 3.5 exibe o alfabeto, observe atentamente as linhas 2 a 7. Elas declaram uma union chamada charint que contém dois campos, um do tipo char chamado c e outro do tipo int chamado i. Exceto pela palavra-chave union, esse formato é idêntico ao formato utilizado pelas declarações de estruturas, ou seja, de uma struct. A linha 7 declara uma variável ci do tipo charint. As linhas 12 a 16 executam um loop for que a cada iteração incrementa uma variável do tipo int chamada i, sendo que a única finalidade desta variável é atribuir o seu valor ao campo i da variável, ou seja, o campo ci.i. A linha 15 exibe então o outro campo da união, o campo ci.c. Apesar de nenhuma instrução ter atribuído qualquer valor ao campo c da variável ci.c, o programa ainda assim exibe o alfabeto. Obviamente alguma coisa alterou o valor do campo c. Tal coisa trata-se da atribuição na linha 14. Como todos os campos em uma união se sobrepõem, atribuir valores a um campo afeta todos os outros campos da variável. Neste exemplo, atribuir os valores 65 até 90 ao campo i também atribui os mesmos valores ao campo c, que são os códigos ASCII para as letras A até Z.

3.4. Vetores Da mesma forma que estruturas e uniões, os vetores também reúnem vários pedaços de informação. Todos os vetores possuem duas características principais. Em primeiro lugar, são declarados para armazenar uma ou mais variáveis do mesmo tipo de dados. Por exemplo, um vetor pode armazenar variáveis do tipo int ou uma struct declarada anteriormente no programa, mas não pode armazenar uma struct e uma variável do tipo int. A segunda característica típica dos vetores é o seu tamanho. Quando se defini um vetor, deve-se planejar antecipadamente quantos elementos serão armazenados neste vetor, além do tipo dos dados que serão armazenados neste vetor, conforme já mencionado. Esse é um passo importante porque o compilador só pode reservar memória suficiente para o vetor se lhe for dito quantos elementos o comporão. Dito de maneira simples, o compilador multiplica o número de itens pelo tamanho de cada um e reserva o montante de bytes para o vetor23. Uma demonstração do uso de vetores e de como criá-los e usá-los é mostrada no Código 3.6, onde um programa declara, preenche e exibe o valor de um vetor de cem posições de números inteiros, ou seja, do tipo int e de tamanho igual a 100. A linha 3 do Código 3.6 mostra como é feita a definição de um vetor em C++. A definição é similar à definição de uma variável de um tipo qualquer, porém, acrescida de colchetes ao

23 É possível escrever programas que calculem o tamanho de vetores e reservem espaço para eles em tempo de execução com o uso de ponteiros. Assim, pode-se criar programas que criem vetores cujos tamanhos variem dependendo da necessidade. Tal assunto será abordado no Capítulo 5.

Page 73: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 74

redor de uma constante literal após o nome da variável que se deseja declarar. O par de colchetes é o símbolo de vetor em C++. A declaração da linha 3 cria um vetor de 100 posições, todas elas do tipo inteiro, armazenadas em posições consecutivas da memória do computador. Cada um dos inteiros no vetor é uma variável distinta, com seu próprio espaço na memória (vide figura 3.3).

Código 3.6 - Uso de VETORES em C++.

#include <stream.h> int vetor[100]; int i; main () { //Preenche o vetor com valores de 0 a 99 for (i = 0; i <= 99; i++) vetor[i] = i; //Exibe o conteudo do vetor crescente e decrescentemente cout << "Valores do vetor de [0] a [99]:\n"; for (i = 0; i <= 99; i++) cout << setw(5) << vetor[i]; cout << "\n\nValores do vetor de [99] a [0]:\n"; for (i = 99; i >= 0; i--) cout << setw(5) << vetor[i]; }

Figura 3.3 - Representação esquemática de um vetor de 100 números inteiros.

Para acessar um dos elementos do vetor (por exemplo para exibir o seu valor) basta que se especifique o elemento que desejado com o seu respectivo índice dentro de colchetes, logo após o nome do vetor, conforme foi feito nas linhas 14 e 17 do Código 3.6.

Page 74: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 75

Uma importante consideração a respeito de vetores em C++ é o fato de que o primeiro elemento de todos os vetores em C++ corresponde ao índice de número zero (0), e não ao índice de número um (1). Em outras palavras, para exibir os cinco primeiros valores do tipo inteiro do vetor declarado no Código 3.6 poder-se-ia escrever: cout << vetor[0] << '\n'; cout << vetor[1] << '\n'; cout << vetor[2] << '\n'; cout << vetor[3] << '\n'; cout << vetor[4] << '\n'; Obviamente, para exibir os cinco primeiros valores do tipo inteiro do vetor declarado no Código 3.6 é muito mais fácil (e mais eficiente) usar um loop for, com por exemplo: for (i = 0; i <= 4; i++) cout << vetor[i] << '\n'; Vetores, em C++, podem também ser indexados com expressões, desde que estas expressões gerem um resultado inteiro, positivo e compatível com os índices dos vetores. Assim sendo, qualquer expressão, variável ou constante que representa, ou quando avaliada gera um valor inteiro, serve como um índice de vetor válido. Uma boa utilização de vetores é o armazenamento de variáveis struct múltiplas. Por exemplo, para criar um vetor de 50 registros do tipo inventario, usando as declarações do arquivo de cabeçalho pcdb.h apresentado no Código 3.1, bastaria escrever: inventario sistemas[50]; A declaração acima define um vetor chamado sistemas, capaz de armazenar 50 variáveis struct do tipo inventario, que é uma estrutura mais conveniente do que 50 variáveis do tipo inventario individuais. Para mudar o nome do sexto registro da variável sistemas, basta escrever: sistemas[5].nome = "PS/2"; Assim como com os vetores simples de variáveis do tipo int, o nome do vetor (sistemas) é seguido de um valor de índice entre colchetes ([5]). O C++ reconhece que essa parte da expressão refere-se a um elemento do vetor, neste caso um registro do tipo inventario. Como esse registro é uma struct, para chegar até os campos, a instrução qualifica a referência com o uso da notação de ponto seguido do nome do campo (nome). Um outro programa exemplo que mostra como os vetores podem ser úteis para guardar informações que o próprio programa pode usar mais tarde é apresentado no Código 3.7, que permite que o usuário entre com 18 notas de alunos, de uma avaliação qualquer. De posse das notas o programa calcula a média das notas.

Page 75: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 76

Código 3.7 - Programa para cálculo da média de 18 números utilizando vetores.

#include <stream.h> #include <stdlib.h> const int max_notas = 18; float notas[max_notas]; int i, numero_notas; float soma, media; main () { //Solicita ao usario as notas e as armazena em no vetor for (numero_notas = 0; numero_notas < max_notas; numero_notas++) { cout << "Digite o valor da nota " << (numero_notas + 1) << " (-1) para sair: "; cin >> notas[numero_notas]; if (notas[numero_notas] < 0) break; } if (numero_notas <= 0) exit (0); //Nenhuma nota foi digitada //Exibe as notas digitadas armazenadas no vetor e calcula a soma das notas cout << "\nAs notas digitadas foram:"; for (i = 0; i < numero_notas; i++) { cout << setw(5) << "\nNota " << (i + 1) << " = " << notas[i]; soma += notas[i]; } //Calcula e exibe o valor da media das notas media = soma / numero_notas; cout << "\nForam digitadas " << numero_notas << " notas" << "\nA soma das notas digitadas foi igual a: " << setw(15) << soma << "\nA media das notas digitadas foi igual a: " << setw(14) << media; } Combinar tipos de dados dessa maneira (vetores de structs que contêm outros campos ou até mesmo outros vetores de algum outro tipo) é um conceito poderoso. Em geral, podemos combinar tipos de dados simples, tais como int e float, com structs, unions e vetores para se construir os mais variados tipos de castelos de areia em nossos programas.

Page 76: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 77

3.4.1. Inicializando vetores Quando definimos um vetor de qualquer tipo, assim como as outras variáveis em C++, seus valores podem ou não ser inicializados automaticamente. Se um vetor é global, todos os elementos que pertencem ao vetor recebem o valor zero (0), se o vetor for local, os seus elementos podem (e provavelmente conterão) valores diferentes de zero, ou seja, lixo24. Pode-se provar as afirmações acima executando um programa teste, conforme mostrado no Código 3.8.

Código 3.8 - Programa para teste da inicialização de vetores.

#include <stream.h> int i, vetor1[10]; main () { int vetor2[10]; for (i = 0; i < 10; i++) cout << "vetor global[" << (i + 1) << "] = "

<< vetor1[i] << setw(15) << "vetor local[" << (i + 1) << "] = " << vetor2[i] << '\n';

} O resultado da execução do programa apresentado no Código 3.8 é a exibição de zero (0) para todos os elementos do vetor1, que é um vetor global, e a exibição de uma série de números aparentemente escolhidos ao acaso para os elementos do vetor2, que é um vetor local. Isso acontece porque os vetores se comportam como todas as variáveis em C++. Variáveis globais são inicializadas com 0, mas variáveis locais não são inicializadas com quaisquer valores específicos. Uma maneira de inicializar os elementos de um vetor é atribuindo valores para todos os seus elementos usando um loop for, o qual pode ser semelhante a: for (i = 0; i < 10; i++) vetor[i] = 0; Um outro meio de inicializar os elementos de um vetor é listando valores constantes, dentro de chaves, após a definição do vetor. O C++ lê cada valor listado e o atribui para posições sucessivas dentro do vetor. Por exemplo, para inicializar um vetor de 10 inteiros com valores de 10 até 1 e, então, exibir estes valores em ordem reversa, basta escrever: int vetor[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; 24 Para maiores informações sobre variáveis, globais e locais, e a sua inicialização automática pelo compilador vide os Capítulos 1.2.2. e 1.2.3.

Page 77: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 78

for (int i = 9; i >= 0; i--) cout << "vetor[" << i << "] = " << vetor[i] << '\n';

3.5. Matrizes Todos os vetores já mostrados até aqui são vetores ditos lineares, pois possuem apenas uma dimensão. Algumas vezes, entretanto, os vetores precisam ter mais de uma dimensão. Um vetor não linear é chamado de matriz. As matrizes podem ter duas ou mais dimensões. Um exemplo do uso de um vetor que necessita ter mais de uma dimensão, ou seja, um exemplo do uso de uma matriz, é a tela do monitor do computador. As linhas e as colunas na tela são facilmente representadas por uma estrutura de dados bidimensionais, contendo 25 linhas e 80 colunas, normalmente. Em C++, podemos definir uma matriz deste tamanho (25 x 80) da seguinte maneira: int monitor[25][80]; Definida com dois pares de colchetes e tamanhos, a matriz monitor é tecnicamente um vetor de vetores, capaz de armazenar 25 vetores do tipo int, sendo cada um destes vetores um vetor capaz de armazenar 80 posições também do tipo int. É mais fácil, no entanto, visualizar este tipo de vetor como sendo um conjunto de linhas e colunas (um matriz). Um outro exemplo de matriz é: int multi[7][7]; O vetor multi é composto por sete vetores de sete inteiros, mas a maioria das pessoas a vê como sendo uma matriz de sete linhas e sete colunas de valores inteiros (vide Figura 3.4).

Figura 3.4 - Um vetor bidimensional é um vetor de vetores, mas também pode-se encará-lo como sendo

uma matriz.

Page 78: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 79

Como mencionado anteriormente, todos os vetores são indexados a partir de zero (0) em C++, uma regra que também se estende às matrizes. Para se exibir todos os elementos da matriz multi pode-se usar um loop for inserido dentro de outro da seguinte maneira: for (int linha = 0; linha <= 6; linha++) { for (int coluna = 0; coluna <= 6; coluna++) cout << setw(10) << multi[linha][coluna]; cout << '\n'; } As matrizes são feitas sob medida para o armazenamento de séries de fatos cobrindo categorias. Um exemplo demonstra bem esta idéia e, com um pouco de trabalho extra, o programa que será discutido a seguir pode se transformar em uma ferramenta bastante útil. Suponha que o leitor seja gerente de um escritório de pessoas muito ocupadas, e deseja saber que horários elas têm disponíveis para as reuniões do grupo. Um modo de se fazer isso é perguntar a todos os funcionários quando eles prefeririam se reunir. Se isso não funcionar, o leitor poderia escrever um programa para procurar, em um vetor de horas para as reuniões, os horários que os funcionários têm disponíveis. O Código 3.9 mostra como resolver este problema utilizando uma matriz. Execute o programa e entre com o número entre 0 e 3, inclusive, referente a um funcionário.Então entre com as horas, de 8 até 17, em que essa pessoa já assumiu algum compromisso. Entre com –1 quando findar a digitação das horas. Depois disso tecle um novo número de funcionário e forneça as horas ocupadas deste. Proceda desta maneira para um máximo de quatro funcionários e, ao final, entre com –1 para encerrar essa fase. O programa irá exibir uma tabela com a agenda de compromissos de cada funcionário e uma lista com os possíveis horários de reuniões, isto é, quando todos os funcionários estão com os seus horários vagos.

Código 3.9 - Exemplo do uso de matrizes em C++.

#include <stream.h> #include <conio.h> const int ftotal = 4; const int htotal = 10; const int hzero = 8; enum funcionarios {JOSE, SAMUEL, PAULA, MARIA}; int agenda[htotal][ftotal]; main () { int fn, hora; cout << linha <<"| CALCULO DE HORARIO DE REUNIOES |\n"

<<"|FUNCIONARIOS: |\n" <<"| 0 Jose da Silva |\n"

Page 79: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 80

<<"| 1 Samuel Coelho |\n" <<"| 2 Paula Cristina dos Santos |\n" <<"| 3 Maria Aparecida Ribeiro |\n";

//Leitura dos compromissos dos funcionarios for (;;) { cout << "|Digite o numero do funcionario (-1 para sair): "; cin >> fn; if (fn < 0) break; if (fn > ftotal) { cout <<"|O numero do funcionario esta fora dos limites estipulados|\n"; continue; } for (;;) { cout <<"Hora do compromisso?(8-17,-1 para sair):"; cin >> hora; if (hora < 0) break; if ((hzero <= hora) && (hora < hzero + htotal)) agenda[hora - hzero][fn] = 1; else cout << "| Hora fora dos limites... |\n"; } } cout << "| AGENDA DE COMPROMISSOS |\n" << setw(80) << "| JOSE | SAMUEL | PAULA | MARIA |\n" for (hora = 0; hora < htotal; hora++) { cout << '|'<< setw(6) << (hora + hzero) << ":00" << setw(7) << '|'; for (fn = 0; fn < ftotal; fn++) { if (agenda[hora][fn] != 0) cout << setw(7)<< 'X'<< setw(6) << '|'; else cout << setw(13) << '|'; } cout << '\n' << linha; } cout << "|Prescione qualquer tecla para continuar\n"; getch()25;

25 A função getch()lê um único caractere diretamente do teclado, sem que este seja mostrado na tela do vídeo. Para usar a função getch() deve-se incluir o cabeçalho conio.h.

Page 80: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 81

clrscr(); cout << "| POSSIVEIS HORARIOS PARA UMA REUNIAO |\n"; for (hora = 0; hora < htotal; hora++) { int k = 0; for (fn = JOSE; fn < ftotal; fn++) k += agenda[hora][fn]; if (k == 0) cout << (hora + hzero) << ":00 "; } } Quando você for examinar o Código 3.9, preste muita atenção no modo como as constantes hzero (representando a hora inicial) e htotal (representando o total de horas do expediente) são usadas para o cálculo de índices da matriz. Como todos os vetores em C++ usam índices que começam em 0, usar vetores indexados por valores entre 8 e 17 requer cuidadosa programação. Obviamente, você poderia simplesmente declarar um vetor de 24 posições, cada posição representando uma hora do dia, e aproveitar apenas as posições entre 8 e 17.

3.6. Strings Agora que o leitor tem conhecimento do que são vetores, você está pronto para considerar a verdadeira natureza das strings e responder à pergunta: “O que, afinal de contas, é uma string?”. Se você respondeu “um vetor de caracteres”, acertou em cheio. Como temos visto em vários códigos-fonte nesta apostila, para declarar uma string de 80 caracteres basta escrever: char string[80]; Esta é, obviamente, apenas uma definição de um vetor que cria uma variável chamada string com espaço para 80 elementos do tipo char. Em C++, uma string é um vetor de caracteres, e não um tipo de dados “string”, como acontece em outras linguagens de programação, tais como PASCAL e BASIC. Em uma instrução do tipo: char string[15] = "Luis Felipe"; O compilador C++ não copia os caracteres da string "Luis Felipe" para as posições da memória reservadas para o vetor do tipo char string. O C++ não copia caracteres dentro de uma instrução ou definição que atribua uma string literal a um vetor do tipo char. Em vez disso ele atribui o endereço do primeiro caractere da string à variável do tipo vetor. É necessária muito menos “energia” para fazer uma variável apontar para uma string já armazenada na memória do que para copiar os caracteres da string de uma posição para outra, fato esse que o C++ utiliza para manter os programas se executando mais rapidamente.

Page 81: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 82

3.6.1. Inicializando vetores de caracteres Como acontece com outros vetores, pode-se inicializar vetores do tipo char alinhando os elementos individuais que comporão o vetor entre chaves, após a definição do vetor. Normalmente, no entanto, o mais fácil é simplesmente atribuir uma string como nos exemplos já vistos anteriormente (utilizando aspas para delimitá-la). Todavia, se o leitor preferir, pode-se se inicializar um vetor de caracteres da seguinte maneira: char exemplo[20] = {'N','A','O',' ','U','S','U','A','L'}; A atribuição acima parece atribuir os caracteres individuais a posições sucessivas do vetor, porém, como com todos os vetores de caracteres, o que realmente acontece é que o C++ armazena os caracteres, além do demarcador NULL, na memória e atribui o endereço de 'Q' à variável exemplo. A declaração anterior é idêntica a declaração: char exemplo[20] = "NAO USUAL"; //Declaração mais legível A declaração anterior é usualmente mais fácil de se lidar do que listar caracteres individuais entre chaves.

3.7. Vetores e ponteiros Uma outra maneira de se declarar uma string, conforme já visto em alguns códigos anteriormente, é usando o conceito de ponteiros (assunto que será retomado no Capítulo 5), por exemplo a variável char *modelo definida no Código 3.1. O asterisco à frente do nome da variável diz ao C++ que modelo é um ponteiro para uma ou mais variáveis do tipo char armazenadas em uma localidade na memória. Apesar da falta de colchetes na definição, o C++ permite que modelo seja utilizado como um vetor. A semelhança entre ponteiros como char *modelo e vetores como char modelo[20] é mais que superficial. Todos os vetores em C++ são implementados como ponteiros. Suponha que definamos um vetor como este: int vetor[10]; Quando o compilador processa essa definição ele reserva espaço para 10 números inteiros e atribui o endereço do primeiro inteiro à variável vetor. Pode até parecer que a variável vetor armazena os inteiros dentro dela mesma, mas, no código compilado do programa, a variável vetor é implementada como um ponteiro para o primeiro elemento desse vetor. Variáveis definidas como char *modelo e char modelo[20] são funcionalmente equivalentes, significando que se pode usar modelo de muitos modos semelhantes em instruções de programas. Ambas as definições criam ponteiros para a memória onde caracteres ficam armazenados, mas apenas a definição com colchetes reserva memória para esses caracteres. Por essa razão, até que você aprenda a usar ponteiros no Capítulo 5 use o

Page 82: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 83

tipo de definição com colchetes para criar vetores em seus próprios programas. Declarações dentro de estruturas (struct), no entanto, tem-se necessariamente que usar a forma de declaração com ponteiro (asterisco) para criar campos de strings de comprimentos variáveis, como ilustrado no Código 3.1.

3.8. Campos de bits Freqüentemente ocorre que campos em uma estrutura nunca excedem um certo valor, geralmente pequeno. Por exemplo, em um banco de dados de fatos sobre pais, um campo que representa o número de crianças muito raramente será maior que 15 e, normalmente, será igual a 2 ou 3. O campo que representa o sexo de uma pessoa necessitará de apenas um único bit, 0 para masculino e 1 para feminino. Tal estrutura se parecia com a estrutura a seguir. struct pessoa { unsigned idade; //0...99 unsigned sexo; //0 = masculino, 1 = feminino unsigned filhos; //0...15 }; São necessários seis bytes, dois para cada campo int da estrutura anterior. Infelizmente, há muito espaço desperdiçado nesta estrutura. Por exemplo, o campo sexo requer apenas um único bit para representar o seu valor, e os outros 15 bits deste inteiro nunca serão usados. Se a estrutura pudesse “empacotar” a informação dentro de um espaço menor, com apenas tantos bits quanto os necessários para representar cada campo, ela deixaria de desperdiça-los. A maneira de se fazer isso é com o uso de um campo de bits, que se assemelha a uma struct, porém especifica o número de bits que cada campo da estrutura deverá ocupar, dentro do tamanho de um inteiro (int). Vejamos a seguir como a estrutura anterior se parece quando convertida para um campo de bits: struct pessoa { unsigned idade :7; //0...99 unsigned sexo :1; //0 = masculino, 1 = feminino unsigned filhos :4; //0...15 unsigned :4; //Não usado }; Colocar após cada nome de campo dois-pontos e um valor literal unsigned faz com que esse campo fique empacotado de acordo com o número especificado de bits na estrutura, que pode totalizar até 16 bits para todos os seus campos. No exemplo acima, 7 bits são reservados para o campo idade, 1 bit para o campo sexo e 4 bits para o campo filhos.

Page 83: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 84

A grande maioria dos compiladores C++ permitem que estruturas de campos de bits sejam maiores que 16 bits. Entretanto, o limite oficial para o tamanho de um campo de bits é o mesmo que para uma variável do tipo int, ou seja, 16 bits. Não há nenhuma regra estabelecida se o tamanho máximo de um campo de bits pode, ou não, ser maior que o tamanho de um inteiro, e provavelmente cada compilador construirá os seus campos de bits de modo diferente. O último campo de bits que aparece na estrutura pessoa não recebeu nenhum nome. Ele apenas reserva uma quantidade de espaço e sua finalidade é fazer com que o número total de bits do campo de bits seja igual a 16. Esse campo é opcional, mas é usualmente incluído para podermos responder por todos os bits da estrutura. Pode-se também utilizar mais de um campo desse tipo para forçar os outros campos a começarem em determinados bits. Por exemplo, uma porta de entrada pode definir certos bits com vários significados: se um byte esta disponível, qual o status da porta, se ela está pronta para enviar outro byte, etc. Poderíamos usar uma estrutura de campos de bits para ler essa informação para dentro de um programa, mas devemos usar campos para reservar espaço para fazer com que alguns campos coincidam com posições de valores específicos. Use campos de bits como se faz com campos de outras estruturas. Por exemplo, para exibir na tela a idade e o número de filhos de um campo de bits pessoa poderíamos escrever: pessoa p; ... cout << "\nIdade: " << p.idade << "\nFilhos: " << p.filhos; De maneira diferente que em outras linguagens de programação, em C++ não é preciso extrair os bits da estrutura e não tem-se que usar operadores de bits para isolar valores dos campos de seus vizinhos. Em vez disso, apenas utilize os campos como se fossem do tipo int. O C++ cuida dos detalhes confusos de extrair e inserir os campos de bits empacotados na estrutura sem alterar os outros bits lá presentes. Obviamente, deve-se ser cuidadoso para não atribuir valores maiores do que aqueles que um campo pode representar. Exceto por esse detalhe, pode-se usar campos de bits como se fossem variáveis unsigned int individuais. Lembrando que os campos de bits devem sempre ser declarados em structs, e o seu uso não é permitido em unions. O Código 3.10 mostra como usar campos de bits para extrair informações sobre o hardware de um computador. Tais informações se encontram empacotadas na BIOS do computador. O programa usa uma estrutura de campos de bits para interpretar uma informação retornada pela função _bios_equiplist, declarada no arquivo de cabeçalho bios.h. Quando o programa é executado ele detecta quantas unidades de disco, portas seriais e portas de impressoras estão instaladas, se um adaptador de jogos está disponível e a configuração inicial do vídeo do PC.

Código 3.10 - Exemplo do uso de um CAMPO DE BITS.

#include <stream.h> #include <bios.h>

Page 84: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 85

struct equipamentos { unsigned diskette : 1; unsigned : 1; unsigned planar : 2; unsigned videomode : 2; unsigned numfloppy : 2; unsigned : 1; unsigned numserial : 3; unsigned gameadaptador : 1; unsigned : 1; unsigned numprinters: 2; }; union doistipos { equipamentos eq; int k; }; doistipos dt; main () { dt.k = _bios_equiplist(); cout << "Numero de impressoras" << setw(48) << dt.eq.numprinters << "\nAdaptador de jogos instalado (1)" << setw(37) << dt.eq.gameadaptador << "\nNumero de portas seriais" << setw(45) << dt.eq.numserial << "\nNumero de drives de diskette"; if (dt.eq.diskette) cout << setw(41) << dt.eq.numfloppy; else cout << setw(41) << '0'; cout << "\nResolucao de video (2)" << setw(46) << dt.eq.videomode << "\nMemoria RAM plana (3)" << setw(46) << dt.eq.planar << "\n\n\nObservacoes:" << "\n(1) 0 = FALSO e 1 = VERDADEIRO;" << "\n(2) 1 = 40 * 25 color; 2 = 80 * 25 color; 3 = monocromatico" << "\n(3) 1 = 128 MB"; }

Page 85: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 86

Os campos de bits restringem, normalmente, a utilização de programas a hardwares específicos. Executar o programa apresentado no Código 3.10 em qualquer máquina que não um legítimo IBM, PC, AT, PS/2 ou clone poderá fazer com que o programa não funcione corretamente. Alem disso, como código-fonte se baseia nas declarações do arquivo de cabeçalho bios.h, o programa pode também não funcionar corretamente em outros compiladores C++, já que este é um arquivo de cabeçalho que é distribuído juntamente com o compilador.

3.9. O operador SIZEOF Um operador de extrema importância quando se fala em itens dependentes do sistema é o operador sizeof, que é utilizado para determinar o número de bytes ocupado por uma variável ou por um tipo de dados. Por exemplo, para exibir quantos bytes um float ocupa pode-se escrever: cout << "Tamanho de um float" << sizeof(float); O C++ substitui a expressão sizeof(float) pelo número de bytes que uma variável do tipo float ocupa na memória do computador. De modo similar, pode-se utilizar o operador sizeof para se determinar o tamanho de um vetor, de uma união, de uma estrutura ou de um campo de bits. Para isso basta apenas colocar o nome da variável, ou do tipo de dados, entre parênteses após o operador sizeof. O Código 3.11 demonstra como usar o operador sizeof para determinar o tamanho de todos os tipos de dados básicos da linguagem C++. Este programa é um teste útil a ser executado em outros compiladores, ou mesmo em outros sistemas, para comparar os tamanhos de dados básicos assumidos por compiladores e por sistemas diferentes.

Código 3.11 - Uso do operador SIZEOF.

#include <stream.h> main () { char a = 'A'; short b = -7; int c = 156; long d = 2147483640; float e = 10.5; double f = 0.00045; long double g = 1e-6; cout << setfill('.')

<< "Tamanho de um char: " << setw(40) << sizeof(a) << " byte(s)\n" << "Tamanho de um short: " << setw(39) << sizeof(b) << " byte(s)\n"

Page 86: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 87

<< "Tamanho de um int: " << setw(41) << sizeof(c) << " byte(s)\n" << "Tamanho de um long: " << setw(40) << sizeof(d) << " byte(s)\n" << "Tamanho de um float: " << setw(39) << sizeof(e) << " byte(s)\n" << "Tamanho de um double: " << setw(38) << sizeof(f) << " byte(s)\n" << "Tamanho de um long double: " << setw(33) << sizeof(g) << " byte(s)\n";

} Programadores cuidadosos utilizam o operador sizeof para escrever programas que sejam independentes do sistema em que estes irão ser executados. Ao invés de assumir que uma variável do tipo int ocupa dois bytes, uma instrução pode usar a expressão sizeof(int). Compilar tal instrução em sistemas diferentes dá ao programa meios de determinar quantos bytes um inteiro ocupa. O programa apresentado no Código 3.11 pode ser modificado para calcular o tamanho da estrutura inventario, apresentada no Código 3.1, incluindo-se neste apenas as seguintes linhas. #include "pcdb.h" inventario pc; cout << "Tamanho da struct inventario: " << setw(30) << sizeof(pc) << " byte(s)\n"; Usar sizeof para determinar o tamanho de um vetor ou de uma estrutura é preferível a assumir o tamanho destes como sendo igual à soma do tamanho de cada campo, ou elemento, que compõe a estrutura ou o vetor, respectivamente. O operador sizeof é o único meio confiável de se determinar o verdadeiro tamanho de uma estrutura. A aplicação do operador sizeof a uma união retorna o tamanho do maior elemento da união.

3.10. Exercícios do capítulo 2.8.Escreva loops for, while e do/while que exibam o alfabeto. Insira os loops dentro

de uma instrução switch que permita ao usuário selecionar qual loop deverá ser executado.

2.9. Escreva um programa para exibir caracteres de uma string separados por espaços em

branco. Por exemplo, se o usuário digitar ABCDEFG, o programa deverá exibir A B C D E F G.

Page 87: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 88

4 – FUNÇÕES: PROGRAMANDO POR PARTES

Até este ponto já examinamos a maioria dos operadores e das estruturas de dados da linguagem C++. Com esses elementos, e com a ajuda das instruções de fluxo de controle podemos escrever programas mais sofisticados, que repetem ações e que tomam decisões. Infelizmente, na medida em que seus programas crescem, você perceberá que main() tende a se expandir, como um balão de ar quente com sua válvula de entrada aberta. A menos que se faca algo para aliviar a pressão interna, cedo ou tarde o balão explodirá. Assim sendo, a menos que se faça algo para impedir o abarrotamento de main() com instruções após instruções, seus programas talvez “entrem em pane” à medida que se tornem mais difíceis de serem criados e mantidos. Uma função main() que se estenda por páginas de código-fonte é, além de deselegante, susceptível a bugs. Bons programadores evitam escrever códigos-fonte desta maneira. Em vez disso, eles dividem os seus programas em pequenas partes, que são mais fáceis de se administrar. Tal como main(), estas pequenas partes são denominadas de funções, e é chegada a hora de se explicar o que são funções e como podemos usa-las para escrever até mesmo os mais complexos programas em C++, sem nunca trabalhar com pedaços de código-fonte que não sejam pequenos e de fácil entendimento26. Uma função é um tipo de mini-programa dentro de um programa. Elas reúnem varias instruções sob um único nome que, desta forma, pode ser chamado uma ou mais vezes pelo programa, para a execução dessas instruções. Funções economizam espaço, reduzindo as repetições, e tornam a programação mais fácil, fornecendo-nos um meio de dividir um grande projeto em pequenos módulos, de fácil concepção e administração.

4.1. Chamando uma função Um programa pode conter uma ou mais funções, das quais uma delas deve ser a função main(). A execução do programa sempre começa na função main() e, quando o controle do fluxo do programa encontra uma instrução que inclui o nome de uma função, esta é chamada. Chamar uma função é o meio pelo qual solicitamos que o programa desvie o seu fluxo de controle, passando à execução da função e, ao termino desta, volte à instrução imediatamente posterior à chamada da função.

26 Se você já programou em BASIC ou em linguagem Assembler, reconhecerá as funções em C++ como elementos muito similares às sub-rotinas. Se você conhece Pascal, perceberá que as funções em C++ são equivalentes aos procedimentos e às funções do Pascal. Mesmo se você já possuir experiência em C, você deve, ainda assim, ler este Capítulo, pois as funções em C++ podem parecer iguais às funções em C, mas existem muitas diferenças entre as funções destas duas linguagens.

Page 88: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 89

Você já escreveu, mesmo sem saber ao certo, programas que chamam funções. Como por exemplo o programa apresentado no Código 2.28, que utiliza a função sizeof(), a função setw(), etc. Várias funções, tais como sizeof(), são fornecidas em arquivos de cabeçalho distribuídos junto com o compilador e agregadas aos programas que as chamam durante a compilação do programa. Outras funções podem ser escritas pelo próprio programador. A sintaxe para a chamada de uma função é a mesma tanto para funções escritas pelo programador quanto para funções do sistema.

4.2. Funções simples O Código 4.1 mostra uma função simples, destinada à conversão de um valor de temperatura em graus Fahrenheit para graus Celsius. Como se pode ver, a estrutura de uma função em C++ é semelhante à da função main(). A única diferença é que a função main() possui um nome reservado especial.

Código 4.1 - Exemplo do uso de uma FUNÇÃO simples.

#include <stream.h> float celsius (float fahr); //Prototipo da funcao Celsius main () { float c, f; cout << "Digite a temperatura em graus Fahrenheit: "; cin >> f; c = celsius(f); //Chamada a funcao cout << setw(5) << f << " graus Fahrenheit = " << setw(5) << c << " graus Celsius"; } float celsius (float fahr) //Definicao da funcao { float c; c = (fahr - 32) * 5 / 9; return c; } Os componentes necessários para se adicionar uma função a um programa são: o protótipo de uma função, a chamada à função e a definição da função.

Page 89: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 90

4.3. Protótipo de funções Da mesma forma que com relação a uma variável, não podemos também utilizar uma função sem antes declará-la. A declaração de uma função é chamada de protótipo de uma função e é uma instrução colocada, geralmente, no início do programa. Assim sendo, o protótipo de uma função deve preceder a sua definição e a sua chamada. O protótipo de uma função estabelece o tipo da função e os argumentos que esta recebe. O protótipo de uma função tem a mesma forma da definição da função, anterior ao corpo da função, exceto por terminar com ponto-e-vírgula. O corpo de uma função, que é a função propriamente dita, são todas as instruções executadas quando a função é chamada. O principal propósito de se escrever protótipos de funções em C++ é fornecer ao compilador as informações necessárias sobre o tipo da função e o número e tipo dos argumentos que esta usará. Sem o protótipo da função, o compilador não teria como checar se há erros no uso da função. No Código 4.1, o protótipo da função é apresentado na linha: float celsius (float fahr); //Prototipo da funcao Celsius O protótipo acima informa ao compilador que a função de nome celsius() é do tipo real e recebe como argumento um valor também real, que será armazenado na variável fahr. A informação do nome da variável que receberá o argumento é facultativa, mas a informação do seu tipo é obrigatória. Poderíamos escrever o mesmo protótipo da seguinte forma: float celsius (float); //Prototipo da funcao Celsius

4.3.1. Protótipo externo e local Existem duas formas de se declarar funções em C++. A mais usada é a chamada protótipo externo, e é escrita antes de qualquer função, como no exemplo apresentado no Código 4.1. Esta declaração é feita uma única vez e é visível para todas as funções que a chamam. A outra forma de se declarar uma função é a chamada protótipo local, e é escrita no corpo de todas as funções que a chamam, porém antes da sua chamada. O Código 4.2 apresenta o mesmo programa do Código 4.1, porém este agora utiliza a função celsius() como sendo de protótipo local.

Código 4.2 - Uso de funções de protótipo local.

#include <stream.h> main () { float celsius (float fahr); //Prototipo local da funcao Celsius float c, f;

Page 90: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 91

cout << "Digite a temperatura em graus Fahrenheit: "; cin >> f; c = celsius(f); //Chamada a funcao cout << setw(5) << f << " graus Fahrenheit = " << setw(5) << c << " graus Celsius"; } float celsius (float fahr) //Definicao da funcao { float c; c = (fahr - 32) * 5 / 9; return c; }

4.3.2. Eliminando o protótipo de funções Se a definição da função for feita antes da instrução de sua chamada, o seu protótipo não é obrigatório. Assim sendo, poderíamos reescrever o Código 4.1, omitindo-se o protótipo da função celsius() como mostrado no Código 4.3.

Código 4.3 - Uso de funções sem protótipo.

#include <stream.h> float celsius (float fahr) //Definicao da funcao { float c; c = (fahr - 32) * 5 / 9; return c; } main () { float c, f; cout << "Digite a temperatura em graus Fahrenheit: "; cin >> f; c = celsius(f); //Chamada a funcao cout << setw(5) << f << " graus Fahrenheit = " << setw(5) << c << " graus Celsius"; } Bons programadores sempre escrevem o protótipo de todas as suas funções, mesmo os protótipos das funções definidas antes de sua chamada.

Page 91: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 92

4.4. Tipos de funções O tipo de uma função é definido pelo tipo dos dados que ela retorna, e não pelo tipo dos argumentos que ela recebe, sendo este retornado por meio do comando return. Assim sendo, uma função é dita do tipo inteiro quando esta retorna um valor do tipo int. Os tipos de funções em C++ são os mesmos tipos das variáveis básicas. Uma função em C++ pode, entretanto, não retornar valor algum. Neste caso a função é do tipo void.

4.4.1. O comando RETURN O comando return termina a execução de uma função e retorna o controle para a instrução do código-fonte seguinte à chamada à função. O valor da instrução após o comando return é retornado como sendo o valor da própria função, devendo este valor ser condizente com o tipo da função declarado em seu protótipo. Um comando return sem expressão alguma termina uma função do tipo void. A sintaxe de uma instrução return tem uma das três formas seguintes: return; //Função do tipo void return expressão; return (expressão); Podemos eliminar a variável desnecessária declarada no corpo da função celsius() e colocar a expressão de cálculo diretamente junto ao comando return, conforme mostrado abaixo: float celsius (float fahr) //Definicao da funcao { return (fahr - 32) * 5 / 9; } O uso do comando return não é obrigatório e uma função que não possui o comando return se encerra quando o fluxo de controle do programa encontra a chave ('}') que indica o fim da função. Tais funções que não utilizam o comando return são funções do tipo void. O valor de retorno de uma função é acessado, na instrução de chamada, por meio do nome da função seguido de parênteses, parênteses estes que podem conter, ou não, os argumentos necessários à execução da função. O valor de retorno de uma função pode ser atribuído a uma variável ou mesmo fazer parte de uma expressão. Uma mesma função pode conter vários comandos return, mas apenas um será executado cada vez que a função for executada.

Page 92: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 93

4.4.2. Limitações do comando RETURN Enquanto vários valores podem ser passados para uma função na forma de argumentos, não é permitido que uma função retorne mais de um valor por meio do comando return. Existem então formas de uma função retornar mais de um valor, mas isso será abordado mais à frente nesta apostila, ainda neste capítulo, para mais informações vide Capítulo 4.6.1.

4.5. Definição de funções O código-fonte em C++ que descreve o que a função efetivamente faz é chamado de definição da função. A parte da definição da função que vem antes à abertura de chaves ('{') compreende o cabeçalho da função, e tudo o estiver entre as chaves compreende o corpo da função. A forma geral da definição de uma função é a que se segue: tipo_da_função nome_da_função (declaração dos parâmetros) { instruções da função; } A definição de qualquer função em C++ começa com o tipo da função, que deverá ser o mesmo tipo do protótipo da função.

4.5.1. Parâmetros da função As informações transmitidas para uma função são chamadas de parâmetros da função. A função deverá declarar todos os seus argumentos entre os parênteses, tanto no seu cabeçalho quanto no seu protótipo. Os parâmetros de uma função podem ser utilizados livremente no corpo da função que os declarou. Variáveis que não fazem parte dos parâmetros de uma função não podem ser declaradas no cabeçalho da função. A função celsius(), por exemplo, necessita de uma variável auxiliar c declarada após a abertura de chaves. Esta variável é criada toda vez que a função inicia sua execução e é destruída quando a função termina a sua execução. Tal variável é passível de acesso somente por instruções internas ao corpo da função, uma vez que o escopo desta variável é local à função celsius(). A variável c declarada em main() é outra variável, não tendo nada a ver com a variável c declarada em celsius().

4.5.2. Passagem de argumentos por valor No Código 4.1., a função celsius() cria uma nova variável para receber o valor passado a ela na forma de argumento. Sua declaração indica que o valor enviado será armazenado na variável fahr criada quando a função inicia a sua execução e destruída quando a função termina a sua execução, conforme explicado anteriormente. Receber parâmetros desta forma, em que a função cria novas cópias dos parâmetros transmitidos, chama-se passagem por valor. O Código 4.4 mostra um programa que recebe

Page 93: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 94

um número e retorna o seu módulo, que é definido como sendo o valor do número desconsiderando-se o seu sinal. A passagem de parâmetros da função é do tipo passagem por valor.

Código 4.4 - Uso de passagem de argumentos por valor em funções.

#include <stream.h> float abs (float x); main () { float x; cout << "Digite um numero real: "; cin >> x; cout << "O modulo do valor digitado e: " << abs(x); } float abs (float x) { return (x >= 0)? x : -x; }

4.5.3. Funções do tipo VOID Uma função que não retornam nada é definida como sendo do tipo void. Como exemplo, consideremos uma função que desenha uma linha de 21 asteriscos na tela do monitor, conforme apresentado no Código 4.5.

Código 4.5 - Uso de funções do tipo VOID.

#include <stream.h> void linha (int i); main () { linha(21); cout << "\n* FELIZ ANIVERSARIO *\n"; linha(21); } void linha (int i) { for (int j = 0; j < i; j++) cout << '*'; }

Page 94: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 95

O resultado da execução do programa acima é algo como: ********************* * FELIZ ANIVERSARIO * *********************

4.5.4. Funções que não recebem nada e não retornam nada O fato de uma função não retornar nenhum valor é indicado no protótipo da função pela palavra void antes do nome da função. Uma função que não recebe argumento algum tem em seu protótipo a palavra void escrita no lugar de seus argumentos. Mas uma função que não recebe argumento algum e não retorna valor algum é considerada de tipo void e de retorno também void. Uma função deste tipo desempenha o papel de um procedimento da linguagem Pascal e de uma sub-rotina da linguagem FORTRAN. Como já mencionado, main() é uma função, assim sendo este deveria retornar algum valor e receber algum valor, o que não aconteceu em nenhum programa mostrado até aqui. O fato é que, até o presente momento, todos os programas apresentados possuíam a sua função main() void, tanto para argumentos quanto para valores de retorno. A função apresentada no Código 4.6 usa o caractere , também chamado de BELL, para tocar o alto-falante do sistema. Observe que tal função não recebe e não retorna nada.

Código 4.6 - Uso de funções que não recebem e nem retornam nada.

#include <stream.h> void beep (void); main () { int dig; cout << "Digite um numero entre 0 e 9: "; cin >> dig; if (!(dig >= 0 && dig <= 9)) { cout << "ERRO!!!"; beep(); } } void beep (void) { cout << '\a'; for (int i = 0; i < 30000; i++); cout << '\a'; }

Page 95: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 96

4.5.5. Passagem de vários argumentos Se uma função necessita de mais de um argumento, eles devem ser declarados na lista de argumentos, tanto da declaração quanto do protótipo da função. O Código 4.7 mostra o exemplo de um programa que passa dois argumentos a uma função, chamada de retângulo(), cujo propósito é desenhar retângulos de vários tamanhos na tela do computador. Os dois argumentos passados à função são a largura e a altura do retângulo.

Código 4.7 - Uso de funções com mais de um argumento.

#include <stream.h> void retangulo (int largura, int altura); main () { int alt, larg; cout << "Digite um valor para a largura do retangulo: "; cin >> larg; cout << "Digite um valor para a altura do retangulo: "; cin >> alt; retangulo(larg,alt); } void retangulo (int largura, int altura) { for (int i = 1; i < altura; i++) { cout << "\t\t"; for (int j = 1; j < largura; j++) cout << char(1); cout << '\n'; } }

4.5.6. Escrevendo várias funções em um mesmo programa Pode-se escrever quantas funções forem necessárias num mesmo programa, e qualquer uma das funções pode chamar qualquer uma outra. Em C++, no entanto, não é permitido que se defina uma função dentro de outra função. As funções em C++ são módulos independentes uns dos outros.

Page 96: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 97

4.5.7. Funções usadas como argumento de outras funções Conforme já mencionado, a chamada a uma função pode ser utilizada numa expressão da mesma forma que se utiliza valores numéricos. Pode-se também utilizar a chamada a uma função como sendo o argumento de uma outra função. O Código 4.8 apresenta um programa simples que calcula a soma dos quadrados de dois números fornecidos pelo usuário.

Código 4.8 - Uso de funções como argumento de outras funções.

#include <stream.h> float somasqr (float m, float n); float sqr (float x); float soma (float m, float n); main () { float a, b; cout << "Digite dois numeros: "; cin >> a >> b; cout << "A soma de seu quadrados e: " << somasqr(a, b); } float somasqr (float m, float n) { return soma(sqr(m), sqr(n)); } float sqr (float x) { return x*x; } float soma (float m, float n) { return m + n; }

4.6. O operador unário de referência & O operador unário de referência cria um outro nome para uma variável previamente declarada. As instruções abaixo informam ao compilador que n1 é um outro nome para a variável n. Assim sendo, toda operação que for executada em qualquer um dos dois nomes tem o mesmo resultado. int n;

Page 97: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 98

int& n1 = n; Uma referência não é uma cópia da uma variável a qual se refere, é sim a mesma variável sob nomes diferentes. As instruções abaixo imprimem na tela 5 e 8 respectivamente. int n; int& n1 = n; n = 5; cout << n1; n1 = 8; cout << n; Quando se usa o operador unário & na criação de referências, este se torna parte do tipo de dados. Toda referência deve ser, obrigatoriamente, inicializada.

4.6.1. Passagem de argumentos por referência O uso mais importante para referências é na passagem de argumentos para funções. Os exemplos de argumentos de funções vistos até o presente momento foram todos passados por valores. Quando argumentos são passados por valores, a função chamada cria novas variáveis do mesmo tipo dos argumentos e copia nelas o valor dos argumentos passados. Desta forma, a função não tem acesso às variáveis originais passadas à função e assim não as pode modificar. A principal vantagem da passagem por referência é a de que a função pode acessar as variáveis da função que a chamou. Além disso, tal mecanismo de passagem de parâmetros possibilita que uma função retorne mais de um valor para a função que a chamou. Assim sendo, quando se deseja retornar mais de um valor, estes são colocados como referências a variáveis da função de execução mais externa. O programa apresentado no Código 4.9 mostra um exemplo simples de passagem de argumentos por referência. O programa solicita ao usuário o preço atual de uma mercadoria e uma taxa de juros. De posse destes o programa modifica o preço para um valor reajustado de acordo com a taxa de juros informada pelo usuário e calcula o aumento em termos de preço.

Código 4.9 - Uso de passagem de argumentos por referência em funções.

#include <stream.h> void juros (float& p, float& r, float i); main () { float preco, taxa, val_reaj;

Page 98: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 99

cout << "Digite o preco atual (em R$): "; cin >> preco; cout << "Digite a taxa de juros (em %): "; cin >> taxa; juros(preco, val_reaj, taxa); cout << "O novo preco sera: " << setw(10) << "R$ " << preco << "\nO aumento foi de: " << setw(11) << "R$ " <<val_reaj; } void juros (float& p, float& r, float i) { r = p * i / 100; p = p * (1 + (i / 100)); } A função juros() apresentada no Código 4.9 poderia encontrar o valor do novo preço e do reajuste sem a utilização de referências, mas como estas informações seriam passadas para a função main()? Por meio do comando return é possível retornar apenas um dos dois valores calculados, mas não os dois. Uma das respostas a esta pergunta é o uso de referências, mas existem outras, que serão faladas ainda neste capítulo. Funções que recebem argumentos por referência utilizam o operador & somente nas definições dos tipos dos argumentos da função. Observe no exemplo anterior que a chamada a uma função que recebe uma referência é idêntica à chamada às funções em que os argumentos são passados por valores. A declaração abaixo indica que os argumentos p e r da função juros() são outros nomes para as variáveis passadas como argumento pela função que chamar a função juros(). Em outras palavras, quando usamos as variáveis p e r, estamos utilizando, na realidade, as variáveis preco e val_reaj da função main(). void juros (float& p, float& r, float i) As referências não existem na linguagem C, são exclusividades da linguagem C++, e não há um meio de se criar um outro nome para uma mesma variável em tal linguagem. O próximo exemplo, apresentado no Código 4.10, cria uma função que troca o conteúdo de duas variáveis. Utilizaremos a função troca() para ordenar um vetor de 10 números reais digitados pelo usuário.

Código 4.10 - Uso de passagem de argumentos por referência para ordenar um vetor.

#include <stream.h> void troca (float& x, float& y);

Page 99: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 100

main () { float valores[10], ordenados[10]; cout << "Digite 10 valores em qualquer ordem\n"; for (int i = 0; i < 10; i++) { cout << "Valor " << (i + 1) << " : "; cin >> valores[i]; ordenados[i] = valores[i]; } for (int j = 0; j < 10; j++) for (i = 0; i < 10; i++) troca(ordenados[i], ordenados[i+1]); cout << "\n\nVetor digitado Vetor ordenado\n"; for (i = 0; i < 10; i++) cout << setw(15) << valores[i] << setw(31) << ordenados[i] << '\n'; } void troca (float& x, float& y) { float bolha; if (x > y) { bolha = x; x = y; y = bolha; } }

4.6.2. Referências constantes Pode-se combinar a palavra-chave const com a declaração de referência a uma variável a fim de se criar uma referência “somente leitura” (read-only) de uma variável. Considere o exemplo abaixo. int n; const int& n1 = n; As declarações anteriores fazem da variável n1 um nome read-only para a variável n. Assim sendo, não se pode mudar o valor da variável n1 diretamente, para isso deve-se alterar o valor da variável n. Utilizam-se referências constantes quando a função não necessita alterar as variáveis enviadas como argumento. Imagine uma função que recebe muitos dados como

Page 100: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 101

argumentos. Se estes argumentos forem passados por valor, a função criará uma cópia das variáveis a ela passadas, mas como são muitos os argumentos, pode-se ter problemas com falta de memória e o programa não rodar. O exemplo apresentado no Código 4.11 solicita do usuário dois números reais e, de posse destes, calcula o quadrado de cada um deles e imprime o resultado, usando passagem de argumentos por referência constante.

Código 4.11 - Uso de passagem de argumentos por referências constantes.

#include <stream.h> void imprime (const double& x, const double& y); main () { double n1, n2; cout << "Digite 2 numeros:"; cin >> n1 >> n2; imprime(n1, n2); } void imprime (const double& x, const double& y) { cout << "O quadrado de " << x << " e " << x*x << '\n' << "O quadrado de " << y << " e " << y*y; }

4.6.3. Considerações sobre referências O uso de referências poderá ser bastante confuso para alguém que leia um programa que as utiliza. Sem que seja lido o protótipo da função, é impossível saber se a função recebe uma referência da variável ou um valor. Assim, não sabemos se a variável passada como argumento é alterada ou não. As referências devem ser usadas com muito cuidado e sempre acompanhadas de linhas de comentário.

4.7. Valores default para os argumentos de uma função O protótipo de uma função pode incluir valores para um, alguns ou todos os argumentos de uma função. Se forem omitidos, os argumentos correspondentes na chamada à função, os valores default serão automaticamente usados. Se os argumentos correspondentes forem enviados, estes serão respeitados. Como exemplo, vamos alterar a função linha() apresentada no Código 4.5 para que ela receba dois argumentos, sendo o primeiro o caractere para o desenho da linha e o segundo o número de caracteres a serem desenhados. A função imprime uma linha de 20 asteriscos por default.

Page 101: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 102

Código 4.12 - Uso de valores default para argumentos de funções.

#include <stream.h> void linha (int i = 20, char ch = '*'); main () { linha(); //Imprime os argumentos default cout << '\n'; linha(34); //Imprime os 34 asteriscos cout << '\n'; linha(45, '='); //Imprime os 45 caracteres '=' } void linha (int i, char ch) { for (int j = 0; j < i; j++) cout << ch; } A saída do programa acima é: ******************** ********************************** ======================================== No programa apresentado no Código 4.12 a função linha() é chamada três vezes. Na primeira vez todos os argumentos da função são omitidos, e estes assumirão os valores 20 e '*', respectivamente. Na segunda vez, os argumentos assumirão os valores de 34 e '*', respectivamente. E, na terceira vez, serão respeitados os argumentos passados à função, e estes assumirão os valores de 45 e '='. Se o primeiro argumento de uma função, com mais de um argumento, for omitido, todos os argumentos subseqüentes também deverão ser omitidos. Se o segundo argumento for omitido, todos os argumentos subseqüentes também deverão ser omitidos. Assim sendo, a seguinte instrução é ilegal. linha( , '+'); //ERRADO!!!! Pode-se escrever funções que tenham argumentos inicializados com valores default e argumentos não inicializados, mas após a primeira inicialização todos os argumentos seguintes deverão ser inicializados também, como no exemplo abaixo. void linha(int n = 20, char ch); //ERRADO!!!! void linha(int n = 20, char ch = '+'); //CORRETO

Page 102: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 103

4.8. Sobrecarga de funções Sobrecarregar uma função significa criar uma família de funções, todas com o mesmo nome, mas com a lista de parâmetros diferentes. Funções sobrecarregadas devem ter a lista de parâmetros diferentes, seja em número ou em tipo. Quando uma função é chamada, é a lista de parâmetros passada para ela que permite ao compilador identificar qual é o código-fonte deverá ser executado. Por exemplo, suponhamos que você queira criar uma função que calcule o cubo de seu argumento e que o argumento possa ser um número inteiro, um float ou double. Para resolver este problema, pode-se criar uma família de funções chamadas cubo() com parâmetros diferentes, conforme mostrado no Código 4.13.

Código 4.13 - Uso de sobrecarga (overload) de funções.

#include <stream.h> int cubo (int i); float cubo (float i); double cubo (double i); main () { cout << cubo(876) << '\n'; cout << cubo(12.34) << '\n'; cout << cubo(4567.35) << '\n'; } int cubo (int i) { return i * i * i; } float cubo (float i) { return i * i * i; } double cubo (double i) { return i * i * i; } Observe que a sistema considera somente a lista de argumentos da família de funções para escolher qual das funções será executada, e não o valor de retorno da função. Assim sendo, o exemplo abaixo é considerado erro pelo compilador. int cubo (int i);

Page 103: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 104

float cubo (int i); //ERRADO!!! Pois ambas funções tem o mesmo número e tipo de //argumento O programa apresentado no Código 4.12 não permite definir o caractere que será usado para desenhar a linha sem também definir o número de caracteres que deverão ser desenhados. Suponhamos que você queira que a função possa ser chamada com qualquer um dos dois argumentos, com os dois argumentos em qualquer ordem, ou mesmo sem nenhum dos dois argumentos. Para isso, podemos definir uma família de funções, como mostra o Código 4.14.

Código 4.14 - Código 4.12 modificado para o uso de sobrecarga de funções.

#include <stream.h> void linha (void); void linha (int i); void linha (char ch); void linha (int i, char ch); void linha (char ch, int i); main () { linha(); //Imprime 20 caracteres '*' cout << '\n'; linha(34); //Imprime 34 caracteres '*' cout << '\n'; linha(45, '='); //Imprime 45 caracteres '=' cout << '\n'; linha('+'); //Imprime 20 caracteres '+' } void linha (void) { for (int j = 0; j < 20; j++) cout << '*'; } void linha (int i) { for (int j = 0; j < i; j++) cout << '*'; } void linha (char ch) { for (int j = 0; j < 20; j++) cout << ch; } void linha (int i, char ch)

Page 104: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 105

{ for (int j = 0; j < i; j++) cout << ch; } void linha (char ch, int i) { for (int j = 0; j < i; j++) cout << ch; }

4.9. Funções inline A palavra-chave inline, quando colocada como primeiro elemento do cabeçalho da definição de uma função, causa a inserção de uma cópia da função em todo lugar onde a função é chamada. Como exemplo consideremos o Código 4.15, onde é apresentado um programa que lê caracteres do teclado e chama uma função que analisa cada caractere. Caso o caractere seja uma letra maiúscula, a função converte-a para minúscula, senão retorna o próprio caractere.

Código 4.15 - Uso de funções INLINE.

#include <stream.h> #include <conio.h> inline char minusculo (char ch) { return ((ch >= 'A' && ch <= 'Z')? (ch - 'A' + 'a'): ch); } main () { const char ctrlz = 26; char ch; while ((ch = minusculo(getch())) != ctrlz) cout << ch; } O programa apresentado no Código 4.15 é muito simples em sua concepção, e sua finalidade é semelhante a um editor de textos, porém não há como o usuário digitar uma letra maiúscula pois, caso ele tente, a função minúsculo() converterá a letra maiúscula digitada para a sua correspondente minúscula. Como se pode observar no Código 4.15, uma função inline é escrita exatamente igual a uma função comum, exceto pela inclusão do qualificador inline no início da definição da função.

Page 105: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 106

Uma outra diferença nas funções inline é que estas são escritas antes da função main(). Ou seja, a definição de uma função inline deve preceder a sua primeira chamada. Isto se deve ao fato de que o compilador deve conhecer de antemão o código da função para poder inseri-lo dentro do programa. Como a função minúsculo() foi definida antes de ser chamada, não é necessário escrever o seu protótipo. Conforme já mencionado, uma função é um código-fonte presente uma única vez no corpo do programa, mas que pode ser executado várias vezes. Assim, um dos motivos de se utilizar funções é o de poupar memória. A chamada de uma função provoca o desvio do fluxo do programa para o código-fonte da função e, ao término desta, um novo desvio do fluxo do programa, afim de que este retorne à instrução seguinte à chamada da função. Se por um lado o uso de funções poupa espaço na memória, por outro lado, o uso de funções requer mais tempo de execução. Quando escrevemos funções pequenas, podemos poupar tempo de execução do programa colocando o código-fonte da função diretamente no programa. Quando uma função é muito pequena (constituída por uma ou duas instruções apenas), as instruções necessárias à sua chamada podem ocupar mais espaço na memória que as instruções do seu próprio código-fonte. Assim sendo, em casos semelhantes, é extremamente aconselhável o uso de funções inline.

4.10 Funções recursivas Uma função é dita recursiva se esta é definida em termos dela mesma. Isto é, uma função é dita recursiva se quando dentro dela está presente uma chamada a ela mesma. O Código 4.16 mostra um programa que calcula o fatorial de um número n, representado matematicamente por n!, utilizando os conceitos de recursividade de funções.

Código 4.16 - Uso de funções recursivas.

#include <stream.h> long double fatorial (int n); main () { int n; for (;;) { cout << "Digite um numero (-1 para sair): "; cin >> n; if (n < 0) break; cout << n << "! = " << setprecision(5) << fatorial(n) << '\n'; } }

Page 106: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 107

long double fatorial (int n) { return ( (n == 0)? 1: n * fatorial(n - 1)); } O código-fonte gerado por uma função recursiva exige a utilização de mais memória do computador, o que torna a sua execução mais lenta. Não é difícil criar funções recursivas, o difícil é conhecer as situações nas quais a recursão é apropriada. Assim sendo, três pontos devem ser lembrados ao se escrever funções recursivas: i. Antes de criar a função deve-se escrever o problema em termos de recursão. No

exemplo do Código 4.16, o fatorial de um número n qualquer pode ser definido por meio da seguinte expressão:

n! = n * (n – 1)!

ii. Toda função recursiva deve ter uma condição de término, chamada de condição básica. A função fatorial(), quando chamada, verifica se n é zero. Se esta condição for verdadeira, a função fatorial() deixa de ser recursiva, encerrando a sua execução.

iii. Cada vez que uma função é chamada recursivamente, esta deve estar mais próxima de

satisfazer a sua condição básica. Isto garante que o programa converge para a sua condição básica. Em caso contrário o programa entraria em uma espécie de loop infinito, não encerrando nunca a sua execução. A cada chamada da função fatorial(), o valor de n estará sempre mais próximo de zero, e conseqüentemente, da condição básica da função.

4.10.1. Como trabalha uma função recursiva? À primeira vista não é fácil entender o funcionamento de uma função recursiva. Para facilitar o seu entendimento, vamos imaginar que uma chamada recursiva é uma chamada a uma outra função, que tenha o mesmo código-fonte da função original. Assim sendo, a função fatorial() pode ter a sua recursividade substituída por uma seqüência de funções, idênticas à função fatorial(). Por exemplo, para se calcular o fatorial de 3, a seqüência de funções poderia ser a seguinte: long double fatorial (int n) { return ( (n == 0)? 1: 3 * f1(2)); //Chama outra função } long double f1 (int n) { return ( (n == 0)? 1: 2 * f2(1)); //Chama outra função } long double f2 (int n)

Page 107: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 108

{ return ( (n == 0)? 1: 1 * f3(0)); //Chama outra função } long double f3 (int n) { return 1; //Condição básica } O que ocorre na memória do computador é quase a mesma coisa , exceto pelo fato de que não há repetição do código-fonte a função recursiva. Várias chamadas a uma função recursiva podem estar ativas ao mesmo tempo e, enquanto a última chamada não encerrar a sua execução a penúltima chamada não encerra a sua, e assim por diante. Isto faz com que variáveis de cada chamada à função sejam todas mantidas na memória, o que requer mais memória do computador. O exemplo do Código 4.17 mostra um programa que solicita que usuário digite uma frase inteira, terminada quando a tecla [ENTER] é pressionada. Ao término da frase, o programa imprime a frase digitada pelo usuário de traz para frente.

Código 4.17 - Programa que imprime uma frase digitada pelo usuário ao contrário.

#include <stream.h> #include <conio.h> void inverte (void); main () { inverte(); } void inverte (void) { char ch; if ((ch = getche()) != '\r') inverte(); cout << ch; } Quando o programa acima é compilado e o usuário digita uma frase, como por exemplo “BOM DIA!”, o programa imprime na tela, assim que o usuário pressiona a tecla [ENTER] a frase “!AID MOB”.

4.10.2. O jogo das torres de Hanói Neste jogo existem três hastes, que são chamadas de Origem, Destino e Temporária, e um número qualquer de discos, de diferentes tamanhos, posicionados inicialmente na haste

Page 108: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 109

Origem, conforme ilustrado na Figura 4.1. Os discos são dispostos em ordem decrescente de tamanho, de forma que o maior disco fica embaixo e o menor disco em cima dos demais. O objetivo do jogo é movimentar, um a um, todos os discos da haste Origem para a haste Destino, utilizando, quando necessário, a haste Temporária como uma haste auxiliar.

Porém, nenhum disco pode ser colocado sobre um disco menor que ele próprio.

Figura 4.1 - Esquema de funcionamento do jogo das torres de Hanói.

A função que iremos escrever recebe o número de discos e o nome das hastes como argumento e imprime a solução para o problema do deslocamento dos discos.Para isso, vamos considerar os seguintes passos como uma solução do problema das torres: i. Mover (n – 1) discos da haste Origem para a haste Temporária;

ii. Mover o n-ésimo disco da haste Origem para a haste Destino; iii. Mover (n – 1) discos da haste Temporária para a haste Destino. Assim sendo, pode-se escrever uma função recursiva chamada mover(), conforme mostra o Código 4.18.

Código 4.18 - Resolução do problema das torres de Hanói.

#include <stream.h> void mover (int n, char orig, char temp, char dest); main () { mover(3, 'O', 'T', 'D'); } void mover (int n, char orig, char temp, char dest) { if (n == 1) cout << "\nMova o disco 1 da haste " << orig << " para a haste " << dest; else

Page 109: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 110

{ mover(n - 1, orig, dest, temp); cout << "\nMova o disco " << n << " da haste " << orig << " para a haste " << dest; mover(n - 1, temp, orig, dest); } } A execução do programa acima gera a seguinte saída no vídeo: Mova o disco 1 da haste O para a haste D Mova o disco 2 da haste O para a haste T Mova o disco 1 da haste D para a haste T Mova o disco 3 da haste O para a haste D Mova o disco 1 da haste T para a haste O Mova o disco 2 da haste T para a haste D Mova o disco 1 da haste O para a haste D

4.11. Classes de armazenamento Todas as variáveis e funções em C++ têm um tipo e uma classe de armazenamento. Você já sabe que o tipo de uma variável diz respeito ao tamanho que ela ocupará na memória e à forma como ela será armazenada. A classe de armazenamento de uma variável determina em que pontos do programa ela pode ser acessada, quando ela será criada e quando será destruída, em que lugar ela será armazenada e qual será seu valor inicial. São quatro as classes de armazenamento em C++:

� auto (automáticas) � extern (externas) � static (estáticas) � register (em registradores)

As duas primeiras classes de armazenamento (auto e extern) já foram, indiretamente, abordadas nesta apostila. Elas são, na verdade, as variáveis de escopo local e global, respectivamente. No entanto, as classes de armazenamento vão além do conceito de escopo de uma variável. Assim sendo, os conceitos sobre ambas as classes serão reapresentados ao leitor de forma que este, além de relembrar o já aprendido, possa ampliar seus conceitos sobre variáveis.

4.11.1. A classe de armazenamento auto As maiorias das variáveis que temos usado em nossos exemplos estão confinadas nas funções que as usam, isto é, são “visíveis” ou “acessíveis” somente nas funções em que

Page 110: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 111

estão declaradas. Tais variáveis assim definidas são da classe auto. As variáveis automáticas foram, até então, tratadas apenas por variáveis de escopo local. A palavra-chave auto pode ser usada para especificar uma variável automática, mas não é necessária, visto que a classe auto é a classe default das variáveis em C++. As variáveis do tipo auto são criadas em tempo de execução do programa, especificamente quando o programa encontra a instrução de sua declaração, e são destruídas ao término do bloco ao qual pertencem. Variáveis automáticas podem ser acessadas somente pelas instruções do bloco que as define e utilizadas somente após a sua declaração. Por meio de referência a elas, algumas funções poderão também acessa-las. Quando uma variável automática é criada, o programa não as inicializa com nenhum valor específico. Variáveis automáticas podem então conter um valor inicial aleatório, já definido por nós por lixo.

4.11.2. A classe de armazenamento extern Ao contrário das variáveis automáticas, declaradas dentro das funções que as utilizam, as variáveis externas são declaradas fora de qualquer função. A instrução de declaração de uma variável externa é idêntica à declaração de uma variável automática. A única diferença é que as variáveis externas são declaradas externamente a qualquer função, ou seja, são as variáveis de escopo global. As variáveis da classe extern são criadas em tempo de compilação, especificamente quando o programa estiver sendo compilado, e são inicializadas com zero quando não existir uma inicialização explicita.

4.11.2.1. O operador de escopo de variáveis “::” Observe o programa mostrado a seguir. #include <stream.h> int i; //Variavel inicializada com zero int j = 234; //Variavel inicializada com 234 main () { int i = 5, j = 10; //Declaracao de variaveis automaticas cout << "i = " << i << "\nj = " << j; }

Page 111: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 112

O que o programa acima imprime? Quais variáveis serão usadas em main()? Em C++, as variáveis automáticas têm precedência sobre as variáveis externas. Assim sendo, o código anterior imprimirá: i = 5 j = 10 Às vezes pode-se ser necessário solicitar ao compilador que utilize uma variável externa em vez da variável automática. Para isso basta que se utilize o operador de escopo :: antes do nome da variável. Assim sendo, o exemplo anterior pode ser alterado, para que este imprima o valor das variáveis externas ao invés das variáveis automáticas da seguinte maneira:

Page 112: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 113

Código 4.19 - O operador de escopo de variáveis “::”.

#include <stream.h> int i; //Variavel inicializada com zero int j = 234; //Variavel inicializada com 234 main () { int i = 5, j = 10; //Declaracao de variaveis automaticas cout << "i = " << ::i //Imprime as variaveis esternas << "\nj = " << ::j; cout << "\ni = " << i //Imprime as variaveis esternas << "\nj = " << j; } O programa acima imprimirá na tela: i = 0 j = 234 i = 5 j = 10

4.11.2.2. A palavra-chave EXTERN Um erro muito comum entre novos, e até mesmo antigos, programadores é achar que a palavra-chave extern é utilizada em C++ para criar variáveis externas, o que não é verdade. A palavra-chave extern é usada apenas para informar ao compilador que a variável em questão foi criada em outro programa, ou seja, esta é externa ao programa em questão. Assim sendo, uma variável que tem a palavra-chave extern precedendo-a foi compilada em outro programa e apenas será criado um link com o novo programa. A sintaxe de uma variável externa a um programa é a seguinte: extern int i; //Link com a variável Se uma variável externa, criada em outro programa, for usada em um código-fonte de sua autoria, o compilador apresentará um erro ao compilar o programa, devido ao fato de que a palavra-chave extern só deve ser usada para indicar que a variável está armazenada em um módulo de definições27, externo ao programa.

27 Os conceitos sobre módulos de definições de variáveis serão apresentados mais à frente nesta apostila. Por hora basta apenas saber que módulos de definições são como arquivos de cabeçalhos contendo apenas definições de variáveis.

Page 113: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 114

Devido ao fato do uso do adjetivo “externa” para designar uma variável criar uma certa ambigüidade quanto a sua natureza, se esta é externa no sentido do seu escopo ser global ou se esta é externa no sentido de ser externa ao programa, utilizarei a seguinte nomenclatura: variável global é uma variável externa, ou seja, declarada antes de main(), mas no mesmo programa que a utiliza; uma variável externa é uma variável que tem a palavra-chave extern anteposta à sua declaração.

4.11.2.3. Retornando valores de uma função por referência Além de passar argumentos por referência para uma função, uma referência a uma variável global pode ser utiliza a fim de que uma função possa retornar mais que um valor apenas. O Código 4.20 mostra uma função que recebe três argumentos por referência e, após realizar algumas contas com cada um deles, encerra a sua execução. Logo em seguida o programa imprime na tela o valor dos argumentos passados à função e, obviamente, os valores dos três argumentos terão sido modificados. Assim sendo, pode-se considerar como se a função tivesse retornado três valores, mesmo esta sendo do tipo void.

Código 4.20 – Exemplo de uma função que retorna mais de um valor usando referências.

#include <stream.h> int a, b, c; void retorna (int& d, int& e, int& f); main () { cout << "Digite tres valores: "; cin >> a >> b >> c; retorna(a, b, c); cout << "a = " << a << "\nb = " << b << "\nc = " << c; } void retorna (int& d, int& e, int& f) { d = e + f; e = 5 * f; f = f / 12; } Além de poder retornar mais de um valor, uma função pode usar o conceito de referência para que ela mesma seja avaliada como uma referência a uma variável, ou seja, o valor retornado por uma função pode ser uma referência a uma variável. O Código 4.21 mostra uma função que, ao receber um valor, altera a valor da variável a qual esta se referencia.

Page 114: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 115

Código 4.21 - Exemplo de uma função que retorna um valor à uma variável por referência.

#include <stream.h> int x; int& intx (void); main () { intx() = 567; //Atribui um valor a variavel x cout << "x = " << x; } int& intx (void) { return x; } Quando se executa o Código 4.21 o valor retornado pela função intx() é uma referência à variável x. Como resultado, a expressão intx() é um outro nome para a variável x e a chamada à função pode aparecer do lado esquerdo do sinal de atribuição. Para entender a utilidade prática desta técnica é necessário que se aprenda um pouco mais sobre C++. Mais adiante nesta apostila serão mostrados alguns exemplos práticos que ajudaram o leitor a compreender o porque de se utilizar tal técnica.

4.11.3. A classe de armazenamento static As variáveis da classe static se assemelham, por um lado, às variáveis automáticas, pois estas só podem ser acessadas pelas funções que as declaram, mas, por outro lado, se assemelham às variáveis globais, pois matem os seus valores na memória do computador mesmo quando a função que as declarou termina a sua execução. Assim sendo, variáveis do tipo static são utilizadas quando se deseja chamar uma função várias vezes, mas se deseja que os valores das variáveis locais a esta função não se percam entre duas chamadas à função. As variáveis da classe static são criadas em tempo de compilação, especificamente quando o programa está sendo compilado, e são inicializadas com zero na falta de inicialização explícita, tal qual as variáveis globais. O Código 4.22 apresenta a função soma() que imprime uma variável static e a incrementa de uma unidade. A função soma() é chamada três vezes pelo programa principal, sendo que a variável k da classe static não perde o seu valor em nenhuma das chamadas. Nota-se ainda que, apesar da variável k ter sido inicializada após a sua declaração, esta é inicializada uma única vez, não importando quantas vezes a função seja chamada.

Page 115: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 116

Código 4.22 - Uso de variáveis da classe static.

#include <stream.h> void soma (void); main () { soma(); soma(); soma(); } void soma (void) { static int x; cout << "x = " << x++ << '\n'; } O resultado do Código 4.22 é a seguinte impressão na tela: x = 0 x = 1 x = 2

4.11.3.1. Números aleatórios e simulação Quando se deseja simular um evento, por exemplo o lançamento de uma moeda ou mesmo de um dado, fatalmente se cai em um caso onde é necessário o uso de números aleatórios que, teoricamente, são números compreendidos em um determinado intervalo que possuem igual probabilidade de chance de serem escolhidos ao acaso. De outra forma, podemos dizer que x é um número aleatório pertencente ao intervalo (a, b) se todos os elementos do intervalo tem a mesma probabilidade do elemento x de serem sorteados. Em se tratando de números aleatórios gerados computacionalmente, é usual trata-los por números pseudoaleatórios, uma vez que a maioria das rotinas computacionais que deveriam gerar números aleatórios geram, na verdade, números com uma certa tendência. O compilador C++ possui uma função geradora de números pseudoaleatórios chamada rand(). A função rand() usa um gerador aleatório de congruência multiplicativa, com período de segunda até trigésima segunda potência, para retornar sucessivos números pseudoaleatórios, estes variando em uma faixa de valores de 0 até RAND_MAX, sendo a constante simbólica RAND_MAX definida no arquivo de cabeçalho stdlib.h. Por default, RAND_MAX é igual a 32767 (ou 0x7FFF). Assim sendo, a função rand() gera números inteiros pseudoaleatórios compreendidos no intervalo [0, 32767]. O Código 4.23 apresenta um pequeno programa sobre simulação. Apesar de muito simples, ele tem por finalidade mostrar ao leitor o que é uma simulação, neste exemplo simula-se uma moeda ou um dado, de quantas faces o usuário desejar. Quando uma moeda é a joga,

Page 116: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 117

os resultados possíveis são dois, ou será cara ou coroa. Assim sendo, se a função rand() for usada da seguinte maneira: int a = rand() % 2 Os valores atribuídos à variável a serão sempre 0 ou 1, que corresponde à cara ou coroa, respectivamente. Um procedimento análogo é adotado quanto ao lançamento de um dado simulado pelo programa.

Código 4.23 - Uso da função RAND para a simulação de um lançamento de um dado ou de uma moeda.

#include <stream.h> #include <ctype.h> #include <stdlib.h> #include <conio.h> void simula (int eventos); main () { int eventos; char resp; for (;;) { clrscr(); cout << "Programa para simulacao de eventos aleatorios.\n\n\n" << "Digite o numero de faces de um dado, ou 2 para uma moeda.\n" << "RESPOSTA: "; cin >> eventos; if (eventos < 2) { cout << "O numero de eventos deve ser maior que dois..."; continue; } simula(eventos); clrscr(); cout << "Repetir o programa? (S/N): "; cin >> resp; if (toupper(resp) == 'N') exit(0); } } void simula (int eventos) {

Page 117: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 118

char resp; for (;;) { if (eventos == 2) { if ((rand() % eventos) == 0) { cout << "\nO lancamento da moeda deu cara.\n" << "Jogar a moeda novamente? (S/N): "; cin >> resp; if (toupper(resp) == 'N') break; } else { cout << "\nO lancamento da moeda deu coroa.\n" << "Jogar a moeda novamente? (S/N): "; cin >> resp; if (toupper(resp) == 'N') break; } } else { cout << "\nO lancamento do dado deu a face " << (rand() % eventos) + 1 << "\nJogar o dado novamente? (S/N): "; cin >> resp; if (toupper(resp) == 'N') break; } } }

4.11.3.2. Um gerador de números pseudoaleatórios Agora que o conceito de números pseudoaleatórios já foi introduzido, podemos criar a nossa própria função geradora de números pseudoaleatórios, utilizando o conceito de variáveis da classe static. A função geradora deverá sempre ser inicializada com um número, usualmente chamado de semente, que será utilizado para produzir um novo número, que se tornará a semente para a produção de um novo número, e assim por diante. Desta forma, a nossa função geradora deve necessariamente lembrar-se da semente usada na sua última chamada, daí a necessidade de se utilizar uma variável da classe static. O método utilizado é baseado em congruência linear, método este que não será explicado nesta apostila. O Código 4.24 apresenta então a nossa função ale(), para a geração de números pseudoaleatórios.

Page 118: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 119

Código 4.24 - Gerador de números pseudoaleatórios.

#include <stream.h> unsigned ale (void); main () { for (int i = 0; i < 5; i++) cout << ale() << '\n'; } unsigned ale (void) { static unsigned semente = 1; semente = (semente * 25173 + 13849)%65536; return semente; } A variável semente começa com o valor 1 e este é alterado pela fórmula a cada chamada à função ale(). Ao executar o Código 4.24 obteremos a seguinte saída: 39022 61087 20196 45005 3882 Se o programa for executado novamente, a saída novamente será: 39022 61087 20196 45005 3882 Observe que a saída do programa é exatamente a mesma nas duas execuções do programa. O problema da função ale() é que,a cada execução do programa, a variável semente começa sempre com o seu valor igual a 1. Para resolver este problema devemos inicializar a variável semente com um valor novo a cada execução do programa. Podemos fazer isso de duas maneiras, a primeira poderia ser criar uma chamada ao relógio do sistema e este seria o valor da semente. Uma outra maneira seria solicitar ao usuário a entrada do valor da semente. Para se utilizar a primeira solução acima nós precisamos conhecer a função que retorna o valor da hora do sistema, sendo esta em horas, minutos e segundos. Nós já utilizamos tal função no Código 1.6, onde a função _dos_gettime() era utilizada com tal finalidade. O Código 4.25 apresenta o mesmo programa mostrado no Código 4.24, apenas com a modificação que a variável semente é inicializada com o relógio do sistema.

Page 119: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 120

Código 4.25 - Gerador de números aleatórios que usa a função _dos_gettime().

#include <stream.h> #include <dos.h> unsigned ale (void); main () { for (int i = 0; i < 5; i++) cout << ale() << '\n'; } unsigned ale (void) { struct dostime_t agora; _dos_gettime(&agora); static unsigned semente = agora.second + 60 * agora.minute; semente = (semente * 25173 + 13849)%65536; return semente; } Anteriormente, quando a função _dos_gettime() foi utilizada pela primeira vez, intencionalmente, algumas considerações importantes foram omitidas sobre ela. A primeira consideração a se fazer é que a função _dos_gettime() utiliza passagem de parâmetros por referência, para que esta possa retornar três valores, as horas, os minutos e os segundos atuais do sistema. A segunda consideração é que, como a função deseja apenas retornar três valores utilizando passagem de argumentos por referência, não há muito sentido em se criar três argumentos para a função, e sim criar uma struct que possui os três campos necessários, que são hour, minute e second. Ao executar o Código 4.25 obteremos, por exemplo, a seguinte saída: 56310 27335 55340 54453 8242 Se o programa for executado novamente obteremos, por exemplo, a seguinte saída: 53164 565 15282 11315 26888

Page 120: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 121

Nota-se que as saídas do programa serão diferentes. Na verdade o Código 4.25 não resolve o problema da escolha da semente dos números pseudoaleatórios, mas sim o mitiga. Uma vez que a semente será sempre a soma, em segundos, dos minutos e dos segundos atuais do relógio do sistema, a cada hora completa, a mesma seqüência de números pseudoaleatórios será gerada, uma vez que a semente será novamente a mesma. Assim sendo, o período da semente é de uma hora.

4.11.4. A classe de armazenamento static extern A outra maneira de se tentar solucionar o problema da inicialização da semente de geração dos números pseudoaleatórios é solicitando ao usuário que digite o valor da semente. Assim sendo, a variável semente deverá ser acessível tanto pela função ale() como pela função que a chama, no nosso caso a função main(). Intuitivamente pode-se pensar em mudar a classe da variável semente para global. Mas variáveis globais podem ser acessadas por quaisquer funções do programa e não queremos que nenhuma outra função, exceto a função ale() e a função que a chamou, tenha acesso à variável semente. A solução está no uso de variáveis globais estáticas (usualmente chamadas de variáveis static extern). Quando se utiliza uma variável static extern cria-se uma variável global vinculada a um mecanismo de privacidade. Para se declarar uma variável static extern basta que se adicione a palavra-chave static anteposta à declaração da variável global. Uma variável static extern tem as mesmas propriedades de uma variável global, exceto pelo fato de que as variáveis extern podem ser usadas em qualquer parte do programa, entanto que as variáveis static extern só podem ser acessadas pelas funções do mesmo código-fonte e definidas abaixo de suas declarações. O Código 4.26 apresenta o programa do Código 2.24 utilizando uma variável static extern como semente do gerador de números pseudoaleatórios.

Código 4.26 - Gerador de números aleatórios que utiliza uma variável da classe static extern como semente.

#include <stream.h> unsigned ale (void); void ini_s (int n); main () { int s; cout << "Digite um numero inteiro para ser usado como semente: ";

Page 121: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 122

cin >> s; ini_s(s); for (int i = 0; i < 5; i++) cout << ale() << '\n'; } static int semente; unsigned ale (void) //Funcao que gera numeros pseudoaleatorios { semente = (semente * 25173 + 13849)%65536; //Formula magica... return semente; } void ini_s (int n) { semente = n; } Ao executar o Código 4.26 obteremos, por exemplo, a seguinte saída: Digite um numero inteiro para ser usado como semente: 1 39022 61087 20196 45005 3882 Se o programa for executado novamente obteremos, por exemplo, a seguinte saída: Digite um numero inteiro para ser usado como semente: 2 64195 7896 9169 7294 59375

4.11.5. A classe de armazenamento register A classe de armazenamento register indica que a variável associada deve ser armazenada fisicamente numa memória de acesso muito mais rápido, chamada registrador. Um registrador da máquina é um espaço de 16 bits (no IBM-PC) onde podemos armazenar um int ou um char. Em outras palavras, as variáveis da classe register são semelhantes às variáveis automáticas, mas se aplicam apenas às variáveis do tipo int e char.

Page 122: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 123

Cada máquina oferece um certo número de registradores que podem ser manuseados pelos usuários. Por exemplo, o computador IBM-PC oferece somente dois registradores para esse uso, portanto só podemos ter duas variáveis em registradores de cada vez. Mas isto não nos impede de declarar quantas variáveis da classe register que quisermos. Se os registradores estiverem ocupados, o computador simplesmente ignora a palavra-chave register das declarações das variáveis. Basicamente, variáveis da classe register são usadas para aumentar a velocidade de processamento do computador. Assim, o programador deve escolher as variáveis que são mais freqüentemente acessadas pelo programa e declara-las como variáveis da classe register. As variáveis que são as candidatas mais fortes a receberem tal tratamento são as variáveis de laços e os argumentos de funções. O Código 4.27 apresenta um exemplo que mostra a diferença da velocidade de processamento de variáveis de memória e variáveis de registradores. O programa executa um teste muito simples em sua concepção, ele executa dois loops for aninhados, primeiramente utilizando variáveis do tipo register e posteriormente utilizando variáveis não register.

Código 4.27 - Uso de variáveis da classe register.

#include <stream.h> #include <time.h> main () { register int a, b; int c, d; long t; t = time(0); for (a = 0; a < 30000; a++) for (b = 0; b < 30000; b++) ; cout << "A duracao dos loops com variaveis REGISTER foi: " << (time(0) - t) << " s."; t = time(0); for (c = 0; c < 30000; c++) for (d = 0; d < 30000; d++) ; cout << "\nO duracao dos loops com variaveis NAO REGISTER foi: " << (time(0) - t) << " s.";

Page 123: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 124

} Ao executar o Código 4.27 obtive, no meu computador, a seguinte saída: A duracao dos loops com variaveis REGISTER foi de 4 s. A duracao dos loops com variaveis NAO REGISTER foi de 7 s. Conforme mostrado acima, a duração dos loops for utilizando variáveis register é cerca de metade do tempo dos mesmos loops utilizando-se variáveis comuns. O Código 4.27 utiliza uma função ainda não estuda, chamada time(), declarada no arquivo de cabeçalho time.h. Quando se passa o valor 0 como argumento da função time(), esta retorna um número longo, que é a hora corrente do sistema, em segundos, desde 1 de Janeiro de 1970, 00:00:00 GMT. Assim sendo, a função time() é uso perfeito quando se precisa criar um cronômetro cuja precisa pode ser de segundos.

4.12. O pré-processador C++ O pré-processador C++ é um programa que examina automaticamente o código-fonte em C++ e executa certas modificações no código-fonte, antes de sua compilação, com base em instruções chamadas de diretivas. Assim sendo, as diretivas são instruções para o pré-processador. As diretivas devem fazer parte do código-fonte que criamos, mas não farão parte do programa compilado, pois estas são retiradas do código-fonte pelo pré-processador antes da compilação. As diretivas são utilizadas, geralmente, para tornar o código-fonte de um programa mais claro e fácil de se criar e manusear. As diretivas mais comumente utilizadas em C++ são:

#define #undef #include #if #ifdef #ifndef #else #elif #endif #error

As diretivas podem ser colocadas em qualquer lugar do código-fonte de um programa, ma estas são escritas, geralmente, nas primeiras linhas do código-fonte, antes da função main() ou mesmo antes do protótipo de qualquer função. As diretivas se aplicam somente do ponto onde estas são escritas até o final do código-fonte.

Page 124: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 125

Algumas das diretivas acima não são estranhas para nós, outras ainda são novidade. Assim sendo, abordaremos a seguir as diretivas mais importantes e as mais usuais.

4.12.1. A diretiva #define Conforme visto anteriormente no Capítulo 1.4.2 a diretiva #define, em sua forma mais simples, é utilizada para definir constantes simbólicas (usualmente tratadas por constantes definidas)28.

4.12.1.1. Uso de MACROS Até o presente momento, consideramos somente a diretiva #define em sua forma mais simples, ou seja, utilizada apenas na criação de constantes definidas. A seguir será mostrado como escrever diretivas #define que aceitam argumentos, analogamente a uma função, chamadas de macros. Uma macro é como se fosse uma função, porém todas as chamadas às macros são substituídas pelo pré-processador por suas definições. O Código 4.28 apresenta um uso simples de uma macro chamada PRN(n).

Código 4.28 - Uso de macros.

#include <stream.h> #define PRN(n) cout << n << '\n' main () { int a = 1416, b = 708; PRN(a); //Chamada a macro PRN PRN(b); } Ao executar o Código 4.28 obtém-se a seguinte saída: 1416 708 Toda ocorrência da macro PRN(n) apresentada no Código 4.28 será substituída, pelo pré-processador, pelo comando cout << n << '\n' de forma que no lugar do argumento n é usado o argumento usado na chamada à macro. Na definição de uma macro nunca deve haver espaço em branco entre o identificador da macro e o seu argumento. Por exemplo, a macro: #define PRN (n) cout << n << '\n' //ERRADO

28 Para maiores informações sobre o uso da diretiva #define para a criação de constantes definidas vide Capítulo 1.4.2.

Page 125: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 126

não funcionará, porque o espaço em branco colocado entre PRN e (n) é interpretado pelo pré-processador como sendo o fim do identificador PRN.

4.12.1.2. Uso de parênteses em macros A falta de parênteses em macros pode provocar erros consideráveis em um programa. Por exemplo, suponha que seu programa contenha as seguintes linhas em seu código-fonte: #define SOMA(x,y) x + y ... ... z = 10 * SOMA(3,4); Qual será o valor atribuído à variável z? Intuitivamente somos levados a pensar que 3 será somado a 4, gerando o resultado 7 e este resultado, quando multiplicado por 10, resultará em 70, sendo este o valor atribuído à variável z. ERRADO! O pré-processador executa uma substituição literal, e em nada inteligente, da macro pela sua definição. Assim sendo, a instrução acima, após a substituição executada pelo pré-processador será: z = 10 * 3 + 4; e o valor atribuído à variável z será 34, e não 70. A solução para tais tipos de problemas no uso de macros é o uso de parênteses que, devidamente colocados, indicam a correta ordem de precedência a ser adotada durante a compilação do programa. O exemplo anterior deve então ser corrigido da seguinte maneira: #define SOMA(x,y) (x + y) Após a substituição executada pelo pré-processador, a instrução de atribuição será: z = 10 * (3 + 4); e o valor atribuído à variável z será 70. Entretanto, em alguns casos, envolver a definição da macro em parênteses pode não ser suficiente para resolver o problema de precedência de operadores. Considere o seguinte trecho de um programa: #define PROD(x,y) (x * y) ... ... z = PROD(2 + 3, 4); Assim sendo, a instrução acima, após a substituição executada pelo pré-processador será:

Page 126: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 127

z = (2 + 3 * 4); e o valor atribuído à variável z será 14, e não 20. A solução para o problema anterior é envolver cada argumento da macro por parênteses. Logicamente tal procedimento deverá ser executado de maneira que a precedência29 desejada dos operadores seja alcançada. Assim sendo o exemplo anterior será resolvido da seguinte maneira: #define PROD(x,y) ((x) * (y))

4.12.1.3. Definindo macros usando outras macros Uma macro pode ser definida utilizando outras macros. O Código 4.29 apresenta um programa que calcula a área superficial de uma esfera utilizando o conceito de macros que usam macros. Observe que a macro AREA utiliza, na sua definição, as macros PI e SQR.

Código 4.29 - Uso de uma macro na definição de outra macro.

#include <stream.h> #define PI 3.14159265358979323846 #define SQR(x) ((x) * (x)) #define AREA(x) ((4) * (PI) * SQR(x)) main () { float raio; cout << "Digite o valor do raio da esfera: "; cin >> raio; cout << "\nA area superficial da esfera e: " << AREA(raio); } Comumente uma macro pode conter instruções que não caibam todas em uma mesma linha. Para se escrever uma macro em mais de uma linha basta que se acrescente uma barra invertida (\) no final da linha, como mostrado no exemplo abaixo: #define MAIUSC(ch) ((ch) >= 'a' && (ch) <= 'z')?\ ((ch) - 'a' + 'A') : (ch)

29 Para maiores informações sobre precedência de operadores vide o Capítulo 1.5.1.

Page 127: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 128

4.12.1.4. Macros versus funções inline As macros definidas utilizando o comando #define são similares a funções inline, entretanto, as funções inline são reconhecidas pelo compilador C++, ao passo que as macros sapo implementadas no programa por meio de uma simples substituição, muitas vezes burra, de texto. O tipo do argumento de uma macro não é avaliado pelo compilador, o que em alguns casos pode ser uma vantagem. Por exemplo, a macro SOMA pode ser utilizada com qualquer tipo de valor, como se segue abaixo: a = SOMA(3.48,4.5); b = SOMA(3,4); c = SOMA(3.48e89,4.5e90); Se uma função for escrita para calcular a soma de dois números será necessário respeitar o tipo dos argumentos da função, ou escrever uma família de funções utilizando os conceitos de sobrecarga de funções vistos no Capítulo 4.8. Por outro lado, a falta da avaliação dos argumentos de uma macro pode provocar resultados inesperados, e muitas vezes indesejados. A seguir são mostrados dois exemplos, sendo que o Código 4.30 utiliza uma macro e o Código 4.31, que realiza a mesma tarefa, utiliza uma função inline.

Código 4.30 - Problemas no uso de macros.

#include <stream.h> #include <conio.h> #define MAIUSC(ch) ((ch) >= 'a' && (ch) <= 'z')?\ ((ch) - 'a' + 'A') : (ch) main () { char ch; cout << "Digite uma letra minuscula: "; ch = MAIUSC(getche()); //Problemas... cout << "\nA letra digitada, maiuscula, e: " << ch; }

Código 4.31 - Uso de funções inline no lugar de macros.

#include <conio.h> inline char maiusc (char ch)

Page 128: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 129

{ return (ch >= 'a' && ch <= 'z')? (ch - 'a' + 'A') : ch; } main () { char ch; cout << "Digite uma letra minuscula: "; ch = maiusc(getche()); //Sem Problemas!!! cout << "\nA letra digitada, maiuscula, e: " << ch; } Executando os dois programas acima percebe-se, e muito bem, que no programa do Código 4.30 tem-se que digitar a letra três vezes para que esta seja convertida para maiúscula, o que não acontece no Código 4.31. A seguir são mostrados mais dois exemplos onde o uso de uma função inline supera o uso ineficiente de uma macro. Os Códigos 4.32 e 4.33 apresentam um programa que retorna o menor de dois valores, sendo que o primeiro utiliza uma macro e o segundo utiliza uma função inline para executar esta tarefa.

Código 4.32 - Outro exemplo do uso incorreto de uma macro.

#include <stream.h> #define MIN(x,y) (x < y)?(x) : (y) main () { int n1 = 1, n2 = 2, n; n = MIN(n1++, n2++); //Problemas... cout << "n1 = " << n1 << " n2 = " << n2 << " n = " << n; }

Código 4.33 - Outro exemplo do uso de funções inline no lugar de macros.

#include <stream.h> inline char min (int x, int y) { return (x < y)? x : y; } main ()

Page 129: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 130

{ int n1 = 1, n2 = 2, n; n = min(n1++, n2++); //Sem problemas!!! cout << "n1 = " << n1 << " n2 = " << n2 << " n = " << n; } Por mais incrível que possa parecer, o resultado da execução do Código 4.32 é a seguinte tela: n1 = 3 n2 = 3 n = 2 A resposta acima é, logicamente, um absurdo. Inicialmente a variável inteira n1 tem valor 1 e a variável n2 tem valor 2. Após o incremento de ambas, estas deveriam ter os valor 2 e 3, respectivamente, e não 3 e 3 conforme mostrado acima. Assim sendo, a variável n deveria ser igual a 1, pois 1 < 2, que são os valores das variáveis n1 e n2 antes de serem incrementadas. O resultado da execução do Código 4.33 é a seguinte tela: n1 = 2 n2 = 3 n = 1 O que prova claramente que a macro do Código 4.32 não se mostrou eficiente, mais uma vez, na execução de uma tarefa simples. De uma maneira geral, não se deve usar uma macro que chama uma função. Nos Códigos 4.30 e 4.32 as macros problemáticas eram macros que tinham funções como seus argumentos. Criar uma macro desta maneira representa, na maioria dos casos, problemas. Pode-se adotar então, como via de regra, que sempre que um argumento a ser usado for uma função, utilizaremos também uma função para operar sobre este argumento. Caso contrário pode-se pensar em se utilizar uma macro.

4.12.2. A diretiva #undef Se por um lado a diretiva #define define uma constante declarada, ou mesmo uma macro, por outro lado a diretiva #undef (do inglês undefine) remove a definição de uma constante definida, ou de uma macro. O exemplo abaixo é parte de um código fonte e tem por finalidade apenas demonstrar o uso da diretiva #undef. Observe que para se remover a definição de uma macro somente o nome da macro deve constar na diretiva #undef, ou seja, não se deve incluir a lista de argumentos da macro. #define GRANDE 3 #define ENORME 8 #define SOMA(x,y) (x) + (y)

Page 130: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 131

...

... #undef GRANDE //Remove a definicao da constante

//declarada GRANDE #define ENORME 10 //A constante declarada ENORME

//agora vale 10 ... ... #undef ENORME //A constante declarada ENORME

//volta a valer 8 ... ... #undef ENORME //Remove a definicao da constante

//declarada ENORME ... ... #undef SOMA //Remove a definicao da macro SOMA ...

4.12.3. A diretiva #include Conforme já mencionado no Capítulo 1.1.7, a diretiva #include causa a inclusão de um arquivo em nosso código-fonte. Na verdade o compilador substitui a diretiva #include pelo conteúdo do arquivo indicado, antes do programa ser compilado. Assim sendo, a linha #include <stream.h> solicita ao compilador que o arquivo stream.h, localizado no diretório INCLUDE, seja incluso no código-fonte antes que este seja compilado. Além do uso dos sinais < >, a diretiva #include aceita ainda uma segunda sintaxe, onde se utilizam aspas (""). Assim sendo, a linha #include "bintoint.h" solicita ao compilador que o arquivo bintoint.h, localizado no diretório atual, seja incluso no código-fonte antes que este seja compilado.

4.12.4. As diretivas #if, #ifdef, #ifndef, #elif, #else e #endif O pré-processador oferece diretivas que permitem que a compilação de um código-fonte seja feita de maneira condicional, utilizando diretivas semelhantes, em sintaxe e em significado, à estrutura if/else if/else. Tais diretivas facilitam tanto o desenvolvimento do programa quanto a escrita de códigos com maior portabilidade de uma máquina para outra, ou mesmo de um sistema operacional para outro. São elas as diretivas #if, #ifdef, #ifndef, #elif, #else e #endif.

Page 131: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 132

Cada diretiva #if deve sempre terminar pela diretiva #endif, que como o próprio nome diz, marca o fim da diretiva #if. Entre a diretiva #if e a diretiva #endif podem ser utilizadas quantas diretivas #elif forem necessárias, mas só pode ser utilizada uma única diretiva #else. Tanto a diretiva #elif quanto a diretiva #else são opcionais e, caso sejam utilizadas, a diretiva #else deve ser a última diretiva condicional antes da diretiva #endif. Obviamente que o leitor já deve ter estabelecido a correlação lógica entre as diretivas condicionais e a estrutura tomadora de decisão if/else if/else. Apenas no sentido de elucidação, a Tabela 4.1 mostra a correlação direta entre as duas estruturas.

Tabela 4.1 - Analogia entre as diretivas de compilação condicional e operador if.

Diretiva condicional Tomador de decisão análogo #if if #elif else if #else else #endif }

O exemplo abaixo é um fragmento de código-fonte, que compara uma constante definida chamada DEBUG, executando três instruções diferentes, de acordo com o valor da constante. #define DEBUG 1 ... ... #if DEBUG == 1 cout << "ERRO = " << erro1; #elif DEBUG == 2 cout << "ERRO = " << erro2; #else cout << "ERRO nao documentado"; #endif Seja agora um outro exemplo, onde de acordo com o valor de uma constante definida chamada CORES são incluídos diferentes arquivos de cabeçalho ao programa. #if CORES > 5 #define SOMBRA 1 #if COR_FUNDO 1 #include "corfundo.h" #else #include "semfundo.h" #endif #else #define SOMBRA 0

Page 132: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 133

#if CGA == 1 #include "cga.h" #else #include "mono.h" #endif #endif Para testar uma determinada constante foi definida pode-se utilizar as diretivas #ifdef e #ifndef. O exemplo abaixo mostra como um mesmo código-fonte pode gerar dois executáveis diferentes entre si. Se a diretiva que define a constante VERSAO_DEMO for inserida no código-fonte, o executável só poderá manipular 20 registros, e estará criada assim a versão de demonstração do seu programa. Caso esta diretiva não esteja presente no código-fonte, o programa poderá manipular o número máximo de registros. #define VERSAO_DEMO ... ... #ifdef VERSAO_DEMO #define NUM_REC 20 #else #define NUM_REC MAXINT #endif Veja outro exemplo: #ifdef REDE #define PASSWORD #include "redes.h" #else #include "monouso.h" #endif A diretiva #ifdef vem do inglês if defined, ou seja, “se definida”, e a diretiva #ifndef significa if not defined, ou seja, “se não definida”. Assim sendo, a diretiva #ifndef verifica a não definição de uma constante definida. Por exemplo: #ifndef WINDOWS #define VERSAO "VERSAO DOS" #else #define VERSAO "VERSAO WINDOWS" #endif Uma alternativa para o uso das diretivas #ifdef e #ifndef é o operador defined. O exemplo abaixo mostra o mesmo exemplo anterior, porém utilizando o operador defined ao invés da diretiva #ifndef. #if !defined(WINDOWS) #define VERSAO "VERSAO DOS"

Page 133: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 134

#else #define VERSAO "VERSAO WINDOWS" #endif O exemplo acima pode ainda ser escrito como: #if defined(WINDOWS) #define VERSAO "VERSAO WINDOWS" #else #define VERSAO "VERSAO DOS" #endif

4.12.5. A diretiva #error A diretiva #error provoca uma mensagem de erro do compilador em tempo de compilação, o que é muito útil quando se quer fazer um programa para ser distribuído em vários sistemas operacionais. Considere o exemplo abaixo, ele usa uma diretiva de compilação condicional #if e, de acordo com o seu resultado, gera um erro. #if TAMANHO > TAMANHO_MAX #error "Tamanho incompativel" #endif

Page 134: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 135

4.13. Exercícios do capítulo 4.1. Quais das razões abaixo são válidas em se tratando de escrever funções?

a. economizar memória; b. rodar mais rápido; c. dar um nome a um bloco de código; d. dividir uma tarefa em pequenas unidades; e. ajudar a organizar o programa; f. reduzir o tamanho do código-fonte do programa.

4.2. Quais das instruções abaixo é uma chamada à função sorte?

a. sorte = 5; b. int sorte() {return rand();} c. x = sorte(); d. int y = sorte() % 10;

4.3. Qual é a diferença entre definir e declarar uma função? 4.4. Parâmetros de uma função são:

a. a parametrização das variáveis recebidas; b. as variáveis da função que recebem os valores da função que a chama; c. os valores retornados da função; d. as variáveis visíveis somente pela função que a chama.

4.5. É correto afirmar sobre o protótipo de uma função:

a. pode ser escrito em qualquer lugar do programa; b. deve preceder a definição da função e toda chama a esta; c. pode ser suprimido se a função for definida antes de ser chamada; d. é uma instrução que pertence ao corpo da função.

4.6. É correto afirmar sobre o tipo de uma função:

a. é definido pelos argumentos que esta recebe; b. é definido pelo valor retornado pelo comando return; c. é sempre void; d. pode ser qualquer um, exceto void.

4.7. É correto afirmar sobre o comando return:

a. é de uso obrigatório em todas as funções; b. termina a execução de uma função; c. retorna o fluxo do programa para o início da função; d. só pode retornar um único valor à função que o chama.

4.8. Os argumentos de uma função podem ser:

a. constantes; b. variáveis;

Page 135: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 136

c. chamadas a outras funções; d. expressões; e. protótipos de funções.

4.9. Quando argumentos são passados por valor:

a. a função cria novas variáveis para recebê-los; b. a função acessa as próprias variáveis da função que a chama; c. a função pode alterar as variáveis da função que a chama; d. a função não pode alterar as variáveis da função que a chama.

4.10. Uma função que não recebe nenhum argumento é do tipo:

a. int; b. void; c. float; d. Nenhuma das alternativas acima.

4.11. Uma função que não retorna nenhum valor é do tipo:

a. int; b. void; c. float; d. Nenhuma das alternativas acima.

4.12. A instrução int& x = y;

a. cria a variável x com o valor da variável y; b. cria uma cópia da variável y; c. x é a mesma variável que y; d. y é uma referência à variável x.

4.13. Quando os argumentos são passados por referência:

a. a função cria novas variáveis para recebê-los; b. a função acessa as próprias variáveis da função que a chama; c. a função pode alterar as variáveis da função que a chama; d. a função não pode alterar as variáveis da função que a chama.

4.14. O que são referências constantes e para que servem? 4.15. Funções sobregarregadas:

a. são um grupo de funções, todas de mesmo nome; b. simplificam a vida do programador; c. todas têm o mesmo tipo e o mesmo número de argumentos; d. sobrecarregam o programador.

4.16. Funções inline:

a. poupam memória; b. poupam tempo de execução; c. aumentam a legibilidade do programa;

Page 136: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 137

d. usam mais memória. 4.17. Uma função inline:

a. deve ser escrita em uma única linha; b. pode ser escrita em qualquer parte do programa; c. deve ser escrita antes da primeira chamada a ela; d. haverá tantas cópias de seu código no programa quantas forem as chamadas a ela.

4.18. Uma função recursiva:

a. é definida dentro de outra função; b. contém grandes recursos; c. contém uma chamada a ela mesma; d. solicita recursos de outros programas.

4.19. As funções recursivas:

a. poupam memória; b. poupam tempo de execução; c. aumentam a legibilidade do programa; d. usam mais memória.

Page 137: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 138

5 – MANIPULAÇÃO DE ARQUIVOS EM DISCO

.

Page 138: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 139

6 – PONTEIROS

.

Page 139: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 140

APÊNDICES

A.1. BASES NUMÉRICAS A necessidade da criação de uma base numérica remonta ao tempo em que o ser humano começava a caminhar pelas árduas trilhas da matemática. Obviamente a base adotada foi a base decimal, uma vez que o primeiro instrumento de cálculo que o ser humano adotou foram os dedos das suas mãos, e como a soma destes é igual a dez, esta foi a base numérica adotada para a realização das operações algébricas.

A.1.1. A BASE BINÁRIA Apesar de extremamente funcional para a matemática, a base decimal não é a base numérica utilizada pelos computares, uma vez que estes utilizam (atualmente) a base binária que, como o próprio nome já diz, utiliza a base dois (2) ao invés da base dez (10). Quando os primeiros computadores foram construídos eles trabalham com a idéia do 0 ou 1. Por exemplo, LED apagado ou acesso, resultado da conta TRUE ou FALSE. Este conceito computacional ainda não mudou de todo, mesmo nos PC’s mais modernos de hoje em dia. Internamente um PC faz contas apenas utilizando os bits 0 e/ou 1 e esta é a base binária, base à qual são aplicados os operadores binários. Se o leitor tentar entender um operador binário apenas lendo as representações decimais dos números antes e depois da aplicação do operador binário, provavelmente este nunca conseguirá correlacionar os dois números. Já lendo os dois números em sua representação binária a correlação é, além de banal, imediata. Uma vez que a base binária é formada apenas com os algarismos 0 e 1, pode-se estabelecer a seguinte correlação entre as duas bases:

BASE 10 BASE 2 0 0 1 1 2 10 3 11 4 100 5 101 6 110 7 111 8 1000 9 1001

10 1010

Page 140: Apostila de Programação I

Programação I – Prof. André Carlos Silva

Versão 1.1 2005 141

11 1011 e assim por diante.

Para converter um número da base decimal para a base binária basta dividir o número que se deseja converter por dois sucessivamente até que o resto seja 0. Uma vez realizada esta operação “monta-se” o binário utilizando os restos da divisão ordenados da última para a primeira divisão. Este é o modo como se procede para converter manualmente um número da base decimal para a base binária. Observe o exemplo abaixo, o qual consiste na conversão do decimal 23 no seu respectivo binário:

23 / 2 = 11 e resto 1 11 / 2 = 5 e resto 1 5 / 2 = 2 e resto 1 2 / 2 = 1 e resto 0 1 / 2 = 0 e resto 1

Portanto o decimal 23 é igual ao binário 10111. Para a realização da operação inversa, ou seja, para a conversão da base 2 para a base 10, devemos proceder exatamente ao contrário. Para isso utiliza-se um polinômio de potências onde os coeficientes do polinômio são os algarismos do binário. O exemplo abaixo mostra a conversão do binário 1011 para o decimal 23.

1.24 + 0.23 + 1.22 + 1.21 + 1.20 = 16 + 0 + 4 + 2 + 1 = 23

Assim sendo, o polinômio para a conversão de binário para decimal é dado por:

�=

=+++++n

i

ii

nn aaaaaa

001

22

33 2.2.2.2....2.

Onde: an é o n-ésimo dígito do binário, ou seja, o dígito de ordem n do binário; n é a ordem do n-ésimo dígito do binário.

A.1.2. A BASE OCTAL

A.1.3. A BASE HEXADECIMAL