Professional Documents
Culture Documents
C Completo e Total PDF
C Completo e Total PDF
#include# define SIZ 50Cap. 5 Ponteiros 119 void push(int int pop(void): int *tos, *pl, stack({SIZE]; void main(void) { int value; tos = stack; /* faz tos conter o topo da pilha */ pl = stack; /* inicializa pl */ do ( printf(*Digite o valor: "); scanf("%d", &value); if(value!=0) push(value); else printf ("valor do topo é #d\n", pop()); } while(value!=-1); ) void push(int i) c pitt; if (pl==(tos+SIZE)) ( print£("Estouro da pilha"); exit (1); *pl = ij } pop (void) ( if(pl==tos) ( £("Estouro da pilha"); pl--; return *(pi+l); } Vocé pode ver que a meméria para a pilha é fornecida pela matriz stack. O ponteiro p1 ¢ ajustado para apontar para o primeiro byte em stack. A pilha é realmente acessada pela varidvel p1. A varidvel tos contém o endereco do topo da pilha. O valor de tos evita que se retirem elementos da pilha vazia. Uma vez120 C— Completore Total Cap. 5 que a pilha tenha sido inicializada, push0) e pop) podem ser usadas como uma pilha de inteiros. Tanto push) como pop() realizam um teste relacional com o ponteiro p1 para detectar erros de limite. Em push0), p1 é testado junto ao final da pilha adicionando-se SIZE (0 tamanho da pilha) a tos. Em pop0, p1 é veri- ficado junto a tos para assegurar que nao se retirem elementos da pilha vazia. Em pop0, os parénteses sio necessdrios no comando return. Sem eles, © comando seria BW return «pi + que retornaria o valor da posicao pl mais um, nao o valor da posicéo p1+1. Vocé deve usar parénteses para garantir a ordem correta de avaliagao quando usar ponteiros. I Ponteiros e Matrizes Ha uma estreita relagao entre ponteiros e matrizes. Considere este fragmento de programa: char str(80], *pl; pl = str; Aqui, pil foi inicializado com 0 endereco do primeiro elemento da mattiz str. Para acessar 0 quinto elemento em str, teria de ser escrito @ seria ou B *ipi+4) Os dois comandos devolvem o quinto elemento. Lembre-se de que matrizes co- mecam em 0, assim, deve-se usar 4 para indexar str. Também deve-se adicionar 4 ao ponteiro p1 para acessar 0 quinto elemento porque p1 aponta atualmente para o primeiro elemento de str. (Recorde-se que um nome de uma matriz sem um indice retorna o endere¢o inicial da matriz, que é o primeiro elemento.) C fornece dois métodos para acessar elementos de matrizes: aritmética de ponteiros e indexagao de matrizes. Aritmética de ponteiros pode ser mais répida que indexacao de matrizes. J4 que velocidade 6 geralmente uma conside- ragdo em programagao, programadores em C normalmente usam ponteiros para acessar elementos de matrizes.Cap. 5 Ponteiros 121 Essas duas versdes de putstr() — uma com indexagao de matrizes e uma com ponteiros — ilustram como vocé pode usar ponteiros em lugar de indexacao de matrizes. A funcao putstr() escreve uma string no dispositivo de saida padrao. /* Indexa s como uma matriz. */ void puts(char *s) t register int t; for(t=0; s[t}; +#t) putchar(s[tl); /* Acessa s como um ponteiro. */ void putstr(char *s) { while(*s) putchar(*s++); } A maioria dos programadores profissionais em C acharia a segunda versao mais facil de ler e entender. Na realidade, a versao com ponteiros €a forma pela qual rotinas desse tipo sio normalmente escritas em C. Matrizes de Ponteiros Ponteiros podem ser organizados em matrizes como qualquer outro tipo de dado. A declaracao de uma matriz de ponteiros int, de tamanho 10, 6 BH oince *x(10); Para atribuir o endereco de uma varidvel inteira, chamada var, ao terceiro ele- mento da matriz de ponteiros, deve-se escrever @ xi2) = evar; Para encontrar o valor de var, escreve-se B osx2) Se for necessario passar uma matriz de ponteiros para uma funcdo, pode ser usado 0 mesmo método que é utilizado para passar outras matrizes — simples- mente chame a fungao com o nome da matriz sem qualquer indice. Por exemplo, uma funcao que recebe a matriz x se parece com isto:12 C— Complete e Total Cop. 5 void display_array(int *q[]) t int ty for (t=0; t<10; tt+) printf("td ", *altl); Lembre-se de que q nao é um ponteiro para inteiros; q é um ponteito para uma matriz de ponteiros para inteiros. Portanto, 6 necessario declarar 0 parametro q como uma matriz de ponteiros para inteiros, como mostrado no cédigo anterior. Ela nao pode ser simplesmente declarada como um ponteiro para inteiros, porque nao é isso 0 que ela é. Matrizes de ponteiros sdo usadas normalmente como ponteiros para strings. Voeé pode criar uma funcao que exiba uma mensagem de erro, quando é dado seu ntimero de cédigo, como mostrado aqui: void syntax_error(int num) static char ‘eri “Arquivo nao pode ser aberto\n", “Erro de leitura\n", “Erro de escrita\n", Falha da midia\n" Mf printé(*"%s", ery (num]); ) A matriz err contém ponteiros para cada string. Como vocé pode ver, printf) dentro de syntax_error() é chamada com um ponteiro de caracteres que aponta para uma das varias mensagens de erro indexadas pelo nimero de erro passado para a funcio. Por exemplo, se for passado o valor 2, a mensagem Erro de escrita € apresentada. Observe que o argumento da linha de comandos argv é uma matriz de ponteiros a caracteres. (Veja o Capitulo 6) a Indirega@o Multipla Vocé pode ter um ponteiro apontando para outro ponteiro que aponta para 0 valor final. Essa situagdo é chamada indirec@o muiltipla, ou ponteiros para ponteiros.Cap. 5 Ponteiros 123 Ponteiros para ponteiros podem causar confusdo. A Figura 5.3 ajuda a esclarecer © conceito de indirecao miltipla. Como vocé pode ver, o valor de um ponteiro normal é 0 enderego de uma varidvel que contém o valor desejado. No caso de um ponteiro para um ponteiro, o primeiro ponteiro contém o endereco do se- gundo, que aponta para a varidvel que contém o valor desejado. Ponteiro Variavel enderego, |---| valor | Indirecao Simples Ponteiro Ponteiro: Variavel Indirecdo Maltipla Figura 5.3 Indirecio simples e miltipla. A indiregéo miiltipla pode ser levada a qualquer dimensao desejada, mas raramente é necessario mais de um ponteiro para um ponteiro. De fato, indiregao excessiva é dificil de seguir e propensa a erros conceituais. (Nao con- funda indireg4o miltipla com listas encadeadas.) 4 NOTA: Nao confunda a miltipla indirecio com estruturas de dados de alto nivel, void main(void) int x, *p, ta: x = 10; D = &X; @ = 6p; print£("%d", **q); /* imprime o valor de x */ Aqui, p é declarado como um ponteiro para um inteiro e q, como um ponteiro para um ponteiro para um inteiro. A chamada a printf() imprime 10 na tela. Hl Inicializagao de Ponteiros Apés um ponteiro ser declarado, mas antes que lhe seja atribuido um valor, ele contém um valor desconhecido. Se vocé tentar usar um ponteiro antes de Ihe dar um valor, provavelmente quebraré nao apenas seu programa como também o sistema operacional de seu computador — um tipo de erro muito desagradavel! Ha uma importante convengao que a maioria dos programadores de C segue quando trabalha com ponteiros: um ponteiro que atualmente nao aponta para um local de meméria valido recebe o valor nulo (que é zero). Por convencao, qualquer ponteiro que é nulo implica que ele ndo aponta para nada e nao deve ser usado. Porém, apenas 0 fato de um ponteiro ter um valor nulo nao o torna “seguro”. Se vocé usar um ponteiro nulo no lado esquerdo de um comando de atribuicao, ainda correré 0 risco de quebrar seu programa ou o sistema operacional. Como um ponteiro nulo é assumido como sendo nao usado, voce pode utilizar o ponteiro nulo para tornar faceis de codificar e mais eficientes muitas rotinas. Por exemplo, vocé poderia usar um ponteiro nulo para marcar o final de uma matriz de ponteiros. Uma rotina que acessa essa matriz sabe que chegara ao final ao encontrar o valor nulo. A funcao search(), mostrada aqui, ilustra esse tipo de abordagem.Cap.'5 Ponteiros 25 /* procura um nome */ search(char *p{], char ‘name) register int t; for ( p(t}; ++t) if(istremp(p[t], name)) return t; return /* n&o encontrado */ } O laco for dentro de search() é executado até que seja encontrada uma coinci- déncia ou um ponteiro nulo. Como o final da matriz é marcado com um ponteiro nulo, a condigao de controle do laco falha quando ele é atingido. E uma pratica comum entre programadores em C inicializar strings. Voce viu um exemplo disso na funcao syntax_error(), na secdo “Matrizes de Ponteiros”. Uma outra variagao no tema de inicializagao é 0 seguinte tipo de declaragao de string: Como vocé pode observar, 0 ponteiro p ndo é uma matriz. A razdo pela qual esse tipo de inicializagao funciona deve-se 4 maneira como o compilador opera. Todo compilador C cria o que é chamada de tabela de string, que é usada inter- namente pelo compilador para armazenar as constantes string usadas pelo pro- grama. Assim, 0 comando de declaracao anterior coloca o endereco de “alo mundo”, armazenado na tabela de strings no ponteiro p. p pode ser usado por todo o programa como qualquer outra string. Por exemplo, 0 programa que se- gue é perfeitamente valido: ar *p = “alo mundo #include #include char *p = "alo mundo"; void main(void) { register int t; /* imprime o contetido da string de tras para frente */ printf (p); for(t=strlen(p)-1; t>-1; t--) printf£("%e", plt]);126 C—Completo e Total Cap. 5 @ Ponteiros para Funcédes Um recurso confuso, mas poderoso de C, é 0 ponteiro para funcao. Muito embora uma fungao nao seja uma variavel, ela tem uma posicao fisica na meméria que pode ser atribuida a um ponteiro. O enderego de uma fungao é 0 ponto de entrada da fungao. Portanto, um ponteiro de funcao pode ser usado para chamar uma fungio. Para entender como funcionam os ponteiros de fungdes, vocé deve co- nhecer um pouco como uma funcao é compilada e chamada em C. Primeiro, quando cada fungio é compilada, 0 cédigo-fonte é transformado em cédigo-objeto ¢ um ponto de entrada é estabelecido. Quando ¢ feita uma chamada a funcéo, enquanto seu programa est sendo executado, é efetuada uma chamada em linguagem de maquina para esse ponto de entrada. Portanto, se um ponteiro contém o enderego do ponto de entrada de uma funcao, ele pode ser usado para chamar essa fungio. © enderego de uma fungio € obtido usando 0 nome da fungio sem parénteses ou argumentos. (Isso é semelhante 4 maneira como o endereco de uma matriz é obtido quando apenas o nome da matriz, sem indices, 6 usado.) Para ver como isso é feito, estude o programa seguinte, prestando bastante aten- cdo as declaragées: #include #include void check(char *a, char *b, int (*cmp) (const char *, const char *)); void main(void) ( char s1(80], s2[80]; int (*p)(); p = stremp; gets(s1); gets(s2); check(si, s2, p); void check(char *a, char *b, int (*cmp) (const char *, const char *)} {128 C— Completo ¢ Totad Cap. 5 #include #include #include #include "string.h" void check(char *a, char *b, int (*cemp) (const char *, const char*)); int numemp(const char *a, const char *b); void main(vo £ char s1(80], s2(801; gets (sl); gets (s2); if (isalpha(*s1)) check(sl, s2, strcmp); else check(s1, s2, numemp’ ) void check(char *a, char *b, int (*cmp) (const char *, const char*)) printé£("testando igualdade\n"); if(!(*emp) (a, b)) print£ ("igual"); else printf ("diferente"); } numemp(const char *a, const char *b) ‘ if (atoi(a)==atoi(b)) return 0; else return 1; Bas Funcées de Alocacgao Dinamica em C Ponteiros fornecem 0 suporte necessario para 0 poderoso sistema de alocacéo dinamica de C. Alocagio dinémica é 0 meio pelo qual um programa pode obter meméria enquanto est em execucao. Como vocé sabe, varidveis globais tem o armazenamento alocado em tempo de compilagao. Varidveis locais usam a pilha.Cap.5 Ponteiros 129 No entanto, nem variaveis globais nem locais podem ser acrescentadas durante © tempo de execugao. Porém, haveré momentos em que um programa precisard usar quantidades de armazenamento varidveis. Por exemplo, um processador de texto ou um banco de dados aproveita toda a RAM de um sistema. Porém, como a quantidade de RAM varia entre computadores esses programas nao poderao usar varidveis normais. Em vez disso, esses e outros programas alocam meméria, conforme necessario, usando as funcées do sistema de alocacao dinamica de C A meméria alocada pelas funcdes de alocagio dinamica de C é obtida do heap — a regido de memGria livre que esta entre seu programa e a area de arma- zenamento permanente e a pilha. Embora o tamanho do heap seja desconhecido, ele geralmente contém uma quantidade razoavelmente grande de meméria livre. © coracéo do sistema de alocagao dinamica de C consiste nas fungdes malloc() e free(. (Na verdade, C tem diversas outras fungées de alocacae dina- mica, mas essas duas so as mais importantes.) Essas fungdes operam em con- junto, usando a regiao de meméria livre para estabelecer e manter uma lista de armazenamento disponivel. A funco malloc() aloca memoria e free() a libera. Isto 6, cada vez que é feita uma solicitacdo de meméria por malloc(), uma porgio da meméria livre restante é alocada. Cada vez, que é efetuada uma chamada a free() para liberacdo de memoria, a meméria é devolvida ao sistema. Qualquer programa que use essas fungdes deve incluir o cabecalho STDLIB.H. A fungao malloc() tem este protstipo: void *malloc(size_t niimero_de_bytes); Aqui, rimero_de_bytes 6 0 numero de bytes de meméria que vocé quer alocar. (O tipo size_t é definido em STDLIB.H como — mais ou menos — um inteiro sem sinal.) A fungao malloc() devolve um ponteiro do tipo void, o que significa que vocé pode atribui-lo a qualquer tipo de ponteiro. Apés uma chamada bem-sucedida, malloc() devolve um ponteiro para o primeiro byte da regiao de meméria alocada do heap. Se nao ha meméria disponivel para satisfazer a requi- sigao de maliocQ, ocorre uma falha de alocacao e malloc() devolve um nulo. O fragmento de cédigo mostrado aqui aloca 1000 bytes de meméria. char *p; Pp = malloc(1000); /* obtém 1000 bytes */ Apés a atribuicdo, p aponta para o primeiro dos 1000 bytes de memoria livre. O préximo exemplo aloca espago para 50 inteiros. Observe o uso de sizeof para assegurar portabilidade. int *p; p = malloc (50*sizeof(int));130 C—Completo e Total Cop. 5 Como o heap nao é infinito, sempre que alocar meméria, vocé deve testar o valor devolvido por malloc(), antes de usar o ponteiro, para estar certo de que nao & nulo. Usar um ponteiro nulo quase certamente travara o computador. A maneira adequada de alocar meméria é ilustrada neste fragmento de cédigo: if(!(p=malloc(100)) { printf ("sem meméria.\ exit (1); } Obviamente, vocé pode substituir algum outro tipo de manipulador de erro em lugar do exit(). Apenas tenha certeza de nao usar o ponteiro p se ele for nulo. A funco free() 6 0 oposto de malloc(), visto que ela devolve meméria previamente alocada ao sistema. Uma vez, que a memGéria tenha sido liberada, ela pode ser reutilizada por uma chamada subseqiiente a malloc(). A fungdo free() tem este prototipo: void free(void *p); Aqui, p é um ponteiro para memoria alocada anteriormente por malloc(). E muito importante que vocé nunca use free() com um argumento invalido; isso destruiria a lista de meméria livre. O subsistema de alocagao dinamica de C é usado em conjungio com ponteiros para suportar uma variedade de construgées de programacao impor- tantes, como listas encadeadas ¢ arvores bindrias. Vocé vera diversos exemplos disso na Parte 3. Um outro uso importante de alocagéo dinaémica é a matriz dinamica, discutida a seguir. Matrizes Dinamicamente Alocadas Igumas vezes vocé teré de alocar meméria, usando malloc(), mas operar na meméria como se ela fosse uma matriz, usando indexacao de matrizes. Em es- séncia, vocé pode querer criar uma matriz dinamicamente alocada. Como qualquer ponteiro pode ser indexado como se fosse uma matriz unidimensional, isso no representa nenhum problema. Por exemplo, 0 programa seguinte mostra como vocé pode usar uma matriz alocada dinamicamente: /* Rloca espacgo para uma string dinamicamente, solicita a entrada do usudrio e, em seguida, imprime a string de trds para frente. */ #include Cap. 5 Ponteiros 131 #include #include void main(void) { char * register int t; = malloc(80); if(1s) ( printf("Falha na solicitagao de meméria.\n"); exit(h); gets(s); for(t=strlen(s)-1; t>=0; free(s putchar (s[t}); Como 0 programa mostra, antes de seu primeiro uso, s é testado para assegurar que a solicitagao de alocagao foi bem-sucedida e um ponteiro valido foi devolvido por malloc(). Isso é absolutamente necessario para evitar 0 uso acidental de um ponteiro nulo, que, como exposto anteriormente, quase certamente provocaria um problema. Observe como 0 ponteiro s é usado na chamada a gets() e, em seguida, indexado como uma matriz para imprimir a string de trds para frente. Acessar meméria alocada como se fosse uma matriz unidimensional é simples. No entanto, matrizes dinamicas multidimensionais levantam alguns pro- blemas. Como as dimens6es da matriz nao foram definidas no programa, vocé nao pode indexar diretamente um ponteiro como se ele fosse uma matriz mul- tidimensional. Para conseguir uma matriz alocada dinamicamente, vocé deve usar este truque: passar 0 ponteiro como um parametro a uma fungao. Dessa forma, a fungao pode definir as dimens6es do parametro que recebe o ponteiro, permitindo, assim, a indexagao normal de matriz. Para ver como isso funciona, estude o exemplo seguinte, que constréi uma tabela dos ntimeros de 1 a 10 ele- vados a primeira, A segunda, & terceira e A quarta poténcias: /* presenta as poténcias dos mimeros de 1 a 10. Nota: muito embora esse programa esteja correto, alguns compiladores apresentarao uma mensagem de adverténcia com relagdo aos argumentos para as fungdes table() e show(). Se is: so acontecer, ignore. */132 C= Completo « Total Cap. 5 clude #include void main(void) /*136 + C—Completo ¢ Total Cap. 5 Essa nao é uma boa forma de inicializar as matrizes first e second com os nti- meros de 0 a 19. Embora possa funcionar em alguns compiladores, sob certas circunstancias, esta-se assumindo que as duas matrizes sero colocadas uma apés a outra com first primeiro. Jsso pode nao ser sempre 0 caso. © proximo programa ilustra um tipo de erro muito perigoso. Veja se vocé é capaz de encontra-lo. /* Esse programa tem um erro. */ #include #include void main(void) € char ‘pl; ar s(80]; do { gets(s); /* 18 uma string */ /* imprime o equivalente decimal de cada caractere */ while(*pl) printf(" %d", *pi++); } while (stremp(s, "done")); ) Esse programa usa p1 para imprimir os valores ASCII associados a cada caractere contido em s. O problema é que o endereco de s é atribuido a p1 apenas uma vez. Na primeira iteracdo do lao, p1 aponta para o primeiro caractere em s. Porém, na segunda iteragao, ele continua de onde foi deixado, porque ele nao é reinicializado para o comeco de s. O préximo caractere pode ser parte de uma outra string, uma outra varidvel ou um pedaco do programa. A maneira apro- priada de escrever esse programa é /* Esse programa estd correto. */ #include #include void main (void) { nar *pl; ar s(80];Cap. 5 Ponteiros 137 do { pl gets(s); /* 1€ uma string */ /* imprime o equivalente decimal de cada caractere */ while(*pl) printf£(* 8a", *pl++); } while (strcmp(s, "done")); Aqui, cada vez que 0 laco repete, p1 ¢ ajustado para 0 inicio da string. Em geral, vocé deve lembrar-se de reinicializar um ponteiro se ele for reutilizado. O fato de manipular ponteiros incorretamente e poder provocar erros traigoeiros nao é razao para nao usa-los. Apenas seja cuidadoso e assegure-se de que vocé sabe para onde cada ponteiro esta apontando antes de usé-lo.TiaKKON ‘ete Funcgoes lS Fungées sido os blocos de construcdo de C e o local onde toda a atividade do programa ocorre. Elas sao uma das caracteristicas mais importantes de C. E A Forma Geral de uma Funcao A forma geral de uma fungao é especificador_de_tipo nome_da_fungdo(lista de pardmetros) { corpo da funcio ' O especificador_de_tipo especifica o tipo de valor que o comando return da fungao devolve, podendo ser qualquer tipo valido. Se nenhum tipo é espe- cificado, o compilador assume que a fungéo devolve um resultado inteiro. A lista de parimetros € uma lista de nomes de varidveis separados por virgulas e scus tipos associados que recebem os valores dos argumentos quando a fungao 6 chamada. Uma funggo pode nao ter parametros, neste caso a lista de parametros é vazia. No entanto, mesmo que nao existam parametros, os paténteses ainda so necessarios Nas declaragdes de variaiveis, vocé pode declarar muitas variaveis como sendo de um tipo comum, usando uma lista de nomes de varidveis separados por virgulas. Em contraposicao, todos os parametros de fungao devem incluir o tipo e o nome da variavel. Isto é, a lista de declaracéo de parametros para uma fungao tem esta forma geral: f(tipo nomevarl, tipo nomevar2, ..., tipo nomevarN) 138Cap. 6 Fungées 139 Regras de Escopo de Funcées As regras de escopo de uma linguagem sao as regras que governam se uma porgao de cédigo conhece ou tem acesso a outra porgao de cédigo ou dados. Em C, cada fungao é um bloco discreto de cédigo. Um eédigo de uma fungao € privativo aquela fungao e nao pode ser acessado por nenhum comando em uma outra fungao, exceto por meio de uma chamada a fungdo. (Por exemplo, vocé nao pode usar goto para saltar para o meio de outra funcao.) O cédigo que constitui o corpo de uma fungio é escondido do resto do programa e, a menos que use varidveis ou dados globais, ndo pode afetar ou ser afetado por outras partes do programa. Colocado de outra maneira, 0 cdigo e os dados que sio definidos internamente a wna fungao nao podem interagir com 0 cédigo ou da- dos definidos em outra fungao porque as duas funcdes tém escopos diferentes. Varidveis que so definidas internamente a uma funcao sao chamadas varidveis locais. Uma variavel local vem a existir quando ocorre a entrada da fungao e ela 6 destruida ao sair. Ou seja, varidveis locais nao podem manter seus valores entre chamadas a fungées. A tinica excecio ocorre quando a variavel é declarada com o especificador de tipo de armazenamento static. Isso faz com que 0 compilador trate a varidvel como se ela fosse uma varidvel global para fins de armazenamento, mas ainda limita seu escopo para dentro da fungao. (O Capitulo 2 aborda varidveis globais e locais em profundidade.) Em C, todas as fungées estéo no mesmo nivel de escopo. Isto é, nao é possivel definir uma funcao internamente a uma fungao. Esta é a razio de C nao ser tecnicamente uma linguagem estruturada em blocs. Hi Argumentos de Fungdes Se uma fungao usa argumentos, ela deve declarar varidveis que aceitem os va- lores dos argumentos. Essas varidveis sio chamadas de pardmetros formais da funcao. Elas se comportam como quaisquer outras varidveis locais dentro da fungao e séo criadas na entrada e destrufdas na saida. Como mostra a fungdo seguinte, a declaracao de parametros ocorre apés 0 nome da funcao: /* Devolve 1 se c é parte da is_in(char *s, char c) { while(*s) if(*s ring s; 0 caso contrério. */ ) retur:140 C—Gompleto e Total Cap. 6 else s+ return 0; } A fungao is_in@ tem dois parametros: s e c. Essa funcdo devolve 1 se 0 caractere ¢ faz parte da string s; caso contrario, ela devolve 0. Vocé deve assegurar-se de que os argumentos usados para chamar a funcdo sejam compativeis com o tipo de seus parametros. Se os tipos sao incom- pativeis, o compilador nao gera uma mensagem de erro, mas ocorrem resultados inesperados. Ao contrario de muitas outras linguagens, C é robusta e geralmente faz alguma coisa com qualquer programa sintaticamente correto, mesmo que 0 programa contenha incompatibilidades de tipos questionaveis. Por exemplo, se uma fungao espera um ponteiro mas é chamada com um valor, podem ocorrer resultados inesperados. O uso de prototipos de funges (discutidos em breve) pode ajudar a achar esses tipos de erro. Como no caso com varidveis locais, vocé pode fazer atribuigdes a para- metros formais ou usé-los em qualquer expressio C permitida. Embora essas varidveis realizem a tarefa especial de receber o valor dos argumentos passados para a fungio, vocé pode usé-las como qualquer outra varidvel local. Chamada por Valor, Chamada por Referén: Em geral, podem ser passados argumentos para sub-rotinas de duas maneiras. A primeira 6 chamada por valor. Esse método copia 0 valor de um argumento no parametro formal da sub-rotina. Assim, alterages feitas nos parametros da sub- rotina nao tém nenhum efeito nas varidveis usadas para chaméa-la. Chamada por referéncia & a segunda maneira de passar argumentos para uma sub-rotina. Nesse método, o enderego de um argumento é copiado no pa- rametro. Dentro da sub-rotina, 0 enderego é usado para acessar 0 argumento real utilizado na chamada. Isso significa que alterac6es feitas no parametro afetam a varidvel usada para chamar a rotina. Com poucas excegdes, C usa chamada por valor para passar argumentos. Em geral, isso significa que vocé nao pode alterar as varidveis usadas para cha- mar a funcao. (Vocé aprendera, mais tarde, neste capitulo, como forcar uma cha- mada por referéncia, utilizando um ponteiro para permitir alteracdes na varidvel usada na chamada.) Considere o programa seguinte: #include int sar (int x);Cap. 6 Fungoes 1 void main(void) t int t=10; print£("td %a", sar(t), t); sqr(int x) Force return (x); Neste exemplo, 0 valor do argumento para sq, 10, é copiado no parametro x. Quando a atribuigao x = x*x ocorre, apenas a vatidvel local x é modificada. A varidvel t, usada para chamar sqr(), ainda tem o valor 10. Assim, a saida é 100 10, Lembre-se de que é uma c6pia do valor do argumento que é passada para a funcao. O que ocorre dentro da fungdo nao tem efeito algum sobre a varidvel usada na chamada Criando uma Chamada por Referéncia Muito embora a convengio de C de passagem de parametros seja por valor, vocé pode criar uma chamada por referéncia passando um ponteiro para 0 argumento. Como isso faz com que 0 endereco do argumento seja passado para a funcao, vocé pode, entao, alterar 0 valor do argumento fora da funcdo. Ponteiros sao passados para as fungdes como qualquer outra varidvel Obviamente, é necessario declarar os pardmetros como do tipo ponteiro. Por exemplo, a funcao swap(), que troca os valores dos seus dois argumentos inteiros, mostra como. void swap(int *x, int ty) { int temp; temp = *x; /* salva o valor no endereco x */ +x = ty; /* pde y em x */ vy = temp; /* poe x em y */12 C— Completo ¢ Total Cap. 6 swap() é capaz de trocar os valores das duas varidveis apontadas por x e y porque sao passados seus enderecos (e ndo seus valores). Dai que, dentro da fungao, o contetido das varidveis pode ser acessado usando as operacées padrao de ponteiro. Portanto, 0 contetido das variaveis usadas para chamar a funcao € trocado. Lembre-se de que swap() (ou qualquer outra fungao que usa parametros de ponteiros) deve ser chamada com 0 endereso dos argumentos. O programa seguinte mostra a maneira correta de chamar swap(): void swap(int *x, int ty); void main(void) { int i, j; i= 10; 20; swap(&i, &j); /* passa os enderegos de ie j */ } Neste exemplo, ¢ atribuido 10 a varidvel i e 20 a varidvel j. Em seguida, swap0) & chamada com os enderegos de i e j. (O operador undrio & é usado para pro- duzir 0 endereco das varidveis.) Assim, os enderegos de i e j, ndo seus valores, sao passados para a funcgao swap(). Chamando Fungées com Matrizes Matrizes so examinadas em detalhes no Capitulo 4. No entanto, esta seco dis cute a operacao de passagem de matrizes, como argumentos, para funcées, por- que € uma excegao & convengao de passagem de parametros com chamada por valor. Quando uma matriz é usada como um argumento para uma funcdo, apenas o endereco da matriz é passado, nao uma cépia da matriz inteira. Quando vocé chama uma fungao com um nome de matriz, um ponteiro para o primeiro elemento na matriz é passado para a funcao. (Nao se esqueca: em C, um nome de matriz sem qualquer indice é um ponteiro para o primeiro elemento na ma- triz.) Isso significa que a declaragao de parametros deve ser de um tipo de pon- teiro compativel. Existem trés maneiras de declarar um parametro que recebera um ponteiro para matriz, Primeiro, ele pode ser declarado como uma matriz, conforme mostrado aqui:Cap. 6 Fungdes 143 /* Imprime alguns niimeros. */ #include void display(int num[10]); void main(void) int t{10], for(i=0; i void display(int num); void main(void) { int t{10], i: for ( for ( ++i) tlil=ds i++) display(tlil); } void display(int num) { printf ("® } num) ; Como vocé pode ver, 0 parametro para display( é do tipo int. Nao é relevante que display() seja chamada usando um elemento de matriz, pois apenas um valor da matriz é usado. E importante entender que, quando uma matriz 6 usada como um ar- gumento para uma fungao, seu endereco é passado para a funcéo. Isso é uma excegao a convengio de C no que diz respeito a passar parametros. Nesse caso,Cap. 6 Fungdes 5 © cédigo dentro da fungdo ests operando com, e potencialmente alterando, 0 contettdo real da matriz usada para chamar a funcao. Por exemplo, considere a fungao print_upper(), que imprime seu argumento string em maiisculas: #include #include void print_upper(char *string); void main(void) { char s[80); gets(s); print_upper(s); /* Imprime uma string em maitsculas. */ void print_upper(char *string) £ register int t; for(t=0; string[t]; ++t) { string(t] = toupper(string[t]); putchar (string(t]); } Apés a chamada a print_upper0, 0 contetido da matriz s em main() estaré al- terado para maitisculas. Se ndo é isso o que vocé quer, 0 programa poderia ser escrito dessa forma: #include #include void print_upper(char *string); void main(void) ( char s(80]; gets(s); print_upper|s);146 C— Completo e Total void print_upper(char *string) { register int t for(t string[t]; ++t) putchar (toupper (stringft])}; } Nesta versao, 0 contetido da matriz s permanece inalterado, porque seus valores nao sao modificados. A funcao gets() da biblioteca padrao é um exemplo classico de passagem de matrizes para funcées. A funcao gets() da biblioteca padrao € mais sofisticada e complexa. Porém, a funcao mais simples xgets(), dada a seguir, da uma idéia de como ela funciona. /* Uma versdo muito simples da fungdo gets() da biblioteca padrao */ char *xgets(char *s) char ch, *p; int t; P /* gets () devolve um ponteiro para = */ for(t=0; t 0) t--7 break; default: s(t} = chy } ) s(80] = '"\0"; return p;Cap. 6 Fungées 17 A fungao xgets() deve ser chamada com um ponteiro para caractere, que pode ser uma variavel declarada como um ponteiro para caractere ou o nome de uma matriz de caracteres, que, por definigao, ¢ um ponteiro de caractere. Na entrada, xgets() estabelece um laco for de 0 a 80. Isso evita que strings maiores sejam inseridas pelo teclado. Se mais de 80 caracteres forem inseridos, a fungao retorna. (A fungao gets0 real nao tem essa restrigdo.) Como C nao tem verificagao interna de limites, vocé deve assegurar-se de que qualquer varidvel utilizada para cha- mar xgets() pode aceitar pelo menos 80 caracteres. Ao digitar caracteres no te- clado, eles sao colocados na string. Se vocé pressionar a tecla de retrocesso, 0 contador t 6 reduzido em 1. Quando vocé pressiona ENTER, um caractere nulo é colocado no final da string, sinalizando sua terminagao. Como a matriz usada para chamar xgets() 6 modificada, ao retornar ela contém os caracteres digitados. | argc e argv — Argumentos para main() Algumas vezes é titil passar informagdes para um programa quando 0 executa- mos. Geralmente, vocé passa informagées para a fungao main() via argumentos da linha de comando. Um argumento da linha de comando & a informacao que segue 0 nome do programa na linha de comando do sistema operacional. Por exemplo, quando compila programas em C, vocé digita algo apés aviso de co- mando na tela semelhante a: cc nome_programa onde nome_programa & 0 programa que voce deseja compilar. O nome do pro- grama é passado para o compilador C como um argumento. Ha dois argumentos internos especiais, argc e argv, que sio usados para receber os argumentos da linha de comando. O parametro argc contém 0 ntimero de argumentos da linha de comando e é um inteiro. Ele é sempre pelo menos 1 porque © nome do programa é qualificado come primeiro argumento. O para- metro argv é um ponteiro para uma matriz de ponteiros para caractere. Cada elemento nessa matriz aponta para um argumento da linha de comando. Todos os argumentos da linha de comando sao strings — quaisquer nimeros terdo de ser convertidos pelo programa no formato interno apropriado. Pos exemplo, esse programa simples imprime Ola e seu nome na tela se vocé o digitar imediata- mente apés 0 nome do programa #include #include void main(int argc, char *targv[])148 C—Completo Total Cap. 6 if(arge! printé( exit(1); ¢ Vocé esqueceu de digitar seu nome.\n"); print£(*Ola $s", argvfll); } Se esse programa chamasse nome e seu nome fosse Tom, entdo, para rodar 0 programa, vocé deveria digitar nome Tom. A resposta do programa seria Ola Tom. Em muitos ambientes, cada argumento da linha de comando deve ser separado por um espago ou um caractere de tabulacio. Virgulas, pontos~ gulas etc. nao sao considerados separadores. Por exemplo, vir- @ run spot, run 6 constituido de trés strings, enquanto @ Herb, Rick, Fred 6 uma dnica string, uma vez que virgulas nao sao separadores legais. Alguns ambientes permitem que se coloque entre aspas uma string con- tendo espacos. Isso faz com que a string inteira seja tratada como um tnico argumento. Verifique o manual do seu sistema operacional para mais detalhes sobre a definigéo dos parametros na linha de comando do seu sistema. E importante declarar argv adequadamente. O método de declaragéo mais comum é Bochar *argyi): Os colchetes vazios indicam que a matriz é de tamanho indetermimado. Vocé pode, agora, acessar os argumentos individuais indexando argv. Por exemplo, argv[0] aponta para a primeira string, que é sempre o nome do programa; argy{1) aponta para o primeiro argumento e assim por diante. Um outro exemplo usando argumentos da linha de comando é 0 pro- grama chamado countdown, mostrado aqui. Ele conta regressivamente a partir do valor especificado na linha de comando e avisa quando chega a 0. Observe que o primeiro argumento contendo o ntimero é convertido em um inteiro pela funcdo padrao atoi(). Se a string “display” é o segundo argumento da linha de ccomando, a contagem regressiva também sera mostrada na tela.Cop.6 Fungoes /* Programa de contagem regressiva. #include #include #include include void main(int argc, char targv[]} int disp, count; if(argc<2) ( printf (*Vocé deve digi printf£("na linka de comando. exit (1); } if(arge==3 && !stromp(argv[2], aro vi 149 s x a contar\n"); Tente novamente.\n"); “display")) disp = 1 else = 0; for (count=atoi(argv[1]); count; --count) if(disp) printf ("%d\n", count); putchar(‘\a"); /* print£("Terminou"); ) isso ira tocar a campainha na maioria dos computadores y Observe que, se nenhum argumento for especificado, é mostrada uma mensagem de erro. Um programa com argumentos na linha de comando geralmente apre- senta instrugdes se 0 usuario executou o programa sem inserir a informagao apro- priada. Para acessar um caractere individual em uma da: strings de comando, acrescente um segundo indice a argv. Por exemplo, o proximo programa mostra todos os argumentos com os quais foi chamado, um caractere por vez: #include void main(int argc, char *argv(]) txarge; ++t) {150 C — Completo e Total Cap. 6 while(argv(t][il) { putchar (argv(t] (il); } Lembre-se: © primeiro indice acessa a string e 0 segundo acessa os caracteres individuais da string. Normalmente, vocé usa arge e argy para obter os comandos iniciais do seu programa. Teoricamente, vocé pode ter até 32.767 argumentos, mas a maioria dos sistemas operacionais nao permite mais que alguns poucos. Geralmente, voce usa esses argumentos para indicar um nome de arquivo ou uma opgao. O uso dos argumentos da linha de comando da aos seus programas uma aparéncia profissional e facilita 0 uso do programa em arquivos de lote (batch files). E uma pratica comum declarar main() como nao tendo parametro al- gum, usando a palavra-chave (ou palavra reservada) void quando os parametros da linha de comando néo estao sendo usados. (Esta abordagem é usada pelos programas neste livro). Porém, vocé pode simplesmente deixar os parénteses vazios. Os nomes arge e argy so tradicionais, porém arbitrarios. Vocé pode dar quaisquer nomes a esses dois parametros de main(). Além disso, alguns com, ladores podem suportar arguments adicionais para main(); assegure-se, entao, de verificar seu manual do usuario EO Comando return O comando return tem dois importantes usos. Primeiro, ele provoca uma saida imediata da fungao que o contém. Isto é, faz com que a execugio do programa retorne ao cddigo chamador. Segundo, ele pode ser usado para devolver um valor. Retornando de uma Funcao Existem duas maneiras pelas quais uma funcdo termina a execucdo e retorna ao cédigo que a chamou. A primeira ocorre quando o tiltimo comando da funcéo for executado e, conceitualmente, a chave final do programa (J) é encontrada. (Obviamente, a chave nao esta realmente presente no cédigo-objeto, mas vocé pode imaginé-la como se estivesse.) Por exemplo, a funcao pr_reverse() nesse pro- grama, simplesmente escreve a string “Eu gosto de C” de tras para frente na tela.Cap. 6 Fungdes 151 #include #include void pr_reverse(char *s); void main(void) { pr_reverse("Eu gosto de C"); void pr_reverse(char *s) { register int for (t trlen(s)-1; t>=0; t--) putchar(s[t]); Uma vez que a string tenha sido mostrada, nao fica nada por fazer na funcdo pr_reverse(), de forma que ela retorna para o lugar de onde foi chamada. Na realidade, poucas funcdes usam esse método default de terminar a execugao. A maioria das fungdes adota o comando return para encerrar a exe- cugao, seja porque um valor deve ser devolvido seja para tornar 0 cédigo da funcao mais simples e eficiente. Lembre-se de que uma fungdo pode ter diversos comandos return. Por exemplo, a fungio find_substr(, no programa a seguir, devolve a posicio inicial de uma substring dentro de uma string ou, se nao for encontrada, a funcao devolve -1. #include int find_substr(char “sl, char *s2); void main(void) if(£ind_substr("C é legal", "é")!=-1) print£("a substring nao foi encontrada) ; /* Devolve o indice de sl em s2. */ find_substr(char *s1, char *s2)152 C — Completo ¢ Total Cap. 6 for(t=0; sl[t}; t++) { &sl[t}; 2 e(*p2 && “p2==*p) ( pa++ (1*p2) return t; /* 1* retorno */ return - /* 2% retorno */ Retornando Valores Todas as funcées, exceto as do tipo void, devolvem um valor. Esse valor € es- pecificado explicitamente pelo comando return. Se nenhum comando return es- tiver presente, entao o valor de retorno da fungao sera tecnicamente indefinido. (Geralmente, os compiladores C devolvem 0 quando nenhum valor de retorno for especificado explicitamente, mas vocé nado deve contar com isso se ha inte- resse em portabilidade.) Em outras palavras, a partir do momento que uma fun- ga0 nao é declarada como void, ela pode ser usada como operando em qualquer expressio valida de C. Assim, cada uma das seguintes expressées é valida em C: x = powerly}: if(max(x,y) > 100) printé ("maior"); for(ch=getchar(); isdigit(ch); ) Porém, uma fungao nao pode ser o destino de uma atribuigio. Um co- mando tal como esta errado. O compilador C indicaré isso como um erro e nao compilara pro- gramas que contenham um comando como esse. Quando vocé escreve programas, suas funges geralmente serao de trés tipos. O primeiro tipo é simplesmente computacional. Essas fungGes sao proje- tadas especificamente para executar operacdes em seus argumentos e devolver um valor. Uma funcéo computacional é uma funcéo “pura”. Exemplos so as fungées da biblioteca padrao sqrt( e sin, que calculam a raiz quadrada e 0 seno de seus argumentos. ap(x,y) = 100; /* comando incorreto */Cap. 6 Fungdes 153 O segundo tipo de fungao manipula informagdes e devolve um valor que simplesmente indica 0 sucesso ou a falha dessa manipulagao. Um exemplo é a funcao da biblioteca padrao felose), que é usada para fechar um arquivo. Se a operacdo de fechamento for bem-sucedida, a funcao devolvera 0; se a ope- ragéo nao for bem-sucedida, ela devolverd um cédigo de erro. O iltimo tipo nao tem nenhum valor de retorno explicito. Em esséncia, a fungao é estritamente de procedimento e nao produz nenhum valor. Um exem- plo € exit, que termina um programa. Todas as fungdes que nao devolvem valores devem ser declaradas como retornando o tipo void. Ao declarar uma funcao como void, vocé a protege de ser usada em uma expressio, evitando uma utilizacdo errada acidental. Algumas vezes, fungées que, na realidade, nao produzem um resultado relevante de qualquer forma devolvem um valor. Por exemplo, printf() devolve co ntimero de caracteres escritos. Entretanto, é muito incomum encontrar um pro- grama que realmente verifique isso. Em outras palavras, embora todas as fungoes, exceto aquelas do tipo void, devolvam valores, vocé nao tem necessariamente de usar o valor de retorno. Uma questao envolvendo valores de retorno de fun- Goes 6: “Eu ndo tenho de atribuir esse valor a alguma varidvel j& que um valor est sendo devolvido?”. A resposta é nao. Se nao hé nenhuma atribuicao espe- cificada, 0 valor de retorno é simplesmente descartado. Considere 0 programa seguinte, que utiliza a fungdo mul0): #include int mul(int a, int b); void main(void) nt x, yy x = 10; y = 20; z= mul(x, y); wae print£("$d", mul(x,y)); /* 2 */ mul (x, y); emia } mul(int a, int b) £ return a*b;154 C — Completo e Total Cap. 6 Na linha 1, 0 valor de retorno de mul( é atribuido a z. Na linha 2, 0 valor de retorno nao é realmente atribuido, mas é usado pela funcao print£(). Finalmente, na linha 3, o valor de retorno é perdido porque nao é atribuido a outra variavel nem usado como parte de uma expressao. is Funcgées Que Devolvem Valores Ndo-Inteiros Quando o tipo da fungao nao é explicitamente declarado, 0 compilador C atribui automaticamente a ela o tipo padrao, que é int. Para muitas funges em C, esse tipo padrao é aplicavel. No entanto, quando 6 necessério um tipo de dado dife- rente, 0 processo envolve dois passos. Primeiro, deve ser dada a funcéo um es- pecificador de tipo explicito. Segundo, o tipo da funcao deve ser identificado antes da primeira chamada feita a ela. Apenas assim C pode gerar um cédigo correto para fungdes que nao devolvem valores inteiros. As fungdes podem ser declaradas como retornando qualquer tipo de dado valido em C. O método da declaracdo é semelhante a declaragio de varid- veis: o especificador de tipo precede o nome da funcao. O especificador de tipo informa ao compilador que tipo de dado a fungao devolvera. Essa informacao é critica para o programa rodar corretamente, porque tipos de dados diferentes tém tamanhos e representacées internas diferentes. Antes que uma fungdo que nao retorne um valor inteiro possa ser usada, seu tipo deve ser declarado ao programa. Isso porque, a menos que informado em contrario, 0 compilador C assume que uma fungao devolve um valor inteiro. Se seu programa usa uma fungio que devolve um tipo diferente antes da sua declaragao, 0 compilador gera erroneamente o cédigo para a chamada. Para evi- tar isso, vocé deve usar uma forma especial de declaracao, perto do inicio do seu programa, informando ao compilador o tipo de dado que sua fungao real- mente devalverd. Existem duas maneiras de declarar uma funcao antes de ela ser usada: a forma tradicional e 0 moderno método de protétipos. A abordagem tradicional era 0 tinico método disponivel quando C foi inventada, mas agora esta obsoleto. Os prot6tipos foram acrescentados pelo padrao C ANSI. A abordagem tradicional 6 permitida pelo padrao ANSI para assegurar compatibilidade com cédigos mais antigos, mas novos usos séo desencorajados. Muito embora seja antiquado, mui- tos milhares de programas ainda o usam, de forma que vocé deve familiarizar-se com ele. Além disso, 0 método com protétipos é basicamente uma extensio do conceito tradicional.Cap. 6 Fungées 155 Nesta segdo, examinaremos a abordagem tradicional. Embora desatuali- zada, muitos dos programas existentes ainda a utilizam. Além disso, um método do prototipo é basicamente uma extensao do conceito tradicional. (Os prototipos. de fungio sao discutidos na préxima segio.) Com a abordagem tradicional, vocé especifica o tipo e o nome da fungao proximos ao inicio do programa para informar ao compilador que uma funcao devolverd algum tipo de valor diferente de um inteiro, como ilustrado aqui: #include Eloat sum(); /* identifica a funcdo */ float first, second; void main(void) { st = 123.23; second = 99.09; printf ("*£", sum()); 3 float sum(} ( return first A primeira declaracao da fungao informa ao compilador que sum() devolve um tipo de dado em ponto flutuante. Isso permite que o compilador gere correta- mente 0 cédigo para a chamada a sum(). Sem a declaragao, 0 compilador indi caria um erro de incompatibilidade de tipos. O comando tradicional de declaragao de tipos tem a forma geral especificador_de_tipo nome_da_funcio(); Mesmo que a fungao tenha argumentos, eles néo constam na declaracao de tipo, Sem o comando de declaracao de tipo, ocorreria um erro de incompa- tibilidade entre o tipo de dado que a fungao devolve e o tipo de dado que a rotina chamadora espera. Os resultados serao bizarros e imprevisiveis. Se ambas as fungGes estéo no mesmo arquivo, o compilador descobre a incompatibilidade de tipos e n’o compila o programa. Contudo, se as fungSes estao em arquivos diferentes, 0 compilador nao detecta 0 erro. Nenhuma verificacao de tipos € feita durante o tempo de linkedigéo ou tempo de execucéo, apenas em tempo de compilacao. Por essa razdo, vocé deve assegurar-se de que ambos os tipos sio compativeis.156 C — Completo e Total Cap. 6 NOTA: Quando um caractere é devolvido por umv fungio declarada como sendo do tipo int, 0 valor caractere & convertido em um inteiro. Visto que C faz a conversito de caractere para inteiro, e vice-versa, uma fungio que devolve um caractere geral- mente no é declarada come devolvendo um valor caractere. O programador confia na conversito padrio de caractere em inteiro ¢ vice-versa. Esse tipo de coisa & fre giientemente encontrado em cédigos em C mais antigos ¢ tecnicamente nao & consi- derado um erro. & Protétipos de Funcées O padrao C ANSI expandiu a declaracao tradicional de fungao, permitindo que a quantidade e os tipos des argumentos das fungées sejam declarados. A defi- nigao expandida é chamada protétipo de fungdo. Protétipos de funcdes nao faziam parte da linguagem C original. Bles so, porém, um dos acréscimos mais impor- tantes do ANSI a C. Neste livro, todos os exemplos incluem prototipos completos das funcées. Protétipos permitem que C forneca uma verificagao mais forte de tipos, algo como aquela fornecida por linguagens como Pascal. Quando vocé usa prototipos, C pode encontrar e apresentar quaisquer conversées de tipos ilegais entre o argumento usado para chamar uma fungao e a definigao de seus para- metros. C também encontra diferencas entre o ntimero de argumentos usados para chamar a funcao e o ntimero de parametros da fungio. iu A forma geral de uma definicao de protétipo de funcao é tipo nome_func(tipo nome_paramt, tipo nome_param?,..., tipo nome_paramN); O uso dos nomes dos parametros ¢ opcional. Porém, eles habilitam 0 compilador a identificar qualquer incompatibilidade de tipos por meio do nome quando ocor- re um erro, de forma que é uma boa idéia inclui-los. Por exemplo, o programa seguinte produz uma mensagem de erro por- que ele tenta chamar sqr_it0 com um argumento inteiro em vez do exigido pon- teiro para inteiro. (Voce nao pode transformar um inteiro em um ponteiro.) /* Esse programa usa um protétipo de fun¢aéo para forcar uma verificacaéo forte de tipos. */ void sqr_it(int *i}; /* protétipo */ void main(voia) { int xCap. 6 Fungées 157 x = 10; sar_it(: /* incompatibilidade de tipos */ ) void sqr_it(int *i) Devido & necessidade de compatibilidade com a versio original de C, algumas regras especiais sao aplicadas aos protétipos de fungées. Primeiro, quando o tipo de retorno de uma fungao é declarado sem nenhuma informacao de protétipo, 0 compilador simplesmemte assume que nenhuma informacao sobre os parame- tros é dada. No que se refere ao compilador, a funcao pode ter diversos ou nenhum parametro. Assim, como pode ser dado um prot6tipo a uma fungao que nao tem nenhum pardmetro? A resposta é: quando uma funcdo nao tem para- metros, seu protétipo usa void dentro dos parénteses. Por exemplo, se uma fun- ao chamada £0 devolve um float e nao tem parametros, seu protétipo sera BP float £(voia); Isso informa ao compilador que a fungaéo nao tem parametros e qualquer cha- mada a fungao com parametros é um erro. © uso de prototipos afeta a promogao automitica de tipos de C. Quando uma funcao sem protétipo € chamada, todos os caracteres séo convertidos em inteiros e todos os floats em doubles. Essas promocdes um tanto estranhas estao relacionadas com as caracteristicas do ambiente original em que C foi desenvol- vida. No entanto, se a funcao tem prototipo, os tipos especificados no protétipo 10 mantidos e ndo ocorrem promocoes de tipo. Protétipos de fungées ajudam a detectar erros antes que eles ocorram. Além disso, cles auxiliam a verificar se seu programa esta funcionando corretamen- te, nao permitindo que fungdes sejam chamadas com argumentos inconsistentes. Tenha um fato em mente: embora 0 uso de prototipos de fungao seja bastante recomendado, tecnicamente nao é errado que uma fungio nao tenha protétipos. Isso 6 necessério para suportar cédigos C sem protstipos. Todavia, seu codigo deve, em geral, incluir total informagao de prototipos. KA, NOTA: Embora os protétipos sejam opcionais em C, eles sto exigidos pela sucessora Sa de C: Co.158 C — Completa e Total Cap. 6 &@ Retornando Ponteiros Embora funcdes que devolvem ponteiros sejam manipuladas da mesma forma que qualquer outro tipo de fungao, alguns conceitos importantes precisam ser discutidos Ponteiros para varidveis nao sao varidveis e tampouco inteiros sem sinal. Eles so 0 endereco na memoria de um certo tipo de dado. A razao para a distingao deve-se ao fato de a aritmética de ponteiros ser feita relativa ao tipo de base. Por exemplo, se um ponteiro inteiro é incrementado, ele conteré um valor que é maior que o seu anterior em 2 (assumindo inteiros de 2 bytes). Em geral, cada vez que um ponteiro é incrementado, ele aponta para o préximo item de dado do tipo correspondente. Desde que cada tipo de dado pode ter um comprimento diferente, o compilador deve saber para que tipo de dados 0 pon- teiro esta apontando. Por esta razao, uma funcao que retorna um ponteiro deve declarar explicitamente qual tipo de ponteiro ela esta retornando. Para retornar um ponteiro, deve-se declarar uma fungéo como tendo tipo de retorno ponteiro. Por exemplo, esta funcéo devolve um ponteiro para a primeira ocorréncia do caractere ¢ na string s: /* Devolve um ponteiro para a primeira ocorréncia de c ems. */ char *match(char ¢, char *s) ( while(c!=*s && *s) s++ return(s); } Se nenhuma coincidéncia for encontrada, ¢ devolvido um ponteiro para o ter- minador nulo. Aqui esta unt programa pequeno que usa match0: #include char *match(char ¢, char *s); /* protétipo */ void main(void) { char s[80], *p, ch; gets(s}; ch = getchar(); p = matchich, s); if(*p) /* encontrou */Cap: 6 Fungoes 159 printf("ts ", p); else printf ("Nao encontrei."); } Esse programa Ié uma string e, em seguida, um caractere. Se 0 caractere esté na string, 0 programa imprime a string do ponto em que ha a coincidéncia. Caso contrario, ele imprime Nao encontr gE Funcées do Tipo void Um dos usos de void é declarar explicitamente fungdes que nao devolvem va- lores. Isso evita seu uso em express6es ¢ ajuda a afastar um mau uso acidental. Por exemplo, a funcdo print_vertical() imprime seu argumento string vertical- mente para baixo, do lado da tela. Visto que nao devolve nenhum valor, ela é declarada como void void print_vertical (char *str) € while(*str) printf(%c\n", *str++); } Antes de poder usar qualquer fungao void, vocé deve declarar seu protétipo. Se isso nao for feito, C assumira que ela devolve um inteiro e, quando o compilador encontrar de fato a funcao, ele declararé um erro de incompatibilidade. O pro- grama seguinte mostra um exemplo apropriado que imprime verticalmente na tela um unico argumento da linha de comande: #include void print_vertical(char *str); /* protétipo */ void main(int argc, char *argv[]) if(arge) print_ver al (argv(1}); ) void print_vertical (char *str) while(*str) printf("8c\n", *str++);160 C= Completo e Total Cap.6 Antes que 0 padrao C ANSI definisse void, fungdes que nao devolviam valores simplesmente eram assumidas como do tipo int por padrao. Portanto, nao fique surpreso ao ver muitos exemplos disto em cédigos mais antigos. @ O Que main() Devolve? De acordo com 0 padrao ANSI, a fungdo main() devolve um inteiro para 0 pro- cesso chamador, que é, geralmente, o sistema operacional. Devolver um valor em main() é equivalente a chamar exit() com o mesmo valor. Se main() nao devolve explicitamente um valor, o valor passado para 0 proceso chamador ¢ tecnicamente indefinido. Na pratica, a maioria dos compiladores C devolve 0, mas nao conte com isso se ha interesse em portabilidade. Vocé também pode declarar main() como void se ela nao devolve um valor. Alguns compiladores geram uma mensagem de adverténcia, se a funcao nao é declarada como void e também nao devolve um valor. @ Recursao Em C, fungdes podem chamar a si mesmas. A fungao é recursiva se um comando no corpo da fungao a chama. Recursao ¢ 0 processo de definir algo em termos de si mesmo e 6, algumas vezes, chamado de definigao circular. Um exemplo simples de fungao recursiva 6 facts), que calcula o fatorial de um inteiro. O fatorial de um némero n é 0 produto de todos os ntimeros inteiros entre 1 en. Por exemple, fatorial de 3 é 1 x 2 x 3, ou seja, 6. Tanto factr) como sua equivalente iterativa sio mostradas aqui: factr n) /* recursiva */ { int answer; if (n==1) returm (1); answer = factr(n-1)*n; /* chamada recursiva */ return(answer) ; 3 fact(int n) /* n&o-recursiva */ { int t, answer;Cap. 6 Fungies 161 nswer = for(t=1; t<=n; t++) answer=answer*(t); re en (answer) ; ) A versao ndo-recursiva de factt) deve ser clara. Ela usa um lago que é executado de 1a ne multiplica progressivamente cada ntimero pelo produto mével. A operacao de factr() recursiva é um pouco mais complexa. Quando factr) é chamada com um argumento de 1, a funcao devolve 1. Caso contrario, ela devolve o produto de factr(n-1)*n. Para avaliar essa expresso, factr() é cha- mada com nel. Isso acontece até que n se iguale a 1 e as chamadas a funcdo comecem a retornar. Calculando o fatorial de 2, a primeira chamada a faetr() provoca uma segunda chamada com o argumento 1. Essa chamada retorna 1, que é, entdo, multiplicado por 2 (0 valor original de n). A resposta, entao, é 2. (Vocé pode achar interessante inserir comandos printf() em factr() para ver o nivel de cada chamada e quais sao as respostas intermediarias.) Quando uma fungdo chama a si mesma, novos parametros e varidveis locais so alocados na pilha e 0 cédigo da fungao é executado com essas novas varidveis. Uma chamada recursiva nao faz uma nova cépia da funcao; apenas os argumentos sao novos. Quando cada fungao recursiva retorna, as varidveis locais e os parametros sao removidos da pilha e a execucao recomeca do ponto da chamada a funcao dentro da funcao. A maioria das fungdes recursivas néo minimiza significativamente 0 tamanho do cédigo nem melhora a utilizagio da meméria. Além disso, as versoes recursivas da maioria das rotinas podem ser executadas um pouco mais lenta- mente que suas equivalentes iterativas devido as repetidas chamadas & funcio. De fato, muitas chamadas recursivas a uma fungéo podem provocar um estouro da pilha. Como o armazenamento para os parametros da funcao e variaveis locais estd na pilha e cada nova chamada cria uma nova cépia dessas variaveis, a pilha pode provavelmente escrever sobre outra meméria de dados ou de program. Contudo, vocé possivelmente nunca terd de se preocupar com isso, a menos que uma fungao recursiva seja executada de forma desenfreada. A principal vantagem das fungées recursivas é que vocé pode usé-las para criar versdes mais claras e simples de varios algoritmos. Por exemplo, 0 QuickSort, na Parte 3, é muito dificil de implementar numa forma iterativa. Além disso, alguns problemas, especialmente aqueles relacionados com inteligéncia ar-162 C — Completo e Total Cap. 6 tificial, resultaram em solugoes recursivas. Finalmente, algumas pessoas parecem pensar recursivamente com mais facilidade que iterativamente. Ao escrever fungées recursivas, vocé deve ter um comando if em algum lugar para forcar a fungao a retornar sem que a chamada recursiva seja executada. Se vocé nao o fizer, a fungao nunca retornat quando chamada. Omitir o if & um erro comum quando se escrevem fungées recursivas. Use printf) e getchar() deliberadamente durante 0 desenvolvimento do programa de forma que vocé possa ver 0 que esta acontecendo e encerrar a execugao se localizar um erro. I Declarando uma Lista de Pardmetros de Extensao Varidvel Em C, vocé pode especificar uma func4o que possui a quantidade e os tipos de parametros varidveis. O exemplo mais comum € printf(). Para informar ao com- pilador que um namero desconhecido de parametros seré passado para uma fungao, vocé deve terminar a declaragao dos seus parametros usando trés pontos. Por exemplo, esta declaracdo especifica que func() terd pelo menos dois parame- tros inteiros e um numero desconhecido (incluindo 0) de parametros apés eles. @ otunc(int a, int b, ...); Essa forma de declaragao também é usada por um protétipo de fungao. Qualquer fungao que use um ntimero varidvel de argumentos deve ter pelo menos um argumento verdadeiro. Por exemplo, isto esta incorreto: ® func(...); Para mais informagées sobre ntimero e tipos varidveis, veja a Parte 2, sobre a fungao va_arg() da biblioteca C padrao. | Declaracao de Pardmetros de Funcgées Moderna Versus Classica C originalmente usava um método de declaragao de parametros diferente, algu- mas vezes chamado de forma cldssica. Este livro usa a abordagem de declaragao chamada de forma moderna. O padrao ANSI para C suporta as duas formas, masCap. 6 Fungoes 163 recomenda fortemente a forma moderna. Porém, vocé deve saber a forma classica porque, literalmente, milhdes de linhas de cédigo ja existentes a usam! (Além disso, muitos programas usam essa forma porque ela funciona com todos os compiladores — mesmo os antigos.) A declaracao classica de pardmetros de fungées consiste em duas partes: uma lista de parametros, que ficam dentro dos parénteses que seguem 0 nome da funcao, e as declaracées reais dos parametros, que ficam entre o fecha-parén- teses e o abre-chaves da fungao. A forma geral da declaragao classica é tipo nome_func\param!, param2,...paramN) tipo param; tipo paramn2; tipo paramN; { cédigo da fungao } Por exemplo, esta declaragao moderna: float f(int a, int b, char ch) era ) ird se parecer com isto na sua forma classica: float f(a, b, ch) int a, b; char ch; { aera ) Observe que a forma classica pode suportar mais de um parametro em uma lista apés © nome do tipo Lembre-se de que a forma classica de declaragao de parametro esté ob- soleta. Contudo, seu compilador ainda pode compilar programas mais antigos que usam a forma cléssica sem qualquer problema. Isso permite a manutengao de cédigos mais antigos,164 © ~ Completo e Total Cap. 6 I Questées sobre a Implementacgao Ha uns poucos pontos a lembrar, quando se criam fungdes em C, que afetam sua eficiéncia e usabilidade. Essas questdes sio 0 t6pico desta secao. Pardmetros e Fungoes de Propésito Geral Uma fungao de propésito geral é aquela que sera usada, em uma variedade de situagées, talvez por muitos outros programadores. Tipicamente, vocé nao deve basear fungées de propésito geral em dados globais. Todas as informagées de que uma funcao precisa devem ser passadas para ela por meio de seus parame- tros. Quando isso nao é possivel, voce deve usar varidveis estaticas. Além de tornar suas fungdes de proposito geral, os parametros deixam seu cédigo legivel e menos suscetivel a erros resultantes de efeitos colaterais. Eficiéncia Fungées sao 0s blocos de construgao de C e sao cruciais para todos os programas, exceto os mais simples. Entretanto, em certas aplicagdes especializadas, voce tal- vez precise eliminar uma fungao ¢ substitui-la por cédigo em linha (in-line). Co- digo em linha é 0 equivalente aos comandos da fungao usados sem uma chamada a funcao. Deve-se usar cdigo em linha em lugar de chamadas a funcdes apenas quando 0 tempo de execucio € critico. Cédigo em linha é mais rapido que a chamada a uma fungao por duas razoes. Primeiro, uma instrugao CALL leva tempo para ser executada. Segundo, se ha argumentos para passar, eles devem ser colocados na pilha, o que também toma tempo. Para a maioria das aplicagdes, esse aumento muito pequeno no tempo de execucao nao é significative. Mas, se for, lembre-se de que cada cha- mada a funcao usa um tempo que poderia ser economizado se 0 codigo da funcéo fosse colocado em linha. Por exemplo, seguem duas versdes de um programa que imprime 0 quadrado dos numeros de 1 a 10. A versao em linha é executada mais rapidamente que a outra porque a chamada 4 fungao toma tempo. em linha chamada 4 funcae #include #include int sqr(int a); void main(void) void main(void) ( { int x;Cap. 6 Fungdes 165 for(x=1; x void main(void)Cap.7 Estruturas, unides, enumeragdes e tipos definidos pelo usudrio ai y =x; /* atribui uma estrutura a outra */ print£("%d*, y.a); Apés a atribuigao, y.a contera o valor 10. @ Matrizes de Estruturas Talvez 0 uso mais comum de estruturas seja em matriz de estruturas. Para de- clarar uma matriz de estruturas, vocé deve primeiro definir uma estrutura e, entao, declarar uma varidvel matriz desse tipo. Por exemplo, para declarar uma matriz de estruturas com 100 elementos do tipo addr, que foi definido anterior- mente, deve-se escrever Isso cria 100 conjuntos de variaveis que estio organizados como definido na estrutura addr. Para acessar uma estrutura especifica, deve-se indexar 0 nome da estru- tura. Por exemplo, para imprimir 0 cédigo do CEP da estrutura 3, escreva truct addr addr_info[100]; BP oprint£(-sd", addr_infol2].2ip); Como todas as outras matrizes, matrizes de estruturas comegam a indexagao em 0. Um Exemplo de Lista Postal Para ilustrar como estruturas e matrizes de estruturas sdo usadas, esta segdo desenvolve um programa simples de lista postal que usa uma estrutura para guardar as informagGes de endereco. Nesse exemplo, a informagao armazenada inclui nome, rua, cidade, estado e CEP. Para definir a estrutura basica de dados, addr, que contém essa infor- macao, escreva172 C— Completo e Total Cap. 7 struct addr { char name [30]; char street [40]; char city[20}; char state(3]; unsigned long int zip; } addr_info [MAX] ; Observe que © campo de CEP é um inteiro longo sem sinal. Isso ocorre porque os CEPs maiores que 64000 — como 94564 — nao podem ser represen- tados em um inteiro de 2 bytes. Nesse exemplo, um inteiro possui 0 cédigo do CEP para ilustrar um elemento de estrutura numérico. Porém, a pratica mais. comum é usar uma string de caracteres para acomodar cédigos postais com letras além de numeros (como usado no Canada e em outros paises), O valor de MAX pode ser definido para satisfazer necessidades especificas. A primeira fungao necessaria para 0 programa é main(). /* Um exemplo simples de 1 matriz de estruturas. */ #include 1 usando uma post, #define MAX 100 struct addr { char name(30); char street [40]; char city([20]; char state[3]; unsigned long int zip; } addr_info[MAx]; void init_list (void), enter(void); void delete(void), list (void) ; int menu_select (void), find_free(void); void main(void) { char choice; init_list(); /* inicializa a matriz de estruturas */ for(i7) ¢Cap. 7 Estruturas, unides, enumeragées e tipos definidios pelo usuario 173 choice=menu_select (); switch(choice) { case 1: enter(); break; e@ 2: delete(); break; case 3: list(); break; case 4: exit (0); } Primeiro, a funcdo init_list0 prepara a matriz de estruturas para ser usada, co- locando um caractere nulo no primeiro byte do campo nome. O programa as- sume que uma variavel estrutura nao esta sendo usada se nome estiver vazio. A funcao init_list() é mostrada aqui /* Inicializa a lista. */ void init_list (void) { register int for(t=0; t ) MAX; ++t) addr_info[t].name[0] = ‘\0‘; A funcdo menu_select() apresenta as mensagens de opcio e devolve a selegdo do usuario /* Obtém a selecao. */ menu_select (void) { char s[80]; int ¢; print€("1, Inserir um nome\n"); print£("2. Excluir um nome\n"); print£("3. Listar o arquivo\n"); printf£("4. Sair\n"); do { printf gets(s); c = atoils); \nDigite sua escolha174 ‘C— Completo e Total Cap. 7 )} while(c 4); return c; } A funcao enter() espera pela entrada do usudrio e coloca a informagao recebida na proxima estrutura livre. Se a matriz, estiver cheia, entao a mensagem lista cheia seré escrita na tela. A funcdo find_free() procura um elemento nao usado na matriz de estruturas. /* Insere os enderegos na lista. */ void enter (void) ( int slot; char s(80]; slot = find_free(); if(slot==-1) { printé(*\nLista cheia"); return; print£("Digite o nome: "}; gets (addr_info[slot] .name) ; print£("Digite a rua: "); gets (addr_info{slot] .street); printf£("Digite a cidade: "); gets (addr_info{slot] .city); print£("Digite o estado: "); gets (addr_info[slot].state); printf ("Digite o cep: gets(s); addr_info[slot].zip = strtoul(s, ‘\0’, 10); /* Encontra uma estrutura ndo usada. */ find_free(void) { register int t; for ( addr_info[t].name[0] && t #include define MAX 100 struct addr ( char name [30]; char street [40]; char city[20]; char state[3]; unsigned long int zip; } addy_info[MAX) ; void init_list (void), enter (void); void delete(void), list (void); nt menu_select (void), find_free(void); void main (void) char choice; /* inicializa a matriz de estruturas { e=menu_select (); switch(choice) { case 1: enter(}; break, case 2: delete(); break, case 3 break case 4: exit (0 list); aCap. 7 Estruturas, wnides, enumeracies e tipés definidos pelo usudrio 17 /* Inicializa a lista. */ void init_list (void) register int t; for(t=0; t 4); return c; /* Insere os enderecos na lista. */ void enter (void) { int slot; char s[80]; slot = find_free(); if(slot==-1) ( printf ("\nlista cheia return; } printé("Digite o nome: "); gets (addr_info[slot] .name) ; print£(*Digite a rua: gets (addr_info[slot].street);78 C— Completo ¢ Total printf ("Digite a cidade: "); gets (addr_info[slot] .city); printf ("Digite o estado: "}; gets (addr_info[slot].state) ; printf ("Digite o cep: "}i gets(s); addr_info[{slot].zip = strtoul(s, '\0’, 10); i /* Encontra uma estrutura ndo usada. */ find_free(void) { register int t; for (t=0; addr_infoft].name(0] && t =0 && slot < MAX) addr_info[slot].name[0] = '\0'; } /* Mostra a lista na tela. */ void list (void) { register int t; for(t=0; t /* Define um tipo de estrutura. */ struct struct_type { int a, b; char ch; De void fl(struct struct_type parm); void main(void) { struct struct_type arg; arg.a = 1000; fllarg);Cap. 7 Estrulsiras, unides, enumeracées ¢ tipos definidos pelo usuario 181 ) void fl(struct struct_type parm) { printf£(*%d", parm.a); } Como este programa ilustra, se vocé declarar pardmetros que sao estruturas, deveré tornar a declaracao do tipo de estrutura global, para que todas as partes do seu programa possam usé-la. Por exemplo, se struct_type tivesse sido decl rada dentro de main) (por exemplo), entao nao seria visivel a f10. Como acabamos de enunciar, ao passar estruturas, o tipo do argumento deve coincidir com 0 tipo do parametro. Nao é suficiente que eles sejam fisica- mente semelhantes; os nomes dos seus tipos devem coincidir. Por exemplo, a versdo seguinte do programa anterior é incorreta e nao compilara porque o nome do tipo do argumento usado para chamar f10 difere do nome do tipo de seu parametro. /* Este programa esta errado e néo poderd ser compilado. */ #include /* Define um tipo de estrutura. */ struct struct_type ( int a, b; char ch; 08 /* Define uma estrutura similar a struct_| mas com outro nome. */ struct struct_type2 ( int a, b; char DG void fl(struct struct_type2 parm); void main(void) { struct struct_type arg; arg.a = 1000; fllarg); /* erro de tipos */182 C— Complete ¢ Total Cap. 7 ) void filstruct struct_type2 parm) ‘ print£("%d", parm.a); } EZ Ponteiros para Estruturas C permite ponteiros para estruturas exatamente como permite ponteiros para outros tipos de varidveis. No entanto, ha alguns aspectos especiais de ponteiros de estruturas que vocé deve conhecer. Declarando um Ponteiro para Estrutura Como outros ponteiros, vocé declara ponteiros para estrutura colocando * na frente do nome da estrutura. Por exemplo, assumindo a estrutura previamente definida addr, 0 cédigo seguinte declara addr_pointer como um ponteiro para dados daquele tipo. WB struct addr *addr_pointer: Usando Ponteiros para Estruturas Ha dois usos primarios para ponteiros de estrutura: gerar uma chamada por referéncia para uma fungao e criar listas encadeadas e outras estruturas de dados dinamicas usando o sistema de alocagao de C. Este capitulo cobre 0 primeiro uso. O segundo uso é coberto detalhadamente na Parte 3. Ha um prejuizo maior em passar todas as estruturas, exceto as mais simples, para fungdes: 0 tempo extra necessdrio para colocar (e tirar) todos os elementos da estrutura na pilha. Em estruturas simples, com poucos elementos, esse tempo extra nao é tio grande. Se varios elementos séo usados, porém, ou se alguns dos elementos sao matrizes, a performance pode ser reduzida a niveis inaceitaveis. A solugao para esse problema 6 passar apenas um ponteiro para uma funcdo. Quando um ponteiro para uma estrutura é passado para uma fungao, apenas 0 endereco da estrutura € colocado (e tirado) da pilha. Isso contribui para chamadas muito rapidas a fungdes. Uma segunda vantagem, em alguns casos, 6Cap. 7 Estruturas, unides, enumeragdes ¢ tipos definidos pelo ustatrio 183 quando a funcao precisa referenciar o argumento real em lugar de uma copia Passando um ponteiro, é possivel alterar 0 contetido dos elementos reais da es- trutura usada na chamada. Para encontrar o enderego da variavel estrutura, deve-se colocat 0 ope- rador & antes do nome da estrutura. Por exemplo, dado o seguinte fragmento: struct bal ( float balance; char name[80] ; } person; uct bal *p; /* declara um ponteiro para estrutura */ entdo poe 0 endereco da estrutura person no ponteiro p. &person; Para acessar os elementos de uma estrutura usando um ponteiro para a es- trutura, voce deve usar 0 operador ->. Por exemplo, isso referencia o campo balance: B p->palance O -> é normalmente chamado de operador seta, e cansiste no sinal de subtragio seguido pelo sinal de maior. A seta é usada no lugar do operador ponto quando se est acessando um elemento de estrutura por meio de um ponteiro para a estrutura. Para ver como um ponteiro para estrutura pode ser usado, examine este programa simples, que escreve as horas, minutos e segundos na tela usando um reldgio (timer) por software. /* Mostra um relégio por software. */ clude #define DELAY 128000 struct my_time { int hours; int minutes; int seconds;184 C— Completo e Total void display(struct my_time *t); void update(struct my_time *t); void delay (void); void main(void) { struct my_time systime; systime.hours = 0 systime.minutes = 0; systime.seconds = 0; for (ii) { update (&systime) ; display (asystime) ; } void update(struct my_time *t) { t->seconds++; if (t->seconds==60) ( t->seconds = 0; t->minutes++ if (t->minutes==60) { >minutes = 0; t->hours++; 4) t->hours = (t->hour: delay(); void display(struct my_time *t) { printé("%02d:", t->hours); printf ("02d:", t->minutes); print£("%024\n", t->seconds) ; } void delay (void)Cap. 7 Estruturas, unides, enunieracdes e tipds definides| pelo wsubrio 185 necessério */ t hours-=24) t->hours = 0; Essa linha de cédigo informa ao compilador para tomar o enderego de t (que aponta para systime em mainO) e atribuir zero a seu elemento hours. Lembre-se de usar 0 operador ponto para acessar elementos de estrutu- ras quando estiver operando na propria estrutura. Quando vocé tem um ponteiro para a estrutura, use o operador seta. I Matrizes e Estruturas Dentro de Estruturas Um elemento de estrutura pode ser simples ou complexo. Um elemento simples € qualquer dos tipos de dados intrinsecos, como um caractere ou inteiro. Voce ja viu um elemento complexo: a matriz de caracteres usada em addr. Outros tipos de dados complexos sao matrizes unidimensionais e muitidimensionais e outros tipos de dados e estruturas. Um elemento de estrutura que é uma matriz é tratada como vocé poderia esperar a partir dos exemplos anteriores. Por exemplo, considere esta estrutura: B struct x {186 C—Completo e Total Cap.7 int a[10](10]; /* matriz de 10 x 10 itens */ float b; hy: Para referenciar 0 inteiro 3,7 em a da estrutura y, escreva @ y.at3117) Quando um elemento de uma estrutura é um elemento de outra estrutura, ela 6 chamada estrutura aninhada. Por exemplo, a estrutura address 6 aninhada em emp neste exemplo: struct emp ( struct addr address; /* estrutura aninhada */ float wage; ) worker; Aqui, a estrutura emp foi definida como tendo dois elementos. O primeiro ele- mento é a estrutura do tipo addr, que contém o endereco de um empregado. O outro 6 wage, que contém o saldrio do empregado. O seguinte fragmento de cédigo atribui 93456 ao elemento zip de address. @ worker.address.zip = 93456; Como voeé pode ver, os elementos de cada estrutura sao referenciados do mais externo ao mais interno. O padrao ANSI C especifica que as estruturas podem ser aninhadas até 15 niveis. A maioria dos compiladores permite mais. @ Campos de Bits Ao contrario da maioria das linguagens de computador, C tem um método in- trinseco para acessar um tinico bit dentro de um byte. Isso pode ser util por um certo ntimero de razies: ™ Se o armazenamento é limitado, vocé pode armazenar diversas varidveis booleanas (verdadeiro/falso) em um byte. Certos dispositivos transmitem informacées codificadas nos bits. @ Certas rotinas de criptografia precisam acessar os bits dentro de um byte.Cap. 7 Estruturas, unibes, enumeragdes ¢ tipos definidos pelo isiirio 187 Embora essas tarefas possam ser realizadas usando os operadores bit a bit, um campo de bit pode acrescentar mais estrutura (e possivelmente eficiéncia) ao seu cédigo. Para acessar os bits, C usa um método baseado na estrutura. Um campo de bits é, na verdade, apenas um tipo de elemento de estrutura que define 0 com- primento, em bits, do campo. A forma geral de uma definigao de campo de bit ¢ struct identificador| tipo nomel : comprimento; tipo nome2 : comprimento; tipo nomeN : comprimento; } lista_de_varidveis; Um campo de bits deve ser declarado como int, unsigned ou signed. Campos de bits de comprimento 1 devem ser declarados como unsigned, porque um tinico bit nao pode ter um sinal. (Alguns compiladores s6 permitem campos de bit unsigned.) O ntimero de bits no campo de bits é especificado por comprimento. Campos de bits sao freqiientemente usados quando se analisa a entrada de um dispositivo de hardware. Por exemplo, o estado da porta do adaptador de comunicagées seriais poderia retornar um byte de estado organizado desta forma: Bit Significade quando ligado 0 Allteragao na linha clear-to-send Alteracao em data-set-ready Borda de subida da portadora detectada Alteracao na linha de recepcao 1 2 3 4 Clear-to-send 5 Data-set-ready 6 — Chamada do telefone 7 Sinal recebido Vocé pode representar a informagao em um byte de estado usando o seguinte campo de bits: struct status_type { unsigned delta_cts: unsigned delta_dsr unsigned tr_edge: 1;188 Cap. 7 unsigned delta_rec: unsigned cts: unsigned dsr: unsigned ring: unsigned rec_lin } status; Vocé pode usar uma rotina semelhante a esta, mostrada aqui, para permitir que um programa determine quando pode enviar ou receber dados. status = get_port_status(}; (status.cts) printf("livre para envia: status.dsr) printf("dados prontos"}; Para atribuir um valor a um campo de bits, simplesmente use a forma que vocé usaria para qualquer outro tipo de elemento de estrutura. Por exemplo, este frag- mento de cédigo limpa o campo ring: @ status.ring = 0 Como vocé pode ver, a partir destes exemplos, cada campo de bits 6 acessado com o operador ponto. Porém, se a estrutura é referenciada por meio de um ponteiro, vocé deve usar o operador ->. Nao é necessério dar um nome a todo campo de bits. Isso torna facil alcangar o bit que vocé quer, contornando os nao usados. Por exemplo, se apenas cts e dsr importam, vocé poderia declarar a estrutura status_type desta forma: struct status_type { unsigned : 4a; unsigned cts: ae unsigned ds. a: ) status; Além disso, note que os bits apds dsr nao precisam ser especificados se nao sio usados. E vélido misturar elementos normais de estrutura com elementos de campos de bit. Por exemplo, struct emp { struct addr address; float pay; unsigned lay_off: /* ocioso ou ativo */Cap. 7 Estruturas, uniées, enumeragées e tipos definidos pelo usurio 189 unsigned hourly: 1; /* pagamento por horas ou salario */ unsigned deduction:3; /* dedugées de imposto */ a define um registro de empregado que utiliza apenas 1 byte para segurar trés pedacos de informacao: 0 estado do empregado se contratado ou assalariado, e © ntimero de dedugdes. Sem o campo de bit, essa informagio usaria 3 bytes. Varidveis de campo de bits tém certas restrigdes. Vocé nao pode obter o endereco de uma varidvel de campo de bits. Varidveis de campo de bits nao podem ser organizadas em matrizes. Vocé nao pode ultrapassar os limites de um inteiro. Nao pode saber, de maquina para maquina, se os campos estarao dispostos da esquerda para direita ou da direita para a esquerda. Em outras palavras, qualquer cédigo que use campos de bits pode ter algumas dependén- cias da maquina EE Unies Em C, uma union é uma posigao de meméria que é compartilhada por duas ou mais varidveis diferentes, geralmente de tipos diferentes, em momentos diferen- tes. A definicéo de uma union é semelhante a definicao de estrutura. Sua forma geral é union identificador { tipo nome_da_variivel; tipo nome_da_varifvel; tipo nome_da_varidvel; | varidveis_uniao; Por exemplo, union u_type ( int i; char ch; oe Essa definicao nao declara quaisquer variaveis. Vocé pode declarar uma variavel colocando seu nome no final da definigdo ou usando um comando de declaragao. separado. Para declarar um varidvel union cnvt do tipo u_type, usando a defi- nigéo dada ha pouco, escreva