Notas Aula
Notas Aula
Notas Aula
Junho 2012
Parte I
Programas C++
Essencialmente, um programa C++ consiste de uma ou mais partes chamadas funes1 . Alm disso, um programa em C++ deve denir pelo menos uma funo chamada main. Esta funo marca o ponto de incio de execuo do programa. Programas C++ tem a seguinte estrutura geral: #include <iostream> using namespace std; denio de constantes funes int main() { declarao de variveis .... sentenas .... }
1.1
Cada instruo em C++ chamada de sentena. Sentenas simples so terminadas com um ponto e vrgula. Usando chaves, podemos agrupar sentenas em blocos, chamados de sentenas compostas. Exemplos de sentenas incluem: Simples: x = 3; Composta: { i = 3; cout << i << endl; i = i + 1; } O corpo da funo main() um exemplo de sentena composta.
1.2
Variveis em C++
Uma varivel uma informao qe voc pode usar dentro de um programa C++ . Esta informao est associada com um lugar especco da memria (isso feito pelo compilador). O nome da varivel e o endereo da memria onde a informao est armazenada esto associados. O nome e o endereo no mudam. Mas, o valor da informao pode mudar (o valor do que est dentro da caixa pode mudar, embora o
Na verdade, um programa C++ composto pela denio de funes e de elementos estruturais denominados classes. Estes so objeto de estudo em cursos avanados de programao orientada a objetos.
1
tipo seja sempre o mesmo). Cada varivel tem um tipo associado. Alguns tipos de variveis que discutiremos incluem int, char e oat. Cada varivel usa uma determinada quantidade de armazenamento em memria. A maneira como sabemos quantos bytes so utilizados pelo tipo da varivel. Variveis do mesmo tipo utilizam o mesmo nmero de bytes, no interessando qual o valor que a varivel armazena. Um dos tipos utilizados para armazanar nmeros o int. Ele usado para armazenar nmeros inteiros. Outro tipo o char, usado para armazenar caracteres. Um caracter um smbolo (uma letra do alfabeto, um dgito, um smbolo de pontuao, etc). Um char armazenado em 1 byte de memria. Cada caracter associado com um valor entre 0 e 255. O compilador C++ faz a traduo para voc, portanto voc no precisa saber estes nmeros. Em C++ , um caracter representado entre apstrofes (). Por exemplo, C, a, 5, $. Note que 5 um caracter, e no o inteiro 5.
A gura acima mostra como um int e um char so armazenados na memria. Outro tipo existente o oat, usado para armazenar nmeros reais (nmeros com o ponto decimal). Este nmeros so armazenados em duas partes: a mantissa e o expoente. Eles so armazenados de uma maneira que se assemelha a notao exponencial. Por exemplo, o nmero 6.023 1023 escrito como 6.023e23. Neste caso, a mantissa 6.023 e o expoente 23. Estes nmeros so armazenados de uma forma padro, tal que a mantissa tem apenas um dgito para a esquerda do ponto decimal. Desta forma, 3634.1 escrito como 3.6341e3, e 0.0000341 escrito 3.41e5. Note tambm que a preciso limitada pela mantissa. Somente os 6 dgitos mais signicativos so armazenados. Em Code::Blocks um oat ocupa 4 bytes de memria. H muitos outros tipos ( short, long, double), que sero descritos no futuro.
1.3
Se voc usa variveis no programa, voc deve deni-las. Isto envolve especicar o tipo da varivel e o seu nome. As regras para formar nomes de variveis em C++ so: qualquer sequncia de letras, digitos, e _, MAS DEVE COMEAR com uma letra ou com _. Por exemplo, hora_inicio, tempo, var1 so nomes de variveis vlidos, enquanto 3horas, total$ e azul-claro no so nomes vlidos; Maisculas = Minsculas; No so permitidos nomes ou palavras reservadas da linguagem. sempre uma boa idia ter certas regras (para voc mesmo) para nomear variveis para tornar o programa mais legvel: D nomes signicativos as variveis (mas no muito longos); 3
Tabela 1: Palavras Reservadas da Linguagem C++ Use nomes de variveis do tipo i, j, k somente para variveis tipo contadores; Pode-se usar letras maisculas ou _ para juntar palavras. Por exemplo, horaInicio ou hora_inicio. Use o que voc preferir, mas SEJA CONSISTENTE em sua escolha. Os tipos bsicos de dados existentes em C++ so: Tipo de Dado char bool int oat double Bits 8 8 32 32 64 Faixa de Valores -128 a 127 true ou false -2.147.483.647 a 2.147.483.647 7 dgitos signicativos 15 dgitos signicativos
Abaixo est um exemplo de um programa com diversas denies de variveis: int main() { int pera; char qualidade; oat peso; pera = 3; qualidade = A; peso = 0.653; ... } Quando variveis so denidas, elas no possuem valores ainda. Ns damos valores s variveis usando o operador de atribuio (=). Variveis tambm podem ser inicializadas para conter valores quando so denidas. Usando esta forma, o program acima caria: int main() { int pera = 3; char qualidade = A; oat peso = 0.653; ... } 4
Para resumir: quando um programa executado, uma varivel associada com: um tipo: diz quantos bytes a varivel ocupa, e como ela deve ser interpretada. um nome: um identicador. um endereo: o endereo do byte menos signicativo do local da memria associado a varivel. um valor: o contedo real dos bytes associados com a varivel; o valor da varivel depende do tipo da varivel; a denio da varivel no d valor a varivel; o valor dado pelo operador de atribuio, ou usando a funo cin. Ns veremos mais tarde que a funo cin atribui a uma varivel um valor digitado no teclado. Em C++ , nomes de variveis devem ser declarados antes de serem usados. Se no for declarado, ocorrer um erro de compilao. Devem ser dados valores s variveis antes que sejam utilizadas. Se voc tentar utilizar a varivel antes de especicar o seu valor, voc obter lixo (o que quer que esteja armazenado no endereo da varivel na memria quando o programa comea sua execuo), culminando com falha na execuo do programa.
1.4
Constantes
Em C++ , alm de variveis, ns podemos usar tambm nmeros ou caracteres cujos valores no mudam. Eles so chamados de constantes. Constantes no so associados a lugares na memria. Assim como variveis, constantes tambm tm tipos. Uma constante pode ser do tipo int, char, etc. Voc nao tem que declarar constantes, e pode utiliz-las diretamente (o compilador reconhece o tipo pela maneira que so escritos). Por exemplo, 2 do tipo int, e 2.0 do tipo double. Por conveno, todas as constantes reais so do tipo double.
1.5
Caracteres Constantes
Um constante caracter escrita entre apstrofes, como em A. Todas as letras, nmeros e smbolos que podem ser impressos so escritos desta forma em C++ . s vezes precisamos de caracteres que no podem ser impressos, por exemplo, o caracter de nova linha, que no tem uma tecla especca no teclado. Neste caso, usa-se caracteres de escape. Tais caracteres so escritos no somente como um smbolo entre apstrofes, mas como um sequncia de caracteres entre apstrofes. Por exemplo, \n o caracter para nova linha (uma sequncia que inicia com a barra invertida chamada de sequncia de escape). Se quisermos representar o caracter de barra invertida, temos que escrever \\. Note que \n o caracter de nova linha - embora use-se dois smbolos para represent-lo. A barra invertida chamada de escape. Ele diz ao compilador que o n que segue no a letra n, mas que a sequncia completa de caracteres deve ser interpretada como o caracter de nova linha. Cada caracter constante tem um valor inteiro igual ao seu valor numrico do seu cdigo ASCII. Por exemplo, considere a constante A, que tem cdigo ASCII 65, e B que tem cdigo 66. Ns podemos usar a expresso A + 1. O resultado o valor 66. E se o tipo da expresso resultante for char, ento o resultado da expresso B.
1.6
Entrada e Sada
Se quisermos que um programa C++ mostre alguns resultados, ou se quisermos que o programa pea ao usurio que entre com alguma informao, ns podemos usar os elementos cout e cin2 . Se voc quiser usar estes elementos em seu programa, voce deve incluir as seguintes linhas no incio do seu cdigo fonte:
cout e cin so na verdade objetos das classes ostream e istream. Mas este detalhe no abordado nestas notas de aula. Ser visto apenas o uso destes objetos como primitivas simples para Entrada e Sada de dados.
2
#include <iostream> using namespace std; Isto faz com que o arquivo header chamado iostream seja includo no seu arquivo fonte durante a compilao. Este arquivo contm denies de diversas funes e classes (por exemplo, cout e cin). Ele declara ao compilador o nome das funes e algumas informaes adicionais necessrias para que as instrues sejam executadas corretamente. 1.6.1 Exibindo informaes na tela: cout
cout pode ser utilizado para imprimir mensagens e valores em uma variedade de formatos. Por enquanto, cout melhor descrito atravs de exemplos. cout << "Al todo mundo" << endl; Imprimir Al todo mundo em uma linha na tela do computador. O valor endl representa a mudana de linha. Para o comando cout fazer o que deve, ns devemos especicar o que ser impresso. Ns devemos dar ao comando o que chamamos de argumentos. No exemplo acima, Al todo mundo e endl so argumentos para cout. Os argumentos de cout podem ser uma varivel, uma expresso ou um string (uma srie de caracteres entre aspas (")). Ns tambm podemos colocar caracteres de escape no string para imprimir caracteres especiais. Por exemplo, colocando \n no string causa que o restante do string seja impresso na linha seguinte. Outros caracteres de escape sero apresentados no futuro. Considere o seguinte programa: #include <iostream> using namespace std; #dene PRECO 1.99 int main() { int pera = 3; char qualidade = A; oat peso = 2.5; cout << << cout << << } A sada do programa ser: Existem 3 peras de qualidade A pesando 2.5 quilos. O preco por quilo eh 1.99, o total eh 4.975 A linha #dene PRECO 1.99 no incio do programa dene uma macro. Ou seja, denimos que PRECO um sinnimo para 1.99 e, portanto, toda ocorrncia de PRECO no programa substitudo por 1.99 antes que ele seja compilado. 6 "Existem " << pera << "peras de qualidade " << qualidade "pesando " << peso << "quilos." << endl; "O preco por quilo eh R$" << PRECO ", o total eh R$" << peso * PRECO << endl;
1.6.2
cin pode ser usado para ler valores digitados no teclado. Considere o seguinte programa: #include <iostream> using namespace std; int main() { int idade; cout << "Entre sua idade: "; cin >> idade cout << "Voce tem " << idade << "anos." << endl; } Este programa mostrar no monitor: Entre sua idade: e aguardar que um nmero seja digitado e a tecla ENTER. Depois disso, a varivel idade conter o valor digitado pelo usurio. Mais de um valor pode ser lido por um mesmo cin. Considere o seguinte exemplo: #include <iostream> using namespace std; int main() { int dia, mes, ano; cout << "Entre com a data do seu aniversario (dd mm aa): "; cin >> dia >> mes >> ano; cout << "Voce nasceu em " << dia << "/" << mes << "/" << ano << endl; } Este exemplo funciona exatamente como o exemplo anterior. Um nico cin l os 3 nmeros quando estes nmeros so separados por espaos (espaos em branco, tabulao, novas linhas). Ento voc pode teclar ENTER depois de cada nmero, ou colocar espaos ou tabulaes entre os nmeros. Os espaos so ignorados pelo cin.
1.7
Algoritmo X Programa
ALGORITMO PERIMETRO_AREA /* Calcula o permetro e a area de uma circunferencia de raio R (fornecido pelo usuario) */ /* Definir variaveis */ int Raio; float Perim, Area, PI; PI = 3.14159; 7
/* Obter Raio da circunferencia */ Escreva("Entre com o valor do raio:"); Leia(Raio); /* Calcular Perimetro do Circulo */ Perim = 2 * PI * Raio; /* Calcular Area da Circunferencia */ Area = PI * Raio ** 2; /* Exibir Resultados */ Escreva("O perimetro da circunferencia de raio", Raio, "eh", Perim); Escreva("e a area eh ",Area); /* Terminar Programa */ FIM_ALGORITMO PERIMETRO_AREA Programa em C++ /* programa que calcula o permetro e a rea de uma circunferncia de raio R (fornecido pelo usurio) */ #include <iostream> /* inclui diretivas de entrada-sada */ #include <cmath> /* inclui diretivas das funes matemticas */ using namespace std; #define PI 3.14159
int main( ) { /* Definir variaveis */ int Raio; float Perim, Area; /* Obter Raio da circunferencia */ cout << "Entre com o valor do raio: "; cin >> Raio; /* Calcular Perimetro do Circulo */ Perim = 2 * PI * Raio; /* Calcular Area da Circunferencia */ Area = PI * pow(Raio, 2); /* Exibir Resultados */ cout << "O perimetro da circunferencia de raio " << Raio << " eh " << Perim << endl; cout << "e a area eh " << Area << endl; 8
2
2.1
Em C++ , ns podemos executar operaes aritmticas usando variveis e constantes. Algumas operaes mais comuns so: + adio - subtrao * multiplicao / diviso % resto (mdulo) Estas operaes podem ser usadas como mostram os exemplos abaixo, assumindo que as variveis necessrias j esto declaradas: celsius = (fahrenheit - 32) * 5.0 / 9.0; forca = massa * aceleracao;
Em C++ , assim como em lgebra, h uma ordem de precedncia de operadores. Assim, em (2 + x)(3x2 + 1), expresses em parntesis so avaliadas primeiro, seguidos por exponenciao, multiplicao, diviso, adio e subtrao. Da mesma forma, em C++ , expresses entre parntesis so executadas primeiro, seguidas de *, / e % (que tem todos a mesma precedncia), seguido de + e - (ambos com a mesma precedncia). Quando operaes adjacentes tm a mesma precedncia, elas so associadas da esquerda para a direita. Assim, a * b / c * d % e o mesmo que ((((a * b) / c) * d) % e). 2.1.2 A Operao de Resto (%)
Esta operao usada quando queremos encontrar o resto da diviso de dois inteiros. Por exemplo, 22 dividido por 5 4, com resto 2 (4 5 + 2 = 22). Em C++ , a expresso 22 % 5 ter valor 2. Note que % s pode ser utilizados entre dois inteiros. Usando ele com um operando do tipo float causa um erro de compilao (como em 22.3 % 5). 2.1.3 Expresses e Variveis
Expresses aritmticas podem ser usadas na maior parte dos lugares em que uma varivel pode ser usada. O exemplo seguinte vlido: int raio = 3 * 5 + 1; cout << "circunferencia = " << 2 * 3.14 * raio << endl; 10
Exemplos de lugares onde uma expresso aritmtica NO pode ser usada incluem: int yucky + 2 = 5; cin >> oops * 5; Este exemplo ilegal e causar erro de compilao.
2.2
Operadores Relacionais
Em C++ , h operadores que podem ser usados para comparar expresses: os operadores relacionais. H seis operadores relacionais em C++ :
< menor que > maior que <= menor ou igual que () >= maior ou igual que () == igual a != no igual a (=) Os resultados deste operadores 0 (correspondendo a falso), ou 1 (correspondendo a verdadeiro). Valores como esses so chamados valores booleanos. Algumas linguagens de programao como Pascal tem um tipo de varivel distinto para valores booleanos. Este no o caso do C++ , onde valores booleanos so armazenados como variveis numricas tais como o int. Considere o seguinte programa: int main() { int idade; idade = cout << idade = cout << } A sada deste programa ser: Pode tirar carteira de motorista? 0 Pode tirar carteira de motorista? 1 Na primeira linha, idade 17. Logo, 17 >= 18 falso, que 0. Depois disso, idade 35. Logo, 35 >= 18 verdadeiro, que 1. Note tambm que o operador de igualdade escrito com sinais de igual duplo, ==, no =. Tenha cuidado com esta diferena, j que colocar = no lugar de == no um erro sinttico (no gera erro de compilao), e no signica o que voc espera. 17; "Pode tirar carteira de motorista? " << (idade >= 18) << endl; 35; "Pode tirar carteira de motorista? " << (idade >= 18) << endl;
11
2.2.1
Operadores aritmticos tem precedncia maior que os operadores relacionais. Por exemplo, a expresso 3 + 5 < 6 * 2 o mesmo que (3 + 5) < (6 * 2). Se por alguma razo voc quer que o resultado do uma operao relacional em uma expresso aritmtica, necessrio usar parntesis. Por exemplo, a expresso score + (score == 0) ser sempre igual ao valor de score, exceto quando o valor de score seja 0. Neste caso, o valor da expresso 1 (porque (score == 0) igual a 1). Uma observao sobre valores booleanos embora voc possa assumir que o valor de uma operao relacional 0 ou 1 em C++ , qualquer valor diferente de zero considerado verdadeiro. Falaremos sobre isso mais tarde durante o curso.
2.3
Reviso de Expresses:
O que impresso pelos dois programas abaixo? #include <iostream> using namespace std; int main() { int score = 5; cout cout cout cout cout } #include <iostream> using namespace std; int main() { int n1, n2, n3; cout << "Entre com um numero inteiro: "; cin >> n1; n1 += n1 * 10; n2 = n1 / 5; n3 = n2 % 5 * 7; n2 *= n3-- % 4; cout << n2 << " " << n3 << " " << (n2 != n3 + 21) << endl; } Como a seguinte expresso completamente parentizada ? a * b / c + 30 >= 45 + d * 3 ++e == 10 << << << << << 5 + 10 * 5 % 6; 10 / 4; 10.0 / 4.0; A + 1 score + (score == 0); // // // // // 7 2 2.5 B 5
2.4
Exemplo de programas
Exemplo 1: escreva um programa que leia um nmero inteiro e imprima 0 se o nmero for par e 1 se o nmero for mpar. 12
#include <iostream> using namespace std; int main() { int numero; cout << "Entre com um numero inteiro: "; cin >> numero; cout << "\nPar? " << numero % 2 << endl; } Exemplo 2: escreva um programa que leia 3 nmeros inteiros e calcule a soma, mdia, e produto. #include <iostream> #include <iomanip> using namespace std; int main() { int n1, n2, n3; int soma; cout << "Entre com 3 numeros inteiros: "; cin >> n1 >> n2 >> n3; soma = n1 + n2 + n3; cout << "Soma = " << soma << endl; cout.setf (ios::fixed | ios::showpoint); // reais em ponto fixo cout.precision(2); // 2 casa decimais // setw(8) fixa tamanho da representao em 8 digitos cout << "Media = " << setw(8) << soma / 3.0 << endl; cout << "Produto = " << (unsigned) n1 * n2 * n3 << endl; }
2.5
Operador () ++ * + < == =
Associatividade esquerda para direita direita para esquerda esquerda para direita esquerda para direita esquerda para direita esquerda para direita direita para esquerda
%=
13
Em C++ , todas as expresses so avaliadas. O resultado da avaliao um valor e pode ser usado em quaisquer lugares.
3.1
Como voc j sabe, expresses usando operadores aritmticos, relacionais e lgicos3 so avaliados. O valor resultante um nmero. Para os operadores relacionais e lgicos, este nmero pode ser 0 (que signica falso) ou 1 (que signica verdadeiro). Por exemplo: 3 + 5 * 4 % (2 + 8) tem valor 3; 3 < 5 tem valor 1; x + 1 tem valor igual ao valor da varivel x mais um; (x < 1) || (x > 4) tem valor 1 quando o valor da varivel x fora do intervalo [1,4], e 0 quando x est dentro do intervalo.
3.2
Um lvalue (do ingls left-hand-side value - valor a esquerda) um valor que se refere a um endereo na memria do computador. At agora, o nico lvalue vlido visto no curso o nome de uma varivel. A maneira que a atribuio funciona a seguinte: a expresso do lado direito avaliada, e o valor copiado para o endereo da memria associada ao lvalue. O tipo do objeto do lvalue determina como o valor da expressao armazenada na memria. Expresses de atribuio, assim como expresses, tm valor. O valor de uma expresso de atribuio dado pelo valor da expresso do lado direito do =. Por exemplo: x = 3 tem valor 3; x = y+1 tem o valor da expresso y+1. Como consequncia do fato que atribuies serem expresses que so associadas da direita para esquerda, podemos escrever sentenas como: i = j = k = 0; Que, usando parnteses, equivalente a i = (j = (k = 0)). Ou seja, primeiro o valor 0 atribudo a k, o valor de k = 0 (que zero) atribudo a j e o valor de j = (k = 0) (que tambm zero) atribudo a i. Uma caracterstica muito peculiar de C++ que expresses de atribuio podem ser usados em qualquer lugar que um valor pode ser usado. Porm voc deve saber que us-lo dentro de outros comandos produz um efeito colateral que alterar o valor da varivel na memria. Portanto, a execuo de: int quadrado, n = 2; cout << "Quadrado de " << n << " eh menor que 50? " << ((quadrado = n * n) <
3
14
causa no apenas que o valor 4 seja impresso, como a avaliao da expresso relacional dentro do cout faz com que o nmero 4 seja copiado para o endereo de memria associado com a varivel quadrado. Note que necessrio usar parnteses em quadrado = n * n j que = tem menor precedncia que o operador relacional <. Agora compare o exemplo anterior com o prximo, no qual o valor 4 impresso, mas sem nenhum efeito colateral: int quadrado, n = 2; cout << "Quadrado de " << n << " eh menor que 50? " << (n * n < 50) << endl; Note que agora no h necessidade de parnteses para a expresso n * n porque * tem maior precedncia que o operador relacional <. 3.2.1 Operadores de atribuio aritmtica
Como foi discutido em classe, estes comandos de atribuio funcionam de forma similar que o comando de atribuio. O lado esquerdo da expresso deve ser um lvalue. O valor da expresso de atribuio aritmtica igual ao valor da sentena de atribuio correspondente. Por exemplo: x += 3 igual a x = x + 3 e tem valor x + 3 x *= y + 1 igual a x = x * (y + 1) e tem valor x * (y + 1)
15
A execuo de um programa C++ comea com a funo main(). Em todos os exemplos que vimos at este momento, sentenas so executadas sequencialmente. A ordem sequencial de execuo de senteas pode ser alterada se certas condies forem satisfeitas durante a execuo do programa. Isto chamado desvio condicional. Todas as linguagens de programao oferecem comandos para o desvio condicional. O mais simples a sentea if. Em C++ , ele tem o formato: if (expressao) sentenca Quando uma sentena if encontrada em um programa, 1. O teste na expressao em parnteses avaliada. 2. Se o valor da expresso de teste for DIFERENTE de zero, a sentena que segue a expresso de teste executada.
Figura 1: O comando if Considere o seguinte exemplo que converte uma frao digitada pelo usurio (numerador e denominador) em decimal e imprime o resultado: #include <iostream> using namespace std; int main( ){ int a, b; cout << "Entre com uma cin >> a >> b; fracao (numerador and denominador): ";
cout << "A fracao em decimal eh " << 1.0 * a / b << endl; } No exemplo acima, escrevemos 1.0 * a / b, j que a e b so do tipo int, e portanto a / b uma diviso de inteiros e a parte fracional do resultado seria truncado, o que certamente no o que desejamos. 16
Voce v algo errado neste programa ? Uma coisa a ser notada que se o usurio digitar um denominador igual a 0, ns teremos um erro de execuo, j que o programa tentaria executar uma diviso por zero. O que necessrio fazer testar se o denominador igual a zero e dividir s no caso dele for diferente de zero. Poderamos reescrever o programa acima da seguinte forma: Exemplo 1: #include <iostream> using namespace std; int main( ){ int a, b; cout << "Entre com uma cin >> a >> b; fracao (numerador e denominador): ";
if (b != 0) cout << "A fracao em decimal eh " << 1.0 * a / b << endl; }
Exemplo 2: o segundo.
Programa que l dois nmeros e ordena o par caso o primeiro nmero digitado for maior que
#include <iostream> using namespace std; int main( ){ int num1, num2, aux; cout << "Entre com dois numeros inteiros: "; cin >> num1 >> num2; if (num1 > num2) { aux = num1; num1 = num2; num2 = aux; cout << "Trocou \n"; } cout << "Os numeros ordenados: " << num1 << " " << num2 << endl; } O programa do Exemplo 1 acima caria ainda melhor se ao invs de no fazer nada no caso do denominador ser zero, imprimirmos uma mensagem de erro ao usurio, explicando o que h de errado. A sentena em C++ que permite fazermos isso o if - else. O formato do if-else : if (expressao) 17
Figura 2: O comando if-else Primeiro, a expressao (que usualmente chamamos de condio) avaliada. Caso a condio seja verdadeira (o que equivalente a dizer que o valor diferente de zero), entao a sentenca1 executada. Caso contrrio, a sentenca2 executada. Note que uma sentena pode ser simples ou composta. Se voc quiser agrupar diversas sentenas para serem executadas, voc pode coloc-las entre chaves ({ e }). Por hora, vamos continuar com nosso exemplo simples e torn-lo mais explicativo: Exemplo 3: #include <iostream> using namespace std; int main( ){ int a, b; cout << "Entre com uma fracao (numerador and denominador): "; cin >> a >> b; if (b != 0) cout << "A fracao decimal eh " << 1.0 * a / b << endl; else cout << "Erro: denominador zero!\n"; } Exemplo 4: Considere agora o exemplo j visto que pede que um usurio entre com um nmero e verique se o nmero par. Porm agora, queremos que o programa imprima o numero e par ou o numero e impar. #include <iostream> using namespace std; 18
int main( ){ int num; // obtem um numero do usuario cout << "Entre com um inteiro: "; cin >> num; // imprime uma mensagem dizendo se o numero e par ou impar if (num % 2 == 0) cout << "O numero eh par.\n"; else cout << "O numero eh impar.\n"; }
4.1
Um erro comum
muito frequente utilizar o operador relacional == em expresses condicionais da sentena if. Por exemplo: int saldo = 2000;
if (saldo == 1) cout << "Voce esta quebrado! " << endl; else cout << "Seu saldo eh " << saldo << endl; Como a sentena saldo = 2000 inicializa o valor da varivel saldo com 2000, a expresso saldo == 1 tem valor 0. Portanto, a sentea que segue o else ser executada, e a mensagem Seu saldo e 2000
ser impressa. Agora, suponha que, devido a um erro, voc tenha colocado = ao invs de ==: int saldo = 2000; if (saldo = 1) cout << "Voce esta quebrado! " << endl; else cout << "Seu saldo eh " << saldo << endl; Agora, a expresso saldo = 1 tem valor 1. Portanto, a sentena que segue o if ser executada, e a mensagem Voce esta quebrado! ser impressa. Alm disso, a atribuio causar um efeito colateral, e alterar o valor de saldo para 1. 19
Tal uso do operador de atribuio no ilegal, e no ser detectado pelo compilador como erro. Portanto, tome cuidado com o uso de atribuio no lugar de igualdade. Tal erro muito comum, e no fcil de achar. Como regra geral, NO utilize atribuies dentro de outras sentenas.
Como era de se esperar, possvel colocar uma sentena condicional dentro de outra. Por exemplo, se quisermos imprimir uma mensagem apropriada caso um nmero seja positivo ou negativo e par ou mpar, ns poderamos escrever o seguinte: #include <iostream> using namespace std; int main( ){ int num; // Obtem um numero do usuario cout << "Entre com um inteiro: "; cin >> num; // Imprime uma mensagem dizendo se o numero e positivo ou negativo, // positivo ou negativo. if (num >= 0) { if (num % 2 == 0) cout << "O numero e par e positivo\n"; else cout << "O numero e impar e positivo\n"; } else { if (num % 2 == 0) cout << "O numero e par e negativo\n"; else cout << "O numero e impar e negativo\n"; } }
5.1
A ambigidade do else
O aninhamento de sentenas if-else sem usar chaves ({ e }) para delimitar o bloco de senteas a ser executado pode trazer efeitos indesejados. H uma regra simples para determinar qual if est associado a qual else. Regra de associao: Um else est associado com a ltima ocorrncia do if sem else. O exemplo seguinte est errado porque associa o else ao if "incorreto": #include <iostream> using namespace std; int main( ){ 20
int num; // Obtem um numero do usuario cout << "Entre com o numero de peras: "; cin >> num; // Imprime uma mensagem dizendo se o numero de peras e 0 ou 1 // (*** isto esta errado !! ***) if (num != 0) if (num == 1) cout << "Voce tem uma pera.\n"; else cout << "Voce nao tem nenhuma pera.\n"; } Neste exemplo, o if tem o seguinte signicado, segundo a regra de associao: #include <iostream> using namespace std; int main( ){ int num; // Obtem um numero do usuario cout << "Entre com o numero de peras: "; cin >> num; // Como a sentenca if e vista pelo compilador if (num != 0) if (num == 1) cout << "Voce tem uma pera.\n"; else cout << "Voce nao tem nenhuma pera.\n"; } Para evitar este problema, chaves ({ e }) devem ser usadas para tirar a ambiguidade. O exemplo abaixo mostra como as chaves podem ser inseridas para corrigir o programa acima. #include <iostream> using namespace std; int main( ){ int num; // Obtem um numero do usuario cout << "Entre com o numero de peras: "; cin >> num; // Como corrigir o problema (este programa funciona) 21
if (num != 0) { if (num == 1) cout << "Voce tem uma pera.\n"; } else cout << "Voce nao tem nenhuma pera.\n"; } Exerccio 1: Faa um programa que leia 3 nmeros e imprima o maior. #include <iostream> using namespace std; int main() { int a, b, c, maior; cout << "Entre com os tres numeros: "; cin >> a >> b >> c; if (a > b) maior = a; else maior = b; if (maior < c) maior = c; cout << "O Maior numero eh " << maior << endl; }
Operadores Lgicos
Todos os programas at agora consideraram if com condies de teste simples. Alguns exemplos de testes simples: b != 0, contador <= 5. Estas expresses testam uma condio. Portanto, quando mais de uma condio precisa ser testada, precisamos usar sentenas if e if-else aninhadas. A linguagem C++ , assim como a maioria das linguagens de programao de alto nvel suportam operadores lgicos que podem ser usados para criar operaes lgicas mais complexas, combinando condies simples. O valor de uma expresso lgica ou VERDADEIRO ou FALSO. Lembre que no h constantes lgicas VERDADEIRO e FALSO em C++ ; em expresses lgicas 0 interpretado como FALSO, e qualquer valor diferente de zero interpretado como VERDADEIRO. Os operadores lgicos so ! NO lgico, operao de negao (operador unrio) && E lgico, conjuno (operador binrio) || OU lgico, disjuno (operador binrio). Por exemplo, se quisermos testar se um nmero num positivo e par, e imprimir uma mensagem como no exemplo anterior, podemos escrever: 22
if (num >= 0) if (num % 2 == 0) cout << "Numero par nao negativo." << endl; Com os operadores lgicos isso pode ser simplicado: if ((num>=0) && (num%2 == 0)) cout << "Numero par nao negativo." << endl; A operao de negao, !, pode ser usado da seguinte forma: !expresso lgica: O valor a negao lgica da expresso dada. Por exemplo: !0 1 !1 0 Ns podemos usar o operador de negao lgica e escrever o exemplo acima como: if (num>0 && !(num%2)) cout << "Numero par nao negativo." << endl; Os dois operadores binrios operam sobre duas expresses lgicas e tem o valor 1 (verdadeiro) or 0 (falso). Os exemplos abaixo mostram o seu uso: a==0 && b==0 (verdadeiro se ambos a == 0 e b == 0, portanto se a e b so 0) a==0 || b==0 (verdadeiro se pelo menos uma das variveis a or b for 0) Uma expresso usando && verdadeira somente se ambos os operadores forem verdadeiros (no zero). Uma expresso usando || falsa somente se ambos os operadores forem falsos (zero). Verique na Tabela 2 o resultado do uso de operadores lgicos: expr1 expr2 expr1 && expr2 expr1 || expr2 verdadeiro verdadeiro verdadeiro verdadeiro verdadeiro f also f also verdadeiro f also verdadeiro f also verdadeiro f also f also f also f also Tabela 2: Resultado de uso de Operadores Lgicos A precedncia do operador de negao lgica a mais alta (no mesmo nvel que o - unrio). A precedncia dos operadores lgicos binrios menor que a dos operadores relacionais, e mais alta que a operao de atribuio. O && tem precedncia mais alta que o ||, e ambos associam da esquerda para a direita (como os operadores aritmticos). Como a precedncia dos operadores lgicos menor que a dos operadores relacionais, no necessrio usar parnteses em expresses como: x >= 3 && x <= 50 x == 1 || x == 2 || x == 3 A Tabela 3 mostra o quadro completo de precedncia de operadores aritmticos, relacionais e lgicos. 23
Associatividade esquerda para direita direita para esquerda esquerda para direita esquerda para direita esquerda para direita esquerda para direita esquerda para direita esquerda para direita direita para esquerda
- ++ / % <= > !=
--
>=
+=
-=
*=
/=
%=
Tabela 3: Precedncia e associatividade de operadores No prximo exemplo, o programa verica se as trs variveis lado1, lado2, e lado3, podem ser lados de um tringulo reto. Ns usamos o fato que os trs valores devem ser positivos, e que o quadrado de um dos lados deve ser igual a soma dos quadrados dos outros lados (Teorema de Pitgoras) para determinar se o tringulo reto.
24
#include <iostream> using namespace std; int main( ){ int lado1, lado2, lado3; int s1, s2, s3; cout << "Entre com o tamanho dos lados do triangulo: "; cin >> lado1 >> lado2 >> lado3; // s1 s2 s3 calcula o quadrado dos lados = lado1*lado1; = lado2*lado2; = lado3*lado3;
// testa a condicao para um triangulo reto if ( lado1>0 && lado2>0 && lado3 > 0 ) { if (s1==s2+s3 || s2==s1+s2 || s2==s1+s3) ) { cout << "Triangulo reto!\n"; } else { cout << "Nao pode ser um triangulo!\n"; } } Na utilizao de expresses lgicas, as seguintes identidades so teis. Elas so chamadas de Lei de DeMorgan: !(x && y) equivalente a !x || !y e !(x || y) equivalente a !x && !y
7
7.1
Exemplos
IF - ELSE
Assuma as seguintes declaraoes de variveis: int x = 4; int y = 8; O que impresso pelos seguintes programas ? 1. if (y = 8) if (x = 5) cout << "a "; else cout << "b "; cout << "c "; cout << "d" << endl; ==> a c d 25
2. mude = para == ==> b c d 3. altere o programa acima para produzir a seguinte saida: Assuma x = 5 e y = 8 (a) a (b) a d Assuma x = 5 e y = 7 (a) b c d
7.2
Operadores lgicos
O que impresso pelas seguintes sentenas? 1. Assuma x = 5 e y = 8. if (x == 5 && y == 8) cout << "a" << endl; else cout << "b" << endl; 2. Assuma x = 4 e y = 8. if (x == 5 || y == 8) cout << "a" << endl; else cout << "b" << endl;
==> a
==> a
if !(x == 5 || y == 8) // equiv. (x != 5 && y != 8) cout << "a" << endl; else cout << "b" << endl; ==> b
if !(x == 5 && y == 8) // equiv. (x != 5 || y != 8) cout << "a" << endl; else cout << "b" << endl; ==> a 3. Precedncia: ! > && > ||
if (x == 5 || equiv.
y == 8 && z == 10)
if (x == 5 || (y == 8 && z == 10)) 26
A construo else-if
Embora ela no seja um tipo diferente de sentena, a seguinte construo bastante comum para programar decises entre diversas alternativas: if (expressao1 ) sentenca1 else if (expressao2 ) sentenca2 else if (expressao3 ) sentenca3 . . . else if (expressaon1 ) sentencan1 else sentencan As expresses lgicas so avaliadas em ordem, comeando com a expressao1 . Se uma das expresses for verdadeira, a sentena associada ser executada. Se nenhuma for verdadeira, ento a sentena, sentencan , do ltimo else ser executada como opo default. Se a opo default no for necessria, ento a parte else sentencan pode ser removida.
Exemplo 9: O seguinte exemplo mostra um else-if de trs opes. O programa l dois nmeros e diz se eles so iguais ou se o primeiro nmero menor ou maior que o segundo. #include <iostream> using namespace std; int main( ){ int num1, num2; // obtem 2 numeros do usuario cout << "Entre um numero: "; 27
cin >> num1; cout << "Entre com um outro numero: "; cin >> num2; // mostra a mensagem de comparacao if (num1 == num2) cout << "Os numeros sao iguais\n"; else if (num1 < num2) cout << "O primeiro numero e menor\n"; else cout << "O primeiro numero e maior\n"; } No programa acima, se (num1 == num2) for verdadeiro, ento os nmeros so iguais. Seno, vericado se (num1 < num2). Se esta condio for verdadeira, ento o primeiro nmero menor. Se isso no for verdadeiro, ento a nica opo restante que o primeiro nmero maior. Exemplo 10: Este programa l um nmero, um operador e um segundo nmero e realiza a operao correspondente entre os operandos dados. #include <iostream> using namespace std; int main( ){ float num1, num2; char op; // obtem uma expressao do usuario cout << "Entre com numero operador numero\n"; cin >> num1 >> op >> num2; // mostra o resultado da operacao if (op == +) cout << " = " << setprecision(2) else if (op == -) cout << " = " << setprecision(2) else if (op == /) cout << " = " << setprecision(2) else if (op == *) cout << " = " << setprecision(2) else cout << " Operador invalido."; cout << endl; } Exemplos da execuo deste programa: Entre com numero operador numero: 5 * 3.5 = 17.50 28
<< num1 + num2; << num1 - num2; << num1 / num2; << num1 * num2;
Entre com numero operador numero: 10 + 0 = 10.00 Entre com numero operador numero: 10 x 5.0 Operador invalido.
29
9
9.1
Funes
Funes: o que so e por que us-las
Quando queremos resolver um problema, em geral tentamos dividi-lo em subproblemas mais simples e relativamente independentes, e resolvemos os problemas mais simples um a um. A linguagem C++ dispe de construes (abstraes) que auxiliam o projeto de programas de maneira top-down. Uma funo cria uma maneira conveniente de encapsular alguns detalhes de processamento, ou seja, como algum resultado obtido. Quando esta computao necessria, a funo chamada, ou invocada. Desta forma, quando uma funo chamada o usurio no precisa se preocupar como a computao realizada. importante saber o que a funo faz (qual o resultado da execuo de uma funo) e tambm como se usa a funo. Criando funes, um programa C++ pode ser estruturado em partes relativamente independentes que correspondem as subdivises do problema. Voc j viu algumas funes: cin.get(), sqrt(). Elas so funes de uma biblioteca padro (do C++ ). Voc no sabe como elas foram escritas, mas j viu como utiliz-las. Ou seja, voc sabe o nome das funes e quais informaes especcas voc deve fornecer a elas (valores que devem ser passados para as funes) para que a funo produza os resultados esperados. Quando nos referirmos a uma funo neste texto usaremos a maneira frequentemente utilizada que o nome da funo seguido de (). Tomemos como exemplo o programa abaixo, que calcula o produto de 2 nmeros inteiros positivos apenas se ambos forem primos: /*---------------------------------------------------------------------Verifica se 2 Numeros sao primos e multiplica um pelo outro se o forem ---------------------------------------------------------------------*/ #include <iostream> int main() { int n1, n2, j; int prod = 0; printf("\nEntre com 2 numeros inteiros: "); scanf ("%d %d", &n1, &n2);
/* Se for indicado 1, 0 ou negativo, nao sao primos */ if ( n1 >= 2 && n2 >= 2) { /* Testa se n1 primo */ j = n1 - 1; while ((j > 1) && (n1 % j != 0)) { j = j - 1; } if( j == 1) { /* n1 eh primo */ /* Testa se n2 primo */ j = n2 - 1; 30
while ((j > 1) && (n2 % j != 0)) { j = j - 1; } if( j == 1) prod = n1 * n2; } } if (prod) printf("PRODUTO = %d\n", prod); } /* n2 eh primo */
Observe que o cdigo que verica se um nmero primo teve que ser reproduzido dentro do programa por duas vezes (para testar se os nmeros fornecidos pelo usurio eram primos). Um dos benefcios mais bvios de usar funes que podemos evitar repetio de cdigo. Em outras palavras, se voc quiser executar uma operao mais de uma vez, voc pode simplesmente escrever a funo uma vez e utiliz-la diversas vezes ao invs de escrever o mesmo cdigo vrias vezes. Outro benefcio que se voc desejar alterar ou corrigir alguma coisa mais tarde, mais fcil alterar em um nico lugar. O exemplo acima poderia ser simplicado pela criao de uma funo chamada ehPrimo, que dado um nmero n, d como resultado 1 se este nmero primo, ou 0 (zero) se o nmero no primo: int ehPrimo(unsigned int n) { int j; for(j = n - 1; (j > 1) && (n % j != 0); j--); if (j == 1) return 1; else return 0; }
O exemplo pode ser ento alterado e simplicado com o uso da funo ehPrimo(): /*---------------------------------------------------------------------Verifica se 2 Numeros sao primos e multiplica um pelo outro se o forem ---------------------------------------------------------------------*/ #include <iostream> int ehPrimo(int n) { int j; 31
j = n - 1; while ((j > 1) && (n % j != 0)) { j = j - 1; } if (j == 1) return 1; else return 0; } int main() { int n1, n2, j; int prod = 0, ep1, ep2; printf("\nEntre com 2 numeros inteiros: "); scanf ("%d %d", &n1, &n2);
if (ep1 != 0 && ep2 != 0) prod = n1 * n2; } if (prod) printf("PRODUTO = %d\n", prod); } Como pode ser observado, sejam quais forem os 2 nmeros fornecidos, no precisa escrever um cdigo similar ao mostrado na funo ehPrimo acima para cada nmero.Basta chamar a funo ehPrimo(), passar os valores necessrios para vericar a primalidade de cada nmero, e utilizar os resultados. Evitar repetio de cdigo a razo histrica que funes foram inventadas (tambm chamado de procedimento ou subrotinas em outras linguagens de programao). A maior motivao para utilizar funes nas linguagens contemporneas a reduo da complexidade do programa e melhoria da modularidade do programa. Dividindo o programa em funes, muito mais fcil projetar, entender e modicar um programa. Por exemplo, obter a entrada do programa, realizar as computaes necessrias e apresentar o resultado ao usurio pode ser implementado como diferentes funes chamadas por main() nesta ordem. Funes podem ser escritas independentemente uma da outra. Isto signica que, em geral, variveis usadas dentro de funes no so compartilhadas pelas outras funes. Assim sendo, o comportamento da funo previsvel. Se no for assim, duas funes completamente no relacionadas podem alterar os dados uma da outra. Se as variveis so locais a uma funo, programas grandes passam a ser mais fceis de serem escritos. A comunicao entre funes passa a ser controlada elas se comunicam somente atravs pelos valores passados as funes e os valores retornados. 32
9.2
Denindo funes
Um programa C++ consiste de uma ou mais denies de funes (e variveis). H sempre uma funo chamada main. Outras funes tambm podem ser denidas. Cada uma pode ser denida separadamente, mas nenhuma funo pode ser denida dentro de outra funo. Abaixo, mostramos um exemplo simples de um programa que consiste de duas funes: main() e alo(). Quando executado, este programa imprimir a mensage Alo! trs vezes. #include <iostream> using namespace std; // declaracao (prottipo) da funcao alo() void alo(void); // definicao da funcao main() int main () { int i; i = 1; while (i <= 3) { alo(); i += 1; } } // definicao da funcao alo() void alo(void) { cout << "Alo!" << endl; } Todas as funes devem ser declaradas antes de serem usadas. As funes da biblioteca padro, tais como cin.get(), so pr-denidas, mas mesmo assim devem ser declaradas (deve ser anunciado ao compilador que elas existem). por isso que inclumos a linha #include <iostream> no incio do cdigo fonte. O formato geral da denio de uma funo tipo-do-resultado nome-da funo (lista-de-argumentos) { declaraes e sentenas } A primeira linha da denio o cabealho da funo. Ela tm trs partes principais: o nome da funo, o tipo do resultado (que um valor) que a funo computa e retorna, e entre parnteses uma lista de parmetros (tambm chamado de argumentos formais). Se a funo no retorna nenhum valor, o tipo chamado de void, e esta palavra escrita no cabealho na frente do nome da funo. Se a funo no tiver argumentos formais, a palavra void escrita no lugar da lista de argumentos formais entre os parnteses. Para simplicar a exposio, falaremos sobre o tipo do retorno e os argumentos formais mais tarde. Eles servem para permitir que as funes troquem informaes entre si. 33
9.3
Funes simples
Para comear, vamos utilizar funes na seguinte forma: void nome-da-funo(void) { declaraes e senteas (corpo da funo) } O primeiro void signica que esta funo no tem tipo de retorno (no retorna um valor), e o segundo signica que a funo no tem argumentos (ela no precisa de nenhuma informao externa para ser executada). Isso no signica que a funo no faz nada. Ela pode realizar alguma ao, como imprimir uma mensagem. O exemplo abaixo mostra um programa que usa uma funo como essa: void alo(void); main() { alo(); } void alo(void) { cout << "Alo." << endl; } Neste exemplo, o programa consiste de duas funes, main() e alo(). A ordem em que as funes so denidas no importante, desde que prottipos de funes so usadas. A linha void alo(void); no topo do programa um prottipo de funo para a funo alo(). Um prottipo usado para declarar uma funo. Um prottipo passa ao compilador informaes sobre uma funo que denida dentro de um programa em algum lugar. Prottipos so sempre colocados prximo ao incio do programa, antes do comeo da denio de funes. A funo alo() imprime a mensagem Alo. quando chamada. A sentena cout o corpo da funo. Dentro da funo main() h uma chamada a funo alo(). A funo chamada pelo seu nome seguido de () (j que a funo alo no tem argumentos, nenhuma expresso escrita dentro dos parnteses). A funo alo() no retorna um valor, ela chamada simplesmente para realizar uma ao (imprimir a mensagem). A chamada de funo uma sentena vlida em C++ , portanto deve ser terminada por ponto e vrgula (;). alo(); Outra coisa que voc deve ter notado que main() tambm uma funo. A funo main() no difere em nada das demais funes, com a exceo de que contm o programa principal. Alm disso, no necessrio declarar o prottipo da funo main(). 9.3.1 Argumentos
Nosso prximo exemplo pede que o usurio digite suas iniciais, e ento chama a funo cumprimenta() para imprimir a mensagem Ola junto com as iniciais digitadas. Estas iniciais (seus valores) so passadas para a funo cumprimenta(). A funo cumprimenta() denida de forma que ela imprimir a mensagem incluindo quaisquer iniciais passadas. 34
#include <iostream> using namespace std; void cumprimenta(char, char); int main() { char primeiro, segundo; cout << "Entre com duas iniciais (sem separacao): "; cin >> primeiro >> segundo ; cumprimenta(primeiro, segundo); } void cumprimenta(char inic1, char inic2) { cout << "Ola, " << inic1 << inic2 << "!" << endl; } A funo main() chama a funo cumprimenta(); main() passa para cumprimenta() os valores dos dois caracteres para serem impressos. Veja um exemplo de execuo do programa: Entre com duas iniciais (sem separacao): YK Alo, YK! Note que h uma correspondncia entre o nmero e tipo dos valores que main() passa (estes so chamados de parmetros reais ou argumentos reais) e os argumentos listados no cabealho da funo cumprimenta().
9.4
Funes que no retornam nenhum valor (como alo(), main()) possuem tipo void. Alm de executarem aes (como imprimir) uma funo tambm pode retornar um valor para o programa que o chamou. Uma funo que retorna um valor tem no cabealho o nome do tipo do resultado. O valor retornado pode ser de qualquer tipo, incluindo int, float e char ( claro que uma vez denida, a funo s de um tipo especco). Uma funo que retorna um tipo diferente de void executa alguns clculos, e retorna o resultado (que um nico valor) para quem a chamou. A funo chamadora pode ento usar o resultado. Para retornar um valor para a funo chamadora, a funo usa a sentena return. O formato da sentena return a seguinte: return expresso; A expresso avaliada e o seu valor convertido ao tipo de retorno da funo (o tipo da funo dado no cabealho da funo antes do nome da funo). Considere o seguinte exemplo. O programa consiste de duas funes: main() e quadrado. O programa pede que o usurio digite trs nmeros e verica se eles podem ser os lados de um tringulo reto. // programa que verifica se 3 numeros podem ser os lados de um // triangulo reto. // #include <iostream> using namespace std; 35
int quadrado(int); int main() { int s1, s2, s3; cout << "Entre tres inteiros: "; cin >> s1 >> s2 >> s3; if ( s1 > 0 && s2 > 0 && s3 > 0 && (quadrado(s1) + quadrado(s2) == quadrado(s3) || quadrado(s2) + quadrado(s3) == quadrado(s1) || quadrado(s3) + quadrado(s1) == quadrado(s2)) ) cout << " " << s1 << " " << s2 << " " << s3 << " podem formar um triangulo reto\n"; else cout << " " << s1 << " " << s2 << " " << s3 << " nao podem formar um triangulo reto\n"; } // funcao que calcula o quadrado de um numero int quadrado(int n) { return n * n; } Note que quando chamamos a funo quadrado() passamos o valor no qual desejamos executar o clculo, e tambm usamos o valor retornado pela funo em expresses. O valor de quadrado(s1) o valor que a funo quadrado() retorna quando chamado com o valor do argumento sendo igual ao valor da varivel s1. Os valores retornados pelas chamadas de funes podem ser usados em todos os lugares valores podem ser usados. Por exemplo, y = quadrado(3); Aqui quadrado(3) tem o valor 9, portanto 9 pode ser atribudo a varivel y; x = quadrado(3) + quadrado(4); atribuir 25 a varivel x, e area = quadrado(tamanho); atribuir a varivel area o valor da varivel tamanho elevado ao quadrado. O prximo exemplo tem uma funo chamada cinco: int cinco(void); main() { cout << "cinco = " << cinco() << endl; } int cinco(void) { return 5; 36
} A sada do programa ser cinco = 5 porque o valor de cinco() dentro da sentena cout 5. Olhando na sentena return, 5 a expresso retornada para o chamador. Outro exemplo: int obtem_valor(void); main() { int a, b; a = obtem_valor(); b = obtem_valor(); cout << "soma = " << a + b << endl; } int obtem_valor(void) { int valor; cout << "Entre um valor: "; cin >> valor; return valor; } Este programa obtm dois inteiros do usurio e mostra a sua soma. Ele usa a funo obtem valor() que mostra uma mensagem e obtm o valor do usurio. Um exemplo de sada deste programa : Entre um valor: 15 Entre um valor: 4 soma = 19
9.5
Quando uma funo return executada, a funo imediatamente acaba mesmo que haja cdigo na funo aps a sentena return. A execuo do programa continua aps o ponto no qual a chamada de funo foi feita. Sentenas return podem ocorrer em qualquer lugar na funo no somente no nal. Tambm vlido ter mais de um return dentro de uma funo. A nica limitao que return retorna um nico valor. O seguinte exemplo mostra uma funo (uma verso para int da funo obtem valor) que pede para usurio um valor e se o usurio digitar um valor negativo, imprime uma mensagem e retorna um valor positivo. int obtem_valor_positivo(void) { int valor; 37
cout << "Entre um valor: "; cin >> valor; if (valor >= 0) return valor; cout << "Tornando o valor positivo..." << endl; return -valor; } Em uma funo void, return; (s com ;) pode ser usado para sair de uma funo. O exemplo seguinte, pede instrues ao usurio. Se o usurio reponder nao, a funo termina. Do contrrio, ele imprime as instrues e depois termina. void instrucoes(void) { int ch; cout << "Voce quer instrucos? (s/n): "; ch = cin.get(); /* Termina se resposta for n */ if (ch == n || ch == N) return; /* Mostra instrucoes */ cout << "As regras do jogo sao . . . "; . . . return; } O return nal (antes de fechar as chaves do corpo da funo) na funo opcional. Se omitido, a funo atingir o nal da funo e retornar automaticamente. Note que o return opcional somente para funes void.
9.6
A comunicao entre uma funo e o chamador pode ser nas duas direes. Argumentos podem ser usados pelo chamador para passar dados para a funo. A lista de argumentos denida pelo cabealho da funo entre parnteses.. Para cada argumento voc precisa especicar o tipo do argumento e o nome do argumento. Se houver mais de um argumento, eles so separados por vrgula. Funes que no possuem argumentos tem void como lista de argumento. No corpo da funo os argumentos (tambm chamados de argumentos formais ou parmetros formais) so tratados como variveis. erro deni-los dentro do corpo da funo porque eles j esto denidos no cabealho. Antes da execuo da funo os valores passados pelo chamador so atribudos aos argumentos da funo. Considere o seguinte programa com a funo abs() que calcula o valor absoluto de um nmero. int abs(int); 38
main() { int n; cout << "Entre um numero: "; cin >> n; cout << "Valor absoluto de " << n << " eh " << abs(n) << endl; } /* Definicao da funcao abs */ int abs(int x) { if (x < 0) x = -x; return x; } A funo abs() tem um argumento do tipo int, e seu nome x. Dentro da funo, x usado como uma varivel x. Uma vez que abs() tem um nico argumento, quando ela chamada, h sempre um valor dentro do parnteses, como em abs(n). O valor de n passado para a funo abs(), e antes da execuo da funo, o valor de n atribudo a x. Aqui est um exemplo de uma funo que converte uma temperatura de Farenheit para Celsius: float fahr_para_cels(float f) { return 5.0 / 9.0 * (f - 32.0); } Como voc pode ver, esta funo tem somente um argumento do tipo float. Um exemplo de chamada desta funo poderia ser: fervura = fahr_para_cels(212.0); O resultado da funo fahr para cels(212.0) atribudo a fervura. Portanto, depois da execuo desta sentena, o valor de fervura (que do tipo float) ser 100.0. O exemplo seguinte possui mais de um argumento: float area(float largura, float altura) { return largura * altura; } Esta funo possui dois argumentos do tipo float. Para chamar uma funo com mais de um argumento, os argumentos devem ser separados por vrgula. A ordem em que os argumentos so passados deve ser na mesma em que so denidos. Neste exemplo, o primeiro valor passado ser a largura e o segundo a altura. Um exemplo de chamada seria tamanho = area(14.0, 21.5); Depois desta sentena, o valor de tamanho (que do tipo float) ser 301.0. Quando passar os argumentos, importante ter certeza de pass-los na ordem correta e que eles so do tipo correto. Se isso no for observado, pode ocorrer erro ou aviso de compilao, ou resultados incorretos podem ser gerados. 39
Uma ltima observao. Os argumentos que so passados pelo chamador podem ser expresses em geral e no somente constantes e varivies. Quando a funo chamada durante a execuo do programa, estas expresses so avaliadas, e o valor resultante passado para a funo chamada.
9.7
p = quadrado(x);
somente o valor (no o endereo) de x passado para quadrado. Por exemplo, se a varivel tem valor 5, para a funo quadrado(), quadrado(x) ou quadrado(5) so o mesmo. De qualquer forma, quadrado() receber somente o valor 5. quadrado() no sabe se na chamada da funo o 5 era uma constante inteira, o valor de uma varivel do tipon int, ou alguma expresso como 625/25 - 4 * 5. Quando quadrado() chamado, no interessa qual a expresso entre parnteses, ela ser avaliada e o valor passado para quadrado(). Esta maneira de passar argumentos chamada de chamada por valor. Argumentos em C++ so passados por valor. Portanto, a funo chamada no pode alterar o valor da varivel passada pelo chamador como argumento, porque ela no sabe em que endereo de memria o valor da varivel est armazenado.
9.8
Variveis locais
Como voc provavelmente j reparou em alguns exemplos, possvel denir variveis dentro de funes, da mesma forma que temos denido variveis dentro da funo main(). A declarao de variveis feita no incio da funo. Estas variveis so restritas a funo dentro da qual elas so denidas. S esta funo pode enxergar suas prprias variveis. Por exemplo: void obtem_int(void); main() { obtem_int(); /* **** Isto esta errado **** */ cout << "Voce digitou " << x << endl; } void obtem_int(void) { int x; cout << "Entre um valor: "; cin >> x; cout << "Obrigado!\n"; } A funo main() usou um nome x, mas x no denido dentro de main; ele uma varivel local a get int(), no a main(). Este programa gera erro de compilao. 40
Note que possvel ter duas funes que usam variveis locais com o mesmo nome. Cada uma delas restrita a funo que a dene e no h conito. Analise o seguinte programa (ele est correto): int obtem_novo_int(void); main() { int x; x = obtem_novo_int(); /* ****Isto nao esta errado !! **** */ cout << "Voce digitou " << x << endl; } int obtem_novo_int(void) { int x; cout << "Entre um valor: "; cin >> x; cout << "Obrigado!\n"; return x; } A funo obtem novo int() usa uma varivel local chamada x para armazenar o valor digitado e retorna como resultado o valor de x. main() usa outra varivel local, tambm chamada de x para receber o resultado retornado por obtem novo int(). Cada funo tem sua prpria varivel x.
9.9
Prottipos
Os prottipos servem para dar ao compilador informaes sobre as funes. Isso para que voc possa chamar funes antes que o compilador tenha a denio (completa) das funes. O prottipo de uma funo idntico ao cabealho da funo, mas o nome dos argumentos podem ser omitidos e ele terminado com uma vrgula. Prottipos declaram uma funo ao invs de deni-las. O formato dos prottipos : tipo-de-retorno nome-da-funo(lista-dos-tipos-dos-argumentos); Denindo prottipos, voc no precisa se preocupar com a ordem em que dene as funes dentro do programa. A principal vantagem de denir prottipos que erros de chamada de funes (como chamar uma funo com o nmero incorreto de argumentos, ou com argumentos de tipo errado) so detectados pelo compilador. Sem prottipos, o compilador s saberia que h erro depois de encontrar a denio da funo. Em verses antigas do compilador C++ , programas com tais erros compilariam sem erros, o que tornava a depurao de erros mais difcil. Abaixo, mostramos duas funes e seus prottipos: float volume(float, float, float); float dinheiro(int, int, int, int);
41
float volume(float comprimento, float largura, float altura) { return comprimento * largura * altura; } float dinheiro(int c25, int c10, int c5, int c1) { return c25 * 0.25 + c10 * 0.10 + c5 * 0.05 + c1 * 0.01; }
9.10
Documentao de funes
Voc deve documentar as funes que escreve. Na documentao voc deve especicar as seguintes informaes: Ao o que a funo faz Entrada descrio dos argumentos passados para a funo Sada descrio do valor retornado pela funo Suposies o que voc assume ser verdade para que a funo funcione apropriadamente Algoritmo como o problema resolvido (mtodo) Estas informaes devem ser colocadas como comentrio antes da denio da funo.
9.11
Comentrios
Voc pode colocar comentrios no seu programa para documentar o que est fazendo. O compilador ignora completamente o que quer esteja dentro de um comentrio. Comentrios em C++ comeam com um //. Os smbolos // no possuem espao entre si. Alguns exemplos: // Este e um comentario sem graca // // // // // Este e um comentario que usa diversas linhas
Regras para comentrio sempre uma boa idia colocar comentrios em seu programa das coisas que no so claras. Isto vai ajudar quando mais tarde voc olhar o programa que escreveu j h algum tempo ou vai ajudar a entender programas escritos por outra pessoa. Um exemplo de comentrio til: 42
/* converte temperatura de farenheit para celsius */ celsius = (fahrenheit - 32) * 5.0 / 9.0; O comentrio deve ser escrito em portugus e no em C++ . No exemplo abaixo /* usando scanf, obter valor de idade e multiplicar por 365 para * obter dias */ cin >> idade; dias = idade * 365; o comentrio basicamente uma transcrio do cdigo do programa. Em seu lugar, um comentrio como /* obtem idade e transforma em numero de dias */ seria mais informativo neste ponto. Ou seja, voc deve comentar o cdigo, e no codicar o comentrio. Voc tambm deve evitar comentrios inteis. Por exemplo: /* Incrementa i */ i++; No h necessidade de comentrios j que i++ j auto explicativo. E abaixo est um exemplo de como voc deve comentar uma funo. /* funcao instrucoes() mostra instrucoes do programa * acao: nenhuma * entrada: nenhuma * saida: suposicoes: nenhuma * imprime as instrucoes * algoritmo: / * void instrucoes(void) { /* mostra instrucoes */ cout << "O processo de purificacao do Uranio-235 e . . . . . }
";
43
10
Estruturas de Repetio
A linguagem C++ possui comandos para repetir uma sequncia de instrues. Estas estruturas de repetio, tambm conhecidas como laos (do ingls loops). A primeira construo que veremos o while, seguida de for e de do ... while.
10.1
O comando de repetio while tem duas partes: a expresso de teste e o corpo da repetio. O formato do while : while (expresso teste ) corpo da repetio A expresso teste inicialmente avaliada para vericar se o lao deve terminar. Caso a expresso seja verdadeira (isto , diferente de 0 (zero)), o corpo da repetio executado. Depois desta execuo, o processo repetido a partir da expresso teste. O corpo do lao, por sua vez, pode ser uma sentena simples ou composta.
O exemplo abaixo mostra o uso do comando de repetio while: int contador = 0; while( contador < 5 ) { cout << "contador = " << contador << endl; contador += 1; } cout << "ACABOU !!!!\n"; Sada: contador = 0 contador = 1 contador = 2 contador = 3 contador = 4 ACABOU !!!! Neste exemplo, a expresso de teste contador < 5, e o corpo do lao a sentena cout. 44
Se examinarmos cuidadosamente este exemplo, veremos que a varivel contador inicializada com 0 (zero) quando denida. Depois disso, a expresso de teste vericada e, como 0 < 5 verdadeiro, o corpo da repetio executado. Assim, o programa imprime contador = 0, e incrementa contador de um (atravs do ps-decremento indicado no argumento de cout ). Em seguida, a expresso de teste vericada novamente e todo o processo se repete at que contador seja 4 e contador = 4 seja impresso. Depois disso, contador incrementado para 5 e o teste executado. Mas desta vez, 5 < 5 falso, ento o lao no continua. A execuo do programa continua na sentena que segue o lao (no caso, imprimir a frase ACABOU !!!). Aps a execuo do while, a varivel contador tem valor 5. No exemplo acima, h uma sentena simples no corpo da repetio. Quando este for denido por uma sentena composta (bloco), no se deve esquecer de usar as chaves ( { e }) para delimitar o bloco da sentena composta. O exemplo seguinte mostra um uso mais apropriado do comando while: Em situaes onde o nmero de repeties no conhecido antes do inico do comando while: Exemplo 1: Este programa pede nmeros ao usurio at que a soma de todos os nmeros digitados for pelo menos 20. #include <iostream> using namespace std; int main( ){ int total, num; total = 0; while( total < 20 ) { cout << "Total = " << total << endl; cout << "Entre com um numero: "; cin >> num; total += num; } cout << "Final total = " << total << endl; } Exemplo de sada: Total Entre Total Entre Total Entre Final = 0 com um numero: 3 = 3 com um numero: 8 = 11 com um numero: 15 total = 26
Inicialmente, dado o valor 0 varivel total, e o teste verdadeiro ( 0 < 20). Em cada iterao, o total impresso e o usurio digita um nmero que somado a total. Quanto total for maior ou igual a 20, o teste do while torna-se falso, e a repetio termina. 45
10.2
A regra principal ser consistente. Assim, seu programa ser mais legvel. 10.2.1 Colocao das chaves
H trs estilos comuns de colocar as chaves: while (expressao) { sentenca; } while (expressao) { sentenca; } while (expressao) { sentenca; } APENAS UM DESTES ESTILOS deve ser consistentemente usado para as sentenas for, while e do ... while. Use o estilo com o qual voc se sentir mais confortvel.
10.2.2
Foi mencionado anteriormente que as chaves ( { e }) podem ser omitidas quando o corpo da repetio contiver apenar uma sentena. Por exemplo: while( i < 5 ) i += 1; Embora as chaves possam ser omitidas, h uma nica razo para coloc-las sempre: while( i < 5 ) { i += 1; } Quando voc adicionar algo ao programa, voc poder adicionar uma sentena para um lao com apenas uma sentena. Se voc zer isso, vital que voc tambm adicione chaves. Se voc no zer isso, a segunda sentena do lao no ser considerada como parte do lao. Por exemplo: while( i < 5 ) i += 1; j += 1; na verdade o mesmo que: while( i < 5 ) i += 1; j += 1; 46
enquanto a inteno era na realidade: while( i < 5 ) { i += 1; j += 1; } 10.2.3 Uso de espao em branco
A outra questo de formato se deve ser colocado um espao em branco depois do while e antes do abre parnteses ( (). Por exemplo: while (i<5) ou while (i<5) ou while( i < 5 ) Isto tambm uma escolha pessoal. Porm seja consistente em sua escolha ! 10.2.4 Laos aninhados
possvel colocar um lao dentro de outro (lao aninhado). Exemplo 2: #include <iostream> #include <iomanip> using namespace std; int main( ){ int linha, coluna; linha = 1; while (linha < 5) { coluna = 1; while (coluna < 5) { cout << setw(3) << linha * coluna; coluna += 1; } linha += 1; } cout << endl; } Sada: 47
1 2 3 4
2 3 4 4 6 8 6 9 12 8 12 16
No exemplo acima, para cada iterao do lao externo, o lao interno imprime uma linha com nmeros e depois pula de linha. Exemplo 3: Este exemplo parecido com o anterior, exceto que o cout colocado dentro do lao interno. Como era de se esperar uma nova linha impressa aps cada valor ao invs de ser depois de 4 valores. #include <iostream> #include <iomanip> using namespace std; int main( ){ int linha, coluna; linha = 1; while (linha < 5) { coluna = 1; while (coluna < 5) { cout << setw(3) << linha * coluna; cout << endl; coluna += 1; } linha += 1; } } Sada: 1 2 3 4 2 4 6 8 3 6 9 12 4 8 12 16 48
Exemplo 4: #include <iostream> using namespace std; int main( ){ int linha, coluna; cout << endl; linha = 1; while (linha < 8) { cout << "\t"; coluna = 1; while (coluna < linha) { cout << "*"; coluna += 1; } cout << endl; linha += 1; } } Sada:
49
11
Considere o programa abaixo que pede ao usurio dois inteiros, armazena-os em duas variveis, troca seus valores, e os imprime. #include <iostream> using namespace std; main() { int a, b, temp; cout << "Entre dois numeros: "; cin >> a >> b; cout << "Voce entrou com " << a << " e " << b << endl; /* Troca a com b */ temp = a; a = b; b = temp; cout << "Trocados, eles sao " << a << " e " << b << endl; }
Aqui est um exemplo de execuo do programa: Entre dois numeros: 3 5 Voce entrou 3 e 5 Trocados, eles sao 5 e 3 O seguinte trecho do programa executa a troca de valores das variveis a e b: temp = a; a = b; b = temp; possvel escrever uma funo que executa esta operao de troca? Considere a tentativa abaixo de escrever esta funo: #include <iostream> using namespace std; void troca(int x, int y) { int temp; temp = x; x = y; y = temp; } 50
main() { int a, b; cout << "Entre dois numeros: "; cin >> a >> b; cout << "Voce entrou com " << a << " e " << b << endl; // Troca a com b troca(a, b); cout << "Trocados, eles sao " << a << " e " << b << endl; }
Se voc executar este programa, ver que ele no funciona: Entre dois numeros: 3 5 Voce entrou 3 e 5 Trocados, eles sao 3 e 5 Como voc j viu nas notas anteriores, em C++ os argumentos so passados por valor. Uma vez que somente os valores das variveis so passados, no possvel para a funo troca() alterar os valores de a e b porque troca() no sabe onde na memria estas variveis esto armazenadas. Alm disso, troca() no poderia ser escrito usando a sentena return porque podemos retornar APENAS UM valor (no dois) atravs da sentena return.
11.1
Usando referncia
A soluo para o problema acima ao invs de passar os valores de a e b, passar uma referncia s variveis a e b. Desta forma, troca() saberia que endereo de memria escrever, portanto poderia alterar os valores de a e b. Lembre-se que em C++ a cada varivel est associado: (i) um nome; (ii) um tipo; (iii) um valor; e (iv) um endereo. Assuma que existam as seguintes denies de variveis. int i = 5; char c = G; Na memria, eles podem estar armazenados da forma indicada na Figura 11.1: A varivel inteira i est armazenada no endereo 1342. Ela usa dois bytes de memria (quando um objeto usa mais de um byte, seu endereo onde ele comea neste caso, 1342 e no 1343). A varivel do tipo char c est armazenada no endereo 1346 e usa um byte de memria. O compilador que controla do local de armazenamento destas variveis em memria.
11.2
Considere novamente o exemplo da funo troca(). Quando a e b so passados como argumentos para troca(), na verdade, somente seus valores so passados. A funo no podia alterar os valores de a e b porque ela no conhece os endereos de a e b. Mas se referncias para a e b forem passados como argumentos ao invs de a e b, a funo troca() seria capaz de alterar seus valores; ela saberia ento 51
Figura 3: Variveis em memria em que endereo de memria escrever. Na verdade, a funo no sabe que os endereos de memria so associados com a e b, mas ela pode modicar o contedo destes endereos. Portanto, passando uma varivel por referncia (ao invs da varivel), habilitamos a funo a alterar o contedo destas variveis na funo chamadora. A denio da funo troca() deve ser alterada, e a lista de parmetros formais deve ter argumentos no do tipo int, mas referncias para int, ou seja, int & . Quando chamamos a funo troca(), ns continuamos passando parmetros reais a e b, mas desta vez, o compilador sabe que o que ser passado para a funo troca() so as referncias a estas variveis, e no seus valores. Dentro da funo troca() no dever haver mudanas. Uma vez que agora os parmetros formais so referncias, o acesso aos objetos deve ser escrito normalmente, mas deve-se ter em mente que qualquer alterao nos valores dos parmetros formais da funo implica em alterar o valor dos argumentos passados para a funo no momento de sua chamada. Assim, a funo troca() capaz de alterar os valores de a e b remotamente. O programa abaixo a verso correta do problema enunciado para a funo troca(): #include <iostream> using namespace std; /* function troca(px, py) troca os valores inteiros apontados por px e py * acao: apontadores px e py * entrada: saida: valor de px e py trocados na origem da chamada da funo * * suposicoes: px e py sao apontadores validos primeiro guarda o primeiro valor em um temporario e * algoritmo: troca * */ void troca(int & px, int & py) { int temp; temp = px; px = py; py = temp; } main() { 52
int a, b; cout << "Entre dois numeros: "; cin >> a >> b; cout << "Voce entrou com " << a << " e " << b << endl; // Troca a com b -- passa argumentos por referencia troca(a, b); cout << "Trocados, eles sao " << a << " e " << b << endl; }
A sada deste programa : Entre dois numeros: 3 5 Voce entrou com 3 e 5 Trocados, eles sao 5 e 3 Basicamente, se a funo precisa alterar o valor de uma varivel no ponto de chamada da funo, ento passamos o nome da varivel como parmetro real, e escrevemos a funo indicando os parmetros como sendo de SADA ou PASSADOS POR REFERNCIA.
53
12
O pr-processador
O pr-processador um programa que faz alguns processamentos simples antes do compilador. Ele executado automaticamente todas as vezes que seu programa compilado, e os comandos a serem executados so dados atravs de diretivas do pr-processador. Estas diretivas so colocadas em linhas que contm somente a diretiva (elas no so cdigo da linguagem C++ , portanto as regras para elas so um pouco diferentes). As linhas que comeam com um # so comandos para o pr-processador. A linha inteira reservada para este comando (nenhum cdigo C++ pode aparecer nesta linha e comandos do pr-processador no podem estar separados em diversas linhas).
12.1
A diretiva #define
Uma diretiva que usada frequentemente o #define. Esta diretiva usada para fazer substituio de macros. Por enquanto, mostraremos uma utilizao simples do #define, que simplestemente uma substituio no texto. O uso mais frequente desta diretiva dar nomes simblicos a uma constante (voc j viu outra maneira de denir contantes que colocar a palvavra const antes da denio de uma varivel). Por exemplo, seria conveniente usar PI em seus programas ao invs de digitar 3.1415926535 toda hora. Como outro exemplo, se voc quiser escrever um programa sobre estudantes de uma turma de 81 alunos, voc poderia denir NUM_ALUNOS como 81. Assim, se o nmero de alunos mudar, voc no precisaria modicar todo o seu programa onde o nmero de alunos (81) utilizado, mas simplesmente alterar a diretiva #define. Estas duas diretivas so denidas da seguinte forma: #define PI 3.1415926535 #define NUM_ALUNOS 81 Por conveno, nomes introduzidos por um #define so geralmente em letra maiscula (e variveis so em letra minscula, ou uma mistura de letras minsculas e maisculas). Assim, quando voc v um nome em um programa, voc sabe se o nome refere-se a uma varivel ou um nome denido por um #define. Considere o seguinte programa exemplo que usa PI: #define PI int main() { double raio; cout << "Entre com o raio: "; cin >> raio; cout << "Circunferencia = " << 2.0 * PI * raio << endl; } Lembre-se que o nome PI no um nome de varivel. Ele um nome que o pr-processador substituir pelo texto especicado pelo #define (mais ou menos da mesma forma que o comando pesquisa-e-substitui do editor de texto). O compilador nunca v ou sabe sobre PI. O compilador v o seguinte cout do programa acima depois do pr-processador ser executado: cout << "Circunferencia = " << 2.0 * 3.14159265 * raio << endl; 3.14159265
12.2
A diretiva #include
Agora imagine que estamos escrevendo uma biblioteca geomtrica: um conjunto de funes para calcular a rea de cilindros, cones, esferas. Se diferentes pessoal esto escrevendo cada uma das funes, eles 54
provavelmente colocaro suas funes em diferentes arquivos. Mas todas as funes usam o numero , e algumas outras constantes podem ser necessrias tambm. Ao invs de colocar o #define no incio de cada arquivo, um nico arquivo geom.h pode ser criado. Este arquivo conter a linha #define PI 3.14159265 Assim, se todos os arquivos de funes geomtricas puderem enxergar geom.h, eles compartilharo as mesmas denies. para isso que usamos a diretiva #include, para incluir em seu programa, informaes que esto em outro arquivo. Estas diretivas geralmente esto no incio do programa fonte, antes da denio de funes e varveis. Por exemplo, a diretiva #include "geom.h" colocada nos arquivos fontes que contm as funes geomtricas far com que todos eles usem o nome simblico PI ao invs de 3.14159265. O fato do nome do arquivo estar em aspas signica que o arquivo geom.h est no mesmo diretrio que os arquivos fontes (ao invs do diretrio onde se encontram as bibliotecas padro de C++ ). A diretiva #include <iostream> colocada no incio do programa fonte para incluir informaes (como prottipos de funes) que so necessrios quando cout e cin so chamados dentro do programa. O arquivo entre < > est em algum diretrio padro conhecido pelo pr-processador. Este arquivo iostream comum a todas as implementaes da linguagem C++ e contm infomaes necessrias para executar operaes de entrada e sada da entrada e sada padro (teclado e monitor).
12.3
Comentrios
De um modo geral, o pr-processador dos compiladores existentes remove todos os comentrios do arquivo fonte antes do programa ser compilado. Portanto, o compilador nunca v realmente os comentrios.
55
13
Arrays
Considere o seguinte programa. Este programa pede ao usurio notas de 4 estudantes, calcula a mdia e imprime as notas e a mdia. int main() { int nota0, nota1, nota2, nota3; int media; cout << "Entre cin >> nota0; cout << "Entre cin >> nota1; cout << "Entre cin >> nota2; cout << "Entre cin >> nota3; a nota do estudante 0: "; a nota do estudante 1: "; a nota do estudante 2: "; a nota do estudante 3: ";
media = (nota0 + nota1 + nota2 + nota3) / 4; cout << "Notas: " << gr0 << " " << gr1 << " " << gr2 << " " << gr3 << endl; cout << "Media: " << media << endl; } Este programa bem simples, mas ele tem um problema. O que acontece se o nmero de estudantes aumentar ? O programa caria muito maior (e feio !!). Imagine o mesmo programa se existissem 100 estudantes. O que precisamos uma abstrao de dados para agrupar dados relacionados. Este o objetivo de arrays em C++ . Um array uma coleo de um ou mais objetos, do mesmo tipo, armazenados em endereos adjacentes de memria. Cada objeto chamado de elemento do array. Da mesma forma que para variveis simples, damos um nome ao array. O tamanho do array o seu nmero de elementos. Cada elemento do array numerado, usando um inteiro chamado de ndice. Em C++ , a numerao comea com 0 e aumenta de um em um. Assim, o ltimo ndice igual ao nmero de elementos do array menos um. Por exemplo, podemos denir um array nota de tamanho 100 para armazenar as notas dos cem estudantes: int nota[100]; Quando o compilador encontra esta denio, ele aloca 200 bytes consecutivos de memria (dois bytes referente a cada int para cada nota). Cada nota pode ser acessada dando o nome do array e o ndice entre colchetes: como nota[0] (para a primeira nota), nota[1] para a segunda nota, e assim por diantes, at a ltima nota, nota[99].
13.1
A denio de arrays muito parecida com a denio de variveis. A nica diferena que em array necessrio especicar seu tamanho (quantos elementos ele tem). Os colchetes [ e ] so usados na denio do tamanho, como mostra os exemplos a seguir: 56
int total[5]; float tamanho[42]; O primeiro exemplo um array de 5 inteiros (o tipo int) com o nome total. Como a numerao de arrays comea com 0, os elementos da array so numerados 0, 1, 2, 3 e 4. O segundo exemplo um array de 42 elementos do tipo float com ndices de 0 a 41. Cada elemento do array total do tipo inteiro e pode ser usado do mesmo jeito que qualquer varivel inteira. Para nos referirmos a um elemento do array, usamos colchetes tambm ([ e ]). O valor dentro dos colchetes pode ser qualquer expresso do tipo inteiro. Quando um array denido, armazenamento suciente (bytes contnuos na memria) so alocados para conter todos os elementos do array. Note na tabela de precedncia abaixo que [ ] tem precedncia maior que todos os demais operadores. Operador () ! * + < == && || = , [] -> . - ++ -- * / % <= > >= != Associatividade esquerda para direita direita para esquerda esquerda para direita esquerda para direita esquerda para direita esquerda para direita esquerda para direita esquerda para direita direita para esquerda esquerda para direita
\&
+=
-=
*=
/=
%=
Verique se voc entende as sentenas do programa abaixo. int i, x, sala, total[5]; float area; float tamanho[42]; x = total[3]; i = 4; total[i] = total[i-1] + total[i-2]; total[4]++; tamanho[17] = 2.71828; sala = 3; area = tamanho[sala] * tamanho[sala]; cin >> tamanho[41]; Agora, podermos reescrever o programa que calcula a mdia de uma classe de 4 alunos: int main() { int indice, nota[4], total = 0; 57
for (indice = 0; indice < 4; indice++) { cout << "Entre a nota do estudante " << indice << ": "; cin >> nota[indice]; } cout << "Notas: "; for (indice = 0; indice < 4; indice++) { cout << nota[indice]; total += nota[indice]; } cout << "\nMedia: " << total/4 << endl; } Exemplo de Sada: Entre a nota do estudante Entre a nota do estudante Entre a nota do estudante Entre a nota do estudante Notas: 93 85 74 100 Media: 88 0: 1: 2: 3: 93 85 74 100
O programa consideravelmente mais curto. O nico problema que ainda no fcil modicar o programa para cem alunos porque 4 est em vrios pontos do programa. Ns podemos usar o #define para manter o tamanho do array como uma constante simblica ao invs de utilizar uma constante numrica. #define ESTUDANTES 4 int main() { int nota[ESTUDANTES], indice, total = 0; indice = 0; while (indice < ESTUDANTES) { cout << "Entre a nota do estudante " << indice << ": "; cin >> nota[indice]; indice += 1; // mesma coisa que "indice = indice + 1" } cout << "Notas: "); indice = 0; while (indice < ESTUDANTES) { cout << nota[indice]; total = total + nota[indice]; indice += 1; // mesma coisa que "indice = indice + 1" } cout << "\nMedia: " << total / ESTUDANTES << endl; }
58
13.2
Inicializao de arrays
Os arrays podem ser inicializados quando so denidos. Se o array no for inicializado, ento ele contem valores indenidos (tambm conhecidos como lixo). Para inicializar um array, um valor para cada elemento deve ser especicado. Estes valores devem estar entre chaves ({ e }) e so separados por vrgula (,). Alguns exemplos: int valor[4] = { 1, 42, -13, 273 }; // o tamanho do array pode ser omitido int peso[] = { 153, 135, 170 }; No primeiro exemplo, valor um array de 4 inteiros onde valor[0] e 1, valor[1] e 42, valor[2] e -13, e valor[3] e 273. Note que no segundo exemplo, o tamanho do array foi omitido. Neste caso, o compilador calcula o tamanho como sendo o nmero de elementos listados. Quando um array denido, se ele no for inicializado, o tamanho do array deve ser especicado. Se o array for inicializado, o tamanho pode ser omitido. O segundo exemplo acima equivalente a int peso[3] = { 153, 135, 170 }; Se o tamanho no for omitido, o nmero de elementos presentes no deve exceder o tamanho. Se exceder, o compilador gerar uma mensagem de erro. Se houver menos elementos na lista de inicializao, ento os elementos dados so usados para inicializar os primeiros elementos do array. Qualquer elemento no inicializado conter lixo. Note que este tipo de inicializao s vlido no contexto onde o array denido. Uma sentena como a seguinte produzir um erro do compilador, uma vez que arrays s podem ser inicializados quando denidos. int erro[5]; erro = { 2, 4, 6, 8, 10 }; // ISTO ESTA ERRADO
H mais uma restrio na inicializao de um array. Os valores devem ser todos constantes nenhuma varivel ou expresso permitida. O seguinte trecho de programa produz um erro porque um dos valores de inicializao uma varivel: int x = 21; int yy[3] = { 1, 2, x }; // ISTO ESTA ERRADO
13.3
Vericao de Limite
Quando um array denido, alocado espao em memria para conter todos os elementos do array (no mais). O tamanho do array dado explicitamente escrevendo o tamanho, ou implicitamente, inicializando o array. Embora arrays tenham tamanhos especcos, possvel que um programa tente acessar endereos de memria de elementos ctcios, ou seja, endereos de memria que no pertencem ao array. Isto acontece quando usamos um ndice que no esteja entre 0 e n-1 para um array de tamanho n. O compilador no gera nenhum aviso quando isto acontece. Quando executamos um acesso fora dos limites do array, o resultado pode ser desastroso. Isto siginica que o programa pode no fazer nada, cancelar a execuo, travar o computador, entrar em um loop innito, etc. Se voc executar uma atribuio a um elemento do array fora do seu limite, voc estar escrevendo em um endereo de memria que pode conter algo importante, destruindo-o. Em geral, erros como estes so difceis de encontrar, j que o programa pode at executar, s que faz algo estranho. Se voc estiver usando o Code::Blocks , voc poder ver uma mensagem como Esta aplicao violou a integridade do 59
sistema devido a execuo de uma instruo invlida e ser cancelada.. No entre em pnico !! Voc ter que reinicializar o seu computador e examinar o seu programa cuidadosamente para achar acessos a array fora do seu limite. claro que ao reinicializar o seu computador, voc perder todo o seu trabalho se no tiver salvado antes. MORAL: depois que seu programa compilar com sucesso, salve o seu programa em disco antes de execut-lo. Por exemplo, considere o seguinte programa: int main() { int ops[10], i; // Acesso fora dos limites quando i = 10 i = 0; while (i <= 10) { ops[i] = 0; i += 1; } } Este programa conta de 0 a 10, inicializando cada elemento do array com 0. O problema ocorre quando i tem o valor 10. Neste ponto, o programa coloca 0 em ops[10]. Isto pode produzir resultados indenidos (e desastrosos) embora o compilador no gere nenhum erro.
13.4
Para passar um array como argumento (com todos os seus elementos) de uma funo passamos o nome do array. Considere o exemplo abaixo. #include <iostream> use namespace std; #define TAMANHO 5 // funcao array_max(a) // acao: acha o maior inteiro de um array de TAMANHO elementos // entrada: array a de inteiros // saida: o maior valor do array // suposicoes: a tem TAMANHO elementos // algoritmo: inicializa max com o primeiro elemento do array; em // uma repeticao compara o max com todos os elementos // do array em ordem e muda o valor de max quando um // elemento do array for maior que o max ja encontrado. // int array_max(int a[]) { int i, max; // Achar o maior valor do array max = a[0]; 60
i = 1; while (i < TAMANHO) { if (max < a[i]) { max = a[i]; } i += 1; } return max; }
/* Programa principal */ int main() { int i, valor[TAMANHO]; i = 0; while (i < TAMANHO) { cout << "Entre um inteiro: "; cin >> valor[i]; i += 1; } cout << "O maior eh " << array_max(valor) << endl; }
Aqui est um exemplo de execuo deste programa Entre um inteiro: Entre um inteiro: Entre um inteiro: Entre um inteiro: Entre um inteiro: O maior e 85 73 85 42 -103 15
Em main() a chamada para array max() tem valor como seu argumento, que copiado para o parmetro formal a, que um array de inteiros. Note que o tamanho no foi especicado, somente o nome do array, a. Porm tambm correto incluir o tamanho (isto uma questo de estilo escolha o que voc preferir): int array_max(int a[TAMANHO]) { ... } A incluso do tamanho de um array unidimensional na denio da funo somente por razes de legibilidade (para arrays multi-dimensionais, todas as dimenses exceto a primeira deve ser especicada). At este ponto, parece que no h diferena entre passar uma varivel simples e um array como argumento para uma funo. Mas h uma diferena fundamental: QUANDO PASSAMOS O ARRAY COMO 61
ARGUMENTO, ALTERAES NO ARRAY FEITAS DENTRO DA FUNO ALTERAM O CONTEDO DO ARRAY PASSADO COMO PARMETRO REAL NA CHAMDA DA FUNO.. Note que isto no acontece quando uma varivel simples passada como argumento. Considere o exemplo seguinte: #include <iostream> using namespace std; // Troca o valor de uma variavel void troca( int a ){ a = 20; } // Troca valores de elementos em um vetor void troca_vet( int vet[] ){ vet[0] = 60; vet[1] = 70; vet[2] = 80; } // Programa Principal int main() { int x, y; int v[3]; x = 10; v[0] = 30; v[1] = 40; v[2] = 50; troca( x ); cout << "x=" << x << endl; troca_vet( v ); cout << "v[0]=" << v[0] << " v[1]=" << v[1] << " v[2]=" << v[2] ; } A sada deste programa : x=10 v[0]=60 v[1]=70 v[2]=80 O valor da varivel x do programa principal no se altera porque como j vimos nas notas de aula 7, quando a funo troca chamada, o valor do argumento real x avaliado, que 10, este valor copiado para o parmetro formal a da funo troca e a funo ento executada. O parmetro a da funo tratada como varivel local, portanto quando atribumos 20 a a, estamos atribuindo 20 a uma varivel local. Terminada a funo, a execuo retorna ao programa principal, que imprime o valor de x, que no foi alterado, ou seja, imprime x=10. Quando a funo troca_vet chamada, o array v passado como argumento e copiado para o parmetro formal vet. A funo ento executada, e os elementos do array so alterados para 60, 70, 80. Como mencionado anteriormente, quando passamos um array como parmetro, as alteraes feitas no 62
array dentro da funo alteram o array passado como parmetro. Portanto, quando a funo termina e a execuo continua no programa principal com a impresso dos valores dos elementos de v, ser impresso 60, 70, 80, os novos valores alterados de dentro da funo troca_vet. Vamos entender por que quando passamos s o nome do array como argumento as alteraes afetam o array passado como parmetro real. Como j mencionamos anteriormente, quando um array denido, como v no programa principal acima, alocado espao suciente na memria para conter todos os elementos do array. Na ilustrao abaixo, so alocados 6 bytes de memria a partir do endereo 1342 para conter o array. O array como um todo no tem um valor, mas cada elemento do array tem (neste caso, foram inicializados com 30, 40, 50). O nome do array, na verdade, contm o endereo onde comea o array, neste caso, o endereo 1342. Portanto, quando passamos o nome do array como argumento para uma funo estamos na realidade passando como argumento o endereo de memria onde comea o array. No exemplo anterior, 1342 passado como argumento para o parmetro formal vet da funo troca_vet. Portanto, da mesma forma que no caso da varivel simples, o valor de v, que o endereo 1342, copiado para o parmetro vet de troca_vet. Ento, quando a funo troca_vet executada, vet um array de elementos do tipo int que comea no endereo 1342. Quando atribumos o valor 60 a vet[0], estamos atribuindo 60 ao primeiro elemento do array que comea no endereo 1342. Como este o mesmo endereo onde comea o array v do programa principal, quando a funo troca_vet termina, o array v enxergar o valor dos elementos do array que comea no endereo 1342, que foram alterados pela funo.
Quando passamos variveis simples como argumento para uma funo estamos passando somente o valor da varivel, portanto, de dentro da funo no possvel saber qual o endereo da varivel para poder alter-la. Lembre-se que o endereo s passado para a funo quando passamos o array COMO UM TODO (ou seja, o nome do array, sem ser indexado por um elemento). Se passarmos como argumento apenas um elemento do array, o comportamento o mesmo que se passssemos uma varivel simples. Ou seja, o nome do array indexado por um valor entre colchetes refere-se ao valor do elemento do array, enquanto o nome do array sozinho refere-se ao endereo onde comea o array. Assim, no programa abaixo: #include <iostream> using namespace std; // Troca o valor de uma variavel void troca( int a ){ a = 20; } // Troca valores de elementos em um vetor 63
void troca_v( int vet[] ){ vet[0] = 60; vet[1] = 70; vet[2] = 80; } // Programa Principal int main(){ int v[] = {30, 40, 50}; troca( v[0] ); cout << "v[0]=" << v[0] << endl; troca_v( v ); cout << "v[0]=" << v[0] << " v[1]=" << v[1] << " v[2]=" << v[2] ; }
A sada do programa : v[0]=30 v[0]=60 v[1]=70 v[2]=80 Outro exemplo: a funo inicializaArray abaixo inicializa todos os elementos do array valor com um valor passado como argumento pelo programa principal. #include <iostream> using namespace std; #define TAMANHO 30 // funcao inicializaArray(a, k) // acao: inicializa todos os elementos de a com k // entrada: array de inteiros a, inteiro k // saida: nenhum // suposicoes: a tem TAMANHO elementos // algoritmo: uma repeticao for, inicializando um elemento a // cada repeticao // void inicializaArray(int a[], int k) { int i; i = 0; while (i < TAMANHO) { a[i] = k; i += 1; } } // Programa principal int main() 64
{ int valor[TAMANHO]; // Inicializa todos os elementos do array com 42 inicializaArray(valor, 42); // O resto do programa principal // . // . // . } Como as alteraes feitas por inicializaArray so vistas do programa principal, depois da funo inicializaArray ser executada, no programa principal todos os elementos do array valor tero o valor 42.
13.5
Pesquisar (procurar) em um array um determinado valor (chamado de chave) um problema muito comum em programao. Ele tem diversas aplicaes. Por exemplo, podemos pesquisar um array de notas para vericar se algum aluno tirou 100 na prova. H diversos algoritmos de pesquisa: cada um com suas vantagens e desvantagens. Nestas notas de aula, discutiremos um algoritmo simples, chamado de pesquisa linear. A pesquisa feita usando uma repetio e examinando cada elemento do array a cada repetio e comparando o elemento com a chave que buscamos. A pesquisa termina quando um elemento do array que casa com a chave encontrada, ou quando o array todo percorrido e a chave procurada no encontrada. 13.5.1 O Problema
Escreva uma funo pesquisa linear que tem como argumento de entrada: um array de inteiros a ser pesquisado, o tamanho do array, e uma chave (um valor inteiro) a ser procurado. A funo retorna um inteiro: o ndice do elemento do array (se a chave for achada) ou -1 caso contrrio. 1. Prottipo: int pesquisa_linear(int [], int, int); 2. Denio: #define NAO_ACHOU -1 /* Procura uma chave em um array * entrada: array a ser pesquisado (arr ), tamanho do array (tam), chave a ser procurada (chave) * saida: o indice do elemento que e igual a chave ou -1 caso nao ache * * suposicao: nao assume que o array esteja ordenado */ int pesquisa_linear(int arr[], int tam, int chave) { int i; 65
13.6
13.6.1
Escrever uma funo que some dois arrays de floats, do mesmo tamanho. Dar o resultado em um terceiro array. 1. Prottipo: void soma_array( float [], float [], float [], int ); 2. Denio de soma array(): void soma_array( float arr1[], float arr2[], float arr3[], int tam ) { int i; for (i = 0; i < tam; i++) arr3[i] = arr1[i] + arr2[i]; }
13.7
Um outro programa muito popular com arrays orden-lo de acordo com algum critrio. Por exemplo, um array de inteiros pode ser ordenado em ordem crescente ou decrescente. O algoritmo chamado Bubble sort bastante simples (porm no muito eciente) de ordenao. Basicamente, o algoritmo funciona da seguinte forma: na primeira passagem sobre o array: comeando do ltimo elemento do array at o segundo elemento, compare o valor de cada elemento com o valor do elemento anterior a ele. Se os elementos comparados estiverem fora de ordem, trocar os seus valores. Depois que esta primeira passada terminar, o que acontece que o menor elemento do array torna-se o primeiro elemento do array. na segunda passagem pelo array: comeando com o ltimo elemento do array at o terceiro elemento, compare o valor de cada elemento com o valor do elemento anterior a ele. Se os dois elementos comparados estiverem fora de ordem, trocar os seus valores. Depois que esta passagem sobre o array terminar, o segundo menor elemento do array ser o segundo elemento do array. repetir a passagem sobre o array de maneira similar at que a ltima passagem ser simplesmente uma comparao dos valores do ltimo elemento com o elemento anterior. Por exemplo, se comearmos com um array: 9 8 7 6 5 4 3 2 1, (o primeiro elemento 9 e o ltimo elemento 1) isto o que aocntece com os elementos do array depois de cada passagem sobre ele (e troca de valores adjacentes):
66
passagem ~~~~ 1 --> 2 --> 3 --> 4 --> 5 --> 6 --> 7 --> 8 -->
Note que mesmo que se comessemos com um array ordenado de 9 elementos, ainda assim o algoritmo dado faz 8 passagens sobre o array. Para melhorar isso, ns apresentamos uma verso melhorada do Bubble sort. A idia a seguinte. Antes de comear cada passagem, inicializamos uma varivel ordenado com 1. Se durante a passagem uma troca de valores ocorrer, trocamos o valor da varivel para 0. Assim, se depois da passagem, o valor da varivel continuar sendo 1, isso signica que nenhuma troca ocorreu e que o array est ordenado. 13.7.1 13.7.2 Bubble sort Algoritmo
Enquanto o array nao estiver ordenado 1. inicializar ordenado com 1 2. comparar pares adjacentes do array troque seus valores se estiver fora de ordem ordenado = 0. 13.7.3 Prottipo da funo e denio
1. Prottipo void bubble_sort(int [], int); 2. Denicao /* Uma funcao que ordena um array de inteiros usando o algoritmo de * Bubble sort. * Entrada: array a ser ordenado -- lista[] tamanho do array -- tam * / * void bubble_sort(int lista[], int tam) { 67
int ordenado, // se 1 depois da passagem o array esta ordenado elem_final = 1, // em uma passagem, elementos do ultimo ate // elem_final sao comparados com o // elemento anterior i,j, temp; // enquanto o array nao estiver ordenado, fazer uma passagem sobre ele do { ordenado = 1; // assume que array esta ordenado // Examina o array do ultimo elemento ate elem_final. Compara // cada elemento com o anteior e troca seus valores se estiver // fora de ordem. for (i = tam - 1; i >= elem_final; i--) { if (lista[i] < lista[i - 1]) { // troca os elementos de i e i-1 temp = lista[i]; lista[i] = lista[i - 1]; lista[i - 1] = temp; ordenado = 0; // marca como nao ordenado } } elem_final++; } while (!ordenado); }
13.8
Comentrios Finais
Neste curso, um dos nicos lugares que veremos o nome do array sem estar indexado quando passamos o array (como um todo) para uma funo. Para outras nalidades, veremos sempre o array indexado. Por exemplo, o seguinte trecho de programa est errado: int main(){ int arr1[4] = {10, 20, 30, 40}; int arr2[4]; arr2 = arr1; // ERRADO: copia arr1 em arr2 // tem que copiar elemento por elemento
if( arr1 == arr2 ) // ERRADO: nao podemos comparar arrays inteiros cout << "X"; // tem que comparar elemento por elemento }
68
14
Arrays Multidimensionais
Nas notas de aula anteriores, apresentamos arrays unidimensionais. Em C++ , possvel tambm denir arrays com 2 ou mais dimenses. Eles so arrays de arrays. Um array de duas dimenses podem ser imaginado como uma matriz (ou uma tabela). Como voc deve ter imaginado, para denir e acessar arrays de dimenses maiores, usamos colchetes adicionais ([ e ]). Por exemplo: int tabela[3][5]; Dene um array bidimensional chamado tabela que uma matriz 3 por 5 de valores do tipo int (15 valores no total). Os ndices da primeira dimenso vo de 0 a 2, e os ndices da segunda dimenso vo de 0 a 4. Abaixo apresentamos um programa que imprime os elementos de um array bidimensional. #include <iostream> using namespace std; #define ALTURA 5 #define LARGURA 5 int main() { int x; int y; char matriz [ALTURA] [LARGURA];
// // //
//
y = 0; while(y < ALTURA) { x = 0; while(x < LARGURA) { matriz[y][x] = 0; x+=1; } y+=1; } // Imprime a matriz com zeros e a coordenada escolhida com 1
cout << endl << "Entre coordenadas na forma \"y x\"." << endl; cout << "Use valores negativos para sair do programa." << endl; cout << "Coordenadas: "; cin >> y >> x; while (x >= 0 && y >= 0) 69
y = 0; while (y < ALTURA) // imprime o array todo { x = 0; while (x < LARGURA) { cout << matriz[y][x] << " "; x += 1; } cout << endl << endl; y += 1; } cout << endl; cout << "Coordenadas: "; cin >> y >> x; } } Neste exemplo, matriz um array bidimensional. Ela tem nmero de elementos igual a ALTURAxLARGURA, sendo cada elemento do tipo int. O exemplo abaixo preenche os elementos de um array bidimensional com os valores que representam a taboada e imprime a matriz. ATENO: a partir daqui os exemplos usam a estrutura de controle for. Veja a explicao sobre esta estrutura (uma variao do while()) na Seo 19.1. // Exemplo de array 2-D - taboada
#include <iostream> #include <iomanip> using namespace std; #define LIN 10 #define COL 10 int main() { int x; int y; int tabela[LIN] [COL]; // preenche a tabela
// // //
for (y=0; y < LIN; y+=1) for(x=0; x < COL; x+=1) tabela[y][x] = y*x; cout << "\n Tabela de Multiplicacao\n"; 70
//
cout << setw(6) << 0; for (x=1; x < COL; x+=1) cout << setw(3) << x; cout << endl; // Imprime uma linha horizontal cout << " "; for (x=0; x < 3*COL; x+=1) cout << "-"; cout << endl; // // Imprime as linhas da tablea. Cada linha a precedida pelo indice de linha e uma barra vertical
for (y=0; y < LIN; y+=1) { cout << setw(2) << y << "|"; for(x=0; x < COL; x+=1) cout << setw(3) << tabela[y][x]; cout << endl; } } A sada do programa : Tabela de Multiplicacao 0 1 2 3 4 5 6 7 8 9 -----------------------------0| 0 0 0 0 0 0 0 0 0 0 1| 0 1 2 3 4 5 6 7 8 9 2| 0 2 4 6 8 10 12 14 16 18 3| 0 3 6 9 12 15 18 21 24 27 4| 0 4 8 12 16 20 24 28 32 36 5| 0 5 10 15 20 25 30 35 40 45 6| 0 6 12 18 24 30 36 42 48 54 7| 0 7 14 21 28 35 42 49 56 63 8| 0 8 16 24 32 40 48 56 64 72 9| 0 9 18 27 36 45 54 63 72 81
14.1
Inicializao
Arrays multidimensionais podem ser inicializados usando listas aninhadas de elementos entre chaves. Por exemplo, um array bidimensional tabela com trs linhas e duas colunas pode ser inicializado da seguinte forma: double tabela[3][2] = { {1.0, 0.0}, {-7.8, 1.3}, {6.5, 0.0} }; 71 // // // linha 0 linha 1 linha 2
Quando o array inicializado, o tamanho da primeira dimenso pode ser omitido. A denio de array abaixo equivalente a dada anteriormente. double tabela[][2] = { {1.0, 0.0}, {-7.8, 1.3}, {6.5, 0.0} }; // // // linha 0 linha 1 linha 2
14.2
O formato da denio de um array de dimenso k , onde o nmero de elementos em cada dimenso n0 , n1 , . . . , nk1 , respectivamente, : nome_tipo nome_array [n0 ][n1 ]...[nk1 ]; Isto dene um array chamado nome_array consistindo de um total de n0 n1 . . . nk1 elementos, sendo cada elemento do tipo nome_tipo. Arrays multidimensionais so armazenados de forma que o ltimo subscrito varia mais rapidamente. Por exemplo, os elementos do array int tabela[2][3]; so armazenados (em endereos consecutivos de memria) como
tabela[0][0], tabela[0][1], tabela[0][2], tabela[1][0], tabela[1][1], tabela[1][2 Um array de dimenso k, onde o nmero de elementos em cada dimenso n0 , n1 , . . . , . . . , nk1 , respectivamente, pode ser imaginado como um array de dimenso n0 cujos elementos so arrays de dimenso k 1. Por exemplo, o array bidimensional tabela, com 20 elementos do tipo int int tabela[4][5] = { {13, {20, {31, {40, 15, 22, 33, 42, 17, 24, 35, 44, 19, 26, 37, 46, 21}, 28}, 39}, 48} };
pode ser imaginado como um array unidimensional de 4 elementos do tipo int[], ou seja, arrays de int; cada um dos 4 elementos um array de 5 elementos do tipo int: tabela[0] tabela[1] tabela[2] tabela[3] ---> ---> ---> ---> {13, {20, {31, {40, 15, 22, 33, 42, 17, 24, 35, 44, 19, 26, 37, 46, 21} 28} 39} 48}
14.3
Quando o parmetro formal de uma funo um array multidimensional (um array com dimenso maior que um), todas as dimenses deste array, exceto a primeira, precisa ser explicitamente especicada no cabealho e prottipo da funo. tipo_do_resultado nome_da_f uncao ( nome_do_tipo nome_do_array [ ][n1 ]...[nk1 ], ...... ) Quando uma funo com um parmetro formal do tipo array chamada, na chamada da funo somente o nome do array passado como parmetro real. O tipo (e portanto a dimenso) do array passado como parmetro real deve ser consistente com o tipo (e portanto a dimenso) do array que o parmetro formal. O programa abaixo mostra o exemplo da tabela de multiplicao escrita usando funes. 72
//
#include <iostream> #include <iomanip> using namespace std; #define LIN 10 #define COL 10 void inicializa_arr (int arr[][COL], int); void imprime_arr (int arr[][COL], int); int main() { int tabela[LIN] [COL]; inicializa_arr(tabela, LIN); cout << "\n Tabela de Multiplicacao\n";
void inicializa_arr (int arr[][COL], int nlin) { int x; // numero da coluna int y; // numero da linha // preenche o array
for (y=0; y < nlin; y++) for(x=0; x < COL; x++) arr[y][x] = y*x; } // imprime um array LIN x COL
void imprime_arr(int arr[][COL], int nlin) { int x; // numero da coluna int y; // numero da linha // imprime o numero das colunas
cout << setw(6) << 0; for (x=1; x < COL; x++) cout << setw(3) << x; cout << endl; 73
// imprime uma linha horizontal cout << " "; for (x=0; x < 3*COL; x++) cout << "_"; cout << endl; // // imprime as linhas do array. cada linha e precedida pelo numero da linha e uma barra vertical
for (y=0; y < nlin; y++) { cout << setw(2) << y << "|"; for(x=0; x < COL; x++) cout << setw(3) << arr[y][x]; cout << endl; } } Outro exemplo com funoes de manipulao de arrays bidimensionais: // funcoes com argumentos tipo array 2-D
#include <iostream> using namespace std; #define ALTURA 5 #define LARGURA 5 void void void void void void seleciona_elem (char [][LARGURA], int); pontos (char [][LARGURA], int); imprime_matriz (char [][LARGURA], int); marca_triang (char [][LARGURA], int); flip (char [][LARGURA], int); espera_entrada(void);
// *** DEFINICAO DE FUNCOES ******* // funcao que preenche uma matriz nlin X LARGURA com pontos void pontos( char matriz[][LARGURA], int nlin) { int x,y; for(y=0; y<nlin; y++) for(x=0; x<LARGURA; x++) matriz[y][x] = .; } /* funcao que preenche os elementos selecionados da matriz com um * quadrado e imprime a matriz 74
*/ void seleciona_elem(char matriz[][LARGURA], int nlin) { int x, y; cout << "\nEntre com as coordenadas na forma \"y x\".\n"; cout << "Use numeros negativos para terminar.\n"; while (1) { cout << "Coordenadas: "; cin >> y >> x; if (x >= 0 && y >= 0) { matriz[y][x]=\xB1; // preenche o elemento com quadrado imprime_matriz(matriz, nlin); // imprime a matriz } else break; } } /* funcao que marca todos os elementos abaixo da diagonal principal de * um array nlin X LARGURA com quadrados */ void marca_triang(char matriz[][LARGURA], int nlin) { int x, y; cout << "Triangulo\n"; pontos(matriz, nlin); for (y = 0; y < nlin; y++) for (x = 0; x <= y; x++) matriz[y][x] = \xB1; } // funcao que imprime um array 2-D nlin X LARGURA void imprime_matriz(char matriz[][LARGURA], int nlin) { int x,y; for(y=0; y<nlin; y++) { for(x=0; x<LARGURA; x++) cout << matriz[y][x] << " "; cout << endl << endl; } cout << endl; } // funcao que flipa um array ao longo da diagonal principal void flip(char matriz[][LARGURA], int nlin) 75
{ int x, y; int temp; cout << "Flipado ao longo da diagonal principal.\n"; for (y = 0; y < nlin; y++) for (x = 0; x <= y; x++){ temp = matriz[y][x]; matriz[y][x] = matriz[x][y]; matriz[x][y] = temp; } } // funcao que espera ate que uma tecla seja digitada void espera_entrada( void ) { char dummy; cin.get(dummy); } // ********* MAIN *********** // alguns exemplos de chamadas de funcoes com argumentos array 2-D int main() { char matriz [ALTURA] [LARGURA]; pontos(matriz, ALTURA); seleciona_elem(matriz, ALTURA); espera_entrada(); flip(matriz, ALTURA); imprime_matriz(matriz,ALTURA); espera_entrada(); marca_triang( matriz, ALTURA); imprime_matriz( matriz, ALTURA); espera_entrada(); flip( matriz, ALTURA); imprime_matriz(matriz, ALTURA); espera_entrada(); }
76
Parte II
Tpicos Avanados
As sees seguintes apresentam temas mais avanados da linguagem C++ , no abordadas em sala de aula. Elas podem ser estudadas pelo aluno como atividade complementar, pois apresentam mecanismos bastantes teis em programas mais complexos (tratamento de arquivos e textos, manipulao dinmica de memria, etc.).
77
15
15.1
tamanho = tamanho * 2.5; x = x * (y + 1); j = j - 1; C++ fornece operadores adicionais que podem ser usados para tornar estes tipos de atribuies mais curtos. H um operador de atribuio para cada operao aritmtica listada anteriormente: += operao de atribuio de adio -= operao de atribuio de subtrao *= operao de atribuio de multiplicao /= operao de atribuio de diviso %= operao de atribuio de resto Cada uma dessas operaes podem ser usadas para tornar as expresses anteriores mais curtas: tudo += parte; tamanho *= 2.5; x *= y + 1; j -= 1;
15.2
H alguns operadores em C++ que so equivalentes as seguintes expresses (que so bastante comuns em programas): k = k + 1; j = j - 1; Estes operadores adicionais, que so ++ e --, podem ser usados para encurtar as operaes acima: k++; j--; Estes operadores tambm podem ser colocados depois do nome da varivel: 78
++k; --j; O fato do operador de incremento ser colocado antes ou depois da varivel no altera o efeito da operao o valor da varivel incrementada ou decrementada de um. A diferena entre os dois casos QUANDO a varivel incrementada. Na expresso k++, o valor de k primeiro usado e ento incrementada isto chamado ps-incremento. Na expresso ++k, k incrementado primeiro, e ento o valor (o novo valor) de k usado isso chamado pr-incremento. A diferena ilustrada nos seguintes exemplos: int main() { int k = 5; cout << "k1 = " << k << endl; cout << "k2 = " << k++ << endl; cout << "k3 = " << k << endl; } O programa acima (que usa ps-incremento) imprimir o seguinte: k1 = 5 k2 = 5 k3 = 6 A segunda linha impressa com o valor de k 5 porque o valor de k++ era 5, e k 6 depois da impresso. Para o programa: int main() { int k = 5; cout << "k1 = " << k << endl; cout << "k2 = " << ++k << endl; cout << "k3 = " << k << endl; } O programa, que usa pr-incremento, ter a seguinte sada: k1 = 5 k2 = 6 k3 = 6 A segunda linha impressa 6 porque o valor de ++k 6. Os operadores de atribuio no podem ser usados com expresses aritmticas. Por exemplo, as expresses (ack + 2)++; (nope + 3) += 5; resultaro em erros de compilao. Finalmente, quando usar o operador de incremento em um cout, tome cuidado para no fazer o seguinte: 79
cout << ++uhoh << uhoh * 2 << endl; Embora isso seja perfeitamente legal em C++ , os resultados no so garantidados que sejam consistentes. A razo para isso que no h garantia que os argumentos do cout sejam avaliados em uma determinada ordem. O resultado do cout ser diferente dependendo se ++uhoh avaliado primeiro ou depois de uhoh * 2. A soluo para este problema escrever o seguinte: ++uhoh; cout << uhoh << uhoh * 2 << endl;
15.3
J que incremento e decremento so formas de atribuio, o operando deve ser um lvalue. O valor de uma expresso de incremento ou decremento depende se o operador usado na notao PR ou PS xada (x++, ++x, x--, --x). Se for pr-xada, o valor da expresso o novo valor aps o incremento ou decremento. Se for ps-xada, o valor da expresso o valor antigo (antes do incremento ou decremento). Por exemplo no caso de incremento, a expresso: x++ tem o valor de x ++x tem o valor de x + 1 Note que no importando a notao usada, o valor de x (o contedo do endereo de memria associada a x) ser x + 1. A diferena est no valor das expresses x++ e ++x, no no valor de x (em ambos os casos o valor de x ser incrementada de um).
15.4
s vezes, problemas podem acontecer devido o fato que C++ no especica a ordem de avaliao dos operadores em uma operao binria. Em outras palavras, em expresses como a + b ou a < b, no h maneira de saber se o valor de a ser avaliado antes ou depois de b (pense em a e b como sendo qualquer expresso, no somente variveis.) Qual deles ser avaliado primeiro particular de cada compilador, e diferentes compiladores em mquinas diferentes podem ter resultados diferentes. Portanto, se a avaliao de um dos operadores pode alterar o valor do outro, o resultado pode ser diferente dependendo da ordem de avaliao. Portanto, em expresses do tipo x + x++, o valor pode diferir dependendo do compilador utilizado. Isto porque no sabemos quando exatamente o incremento de x ocorre. Outros maus exemplos: y = x + x-- e x = x++. De forma geral, para evitar este problema, no utilize senteas como estas.
80
16
Expresses no tem somente um valor, mas tambm tem um tipo associado. Se ambos os operandos de uma operao aritmtica binria so do mesmo tipo, o resultado ter o mesmo tipo. Por exemplo: 3 + 5 8, e o tipo int 3.5 + 2.25 5.75, e o tipo double O nico comportamento no bvio a da diviso de inteiros: 30 / 5 6 31 / 5 6 29 / 5 5 3 / 5 0
1 Lembre-se de evitar escrever algo como 1 / 2 * x signicando 2 x. Voc sempre obter o valor 0 porque 1 / 2 * x (1 / 2) * x que 0 * x que 0. Para obter o resultado desejado, voc poderia escrever 1.0 / 2.0 * x.
16.1
Converso de tipos
Valores podem ser convertidos de um tipo para outro implicitamente, da forma j comentada anteriormente. Em expresses envolvendo operadores binrios com operandos de tipos diferentes, os valores dos operandos so convertidos para o mesmo tipo antes da operao ser executada: tipos mais simples so promovidos para tipos mais complexos. Portanto, o resultado da avaliao de uma expresso com operandos de tipos diferentes ser o tipo do operando mais complexo. Os tipos em C++ so (do mais simples para o mais complexo): char < int < long < float < double O sinal de < signica que o tipo da esquerda promovido para o tipo da direita, e o resultado ser do tipo mais a direita. Por exemplo: 3.5 + 1 4.5 4 * 2.5 10.0 Esta regra estende-se para expresses envolvendo mltiplos operadores, mas voc deve se lembrar que a precedncia e associatividade dos operadores pode inuenciar no resultado. Vejamos o exemplo abaixo: int main() { int a, b; cout << "Entre uma fracao (numerador e denominador): "; cin >> a >> b; cout << "A fracao em decimal e } 81 " << 1.0 * a / b << endl;
Multiplicando por 1.0 assegura que o resultado da multiplicao de 1.0 por a ser do tipo real, e portanto, a regra de converso automtica evitar que o resultado da diviso seja truncado. Note que se tivssemos primeiro feito a diviso a/b e depois multiplicado por 1.0, embora o tipo da expresso a/b*1.0 seja do tipo double, o valor da expresso seria diferente do valor de 1.0 * a/b. Por que ? Em atribuies, o valor da expresso do lado direito convertido para o tipo da varivel do lado esquerdo da atribuio. Isto pode causar promoo ou rebaixamento de tipo. O rebaixamento pode causar perda de preciso ou mesmo resultar em valores errados. Em operaes de atribuio, atribuir um int em um float causar a converso apropriada, e atribuir um float em um int causar truncamento. Por exemplo: float a = 3; equivalente a a = 3.0 int a = 3.1415; equivalente a a = 3 (truncado) Basicamente, se o valor da expresso do lado direito da atribuio de um tipo que no cabe no tamanho do tipo da varivel do lado esquerdo, resultados errados e no esperados podem ocorrer.
16.2
Modicadores de tipos
Os tipos de dados bsicos em C++ podem estar acompanhados por modicadores na declarao de variveis. Tais modicadores so: long, short, signed e unsigned. Os dois primeiros tm impacto no tamanho (nmero de bits) usados para representar um valor e os dois ltimos indicam se o tipo ser usado para representar valores negativos e positivos (signed) ou sem este modicador) ou apenas positivos (unsigned). A Tabela 4 mostra uma lista completa de todos os tipos de dados em C++ , com e sem modicadores:
Modicador char unsigned char int unsigned int short int unsigned short int long int unsigned long int oat double long double
Tamanho em bits 8 8 16 16 16 16 32 32 32 64 80
Faixa de valores -127 a 127 0 a 255 -32767 a 32767 0 a 65535 -32767 a 32767 0 a 65535 -2147483647 a 2147483647 0 a 4294967295 Mantissa de 6 dgitos Mantissa de 10 dgitos Mantissa de 10 dgitos
16.3
Cast de tipos
C++ tem um operador para alterar o tipo de um valor explicitamente. Este operador chamado de cast. Executando um cast de tipos, o valor da expresso forado a ser de um tipo particular, no importando a regra de converso de tipos. O formato do cast de tipos : (nome-do-tipo)expressao 82
O parnteses NO opcional na expresso acima. Podemos usar o cast de tipos da seguinte forma: int fahr = 5; float cels; cout << "Valor = " << (float) fahr << endl; cels = (float)5 / 9 * (fahr - 32); cout << "celsius = " << (int) cels << endl; Agora que conhecemos o operador de cast de tipo podemos reescrever o programa que faz a converso de frao para decimal. int main() { int a, b; cout << "Entre com uma fracao (numerador e denominador): "; cin >> a >> b; cout << "A fracao em decimal e } O cast de tipo tem a maior precedncia possvel, portanto podemos fazer o cast de a ou de b para ser do tipo float, e no h necessidade de parnteses extra. No exemplo acima, o cast causa o valor da varivel a ser convertido para float, mas no causa mudana no tipo da varivel a. O tipo das variveis denido uma vez na declarao e no pode ser alterado. " << (float) a / b << endl;
83
17
A sentena switch
A sentena switch outra maneira de fazer decises mltiplas. Ele pode ser usado para testar se uma dada expresso igual a um valor constante e, dependendo do valor, tomar determinadas aes. O formato da sentena switch : switch (expressao) { case expressao-constante 1: sentencas 1 case expressao-constante 2: sentencas 2 . . . default: sentencas n }
A sentena switch primeiro avalia a expresso. Se o valor da expresso for igual a uma das expresses constantes, as sentenas que seguem o case so executados. Se o valor da expresso no for igual a nenhuma das constantes, as sentenas que seguem default so executadas. As sentenas que seguem o case so simplesmente uma lista de sentenas. Esta lista pode conter mais de uma sentena e no necessrio coloc-las entre chaves ({ e }). A lista de sentenas tambm pode ser vazia, isto , voc pode no colocar nenhuma sentena seguindo o case. Tambm no obrigatrio colocar o default. S o use quando for necessrio. Note no diagrama acima que TODAS as sentenas que seguem a constante com o valor igual ao da expresso sero executados. Para que se execute APENAS as sentenas que seguem o case que seja igual ao valor da expresso precisamos usar a sentena break, que veremos em seguida.
18
A sentena break
O break faz com que todas as sentenas que o seguem dentro da mesma sentena switch sejam ignorados. Ou seja, colocando a sentena break no nal de uma sentena case faz com que as sentenas que seguem os cases subsequentes no sejam executadas. Em geral, este o comportamento desejado quando se usa o switch, e cases sem o break no nal so de pouca utilidade. Portanto, o uso de sentenas case sem o break devem ser evitados e quando utilizados devem ser comentados ao lado com algo como /* continua proxima sentenca - sem break */. Com a sentena break o diagrama de uxo ca: 84
Note a similaridade com o diagrama da sentena else-if e a diferena com o diagrama da sentena switch acima. O prximo programa tem a mesma funo de calculadora do programa anterior, porm utilizando a sentena switch. Exemplo 11: #include <iostream> using namespace std; int main( ){ float num1, num2; char op; // obtem uma expressao do usuario cout << "Entre com numero operador numero\n"; cin >> num1 >> op >> num2; switch (op) case +: cout << " break; case -: cout << " break; case *: cout << " break; case /: cout << " break; default: cout << " break; { = " << setprecision(2) << num1 + num2;
Operador invalido."; 85
} cout << endl; } Como mencionado anteriormente, possvel no colocar nenhuma sentena seguindo um case. Isso til quando diversas sentenas case (diversas constantes) tm a mesma ao. Por exemplo, podemos modicar o programa acima para aceitar x e X para multiplicao e \ para diviso. O programa ca ento: #include <iostream> using namespace std; int main( ){ float num1, num2; char op; // obtem uma expressao do usuario cout << "Entre com numero operador numero\n"; cin >> num1 >> op >> num2; switch (op) { case +: cout << " = " << setprecision(2) break; case -: cout << " = " << setprecision(2) break; case *: case x: case X: cout << " = " << setprecision(2) break; case /: case \\: cout << " = " << setprecision(2) break; default: cout << " Operador invalido."; break; } cout << endl; } Exerccio 2: Ler mes e ano e imprimir o numero de dias do mes no ano digitado. #include <iostream> using namespace std; int main() { int mes, ano, numDias; 86
cout << "Entre com mes e ano (mm aa): "; cin >> mes >> ano; if( mes < 1 || mes > 12 || ano < 0 || ano > 99 ) cout << "mes ou ano invalido\n"; else { switch( mes ){ case 1: case 3: case 5: case 7: case 8: case 10: case 12: numDias = 31; break; case 2: if( ano % 4 == 0 ) numDias = 29; else numDias = 28; break; default: numDias = 30; } cout << mes << "/" << ano << " tem " << numDias << " dias\n"; } }
87
19
Estruturas de Repetio
A linguagem C++ possui comandos para repetir uma sequncia de instrues. Estas estruturas de repetio, tambm conhecidas como laos (do ingls loops). A primeira construo que veremos o while, seguida de for e de do ... while.
19.1
Considere o lao while abaixo: int i; i = 0; /* valor inicial da varivel de controle da repetio */ while (i <= 10) /* Testa varivel de controle para saber se haver repetio ou no do corpo do "while" */ { .... .... i += 1; /* expresso de incremento da varivel de controle da repetio */ } Este lao pode ser expresso de forma diferente utilizando a estrutura de repetio for. A estrutura acima pode ser expressa de forma equivlente com um for da seguinte forma: int i; for (i = 0; i <= 10; i += 1) { .... .... }
Como pode-se observar, h 4 partes no lao for: inicializao, expresso de teste, expresso de incremento e o corpo do lao. O formato do lao for : for (inicializao ; expresso de teste ; incremento ){ corpo da repetio } A inicializao executada uma nica vez no incio do lao. A expresso teste ento avaliada para vericar se o lao deve terminar. Caso a expresso seja verdadeira (isto , diferente de Zero), o corpo da repetio executado. Depois desta execuo, a expresso de incremento executada e o processo repetido a partir da expresso teste. O corpo da repetio, por sua vez, pode ser uma sentena simples ou composta.
88
Veja abaixo um exemplo simples do lao for: int contador; for( contador = 0; contador < 5; contador += 1 ) cout << "contador = " << contador << endl; cout << "ACABOU !!!!\n"; Sada do programa: contador = 0 contador = 1 contador = 2 contador = 3 contador = 4 ACABOU !!!! Se voc examinar cuidadosamente este exemplo, poder ver precisamente o que est acontecendo. Primeiro, a inicializao executada, que a sentena contador = 0. Isso modica o valor da varivel contador para 0. Ento, o teste executado. como 0 < 5 verdadeiro, o lao continua. Assim, o corpo da repetio executado, imprimindo a primeira linha da sada, contador = 0. Depois disso, o incremento executado, que a sentena contador++, que altera o valor da varivel contador para 1. Esta a 1a iterao do lao. Ento, o teste executado novamente (como 1 < 5 verdadeiro, o lao continua), o corpo da repetio mostra contador = 1, e contador incrementado novamente. Este processo continua at que contador seja 4 e contador = 4 seja impresso. Depois disso, contador incrementado para 5 e o teste executado. Mas desta vez, 5 < 5 falso, ento o lao no continua. A execuo do programa continua na sentena que segue o lao (no caso, imprimir a frase ACABOU !!!). Aps a execuo do lao, a varivel contador tem valor 5. Ao invs de usar o teste contador < 5, voc poderia tambm ter usado a expresso contador <= 4. O resultado seria o mesmo. Use a expresso que voc preferir. Outra expresso que tambm poderia ter sido usada contador != 5. Porm esta expresso torna o programa menos legvel (no to evidente 89
que o valor de contador est sendo incrementado at atingir o valor 5). Alm disso, isso poderia causar problemas se mudssemos a inicializao para um valor maior que 5. Por exemplo, se a inicializao for contador = 25 e a expresso teste for contador != 5 o lao nunca terminaria, pois o contador comea com 25 e a cada iterao o valor incrementado, o que nunca tornaria o teste falso. Tambm poderamos ao invs de usar contador += 1 como a expresso de incremento, usar ++contador, contador++ e contador = contador + 1. O resultado seria o mesmo (neste caso, o uso de ps- e princremento no faz diferena). Se voc quisesse incrementos de dois, voc poderia escrever contador += 2 (ou contador = contador + 2). 19.1.1 Diversas sentenas dentro de um lao
Como no comando while, o corpo da repetio pode ser denido por uma sentena simples ou composta. No caso de uma sentena composta (bloco), no se deve esquecer de usar as chaves ( { e }) para delimitar o bloco da sentena composta. Em um for tambm podemos ter mais de uma expresso de inicializao ou incremento. Nestes caso, elas devem ser separadas por vrgula ( ,) o que ilustrado no exemplo abaixo: Exemplo 1: #include <iostream> using namespace std; int main( ){ int contador, total; for( contador = 0, total = 0; contador < 10; contador += 1 ){ total += contador; cout << "contador = " << contador << ", total = " << total << endl; } } Sada: contador contador contador contador contador contador contador contador contador contador = = = = = = = = = = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, total total total total total total total total total total = = = = = = = = = = 0 1 3 6 10 15 21 28 36 45
No exemplo acima, contador = 0, total = 0 a inicializao, contador < 10 a expresso teste, e contador += 1 a expresso de incremento. Exemplo 2: Um programa que imprime todos os nmeros entre 30 e 5 (nesta ordem) divisveis por 3, e no nal imprime sua soma.
90
#include <iostream> #include <iomanip> using namespace std; int main( ){ int i, soma; soma = 0; for( i = 30; i >= 5; i -= 1 ){ if( (i % 3) == 0 ){ cout << "\t" << setw(2) << i << endl; soma += i; } } cout << "\t soma = " << soma << endl; } Sada do programa: 30 27 24 21 18 15 12 9 6 soma = 162
19.2
Embora qualquer lao possa ser escrito usando while ou for, a escolha baseada principalmente no estilo. Por exemplo, se o lao precisa de uma inicializao e um incremento, ento o for geralmente usado. No caso em que o nmero de repeties no pr-determinado em geral usa-se o while. Como o comando for: for( inicializacao; teste; incremento ) sentenca;
equivalente a: inicializacao; while( teste ) { sentenca; incremento; }; voc pode escolher o que preferir, a princpio. 91
19.3
H outro comando de repetio em linguagem C++ . O do...while bastante parecido com while, com a diferena que a expresso de teste avaliada DEPOIS que o corpo da repetio executado. O formato do do...while : do corpo da repetio while (expresso teste )
O exemplo abaixo usa o do...while: int contador = 0; do { cout << "contador = " << contador << endl; contador += 1; } while( contador < 5 ); cout << "ACABOU !!!!\n"; A execuo deste programa idntico ao primeiro exemplo mostrado para o comando while, com a expresso de teste mudada para o nal. Sada: contador = 0 contador = 1 contador = 2 contador = 3 contador = 4 ACABOU !!!! O do...while usado quando o corpo da repetio deve ser executado pelo menos uma vez. Um exemplo comum disto o processamento da entrada de um programa. Exemplo 3: Neste exemplo, o teste do lao baseado no valor digitado pelo usurio. O lao deve ser executado pelo uma vez antes que o teste sobre o valor seja executado. #include <iostream> using namespace std; 92
int main( ){ int num; cout << "Entre com um numero par:\n"; do{ cin >> num; } while( num % 2 != 0 ); cout << "Obrigado.\n"; } Exemplo de execuo: Entre com um numero par: 3 1 5 4 Obrigado. Neste caso, o valor da varivel num digitado pelo usurio. Depois disso, o teste executado para vericar se o nmero par (o teste num % 2 != 0 falso se num par j que o resto da diviso de qualquer nmero par por 2 zero). possvel escrever o programa acima usando while: #include <iostream> using namespace std; int main( ){ int num; // Atribui um numero impar a num
cout << "Entre com um numero par:\n"; num = 1; while( num % 2 != 0 ){ cin >> num; } cout << "Obrigado.\n"; } O problema com este programa que a varivel num deve ser inicializada com um valor que torne o teste do lao verdadeiro. Neste exemplo, simples encontrar tal valor. Para uma expresso teste mais complicada, isso pode no ser to fcil.
93
20
Tipo Enumerado
Em muitos programas, variveis do tipo int so utilizadas no por suas propriedades numricas, mas para representar uma escolha dentre um pequeno nmero de alternativas. Por exemplo: int sexo; int cor; // masculino = 1 , feminino = 2 // vermelho = 1 , amarelo = 2 , verde = 3
A utilizao de cdigos para representar os valores que uma varivel poder assumir, certamente compromete a clareza da estrutura de dados do programa, tornando sua lgica obscura e inconsistente. Por exemplo: cor = 3; if( sexo == 2 ) ... cor = cor + sexo; for( cor = 1; cor < 10; cor ++ )... Um tipo enumerado permite denir uma lista de valores que uma varivel deste tipo poder assumir. A denio de um tipo enumerado feita da seguinte forma: enum N ome_do_tipo { valor1 , valor2 , . . ., valorn }; Exemplos de denio de tipos enumerados: enum TpCores {VERMELHO, AMARELO, VERDE}; enum TpDias {SEG, TER, QUA, QUI, SEX, SAB, DOM}; enum TpSexos {MASCULINO, FEMININO}; Variveis destes tipos so denidas da seguinte forma: enum TpCores var1, var2; enum TpDias var3; Agora, possvel dar valores a estas variveis, por exemplo: var1 = AMARELO; var3 = QUI; um erro usar valores no denidos na declarao do tipo. A expresso var2 = AZUL; causa erro de compilao. Internamente, o compilador trata variveis enumeradas como inteiros. Cada valor na lista de valores possveis corresponde a um inteiro, comeando com 0 (zero). Portanto, no exemplo enum TpCores, VERMELHO armazenado como 0, AMARELO armazenado como 1, e VERDE armazenado como 2. Utilizao de tipos enumerados Variveis de tipos enumerados so geralmente usados para claricar a operao do programa. Considere o seguinte trecho de programa que codica dias da semana como inteiros (sendo sabado = 5 e domingo = 6) para vericar se o dia do pagamento cai no nal de semana e altera a dia para a segunda-feira seguinte. #include <iostream> using namespace std; // prototipo da funcao que dada a data, retorna o dia da semana. // seg=0, ter=1, qua=2, qui=3, sex=4, sab=5, dom=6 94
int diaDaSemana( int dia, int mes, int ano ); int main(){ int diaPgto, mesPgto, anoPgto; int diaSem; cout << "Entre com a data de pagamento (dd mm aa): "; cin >> diaPgto >> mesPgto >> anoPgto; diaSem = diaDaSemana( diaPgto, mesPgto, anoPgto ); if( diaSem == 5 ) diaPgto = diaPgto + 2; else if( diaSem == 6 ) diaPgto++; cout << "Data do pagamento: " << diaPgto << "/" << mesPgto << "/" << anoPgto << endl; } Este programa caria mais legvel se ao invs de codicar os dias da semana como inteiros e colocar a codicao como comentrio, utilizar tipos enumerados. O programa caria ento #include <iostream> using namespace std; enum TpSemana {SEG, TER, QUA, QUI, SEX, SAB, DOM}; // prototipo da funcao que dada a data, retorna o dia da semana enum TpSemana diaDaSemana( int dia, int mes, int ano ); int main(){ int diaPgto, mesPgto, anoPgto; int diaSem; cout << "Entre com a data de pagamento (dd mm aa): "; cin >> diaPgto >> mesPgto >> anoPgto; diaSem = diaDaSemana( diaPgto, mesPgto, anoPgto ); if( diaSem == SAB ) diaPgto = diaPgto + 2; else if( diaSem == DOM ) diaPgto++; cout << "Data do pagamento: " << diaPgto << "/" << mesPgto << "/" << anoPgto << endl; } Note que a funo diaDaSemana agora retorna apenas um dos valores da lista SEG, TER, QUA, QUI, SEX, SAB, DOM e portanto, no programa principal ao invs de testar se o diaSem == 5 podemos escrever diaSem == SAB, o que torna o programa muito mais legvel.
95
21
A nfase aqui ser em como funes funcionam. O que acontece quando uma funo chamada ? A que varivel um nome est se referenciando? O tratamento em tempo de execuo de um nome de varivel em C++ simples: um nome de varivel ou uma varivel local (a funo) ou uma varivel global (denida fora de qualquer funo). Em C++ , todas as funes tem que ser denidas. Para cada funo deve ser denido um prottipo. O prottipo escrito fora de qualquer funo. Desta forma, nomes de funes so visveis para todas as outras funes que podem ento invoc-las. A funo main() especial: onde a execuo do programa comea, e o prottipo de main() pode ser omitido. Uma denio de funo consiste de quatro partes: 1. o nome da funo; 2. a lista de parmetros formais (argumentos) com seus nomes e tipos. Se no houver argumentos, a palavra void escrita entre os parnteses. 3. o tipo do resultado que a funo retorna atravs da sentena return ou void se a funo no retorna nenhum valor. Lembre-se que somente um valor pode ser retornado por uma sentena return. 4. o corpo da funo, que uma sentena composta (comea e termina com chaves ({ }) contendo denio de variveis e outras sentenas. Em C++ , no se pode denir uma funo dentro de outra. Para funes com argumentos: uma funo chamada dando o seu nome e uma lista de argumentos (expresses que so avaliadas e cujos valores so atribudos para os correspondentes parmetros formais da funo). Por exemplo, suponha que triang area() e circ area() sejam funes que calculam a rea de tringulos e crculos, respectivamente. Seus prottipos so: float triang_area(float , float); float circ_area(float); Estas funes podem chamadas de dentro de outras funes. Os argumentos reais com os quais elas so chamadas podem ser expresses constantes, or variveis locais, ou qualquer expresso cuja avaliao resulte em valores do tipo float (inteiros so convertidos para float da mesma forma que ocorre com atribuio de inteiros para variveis do tipo float). Alguns exemplos de chamadas: float area2, area3, area4, area5, base, altura, raio;
cout << "area do triangulo = " << triang_area(0.03, 1.25); base = 0.03; altura = 1.25; area2 = triang_area(base, altura); area3 = triang_area(1.6, altura); area4 = triang_area( 0.03 + base, 2 * altura); raio = base + altura; area5 = triang_area(raio, circ_area(raio)); A ltima sentena do exemplo acima atribui a varivel area5 a rea de um tringulo cuja base igual ao valor da varivel raio e a altura igual a area de um crculo de raio igual ao valor da varivel raio. Quando um programa executado, somente uma nica funo tem o controle em determinado momento. Falaremos mais sobre o que acontece quando uma funo chamada mais tarde nestas notas de aula. 96
Variveis Locais Variveis que so denidas dentro de uma funo so variveis locais desta funo. Parmetros formais de uma funo so variveis locais da funo. Variveis locais so privativas a funo na qual so denidas. Somente esta funo pode enxerg-las (ela conhece o endereo das variveis e pode usar e modicar o seu contedo). Nenhuma outra funo pode acessar variveis locais de outra funo sem permisso (uma funo pode acessar variveis locais de outra se esta passar o endereo da varivel local como argumento este assunto ser tratado em notas de aula futuras). O fato de cada funo manter variveis locais escondidas do resto do mundo torna mais fcil a tarefa de escrever programas estruturados e modulares. Quando voc est escrevendo uma funo, voc pode dar as suas variveis locais o nome que quiser. Voc tambm no precisa se preocupar se outra pessoa escrevendo outra funo ter acesso ou altera variveis locais a sua funo. Variveis locais que so denidas dentro da funo devem ser inicializadas com algum valor antes de serem usadas. Caso contrrio, o seu valor indenido. J que parmetros formais (argumentos) so variveis locais da funo, eles podem ser usados no corpo da funo. Eles no devem ser denidos dentro da funo (sua denio j est no cabealho da funo). Os parmetros formais no precisam ser inicializados. Seus valores so fornecidos pelo chamador da funo atravs dos argumentos reais. Considere o seguinte exemplo: /***************************************************************** * Um programa que calcula a area de triangulos e circulos. * A base, altura e raio sao fornecidos pelo usuario. * A saida do programa e a area do triangulo e circulo. *****************************************************************/ #include <iostream> using namespace std; #define PI 3.1415 /******************* prototipos *******************/ float triang_area(float, float); float circ_area(float); /******************* definicao de funcoes *******************/ main() { /* definicao das variaveis locais */ float base, altura, raio; /* dialogo de entrada */ cout << "\nEntre com a base e altura do triangulo: "; cin >> base >> altura; cout << "\nEntre com o raio do circulo: "; cin >> raio; /* chama as funcoes e imprime o resultado */ 97
"Area do triagulo com base e altura " << base << " e " altura << " = " << triang_area(base, altura) << endl;; "Area do circulo com raio " << raio " = " << circ_area(raio) << endl;
/***************************************************************** * funcao: triang_area * calcula a area de um triangulo dada a base e altura * Entrada: base e altura do triangulo * Saida: area do triangulo *****************************************************************/ float triang_area(float base, float alt) { return 0.5*base*alt; } /***************************************************************** * funcao: circ_area * calcula a area de um circulo dado o raio * Entrada: raio do circulo * Saida: area do circulo *****************************************************************/ float circ_area(float r) { return PI*r*r; }
Este programa C++ consiste de trs funes, main(), triang_area(), e circ_area(). main() tem variveis locais chamadas base, altura e raio; triang_area() tem como variveis locai seus parmetros formais, base e alt; circ_area() tem como varivel local seu parmetro formal r. Em geral, uma varivel local s existe durante a execuo da funo na qual ela est denida. Portanto, variveis locais existem desde o momento que a funo chamada at o momento em que a funo completada. Tais variveis so chamadas de automatic. Em C++ , uma varivel pode ser denida como sendo static. Neste caso, uma varivel local no visvel de fora do corpo da funo, mas ela no destruda no nal da funo como variveis automticas so. Cada vez que a funo chamada, o valor das variveis static o valor nal da varivel da chamada anterior. Variveis Globais At este momento, todas as variveis que vimos so denidas dentro de funes (no corpo da funo ou como parmetros formais). possvel tambm denir variveis fora das funes. Tais variveis so chamadas de variveis globais ou externas. O formato da denio de variveis globais o mesmo da denio de variveis locais. A nica diferena onde a varivel denida: variveis globais so denidas fora de qualquer funo. Ao contrrio das variveis locais, variveis globais podem ser vistas por todas as funes denidas aps a denio das variveis globais. Ns temos usado declaraes globais este tempo todo por exemplo, as declaraes de prottipos de funes. Elas so declaradas fora de qualquer funo e podem ser vistas por qualquer funo que esto aps sua declarao. No exemplo seguinte, uma varivel saldo que atualizada por trs funes diferentes denida como uma varivel global. As trs funes que a atualizam no chamam uma a outra. 98
/***************************************************************** * Caixa eletronico simples * o saldo e o valor a ser alterado e entrado pelo usuario * a saida do programa e o saldo atualizado, incluindo juros *****************************************************************/ #include <iostream> using namespace std; #define JUROS 0.07 /******************* prototipos *******************/ void credito(float); void debito(float); void juros(void); /******************* globais *******************/ float saldo; /* saldo atual; * Alterada em: credito(), debito(), juros(), main() * Lida em: */
// valor a ser
depositado/retirado
cout << "Entre com o saldo atual: "; cin >> saldo; cout << "Deposito: "; cin >> valor; credito(valor); cout << "Retirada: "; cin >> valor; debito(valor); juros(); cout << "Juros " << JUROS * 100 << "%.\n"; cout << "Saldo = " << saldo << endl; } /***************************************************************** * Deposita um valor; atualiza a variavel global saldo 99
* Entrada: valor a ser depositado * Saida: nenhum *****************************************************************/ void credito(float val) { saldo += val; } /***************************************************************** * Debita um valor; atualiza a variavel global saldo * Entrada: valor a ser debitado * Saida: nenhum *****************************************************************/ void debito(float val) { saldo -= val; } /***************************************************************** * Acumula juros; atualiza a variavel global saldo; juros: RATE * Entrada: nenhuma * Saida: nenhuma *****************************************************************/ void juros(void) { saldo += (saldo * JUROS); }
Entre com o saldo atual: 1000 Deposito: 200 Retirada: 80 Juros 7%. Saldo = 1198.40 Variveis globais devem ser usadas SOMENTE quando muitas funes usam muito as mesmas variveis. No entanto, o uso de variveis globais perigoso (e no recomendado) porque a modularidade do programa pode ser afetada. Uma varivel global pode ser alterada de dentro de uma funo, e esta alterao pode inuir no resultado de uma outra funo, tornando-a incorreta (em um exemplo dado posteriormente nestas notas, duas chamadas a funo soma_y() com o mesmo argumento (zero) produz resultados diferentes, 100 e 300). Quando variveis globais so utilizadas, deve ser dado a elas nomes descritivos e um breve comentrio qual a nalidade da varivel e quais funes a acessam. Neste curso, voc utilizar variveis globais SOMENTE QUANDO FOR DADO PERMISSO PARA FAZ-LO. Caso contrrio, no permitido utiliz-las (ou seja, sero descontados pontos). Escopo de Variveis 100
Como j discutimos anteriormente, uma varivel uma abstrao de dados que ns usamos em um programa. A varivel representa um endereo de memria onde os valores so armazenados. Durante a execuo do programa, valores diferentes poder ser armazenados neste endereo. Quando uma varivel denida, o nome da varivel atrelada a um endereo especco na memria. At este momento, j discutimos o que o nome de uma varivel, seu endereo, tipo e valor. Outra caracterstica que apresentaresmo agora o escopo. O escopo de uma varivel refere-se a parte do programa onde podemos utilizar a varivel. Em outras, palavras, uma varivel visvel dentro do seu escopo. O escopo de uma varivel local a funo na qual ela denida. Os parmetros formais de uma funo tambm so tratados como variveis locais. O escopo de uma varivel global a poro do programa depois da denio da varivel global (a partir do ponto onde ela denida at o nal do programa). Se o nome de uma varivel global idntico a uma varivel local de uma funo, ento dentro desta funo em particular, o nome refere-se a varivel local. (Embora tais conitos devem ser evitados para evitar confuso). Por exemplo, considere o seguinte programa: int valor = 3; /* definicao da variavel global */
int main() { /* definicao local de valor */ int valor = 4; cout << valor << endl; } A sada do programa acima ser 4 j que valor refere-se a denio local. Considere outro exemplo: #include <iostream> using namespace std; int soma_y(int); int soma_yy(int); int y = 100; main() { int z = 0; // variavel global
// variavel local
cout << soma_y(z) << endl; cout << soma_yy(z) << endl; cout << soma_y(z) << endl; } int soma_y(int x) { return x + y; // x e variavel local, y e global } int soma_yy(int x) { 101
y = 300; return x + y; }
Vamos seguir a execuo deste programa. Primeiro, a varivel global y criada e inicializada com 100. Ento, a execuo da funo main() comeca: alocado espao na memria para a varivel local z. Esta varivel inicializada com 0. Considere a primeira sentena cout : cout << soma_y(z) << endl; Esta uma chamada para cout . Os parmetros reais desta chamada a expresso soma_y(z). Ela consiste da chamada da funo soma_y(). O valor desta expresso o resultado retornado por soma_y(). Qual o resultado? A funo soma_y chamada com o parmetro real z. Como z = 0, este o valor que ser passado para a funo soma_y; o 0 copiado para o parmetro formal x da funo soma_y(). Portanto, durante a excuo da primeira chamada a funo soma_y(), o valor da expresso x + y ser 0 + 100, que 100. Portanto, o valor da primeira chamada soma_ y(z) 100, e este nmero ser impresso com o primeiro cout em main(). Agora considere a segunda sentena: cout << soma_yy(z) << endl; Quando a funo soma_yy(z) chamada, o valor de z (a varivel local z) ainda 0, portanto novamente 0 copiado para o parmetro formal int x da funo soma_yy. Quando a execuo de soma_yy() comea, ela primeiro troca o valor da varivel global y para 300 e ento retorna o valor de x + y, que neste caso 0 + 300. Portanto, o valor desta chamada a soma_yy(z) 300, e este nmero ser impresso pelo segundo cout em main(). Por ltimo, considere a terceira sentena: cout << soma_y(z) << endl; Quando a funo soma_y(z) chamada, o valor de z ainda 0, portanto, 0 copiada para o parmetro formal int x da funo soma_y(). Quando soma_ y() executada pela segunda vez, a varivel global y foi modicada para 300, portanto o valor de x + y 0 + 300. Portanto, o valor da chamada soma_yy(z) 300, e este nmero ser impresso pelo terceiro cout em main(). Portanto, a sada da execuo deste programa ser 100 300 300 Neste exemplo, o escopo da varivel global y o programa todo. O escopo da varivel local z, denida dentro de maio o corpo da funo main. O escopo do parmetro formal x da funo soma_y o corpo de soma_y. O escopo do parmetro formal x da funo soma_yy o corpo de soma_yy.
21.1
Outro exemplo
Aqui apresentamos um exemplo de uma funo mais complicada. Esta funo calcula a raiz quadrada inteira de um nmero (o maior inteiro menor ou igual a raiz quadrada do nmero). Este programa usa o algoritmo divide e calcula mdia (uma aplicao do mtodo de Newton). Ele executa o seguinte: Dado x, achar x computando sucessivamente 102
an =
1
x +an1 an1
se n = 0 caso contrrio
para todo n N
Por exemplo, para achar a raiz quadrada inteira de 42 (usando diviso inteira que trunca a parte fracional do nmero) a0 = 1, a1 = (42/1 + 1)/2 = 21, a2 = (42/21 + 21)/2 = 11, a3 = (42/11 + 11)/2 = 7, a4 = (42/7 + 7)/2 = 6. 2 2 2 Uma vez que a2 4 = 6 = 36 42 < (a4 + 1) = 7 = 49, o processo termina e a resposta 6. (No necessrio voc entender por que este algoritmo funciona portanto no se preocupe se no conseguir entend-lo)
103
int raizInteira(int);
/* prototipo */
/************************************************************** * function: raizInteira(x) dado x, retorna a raiz quadrada inteira de x * acao: inteiro positivo x * in: out: raiz quadrada inteira de x * * suposicoes: x >= 0 metodo de dividr e calcular media: comecando com * algoritmo: um palpite de 1, o proximo palpite e calculado como * (x/palpite_ant + palpite_ant)/2. Isso e repetido * ate que palpite^2 <= x < (palpite+1)^2 * ***************************************************************/ int raizInteira(int x) { int palpite = 1; /* Continue ate que o palpite esteja correto */ while (!(x >= palpite*palpite && x < (palpite+1)*(palpite+1))) { /* Calcula proximo palpite */ palpite = (x/palpite + palpite) / 2; } return palpite; }
Note que usando a lei de DeMorgan, podemos re-escrever a expresso teste do while em uma forma equivalente: x < palpite * palpite || x >= (palpite + 1) * (palpite + 1) Deve estar claro neste ponto a diferenca entre ao e algoritmo. Uma pessoa que quer usar esta funo precisa saber somente a ao, no o algoritmo. tambm importante especicar os dados que so esperados pela funo e retornados por ela para outras pessoas poderem us-la. As suposies devem esclarecer as restries da funo sobre quando a funo pode falhar ou produzir resultados errados. Neste caso, um nmero negativo produziria um erro, j que nmeros negativos no possuem raiz quadrada. No h necessidade de ir em muitos detalhes em qualquer parte da documentao da funo. Embora ela deva conter informao suciente para que algum (que no possa ver o cdigo) saber utiliz-la. Detalhes sobre implementao e detalhes menores sobre o algoritmo devem ser colocados como comentrios no prprio cdigo.
22
Ativao de funo
Uma funo comea sua execuo assim que for chamada. Cada execuo da funo chamada de ativao da funo. Como j mencionamos em notas de aula anteriores, variveis locais so locais a ativao da funo: cada ativao possui suas prprias variveis locais. No comeo da ativao, memria alocada para as variveis locais e no nal da execuo, elas so dealocadas. Denies de funes em C++ no podem ser aninhadas, mas ativaes de funo podem: uma funo, digamos A, pode chamar uma outra funo, digamos B (dizemos que A chama B). Nos referimos a A como o chamador e B como a funo chamada. 104
O que acontece quando uma funo chama outra (quando A chama B)? Um registro especial, chamado registro de ativao criado. A informao neste registro necessria para a ativao da funo chamada e para a reativao do chamador depois que a execuo da funo chamada termina. 1. C++ usa chamada-por-valor, ou seja, o chamador avalia as expresses que so os parmetros reais e passa seus valores para a funo chamada. 2. A informao necessria para reiniciar a execuo da funo chamadora guardada em um registro de ativao. Tal informao inclui o endereo da instruo do chamador que ser executada depois que a funo chamada termine. 3. A funo chamada aloca espao na memria para suas variveis locais. 4. O corpo da funo chamada executado. 5. O valor retornado para a funo chamadora atravs de um return colocado em um lugar especial para que a funo chamadora possa encontr-lo. O controle retorna a funo chamadora. O uxo de controle atravs de ativao de funes da forma ltimo-que-entra-primeiro-que-sai. Se A chama B e B chama C: A ativado primeiro, ento B ativado (um registro de ativao para A chama B criado e armazenado, A temporariamente suspenso), ento C ativado (um registro de ativao de B chama C criado e armazenado, A e B so suspensos); C o ltimo a iniciar execuo, mas o primeiro a terminar (ltimo-a-entrar-primeiro-a-sair). Depois que C termina, B reativado. O registro de ativao B chama C foi criado por ltimo, mas o primeiro a ser destrudo (no momento que o controle retornada para B). Depois que B termina, A reativado. O registro de ativao correspondente a A chama B destrudo no momento em que o controle retorna para A.
105
23
Array de Caracteres
Nas notas de aula anteriores, enfatizamos arrays de nmeros. Em geral, podemos ter arrays com elementos de qualquer um dos tipos vistos at agora (incluindo arrays visto nas notas de aula 9). Nesta seo, apresentaremos arrays com elementos do tipo char. Abaixo, apresentamos um exemplo de programa que dene e inicializa um array de caracteres, e depois imprime o array em ordem reversa. #include <iostream> using namespace std; int main() { char arr1[] = {c,i,2,0,8}; int i; for (i = 4; i >= 0; i -= 1) cout << arr1[i]; } Arrays de caracteres so usados para armazenar texto, mas muito inconveniente se tivermos que colocar cada caractere entre apstrofes. A alternativa dada pela linguagem C++ char arr2[] = "ci208" ; Neste caso, ci208 um string de caracteres ou uma constante do tipo string. Ns j usamos strings antes, com as funes cout e cin (constantes do tipo string esto sempre entre aspas - "): cout << "Entre com a nota para o estudante 2: "; cin >> gr2;
24
Strings
Strings so arrays de caracteres (arrays com elementos do tipo char) que DEVEM terminar com \0 (o caracter NULL). Se voc usa o nome NULL em seu programa, ento necessria a denio #define NULL \0. No exemplo acima, embora no tenhamos escrito explicitamente o caracter NULL, o compilador automaticamente o colocou como o ltimo elemento do array arr2[]. Portanto, o tamanho de arr2[] 6: 5 para os caracteres que digitamos (ci208) e 1 para o caractere NULL que o compilador introduziu automaticamente. As denies abaixo so equivalentes. char arr2[] = char arr2[] = {c,i, , 2,0,8,\0}; {c,i, , 2,0,8, NULL};
O caractere NULL marca o m de um string. Outros exemplos: // a maneira tediosa char name1[] = { j,o,s,e, ,s,i,l,v,a,\0 }; // e a maneira facil char name2[] = "jose silva";
106
Embora o primeiro exemplo seja um string, o segundo exemplo mostra como strings so geralmente escritos (como constantes). Note que se voc usar aspas quando escreve uma constante, voc no precisa colocar \0, porque o compilador faz isso para voc. Quando voc for criar um array de caracteres de um tamanho especco, lembre-se de adicionar 1 para o tamanho mximo de caracteres esperado para armazenar o caractere NULL. Por exemplo, para armazenar o string programar e divertido, voc precisa de um array de tamanho 22 (21 caracteres + 1 para o NULL).
24.1
Strings podem ser impressos usando cout . Por exemplo: int main() { char mensagem[] = "tchau"; cout << "ola" << endl << mensagem << endl; } A sada deste programa : ola tchau
24.2
A funo cin.getline() l uma linha de texto digitado no teclado e a armazena em um vetor indicado como primeiro argumento. O segundo argumento da funo indica o nmero mximo de caracteres que ser lido. Veja o exemplo abaixo: #include <iostream> using namespace std; int main() { char nome[100]; cout << "Entre seu nome: "; cin.getline (nome, 100); cout << "Oi, " << nome << "." << endl ; } Exemplo de execuo Entre seu nome: Jose Silva Oi, Jose Silva. Passando um nome de array para a funo cin.getline(), como ilustrado no programa acima, coloca a linha inteira digitada pelo usurio no array nome (tudo ou mximo de 99 caracteres at que seja teclado enter). Note que se o usurio digitar caracteres demais (neste caso, mais de 99 caracteres), apenas os primeiros 99 caracteres digitados sero copiados para o array indicado no primeiro argumento da funo. A funo cin pode ser usada de maneira similar. A nica diferena que cin l somente a primeira palavra (tudo at que se digite um separador um espao em branco, tabulao, ou enter). 107
#include <iostream> using namespace std; int main() { char nome[100]; cout << "Entre seu nome: "; cin >> nome; cout << "Oi, " << nome << "." << endl; } Exemplo de execuo Entre seu nome: Jose Silva Oi, Jose. Note que somente o primeiro nome lido pelo cin porque a funo pra no primeiro espao em branco que encontra (enquanto cin.getline() pra quando encontra um enter).
24.3
Array de Strings
Em notas de aula anteriores, vimos alguns exemplos de arrays de arrays (matrizes ou tabelas). Como strings so tambm arrays, podemos denir arrays de strings. O programa abaixo inicializa um array de strings com nomes e os imprime. #include <iostream> using namespace std; #define NUM_NOMES 5 #define TAM 20 // define a quantidade de nomes no array // define o tamanho maximo do nome
int main() { char nomes[NUM_NOMES][TAM] = {"Jose Silva", "Maria Silva", "Antonio dos Santos", "Pedro dos Santos", "Joao da Silva"}; int i; for(i = 0; i < 5; i++) cout << nomes[i] << endl; } A sada deste programa : Jose Silva Maria Silva Antonio dos Santos Pedro dos Santos Joao da Silva 108
24.4
Funes de String
H funes para manipulao de string j denidas na biblioteca padro C++ chamada cstring. Todas as funes que apresentaremos nesta seo so parte desta biblioteca. Portanto, se seu programa utilizar uma destas funes voc deve incluir a linha #include <cstring> no incio do seu programa. O objetivo desta seo mostrar como estas funes poderiam ser implementadas como exemplos de programas de manipulao de strings. 24.4.1 A funo strlen()
A funo strlen() tem como argumento um string. Ela retorna um inteiro que o comprimento do string (o nmero de caracteres do string, no contando o caractere NULL). Por exemplo, o comprimento do string alo 3. #include <iostream> #include <cstring> using namespace std; int main() { char nome[100]; int comprimento; cout << "Entre seu nome: "; cin.getline(nome, 100); comprimento = strlen(nome); cout << "Seu nome tem } Um exemplo de execuo: Entre seu nome: Dostoevsky Seu nome tem 10 caracteres. Abaixo, mostramos como a funo strlen() poderia ser implementada. int strlen( char str[] ) { int comprimento = 0; " << comprimento << " caracteres." << endl;
A funo strcmp() usada para comparar dois strings. Lembre que no podemos usar ==, como em str1 == str2, para comparar dois strings, uma vez que strings so arrays. Strings devem ser comparados caractere por caractere. A funo strcmp() tem como argumento dois strings e retorna um inteiro. 109
Strings so ordenados de forma similar a maneira como palavras so ordenadas em um dicionrio. Ordenamos palavras em um dicionrio alfabeticamente, e ordenamos strings respeitando a ordem dos caracteres no conjunto de caracteres da mquina. A ordenao abaixo vlida em qualquer computador: 0 < 1 < ... < 8 < 9 A < B < ... < Y < Z a < b < ... < y < z A ordem relativa do trs conjuntos (dgitos, letras maisculas e letras minsculas) depende do computador utilizado. Se s1 e s2 so strings, o resultado da chamada de funo strcmp(s1, s2) : se s1 =s s2, strcmp() retorna 0 se s1 <s s2, strcmp() retorna um nmero negativo (< 0) se s1 >s s2, strcmp() retorna um inteiro positivo (> 0) (onde =s , <s e >s so =, < e > para strings) s1 <s s2 signica s1 vem antes de s2 no dicionrio. Exemplos: tudo menor que xadrez, calor menor que calorao, frio menor que quente, e claro o string vazio , NULL, menor que qualquer string. Considere o exemplo abaixo que usa strcmp(): #include <iostream> #include <cstring> using namespace std; int main() { char palavra1[100], palavra2[100]; int resultado; cout << "entre com uma palavra: "; cin >> palavra1; cout << "entre outra palavra: "; cin >> palavra2; resultado = strcmp(palavra1, palavra2); if (resultado == 0) cout << "igual" << endl; else if (resultado > 0) cout << "o primeiro e maior" << endl; else cout << "o segundo e maior" << endl; } Aqui est um exemplo de como a funo strcmp() poderia ser implementada. int strcmp( char s1[], char s2[] ) { int i = 0; while (1) { 110
if (s1[i] == NULL && s2[i] == NULL) return 0; else if (s1[i] == NULL) return -1; else if (s2[i] == NULL) return 1; else if (s1[i] < s2[i]) return -1; else if (s1[i] > s2[i]) return 1; else ++i; } } Na biblioteca padro, a funo strcmp() faz distino entre letras maisculas e minsculas. Se voc no quer que a funo faa esta distino, voc pode modicar o seu string para ter apenas letras minsculas (ou maisculas) antes de pass-lo como argumento para a funo strcmp(). Para fazer isso, voc pode usar a funo da biblioteca padro tolower(), que tem como argumento um caractere. Se o caractere passado uma letra maiscula, ele retorna esta letra minscula; caso contrrio, retorna o mesmo caractere. Por exemplo: tolower(A) a, tolower(1) 1, tolower(a) a. 24.4.3 A funo strcpy()
A funo strcpy() usada para copiar o contedo de um string para outro. Ela tem dois argumentos: strcpy(s1, s2) copia o contedo do string s2 para o string s1. A funo strcpy() que apresentamos abaixo no retorna um valor. Seu prottipo void strcmp(char [], char []); O exemplo abaixo mostra a utilizao do strcpy(). #include <iostream> #include <cstring> using namespace std; int main() { char pal[100], palCopia[100]; cout << "entre com uma palavra: "; cin >> pal; strcpy(palCopia, pal); cout << "entre outra palavra: "; cin >> pal; cout << "voce entrou primeiro: " << palCopia << endl; } Embora este programa pudesse ter sido escrito sem usar strcpy(), o objetivo mostrar que se pode usar strcpy() para fazer atribuio de strings. A funo strcpy() poderia ter sido implementada da seguinte forma: 111
void strcpy( char s1[], char s2[] ) { int i = 0; while ( s2[i] != NULL ) { s1[i] = s2[i]; ++i; } s1[i] = s2[i]; }
112
25
Estruturas
A estrutura de dados array usada para conter dados do mesmo tipo junto. Dados de tipos diferentes tambm podem ser agregados em tipos chamados de estruturas ou registros (tipo struct em linguagem C). Primeiro, o tipo estrutura declarado (precisamos especicar que tipos de variveis sero combinados na estrutura), e ento variveis deste novo tipo podem ser denidas (de maneira similar que usamos para denir variveis do tipo int ou char).
25.1
Declarao de Estruturas
Uma declarao de estrutura declara um tipo struct. Cada tipo struct recebe um nome (ou tag). Referese quele tipo pelo nome precedido pela palavra struct. Cada unidade de dados na estrutura chamada membro e possui um nome de membro. Os membros de uma estrutura podem ser de qualquer tipo. Declaraes de estrutura no so denies. No alocada memria, simplesmente introduzida um novo tipo de estrutura. Geralmente declaraes de estruturas so globais. Elas so colocadas prximas ao topo do arquivo com o cdigo fonte do programa, assim elas so visveis por todas as funes (embora isto dependa de como a estrutura est sendo usada). A forma padro de declarao de uma estrutura : struct nome-estrutura { declarao dos membros } denio de variveis (optional); Abaixo se apresenta um exemplo de um tipo estrutura que contm um membro do tipo int e um outro membro do tipo char. struct facil { int num; char ch; }; Esta declaracao cria um novo tipo chamado struct facil que contm um inteiro chamado num e um caracter chamado ch.
25.2
Como acontece com qualquer outro tipo de dados, variveis de tipos de estruturas so denidas fornecendo o nome do tipo e o nome da varivel. Considere a denio abaixo relativa a uma varivel com o nome fac1 que do tipo struct facil: struct facil fac1; Tal denio est associada com a alocao de memria: memria suciente ser alocada para guardar um int e um char (nesta ordem). Como qualquer outra varivel, fac1 tem um nome, um tipo, e um endereo associados. Variveis de estruturas possuem tambm valores, e como outras variveis locais, se elas no tem atribudas um valor especco, seu valor indenido. possvel denir variveis durente a declarao do tipo estrutura: struct facil { int num; char ch; } fac1; 113
Note-se que sem conito, nomes de membros (tais como num e ch) podem ser usados como nomes de outras variveis independentes (fora do tipo estrutura denido) or como nomes de membros em outros tipos estrutura. No entanto, deve-se evitar situaes que criem confuso.
25.3
Dada uma varivel de estrutura, um membro especco referenciado usando o nome da varivel seguida de . (ponto) e pelo nome do membro da estrutura. Assim, as seguintes referncias a membros de uma estrutura so vlidas: fac1.num se refere ao membro com nome num na estrutura fac1; fac1.ch se refere ao membro com nome ch na estrutura fac1. Membros de estrutura (como fac1.num) so variveis, e podem ser usadas como valores (no lado direito de uma atribuio, em expresses como argumentos para funes), ou como lvalues (no lado esquerdo de atribuies, com operadores de incremento/decremento ou com o operador de endereo (&)). O exemplo abaixo mostra alguns exemplos do uso de membros de estrutura: fac1.ch = G; fac1.num = 42; fac1.num++; if (fac1.ch == H) { cout << fac1.num << endl; } Tentar acessar um nome de membro que no existe causa um erro de compilao.
25.4
Uma varivel de estrutura pode ser tratada como um objeto simples no todo, com um valor especco associado a ela (a estrutura fac1 tem um valor que agrega valores de todos os seus membros). Note a diferena com arrays: se arr[] um array de tamanho 2 denedo como int arr[2] = {0,1};, o nome arr2 no se refere ao valor coletivo de todos os elementos do array. Na verdade, arr2 um apontador constante e se refere ao endereo de memria onde o array se inicia. Alm disso arr2 no um lvalue e no pode ser mudado. Variveis de estrutura so diferentes. Elas podem ser usadas como valores e lvalues, mas com certas limitaes. Os nicos usos vlidos de uma varivel de estrutura so dos dois lados de um operador de atribuio (=), como operando do operador de endereo & (obtendo o endereo da estrutura), e referenciando seus membros. De todas as variaes de atribuio (incluindo o incremento e decremento) atribuio de estruturas pode ser usada APENAS com =. O uso de outros operadores de atribuio ou de incremento causar um erro de compilao A atribuio de um valor de estrutura para outro copia todos os membros de uma estrutura para outra. Mesmo que um dos membros seja um array ou outra estrutura, ela copiada integralmente. As duas estruturas envolvidas na atribuio devem ser do mesto tipo struct. Considere o seguinte exemplo: struct facil { int num; char ch; }; void main(void) { 114
struct facil fac1, fac2; fac1.num = 3; fac1.ch = C; /* Atribuindo fac1 a fac2 */ fac2 = fac1; } Lembre-se que este tipo de atribuio ilegal com arrays. Tentar fazer isto com dois arrays causa um erro de compilao (uma vez que nomes de arrays so apontadores constantes). int a[5], b[5]; /* Est errado -- No ir compilar */ a = b;
25.5
Inicializao de estruturas
Variveis de estruturas no-inicializadas contm valores indenidos em cada um de seus membros. Como em outras variveis, variveis de estruturas podem ser inicializadas ao serem declaradas. Esta inicializao anloga ao que feito no caso de arrays. O exemplo abaixo ilustra a inicializao de estruturas: struct facil { int num; char ch; }; void main(void) { struct facil fac1 = { 3, C }, fac2; fac2 = fac1; } Uma lista de valores separados por vrgula ca entre chaves ({ e }). Os valores de inicializao devem estar na mesma ordem dos membros na declarao da estrutura.
25.6
Como qualquer outro valor do tipo int ou float, valores de estruturas podem ser passados como argumentos para funes, e podem ser retornados de funes. O exemplo abaixo ilustra tal prorpiedade: #include <iostream> using namespace std; #define LEN 50 struct endereco { char rua[LEN]; char cidade_estado_cep[LEN]; }; 115
struct endereco obtem_endereco(void); void imprime_endereco(struct endereco); struct endereco obtem_endereco(void) { struct endereco ender; cout << "\t Entre rua: "; cin.getline(ender.rua, LEN); cout << "\t Entre cidade/estado/cep: "; cin.getline(ender.cidade_estado_cep, LEN); return ender; } void imprime_endereco(struct endereco ender) { cout << "\t " << ender.rua << endl; cout << "\t " << ender.cidade_estado_cep << endl; } main() { struct endereco residencia; cout << "Entre seu endereco residencial:\n"; residencia = obtem_endereco(); cout << "\nSeu endereco eh:\n"; imprime_endereco(residencia); } No exemplo acima, a estrutura struct endereco contm dois arrays de tamanho 50. Dentro da funo obtem_endereco(), a varivel ender declarada como sendo do tipo struct endereco. Aps usar cin.getline() para o fornecimento da informao, o valor de ender retornado para main(), de onde a funo obtem_endereco() foi chamada. Este valor ento passado para a funo imprime_endereco(), onde o valor de cada membro da estrutura exibido na tela. Este programa pode ser comparado ao programa abaixo, que usa valores do tipo int no lugar de valores do tipo struct endereco (claro que a informao lida e exibida um simples valor numrico, e no um nome de rua, etc.): int obtem_int(void); void imprime_int(int); int obtem_int(void) { int i; cout << "Entre valor: "; cin >> i; 116
return i; } void imprime_int(int i) { cout << i << endl; } void main(void) { int valor; valor = obtem_int(); cout << "\nSeu valor:\n"; imprime_int(valor); }
25.7
Arrays de estruturas
Arrays de estruturas so como arrays de qualquer outro tipo. Eles so referenciados e denidos da mesma forma. O exemplo abaixo anlogo ao exemplo de endereo apresentado anteriormente, exceto que uma quantidade de NUM endereos armazenada ao invs de apenas um. #define LEN 50 #define NUM 10 struct endereco { char rua[LEN]; char cidade_estado_cep[LEN]; }; void obtem_endereco(struct endereco [], int); void imprime_endereco(struct endereco); void obtem_endereco(struct endereco aender [], int index) { cout << "Entre rua: "; cin.getline(aender[index].rua, LEN); cout << "Entre cidade/estado/cep: "; cin.getline(aender[index].cidade_estado_cep, LEN); } void imprime_endereco(struct endereco ender) { cout << ender.rua << endl; cout << ender.cidade_estado_cep << endl;; } void main(void) 117
{ struct endereco residencias[NUM]; int i; for (i = 0; i < NUM; i++) { cout << "Entre o endereco da pessoa " << i << ":\n"; obtem_endereco(residencias,i); } for (i = 0; i < NUM; i++) { cout << "endereco da pessoa " << i << ":\n"; imprime_endereco(residencias[i]); } } Neste programa, o array residencias passado para obtem_endereco(), juntamente com o indice onde deve ser guardado o novo endereo. Depois, cada elemento do array passado para imprime_endereco() um por vez. Observe-se ainda na funo obtem_endereco() como os membros de cada elemento do array podem ser acessados. elements da estrutura em can be accessed as well. Por exemplo, para acessar a rua do elemento residencias[0] usa-se: cout << residencias[0].rua << endl; cout << residencias[0].cidade_estado_cep << endl;
25.8
Estruturas aninhadas
Como denido anteriormente, membros de estruturas podem ser de qualquer tipo. Isto inclui outras estruturas. Abaixo dene-se duas estruturas, a segunda tendo membros que so tambm estruturas: #define LEN 50 struct endereco { char rua[LEN]; char cidade_estado_cep[LEN]; }; struct student { char id[10]; int idade; struct endereco casa; struct endereco escola; }; struct student pessoa; Dadas estas denies, pode-se potencialmente acessar os seguintes campos de pessoa, uma varivel do tipo struct student: pessoa.id pessoa.casa.rua pessoa.casa.cidade_estado_cep 118
pessoa.escola.rua pessoa.escola.cidade_estado_cep Note o uso repetido de . quando se acessa membros dentro de membros.
119
26
A forma com que um programa em C++ se comunica com o mundo externo atravs de entrada e sada de dados: o usurio fornece dados via teclado e o programa imprime mensagens na tela. Todos os programas vistos at agora lem suas entradas do teclado e produzem suas sadas na tela. Em C++ toda entrada e sada feita com uxos (streams) de caracteres organizados em linhas. Cada linha consiste de zero ou mais caracteres e termina com o caracter de nal de linha. Pode haver at 254 caracteres em uma linha (incluindo o caracter de nal de linha). Quando um programa inicia, o sistema operacional automaticamente dene quem a entrada padro (geralmente o teclado) e quem a sada padro (geralmente a tela). As facilidades de entrada e sada no fazem parte da linguagem C++ . O que existe uma biblioteca padro de funes para manipular a transferncia de dados entre programa e os dispositivos (devices) de sada e entrada padro. Algumas destas funes so: cin, cout, cin.get(), cin.put(), puts(), gets(). Estas funes so declaradas no arquivo <iostream>. Existem funes teis para converso e teste de caracteres declaradas no arquivo <ctype.h>. As funes de entrada e sada operam sobre streams (uxos) de caracteres. Toda vez que uma funo se entrada chamada (por exemplo, cin, cin.get()) ela verica pela prxima entrada disponvel na entrada padro (por exemplo, texto digitado no teclado). Cada vez que uma funo de sada chamada, ela entrega o dado para a sada padro (por exemplo, a tela). As funes para leitura da entrada padro e para escrita na sada padro que tm sido usadas at agora so: 1. Entrada e sada de caracteres: int cin.get( void ); int cin.put( int ); 2. Entrada e sada de strings: char *gets(char *); int puts( char *); 3. Entrada e sada formatada: int cin >> arg1 >> arg2 >> ...; int cout << arg1 << arg2 << ...;
26.1
Vamos discutir algumas funes de entrada de dados (diferente do cin). A entrada de texto considerada como um uxo de caratecteres. Um uxo texto uma sequncia de caracteres dividida em linhas; cada linha consiste de zero ou mais caracteres seguido do caractere de nova linha ( \n). Como programador, voc no quer se preocupar em como as linhas so representadas fora do programa. Quem faz isso por voc so funes de uma biblioteca padro. Suponha que voc queira ler um nico caractere, sem fazer qualquer tipo de converso para um tipo de dados especco (o que feito por cin). Isso pode ser feito usando a funo cin.get(). A funo cin.get() l o caracter do teclado e mostra o que foi digitado na tela. #include <iostream> using namespace std; 120
int main() { char ch; cout << "Digite algum caracter: "; cin.get(ch); cout << "\n A tecla pressionada eh " << ch << endl; } O Resultado deste programa na tela : Digite algum caracter: A A tecla pressionada eh A. A funo cin.put() aceita um argumento de entrada, cujo valor ser impresso como caracter: #include <iostream> using namespace std; int main() { char ch; cout << "Digite algum caracter: "; cin.get(ch); cin.put(ch); }
26.2
26.2.1
cin.get() uma funo vinculada primitiva principal de entrada cin. Cada vez que chamada, esta funo l um caractere teclado; cin.get comea a ler depois que a tecla RETURN digitada no nal de uma sequncia de caracteres (dizemos que a entrada para a funo cin.get() est no uxo de entrada). A funo cin.get() retorna um valor, o caractere lido (mais precisamente, o cdigo inteiro ASCII correspondente ao caractere). Vejamos o que acontece quando um programa trivial executado. #include <iostream> using namespace std; int main(){ char ch; 121
cin.get(ch); } cin.get() obtm sua entrada do teclado. Portanto, quando o programa acima executado, o programa espera que o usurio digite alguma coisa. Cada caractere digitado mostrado no monitor. O usurio pode digitar diversos caracteres na mesma linha, inclusive backspace para corrigir caracteres j digitados. No momento que ele teclar RETURN , o primeiro caractere da sequncia digitada o resultado da funo cin.get(). Portanto, na instruo do programa acima o caractere (ou melhor, o seu cdigo ASCII) atribudo a varivel ch. Note que o usurio pode ter digitado diversos caracteres antes de teclar RETURN , mas a funo cin.get() s comear a ler o que foi digitado depois que for teclado RETURN . Alm disso, com uma chamada da funo cin.get() s o primeiro caractere da sequncia digitada lida. Voc deve saber que o caractere de nova linha, \n, que tem o cdigo ASCII 10, automaticamente adicionado na sequncia de caracteres de entrada quando o RETURN teclado. Isso no tem importncia quando a funo cin.get() chamada uma nica vez, mas isto pode causar problemas quando ele usado dentro de um lao. No iniccio de qualquer programa que usa cin.get(), voc deve incluir #include <iostream> using namespace std; Esta diretiva do pr-processador diz ao compilador para incluir informaes sobre cin, cin.get() e EOF (mais sobre EOF adiante.). Considere o seguinte programa: #include <iostream> using namespace std; int main(){ char ch; cout << "Entre com uma letra: "; cin.get(ch); if( ch < A || ch > z ) cout << "Voce nao teclou uma letra!"; else cout << "Voce teclou " << ch << ", e seu codigo ASCII eh " << (int) ch << endl; } Um exemplo da execuo do programa: Entre com uma letra: A Voce teclou A, e seu codigo ASCII eh 65. No exemplo de execuo acima o usurio teclou A e depois RETURN . Outro exemplo de execuo do programa: Entre com uma letra: AbcD Voce teclou A, e seu codigo
ASCII eh 65.
122
Neste caso o usurio digitou quatro caracteres e depois teclou RETURN . Embora quatro caracteres tenham sido digitados, somente uma chamada a funo cin.get() foi feita pelo programa, portanto s um caractere foi lido. O valor atribudo ao argumento da funo o cdigo ASCII do primeiro caractere lido. O tipo do resultado da funo cin.get() int e no char. O valor retornado pela funo o cdigo ASCII do caractere lido. 26.2.2 Marcando o nal da entrada
Frequentemente quando voc est digitando a entrada para o programa, voc quer dizer ao programa que voc terminou de digitar o que queria. Em ambiente Unix, digitando ^D (segure a tecla de Ctrl e pressione D) voc diz ao programa que terminou a entrada do programa. Em ambiente MS-Windows, voc faz isto digitando ^Z (segure a tecla de Ctrl e pressione Z). Isto envia uma indicao para a funo cin.get(). Quando isso ocorre, o valor de ch depois de executar cin.get(ch); ser um valor especial do tipo inteiro chamado EOF (que signica end of le nal do arquivo). Considere o seguinte programa exemplo que conta o nmero de caracteres digitados (incluindo o caractere de prxima linha): #include <iostream> using namespace std; int main() { int total = 0; char ch; // Le o proximo caractere em ch e para quando encontrar // final do arquivo cin.get(ch); while( ! cin.eof() ) { total++; cin.get(ch); } cout << endl << total << " caracteres digitados" << endl; }
S para esclarecer: voc deve teclar RETURN depois de entrar com o comando ^D (ou ^Z no MSWindows). 26.2.3 Para evitar problemas com a entrada...
(Observao: nesta seo, espaos em branco so relevantes e so mostrados como ) Quando voc executa um programa, cada caractere que voc digita lido e considerado como parte do uxo de entrada. Por exemplo, quando voc usa cin.get(), voc deve teclar RETURN no nal. Como mencionado anteriormente, o primeiro caractere digitado lido pelo cin.get(). Mas, o caractere de nova linha continua no uxo de entrada (porque voc teclou RETURN ). De qualquer forma, se voc executar um cin.get() depois de um cin ou de um cin.get() voc ler o caractere de nova linha deixado no uxo de entrada. Da mesma forma, quando voc usa cin para ler informaes, ele somente l o que necessrio. Se voce usar cin para ler um nmero inteiro e digitar 42 (seguido de RETURN ), o cin l 42, mas deixa (e o caractere de nova linha do RETURN ) no uxo de entrada. 123
Outro caso problemtico quando o cin usado num lao. Se voc digitar um valor do tipo errado, o cin ler o valor errado e a execuo do lao continuar na sentena aps o cin . Na prxima iterao do lao o cin vai tentar ler novamente, mas o lixo deixado da iterao anterior ainda estar l, e portanto a chamada corrente do cin tambm no dar certo. Este comportamento resultar num lao innito (um lao que nunca termina), ou terminar e ter um resultado errado. H uma maneira simples de resolver este problema; toda vez que voc usar cin.get() (para ler um caracter s) ou cin , voc deve ler todo o lixo restante at o caractere de nova linha. Colocando as seguinte linhas aps chamadas a cin.get() ou cin o problema eliminado: // Pula o restante da linha while( cin.get(c) != \n ); Note que isso no necessrio aps todas as chamadas a cin.get() ou cin . S depois daquelas chamadas que precedem cin.get() (ou cin ), especialmente em um lao. A funo cin na realidade retorna um inteiro que o nmero de itens (valores) lidos com sucesso. Voc pode vericar se o cin funcionou testando se o valor retornado igual ao nmero de especicadores de formato no primeiro argumento da funo. int main(){ int total = 0, num; while( total < 20 ){ cout << "Total = " << total << endl; cout << "Entre com um numero: "; if( (cin >> num) < 1 ) // Ignora o resto da linha while( cin.get() != \n ); else total += num; } cout << } "Final total = " << total << endl;
124
27
Ponteiros
Em linguagem C++ a cada varivel est associado: (i) um nome; (ii) um tipo; (iii) um valor; e (iv) um endereo. Considere as seguintes denies de variveis. int i = 5; char c = G; Na memria, eles podem estar armazenados da forma abaixo:
A varivel inteira i est armazenada no endereo 1342. Ela usa dois bytes de memria (quando um objeto usa mais de um byte, seu endereo onde ele comea neste caso, 1342 e no 1343). A varivel do tipo char c est armazenada no endereo 1346 e usa um byte de memria. O compilador que controla do local de armazenamento destas variveis em memria.
27.1
Ns podemos usar o operador de endereo para determinar o endereo de uma objeto na memria. Este operador s pode ser usado com lvalues (objetos que podem estar no lado esquerdo de uma atribuio, como no caso de variveis) porque lvalues tem um endereo alocado na memria. Por exemplo, no exemplo acima, poderamos usar o operador de endreo como nas expresses abaixo: &i tem valor 1342 &c tem valor 1346
27.2
Tipo ponteiro
Em C++ , uma varivel que contm um endereo de memria uma varivel do tipo ponteiro. Um valor, que um endereo (como &a) um valor de ponteiro. Quando um ponteiro (a varivel) contm um determinado endereo, dizemos que ele aponta para o endereo de memria. Alm disso, se o valor deste ponteiro o endereo de uma outra varivel qualquer, dizemos que tal ponteiro aponta para esta outra varivel. H um tipo distinto de ponteiro para cada tipo bsico C++ (como int, char e float). verdade que todos os endereos tem o mesmo tamanho4 , mas ns tambm precisamos saber algo sobre o que armazenado no endereo de memria apontado (quantos bytes ocupa e como os bytes devem ser interpretados). Assim, a declarao de um tipo ponteiro em C++ feita da seguinte forma: tipo *nome_var;
4
O operador sizeof() pode ser usado para determinar o tamanho de um ponteiro. Por exemplo, sizeof (char *)
125
Esta declarao indica que est sendo denido um ponteiro para tipo chamado nome_var. Por exemplo, um tipo ponteiro usado para apontar para inteiros chamado ponteiro para int e isso denotado por um int *. Variveis do tipo ponteiro para int so usadas para armazenar endereos de memria que contem valores do tipo int. Dadas as denies de i e c acima, ns podemos denir duas novas variveis pi e pc, ambos do tipo ponteiro. int *pi; char *pc; Nesta denio as variveis no foram inicializadas com nenhum valor. Podemos inicializ-las com: pi = &i; pc = &c; Depois destas atribuies, o valor de pi seria 1342, e o valor de pc seria 1346. Note que nesta denio da varivel int *pi, pi o nome da varivel e int * o tipo de pi (ponteiro para int).
27.3
O operador de dereferncia: *
Quando um ponteiro aponta para um endereo de memria, a operao para acessar o contedo do endereo apontado chamado de dereferncia. O operador unrio * usado para fazer a dereferncia. Note que este uso do smbolo * no tem relao com o smbolo de multiplicao. Usando os exemplos anteriores, *pi o objeto apontado por pi (no caso, o valor de um inteiro). *pi tem valor 5 *pc tem valor G Como um ponteiro dereferenciado (tais como *pi ou *pc) refere-se a um objeto na memria, ele pode ser usado no s como valor, mas tambm como um lvalue. Isto signica que um ponteiro dereferenciado pode ser usado no lado esquerdo de uma atribuio. Veja alguns exemplos: printf("Valor= %d, Char = %c\n", *pi, *pc); *pi = *pi + 5; *pc = H; *pi no lado esquerdo do = refere-se ao endereo de memria para o qual pi aponta. *pi no lado direito do = refere-se ao valor armazenado no endereo apontado por pi. A sentena *pi = *pi + 5; faz com que o valor armazenado no endereo apontado por pi seja incrementado de 5. Note que o valor de *pi muda, no o valor de pi. Neste exemplo, os valores das variveis i e c poderiam ter sido alterados sem a utilizao de ponteiros da seguinte forma: printf("Valor = %d, Char = %c\n", i, c); i = i + 5; c = H; Os exemplos acima ilustram como uma varivel pode ser acessada diretamente (atravs do seu nome) ou indiretamente (atravs de um ponteiro apontando para o endereo da varivel).
27.4
Um ponteiro pode ter atribudo a si um valor que seja o endereo de memria onde est armazenado um valor do mesmo tipo do ponteiro. Isto ocorre quando se usa o operador de endereo visto acima, ou quando se usa o valor de um outro ponteiro que aponte para um objeto do mesmo tipo do primeiro ponteiro. Observe-se o exemplo abaixo: 126
int *p1, *p2, x; float *p3; p1 = &x; p2 = p1; p3 = p1; /* Correto */ /* Correto */ /* Incorreto. Compilador acusa "Warning". */
No exemplo acima, a linguagem C++ admite a atribuio de um ponteiro para outro de outro tipo (p3 = p1;), mas a compilao acusa uma mensagem de aviso. Posteriormente sero vistas situaes em que a atribuio de ponteiros de tipos diferentes devem ocorrer e como devem ser manipuladas em C++ .
27.5
Aritmtica de ponteiros
Apenas as operaes de adio e subtrao (e operadores C++ associados) so permitidos com ponteiros. Assim, possvel adicionar ou subtrair valores inteiros de ponteiros. Operaes de soma, subtrao e comparao entre ponteiros tambm so vlidas, desde que os ponteiros envolvidos apontem para o mesmo tipo de dados. Ainda assim, o resultado somente ter algum sentido prtico se os ponteiros apontarem tambm para o mesmo objeto. Alguns exemplos: int num[20], *pnum, diff; char str[30], *pstr, *pn, char nome[20]; pn = nome; pstr = str; pnum = num; pnum += 3; *pnum = 10; pstr++; diff = pstr - pnum; /* pnum = &num[3] */ /* equivale a num[3] = 10 */ /* pstr = &str[1] */ /* INCORRETO. Os ponteiros apontam para * tipos diferentes */ /* CORRETO, mas o valor no tem * necessriamente o sentido de "numero * de bytes entre pn e pstr". */
Um ltimo ponto a respeito de operaes sobre ponteiros: Adicionar um ponteiro a outro no produz nenhum resultado prtico ou vlido.
27.6
Ponteiros e Arrays
Em C++ , o nome de uma varivel que foi declarada como array representa um ponteiro que aponta para o incio do espao de armazenamento do array, isto , o endereo de memria do primeiro byte associado ao primeiro elemento do array: 127
pstr = nome; ptr = val; pstr = nome + 4; ptr = val + 5; pstr = nome++;
/* Equivalente a pstr = &nome[0] */ /* Equivalente a ptr = &val[0] */ /* Equivalente a pstr = &nome[4] */ /* Equivalente a ptr = &val[5] */ /* ATENCAO: INCORRETO !!! */ /* "nome" NO UM PONTEIRO */
Se um ponteiro aponta para um array, pode-se usar indistintamente as formas abaixo para acessar os elementos do array: int val[10], x, *ptr; ptr = val; *(ptr + 3) = 7; ptr[3] = 10; /* Equivalente a ptr = &val[0] */ /* val[3] = 7 */ /* val[3] = 10 */ /* Equivalente a *(ptr + 3) = 10 */
/* ATENCAO:
val[7] = 20 */
27.7
Ponteiros e Estruturas
Como em qualquer outro tipo, ponteiros para estruturas podem ser denidos. Considere o exemplo abaixo: /* declara uma estrutura */ struct facil { int num; char ch; }; int main() { /* definioes de variaveis */ struct facil fac, /* uma variavel do tipo "struct facil" */ /* um ponteiro para "struct facil" */ *pfac; pfac = &fac;
(*pfac).num = 32; /* o membro "num" da "struct facil" apontada por "pfac" (*pfac).ch = A; /* o membro "char" da "struct facil" apontada por "pfac } 128
Como se espera, quando se usa um ponteiro para um tipo struct, o ponteiro deve ter assinalado a si um valor ANTES de ser dereferenciado. A ordem pela qual um membro pode ser acessado atravs do ponteiro, the pointer : primeiro o ponteiro dereferenciado, e ento o operador de membro de estrutura e o nome do membro so usados para acessar um membro em particular da estrutura apontada pelo ponteiro. Uma vez que o operador . tem precedncia mais alta que o operador * (veja Tabela 5), os parenteses so necessrios. 27.7.1 Acesso a membros de estrutura via ponteiro: O operador ->
Uma notao do tipo (*pfac).ch confusa, de forma que a linguagem C++ dene um operador adicional (->) para acessar membros de estruturas atravs de ponteiros. O operador -> formalmente usado como o operador ., exceto que ao invs do nome da varivel de estrutura, um ponteiro para o tipo struct usado esquerda do operador ->. No exemplo acima, as duas ltimas linhas de cdigo podem portanto ser reescritas como: pfac->num = 32; pfac->ch = A; /* o mesmo que (*pfac).num = 32; /* o mesmo que (*pfac).ch = A; */ */
Basicamente, use o operador . se voc tem uma varivel de tipo struct, e o operador -> caso voc tenha um ponteiro para um tipo struct.
27.8
Nos exemplos acima, pode parecer que ponteiros no so teis, j que tudo que zemos pode ser feito sem usar ponteiros. Agora, considere o exemplo da funo troca() abaixo, que deve trocar os valores entre seus argumentos: #include <iostream> using namespace std; void troca(int x, int y) { int temp; temp = x; x = y; y = temp; } main() { int a, b; cout << "Entre dois numeros: "; cin >> a >> b; cout << "Voce entrou com " << a << " e " << b << endl; // Troca a com b 129
troca(a, b); cout << "Trocados, eles sao " << a << " e " << b << endl; }
Quando a e b so passados como argumentos para troca(), na verdade, somente seus valores so passados. A funo no pode alterar os valores de a e b porque ela no conhece os endereos de a e b. Mas se ponteiros para a e b forem passados como argumentos ao invs de a e b, a funo troca() seria capaz de alterar seus valores; ela saberia ento em que endereo de memria escrever. Na verdade, a funo no sabe que os endereos de memria so associados com a e b, mas ela pode modicar o contedo destes endereos. Portanto, passando um ponteiro para uma varivel (ao invs do valor da varivel), habilitamos a funo a alterar o contedo destas variveis na funo chamadora. Uma vez que endereos de variveis so do tipo ponteiro, a lista de parmetros formais da funo deve reetir isso. A denio da funo troca() deveria ser alterada, e a lista de parmetros formais deve ter argumentos no do tipo int, mas ponteiros para int, ou seja, int *. Quando chamamos a funo troca(), ns no passamos como parmetros reais a e b, que so do tipo int, mas &a e &b, que so do tipo int *. Dentro da funo troca() dever haver mudanas tambm. Uma vez que agora os parmetros formais so ponteiros, o operador de dereferncia, *, deve ser usado para acessar os objetos. Assim, a funo troca() capaz de alterar os valores de a e b remotamente. O programa abaixo a verso correta do problema enunciado para a funo troca(): #include <iostream> using namespace std; /* function troca(px, py) troca os valores inteiros apontados por px e py * acao: apontadores px e py * entrada: valor de px e py trocados na origem da chamada da funo * saida: suposicoes: px e py sao apontadores validos * primeiro guarda o primeiro valor em um temporario e * algoritmo: troca * */ void troca(int & px, int & py) { int temp; temp = px; px = py; py = temp; } main() { int a, b; cout << "Entre dois numeros: "; cin >> a >> b; cout << "Voce entrou com " << a << " e " << b << endl; 130
// Troca a com b -- passa argumentos por referencia troca(a, b); cout << "Trocados, eles sao " << a << " e " << b << endl; }
A sada deste programa : Entre dois numeros: 3 5 Voce entrou com 3 e 5 Trocados, eles sao 5 e 3 Basicamente, se a funo precisa alterar o valor de uma varivel na funo chamadora, ento passamos o endereo da varivel como parmetro real, e escrevemos a funo de acordo, ou seja, com um ponteiro como parmetro formal. 27.8.1 Arrays como argumentos de funes
Quando um array passado como argumento para uma funo, somente o ponteiro para a primeira posio do array passada e no o contedo de todo o array. Arrays so portanto passados por referncia e no por valor. Ao se denir um array como o argumento formal de uma funo em C++ , duas formas podem ser usadas. Elas podem ser vistas abaixo nas denies das funes func_1() e func_2(). func_1 (char vet [], int ivet[]) { vet[3] = A; vet++; ivet += 3; } func_2 (char *vet, int *ivet) { vet[4] = B; vet++; ivet += 3; } main() { char ender[20]; char vals[20]; func_1(ender, vals); func_2(ender, vals); } Observe no exemplo acima que a passagem dos arrays ao se chamar as funes func_1() e func_2() feita da mesma forma: Usa-se o NOME das variveis declaradas como arrays. Note tambm o uso dos argumentos formais nas funes: vet e ivet podem ser usadas como ponteiros ou como nomes de arrays (com a notao indexada por []). 131
27.8.2
Quando estruturas so passadas como argumentos para funes o valor de todo o objeto agregado passado literalmente. Alm disso, se este valor alterado na funo, ele deve ser retornado (via return), o que implica em copiar de volta toda a estrutura. Isto pode ser bastante ineciente no caso de uma estrutura grande (com muitos membros, com membros de tamanho grande como arrays, etc.). Assim, em alguns casos melhor passar ponteiros para estruturas. Repare a diferena com arrays passados como argumentos para funes vista na seo anterior. O programa abaixo um exemplo do uso de passagem de ponteiros de estruturas para funes: #define LEN 50 struct endereco { char rua[LEN]; char cidade_estado_cep[LEN]; }; void obtem_endereco(struct endereco *); void imprime_endereco(struct endereco); void obtem_endereco(struct endereco *pender) { printf("Entre rua: "); gets(pender->rua); printf("Entre cidade/estado/cep: "); gets(pender->cidade_estado_cep); } void imprime_endereco(struct endereco ender) { printf("%s\n", ender.rua); printf("%s\n", ender.cidade_estado_cep); } int main() { struct endereco residencia; printf("Entre seu endereco residencial:\n"); obtem_endereco(&residencia); printf("\nSeu endereco:\n"); imprime_endereco(residencia); } Neste caso, main() passa para a funo obtem_endereco() um ponteiro para a variavel residencia. obtem_endereco() pode ento alterar o valor de residencia remotamente. Este valor, em main(), ento passado para imprime_endereco(). Note-se que no necessrio passar um ponteiro para a estrutura se seu valor no ser mudado (como o caso da funo imprime_endereco()). De um modo geral, melhor passar ponteiros para estruturas ao invs de passar e retornar valores de estruturas. Embora as duas abordagens sejam equivalentes e funcionem, o programa ir apenas passar 132
ponteiros ao invs de toda uma estrutura que pode ser particularmente grande, implicando em um tempo nal de processamento maior.
27.9
Precedncia de operadores
A precedncia dos operadores * e & alta, a mesma que outros operadores unrios. A tabela 5 apresenta a precedncia de todos os operadores vistos at agora. Operador () ! * + < == && || = , [] -> . - ++ -- * & (cast) (unrios) / % <= > >= != Associatividade esquerda para direita direita para esquerda esquerda para direita esquerda para direita esquerda para direita esquerda para direita esquerda para direita esquerda para direita direita para esquerda esquerda para direita
+=
-=
*=
/=
%=
133
28
Strings e Ponteiros
Vimos anteriormente que strings em C++ nada mais so que arrays de elementos do tipo char cujo ltimo elemento o caracter \0 (nulo). At agora a declarao e inicializao de um string tem sido como abaixo: char nome[] = "Um string de caracteres"; char str[20] = "Um outro string de caracteres"; No exemplo acima, cada conjunto de caracteres limitados por um par de aspas denominado de uma constante string. Como toda expresso em C++ , uma constante string tem um valor e um tipo associado. O compilador C++ automaticamente aloca espao no programa para armazenar este string. Esta constante na verdade est denindo um array que est armazenado em um endereo de memria mas que no tm um nome de varivel associado a ele. O valor produzido para esta constante string um ponteiro para este array annimo. E o tipo da constante um ponteiro para char. Assim, na maior parte dos casos de programas em C++ , mais conveniente representar um string atravs de um ponteiro para char. O valor inicial deste ponteiro deve ser sempre o endereo do primeiro caracter do string ou uma constante string. O exemplo acima pode ser reescrito como abaixo, que deve ser a forma preferida para se declarar e inicializar strings em programas em linguagem C++ : char *nome = "Um string de caracteres"; char *str = "Um outro string de caracteres";
29 Arrays de ponteiros
Da mesma forma que se pode ter arrays de tipos bsicos (e.g. int, char) e de estruturas, pode-se tambm denir arrays de ponteiros: char *frases[60]; frases[0] = "Um string de caracteres"; frases[3] = "Um outro string de caracteres"; frases[10] = "Mais uma frase muito mais comprida que as outras acima"; frases[20] = "Agora uma frasesita pequena"; frases[30] = "Chega de frases"; puts(frases[3]); No exemplo acima cada elemento do array frase um ponteiro para char. Em cada uma das atribuies, cada elemento do array recebe como valor um ponteiro para um string (veja seo anterior sobre constantes string).
30
Uma aplicao de arrays de ponteiros surge de imediato quando voc deseja acessar os argumentos digitados para seu programa na linha de comando. Para isto, a funo main manipula dois argumentos quando o programa inicia a execuo: main (int argc, char *argv[]) { ..... } 134
O primeiro argumento, denominado argc por conveno, representa o nmero de argumentos digitados na linha de comando, incluindo o nome do programa na linha de comando, que denido como o primeiro argumento. Desta forma o valor de argc sempre maior ou igual a 1 (um). Em UNIX, um argumento considerado como uma sequncia de caracteres at a primeira ocorrncia do caracter de espaamento, que pode ser um espao em branco, tab ou o caracter de mudana de linha5 . O segundo argumento de main o array de ponteiros para caracteres chamado argv, por conveno. O primeiro ponteiro em argv, argv[0], aponta para o nome do programa em execuo. Os elementos sucessivos do array, argv[1], argv[2], ..., argv[argc - 1], contm ponteiros para os argumentos digitados na linha de comando aps o nome do programa. Como um exemplo, considere a execuo do programa chamado nroff. Se a linha de comando abaixo digitada: nroff -mm -TXR memo1 ento o valor de argc ser 4 (o nome do programa mais os trs argumentos que se seguem), e o argumento argv ser um array de ponteiros para caracteres. O primeiro elemento deste array aponta para o string "nroff", o segundo para -mm", o terceiro para -TXR" e nalmente o quarto elemento aponta para o string "memo1". Isto pode ser melhor visualizado na Figura 4.
31
Quando voc declara um array em um programa C++ , voc deve informar quantos elementos devem ser reservados para este array. Se voc conhece este nmero a priori, tudo est bem. No entanto, o tamanho de um array pode ser desconhecido por uma srie de fatores. Por exemplo, se voc deseja ler todas as linhas de um arquivo e armazen-los em um array de seu programa, o tamanho de memria necessrio depender do tamanho do arquivo. E se este tamanho variar muito de um arquivo para outro, voc ter que reservar espao suciente para acomodar o maior tamanho de arquivo possvel, o que um desperdcio se seu programa vai lidar com muitos arquivos de tamanho reduzido e apenas alguns arquivos com tamanho grande. Denir um tamanho mximo para o suas estruturas de dados aumenta o tamanho de seu programa. Em um ambiente multitarefa como UNIXeste seu programa estar competingo por espao livre de memria. Se seu programa aloca espao desnecessariamente, isto quer dizer que menos processos podero ocupar
5
Aspas e apstrofes podem agrupar palavras como um argumento nico, mesmo que tenham espaamento.
135
a memria para serem executados. Portanto, durante a gerncia do processo de sua tarefa pelo sistema operacional, a trasnferncia do programa do disco para a memria e vice-versa (processo conhecido como swapping), ir demorar mais. Para permitir que o espao para estruturas de dados de um programa possam ser alocados durante a execuo do mesmo que a linguagem C++ dene funes de biblioteca chamadas funes de alocao dinmica de memria. Estas funes so: #include <stdlib.h> void void void void *malloc (int tamanho) *calloc (int numero_elementos, int tamanho_elemento) *realloc (void *area_alocada, int novo_tamanho) free (void *area_alocada)
As funes malloc() e calloc() so usadas para alocar espao para seus dados uma vez que voc determinou quanto espao voc precisa. E se sua estimativa se revela muito grande ou muito pequena, voc pode mudar o tamanho deste espao alocado com a funo realloc. Finalmente, uma vez que o programa terminou de usar o espao alocado, a funo free pode ser usada para liberar o espao previamente alocado. Observe-se a diretiva #include que necessria para o uso das funes de alocao.
31.1
malloc e calloc
As funes malloc e calloc alocam espao de memria. malloc recebe como argumento o nmero de bytes a ser alocado. calloc recebe como primeiro argumento o nmero de elementos a aser alocado. O segundo argumento indica o tamanho em bytes de cada elemento. calloc garante que o espao alocado inicializado com zeros, enquanto que malloc no. Estas duas funes retornam um ponteiro para a nova rea alocada. O tipo do ponteiro por conveno char *. Caso o ponteiro seja usado para apontar para outro tipo qualquer, um cast no valor retornado deve ser usado. Observe o exemplo abaixo: #include <stdlib.h> char buf[30], *memchar; int *memint, i; .... memchar = malloc (sizeof(buf)); if (memchar == NULL) { printf ("Erro em alocacao de memoria\n"); exit (1); } else { memcpy (memchar, buf, sizeof(buf)); /* copia o contedo de * buf para memchar */ } memint = (int *) calloc (1, sizeof(int)); 136
if (memint == NULL) { printf ("Erro em alocacao de inteiros\n"); exit (1); } else { i = 0; do { scanf("%d", memint + i); if (memint[i]) { memint = (int *) realloc (memint, (i + 2) * sizeof(int)); if (memint == NULL) break; } } while (memint[i++]); } Oberve no exemplo acima o uso de cast na chamada de calloc. Como o ponteiro retornado ser usado para apontar para inteiros, o retorno da funo deve ser convertido de acordo com o cast. Quando no h sucesso na alocao de memria, as funes malloc e calloc retornam um ponteiro nulo, representado pela constante NULL. Assim, toda vez que um programa usa estas funes, deve-se testar o valor retornado para vericar se houve sucesso na alocao. o que acontece no exemplo acima ao se testar os valores de memchar e memint imediatamente aps a chamada das funes de alocao.
31.2
realloc
A funo realloc usada para redimensionar um espao alocado previamente com malloc ou calloc. Seus argumentos so um ponteiro para o INCIO de uma rea previamente alocada, e o novo tamanho, que pode ser maior ou menor que o tamanho original. realloc retorna um ponteiro para a nova rea alocada. Este ponteiro pode ser igual ao ponteiro original se o novo tamanho for menor que o tamanho original, e diferente do ponteiro original se o novo tamanho for maior que o tamanho original. Neste ltimo caso, realloc copia os dados da rea original para a nova rea. O ltimo bloco else no exemplo da seo anterior um exemplo de uso da funo realloc. Neste bloco, usa-se a funorealloc para aumentar de 1 (um) o tamanho do array dinmico representado por memint.
137
Referncias
[1] H. M. Deitel and P. J. Deitel. C++: Como Programar. Prentice-Hall, Inc., 5a edition, 2006. [2] Stanley B. Lippman. C++ Primer. Addison-Wesley Publishing Company, 1991. ISBN: 0-201-54848-8. Livro bastante didtico sobre C++. [3] Victorine Viviane Mizrahi. Treinamento em Linguagem C++. Prentice-Hall, Inc., 2a edition, 2005. [4] Walter Savitch. C++ Absoluto. Addison-Wesley Publishing Company, 2004. ISBN: 85-88639-09-2. Livro bastante didtico sobre C++. [5] Bjarne Stroustrup. A Linguagem de Programao C++. Bookman, 2000.
138