Programação Basica em C
Programação Basica em C
Programação Basica em C
Maio 2008
Parte I
Programao Bsica em C
Estas notas de aula apresentam os conceitos bsicos da Linguagem C e se prope a abordar apenas o que importante para a compreenso bsica de programas de computadores.
Programas C
Um programa C consiste de uma ou mais partes chamadas funes. Um programa em C consiste de 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 <stdio.h> 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; printf("%d\n", i); 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 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. 2
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 Dev-C++ um oat ocupa 4 bytes de memria. H muitos outros tipos (short, long, double), que sero descritos no futuro.
1.3
Denio de Varivel em C
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); 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. 3
Tabela 1: Palavras Reservadas da Linguagem C Os tipos bsicos de dados existentes em C so: Tipo de Dado char int oat double Bits 8 16 32 64 Faixa de Valores -128 a 127 -32768 a 32767 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; ... } 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. 4
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 scanf(). Ns veremos mais tarde que a funo scanf() 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 as funes existentes em C chamadas printf() e scanf(). Se voc quiser usar estas funes em seu programa, voce deve incluir a seguinte linha no incio do seu cdigo fonte: #include <stdio.h> Isto faz com que o arquivo header chamado stdio.h seja includo no seu arquivo fonte. Este arquivo contm prottipos das funes print() e scanf(). Ele declara ao compilador o nome das funes e algumas informaes adicionais necessrias para que as instrues sejam executadas corretamente.
1.7
printf() pode ser utilizado para imprimir mensagens e valores em uma variedade de formatos. Por enquanto, printf() melhor descrito atravs de exemplos. printf(Al todo mundon); Imprimir Al todo mundo na tela do computador. Para dizer funo printf exatamente o que fazer, ns devemos especicar o que ser impresso. Ns devemos dar a funo o que chamamos de argumentos. No exemplo acima, Alo todo mundo um argumento para a funo printf(). O primeiro argumento do printf() sempre 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. Se quisermos imprimir o valor de expresses variveis, argumentos adicionais so necessrios. Dizemos ao printf() como mostrar valores de expresses usando especicadores de formato. Podemos colocar %c, %d, %f (ou outros especicadores de formato listados no texto) dentro do primeiro argumento para especicar o que queremos dar display. Ns ento passamos argumentos adicionais que ser referem aos especicadores de formato (na ordem em que eles ocorrem). Este argumentos podem ser constantes ou variveis, ou alguma expresso mais complicada. O que quer que eles sejam, eles devem ser avaliados e os valores obtidos e impressos de acordo com os especicadores de formato. Considere o seguinte programa: #include <stdio.h> #dene PRECO 1.99 int main() { int pera = 3; char qualidade = A; oat peso = 2.5; printf(Existem %d peras de qualidade %c , pera, qualidade); printf(pesando %f quilos.\n, peso); printf(O preco por quilo e %f, total e %f\n, PRECO, peso * PRECO); } A sada do programa ser: Existem 3 peras de qualidade A pesando 2.500000 quilos. O preco por quilo e 1.990000, total e 4.975000 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. Ns tambm podemos especicar o tamanho utilizado para impresso da seguinte forma: %6d inteiro, com pelo tamanho pelo menos 6 %6f ponto utuante, com tamanho pelo menos 6 %.3f ponto utuante, com 3 digitos depois do ponto decimal %6.3fponto utuante, com tamanho pelo menos 6 e 3 digitos depois do ponto decimal %6.0fponto utuante, com pelo menos tamanho 6 e nenhum digito depois do ponto decimal. 6
Note que a especicao de tamanho simplesmente determina o tamanho mnimo. Se o nmero no couber no tamanho especicado, o nmero completo ser mostrado. Quando utilizar a funo printf() tenha cuidado para especicar o tipo correto dos argumentos. Se o tipo do argumento no for correto, o compilador Dev-C++ no acusar erro, e um valor incorreto ser mostrado. Por exemplo, no programa abaixo que est incorreto: #include <stdio.h> int main() { printf(Exemplo errado: %d\n, 3.14159); } O resultado do programa ser alguma coisa como: Exemplo errado: -31147
1.8
scanf() pode ser usado para ler valores digitados no teclado. Estes valores so lidos de acordo com especicadores de converso, que so especicados pelo programador como argumentos do scanf(). Considere o seguinte programa: #include <stdio.h> int main() { int idade; printf(Entre sua idade: ); scanf(%d, &idade); printf(Voce tem %d anos\n, idade); } 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. Assim como com o printf(), o primeiro argumento o especicador de formato. Os prximos argumentos correspondem a o que est sendo especicado pelo primeiro argumento. Note o & precedendo a varivel idade. Simplesmente lembre-se que voc geralmente precisar colocar um & precedendo nomes de variveis em um scanf(). Voc sempre precisar us-lo antes de variveis do tipo primrio como os discutidos at este momento (int, char, oat, e suas verses long e unsigned). Mais de um valor pode ser lido por um mesmo scanf(). Considere o seguinte exemplo: #include <stdio.h> int main() { int dia, mes, ano; printf(Entre com a data do seu aniversario (dd mm aa): ); scanf(%d %d %d, &dia, &mes, &ano); 7
printf(Voce nasceu em %d/%d/%d\n, dia, mes, ano); } Este exemplo funciona exatamente como o exemplo anterior. Um nico scanf() 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 scanf(). Os brancos na especicao de formato do scanf(), %d %d %d so simplesmente para facilitar a leitura do programa, e no tem nada a ver com os espaos ignorados pelo scanf(). Se tivssemos escrito %d%d%d, o scanf() funcionaria da mesma forma. Os espaos em branco simplesmente so necessrios para saber quando termina um nmero e comea o outro. Porm se o scanf() estiver lendo caracteres (%c), os espaos no so ignorados, j que espaos so caracteres vlidos na linguagem. Por exemplo, o cdigo ASCII para espao em branco e 32.
1.9
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; /* 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 <stdio.h> /* inclui diretivas de entrada-sada */ #include <math.h> /* inclui diretivas das funes matemticas */ 8
#define
PI
3.14159
int main() { /* Definir variaveis */ int Raio; float Perim, Area; /* Obter Raio da circunferencia */ printf("Entre com o valor do raio: "); scanf("%d", &Raio); /* Calcular Perimetro do Circulo */ Perim = 2 * PI * Raio; /* Calcular Area da Circunferencia */ Area = PI * pow(Raio, 2); /* Exibir Resultados */ printf("O perimetro da circunferencia de raio %d eh %.2f printf("e a area eh %.2f", Area); }
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 *, / and % (que tem todos a mesma precedncia), seguido de + and - (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; printf("circunferencia = %f\n", 2 * 3.14 * raio); 10
Exemplos de lugares onde uma expresso aritmtica NO pode ser usada incluem: int yucky + 2 = 5; scanf("%d", &(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 = 17; printf("Pode tirar carteira de motorista? %d\n", idade >= 18); idade = 35; printf("Pode tirar carteira de motorista? %d\n", idade >= 18); } 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.
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 <stdio.h> int main() { int score = 5; printf(%d, printf(%d, printf(%f, printf(%c, printf(%d, } #include <stdio.h> int main() { int n1, n2, n3; printf(Entre com um numero inteiro: ); scanf(%d, &n1); n1 += n1 * 10; n2 = n1 / 5; n3 = n2 % 5 * 7; n2 *= n3-- % 4; printf(%d %d %d, n2, n3, n2 != n3 + 21); } 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 <stdio.h> int main() { int numero; printf(Entre com um numero inteiro: ); scanf(%d, &numero); printf(\nPar? %d\n, numero % 2 ); } Exemplo 2: escreva um programa que leia 3 nmeros inteiros e calcule a soma, mdia, e produto. #include <stdio.h> int main() { int n1, n2, n3; int soma; printf( "Entre com 3 numeros inteiros: "); scanf( "%d %d %d",&n1, &n2, &n3); soma = n1 + n2 + n3; printf( "Soma = %d\n", soma ); printf( "Media = %8.2f\n", soma / 3.0 ); printf( "Produto = %d\n", n1 * n2 * n3 ); }
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 lgicos1 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; printf("Quadrado de %d eh menor que 50? %d \n", n, (quadrado = n * n) < 50);
1
14
causa no apenas que o valor 4 seja impresso, como a avaliao da expresso relacional dentro do printf() 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; printf("Quadrado de %d eh menor que 50? %d \n", n, n * n < 50); 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 <stdio.h> main(void) { int a, b; printf("Entre com uma fracao (numerador and denominador): "); scanf("%d %d", &a, &b); printf("A fracao em decimal eh %f\n", 1.0 * a / b); }
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 <stdio.h> main(void) { int a, b; printf("Entre com uma fracao (numerador e denominador): "); scanf("%d %d", &a, &b); if (b != 0) printf("A fracao em decimal eh %f\n", 1.0 * a / b); }
Exemplo 2: o segundo.
Programa que l dois nmeros e ordena o par caso o primeiro nmero digitado for maior que
#include <stdio.h> main() { int num1, num2, aux; printf("Entre com dois numeros inteiros: "); scanf("%d %d", &num1, &num2); if (num1 > num2) { aux = num1; num1 = num2; num2 = aux; printf("Trocou \n"); } printf("Os numeros ordenados: %d %d\n", num1, num2); } 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) sentenca1 else sentenca2 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 <stdio.h> main(void) { int a, b; printf("Entre com uma fracao (numerador and denominador): "); scanf("%d %d", &a, &b); if (b != 0) printf("A fracao decimal e %f\n", 1.0 * a / b); else printf("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 <stdio.h> main(void) { int num; 18
/* obtem um numero do usuario */ printf("Entre com um inteiro: "); scanf("%d", &num); /* imprime uma mensagem dizendo se o numero e par ou impar */ if (num % 2 == 0) printf("O numero e par.\n"); else printf("O numero e 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) printf("Voce esta quebrado! \n"); else printf("Seu saldo e %d\n", saldo); 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) printf("Voce esta quebrado! \n"); else printf("Seu saldo e %d\n", saldo); 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. 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.
19
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 <stdio.h> main(void) { int num; /* Obtem um numero do usuario */ printf("Entre com um inteiro: "); scanf("%d", &num); /* Imprime uma mensagem dizendo se o numero e positivo ou negativo, positivo ou negativo. */ if (num >= 0) { if (num % 2 == 0) printf("O numero e par e positivo\n"); else printf("O numero e impar e positivo\n"); } else { if (num % 2 == 0) printf("O numero e par e negativo\n"); else printf("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 <stdio.h> main(void) { int num; /* Obtem um numero do usuario */ printf("Entre com o numero de peras: "); scanf("%d", &num); 20
/* Imprime uma mensagem dizendo se o numero de peras e 0 ou 1 (*** isto esta errado !! ***) */ if (num != 0) if (num == 1) printf("Voce tem uma pera.\n"); else printf("Voce nao tem nenhuma pera.\n"); } Neste exemplo, o if tem o seguinte signicado, segundo a regra de associao: #include <stdio.h> main(void) { int num; /* Obtem um numero do usuario */ printf("Entre com o numero de peras: "); scanf("%d", &num); /* Como a sentenca if e vista pelo compilador */ if (num != 0) if (num == 1) printf("Voce tem uma pera.\n"); else printf("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 <stdio.h> main(void) { int num; /* Obtem um numero do usuario */ printf("Entre com o numero de peras: "); scanf("%d", &num); /* Como corrigir o problema (este programa funciona) */ if (num != 0) { if (num == 1) printf("Voce tem uma pera.\n"); } else printf("Voce nao tem nenhuma pera.\n"); } Exerccio 1: Faa um programa que leia 3 nmeros e imprima o maior. 21
#include <stdio.h> main(void) { int a, b, c, maior; printf("Entre com os tres numeros: "); scanf("%d %d %d", &a, &b, &c); if (a > b) maior = a; else maior = b; if (maior < c) maior = c; printf("O Maior numero eh %d\n", maior); }
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: if (num >= 0) if (num % 2 == 0) printf("Numero par nao negativo.\n"); Com os operadores lgicos isso pode ser simplicado: if ((num>=0) && (num%2 == 0)) printf("Numero par nao negativo.\n"); A operao de negao, !, pode ser usado da seguinte forma: !expresso lgica: O valor a negao lgica da expresso dada. Por exemplo: 22
!0 !1
1 0
Ns podemos usar o operador de negao lgica e escrever o exemplo acima como: if (num>0 && !(num%2)) printf("Numero par nao negativo.\n"); 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. 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.
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 #include <stdio.h> main() { int lado1, lado2, lado3; int s1, s2, s3; printf(Entre com o tamanho dos lados do triangulo: ); scanf(%d %d %d, &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) ) { printf("Triangulo reto!\n"); } else { printf("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
24
int x = 4; int y = 8; O que impresso pelos seguintes programas ? 1. if (y = 8) if (x = 5) printf( "a " ); else printf( "b " ); printf( "c " ); printf( "d\n" ); ==> a c d 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) printf( "a\n" ); else printf( "b\n" ); 2. Assuma x = 4 e y = 8. if (x == 5 || y == 8) printf( "a\n" ); else printf( "b\n" );
==> a
==> a
/* equiv. (x != 5 && y != 8) */
==> b 25
if !(x == 5 && y == 8) printf( "a\n" ); else printf( "b\n" ); 3. Precedncia: ! > && > ||
/* equiv. (x != 5 || y != 8) */
==> a
if (x == 5 || equiv.
y == 8 && z == 10)
if (x == 5 || (y == 8 && z == 10))
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.
26
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 <stdio.h> main(void) { int num1, num2; /* obtem 2 numeros do usuario */ printf("Entre um numero: "); scanf("%d", &num1); printf("Entre com um outro numero: "); scanf("%d", &num2); /* mostra a mensagem de comparacao */ if (num1 == num2) printf("Os numeros sao iguais\n"); else if (num1 < num2) printf("O primeiro numero e menor\n"); else printf("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 <stdio.h> main(void) { float num1, num2; char op; /* obtem uma expressao do usuario */ 27
printf("Entre com numero operador numero\n"); scanf("%f %c %f", &num1, &op, &num2); /* mostra o resultado da operacao */ if (op == +) printf(" = %.2f", num1 + num2); else if (op == -) printf(" = %.2f", num1 - num2); else if (op == /) printf(" = %.2f", num1 / num2); else if (op == *) printf(" = %.2f", num1 * num2); else printf(" Operador invalido."); printf("\n"); } Exemplos da execuo deste programa: Entre com numero operador numero: 5 * 3.5 = 17.50 Entre com numero operador numero: 10 + 0 = 10.00 Entre com numero operador numero: 10 x 5.0 Operador invalido.
28
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.
10
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: 29
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 <stdio.h> main(void) { float num1, num2; char op; printf("Entre com numero operador numero:\n"); scanf("%f %c %f", &num1, &op, &num2); switch (op) { case +: printf(" = %.2f", break; case -: printf(" = %.2f", break; case *: printf(" = %.2f", break; case /: printf(" = %.2f", break; default: printf(" Operador break; } printf("\n");
num1 + num2);
num1 - num2);
num1 * num2);
num1 / num2);
invalido.");
30
} 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 <stdio.h> main(void) { float num1, num2; char op; printf("Entre com numero operador numero:\n"); scanf("%f %c %f", &num1, &op, &num2); switch (op) { case +: printf(" = %.2f", break; case -: printf(" = %.2f", break; case *: case x: case X: printf(" = %.2f", break; case /: case \\: printf(" = %.2f", break; default: printf(" Operador break; } printf("\n"); } Exerccio 2: Ler mes e ano e imprimir o numero de dias do mes no ano digitado. #include <stdio.h> main() { int mes, ano, numDias; printf("Entre com mes e ano (mm aa):"); scanf( "%d %d", &mes, &ano); if( mes < 1 || mes > 12 || ano < 0 || ano > 99 ) printf("mes ou ano invalido\n"); 31
num1 + num2);
num1 - num2);
num1 * num2);
num1 / num2);
invalido.");
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; } printf("%2d/%2d tem %d dias\n", mes, ano, numDias); } }
32
11
11.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: printf(), scanf(), getchar(), 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 <stdio.h> 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; 33
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(int n) { int j; j = n - 1; while ((j > 1) && (n % j != 0)) { j = j - 1; } 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 <stdio.h> 34
int ehPrimo(int n) { int j; j = n - 1; while ((j > 1) && (n % j != 0)) { j = j - 1; } if (j == 1) return 1; else return 0; } 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 35
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.
11.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 <stdio.h> /* declaracao (prottipo) da funcao alo() */ void alo(void); /* definicao da funcao main() */ main() { int i; i = 1; while (i <= 3) { alo(); i += 1; } } /* definicao da funcao alo() */ void alo(void) { printf("Alo!\n"); } Todas as funes devem ser declaradas antes de serem usadas. As funes da biblioteca padro, tais como printf(), scanf() e getchar(), so pr-denidas, mas memsmo assim devem ser declaradas (deve ser anunciado ao compilador que elas existem). por isso que inclumos a linha #include <stdio.h> 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 36
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.
11.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) { printf("Alo.\n"); } 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 printf() 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(). 37
11.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. #include <stdio.h> void cumprimenta(char, char); int main() { char primeiro, segundo; printf("Entre com duas iniciais (sem separacao): "); primeiro = getchar(); segundo = getchar(); cumprimenta(primeiro, segundo); } void cumprimenta(char inic1, char inic2) { printf("Ola, %c%c!\n",inic1,inic2); } 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().
11.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. 38
/* programa que verifica se 3 numeros podem ser os lados de um * triangulo reto. */ #include <stdio.h> int quadrado(int); int main() { int s1, s2, s3; printf("Entre tres inteiros: "); scanf("%d %d %d", &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)) ) printf(" %d %d %d podem formar um triangulo reto\n", s1, s2, s3); else printf(" %d %d %d nao podem formar um triangulo reto\n",s1, s2, s3); } /* 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() { printf("cinco = %d\n", cinco() ); } 39
int cinco(void) { return 5; } A sada do programa ser cinco = 5 porque o valor de cinco() dentro da sentena printf() 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(); printf("soma = %d\n", a + b); } int obtem_valor(void) { int valor; printf("Entre um valor: "); scanf("%d", &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
11.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. 40
int obtem_valor_positivo(void) { int valor; printf("Entre um valor: "); scanf("%d", &valor); if (valor >= 0) return valor; printf("Tornando o valor positivo...\n"); 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; printf("Voce quer instrucos? (s/n): "); ch = getchar(); /* Termina se resposta for n */ if (ch == n || ch == N) return; /* Mostra instrucoes */ printf("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.
11.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. 41
Considere o seguinte programa com a funo abs() que calcula o valor absoluto de um nmero. int abs(int); main() { int n; printf("Entre um numero: "); scanf("%d", &n); printf("Valor absoluto de } /* 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. 42 %d e %d", n, abs(n));
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. 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.
11.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.
11.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 **** */ printf("Voce digitou %d\n", x); } void obtem_int(void) { int x; printf("Entre um valor: "); scanf("%d", &x); printf("Obrigado!\n"); 43
} 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. 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 !! **** */ printf("Voce digitou %d\n", x); } int obtem_novo_int(void) { int x; printf("Entre um valor: "); scanf("%d", &x); printf("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.
11.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); 44
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; }
11.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.
11.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 /* e terminam com um */. Alguns exemplos: /* Este e um comentario sem graca */ /* Este e um comentario que usa diversas linhas */ /* Este e * um comentario * de diversas linhas * mais bonito */ 45
Note que no podemos aninhar comentrios dentro de comentrios. Um comentrio termina no primeiro / que encontrar. O comentrio abaixo ilegal: * /* Este e um comentario /* illegal */ ilegal */
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: /* 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 */ scanf("%d", &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: entrada: nenhuma * nenhuma * saida: suposicoes: nenhuma * imprime as instrucoes * algoritmo: */ void instrucoes(void) { /* mostra instrucoes */ printf("O processo de purificacao do Uranio-235 e . . . . . } 46
");
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; printf("Entre com o raio: "); scanf("%f", &raio); printf("Circunferencia = %f\n", 2.0 * PI * raio); } 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 printf() do programa acima depois do pr-processador ser executado: printf("Circunferencia = %f\n", 2.0 * 3.14159265 * raio); 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 47
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 <stdio.h> colocada no incio do programa fonte para incluir informaes (como prottipos de funes) que so necessrios quando printf() e scanf() so chamados dentro do programa. O arquivo entre < > est em algum diretrio padro conhecido pelo pr-processador. Este arquivo stdio.h 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). A extenso .h vem do ingls header le. Apesar de no ser obrigatrio que arquivos includos tenham a extenso .h, geralmente esta a conveno utilizada.
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.
13
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.
48
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;
printf("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. 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. 49
* A saida do programa e a area do triangulo e circulo. *****************************************************************/ #include <stdio.h> #define PI 3.1415 /******************* prototipos *******************/ float triang_area(float, float); float circ_area(float); /******************* definicao de funcoes *******************/ main(void) { /* definicao das variaveis locais */ float base, altura, raio; /* dialogo de entrada printf("\nEntre com a scanf("%f %f", &base, printf("\nEntre com o scanf("%f", &raio); */ base e altura do triangulo: "); &altura); raio do circulo: ");
/* chama as funcoes e imprime o resultado */ printf("Area do triagulo com base e altura %f e %f = %f\n", base, altura, triang_area(base, altura)); printf("Area do circulo com raio %f = %f\n", raio, circ_area(raio)); } /***************************************************************** * 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 50
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. /***************************************************************** * 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 <stdio.h> #define JUROS 0.07 /******************* prototipos *******************/ void credito(float); void debito(float); void juros(void); /******************* globais *******************/ float saldo; /* saldo atual; 51
/* valor a ser
depositado/retirado
*/
printf("Entre com o saldo atual: "); scanf("%f",&saldo); printf("Deposito: "); scanf("%f", &valor); credito(valor); printf("Retirada: "); scanf("%f", &valor); debito(valor); juros(); printf("Juros 7%%.\n"); printf("Saldo = : %.2f\n ", saldo); } /***************************************************************** * Deposita um valor; atualiza a variavel global saldo * 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 52
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 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 */
A sada do programa acima ser 4 j que valor refere-se a denio local. Considere outro exemplo: #include <stdio.h> int soma_y(int); int soma_yy(int); int y = 100; /* variavel global main(void) { int z = 0;
*/
/* variavel local */
printf("%d\n", soma_y(z)); printf("%d\n", soma_yy(z)); printf("%d\n", soma_y(z)); } int soma_y(int x) { return x + y; /* x e variavel local, y e global */ } int soma_yy(int x) { y = 300; /* y e variavel global */ return x + y; /* x e variavel local */ } 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 printf(): printf("%d\n", soma_y(z)); Esta uma chamada para a funo da biblioteca padro printf(). Os parmetros reais desta chamada so o string "%d\n" e a expresso soma_y(z). A ltima expresso a 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 printf() em main(). Agora considere a segunda sentena: printf("%d\n", soma_yy(z)); 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 printf() em main(). Por ltimo, considere a terceira sentena: 54
printf("%d\n", soma_y(z)); 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 printf() 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.
13.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 an = 1
x +an1 an1
se n = 0 caso contrrio
para todo n N
Para achar a raiz quadrada inteira, este algoritmo repetido at que a2 x < (an + 1)2 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. Uma vez que a2 = 62 = 36 42 < (a4 + 1)2 = 72 = 49, o processo termina e a resposta 6. 4 (No necessrio voc entender por que este algoritmo funciona portanto no se preocupe se no conseguir entend-lo)
55
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.
56
14
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 principal construo que veremos o while2
14.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 ) { printf( "contador = %d\n", contador); contador += 1; } printf("ACABOU !!!!\n"); Sada: contador = 0 contador = 1 contador = 2 contador = 3 contador = 4 ACABOU !!!!
2
Existem outras estruturas de repetio: for (seo 15.1) e do ... while (seo 15.3).
57
Neste exemplo, a expresso de teste contador < 5, e o corpo do lao a sentena printf(). 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 printf()). 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 <stdio.h> main( ){ int total = 0, num; while( total < 20 ) { printf( "Total = %d\n", total ); printf( "Entre com um numero: " ); scanf( "%d", &num ); total += num; } printf( "Final total = %d\n", total ); } 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. 58
14.2
A regra principal ser consistente. Assim, seu programa ser mais legvel. 14.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.
14.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; 59
enquanto a inteno era na realidade: while( i < 5 ) { i += 1; j += 1; } 14.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 ! 14.2.4 Laos aninhados
possvel colocar um lao dentro de outro (lao aninhado). Exemplo 2: #include <stdio.h> int main( ){ int linha, coluna; linha = 1; while (linha < 5) { coluna = 1; while (coluna < 5) { printf( "%3d", linha * coluna ); coluna += 1; } linha += 1; } printf( "\n" ); } Sada:
60
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 printf() 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 <stdio.h> int main( ){ int linha, coluna; linha = 1; while (linha < 5) { coluna = 1; while (coluna < 5) { printf( "%3d", linha * coluna ); printf( "\n" ); coluna += 1; } linha += 1; } } Sada: 1 2 3 4 2 4 6 8 3 6 9 12 4 8 12 16 61
Exemplo 4: #include <stdio.h> int main( ){ int linha, coluna; printf("\n"); linha = 1; while (linha < 8) { printf( "\t" ); coluna = 1; while (coluna < linha) { printf( "*" ); coluna += 1; } printf( "\n" ); linha += 1; } } Sada:
62
15
A linguagem C possui outras estruturas de repetio alm do while (seo 14.1). So elas o for e o do...while.
15.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.
63
Veja abaixo um exemplo simples do lao for: int contador; for( contador = 0; contador < 5; contador += 1 ) printf( "contador = %d\n", contador ); printf{"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 que o valor 64
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). 15.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 <stdio.h> #include <stdio.h> main( void ){ int contador, total; for( contador = 0, total = 0; contador < 10; contador += 1 ) { total += contador; printf( "contador = %d, total = %d\n", contador, total ); } } 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. 65
#include <stdio.h> main( ){ int i, soma; soma = 0; for( i = 30; i >= 5; i -= 1 ){ if( (i % 3) == 0 ){ printf( "\t%2d\n", i ); soma += i; } } printf( "\t soma = %d\n", soma ); } Sada do programa: 30 27 24 21 18 15 12 9 6 soma = 162
15.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.
66
15.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 { printf( "contador = %d\n", contador ); contador += 1; } while( contador < 5 ); printf("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 <stdio> main( ){ 67
int num; printf( "Entre com um numero par:\n" ); do{ scanf( "%d", &num ); } while( num % 2 != 0 ); printf( "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 <stdio> main( ){ int num; /* Atribui um numero impar a num */
printf( "Entre com um numero par:\n" ); num = 1; while( num % 2 != 0 ){ scanf( "%d", &num ); } printf( "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.
68
16
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. 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.
17
Considere o programa abaixo que pede ao usurio dois inteiros, armazena-os em duas variveis, troca seus valores, e os imprime. #include <stdio.h> main(void) { int a, b, temp; printf("Entre dois numeros: "); scanf("%d %d", &a, &b); printf("Voce entrou %d e %d\n", a, b); 69
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 <stdio.h> void troca(int, int); void troca(int x, int y) { int temp; temp = x; x = y; y = temp; } main(void) { int a, b; printf("Entre dois numeros: "); scanf("%d %d", &a, &b); printf("Voce entrou com %d e %d\n", a, b); /* Troca a e b */ troca(a, b); printf("Trocados, eles sao %d e %d\n", a, b); } 70
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 s podemos retornar um valor (no dois) atravs da sentena return.
17.1
Usando ponteiros
A soluo para o problema acima ao invs de passar os valores de a e b, passar o endereo das variveis a e b. Desta forma, troca() saberia em 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 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.
17.2
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 71
17.3
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 endreo, dizemos que ele aponta para o endereo de memria (ou se este endereo de memria for associado a uma varivel, dizemos que ele aponta para esta 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 tamanho (em Dev-C++ so 5 bytes), 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). Por exemplo, um tipo ponteiro usado para apontar para inteiros chamado ponteiro para int e isso denotado por um *. Variveis do tipo ponteiro para int so usados para armazenar endereos de memria que contem valores do tipo int. Por exemplo, 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 no inicializamos as variveis 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).
17.4
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 nada a ver com o smbolo de multiplicao. Usando os exemplos anteriores, *pi o objeto apontado por pi. *pi tem valor 5 *pc tem valor G Como um pointer 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 pointer 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). 72
17.5
Nos exemplos acima, pode parecer que ponteiros no so teis, j que tudo que zemos pode ser feito sem usar ponteiros. Agora, 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 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 da varivel), habilitamos a funo a alterar o contedo destas variveis da 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 <stdio.h> void troca(int *, int *); /* function troca(px, py) troca os valores inteiros apontados por px e py * acao: entrada: apontadores px e py * valor de *px e *py trocados * saida: * suposicoes: px e py sao apontadores validos primeiro guarda o primeiro valor em um temporario e troca * algoritmo: */ void troca(int *px, int *py) { int temp; temp = *px; *px = *py; *py = temp; } main(void) { int a, b; printf("Entre dois numeros: "); scanf("%d %d", &a, &b); printf("Voce entrou com %d e %d\n", a, b); /* Troca a e b -- passa enderecos */ troca(&a, &b); 73
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 varivels da 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.
17.6
Precedncia de operadores
A precedncia dos operadores * e & alta, a mesma que outros operadores unrios. A tabela 4 apresenta a precedncia de todos os operadores vistos at agora. 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
- ++ / % <= > !=
--
>=
+=
-=
*=
/=
%=
74
18
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; printf("Entre a nota scanf("%d", ¬a0); printf("Entre a nota scanf("%d", ¬a1); printf("Entre a nota scanf("%d", ¬a2); printf("Entre a nota scanf("%d", ¬a3); do estudante 0: "); do estudante 1: "); do estudante 2: "); do estudante 3: ");
media = (nota0 + nota1 + nota2 + nota3) / 4; printf("Notas: %d %d %d %d\n", nota0, nota1, nota2, nota3); printf("Media: %d\n", media); } 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].
18.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: int total[5]; float tamanho[42]; 75
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. O nome do array representa um endereo de memria3 constante que aponta para o incio do espao de armazenamento (o primeiro byte do bloco de bytes). O array, como um todo, no tem um valor, mas cada elemento individual tem um valor. 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]; scanf("%f", &tamanho[41]); Agora, podermos reescrever o programa que calcula a mdia de uma classe de 4 alunos: int main()
Este valor de endereo de memria conhecido em C como ponteiro. Este conceito ser visto com detalhes nos prximos captulos.
3
76
{ int indice, nota[4], total = 0; for (indice = 0; indice < 4; indice++) { printf("Entre a nota do estudante %d: ", indice); scanf("%d", ¬a[indice]); } printf("Notas: "); for (indice = 0; indice < 4; indice++) { printf("%d ", nota[indice]); total += nota[indice]; } printf("\nMedia: %d\n", total/4 ); } 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. Note que um & ainda precede o elemento do array passado para o scanf(). No necessrio usar parnteses porque [] tem maior precedncia que &. 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) { printf("Entre a nota do estudante %d: ",indice); scanf("%d", ¬a[indice]); indice += 1; /* mesma coisa que "indice = indice + 1" */ } printf("Notas: "); indice = 0; while (indice < ESTUDANTES) { printf("%d ", nota[indice]); total = total + nota[indice]; indice += 1; /* mesma coisa que "indice = indice + 1" */ } printf("\nMedia: %d\n", total / ESTUDANTES ); } 77
18.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 */
18.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 Dev-C++ , voc poder ver uma mensagem como Esta aplicao violou a integridade do sistema 78
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.
18.4
Para passar um array como argumento (com todos os seus elementos) passamos o nome do array. Considere o exemplo abaixo. #include <stdio.h> #include <math.h> #define TAMANHO 5 /* funcao array_max(a) acha o maior inteiro de um array de TAMANHO elementos * acao: array a de inteiros * entrada: 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]; 79
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) { printf("Entre um inteiro: "); scanf("%d", &valor[i]); i += 1; } printf("O maior e %d\n", array_max(valor)); }
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 ARGUMENTO, ALTERAES NO ARRAY FEITAS DENTRO DA FUNO ALTERAM O CONTEDO DO ARRAY PASSADO COMO PARMETRO REAL. 80
Note que isto no acontece quando uma varivel simples passada como argumento. Considere o exemplo seguinte: #include <stdio.h> #include <math.h> /* Troca o valor de uma variavel */ void troca( int a ) { a = 20; } /* Troca valores de elementos em um vetor */ void troca_v( 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 ); printf("x=%d \n", x); troca_v(v); printf( "v[0]=%d v[1]=%d v[2]=%d \n", v[0], v[1], 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, 81
80. Como mencionado anteriormente, quando passamos um array como parmetro, as alteraes feitas no 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 <stdio.h> /* Troca o valor de uma variavel */ void troca( int a ) { a = 20; } 82
/* Troca valores de elementos em um vetor */ void troca_v( int vet[] ) { vet[0] = 60; vet[1] = 70; vet[2] = 80; } /* Programa Principal */ int main() { int v[3]; v[0] = 30; v[1] = 40; v[2] = 50; troca( v[0] ); printf("v[0]=%d \n", v[0]); troca_v(v); printf( "v[0]=%d v[1]=%d v[2]=%d \n", v[0], v[1], v[2] ); return 0; }
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 <stdio.h> #include <math.h> #define TAMANHO 30 /* funcao inicializaArray(a, k) inicializa todos os elementos de a com k * acao: array de inteiros a, inteiro k * entrada: saida: nenhum * * suposicoes: a tem TAMANHO elementos uma repeticao for, inicializando um elemento a * algoritmo: cada repeticao * */ void inicializaArray(int a[], k) { int i; i = 0; 83
while (i < TAMANHO) { a[i] = k; i += 1; } } /* Programa Principal */ int main(void) { 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.
18.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. 18.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 84
/* 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; i = tam - 1; while (i >= 0) { if (arr[i] == chave) { return i; } i += 1; } return NAO_ACHOU; }
18.6
18.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; i = 0; while (i < tam) { arr3[i] = arr1[i] + arr2[i]; i += 1; } }
18.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 apresentado a seguir um algortmo bsico e nem um pouco eciente, denominado Select sort. 85
Ele usa o fato simples de comparar cada elemento de um array com o restante deste. Quando se acha o menor, ocorre uma troca de valores entre o elemento sob anlise e o outro elemento do array que o menor. Por exemplo, se comearmos com um array: 9 5 2 7 3 8 1 4 6, (o primeiro elemento 9 e o ltimo elemento 6) isto o que acontece com os elementos do array depois de cada passagem sobre ele (e consequente troca de valores): passagem ~~~~ 0 --> 1 --> 2 --> 3 --> 4 --> 5 --> 6 --> 7 --> 8 --> conteudo do array depois da passagem ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 5 2 7 3 8 1 4 6 1 1 1 1 1 1 1 1 5 2 2 2 2 2 2 2 2 5 3 3 3 3 3 3 7 7 7 4 4 4 4 4 3 3 5 5 5 5 5 5 8 8 8 8 8 6 6 6 9 9 9 9 9 9 7 7 4 4 4 7 7 7 9 8 6 6 6 6 6 8 8 9
Note que mesmo que se comessemos com um array ordenado de 9 elementos, ainda assim o algoritmo dado faz 8 passagens sobre o array. 18.7.1 Prottipo da funo e denio
1. Prottipo void ordena(int [], int); 2. Denicao #include <stdio.h> #include "imprimePassos.c" /* Uma funcao que encontra o menor valor em um * "a" e "b" (inclusive) */ int menor_indice(int v[], int a, int b) { int i; int menor; menor = a; i = a+1; 86 array entre os indices
/* Uma funcao que troca os valores entre dois elementos de um array */ void troca_v( int vet[], int i, int j) { int aux; aux = vet[i]; vet[i] = vet[j]; vet[j] = aux; } /* 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 ordena(int lista[], int tam) { int i,j, menor; /* indice do menor valor no array entre i e tam-1 */ i = 0; while (i < tam) { imprimePassos( lista, tam, i ); menor = menor_indice( lista, i, tam-1 ); troca_v( lista, i, menor ); i += 1; } }
18.8
O algoritmo abaixo ligeiramente melhor que o anterior e chamado Bubble sort. Ele bastante simples, porm ainda no muito eciente. Basicamente, o algoritmo funciona da seguinte forma: 87
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 acontece com os elementos do array depois de cada passagem sobre ele (e troca de valores adjacentes): passagem ~~~~ 1 --> 2 --> 3 --> 4 --> 5 --> 6 --> 7 --> 8 --> conteudo do array depois da passagem ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 9 8 7 6 5 4 3 2 1 1 1 1 1 1 1 2 2 2 2 2 2 2 9 3 3 3 3 3 3 8 9 4 4 4 4 4 7 8 9 5 5 5 5 6 7 8 9 6 6 6 5 6 7 8 9 7 7 4 5 6 7 8 9 8 3 4 5 6 7 8 9
Note que, tambm aqui, mesmo que se comessemos com um array ordenado de 9 elementos, ainda assim o algoritmo dado faz 8 passagens sobre o array. Isto pode ser melhorado da seguinte forma: 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. 18.8.1 Algoritmo Bubble Sort otimizado
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.
88
18.8.2
1. Prottipo void ordena(int [], int); 2. Denicao #include <stdio.h> #include "imprimePassos.c" /* 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 ordena(int lista[], int tam) { 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 */ ordenado = 0; while (ordenado == 0) { 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. */ imprimePassos(lista, tam, elem_final - 1); i = tam - 1; while (i >= elem_final) { 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 */ } i -= 1; } elem_final++; 89
} }
18.9
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(void){ 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 */ printf(X); /* tem que comparar elemento por elemento */ }
90
19
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 <stdio.h> #define ALTURA 5 #define LARGURA 5 int main() { int x; /* numero da coluna */ int y; /* numero da linha */ int matriz [ALTURA] [LARGURA]; /* array 2-D [num_lins, num_cols] */
/* preenche a matriz com zeros 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 */ printf("\nEntre coordenadas na forma y,x (2,4).\n"); printf("Use valores negativos para sair do programa.\n"); printf("Coordenadas: "); scanf("%d,%d", &y, &x); while (x >= 0 && y >= 0) { matriz[y][x] = 1; /* coloca 1 no elemento escolhido */ 91
y = 0; while (y < ALTURA) /* imprime o array todo */ { x = 0; while (x < LARGURA) { printf("%d ", matriz[y][x] ); x += 1; } printf("\n\n"); y += 1; } printf("\n"); printf("Coordenadas: "); scanf("%d,%d", &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 15.1. /* Exemplo de array 2-D - taboada */ #include <stdio.h> #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; printf("\n Tabela de Multiplicacao\n");
for (x=1; x < COL; x+=1) printf("%3d", x); printf("\n"); /* Imprime uma linha horizontal */ printf(" "); for (x=0; x < 3*COL; x+=1) printf("-"); printf("\n"); /* Imprime as linhas da tablea. Cada linha a precedida pelo indice de linha e uma barra vertical */ for (y=0; y < LIN; y+=1) { printf("%2d|", y); for(x=0; x < COL; x+=1) printf("%3d", tabela[y][x]); printf("\n"); } } 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
19.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} }; /* 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. 93
19.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}
19.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. 94
/* Exemplo de array 2-D - tabela de multiplicacao */ #include <stdio.h> #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); printf("\n Tabela de Multiplicacao\n");
imprime_arr(tabela, LIN); } /* Inicializa o array com a tabela de multiplicacao */ 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+=1) for(x=0; x < COL; x+=1) 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 */ printf("%6d", 0); for (x=1; x < COL; x+=1) printf("%3d", x); printf("\n");
printf(" "); for (x=0; x < 3*COL; x+=1) printf("_"); printf("\n"); /* imprime as linhas do array. cada linha e precedida pelo numero da linha e uma barra vertical */ for (y=0; y < nlin; y+=1) { printf("%2d|", y); for(x=0; x < COL; x+=1) printf("%3d", arr[y][x]); printf("\n"); } } Outro exemplo com funoes de manipulao de arrays bidimensionais: /* funcoes com argumentos tipo array 2-D */ #include <stdio.h> #define ALTURA 5 #define LARGURA 5 void void void void void void seleciona_elem (int [][LARGURA], int); pontos (int [][LARGURA], int); imprime_matriz (int [][LARGURA], int); marca_triang (int [][LARGURA], int); flip (int [][LARGURA], int); espera_entrada(void);
/**** DEFINICAO DE FUNCOES ********/ /* funcao que preenche uma matriz nlin X LARGURA com pontos */ void pontos( int matriz[][LARGURA], int nlin) { int x,y; for(y=0; y<nlin; y+=1) for(x=0; x<LARGURA; x+=1) matriz[y][x] = .; } /* funcao que preenche os elementos selecionados da matriz com um * quadrado e imprime a matriz */ void seleciona_elem(int matriz[][LARGURA], int nlin) { int x, y; 96
printf("\nEntre com as coordenadas na forma y,x (2,4).\n"); printf("Use numeros negativos para terminar.\n"); while (1) { printf("Coordenadas: "); scanf("%d,%d", &y, &x); if (x >= 0 && y >= 0) { matriz[y][x]=\xB1; imprime_matriz(matriz, nlin); } else break; } } /* funcao que marca todos os elementos abaixo da diagonal principal de * um array nlin X LARGURA com quadrados */ void marca_triang(int matriz[][LARGURA], int nlin) { int x, y; printf("Triangulo\n"); pontos(matriz, nlin); for (y = 0; y < nlin; y+=1) for (x = 0; x <= y; x+=1) matriz[y][x] = \xB1; } /* funcao que imprime um array 2-D nlin X LARGURA */ void imprime_matriz(int matriz[][LARGURA], int nlin) { int x,y; for(y=0; y<nlin; y+=1) { for(x=0; x<LARGURA; x+=1) printf("%c ", matriz[y][x] ); printf("\n\n"); } printf("\n"); } /* funcao que flipa um array ao longo da diagonal principal */ void flip(int matriz[][LARGURA], int nlin) { int x, y; int temp; 97
printf("Flipado ao longo da diagonal principal.\n"); for (y = 0; y < nlin; y+=1) for (x = 0; x <= y; x+=1){ 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 ) { getchar(); } /********** MAIN ************/ /* alguns exemplos de chamadas de funcoes com argumentos array 2-D */ int main() { int 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(); }
98
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.).
99
20
20.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;
20.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 ++ and --, podem ser usados para encurtar as operaes acima: k++; j--; Estes operadores tambm podem ser colocados depois do nome da varivel: 100
++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; printf("k = %d\n", k); printf("k = %d\n", k++); printf("k = %d\n", k); } O programa acima (que usa ps-incremento) imprimir o seguinte: k = 5 k = 5 k = 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; printf("k = %d\n", k); printf("k = %d\n", ++k); printf("k = %d\n", k); } O programa, que usa pr-incremento, ter a seguinte sada: k = 5 k = 6 k = 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 printf(), tome cuidado para no fazer o seguinte: 101
printf("%d %d\n", ++uhoh, uhoh * 2); 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 printf() sejam avaliados em uma determinada ordem. O resultado do printf() ser diferente dependendo se ++uhoh avaliado primeiro ou depois de uhoh * 2. A soluo para este problema escrever o seguinte: ++uhoh; printf("%d 20.2.1 %d\n", uhoh, uhoh * 2);
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). 20.2.2 Ambiguidade em certas expresses
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.
102
21
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.
21.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; printf("Entre uma fracao (numerador e denominador): ") scanf("%d %d", &a, &b); printf("A fracao em decimal e } 103 %f\n", 1.0 * a / b);
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.
21.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 5 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
21.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 104
O parnteses NO opcional na expresso acima. Podemos usar o cast de tipos da seguinte forma: int fahr = 5; float cels; printf("Valor = %f\n", (float)fahr); cels = (float)5 / 9 * (fahr - 32); printf("celsius = %d\n", (int)cels); 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; printf("Entre com uma fracao (numerador e denominador): ") scanf("%d %d", &a, &b); printf("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. %f\n", (float) a / b);
105
22
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.
106
#include <stdio.h> /* 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 */ int diaDaSemana( int dia, int mes, int ano ); int main(){ int diaPgto, mesPgto, anoPgto; int diaSem; printf("Entre com a data de pagamento (dd mm aa): "); scanf("%d %d %d", &diaPgto, &mesPgto, &anoPgto); diaSem = diaDaSemana( diaPgto, mesPgto, anoPgto ); if( diaSem == 5 ) diaPgto = diaPgto + 2; else if( diaSem == 6 ) diaPgto++; printf("Data do pagamento: %d/%d/%d\n", diaPgto, mesPgto, anoPgto); } 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 <stdio.h> 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; printf("Entre com a data de pagamento (dd mm aa): "); scanf("%d %d %d", &diaPgto, &mesPgto, &anoPgto); diaSem = diaDaSemana( diaPgto, mesPgto, anoPgto ); if( diaSem == SAB ) diaPgto = diaPgto + 2; else if( diaSem == DOM ) diaPgto++; printf("Data do pagamento: %d/%d/%d\n", diaPgto, mesPgto, anoPgto); } 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.
107
23
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: scanf(), printf(), getchar(), puts(), gets(). Estas funes so declaradas no arquivo <stdio.h>. 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, getchar(), scanf()) 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 getchar( void ); int putchar( int ); 2. Entrada e sada de strings: char *gets(char *); int puts( char *); 3. Entrada e sada formatada: int scanf(char *format, arg1, arg2, ... ); int printf(char *format, arg1, arg2, ... );
23.1
Vamos discutir algumas funes de entrada de dados (diferente do scanf()). 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, mas no quer usar o scanf(). Isso pode ser feito usando a funo getchar(). A funo putchar() aceita um argumento de entrada, cujo valor ser impresso como caracter: #include <stdio.h> 108
int main() { char ch; printf("Digite algum caracter: "); ch = getchar(); printf("\n A tecla pressionada eh %c.\n", ch); } O Resultado deste programa na tela : Digite algum caracter: A A tecla pressionada eh A. Outro exemplo: #include <stdio.h> int main() { char ch; printf("Digite outro caracter: "); ch = getchar(); putchar(ch); } O Resultado deste programa na tela : Digite outro caracter: B B
23.2
23.2.1
getchar() uma funo da biblioteca padro stdio. Cada vez que chamada, esta funo l um caractere teclado; getchar comea a ler depois que a tecla RETURN digitada no nal de uma sequncia de caracteres (dizemos que a entrada para a funo getchar() est no uxo de entrada). A funo getchar() 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 <stdio.h> main(){ 109
int ch; ch = getchar(); } getchar() 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 getchar(). 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 getchar() s comear a ler o que foi digitado depois que for teclado RETURN . Alm disso, com uma chamada da funo getchar() 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 getchar() chamada uma nica vez, mas isto pode causar problemas quando ele usado dentro de um lao. No iniccio de qualquer programa que usa getchar(), voc deve incluir #include <stdio.h> Esta diretiva do pr-processador diz ao compilador para incluir informaes sobre getchar() e EOF (mais sobre EOF mais tarde.). Considere o seguinte programa: #include <stdio.h> int main(){ int ch; printf( "Entre com uma letra: " ); ch = getchar(); if( ch < A || ch > z ) printf( "Voce nao teclou uma letra!" ); else printf( "Voce teclou %c, e seu codigo ASCII eh %d.\n", ch, ch ); } 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.
Neste caso o usurio digitou quatro caracteres e depois teclou RETURN . Embora quatro caracteres tenham sido digitados, somente uma chamada a funo getchar() 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 getchar() int e no char. O valor retornado pela funo o cdigo ASCII do caractere lido. 110
23.2.2
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 getchar(). Quando isso ocorre, o valor de ch depois de executar ch = getchar(); 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 <stdio.h> int main() { int total = 0, ch; /* Le o proximo caractere em ch e para quando encontrar final do arquivo */ while( (ch = getchar()) != EOF ) { total++; } printf( "\n%d caracteres digitados\n", total ); }
S para esclarecer: voc deve teclar RETURN depois de entrar com o comando ^D (ou ^Z no MSWindows). 23.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 getchar(), voc deve teclar RETURN no nal. Como mencionado anteriormente, o primeiro caractere digitado lido pelo getchar(). Mas, o caractere de nova linha continua no uxo de entrada (porque voc teclou RETURN ). De qualquer forma, se voc executar um getchar() depois de um scanf() ou de um getchar() voc ler o caractere de nova linha deixado no uxo de entrada. Da mesma forma, quando voc usa scanf() para ler informaes, ele somente l o que necessrio. Se voce usar scanf() para ler um nmero inteiro e digitar 42 (seguido de RETURN ), o scanf() l 42, mas deixa (e o caractere de nova linha do RETURN ) no uxo de entrada. Outro caso problemtico quando o scanf() usado num lao. Se voc digitar um valor do tipo errado, o scanf() ler o valor errado e a execuo do lao continuar na sentena aps o scanf(). Na prxima iterao do lao o scanf() vai tentar ler novamente, mas o lixo deixado da iterao anterior ainda estar l, e portanto a chamada corrente do scanf() 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 getchar() (para ler um caracter s) ou scanf(), voc deve ler todo o lixo restante at o caractere de nova linha. Colocando as seguinte linhas aps chamadas a getchar() ou scanf() o problema eliminado: 111
/* Pula o restante da linha */ while( getchar() != \n ); Note que isso no necessrio aps todas as chamadas a getchar() ou scanf(). S depois daquelas chamadas que precedem getchar() (ou scanf()), especialmente em um lao. A funo scanf() na realidade retorna um inteiro que o nmero de itens (valores) lidos com sucesso. Voc pode vericar se o scanf() 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 ){ printf( "Total = %d\n", total ); printf( "Entre com um numero: " ); if( scanf("%d", &num) < 1 ) /* Ignora o resto da linha */ while( getchar() != \n ); else total += num; } printf( "Final total = %d\n", total ); }
112
24
Arquivos
O armazenamento de dados em variveis e arrays temporrio. Arquivos so usados para armazenamento permanente de grandes quantidades de dados (e programas) em dispositivos de armazenamento secundrio, como discos. s vezes no suceinte para um programa usar somente a entrada e sada padro. H casos em que um programa deve acessar arquivos. Por exemplo, se ns guardamos uma base de dados com endereos de pessoas em um arquivo, e queremos escrever um programa que permita ao usurio interativamente buscar, imprimir e mudar dados nesta base, este programa deve ser capaz de ler dados do arquivo e tambm gravar dados no mesmo arquivo. No restante desta seo discutiremos como arquivos de texto so manipulados em C . Como ser visto, tudo ocorre de maneira anloga ao que acontece com entrada e sada padro.
24.1
C visualiza cada arquivo simplesmente como um stream seqencial de bytes. Da mesma forma que uma string em C termina com o caracter nulo, \0, cada arquivo em C termina com um marcador de nal de arquivo (end-of-le), EOF. As regras para acessar um arquivo so simples. Antes que um arquivo seja lido ou gravado, ele aberto. Um arquivo aberto em um modo que descreve como o arquivo ser usado (por exemplo, para leitura, gravao ou ambos). Um arquivo aberto pode ser processado por funes da biblioteca padro em C . Estas funes so similares s funes de biblioteca que lem e escrevem de/para entrada/sada padro. Quando um arquivo no mais necessrio ele deve ser fechado. Ao nal da execuo de um programa todos os arquivos abertos so automaticamente fechados. Existe um nmero mximo de arquivos que podem ser simultaneamente abertos de forma que voc deve tentar fechar arquivos quando voc no precisa mais deles. Quando um arquivo est aberto, um stream associado ao arquivo. Este stream fornece um canal de comunicao entre um arquivo e o programa. Trs arquivos e seus respectivos streams so abertos automaticamente quando um programa inicia sua execuo: a entrada padro, a sada padro e a sada padro de erros. A funo da biblioteca padro fopen() usada para abrir um arquivo. fopen() toma dois argumentos do tipo string: o primeiro argumento o nome do arquivo (por exemplo data.txt), o segundo argumento a indicao do modo no qual o arquivo deve ser aberto. fopen() negocia com o sistema operacional e retorna um ponteiro para um tipo estrutura especial FILE. Este ponteiro chamado le pointer, e aponta para uma estrutura que contm informaes de sistema sobre o arquivo. O tipo FILE predenido em <stdio.h>. O le pointer usado pelas funes de biblioteca que processam o arquivo aberto, e "representa"o arquivo do momento em que aberto at o momento em que fechado. A estrutura FILE referida como le control block (FCB). Cada arquivo possui um FCB correspondente no disco. Quando um arquivo aberto sua FCB copiada para a memria e um ponteiro denido para l. O processamento do arquivo usa o ponteiro para o FCB para manipular arquivos, de forma que o tipo do ponteiro FILE *. A sada padro, a entrada padro e a sada padro de erros so manipulados usando ponteiros pr-denidos chamados stdout, stdin e stderr respectivamente. O usurio no necessita saber detalhes de como a transferncia de dados entre programa e arquivo feita. As nicas sentenas necessrias no programa so a denio de uma varivel do tipo FILE * (um le pointer) e uma atribuio de um valor para aquela varivel por fopen(). As sentenas abaixo do um exemplo de como abrir o arquivo data.txt para leitura. FILE *fp; fp = fopen("data.txt", "r"); O prottipo da funo fopen() : FILE *fopen(char *name, char *mode); 113
fopen() recebe dois argumentos: o primeiro uma string que um nome de um arquivo a ser aberto, e o segundo uma string que representa o modo de abertura do arquivo: r indica que o arquivo ser aberto apenas para leitura, w, para escrita apenas. Se o arquivo no existe e aberto para escrita, fopen() cria o arquivo. Se um arquivo j existente aberto para escrita, o seu contedo descartado. H outros modos, incluindo anexao a um arquivo, leitura e escrita simultnea; para mais detalhes, veja a documentao da funo nos livros-texto ou no manual on-line. Se um arquivo aberto com sucesso, o endereo da estrutura FILE retornado por fopen(). Se a tentativa de abertura resulta em erro, fopen() retorna um ponteiro nulo, NULL. Alguns dos error possveis so: abrir um arquivo que no existe para leitura, abrir um arquivo para escrita quando no h mais espao disponvel em disco, ou abrir um arquivo para qualquer operao sendo que as permisses de acesso do arquivo no o permitem. recomendvel que voc teste o valor de retorno de fopen() para vericar se houve erro de abertura. O trecho de programa abaixo ilustra como faz-lo: #include <stdio.h> .... FILE *fp; char fnome[13]; char fmodo[3]; printf("Entre um nome de arquivo para abrir:"); scanf("%s", fnome); printf("Entre o modo de abertura do arquivo:"); scanf("%s", fmodo); fp = fopen( fnome, fmodo ); if (fp == NULL) { printf("Erro na abertura de %s no modo %s\n", fnome, fmodo); return ; } else printf("Arquivo %s aberto com sucesso no modo %s\n", fnome, fmodo); ... No exemplo acima se o arquivo no puder ser aberto com sucesso, uma mensagem apropriada exibida na sada padro e o programa termina. Caso contrrio uma mensagem indicando o sucesso na abertura do arquivo exibida e o programa continua sua execuo. Cada arquivo aberto possui seu prprio le pointer. Por exemplo, se um programa vai manipular dois arquivos diferentes arq1 and arq2 simultaneamente (um para leitura e outro para escrita), dois le pointers devem ser usados: FILE *fp1, *fp2; fp1 = fopen("arq1", "r"); fp2 = fopen("arq2", "w"); Os valores de le pointer (FILE *) so chamados streams. Eles estabelecem conexo entre o programa e o arquivo aberto. A partir do momento de abertura, o nome do arquivo irrelevante para o programa. Todas as funes que operam sobre o arquivo usam o le pointer associado. 114
Terminada a manipulao do arquivo o programa deve fechar o arquivo. A funo padro fclose() usada com este propsito. Ela quebra a conexo entre o le pointer e o arquivo. Esta funo toma como argumento o le pointer que representa o arquivo a ser fechado. le to be closed. O prottipo de fclose() : int fclose(FILE *); fclose() retorna 0, se h sucesso ou EOF em caso contrrio. Abaixo um exemplo de uso de fclose(): fclose(pf1); fclose(pf2);
24.2
Arquivos podem guardar duas categorias bsicas de dados: texto (caracteres no universo ASCII) ou binrio (como dados armazenados em memria ou dados que representam uma imagem JPEG). Depois que um arquivo de texto aberto, existem 3 formas diferentes de ler ou escrever sequencialmente os dados: (i) um caracter por vez, usando as funes da biblioteca padro fgetc() e fputc(); (ii) uma linha (string) por vez, usando fgets() e fputs(); e (iii) em um formato especco, usando fscanf() e fprintf(). Arquivos binrios podem ser lidos como registros de dados estruturados. Alm disso, uma vez que todos os registros tem o mesmo tamanho, os dados podem ser acessados de forma no-sequencial (acesso aleatrio). As funes usadas para isto so fwrite() e fread(). Outras funes de entrada e sada de mais baixo nvel que podem ser usadas so as funes read() e write(). Estas funes no sero usadas no momento e geralmente somente programadores experientes as usam. 24.2.1 Entrada e sada de caracteres
As funes fgetc() e putc() so similares a getchar() e putchar(). Elas operam sobre um arquivo aberto cujo le pointer passado como argumento. Os prottipos de fgetc() e fputc() are int fgetc(FILE *fp); int fputc(char ch, FILE *fp); getc() returns the next character read from the le represented by the stream fp, or EOF if error or end of le occurs. putc() writes the character ch in the le represented by the stream fp. It returns the character written or EOF. Abaixo segue um exemplo de programa que l um arquivo caracter a caracter e imprime o que foi lido na sada padro (a tela do computador): /************************************************************************ * L um caracter por vez de um arquivo e * o imprime na sada padro ************************************************************************/ #include <stdio.h> /* para funes padro de E/S */ main() { FILE *fp; char fnome[13]; int ch; /* dialogo com usurio */ 115
printf("Entre um nome de arquivo: "); scanf("%s", fnome); fp = fopen( fnome, "r" ); /* abre arquivo*/ if (fp == NULL) { printf("Erro ao abrir %s\n", fnome); return; } else { printf("Arquivo aberto com sucesso.\n"); /* L o arquivo caracter a caracter e imprime em stdout (sada padro) */ while( (ch=fgetc(fp)) != EOF ) printf("%c", ch); fclose(fp); /* fecha arquivo */ } } 24.2.2 Entrada e sada de strings
As funes fgets() and fputs() so similares a gets() e puts(). Elas operam sobre um arquivo aberto cujo le pointer passado como argumento. Os prottipos de fgets() e fputs() so: char *fgets(char *str, int n, FILE *fp); int fputs(char *str, FILE *fp); A funo fgets() l do arquivo conectado ao stream fp no mximo (n - 1) caracteres para o array str, parando a leitura se o caracter \n (uma mudana de linha) encontrado; O caracter \n includo no array e ao elemento do array seguinte atribudo \0 (nal de string). A funo fgets() retorna str ou o ponteiro nulo, NULL, se um erro ou nal de arquivo ocorre. A funo fputs() escreve no arquivo conectado ao stream fp a string str, retornando um nmero no-negativo, ou EOF em caso de erro. No exemplo a seguir usada a funo fgets() (e no gets(). Voc pode dizer por qu?) para salvar em um arquivo um texto digitado atravs da entrada padro (stdin). Para sinalizar pelo teclado que voc terminou de entrar o texto, deve-se teclar ^D (^Z Turbo C ) e ento a tecla ENTER. /************************************************************************ * Escreve texto digitado em stdin em um arquivo ***********************************************************************/ #include <stdio.h> /* funes padro de E/S */ main() { FILE *fp; char fnome[13]; char linha[81]; /* dialogo com usuario */ 116
printf("Entre um nome de arquivo: "); scanf("%s", fnome); fp = fopen( fnome, "w" ); /* abre arquivo. Contedo anterior perdido.*/ if (fp == NULL) { printf("Erro ao abrir %s\n", fnome); return; } else { printf("Arquivo aberto com sucesso"); /* l linha do teclado, armazena em uma string, * salva string em arquivo */ while( fgets(linha, 80, stdin ) != NULL) fputs(linha, fp); fclose(fp); /* fecha arquivo */ } } 24.2.3 Entrada e sada formatada: fscanf(), fprintf()
Para entrada e sada formatada as funes padro fscanf() e fprintf() podem ser usadas. Elas so idnticas s funes scanf() e printf(), exceto que elas tm um argumento adicional (o primeiro em sua lista de argumentos) que o stream conectado ao arquivo a ser lido ou escrito. Informalmente, seus prottipos podem ser escritos como: int fscanf( FILE *fp, char *format, arg1, arg2, ... ); int fprintf(FILE *fp, char *format, arg1, arg2, ... ); A funo fscanf() l do arquivo representado pelo stream fp sob controle de um string de formato format. O string de formato geralmente contm converses (como %d, %s, %f) que dirigem a interpretao da entrada. Os valores convertidos so atribudos para os argumentos subsequentes, cada qual devendo ser um ponteiro. A funo fscanf() retorna quando o string de formato foi totalmente interpretado. O valor retornado por fscanf() EOF se o nal do arquivo foi atingido ou um erro ocorre, caso contrrio retorna a quantidade de itens convertidos e atribudos. A funo fprintf() escreve no arquivo conectado ao stream fp sob controle de um string de formato format. O string de format contm dois tipos de objetos: caracteres ordinrios que so copiados do jeito que so, e especicadores de converso que causam a converso e impresso dos argumentos seguintes de fprintf(). O valor de retorno o nmero de caracteres escritos, ou negativo em caso de ocorrncia de erros. Abaixo segue um exemplo simples de base de dados. Os dados so armazenados permanentemente em um arquivo, agentes.txt. A base de dados contm registros de agentes secretos famosos. Para cada agente um apelido e um nmero de cdigo so armazenados. Uma vez que ser usada e/s formatada, deve-se conhecer o formato no qual os dados esto armazenados no arquivo da base de dados. Este formato : os dados de diferentes agentes esto em linhas separadas; para cada agente em uma linha, tem-se primeiro o apelido e ento o cdigo numrico, separados por espao. O programa orientado a menu abaixo lista todos os registros e adiciona novos itens. /**************************************************************** * programa com menu para operar uma base de dados de no mximo 50 117
* agentes secretos; a base de dados guardada permanentemente em * um arquivo em disco. ****************************************************************/ #include <stdio.h> #define FNOME "agentes.txt" #define NUM 50 #define NOMELEN 30 /* funes padro de E/S */ /* nome do arquivo de dados */ /* numero de registros na base de dados */ /* tamanho de um nome */
/*** declara estrutura de dados ***/ struct pessoal { char nome [NOMELEN]; /* nome codigo(sem espaos em branco) */ int agnum ; /* numero codigo */ }; /*** int int void void prototipos ***/ cargadb(struct pessoal []); novonome(struct pessoal [], int); listatudo(struct pessoal [], int); salvadb(struct pessoal [], int);
118
/****** MAIN *********/ main() { struct pessoal agentes[50]; int n; char ch;
/*** carrega a base de dados em agentes[], n o tamanho da base de dados n = loaddb(agentes); /* seleciona uma opo do menu e processa os dados em memria */ do { printf("\nDigite e para entrar novo agente,"); printf("\n l para listar todos os agentes,"); printf("\n q para terminar: "); ch = getchar(); switch (ch) { case e: n = novonome(agentes, n); /* adiciona um novo agente no indice n break; case l: /* lista todos os registros */ listatudo(agentes, n); break; case q: /* salva todos os registros */ salvadb(agentes, n); break; default: /* Engano do usuario */ printf("\nEntre somente as opes listadas.\n"); } while (fgetc(stdin) != \n) ; } while (ch != q); }
Uma amostra de uma execuo do programa segue abaixo. Inicialmente o contedo do arquivo agentes.txt : Klara 89 Edward 888 ZipZap 109
119
Uma amostra de execuo: Digite e para entrar novo agente, l para listar todos os agentes, q para terminar: l Klara 89 Edward 888 ZipZap 109 Digite e para entrar novo agente, l para listar todos os agentes, q para terminar: e Digite nome e cdigo: TipTop 999 Digite e para entrar novo agente, l para listar todos os agentes, q para terminar: l Klara 89 Edward 888 ZipZap 109 TipTop 999 Digite e para entrar novo agente, l para listar todos os agentes, q para terminar: q Salvar? (s para salvar) y Salvando...Feito A seguir, apresenta-se a implementao das quatro funes cargadb(), novonome(), listatudo(), and salvadb(). /*********************************************************************** * l a base de dados do arquivo (at EOF) no array em memria * ENTRADA: um array de do tipo struct pessoal * RETORNO: nmero de elementos lidos * SUPOSIES: o tamanho da base de dados deve ter no mximo 50 registros ***********************************************************************/ int cargadb(struct pessoal pessoa[]) { int i = 0; FILE *fp; /* define ptr to FILE */ fp = fopen(FNOME, "r"); while ( fscanf(fp, "%s %d", pessoa[i].nome, &pessoa[i].agnum) != EOF ) i++; fclose(fp); return i; }
120
/************************************************************************* * adiciona novo elemento ao indice n no array pessoa[], * o valo da estrutura obtido da entrada padro * ENTRADA: array pessoa[] -- to store the structure value n -- indice do elemento, incrementado a * cada novo elemento * *************************************************************************/ int novonome(struct pessoal pessoa[], int n) { if (n < NUM) { printf("Digite nome e cdigo: "); scanf("%s %d", pessoa[n].nome, &pessoa[n].agnum); n++; } else printf("No h mais espao\n"); return n; } /************************************************************************* * imprime a base de dados na tela * ENTRADA: array pessoa[] a imprimir n nmero de registros para imprimir * *************************************************************************/ void listatudo(struct pessoal pessoa[], int n) { int j; for (j = 0; j < n; j++) { printf("%s %d\n", pessoa[j].nome, pessoa[j].agnum); } } /************************************************************************** * Pergunta ao usurio se quer salvar a base de dados. Se a resposta SIM (s) * abre o arquivo para escrita e grava o array no arquivo * ENTRADA: array pessoa[] a ser salvo n nmero de registros a ser salvo * **************************************************************************/ void salvadb(struct pessoal pessoa[], int n) { int i; FILE *fp; while (fgetc(stdin) != \n) ; printf("Salvar? (s para salvar)\n"); if ( getchar() == s) { 121
fp = fopen(FNOME, "w"); printf("Salvando..."); for (i = 0; i < n; i++) { fprintf(fp, "%s %d\n", pessoa[i].nome, pessoa[i].agnum); } fclose(fp); printf("Feito.\n"); } else printf("Alteraes no foram salvas.\n"); }
122
25
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 <stdio.h> int main(void) { char arr1[] = {c,i,2,0,8}; int i; for (i = 4; i >= 0; -= 1i) printf("%c", 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 printf() e scanf() (constantes do tipo string esto sempre entre aspas - "): printf("Entre com a nota para o estudante 2: "); scanf("%d", &gr2);
26
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";
123
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).
26.1
Strings podem ser impressos usando printf() com o especicador de formato %s. Por exemplo: int main() { char mensagem[] = "tchau"; printf("ola\n%s\n", mensagem); } A sada deste programa : ola tchau A funo puts() simplesmente imprime um string e depois pula de linha. Nenhuma opo de formatao pode ser denida. A funo puts() somente pega um string como argumento e o imprime. O programa abaixo tem a mesma sada que o programa anterior. int main() { char mensagem[] = "tchau"; puts("ola"); puts(mensagem); }
26.2
A funo gets() l uma linha de texto digitado no teclado e a armazena em um string. Veja o exemplo abaixo: #include <stdio.h> int main(void) { char nome[100]; printf("Entre seu nome: "); gets(nome); printf("Oi, %s.\n", nome); } 124
Exemplo de execuo Entre seu nome: Jose Silva Oi, Jose Silva. Passando um nome de array para a funo gets(), como ilustrado no programa acima, coloca a linha inteira digitada pelo usurio no array nome (tudo at que seja teclado enter). Note que se o usurio digitar caracteres demais (neste caso, mais de 99 caracteres), isso causar um erro de acesso fora dos limites (que pode ser PERIGOSO !!) A funo scanf() pode ser usada de maneira similar. A nica diferena que o scanf() l somente a primeira palavra (tudo at que de digite um separador um espao em branco, tabulao, ou enter). Alm disso, como estamos passando um array como argumento para o scanf(), O & QUE GERALMENTE PRECEDE O ARGUMENTO NO DEVE ESTAR PRESENTE. #include <stdio.h> int main() { char nome[100]; printf("Entre seu nome: "); scanf("%s", nome); printf("Oi, %s.\n", nome); } Exemplo de execuo Entre seu nome: Jose Silva Oi, Jose. Note que somente o primeiro nome lido pelo scanf() porque a funo pra no primeiro espao em branco que encontra (enquanto gets() pra quando encontra um enter).
26.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 <stdio.h> #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; 125
A sada deste programa : Jose Silva Maria Silva Antonio dos Santos Pedro dos Santos Joao da Silva
26.4
Funes de String
H funes para manipulao de string j denidas na biblioteca padro C chamada string.h. 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 <string.h> no incio do seu programa. O objetivo desta seo mostrar como estas funes poderiam ser implementadas como exemplos de programas de manipulao de strings. 26.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 <stdio.h> #include <string.h> int main(void) { char nome[100]; int comprimento; printf("Entre seu nome: "); gets(nome); comprimento = strlen(nome); printf("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; 126 %d caracteres.\n", comprimento);
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. 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 <stdio.h> #include <string.h> int main(void) { char palavra1[100], palavra2[100]; int resultado; printf("entre com uma palavra: "); gets(palavra1); printf("entre outra palavra: "); gets(palavra2); resultado = strcmp(palavra1, palavra2); if (resultado == 0) printf("igual\n"); else if (resultado > 0) printf("o primeiro e maior\n"); 127
else printf("o segundo e maior\n"); } Aqui est um exemplo de como a funo strcmp() poderia ser implementada. int strcmp( char s1[], char s2[] ) { int i = 0; while (1) { 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. 26.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 <stdio.h> #include <string.h> int main(void) { char pal[100], palCopia[100]; printf("entre com uma palavra: "); 128
gets(pal); strcpy(palCopia, pal); printf("entre outra palavra: "); gets(pal); printf("voce entrou primeiro: %s\n", palCopia); } 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: void strcpy( char s1[], char s2[] ) { int i = 0; while ( s2[i] != NULL ) { s1[i] = s2[i]; ++i; } s1[i] = s2[i]; }
129
27
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).
27.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.
27.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; 130
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.
27.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) { printf("%d\n", fac1.num); } Tentar acessar um nome de membro que no existe causa um erro de compilao.
27.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 ponteiro 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; }; main() { 131
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 ponteiros constantes). int a[5], b[5]; /* Est errado -- No ir compilar */ a = b;
27.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; }; main() { struct facil fac1 = { 3, C }, fac2; fac2 = fac1; } Uma lista de valores separados por vrgula ca entre chaves ({ and }). Os valores de inicializao devem estar na mesma ordem dos membros na declarao da estrutura.
27.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: #define LEN 50 struct endereco { char rua[LEN]; char cidade_estado_cep[LEN]; }; struct endereco obtem_endereco(void); void imprime_endereco(struct endereco); 132
struct endereco obtem_endereco(void) { struct endereco ender; printf("\t Entre rua: "); gets(ender.rua); printf("\t Entre cidade/estado/cep: "); gets(ender.cidade_estado_cep); return ender; } void imprime_endereco(struct endereco ender) { printf("\t %s\n", ender.rua); printf("\t %s\n", ender.cidade_estado_cep); } main() { struct endereco residencia; printf("Entre seu endereco residencial:\n"); residencia = obtem_endereco(); printf("\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 gets() 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; printf("Entre valor: "); scanf("%d", &i); return i; } 133
void imprime_int(int i) { printf("%d\n", i); } main() { int valor; valor = obtem_int(); printf("\nSeu valor:\n"); imprime_int(valor); }
27.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) { printf("Entre rua: "); gets(aender[index].rua); printf("Entre cidade/estado/cep: "); gets(aender[index].cidade_estado_cep); } void imprime_endereco(struct endereco ender) { printf("%s\n", ender.rua); printf("%s\n", ender.cidade_estado_cep); } main() { struct endereco residencias[NUM]; int i; 134
for (i = 0; i < NUM; i++) { printf("Entre o endereco da pessoa %d:\n", i); obtem_endereco(residencias,i); } for (i = 0; i < NUM; i++) { printf("endereco da pessoa %d:\n", i); 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: printf("%s\n", residencias[0].rua); printf("%s\n", residencias[0].cidade_estado_cep);
27.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 pessoa.escola.rua pessoa.escola.cidade_estado_cep Note o uso repetido de . quando se acessa membros dentro de membros. 135
28
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.
28.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
28.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 *)
136
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).
28.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).
28.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: 137
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.
28.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.
28.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: 138
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 */
28.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; }; 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 } 139
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 6), os parenteses so necessrios. 28.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.
28.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 <stdio.h> void troca(int, int); void troca(int x, int y) { int temp; temp = x; x = y; y = temp; } main(void) { int a, b; printf("Entre dois numeros: "); scanf("%d %d", &a, &b); printf("Voce entrou com %d e %d\n", a, b); 140
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 <stdio.h> void troca(int *, int *); /* function troca(px, py) troca os valores inteiros apontados por px e py * acao: entrada: apontadores px e py * valor de *px e *py trocados * saida: suposicoes: px e py sao apontadores validos * primeiro guarda o primeiro valor em um temporario e troca * algoritmo: */ void troca(int *px, int *py) { int temp; temp = *px; *px = *py; *py = temp; } main(void) { int a, b; printf("Entre dois numeros: "); scanf("%d %d", &a, &b); printf("Voce entrou com %d e %d\n", a, b); 141
/* Troca a e b -- passa enderecos */ troca(&a, &b); printf("Trocados, eles sao %d e %d\n", a, b); }
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. 28.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 []). 142
28.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); } 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 143
ponteiros ao invs de toda uma estrutura que pode ser particularmente grande, implicando em um tempo nal de processamento maior.
28.9
Precedncia de operadores
A precedncia dos operadores * e & alta, a mesma que outros operadores unrios. A tabela 6 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
+=
-=
*=
/=
%=
144
29
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";
30 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).
31
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[]) { ..... } 145
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 3.
32
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.
146
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.
32.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)); 147
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.
32.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.
148
Referncias
[1] Allen I. Holub. Enough Rope to Shoot Yourself in the Foot: rules for C and C++ programming. McGraw-Hill, 1995. [2] Brian W. Kernighan and Dennis M. Ritchie. A Linguagem de Programao C. Editora Campus, 1986. [3] Victorine Viviane Mizrahi. Treinamento em Linguagem C. Prentice-Hall, Inc., 2a edition, 2008. [4] H. Schildt. C Completo e Total. Makron Books, 3a edition, 1997.
149