Você está na página 1de 32

#include<math.

h>
#include<stdio.h>
2  Linguagem de Programação C 
#include<stdlib.h>  

float max(float x,
  float y){
if (x >   y)
return x;
 
return y; SUMÁRIO 
} PREFÁCIO ........................................................................................................................................... 4 

float mediaHarmPond(float amostra1, 1  TIPOS DE DADOS ......................................................................................................................... 5 


float peso1, 1.1  ENUMERAÇÃO .................................................................................................................................. 7 
float amostra2, 1.2  MODIFICADORES DE TIPO DE ACESSO .................................................................................................... 7 
float peso2, 1.2.1  Const ..................................................................................................................................... 7 
1.2.2  Volatile .................................................................................................................................. 8 
float amostra3, 1.3  CONSTANTES .................................................................................................................................... 8 
float peso3) { 1.3.1  Constantes pré‐definidas ...................................................................................................... 9 
return (peso1 + peso2 2  OPERADORES .............................................................................................................................10 
+peso3)/(peso1/max(amostra1,0.0001)+peso2/max(amostra2,0.0001)+peso3/
2.1  OPERADORES BIT A BIT ..................................................................................................................... 10 
max(amostra3,0.0001)); 2.1.1  Operadores bit a bit de Atribuição ..................................................................................... 12 
} 2.2  OPERADOR ? : ................................................................................................................................ 12 
2.3  OPERADORES DE PONTEIROS & E * ..................................................................................................... 13 
void main(){ 2.4  PRECEDÊNCIA DOS OPERADORES ......................................................................................................... 13 
2.5  EXERCÍCIOS..................................................................................................................................... 14 
unsigned long int cod;
int i, n=0; 3  FUNÇÕES ...................................................................................................................................15 
float p1, p2, p3, mh[60], mh_media=0, somatorio=0, s;   3.1  LOCALIZAÇÃO DAS FUNÇÕES ............................................................................................................... 15 
FILE *saida;
  3.1.1  Corpo da função antes do programa principal (no mesmo arquivo) .................................. 16 
3.1.2  Corpo da função depois do programa principal (no mesmo arquivo) ................................ 16 
  3.1.3  Corpo da função escrito em arquivo separado ................................................................... 16 
if ((saida = fopen("l:medias.DAT", "w"))== NULL){ // Cria um 3.2  ARGUMENTOS PARA FUNÇÃO MAIN() .................................................................................................. 17 
 
arquivo para escrita 3.3  PROTÓTIPO DE FUNÇÕES ................................................................................................................... 17 
 
printf("Nao conseguiu criar arquivo de saida.\n"); 3.4  RETORNO DE PONTEIROS .................................................................................................................. 18 
exit(0);
  // Aborta execucao 3.5  CLASSES DE ARMAZENAMENTO .......................................................................................................... 18 
3.5.1  auto..................................................................................................................................... 18 
} 3.5.2  extern .................................................................................................................................. 18 
while(1){ 3.5.3  static ................................................................................................................................... 19 
3.5.4  register ................................................................................................................................ 19 
printf("Forneca o codigo do aluno\n"); 3.6  DIRETIVA #DEFINE ........................................................................................................................... 19 
scanf("%ld",&cod); 3.7  FUNÇÕES RECURSIVAS ...................................................................................................................... 20 
if (cod==0) break; // Sai do laco 3.8  EXERCÍCIOS..................................................................................................................................... 20 
printf("Forneca as notas das provas do aluno\n"); 4  PONTEIROS ................................................................................................................................23 
scanf("%f %f %f",&p1, &p2, &p3); 4.1  EXPRESSÕES COM PONTEIROS ............................................................................................................ 23 
if (p1>=0 && p1<=10 && p2>=0 && p2<=10 && p3>=0 && p3<=10){ 4.1.1  Atribuição de Ponteiros ...................................................................................................... 23 
mh[n] = mediaHarmPond(p1,1,p2,2,p3,3); 4.1.2  Aritmética de Ponteiros ...................................................................................................... 23 
mh_media += mh[n]; 4.2  INICIALIZAÇÃO DE PONTEIROS ............................................................................................................ 24 
fprintf(saida,"Codigo: %8ld Prova 1: %2.2f Prova 2: %2.2f 4.2.1  Comparação de Ponteiros .................................................................................................. 25 
4.3  PONTEIROS E MATRIZES .................................................................................................................... 25 
Prova 3: %2.2f Media harmonica ponderada: %2.2f\n", cod, p1, p2, p3, 4.3.1  Matrizes de Ponteiros ......................................................................................................... 26 
mh[n]); 4.3.2  Acessando partes de Matrizes como vetores ..................................................................... 26 
n++; 4.4  INDIREÇÃO MÚLTIPLA ....................................................................................................................... 27 
} else 4.5  PONTEIROS PARA FUNÇÕES ............................................................................................................... 27 
4.6  MAIS SOBRE DECLARAÇÕES DE PONTEIROS ........................................................................................... 28 
printf("Intervalo de notas invalido\n"); 4.7  EXERCÍCIOS..................................................................................................................................... 30 
}
5  ESTRUTURAS E UNIÕES ..............................................................................................................31 
mh_media = mh_media/n;
for(i=0;i<n;i++) 5.1  ESTRUTURAS ................................................................................................................................... 31 
somatorio+=(mh[i]-mh_media)*(mh[i]-mh_media);
s = sqrt(somatorio/(n-1));
printf("O desvio padrao eh %.4f\n",s);
3  Linguagem de Programação C  4  Linguagem de Programação C 
   

5.1.1  Inicializando Estruturas ...................................................................................................... 32 
5.1.2  Estruturas Aninhadas ......................................................................................................... 32  PREFÁCIO 
5.1.3  Estruturas e funções ........................................................................................................... 33 
Este  texto  tem  o  objetivo  de  fornecer  os  subsídios  para  o  desenvolvimento  de  programas 
5.1.4  Vetor de Estruturas ............................................................................................................. 33 
avançados  na  linguagem  C.  Os  tópicos  estudados  neste  texto  são  estruturas,  uniões,  campos  de  bits, 
5.1.5  Ponteiros para Estruturas ................................................................................................... 34 
alocação dinâmica e arquivos.  
5.2  CAMPOS DE BITS ............................................................................................................................. 34 
5.3  UNIÕES ......................................................................................................................................... 36   
5.4  SIZEOF() ........................................................................................................................................ 37 
5.5  TYPEDEF ........................................................................................................................................ 38 
5.6  EXERCÍCIOS..................................................................................................................................... 39 
6  ALOCAÇÃO DINÂMICA ...............................................................................................................40 
6.1  MAPA DE MEMÓRIA ......................................................................................................................... 40 
6.2  FUNÇÕES DE ALOCAÇÃO DINÂMICA EM C ............................................................................................. 40 
6.2.1  malloc() ............................................................................................................................... 41 
6.2.2  calloc() ................................................................................................................................ 42 
6.2.3  free() ................................................................................................................................... 42 
6.2.4  realloc() ............................................................................................................................... 43 
6.3  MATRIZES DINAMICAMENTE ALOCADAS ............................................................................................... 43 
6.4  LISTAS ENCADEADAS ........................................................................................................................ 44 
6.4.1  Listas Singularmente Encadeadas ...................................................................................... 44 
6.4.2  Listas Duplamente Encadeadas .......................................................................................... 45 
6.5  ÁRVORES BINÁRIAS .......................................................................................................................... 47 
6.6  EXERCÍCIOS..................................................................................................................................... 49 
7  E/S COM ARQUIVO ....................................................................................................................50 
7.1  E/S ANSI X E/S UNIX ..................................................................................................................... 50 
7.2  STREAMS ....................................................................................................................................... 50 
7.3  ARQUIVOS ...................................................................................................................................... 50 
7.4  SISTEMA DE ARQUIVOS ..................................................................................................................... 50 
7.5  ESTRUTURA FILE ............................................................................................................................. 51 
7.6  ABERTURA DE ARQUIVOS .................................................................................................................. 51 
7.7  FECHAMENTO DE ARQUIVO ............................................................................................................... 52 
7.8  VERIFICANDO FIM DE ARQUIVO ........................................................................................................... 52 
7.9  CONDIÇÕES DE ERRO ........................................................................................................................ 52 
7.10  STREAMS PADRÃO ........................................................................................................................... 53 
7.11  LEITURA E GRAVAÇÃO DE CARACTERES ................................................................................................. 53 
7.12  TRABALHANDO COM STRINGS ............................................................................................................ 54 
7.13  FUNÇÕES DE TRATAMENTO DE ARQUIVOS ............................................................................................. 55 
7.13.1  rewind() .............................................................................................................................. 55 
7.13.2  ferror() ................................................................................................................................ 55 
7.13.3  remove() ............................................................................................................................. 56 
7.13.4  fflush() ................................................................................................................................. 56 
7.13.5  Acesso aleatório: fseek() ..................................................................................................... 56 
7.13.6  ftell() ................................................................................................................................... 57 
7.14  COMANDO DE GRAVAÇÃO EM MODO TEXTO FORMATADO ........................................................................ 57 
7.15  LENDO E GRAVANDO REGISTROS ......................................................................................................... 58 
7.15.1  Escrita de um bloco de dados ............................................................................................. 58 
7.15.2  Leitura de um bloco de dados ............................................................................................. 58 
7.15.3  Utilizando os comandos de leitura e gravação de registros ............................................... 59 
7.16  FUNÇÕES PARA MANIPULAÇÃO DE BUFFERS ........................................................................................... 59 
7.17  EXERCÍCIOS..................................................................................................................................... 61 
A.  TABELA ASCII .............................................................................................................................62 

8  BIBLIOGRAFIA ............................................................................................................................63 
 
5  Linguagem de Programação C  6  Linguagem de Programação C 
   

1 TIPOS DE DADOS  unsigned long int 32 %lu 0 4.294.967.295 


float  32  %f  3,4E‐38  3.4E+38  

Flutuant
Ponto 
double 64 %lf 1,7E‐308 1,7E+308 

e
  Em C existem 5 tipos de variáveis básicas. Nos computadores da linha IBM‐PC (plataforma 32 
bits),  a  Tabela  1.1  é  válida.  Estes  tipos  de  dados  definem  a  quantidade  de  memória  que  ocupa  e  o  long double  80  %Lf  3,4E‐4932  3,4E+4932 
intervalo de valores que consegue representar.  
Tabela 1.1 ‐ Tipos de dados básicos para plataformas 32 bits  O uso de signed com inteiros é redundante. No entanto, ele é permitido porque a declaração 
default de inteiros assume um número com sinal. O uso mais importante de signed é modificar char em 
Tipo Bits Faixa Mínima
implementações em que esse tipo, por padrão, não tem sinal. Algumas implementações podem permitir 
char  8  ‐128 a 127  que unsigned seja aplicado aos tipos de ponto flutuante (como em unsigned double). Porém, isso reduz 
int 32 ‐2,147,483,648 a 2,147,483,647 a portabilidade de seu código e geralmente não é recomendado. O modificador unsigned altera o valor 
float  32  3.4E‐38 a 3.4E+38  da faixa mínima do tipo através do uso do bit mais significativo (indicador de sinal).  
double 64 1.7E‐308 a 1.7E+308
void  0  sem valor  O  tamanho,  e  conseqüentemente  o  intervalo  de  valores,  pode  variar  de 
plataforma  para  plataforma.  Por  exemplo,  o  long  double  em  algumas 
plataformas  possui  10  bytes  de  tamanho.  O  char  já  é  um  tipo  que  não 
Os  tipos  char  e  int  armazenam  números  inteiros,  enquanto  que  os  tipos  varia de plataforma. 
float e Double armazenam números de ponto flutuante (é um formato de 
representação digital de números reais).  Exemplo 1.1 
#include <stdio.h>
Com exceção de void, os tipos de dados básicos podem estar acompanhados por modificadores na  int main() {
int qtde;
declaração de variáveis. Os modificadores de tipos da linguagem C são:  char tam;
float total;
• signed;  • unsigned;  qtde = 2; tam = ‘G’;
• long;  • short.  total = 20.70;
printf(“Comprei %d camisas de tamanho %c.”, qtde, tam);
Os modificadores signed, short, long e unsigned podem ser aplicados aos tipos básicos char e int.  printf(“\nNo total, paguei R$ %f.”, custo);
return 0;
Contudo, long também pode ser aplicado à double.   }

A função printf() possui especificadores de formato que permitem mostrar  Execução: 
inteiros  short  e  long.  O  %ld, %li, %lo, %lu, %lx especificam  que o tipo de  Comprei 2 camisas de tamanho G.
No total, paguei R$ 20.70.
dado é long.  O %hd, %hi, %ho, %hu, %hx especificam que o tipo de dado é 
short.  
  As  variáveis  podem  ser  inicializadas  no  momento  em  que  se  faz  a  declaração  das  mesmas. 
O especificador de formato long pode ser ainda utilizado para tipo ponto  Pode‐se ver isto usando o programa anterior, que a execução será a mesma da versão anterior. 
flutuante  (indicando  que  segue  um  double):  %le,  %l  E,  %lf,  %lg,  e  %lG.  Exemplo 1.2 
Outro especificado de formato é o L, o qual é utilizado para associar um  #include <stdio.h>
long double: %Le, %LE, %Lf, %Lg, e %LG.  int main() {
int qtde=2;
A  Tabela  1.2  mostra  todas  as  combinações  de  tipos  de  dados  e  as  informações  sobre  tamanho,  char tam=‘G’;
formatação e intervalo.  float total=20.70;
printf(“Comprei %d camisas de tamanho %c.”, qtde, tam);
Tabela 1.2 ‐ Utilização dos tipos de dados (plataforma 32 bits)  printf(“\nNo total, paguei R$ %f.”, custo);
return 0;
  Tipo  Bits  Formatação  Intervalo  }
  printf()  Inicio  Fim 
char  8  %c  ‐128  127  Devido  às  diferenças  de  tipos  em  diferentes  máquinas  e  plataformas, 
unsigned char  8  %c  0  255  pode‐se  utilizar  a  função  sizeof()  para  descobrir  o  tamanho  real  da 
variável ou tipo. 
signed char  8  %c  ‐128  127 
short int  16  %hi  ‐32.768  32.767  Exemplo 1.3 
#include <stdio.h>
Inteiros  

signed short int  16  %hi  ‐32.768  32.767  


unsigned short int  16  %hu  0  65.535  int main() {
printf("Tipo\t\tTamanho\n");
int  32  %i  ‐2.147.483.648  2.147.483.647  printf("char\t\t%d\n",sizeof(char));
signed int  32  %i  ‐2.147.483.648  2.147.483.647  printf("int\t\t%d\n",sizeof(int));
printf("float\t\t%d\n",sizeof(float));
unsigned int  32  %u  0  4.294.967.295  printf("double\t\t%d\n",sizeof(double));
long int  32  %li  ‐2.147.483.648  2.147.483.647  printf("long int\t%d\n",sizeof(long int));
return 0;
signed long int  32  %li  ‐2.147.483.648  2.147.483.647 
7  Linguagem de Programação C  8  Linguagem de Programação C 
   

} Exemplo 1.6 
const int a=10;
Execução: 
Tipo Tamanho   O Exemplo 1.6 cria uma variável inteira chamada a, com um valor inicial 10, que seu programa 
char 1 não pode modificar.  
int 4
float 4
double 8   Um exemplo do uso do const é para verificar se uma variável em particular é modificada pelo 
long int 4 seu programa. 

1.1 ENUMERAÇÃO  1.2.2 VOLATILE 

  Enumeração  é  um  conjunto  de  constantes  inteiras  que  especifica  todos  os  valores  legais  de    O modificador volatile é usado para informar ao compilador que o valor de uma variável pode 
uma variável desse tipo pode ser. A forma geral para enumeração é:  ser alterado de maneira não explicitamente especificada pelo programa. Por exemplo, um endereço de 
uma  variável  global  pode  ser  passado  para  a  rotina  de  relógio  do  sistema  operacional  e  usado  para 
Sintaxe:  guardar o tempo real do sistema. Nessa situação, o conteúdo de uma variável é alterado sem nenhum 
comando  de  atribuição  explicito  no  programa.  Isso  ajuda  o  programa  no  sentido  de  avisar  ao 
  enum nome { lista_de_enumeração } lista_de_variáveis;  compilador que o conteúdo de uma variável é mutável, mesmo que sua referência não aparecer no lado 
esquerdo da expressão. 
  Aqui,  tanto  o  nome  da  enumeração  quanto  a  lista  de  variáveis  são  opcionais.  O  nome  da 
enumeração é usado para declarar variáveis daquele tipo. Com isso pode‐se declarar as cores    É possível usar const e volatile juntos. Por exemplo, se 0x30 é assumido como sendo o valor de 
Exemplo 1.4  uma  porta  que  é  mudado  por  condições  externas.  Para  evitar  efeitos  colaterais  deve‐se  declarar  da 
enum cores {amarelo, verde, vermelho}; seguinte forma: 
enum cores semaforo; const volatile unsigned char *port = 0x30;

  Dada essa definição e declaração, os tipos de comandos seguintes são perfeitamente válidos:  1.3 CONSTANTES 

semaforo = verde;   Uma  constante  tem  valor  fixo  e  inalterável  durante  a  execução  do  programa.  Isto  pode  ser 
if (semaforo==verde) printf(“Passagem permitida \n”);
exemplificado pelos Exemplos 3.1 e 3.2 da função printf(). 
  Para melhor compreensão da enumeração entende‐se que cada símbolo representa um valor    Em  uma  constante  caractere  é  escrita  entre  aspas  simples  (‘’),  uma  constante  cadeia  de 
inteiro. O valor do primeiro símbolo da enumeração é 0. Assim,  caracteres entre aspas duplas (“”) e constantes numéricas como o número propriamente dito. 
printf (“%d %d”, verde, vermelho); Exemplo 1.7 
  ‘C’
mostra 1 2 na tela.  “programa”
8
465.67
  Como  extensão,  pode‐se  inicializar  os  símbolos  de  forma  alternada  para  algum  problema 
específico.    Constantes em C podem ser de qualquer um dos cinco tipos de dados básicos. A maneira como 
Exemplo 1.5  cada  constante  é  representada  depende  do  seu  tipo.  Pode‐se  especificar  precisamente  o  tipo  da 
enum cores { amarelo, verde=10, vermelho }; constante numérica  através da utilização de um sufixo.  Para  tipos  em ponto flutuante coloca‐se um F 
após o número, ele será tratado como float. Se for colocado um L, ele tornar‐se‐á um long double.  Para 
  Agora os valores destes símbolos são  tipos  inteiros,  o  sufixo  U  representa  unsigned  e  o  L  representa  long.  A  Tabela  1.3  mostra  alguns 
exemplos de constantes. 
  amarelo   0 
Tabela 1.3 ‐ Exemplo de constantes 
  verde    10 
  vermelho  11  Tipo de Dado  Exemplo de Constantes 
int 1  123  21000  ‐234
1.2 MODIFICADORES DE TIPO DE ACESSO 
long int  35000L  ‐34L 
short int 10  ‐12  90
  O padrão ANSI introduziu dois novos modificadores de tipo que controlam a maneira como a 
variáveis  podem  ser  acessadas  ou  modificadas.  Esses  modificadores  são  const  e  volatile.  Devem  unsigned int  10000U  987U  40000“ 
preceder os modificadores de tipo e os nomes que eles modificam.  float  123.23F  2.34e‐3F
double  123.23  12312333  ‐0.9876324 
1.2.1 CONST  long double 1001.2L

  Variáveis do tipo const não podem ser modificadas por seu programa (por isso ela recebe um 
valor inicial).  
9  Linguagem de Programação C  10  Linguagem de Programação C 
   

  Além deste tem‐se as constantes Hexadecimais e Octais. Usam‐se tais sistemas numéricos para 
facilitar  a  programação.  Uma  constante  hexadecimal  deve  consistir  em  um  0x  seguido  por  uma 
2 OPERADORES 
constante na forma hexadecimal. Uma constante octal começa com 0. 
  A  linguagem  C  é  muito  rica  em  operadores  internos.  C  define  quatro  classes  de  operadores: 
Exemplo 1.8 
int hex = 0x80; /* 128 em decimal */ aritméticos, relacionais, lógicos e bit a bit. Além disso, C tem alguns operadores especiais para tarefas 
int oct = 012; /* 10 em decimal */ particulares. 

1.3.1 CONSTANTES PRÉ‐DEFINIDAS  2.1 OPERADORES BIT A BIT 

Em  alguns  compiladores  C,  algumas  constantes  simbólicas  já  estão  pré‐definidas.  Estas  Os  operadores  bit  a  bit  são  comumente  utilizados  para  trabalhar  com  dispositivos  (pois  os 
constantes em geral definam alguns valores matemáticos (π, π/2, e, etc.), limites de tipos etc. A seguir  mesmos utilizam bytes ou palavras codificadas para comunicação), modo de armazenamento (um byte 
são  apresentadas  algumas  (existem  muitas  outras)  constantes  simbólicas  pré‐definidas  no  compilador  pode  representar  8  informações  binárias),  e  até  compactação  de  dados  (utilização  de  bits  ociosos).  A 
Turbo C++ da Borland.  Tabela 2.1 mostra os operadores bit a bit suportados pela linguagem. 
Biblioteca Constante Valor Significado Tabela 2.1 ‐ Operadores bit‐a‐bit 
math.h M_PI 3.14159... π Operador  Ação   
math.h M_PI_2 1.57079... π/2 & E (AND)
math.h M_PI_4 0,78539... π/4 |  OU (OR)   
^ OU exclusivo (XOR)
math.h M_1_PI 0,31830... 1/π
~  Complemento de um   
math.h M_SQRT2 1,41421... √2
>> Deslocamento à esquerda
<<  Deslocamento à direita   

  Os operadores  bit  a bit só podem ser utilizados sobre um  byte  ou uma 


palavra,  isto  é,  aos  tipos  de  dados  char    e  int  e  variantes  do  padrão  C. 
Operações bit não podem ser usadas em float, double, long double, void 
ou outros tipos mais complexos.  

O operador bit a bit & executa um e lógico para cada par de bits, produzindo um novo byte ou 
palavra. Para cada bit dos operandos, o operador & retorna o bit em 1 se ambos os bits dos operandos é 
1. Caso algum bit dos operandos for 0, o operador retorna o bit 0. Este operador é mais utilizado para 
desligar bits (realizando a operação com um operando ‐ também chamado de máscara ‐ cujos bits que 
devam  ser  desligados  estejam  com  valor  0,  enquanto  que  os  outros  estão  em  1)  ou  verificar  se  um 
determinado bit  está ligado ou  desligado (realizando a operação com um operando cujo bit que deva 
ser checado esteja com valor 1, enquanto que os outros estão em 1). 
Exemplo 2.1 
unsigned char x = 7; /* 0000 0111 */
unsigned char y = 4; /* 0000 1010 */
unsigned char mascara = 252; /* 1111 1100 */
unsigned char res;

res = x & y; /* res = 0000 0010 */


res = y & mascara; /* res = 0000 1000 – desligar os bits 0 e 1 */
res = y & 2 /* res = 0000 0010 – bit ligado qdo res > 0 */
res = y & 4 /* res = 0000 0000 – bit desligado qdo res == 0 */

O operador bit a bit | executa um ou lógico para cada par de bits, produzindo um novo byte ou 
palavra. Para cada bit dos operandos, o operador | retorna o bit em 1 se algum dos bits dos operandos 
é 1. Caso ambos os bits dos operandos for 0, o operador retorna o bit 0. Este operador é mais utilizado 
para ligar (realizando a operação com um operando cujos bits que devam ser ligados estejam com valor 
1, enquanto que os outros que não devem ser alterados estão em 0). 
Exemplo 2.2 
unsigned char x = 7; /* 0000 0111 */
unsigned char y = 4; /* 0000 1010 */
unsigned char mascara = 1; /* 0000 0001 */
unsigned char res;
11  Linguagem de Programação C  12  Linguagem de Programação C 
   

5. 2
res = x | y; /* res = 0000 1111 */ 6. 32
res = y | mascara; /* res = 0000 1011 – ligar o bit 0 */ 7. 2
res = x | 8; /* res = 0000 1111 – ligar o bit 3 */ 8. 40

O operador bit a bit ^ executa um ou‐exclusivo (XOR) lógico para cada par de bits, produzindo    O Exemplo 2.5 mostra que os operadores de deslocamento podem ser utilizados com variáveis, 
um novo byte ou palavra. Para cada bit dos operandos, o operador ^ retorna o bit em 1 se somente um  constantes e até mesmo expressões. Entretanto, deve‐se verificar a precedência de operadores quando 
dos bits dos operandos é 1. Caso os bits dos operandos forem iguais, o operador retorna o bit 0. Este  trabalhando com expressões.  
operador  é  mais  utilizado  para  inverter  bits  (realizando  a  operação  com  um  operando  cujos  bits  que  Exemplo 2.6 
devam ser invertidos estejam com valor 1, enquanto que os outros estão em 0).  unsigned char x = 7; /* 0000 0111 */
Exemplo 2.3  unsigned char res;
unsigned char x = 7; /* 0000 0111 */
unsigned char y = 4; /* 0000 1010 */ res = x << 1; /* res = 00001110 res = 14 */
unsigned char mascara = 3; /* 0000 0011 */ res = x << 3; /* res = 01110000 res = 112 */
unsigned char res; res = x << 2; /* res = 11000000 res = 192 */
res = x >> 1; /* res = 01100000 res = 96 */
res = x ^ y; /* res = 0000 1101 */ res = x >> 2; /* res = 00011000 res = 24 */
res = y ^ mascara; /* res = 0000 1001 – inverter os bits 0 e 1 */
res = y ^ 8; /* res = 0000 0010 – inverter o bit 3 */ Não  confunda  os  operadores  relacionais  &&  e  ||  com  &  e  |, 
respectivamente. Os operadores relacionais trabalham com os operandos 
O  operador  bit  a  bit  ~  executa  um  não  lógico  (complemento  de  1)  no  valor  a  sua  direita  como  um  único  valor  lógico  (verdadeiro  ou  falso),  e  eles  produzem 
(operador unário), produzindo um novo byte ou palavra com os bits invertidos.  somente  dois  valores  0  ou  1.  Os  operadores  bit  a  bit  podem  produzir 
Exemplo 2.4  valores arbitrários pois a operação é realizada em nível de bit.   
unsigned char x = 7; /* 0000 0111 */
unsigned char res;
2.1.1 OPERADORES BIT A BIT DE ATRIBUIÇÃO 
res = ~x; /* res = 1111 1000 */
res = ~127; /* res = 1000 0000 */   A Tabela 2.2 mostra os operadores bit a bit de atribuição suportados pela linguagem. 
Tabela 2.2 ‐ Operadores aritméticos de atribuição 
Os  operadores  de  deslocamento,    e  ,  movem  todos  os  bits  de  um  operando  para  a 
esquerda ou direita, respectivamente. A forma geral do comando de deslocamento é:  Operador Ação
x &= y  x = x & y   
Sintaxe:  x |= y x = x | y
operando << número de bits
operando >> número de bits x ^= y  x = x ^ y   
x ~= y x = x ~ y
  Conforme  os  bits  são  deslocados  para  um  direção,  zeros  são  utilizados  para  preencher  a 
x >>= y  x = x >> y   
extremidade  contrária  da  direção  (isto  é,  deslocamento  para  a  direita  coloca  zeros  os  bits  mais 
significativos). Estes operadores são utilizados para recebimento e envio de dados bit a bit (conversores  x <<= y x = x << y
analógico/digitais,  portas  seriais),  e  multiplicação  (deslocamento  para  a  esquerda)  e  divisão  inteira 
(deslocamento para a direita) por 2.     As expressões com este operadores são mais compactas e normalmente produzem um código 
de máquina mais eficiente.  
Exemplo 2.5 
#include <stdio.h>
A execução da operação bit a bit  ocorre por último após a avaliação da 
int main() { expressão à direita do sinal igual.  
unsigned char x=7;
printf("1. %2i\n",x >> 1);
printf("2. %2i\n",x << 1); 2.2 OPERADOR ? :  
printf("3. %2i\n",x >> 2);
printf("4. %2i\n",x << 2);
printf("5. %2i\n",8 >> 2);   O operador ? substitui sentenças da forma Se‐então‐senão. 
printf("6. %2i\n",8 << 2);
printf("7. %2i\n",x+3 >> 2); Sintaxe: 
printf("8. %2i\n",x+3 << 2); Exp1 ? Exp2 : Exp3;
return 0;
}
  Onde Exp1, Exp2 e Exp3 são expressões. Onde Exp1 é avaliada e se a mesma for verdadeira, 
então Exp2 é avaliada e se torna o valor da expressão. Se Exp1 é falso, então Exp3 é avaliada e se torna 
Execução: 
1. 3
o valor da expressão. 
2. 14 Exemplo 2.7 
3. 1 x = 10;
4. 28 y = x > 9 ? 100 : 200;
13  Linguagem de Programação C  14  Linguagem de Programação C 
   

  No  Exemplo  2.7,  y  recebe  o  valor  100,  porque  x  (valor  é  10)  é  maior  que  9.  Uma  expressão  5º  < <= > >= 
equivalente seria  6º  == != 
7º &
x = 10; 8º  ^ 
if (x > 9) y = 100;
else y = 200; 9º   ! 
10º  && 
11º  || 
2.3 OPERADORES DE PONTEIROS & E *  12º  ?: 
13º = *= /= %= += ‐= &= |= ^= ~= <<= >>=
  Um  ponteiro  é  um  endereço  na  memória  de  uma  variável.  Uma  variável  de  ponteiro  é  uma  14º  , 
variável especialmente declarada para guardar um ponteiro para seu tipo especificado.    

  O  primeiro  operador  de  ponteiro  é  &.  Ele  é  um  operador  unário  que  devolve  o  endereço  na  2.5 EXERCÍCIOS 
memória de seu operando. Por exemplo, 
1. Faça  um  programa  que  leia  um número  binário  de  16 bits, armazene‐o, e mostre o valor em 
m = &cont; hexadecimal, decimal e octal. 
2. Faça uma função que receba um valor do tipo int como parâmetro e escreva na tela os valores 
atribui o endereço de memória da variável cont em m.  
do bits do valor 
  Este tipo de operando não pode ser utilizado em três casos:  3. Faça uma função que receba um valor do tipo int como parâmetro e devolva um novo valor int 
com a ordem dos bits invertidos.  
1. &(cont + 4) ‐ sempre se associa a uma variável e não expressão; 
4. Faça  uma  função  que  receba  um  valor  do  tipo  char  como  parâmetro  e  devolva  quantos  bits 
2. &3 ‐ constantes não são válidas; 
estão ligados. 
3. variáveis  declaradas  com  classe  de  armazenamento  register  (não  existe  endereço  para 
registrador).  5. Faça uma função crossover(n, m, pontoDeCorte) que retorna um inteiro que representa os bits 
mais significativos de n e os bits menos significativos de m, de acordo com o ponto de corte, 
  O segundo operador é *. Ele é um operador unário que devolve o valor da variável localizada  que é a posição onde o número será partido. Por exemplo: 
no endereço  que o segue. Por exemplo, se m contém o endereço da variável cont, 
int main(){
q = *m; crossover(10,69,2); // retorna 13 = 0000 1101
// 10 = 0000 1010, teremos que pegar desse nº, os bits +
significativos, ou seja: 0000 1???
coloca o valor de cont em q.  // 69 = 0100 0101, teremos que pegar desse nº, os bits menos
significativos, ou seja: ???? ?101
crossover(10,69,3); // retorna 5 = 0000 0101, ou seja, metade de um +
  Os seguintes operadores * e & colocam o valor 10 na variável chamada target. O resultado (o  metade do outro
valor 10) deste programa é mostrado na tela.  }
Exemplo 2.8  6. Faça  a  função  rodaEsquerda(int  n,  int  nBits)  que  retorna  o  n  com  nBits  rotações  à  esquerda. 
#include <stdio.h> Perceba  que  uma  rotação  não  deve  perder  bits,  ao  contrário  do  operador  de  deslocamento. 
int main() { Trabalhe pensando apenas nos 8 bits para n. 
int target, source;
int *m;
source = 10; int main(){
m = &source; unsigned char x;
target = *m; x = rodaEsquerda (4, 2); // se 4 = 0000 0100,
printf(“%d”,target); // então rodaEsquerda (4,2)== 0000 1000
return 0; }
} 7. Escreva  uma  função  criptografa(int  n)  que  recebe  um  inteiro  n  com  8  bits  (índices: 
7,6,5,4,3,2,1,0) e que retorna esse inteiro embaralhando esses bits para a seguinte seqüência 
2.4 PRECEDÊNCIA DOS OPERADORES  (7,5,3,1,6,4,2,0) 

int main(){
  A Tabela 2.3 mostra a precedência dos operadores da linguagem C.  criptografa(73); // se 73 = 0100 1001,
Tabela 2.3: Precedência dos operadores   // então criptografa(73) == 0010 1001 == 41
}
Precedência  Operador 
1º  () [] ‐>  8. Faça a função descriptografa(int n) que faz o processo invertido da questão 7. 
2º  ‐ (menos unário) ++ ‐‐ ! ~ & (endereço) * (ponteiro)  9. Faça uma função que receba um vetor de 32 posições de inteiros (valores 0 e 1) e que retorne 
3º  * / %  um valor com os bits ligados ou desligados conforme o conteúdo de cada posição do vetor.  
4º  + ‐
4º  << 
15  Linguagem de Programação C  16  Linguagem de Programação C 
   

3.1.1 CORPO  DA  FUNÇÃO  ANTES  DO  PROGRAMA  PRINCIPAL  (NO  MESMO 
3 FUNÇÕES  ARQUIVO) 

  A forma geral de uma função é:  Quando  escrevemos  a  definição  de  uma  função  antes  do  programa  principal  e  no  mesmo 
arquivo deste, nenhuma outra instrução é necessária.  
Sintaxe: 
Exemplo 3.4 
  tipo_função nome_função (declaração_parâmetros) {  float media2(float a, float b) { // função
    corpo_função;  float med;
  }  med = (a + b) / 2.0;
return(med);
Exemplo 3.1  }
int soma(int x, int y) {
... int main() { // programa principal
} float num_1, num_2, med;
puts(”Digite dois números:”);
scanf(”%f %f”, &num_1, &num_2);
As  funções  retornam  um  valor  (do  tipo  indicado  em  tipo_função).  O  valor  retornado  pela  med = media2(num_1, num_2); // chamada da função
função é dado pelo comando return (o valor retornado pode ou não ser utilizado).  printf(”\nA media destes números é %f”, med);
return 0;
  Existem  dois  tipos  de  passagem  de  argumentos:  por  valor  e  por  referência.  A  segunda  é  }
realizada através de apontadores. 
3.1.2 CORPO  DA  FUNÇÃO  DEPOIS  DO  PROGRAMA  PRINCIPAL  (NO  MESMO 
Exemplo 3.2 
  int pot(int x, int n) { /* x elevado na n potência */ ARQUIVO) 
int p;
for(p=1;n>0;n--) Quando  escrevemos  a  definição  de  uma  função  depois  do  programa  principal  e  no  mesmo 
p *= x;
return p; arquivo  deste,  devemos  incluir  um  protótipo  da  função  chamada.  Um  protótipo  é uma  instrução que 
} define  o  nome  da  função,  seu  tipo  de  retorno  e  a  quantidade  e  o  tipo  dos  argumentos  da  função.  O 
protótipo  de  uma  função  indica  ao  compilador  quais  são  as  funções  usadas  no  programa  principal  os 
  No Exemplo 3.2, os argumentos foram passados por valor e a função retorna um valor do tipo  tipo. A sintaxe geral para isto é a seguinte: 
inteiro. A chamada seria: 
Sintaxe: 
a = pot(10,2); int main() { // programa principal
tipo nomef(...); // protótipo da função
...
  No Exemplo 3.3, nenhum valor é retornado (por isso usa‐se o tipo void) mas é realizado uma  var = nomef(...) // chamada a função
troca dos valores das variáveis, necessitando de uma passagem de parâmetros por referência.  ...
}
Exemplo 3.3 
/* troca os valores de duas variáveis*/ tipo nomef(...){ // definição da função
void troca(int *a, *b) { [corpo de função]
int aux; }
aux = *a;
*a = *b; Exemplo 3.5 
*b = aux; #include <stdio.h>
} int main() { // programa principal
float media2(float,float); // protótipo de media2()
float num_1, num_2, med;
  A chamada para esta função seria:  puts(”Digite dois números:”);
scanf(”%f %f”, &num_1, &num_2);
int x=1,y=2; med = media2(num_1, num_2); // chamada a função
troca(&x,&y); printf(”\nA media destes números é %f”, med);
}
  Na  passagem  de  parâmetros  por  referência  é  passado  explicitamente  o  endereço  da  variável  float media2(float a, float b){ // função media2()
float med;
com  o  uso  do  operador  &.  Quando  o  argumento  for  uma  matriz  automaticamente  será  passado  o  med = (a + b) / 2.0;
endereço da matriz para a função.  return(med);
}

A linguagem C aceita chamadas recursivas de funções. 
Protótipo de uma função nada mais é que a declaração da função sem o 
3.1 LOCALIZAÇÃO DAS FUNÇÕES  seu corpo. Por isso, a lista de argumentos do protótipo podem ser escritas 
apenas com os tipos dos argumentos.   
Existem  basicamente  duas  posições  possíveis  para  escrevermos  o  corpo  de  uma  função:  ou 
antes  ou  depois  do  programa  principal.  Podemos  ainda  escrever  uma  função  no  mesmo  arquivo  do  3.1.3 CORPO DA FUNÇÃO ESCRITO EM ARQUIVO SEPARADO 
programa principal ou em arquivo separado.   
17  Linguagem de Programação C  18  Linguagem de Programação C 
   

Em C, como em muitas outras linguagens, é permitido que o usuário crie uma função em um  Protótipos permitem que C forneça uma verificação mais forte dos tipos. Protótipos de funções 
arquivo  e  um  programa que  a  chame  em  outro  arquivo  distinto.  Esta  facilidade  permite  a  criação de  ajudam a detectar erros antes que eles ocorram. É verificado número de parâmetros, compatibilidade 
bibliotecas  de  usuário:  um  conjunto  de  arquivos  contendo  funções  escritas  pelo  usuário.  Esta  de tipos, entre outras. 
possibilidade é uma grande vantagem utilizada em larga escala por programadores profissionais.  
  Existem três tipos de declaração de protótipos: 
Quando  escrevemos  a  definição  de  uma  função  em  arquivo  separado  do  programa  principal 
devemos  incluir  este  arquivo  no  conjunto  de  arquivos  de  compilação  do  programa  principal.  Esta  Sintaxe                Exemplo 
inclusão  é  feita  com  a  diretiva  #include.    Esta  diretiva,  vista  nas  seções  2.4.2  e  3.7.1,    instrui  o  tipo_função nome_função ();          int pot(); 
compilador para incluir na compilação do programa outros arquivos que contem a definição das funções  tipo_função nome_função (lista_tipo_argumentos);      int pot(int,int); 
de usuário e de biblioteca.   tipo_função nome_função (lista_tipo_nome_argumentos);    int pot(int x, int y); 
 
Sintaxe: 
#include ”path” // inclusão da função
int main() { // programa principal 3.4 RETORNO DE PONTEIROS 
...
var = nomef(...) // chamada a função
...
  Ponteiros  para  variáveis  não  são  variáveis  e  tampouco  inteiros  sem  sinal.  São  endereços  na 
} memória. A forma geral é: 
Sintaxe: 
Na diretiva #include, indicamos entre aspas duplas o caminho de localização do arquivo onde    tipo_função *nome_função(lista_de_argumentos); 
está definida a função chamada. 
Exemplo 3.6  3.5 CLASSES DE ARMAZENAMENTO 
#include ”c:\tc\userbib\stat.h” // inclusão da função
int main() { // programa principal
float num_1, num_2, med;   São quatro as classes de armazenamento de variáveis C: 
puts(”Digite dois números:”);
scanf(”%f %f”, &num_1, &num_2); • auto     (automáticas) 
med = media2(num_1, num_2); // chamada a função
printf(”\nA media destes números é %f”, med);
• extern    (externas) 
return 0; • static    (estáticas) 
} • register    (em registradores) 

3.2 ARGUMENTOS PARA FUNÇÃO MAIN() 
3.5.1 AUTO 
  A  função  main()  aceita  argumentos  para  a  passagem  de  parâmetros  realizada  através  da 
chamada do programa. Os dois argumentos são:    As variáveis declaradas nos exemplos anteriores só podem ser acessadas somente às funções 
onde estão declaradas. Tais variáveis são chamadas locais ou automáticas, são criadas quando a função 
argc: contador de argumentos;  é chamada e destruídas quando a função ou o bloco de código termina a sua execução. 

argv: vetor de argumentos (vetor de apontadores para strings).    As  variáveis  declaradas  dentro  de  uma  função  são  automáticas  por  padrão.  A  classe  de 
variáveis automáticas pode ser explicitada usando‐se a palavra auto. O código 
Sintaxe:   
main(int argc, char *argv[]) int main() { int main() {
auto int x; int x;
...
é equivalente a  ...
  É importante lembrar que  
} }
Exemplo 3.7   
#include <stdio.h>
int main(int argc, char *argv[]) {
int cont; 3.5.2 EXTERN 
printf(“Foram encontrados %d argumentos \n”,argc -1);
for (cont=1;cont < argc;cont++)
printf(“Argumento %d: %s \n”, cont, argv[cont]);   Toda variável declarada fora de qualquer função têm a classe de armazenamento extern. 
return 0;
}   Como pode‐se somente uma única vez declarar uma variável global, ao usar diversos arquivos 
para um mesmo programa (por ser grande, por exemplo) deve‐se utilizar a declaração extern nos outros 
  O primeiro argumento (argv[0]) é o nome do programa.  arquivos  onde  a  variável  é  utilizada.  Se  não  proceder  assim,  o  compilador  acusará  um  erro  de 
duplicidade de variável. 
3.3 PROTÓTIPO DE FUNÇÕES 
Exemplo 3.7 
int main() {
  O padrão ANSI C expandiu a declaração tradicional de função, permitindo que a quantidade e  Arquivo 1  ...
os tipos dos argumentos das funções sejam declarados. A definição expandida é chamada protótipo de  int x,y; }
função. Protótipos de funções não faziam parte da linguagem C original.   char ch;
19  Linguagem de Programação C  20  Linguagem de Programação C 
   

void func1() { void func22() {   A  declaração  acima  define  a  função  AREA()  a qual  calcula  a  área  de  uma esfera.  A  vantagem 
x = 123; x = y / 10;
} } desta declaração é a não tipagem do argumento x. Não deve haver espaços entre o nome da macro e 
seus identificadores.  
void func23() {
Arquivo 2  y = 10; 3.7 FUNÇÕES RECURSIVAS 
extern int x,y; } 
extern char ch;
Uma  função  é  dita  recursiva  quando  se  é  definida  dentro  dela  mesma.  Isto  é,  uma  função  é 
  No arquivo 2, a lista de variáveis globais foi copiada do arquivo 1 e o especificador extern foi  recursiva quando dentro dela está presente uma instrução de chamada a ela própria. 
adicionado às declarações. O especificador extern diz ao compilador que os tipos de variável e nomes 
que  o  seguem  foram  declarados  em  outro  lugar.  Isto  é,  o  compilador  não  reserva  um  espaço  de  Exemplo 10.9 
// imprime uma frase invertida . Usa recursão
memória para essas variáveis declaradas com o especificador extern na certeza de estarem declaradas  #include <stdio.h>
em outro módulo.  #include <conio.h>
void inverte()
int main() {
3.5.3 STATIC  clrscr( );
inverte( );
return 0;
  Dentro  de  sua  própria  função  ou  arquivo,  variáveis  static  são  variáveis  permanentes.  Ao  }
contrário das variáveis globais, elas não são reconhecidas fora de sua função ou arquivo, mas mantêm 
seus  valores  entre  chamadas.  O  especificador  static  tem  efeitos  diferentes  em  variáveis  locais  e  em  void inverte() {
char ch ;
variáveis globais.  if ((ch=getche( )) != ‘\r’ ) inverte();
  scanf(“%c”,ch)
}

3.5.3.1 VARIÁVEIS LOCAIS STATIC  3.8 EXERCÍCIOS 

  Quando o modificador static é aplicado a uma variável local, o compilador cria armazenamento  1. Escreva  um  programa  que  receba  como  parâmetro  um  índice  (float).  Após,  ler  uma  seqüência  de 
permanente para ela quase da mesma forma como cria armazenamento para uma variável global. Em  números  (a  qual  termina  por  0)  e  exibir  o  seu  valor  multiplicado  pelo  índice.  A  função  que 
termos simples, uma variável local static é uma variável local que retém seu valor entre chamadas. Mas  transforma uma string em um float é atof(char *x). 
ela só é reconhecida apenas no bloco em que está declarada. 
2. Escreva uma função que receba um caractere como argumento e que retorne a letra maiúscula se a 
mesma for minúscula. funções: islower(int ch), toupper(int ch).  
3.5.3.2 VARIÁVEIS GLOBAIS STATIC 
3. Existe um algoritmo interessante para se obter a raiz quadrada de um número quando ela é exata. 
  Quando o modificador static é aplicado a uma variável global, o compilador cria uma variável  Para isso, basta subtrair  números ímpares consecutivos do número do qual se deseja retirar a raiz 
que é reconhecida apenas no arquivo na qual a mesma foi declarada.  quadrada. O número de vezes será a raiz do número. Por exemplo:  
25 = 25 − 1 − 3 − 5 − 7 − 9 = 0  
3.5.4 REGISTER 
No exemplo, subtraíram‐se de 25 os 5 primeiros números ímpares consecutivos até que se chegasse 
  A  classe  de  armazenamento  register  indica  que  a  variável  associada  deve  ser  guardada  0. Assim, a raiz quadrada de 25 é 5.  Escreva uma função que  receba  um inteiro n e retorne a raiz 
fisicamente numa memória de acesso muito mais rápido, chamada registrador. No caso do IBM‐PC pode  quadrada  de  n.  Por  exemplo,  se  a  função  receber  49,  ele  retornará  7.  O  calculo  da  raiz  quadrada 
ser colocado o tipo int e char associado a register pois o registrador tem apenas 16 bits.   deverá  ser  feito  usando  o  algoritmo  acima,  sem  usar  qualquer  função  pré‐existente  de  alguma 
biblioteca C.  
  Basicamente, variáveis register são usadas para aumentar a velocidade de processamento. Por 
exemplo, variáveis de uso freqüente como variáveis de laços e argumentos de funções.  4. Seja o seguinte programa: 
3.6 DIRETIVA #DEFINE  #include<stdio.h>
void x(int n){
int i, resto;
  A diretiva #define pode ser usada para definir constantes simbólicas com nomes apropriados.  i = n;
Por exemplo, a constante PI pode ser definida com o valor 3.14159.  do{
resto = i%16;
#define PI 3.14159 i=i/16;
switch(resto){
case 10: printf("A"); break;
  Só  pode  ser  escrito  um  comando  destes  por  linha,  e  não  há  ponto‐e‐vírgula  após  qualquer  case 11: printf("B"); break;
diretiva do pré‐processador.  case 12: printf("C"); break;
case 13: printf("D"); break;
case 14: printf("E"); break;
  Esta diretiva é usada para definir macros com argumentos.   case 15: printf("F"); break;
default: printf("%d", resto);
#define AREA(x) (4*PI*x*x) }
21  Linguagem de Programação C  22  Linguagem de Programação C 
   

}while(i>0); onde  
printf("\n");
} Δv = v f − vi  
void main(){
int N; é a variação da velocidade ou a velocidade final menos a velocidade inicial. Escreva uma função em C 
scanf("%d",&N); que  receba  como  parâmetros  a  velocidade  inicial,  a  velocidade  final  e  o  intervalo  de  tempo 
x(N); correspondente  e  retorne  a  aceleração.  Mostre,  também,  uma  função  main()  que  chame  essa 
}
função.  
O  que  será  escrito  na  tela,  supondo  que  o  valor  fornecido  para  N  seja  10846?  Mostre  o  teste  de 
11. O valor de π/2 pode ser calculado pela seguinte série de produtos:  
mesa completo utilizado para determinar a saída.  
π 22446688
= L
5. Simule  a  execução  do  programa  abaixo  mostrando  todas  as  mudanças  de  valores  de  variáveis  e  o  2 13355778
resultado da impressão.  
#include<stdio.h> Escreva uma função em C que receba como argumento um número inteiro n e retorne o valor de π 
int perf(long int N){ calculado através da série acima com n termos. 
long int i, divs=0;
for(i=1; i<= N/2; i++) 12. Um aço é classificado de acordo com o resultado de três testes, que devem verificar se ele satisfaz às 
if (N%i == 0) divs = divs + i;
if (divs == N) return 1; seguintes especificações:  
else return 0;
} • Teste 1: conteúdo de carbono abaixo de 7%; 

void main(){ • Teste 2: dureza Rokwell maior que 50; 


long int x=14;
if (perf(x)==1) printf("%d é perfeito",x); • Teste 3: resistência à tração maior do que 80.000 psi. 
else printf("%d não é perfeito",x);
} O aço recebe grau 10 se passar pelos três testes; 9, se passar apenas nos testes 1 e 2; 8, se passar no 
teste 1; e 7, se não passou nos três testes.  Escreva uma função em C que o conteúdo de carbono 
(em %), a dureza Rokwell e a resistência à tração (em psi) de uma amostra de aço e retorne o grau 
6. Escreva  uma  função  em  C  que  receba  como  argumentos  a  altura  (alt)  e  o  sexo  de  uma  pessoa  e 
obtido.  
retorne o seu peso ideal. Para homens, calcular o peso ideal usando a fórmula 
PI h= 72.7× alt − 58  
13. Escreva uma função em C que receba um número n e retorne 1 se a soma dos dígitos formantes de n 
e , para mulheres,  for 10; 0 caso contrário. Por exemplo, se o valor de n recebido for 145 a função retorna 1. 
PI m= 62.1× alt − 44.7 .
14. Escreva  uma  função  em  C  que  receba  um  numero  e  retorne  seu  fatorial.  O  fatorial  de  n  é 
representado por n! sendo que 
7. Escreva uma função em C com o seguinte protótipo 
long int multiplicatorio(int i, int n)
0! = 1
1! = 1
A função deve retornar o multiplicatório de i a n. Por exemplo, a chamada  2! = 1×2 = 2
multiplicatorio(3,10) 3! = 1×2×3 = 6

retorna 1814400 (3×4×5×6×7×8×9×10).  4! = 1×2×3×4 = 24
5! = 1×2×3×4×5 = 120
8. Escreva uma função em C com o seguinte protótipo 
long int somatório(int i, int n)

A função deve retornar o somatório de i a n. Por exemplo, a chamada 
somatório(3,10)

retorna 52 (3+4+5+6+7+8+9+10). 

9. Escreva uma função em C que receba dois números e retorne o maior deles. 

10. A  aceleração  é  a  taxa  de  variação  da  velocidade  em  relação  ao  tempo,  isto  é,  a  razão  entre  a 
variação da velocidade e o intervalo de tempo. Matematicamente,  
Δv
a=  
Δt
23  Linguagem de Programação C  24  Linguagem de Programação C 
   

  O incremento é sempre realizado do tamanho básico de armazenamento do tipo base. Isto é, 
4 PONTEIROS  se o tipo base for um inteiro e incrementarmos em uma unidade, o apontador apontará para o próximo 
inteiro  (no  caso do  inteiro ocupar 2 bytes o incremento será de dois bytes), no caso de um caractere 
  Para uma boa utilização dos ponteiros deve‐se compreender corretamente o seu uso. Existem  (char) será de um byte. 
três  razões para  isso:  primeiro, ponteiros fornecem os  meios pelos quais as funções podem modificar  Exemplo 4.2 
seus  argumentos;  segundo,  eles  são  usados  para  suportar  as  rotinas  de  alocação  dinâmica  de  C,  e  int *ptri=3000;
terceiro, o uso de ponteiros para aumentar a eficiência de certas rotinas.  char *ptrc=4000;
float *ptrf=4000;
ptri++; /* ptri apontará para o endereço 3002 */
  Por  ser    um  dos  aspectos  mais  poderosos  da  linguagem  também  são  os  mais  perigosos.  Por  ptrc++; /* ptrc apontará para o endereço 4001 */
erros no uso de ponteiros (como a não inicialização de ponteiros ‐ ponteiros selvagens) podem provocar  ptrf++; /* ptrf apontará para o endereço 5004 */
quebra do sistema.  ptri = ptri - 10; /* ptri apontará para o endereço 2982 */
ptrc = ptrc - 10; /* ptrc apontará para o endereço 3991 */
ptrf = ptrf - 10; /* ptrf apontará para o endereço 4964 */
  Por  definição,  um  ponteiro  é  uma  variável  que  contém  um  endereço  de  memória.  Esse 
endereço é normalmente uma posição de outra variável na memória.     Além  de  adição  e  subtração  entre  um  ponteiro  e  um  inteiro,  nenhuma  outra  operação 
aritmética pode ser efetuada com ponteiros. Isto é, não pode multiplicar ou dividir ponteiros; não pode 
  Uma declaração de ponteiros consiste no tipo base, um "*" e o nome da variável. A forma geral  aplicar  os  operadores  de  deslocamento  e  de  mascaramento  bit  a  bit  com  ponteiros  e  não  pode 
é:  adicionar ou subtrair o tipo float ou o tipo double a ponteiros. 
tipo *nome; 
Não se altera o valor de um ponteiro constante (ponteiro para um tipo de 
onde tipo é qualquer tipo válido em C e nome é o nome da variável ponteiro.  dado  básico  ‐  int,  float  double,  …),  somente  de  um  ponteiro  variável 
(ponteiro de estruturas complexas ‐ vetores, matrizes, strings, …). 
  O  tipo  base  do  ponteiro  define  que  tipo  de  variáveis  o  ponteiro  pode  apontar.  Basicamente, 
qualquer  tipo  ponteiro  pode  apontar  para  qualquer  lugar,  na  memória.  Mas,  para  a  aritmética  de  4.2 INICIALIZAÇÃO DE PONTEIROS 
ponteiros é feita através do tipo base.  
  Após  um  ponteiro  ser  declarado,  mas  antes  que  lhe  seja  atribuído  um  valor,  ele  contém  um 
  Os operadores utilizados são * e &, como já foi explicado na seção 6.6.  valor desconhecido. Ao usar este ponteiro antes de inicializar, provavelmente provocará uma falha do 
programa ou até do sistema operacional. 
4.1 EXPRESSÕES COM PONTEIROS 
  Como  um  ponteiro nulo  é  assumido  como  sendo não  usado,  pode‐se utilizar  o ponteiro nulo 
  Nesta seção serão analisados alguns aspectos especiais de expressões com ponteiros.  para fazer rotinas fáceis de codificar e mais eficientes. Por exemplo, pode‐se utilizar um ponteiro nulo 
para marcar o fim de uma matriz de ponteiros. Uma rotina que acessa essa matriz sabe que chegará ao 
final  ao  encontrar  o  valor  nulo.  A  função  search(),  mostrada  no  Exemplo  4.3,  ilustra  esse  tipo  de 
4.1.1 ATRIBUIÇÃO DE PONTEIROS  abordagem. 

  Como  é  o  caso  com  qualquer  variável,  um  ponteiro  pode  ser  usado  no  lado  direito  de  um  Exemplo 4.3 
/* procura um nome */
comando de atribuição para passar seu valor para outro ponteiro.  search(char *p[], char *name)
{
Exemplo 4.1 
register int t;
#include <stdio.h>
for (t=0;p[t];++t)
void main() {
if(!strcmp(p[t],name)) return t;
int x;
return -1; /* não encontrado */
int *p1,*p2; /* declaração do ptr p1 e p2 com o tipo base int. */
}
p1 = &x;
p2 = p1;
printf(“%p”,p2); /* escreve o endereço de x, não seu valor */   O  laço  for  dentro  de  search()  é  executado  até  que  seja  encontrada  uma  coincidência  ou  um 
return 0; ponteiro nulo. Como o final da matriz é marcado com um ponteiro nulo, a condição de controle do laço 
}
falha quando ele é atingido. 
  Agora, tanto p1 quanto p2 apontam para x. O endereço de x é mostrado usando o modificador 
  Outra  utilização de inicialização de ponteiros é a inicialização de strings. Isto pode ser levado 
de  formato  de  printf()  %p,  que  faz  com  que  printf()  apresente  um  endereço  no  formato  usado  pelo 
como uma variação no tema de inicialização usado na variável argv. 
computador hospedeiro.   
Exemplo 4.4 
  char *p= “alo mundo \n”;

  O  ponteiro  p  (Exemplo  4.4)  não  é  uma  matriz,  mas  como  o  compilador  C cria  uma tabela  de 
4.1.2 ARITMÉTICA DE PONTEIROS  strings, a constante  string é colocada em tal tabela sendo que a mesma pode ser utilizada em todo o 
programa como se fosse uma string comum. Por isso, inicializar uma matriz de strings usando ponteiros 
  Existem  apenas  duas  operações  aritméticas  que  podem  ser  usadas  com  ponteiros:  adição  e  aloca menos memória que a inicialização através de matriz. 
subtração. Os operadores permitidos no caso seriam: +, ‐, ++, ‐‐. 
Exemplo 4.5 
25  Linguagem de Programação C  26  Linguagem de Programação C 
   

#include <stdio.h> register int i;


#include <string.h> for(i=0; i<20; i++)
char *p=“alo mundo”; scanf(“%d”,(p+i));
int main() { }
register int t;
printf(p); void mostra_tab(int *p) {
for (t=strlen(p) - 1; t > -1; t--) printf(“%c”,p[t]); register int i;
return 0; for(i=0; i<20; i++)
} printf(“%d”,*(p+i));
}

4.2.1 COMPARAÇÃO DE PONTEIROS  void main(void) {


int mat[20];
le_tab(mat);
  É possível comparar dois ponteiros em uma expressão relacional.   mostra_tab(mat);
}
Exemplo 4.6 
if (p<q) printf(“p aponta para uma memória mais baixa que q /n”);
4.3.1 MATRIZES DE PONTEIROS 
  Geralmente, a utilização de comparação  entre ponteiros é  quando os mesmos apontam para 
um objeto comum.  Exemplo disto é a pilha, onde  é verificado se os ponteiros de início e fim da pilha    Ponteiros podem ser organizados em matrizes como qualquer outro tipo de dado. A declaração 
estão apontando para a mesma posição de memória,  significando pilha vazia.   de uma matriz de ponteiros int, de tamanho 10, é 
int *x[10];
4.3 PONTEIROS E MATRIZES 
  Para atribuir o endereço de uma variável inteira, chamada var, ao terceiro elemento da matriz 
  Existe  uma  estreita  relação  entre  matrizes  e  ponteiros.  Pois  C  fornece  dois  métodos  para  de ponteiros, deve‐se escrever 
acessar  elementos  de  matrizes:  aritmética  de  ponteiros  e  indexação  de  matrizes.  Aritmética  de 
ponteiros  pode  ser  mais  rápida  que  indexação  de  matrizes.  Normalmente  utilizam‐se  ponteiros  para  x[2] = &var;
acessar elementos de matrizes devido a velocidade de acesso. 
para encontrar o valor de var, escreve‐se 
Exemplo 4.7 
char str[80], *p1; *x[2];
p1 = str;

  Se for necessário passar uma matriz de ponteiros para uma função, pode ser usado o mesmo 
Para acessar a string str pode‐se utilizar estes dois mecanismos 
método que é utilizado para passar outras matrizes ‐ simplesmente chama‐se a função com o nome da 
str[4] /* indexação de matrizes */ matriz sem qualquer índice. 
Exemplo 4.10 
ou  void display_array(int *q[]) {
register int t;
*(p1 + 4) /* aritmética de ponteiros */ for (t=0; t<10; t++)
printf(“%d”,*q[t]);
}
Os dois comandos devolvem o quinto elemento. 
  Lembre‐se  de  que  q  não  é  um  ponteiro  para  inteiros;  q  é  um  ponteiro  para  uma  matriz  de 
*(matriz + índice) é o mesmo que matriz[índice].  ponteiros para inteiros. Portanto, é necessário declarar o parâmetro q como uma matriz de ponteiros 
para  inteiros,  como  é  mostrado  no  Exemplo  4.10.  Isto  é,  não  é  uma  passagem  de  parâmetros  por 
  Para  uma  melhor  compreensão  ou  facilidade  de  programação  as  funções  de  indexação  referência por dois motivos: primeiro, matriz como argumento de função é automaticamente passada 
trabalham com ponteiros (como mostra o Exemplo 4.8 a implementação da função puts()).  por  referência por questão da  implementação da linguagem, e  segundo,  é uma matriz de ponteiros e 
Exemplo 4.8  conseqüentemente sua declaração é caracterizada pelo asterisco na frente do nome da variável. 
/* Indexa s como uma matriz */
void put(char *s) {
register int t;
  Matrizes de ponteiros são usadas normalmente como ponteiros de strings como, por exemplo, 
for (t=0;s[t]; ++t) putchar(s[t]); o argumento da linha de comandos argv. 
}
/* Acessa s como um ponteiro */
void put(char *s) { 4.3.2 ACESSANDO PARTES DE MATRIZES COMO VETORES 
while (*s) putchar(*s++);
}
  A  linguagem  C  trata  partes  de  matrizes  como  matrizes.  Mais  especificamente,  cada  linha  de 
uma matriz de duas dimensões pode ser considerada como uma matriz de uma dimensão. Isto pode ser 
  No caso da passagem de parâmetros é possível tratar uma matriz como se fosse um ponteiro. 
muito útil no tratamento de matrizes. O Exemplo 4.11 mostra a atribuição de uma linha da matriz nome 
Exemplo 4.9  para um ponteiro. 
#include <stdlib.h>
#include <stdio.h> Exemplo 4.11 
#include <string.h> int main() {
void le_tab(int *p) { static char nome[10][10];
27  Linguagem de Programação C  28  Linguagem de Programação C 
   

char *p[10]; #include <stdio.h>


for (i = 0;i<10;i++) #include <string.h>
p[i] = nome[i]; void check(char *a, char *b, int (*cmp)());
ordena(p); int main() {
return 0; char s1[80], s2[80];
} int (*p)();
p = strcmp;
4.4 INDIREÇÃO MÚLTIPLA  gets(s1);
gets(s2);
check(s1,s2,p);
  Indireção múltipla é uma situação onde o ponteiro aponta para outro ponteiro e que o mesmo  return 0;
aponta para um valor final. A Figura 4.1 mostra o conceito de indireção múltipla.   }

void check(char *a, char *b, int (*cmp)()) {


Ponteiro             Variável  if (!(*cmp) (a, b)) printf(“igual”);
else printf(“diferente”);
}
endereço                        valor 
  Quando  a  função  check()  é  chamada,  dois  ponteiros  para  caractere  e  um  ponteiro  para  uma 
Indireção Simples 
função são passados como parâmetros. Dentro da função check(), note como o ponteiro para função é 
declarado, pois esta é a forma correta de se declarar este tipo de ponteiro. Os parênteses ao redor de 
    Ponteiro           Ponteiro             Variável 
*cmp são necessários para que o compilador interprete o comando corretamente. 
  endereço            endereço                 valor 
  Outra  forma  de  fazer  a  chamada  é  mostrada  no  Exemplo  4.14  o  qual  dispensa  o  uso  de  um 
Indireção Múltipla  ponteiro adicional. 
Figura 4.1 Indireção simples e múltipla 
check(s1, s2, strcmp);

A  indireção  múltipla  pode  ser  levada  a  qualquer  dimensão  desejada,  mas  raramente  é 
  Uma das grandes utilidades é o uso de drivers de dispositivos (placas de som, placas de vídeo, 
necessário mais de um ponteiro para um ponteiro. 
modems, entre outros) que fornecem rotinas de tratamento para aquele hardware específico. Onde o 
programador  lê  o  arquivo  do  driver  para  a  memória  e  o  executa  de  acordo  com  as  especificações  do 
Não confunda indireção múltipla com listas encadeadas.  fabricante.  

  A declaração deste tipo de variável é feita colocando‐se um * adicional em frente ao nome da    Outra utilidade é o programador poder enviar a função que se apropria para a comparação por 
variável, como mostra o Exemplo 4.12. Tal exemplo mostra a declaração da variável ptrptrint como um  exemplo. Isto é, no caso de strings pode‐se pensar em um comparador de strings genérico onde como 
ponteiro para um ponteiro do tipo int.  terceiro  parâmetro  é  enviado  a  função  que  vai  realizar  a  comparação.  Antes  da  chamada  da  função 
Exemplo 4.12  genérica  pode  verificar  se  a  string  é  composta  por  caracteres  alfanuméricos  (através  da  função 
int **ptrptrint; isalpha())  e  enviar  a  função  strcmp(),  caso  contrário  uma  função  que  realize  uma  comparação  de 
números inteiros (nesta função conterá a conversão das strings em um inteiro (função atoi()). 
  Para acessar o valor final apontado indiretamente por um ponteiro a um ponteiro, você deve 
utilizar o operador asterisco duas vezes, como no Exemplo 4.13:  4.6 MAIS SOBRE DECLARAÇÕES DE PONTEIROS 
Exemplo 4.13 
#include <stdio.h>   As  declarações  de  ponteiros  podem  ser  complicadas  e  é  necessário  algum  cuidado  na  sua 
int main() { interpretação. principalmente em declarações que envolvem funções e matrizes. Assim, a declaração 
int x, *p, **q;
x = 10; int *p(int a);
p = &x;
q = &p;
printf(“%d”, **q); /* imprime o valor de x */ indica uma função que aceita um argumento inteiro e retorna um ponteiro para um inteiro. Por outro 
return 0; lado, a declaração 
}
int (*p)(int a);
4.5 PONTEIROS PARA FUNÇÕES 
indica  um  ponteiro  para  uma  função  que  aceita  um  argumento  inteiro  e  retorna  um  inteiro.  Nessa 
  A linguagem C permite apontadores para funções. Isto é permitido pois toda função tem uma  última  declaração,  o  primeiro  par  de  parênteses  é  usado  para  o  aninhamento  e  o  segundo  par,  para 
posição física na memória que pode ser atribuída a um ponteiro. Portanto, um ponteiro de função pode  indicar uma função. 
ser usado para chamar uma função. 
  A interpretação de declarações mais complexas pode ser extremamente mais trabalhosa. Por 
  O endereço de uma função é obtido usando o nome da função sem parênteses ou argumentos.  exemplo, considere a declaração 
Mas  para  declarar  este  tipo  de  apontador  tem  que  se  seguir  uma  sintaxe  especial  como  mostra  o 
Exemplo 4.14.  int *(*p)(int (*a)[]);

Exemplo 4.14 
29  Linguagem de Programação C  30  Linguagem de Programação C 
   

  Nessa declaração, (*p)(..) indica um ponteiro para uma função. Por isso, int *(*p)(...) indica um   
ponteiro  para  uma  função  que  retorna  um  ponteiro  para  um  inteiro.  Dentro  do  último  par  de 
parênteses (a especificação dos argumentos da função), (*a)[] indica um ponteiro para um vetor. Assim  4.7 EXERCÍCIOS 
int  (*a)[]  representa  um  ponteiro  para  um  vetor  de  inteiros.  Juntando  todas  as  peças,  (*p)(int  (*a)[]) 
representa um ponteiro para uma função cujo argumento é um ponteiro para um vetor de inteiros. E,  1. Como referenciar mat[x][y] em notação de ponteiros. 
finalmente, a declaração original representa um ponteiro para uma função que aceita um ponteiro para 
um vetor de inteiros como argumento e devolve um ponteiro para um inteiro.  2. Qual será a saída deste programa? 
int main() {
int i=5;
Se  logo  após  um  identificador  existir  um  “abre  parênteses”  indica  que  o  int *p;
identificador    representa  uma  função  e  os  colchetes  representam  uma  p = &i;
matriz. Os parênteses e colchetes têm maior precedência do que qualquer  printf(“%u %d %d %d %d \n”, p, *p+2,**&p,3**p,**&p+4);
return 0;
operador.  }

  A seguir será mostrado várias declarações envolvendo ponteiros e seu significado.  3. Escreva uma função que inverta a ordem dos caracteres de uma string. 
int *p; p é um ponteiro para um valor inteiro 
int *p[10]; p  é  uma  matriz  de  ponteiros  com  10  elementos  para  valores  4. Crie  uma  função  que  receba  como  parâmetro  uma  matriz  de  ponteiros  para  strings  e  devolve  a 
inteiros  matriz ordenada. 
int (*p)[10]; p é um ponteiro para uma matriz de inteiros com 10 elementos
int *p(void); p é uma função que retorna um ponteiro para um valor inteiro  5. Faça uma função que receba um ponteiro para uma matriz e que realize a ordenação da mesma. 
int *p(char *a); p é uma função que aceita um argumento que é um ponteiro para 
um caractere e retorna um ponteiro para um valor inteiro  6. Faça a declaração de uma função (nome: teste) que receba um ponteiro para uma função que possui 
int (*p)(char *a); p é m ponteiro para uma função que aceita um argumento que é  dois argumentos (int e char) e retorne um ponteiro para um float. 
um ponteiro para um caractere e retorna um valor inteiro 
int (*p(char *a))[10]; p é uma função que aceita um argumento que é um ponteiro para  7. Faça a declaração e inicialização de uma matriz de ponteiros para os dias da semana. 
um caractere e retorna um ponteiro para uma matriz inteira de 10 
elementos  8. Faça  uma  função  que  receba  uma  matriz  de  ponteiros  para  caracteres  e  realize  a  ordenação 
int p(char (*a)[]); p é uma função que aceita um argumento que é um ponteiro para  alfabética da mesma. 
uma matriz de caractere e retorna um valor inteiro 
int p(char *a[]); p  é  uma  função  que  aceita  um  argumento  que  é  uma  matriz  de   
ponteiros para caractere e retorna um valor inteiro
int *p(char a[]); p  é  uma  função  que  aceita  um  argumento  que  é  uma  matriz  de 
caractere e retorna um ponteiro para um valor inteiro 
int *p(char (*a)[]); p é uma função que aceita um argumento que é um ponteiro para 
uma  matriz  de  caractere  e  retorna  um  ponteiro  para  um  valor 
inteiro 
int *p(char *a[]); p  é  uma  função  que  aceita  um  argumento  que  é  uma  matriz  de 
ponteiros  para  caracteres  e  retorna  um  ponteiro  para  um  valor 
inteiro 
int (*p)(char (*a)[]); p é um ponteiro para uma função que aceita um argumento que é 
um  ponteiro  para  uma  matriz  de  caractere  e  retorna  um  valor 
inteiro 
int *(*p)(char (*a)[]); p é um ponteiro para uma função que aceita um argumento que é 
um ponteiro para uma matriz de caractere e retorna um ponteiro 
para um valor inteiro 
int *(*p)(char *a[]); p é um ponteiro para uma função que aceita um argumento que é 
uma  matriz  de  ponteiros  para    caracteres  e retorna um ponteiro 
para um valor inteiro 
int (*p[10])(char a); p  é  uma  matriz  de  ponteiros  com  10  elementos  para  funções; 
cada  função  aceita  um  argumento  que  é  um caractere e  retorna 
um valor inteiro 
int *(*p[10])(char a); p  é  uma  matriz  de  ponteiros  com  10  elementos  para  funções; 
cada  função  aceita  um  argumento  que  é  um caractere e  retorna 
um ponteiro para um valor inteiro 
int *(*p[10])(char *a); p  é  uma  matriz  de  ponteiros  com  10  elementos  para  funções; 
cada  função  aceita  um  argumento  que  é  um  ponteiro  para  um 
caractere e retorna um ponteiro para um valor inteiro 
31  Linguagem de Programação C  32  Linguagem de Programação C 
   

int codigo;
5  ESTRUTURAS E UNIÕES  float saldo;
};

  A linguagem C permite criar tipos de dados definíveis pelo usuário de cinco formas diferentes.    Para usar esta estrutura em outras declarações deve‐se especificar desta forma: 
A  primeira  é  estrutura,  que  é  um  agrupamento  de  variáveis  sobre  um  nome  e,  algumas  vezes,  é 
chamada de tipo de dado conglomerado. O segundo tipo definido pelo usuário é o campo de bit, que é  struct cad_conta conta1, conta2;
uma  variação  da  estrutura  que  permite  o  fácil  acesso  aos  bits  dentro  de  uma  palavra.  O  terceiro  é  a 
união, a qual  permite que a mesma porção da memória seja definida por dois ou mais tipos diferentes    As estruturas seguem o padrão do escopo de variáveis, isto é, se a declaração estiver contida 
de variáveis. Um quarto tipo de dado é a enumeração, que é uma lista de símbolos, como foi visto na  numa função, a estrutura tem escopo local para aquela função; se a declaração estiver fora de todas as 
seção  1.5.  O  último  tipo  definido  pelo  usuário  é  criado  através  do  uso  de  typedef  e  define  um  novo  funções, ela terá um escopo global. 
nome para um tipo existente. 
  Para acessar um campo específico de uma struct utiliza‐se o operador . (ponto). 
5.1 ESTRUTURAS 
Exemplo 5.3 
conta1.saldo = 0;
  O tipo estruturado struct possibilita a criação de estruturas de dados complexas, isto é, pode‐ conta1.codigo = 0;
se  obter  estruturas  que  contenham  mais  de  um  tipo  de  dado.  Tal  estrutura  é  conhecida  em  outras  strcpy(conta1.nome,”Joao”);
linguagens como registros.  conta1.idade = 21;

  Cada elemento que compõe a estrutura (chamado campo) pode ser acessado individualmente,    É permitida a atribuição entre struct. Neste caso todos os campos são copiados. 
assim como a estrutura pode ser acessada como um todo. Em C, a declaração de uma estrutura é feita  Exemplo 5.4 
da seguinte forma:  conta2 = conta1;

struct [nome_struct] { 
          tipo var1;  5.1.1 INICIALIZANDO ESTRUTURAS 
          tipo var2; 
              …    Uma estrutura só pode ser inicializada se pertencer às classes static ou extern. Observe que a 
          tipo varN;} [nome_var];  classe de uma estrutura é dada pelo ponto em que as variáveis foram declaradas e não pelo ponto onde 
a estrutura foi definida. 
  Deve‐se  encerrar  com  um  ponto‐e‐vírgula  a  declaração  porque  a  definição  de  estrutura  é  na 
realidade uma instrução C.    Da mesma forma que os vetores, as estruturas são inicializadas com uma lista de valores (cada 
um correspondente a um campo de estrutura) entre chaves e separados por vírgulas. 
  A declaração de estruturas pode se apresentar de diversas formas. Tais como:  Exemplo 5.5 
struct cad_conta {
struct { char nome[30];
char nome[30]; int idade;
int idade; int codigo;
int codigo; float saldo;
float saldo; };
} conta1, conta2;
int main() {
static struct cad_conta
  Na  declaração  acima,  o  nome_struct  não  é  utilizado  pois  esta  estrutura  será  utilizada  pelas  conta1 = {“Andre”, 23, 9507, 1567.89},
variáveis de estrutura conta1 e conta2. Para utilizar esta estrutura na definição de outras variáveis tem‐ conta2 = {“Carlos”, 33, 9678, 1000.59};
se  que  declará‐las  juntas  com  a  definição  da  estrutura.  No  caso  de  um  programa  que  utilize  esta  …
estrutura para passar parâmetros, declarar variáveis locais, entre outros, a linguagem permite a criação  }
de rótulos de estruturas (nome_struct). 
Exemplo 5.1  5.1.2 ESTRUTURAS ANINHADAS 
struct cad_conta {
char nome[30];   Como  os  campos  da  estrutura  podem  ser  de  qualquer  tipo,  também  é  permitido  o  uso  de 
int idade; estruturas na declaração. 
int codigo;
float saldo; Exemplo 5.6 
} conta1, conta2; struct data {
int dia;
char mes[10];
  Como  mostra  Exemplo  5.1,  foram  declaradas  as  variáveis  conta1  e  conta2  como  sendo  uma  int ano;
estrutura  do  tipo  cad_conta.  Quando  rotula‐se  a  estrutura  pode‐se  omitir  a  declaração  das  variáveis,  };
como é mostrado no Exemplo 5.2. 
struct func {
Exemplo 5.2  char nome[20];
struct cad_conta { int codigo;
char nome[30]; float salario;
int idade; struct data nascimento;
33  Linguagem de Programação C  34  Linguagem de Programação C 
   

}; int idade;
int codigo;
int main() { float saldo;
static struct func };
funcionario = {“Marcio”, 1234, 3743.44, {10, “Janeiro”, 1967}},
gerente = {“Jose”, 456, 5634.28, {18, “Marco”, 1950}}; int main() {
return 0; int i
} static struct cad_conta conta[10]=
{ {“Andre”, 23, 9507, 1567.89},
{“Carlos”, 33, 9678, 1000.59},
  Observe  a  inicialização  das  variáveis.  A  estrutura  é  inicializada  também  com  uma  lista  de  ...
valores entre chaves e separados por vírgulas. O acesso a um campo de uma estrutura aninhada é feito  };
na forma:  for (i=0;i<10;i++) {
printf(“Nome : %s\n”,conta[i].nome);
funcionário.nascimento.dia = 10; printf(“Idade : %d\n”, conta[i].idade);
strcpy(gerente.nascimento.mes,”Abril”); printf(“Codigo : %d\n”, conta[i].codigo);
printf(“Saldo : %.2f\n”, conta[i].saldo);
}

5.1.3 ESTRUTURAS E FUNÇÕES  }

  Em versões mais antigas de compiladores C, as estruturas não podiam ser usadas em passagem 
de parâmetros por valor para funções. Isto se devia a razões de eficiência, uma vez que uma estrutura  5.1.5 PONTEIROS PARA ESTRUTURAS 
pode  ser  muito  grande  e  a  cópia  de  todos  os  seus  campos  para  a  pilha  poderia  consumir  um  tempo 
exagerado.  Desta  forma,  as  estruturas  eram  obrigatoriamente  passadas  por  referência,  usando‐se  o    C permite ponteiros para estruturas exatamente como permite ponteiros para outros tipos de 
operador de endereço (&).  variáveis. No entanto, há alguns aspectos especiais de ponteiros de estruturas. 

  No Turbo C e outros compiladores mais recentes, a responsabilidade da decisão fica a cargo do    Como  outros  ponteiros,  declara‐se  colocando  um  *  na  frente  do  nome  da  estrutura.  No 
programador. Assim, uma função pode passar ou retornar uma estrutura.   Exemplo 5.9 declara‐se ptr_cta como um apontador da estrutura previamente definida cad_conta. 
Exemplo 5.7  Exemplo 5.9 
struct cad_conta { struct cad_conta *ptr_cta;
char nome[30];
int idade;   Há  dois  usos  primários  para  ponteiros  de  estrutura:  gerar  uma  chamada  por  referência  para 
int codigo;
float saldo; uma  função  e  criar  estruturas  de  dados  dinâmicas  (listas,  pilhas,  filas,  entre  outras)  utilizando‐se  do 
}; sistema de alocação de C. 

int main() {   Na  forma  de  acessar  os  elementos  ou  campos  de  uma  estrutura  usando  um  ponteiro  para  a 
static struct cad_conta conta1;
conta1 = ins_conta(); estrutura,  deve‐se  utilizar  o  operador  ‐>  (seta).  A  seta  é  usada  sempre  no  caso  de  apontador  de 
lista(conta1); estruturas. No Exemplo 5.10 é mostrada a declaração, atribuição e utilização de ponteiros de estruturas. 

} Exemplo 5.10 
struct cad_conta {
struct cad_conta ins_conta() { char nome[30];
struct cad_conta aux; int idade;
gets(aux.nome); int codigo;
scanf(“%d”, &aux.idade); float saldo;
scanf(“%d”, &aux.codigo); } conta;
scanf(“%f”, &aux.saldo);
return(aux); int main() {
} struct cad_conta *ptr;
ptr = &conta; /* o ptr recebe o end. da estrutura */
void lista(struct cad_conta aux) { ptr->idade = 23;
printf(“Nome : %s\n”,aux.nome); ptr->codigo = 1000;
printf(“Idade : %d\n”, aux.idade); return 0;
printf(“Codigo : %d\n”, aux.codigo); }
printf(“Saldo : %.2f\n”, aux.saldo);
} 5.2 CAMPOS DE BITS 

5.1.4 VETOR DE ESTRUTURAS    Ao  contrário  das  linguagens  de  computador,  C  tem  um  método  intrínseco  para  acessar  um 
único bit dentro de um byte. Isso pode ser útil por diversas razões: 
  A criação de tabela de estruturas mantém a sintaxe normal de definição de matrizes, como é 
• Se  o  armazenamento  é  limitado,  você  pode  armazenar  diversas  variáveis  Booleanas 
mostrada no Exemplo 5.8: 
(verdadeiro/falso) em um byte. 
Exemplo 5.8 
struct cad_conta { • Certos dispositivos transmitem informações codificadas nos bits dentro de um byte. 
char nome[30];
35  Linguagem de Programação C  36  Linguagem de Programação C 
   

• Certas rotinas de criptografia precisam acessar os bits dentro de um byte.    Além disso, nota‐se que os bits após dsr não precisam ser especificados se não são usados. 

  Variáveis de campo de bit têm certas restrições: 
  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 o comprimento, em bits, do campo. A forma geral  • Não pode obter o endereço de uma variável de campo de bit. 
de uma definição de campo de bit é:  • Variáveis de campo de bit não podem ser organizadas em matrizes. 
struct nome {  • Não pode ultrapassar os limites de um inteiro. 
   tipo var1 : comprimento;  • Não  pode  saber,  de  máquina  para  máquina,  se  os  campos  estarão  dispostos  da  esquerda 
   tipo var2 : comprimento;  para a direita ou da direita para a esquerda. 
        …  • Em  outras  palavras,  qualquer  código  que  use  campos  de  bits  pode  ter  algumas 
  tipo varN : comprimento;  dependências da máquina. 
} [lista_de_variaveis]; 
  Finalmente,  é  válido  misturar  elementos  normais  de  estrutura  com  elementos  de  campos  de 
Um  campo  de  bit  deve  ser  declarado  como  int,  unsigned  ou  signed.  Campos  de  bit  de  bit.  O  Exemplo  5.12  define  um  registro  de  um  empregado  que  usa  apenas  um  byte  para  conter  três 
comprimento 1 devem ser declarados como unsigned, porque um único bit não pode ter sinal.  (Alguns  informações: o estado do empregado, se o empregado é assalariado e o número de deduções. Sem o 
compiladores só permitem campos do tipo unsigned).  campo de bits, essa variável ocuparia três bytes. 
Exemplo 5.12 
Um  exemplo  de  campos  de  bits  é  a  comunicação  via  serial  que  devolve  um  byte  de  estado  struct emp {
organizado como mostra a Tabela 5.1.  struct addr endereco;
float salario;
Tabela 5.1: Estado da comunicação serial.  unsigned ativo : 1; /* ocioso ou ativo */
Bit  Significado quando ligado  unsigned horas : 1; /* pagamento por horas */
unsigned deducao : 3; /* deduções de imposto */
0  alteração na linha clear‐to‐send  };
1  alteração em data‐set‐ready 
2 borda de subida da portadora detectada 5.3 UNIÕES 
3 alteração na linha de recepção 
4  clear‐to‐send    Uma união é um tipo de dado que pode ser usado de muitas maneiras diferentes. Por exemplo, 
5  data‐set‐ready  uma  união  pode  ser  interpretada  como  sendo  um  inteiro  numa  operação  e  um  float  ou  double  em 
6  chamada do telefone  outra. Embora, as uniões possam tomar a aparência de uma estrutura, elas são muito diferentes. 
7  sinal recebido 
  Uma  união  pode  conter  um  grupo  de  muitos  tipos  de  dados,  todos  eles  compartilhando  a 
  Pode‐se representar a informação em um byte de estado utilizando o seguinte campo de bits:  mesma  localização  na  memória.  No  entanto,  uma  união  só  pode  conter  informações  de  um  tipo  de 
dados de cada vez. Para criar uma união utiliza‐se a seguinte sintaxe: 
Exemplo 5.11 
struct status_type { union [nome_union] { 
unsigned delta_cts : 1;
unsigned delta_dsr : 1;   tipo var1; 
unsigned tr_edge : 1;   tipo var2; 
unsigned delta_rec : 1;   … 
unsigned cts : 1;
unsigned dsr : 1;
  tipo varN; 
unsigned ring : 1; } [nome_var]; 
unsigned rec_line : 1;
} status;   Deve‐se  encerrar  com  um  ponto‐e‐vírgula  a  declaração  porque  a  definição  de  união  é  na 
realidade uma instrução C. A declaração de uniões pode se apresentar de diversas formas. Tais como: 
  Para atribuir um valor a um campo de bit, simplesmente utiliza‐se a forma para atribuição de 
outro tipo de elemento de estrutura.  Exemplo 5.13 
union {
char c;
status.ring = 0; int i;
double d;
  Não é necessário dar um nome a todo campo de bit. Isto torna fácil alcançar o bit que se deseja  float f;
} data;
acessar,  contornando  os  não  usados.  Por  exemplo,  se  apenas  cts  e  dtr  importam,  pode‐se  declarar  a 
estrutura status_type desta forma: 
  Na declaração acima, o nome_union não é utilizado pois esta união será utilizada pela variável 
struct status_type { data.  Para  utilizar  esta  união  na  definição  de  outras  variáveis  tem‐se  que  declará‐las  juntas  com  a 
unsigned : 4; definição  da  união.  No  caso  de  um  programa  que  utilize  esta  união  em  várias  partes  do  programa  a 
unsigned cts : 1; linguagem C permite a criação de rótulos de estruturas (nome_union). 
unsigned dsr : 1;
} status; Exemplo 5.14 
union tipos {
char c;
37  Linguagem de Programação C  38  Linguagem de Programação C 
   

int i;   O  sizeof(data)  é  8.  No  tempo  de  execução,  não  importa  o  que  a  união  data  está  realmente 
double d;
float f; guardando.  Tudo  o  que  importa  é  o  tamanho  da  maior  variável  que  pode  ser  armazenada  porque  a 
} data; união tem de ser do tamanho do seu maior elemento. 

  Como  mostra  o  Exemplo  5.14,  foi  declarada  a  variável  data  como  sendo  uma  união  do  tipo  5.5 TYPEDEF 
tipos.  Quando  rotula‐se  a  união  pode‐se  omitir  a  declaração  das  variáveis,  como  é  mostrado  no 
Exemplo 5.15: 
  A  linguagem  C  permite  que  defina‐se  explicitamente  novos  nomes  aos  tipos  de  dados, 
Exemplo 5.15  utilizando  a  palavra‐chave  typedef.  Não  há  criação  de  uma  nova  variável,  mas  sim,  definindo‐se  um 
union tipos {
char c; novo nome para um tipo já existente. Serve para uma boa documentação ou até tornar os programas 
int i; dependentes de máquina um pouco mais portáteis. A forma geral de um comando typedef é 
double d;
float f; typedef tipo nome; 
};

  Por exemplo, poderia ser criado um novo nome para char utilizando 
  Para usar esta união em outras declarações deve‐se especificar desta forma: 
typedef char boolean;
union tipos data1, data2;

  Esse comando diz ao compilador para reconhecer boolean como outro nome para char. Assim, 
  As estruturas seguem o padrão do escopo de variáveis, isto é, se a declaração estiver contida 
para se criar uma variável char, usando boolean 
numa função, a estrutura tem escopo local para aquela função; se a declaração estiver fora de todas as 
funções, ela terá um escopo global.  boolean ok;

  Para  acessar  um  campo  específico  de  uma  union  utiliza‐se  o  operador  .  (ponto).  Pode‐se    Também  é  válida  a  redefinição,  isto  é,  utilizar  um  novo  nome  para  um  nome  atribuído  a um 
declarar estruturas dentro de uniões.  dado previamente estabelecido. 
Exemplo 5.16  Exemplo 5.18 
struct so_int { #include <stdio.h>
int i1,i2;
}; typedef char boolean;
typedef boolean bool;
struct so_float {
float f1,f2; int main() {
}; boolean a;
bool b;
union { a = 1;
struct so_int i; b = 2;
struct so_float f; printf("%d %d",a,b);
} teste; return 0;
}
int main() {
teste.i.i1 = 2;
teste.i.i2 = 3;   A  declaração  typedef  é  usado  também  para  definir  tipos  estruturados  (struct  e  union)  para 
printf(“i1 = %-3d i2 = %-3d\n”,teste.i.i1,teste.i.i2); facilitar a nomenclatura dos tipos na declaração de variáveis. 
teste.f.f1 = 2.5;
teste.f.f2 = 3.5; Exemplo 5.19 
printf(“f1 = %.1f f2 = %.1f\n”,teste.f.f1,teste.f.f2); typedef struct conta {
return 0; char nome[30];
} int idade;
int codigo;
5.4 SIZEOF()  float saldo;
} cad_conta;

  Com uso de estruturas, uniões e enumerações pode‐se utilizá‐las para a criação de variáveis de  int main() {


cad_conta *ptr;
diferentes tamanhos e que o tamanho real dessas variáveis pode mudar de máquina para máquina. O  ptr = &conta;
operador  unário  sizeof()  calcula  o  tamanho  de  qualquer  variável  ou  tipo  e  pode  ajudar  a  eliminar  ptr->idade = 23;
códigos dependentes da máquina de seus programas.  ptr->codigo = 1000;
return 0;
}
Exemplo 5.17 
union tipos { ou
char c;
int i; struct conta {
double d; char nome[30];
float f; int idade;
} data; int codigo;
float saldo;
39  Linguagem de Programação C  40  Linguagem de Programação C 
   

};
6 ALOCAÇÃO DINÂMICA 
typedef struct conta cad_conta;

int main() {   Programas consistem em duas coisas: algoritmos e estruturas de dados. Um bom programa é 


cad_conta *ptr; uma  combinação  de  ambos.  A  escolha  e  a  implementação  de  uma  estrutura  de  dados  são  tão 
ptr = &conta;
ptr->idade = 23; importantes quanto as rotinas que manipulam os dados.  
ptr->codigo = 1000;
return 0;   Para a manipulação de dados é utilizado mecanismos que auxiliam tanto na forma de como é 
}
armazenado  ou  recuperado.  Existem  vários  mecanismos  que  realizam  este  tipo  de  processamento. 
5.6 EXERCÍCIOS  Abaixo estão listados alguns mecanismos básicos: 

• Listas   • Filas 
1. Faça  um  programa  que  leia  os  dados  de  10  clientes  de  um  banco  e  após  leia  100  conjuntos  de  3 
valores: 
• Pilhas  • Árvores 
• código de operação ‐ 0 depósito, 1 ‐ retirada,    Cada  um  destes  mecanismos  pode  ter  variações  de  acordo  com  a  política  de  processamento 
• valor da operação   (armazenamento/recuperação).  Neste  capítulo  será  abordado  com  mais  ênfase  as  listas  encadeadas, 
• código do cliente.  porque serão como base para a construção dos demais. 

  Realize  as  movimentações  nas  contas  correspondentes  e  ao  final  escreva  o  nome  e  saldo  de  6.1 MAPA DE MEMÓRIA 
cada cliente. 
  Quando  um  programa  em  C  é  executado,  quatro  regiões  de  memória  são  criadas:  código  do 
2. Faça  um  programa  de  cadastro  de  clientes  que  contenham  as  seguintes  opções:  incluir,  alteração,  programa,  variáveis  globais,  heap  e  pilha,  como  ilustrado  Figura  6.1.  A  região  da  pilha  é  a  porção  de 
excluir e consultar por código ou por nome. O cadastro deve ser da seguinte forma:  memória reservada  para armazenar o  endereço de retorno das chamadas de funções, argumentos de 
funções e variáveis locais, o estado atual da CPU. A pilha cresce de cima para baixo, isto é, do endereço 
• nome (30 caracteres);  mais alto para o mais baixo. O heap é a região utilizada para uso do programador através das funções de 
• código (0 a 255);  alocação dinâmica.  Como o heap cresce de baixo para cima (endereço mais baixo par o mais alto), pode 
• idade(char);  haver  uma colisão  entre  as  duas  regiões,  o  que  causa  uma falha no  programa.  Mas isto pode ocorrer 
  somente quando determinados modelos de memória são utilizados para executar o programa.   

 
Figura 6.1: Mapa de memória de um programa em C 

6.2 FUNÇÕES DE ALOCAÇÃO DINÂMICA EM C 

  O padrão C ANSI define apenas quatro funções para o sistema de alocação dinâmica: calloc(), 
malloc(),  free(),  realloc().  No  entanto,  serão  estudadas,  além  das  funções  descritas,  algumas  funções 
que estão sendo largamente utilizadas. 

  O padrão C ANSI especifica que os protótipos para as funções de alocação dinâmica definidas 
pelo  padrão  estão  em  STDLIB.H.  Entretanto,  tais  funções  estão  especificadas  na  biblioteca  ALLOC.H, 
onde encontram‐se mais funções de alocação dinâmica. 

  O  padrão  C  ANSI  especifica  que  o  sistema  de  alocação  dinâmica  devolve  ponteiros  void,  que 
são  ponteiros  genéricos,  podendo  apontar  para  qualquer  objeto.  Porém,  alguns  compiladores  mais 
41  Linguagem de Programação C  42  Linguagem de Programação C 
   

antigos devolvem ponteiros para char. Nesse caso, deve‐se usar um cast quando atribuir a ponteiros de  6.2.2 CALLOC() 


tipos diferentes. 
  Esta função devolve um ponteiro para o primeiro byte de uma região de memória de tamanho 
6.2.1 MALLOC()  size *  num que foi alocada do heap. No caso em que não houver memória suficiente, a função devolve 
um ponteiro nulo. 
  Esta função devolve um ponteiro para o primeiro byte de uma região de memória de tamanho 
size  que  foi  alocada  do  heap.  No  caso  em  que  não  houver  memória  suficiente,  a  função  devolve  um  Sintaxe.: 
void *calloc(size_t num, size_t size);
ponteiro nulo. 
  Onde size_t pode ser considerado um inteiro sem sinal e size é o número de bytes de memória 
Cuidado! Ao usar um ponteiro nulo, pode causar uma falha no programa.  que  se  quer  alocar.  Essa  função  devolve  um  ponteiro  void,  como  mostra  a  sintaxe,  portanto  pode‐se 
atribuir a qualquer tipo de ponteiro. 
Sintaxe.: 
void *malloc(size_t size);
  Para  assegurar  a  portabilidade  de  um  programa  que  utilize  a  alocação  dinâmica,  faz‐se 
necessário a utilização da função sizeof(). 
  Onde size_t pode ser considerado um inteiro sem sinal e size é o número de bytes de memória 
que  se  quer  alocar.  Essa  função  devolve  um  ponteiro  void,  como  mostra  a  sintaxe,  portanto  pode‐se    A diferença entre calloc() e malloc() é que a primeira aloca a memória e inicializa‐a com zeros. 
atribuir a qualquer tipo de ponteiro. 
Exemplo 6.2 
/* Esta função aloca memória suficiente para conter um
  Para  assegurar  a  portabilidade  de  um  programa  que  utilize  a  alocação  dinâmica,  faz‐se  vetor de 100 elementos */
necessário a utilização da função sizeof().  #include <stdlib.h>
#include <stdio.h>
Exemplo 6.1:  
/* Esta função aloca memória suficiente para conter uma float *get_mem() {
estrutura do tipo addr */ float *p;
struct addr { p=(float*)calloc(100, sizeof(float));
char nome[40]; if (!p) {
char rua[40]; printf(“ erro de alocação - abortando”);
char cidade[40]; exit(1);
char estado[3]; }
char cep[10]; return p;
}; }
struct addr *get_struct(void) {
struct addr *p; No fragmento abaixo é alocado memória suficiente para 50 inteiros. 
if ((p=(struct addr*)malloc(sizeof(addr)))==NULL)
{ int *p;
printf(“ erro de alocação - abortando”); p = (int*)calloc(50,sizeof(int));
exit(1);
}
return p; 6.2.3 FREE() 
}

  Esta função devolve ao heap a memória apontada por ptr, tornando a memória disponível para 
O fragmento do código mostra a alocação de 1000 bytes de memória. 
alocação futura. 
char *p;
p = (char*)malloc(1000);   A função free() deve ser chamada somente com um ponteiro que foi previamente alocado com 
uma  das  funções  do  sistema  de  alocação  dinâmica.  A  utilização  de  um  ponteiro  inválido  na  chamada 
No fragmento abaixo é alocado memória suficiente para 50 inteiros.  provavelmente  destruirá  o  mecanismo  de  gerenciamento  de  memória  e  provocará  uma  quebra  do 
sistema. 
int *p;
p = (int*)malloc(50 * sizeof(int)); Exemplo 6.3 
#include <string.h>
#include <stdio.h>
  O  compilador  deve  conhecer  duas  informações  sobre  qualquer  ponteiro:  o  endereço  da  #include <alloc.h>
variável apontada e seu tipo. Por isso, precisa‐se fazer uma conversão de tipo (cast) do valor retornado 
por malloc(), já que o mesmo retorna um void. Portanto, no Exemplo 6.1 deve‐se indicar ao compilador  int main() {
char *str;
que o valor retornado por  malloc() é do tipo ponteiro para struct addr. 
/* aloca memória para uma string */
p=(struct addr*) malloc(sizeof(addr))  str = (char*)malloc(10);

/* copia "Hello" para a string */


  Este  tipo  de  conversão  deve  ser  realizado  em  todas  as  funções  de  alocação  como  calloc(),  strcpy(str, "Hello");
realloc() e malloc(). 
/* mostra a string */
43  Linguagem de Programação C  44  Linguagem de Programação C 
   

printf("String: %s\n", str); int main() {


char *s;
/* libera a memória */ register int t;
free(str); s=(char*)malloc(80);
if (!s) {
return 0; printf(“ erro de alocação - abortando”);
} exit(1);
}
gets(s);
6.2.4 REALLOC()  for (t=strlen(s)-1; t>=0; t--) putchar(s[t]);
free(s);
return 0;
  Esta  função  modifica  o  tamanho  da  memória  previamente  alocada  apontada  por  ptr  para  }
aquele especificado por size. O valor de size pode ser maior ou menor que o original. Um ponteiro para 
o  bloco  de  memória  é  devolvido  porque  realloc()  pode  precisar  mover  o  bloco  para  aumentar  seu    Para  acessar  uma  matriz  unidimensional  é  simples,  mas  para  mais  de  uma  dimensão  levam 
tamanho. Se isso ocorre, o conteúdo do bloco antigo é copiado no novo bloco; nenhuma informação é  alguns problemas com a indexação. 
perdida. 
  Para  conseguir  uma  matriz  alocada  dinamicamente,  deve‐se  utilizar  um  truque:  passar  um 
Sintaxe.:  ponteiro  como  um  parâmetro  a  uma  função.  Dessa  forma,  a  função  pode  definir  as  dimensões  do 
void *realloc(void *ptr, size_t size); parâmetro que recebe o ponteiro, permitindo, assim, a indexação normal da matriz. Isto é mostrado no 
Exemplo 6.6. 
  Se ptr é um nulo, realloc() simplesmente aloca size bytes de memória e devolve um ponteiro 
para a memória alocada. Se size é zero, a memória apontada por ptr é liberada.  Exemplo 6.6:  
#include <stdlib.h>
#include <stdio.h>
  Se  não  há  memória  livre  suficiente  no  heap  para  alocar  size  bytes,  é  devolvido  um  ponteiro  #include <string.h>
nulo e o bloco original é deixado inalterado.  void le_tab(int mat[20][5]) {
register int i,j;
Exemplo 6.4:   for(i=0; i<20; i++)
for(j=0; j<5; j++)
/* Esta programa primeiro aloca 23 caracteres, copia a string “isso são scanf(“%d”,&mat[i][j]);
22 caracteres” neles e, em seguida, usa realloc() para aumentar o }
tamanho para 24 e, assim, pôr um ponto no final. */
void mostra_tab(int mat[20][5]) {
#include <stdlib.h> register int i,j;
#include <stdio.h> for(i=0; i<20; i++)
#include <string.h> for(j=0; j<5; j++)
printf(“%d”,mat[i][j]);
int main() { }
char *p;
p=(char*)malloc(23); int main() {
if (!p) { char *p;
printf(“ erro de alocação - abortando”); register int t;
exit(1); s=(int*)calloc(100, sizeof(int));
} if (!p) {
strcpy(p,”isso são 22 caracteres”); printf(“ erro de alocação - abortando”);
p = (char*)realloc(p,24); exit(1);
if (!p) { }
printf(“ erro de alocação - abortando”); le_tab(p);
exit(1); mostra_tab(p);
} return 0;
strcat(p,”.”); }
printf(p);
free(p); 6.4 LISTAS ENCADEADAS 
return 0;
}
  Listas  encadeadas  são  usadas  para  dois  propósitos  fundamentais.  O  primeiro é  criar  matrizes 
6.3 MATRIZES DINAMICAMENTE ALOCADAS  de tamanho desconhecido na memória. Listas encadeadas também são usadas em armazenamento de 
banco de dados em arquivos em disco.  
  Qualquer ponteiro pode ser indexado como se fosse uma matriz unidimensional, portanto não 
haverá nenhum problema para utilizar.    Listas encadeadas podem ser singularmente (simplesmente) ‐ um elo para o próximo item ‐ ou 
duplamente ‐ elos para o anterior e próximo elemento da lista ‐ encadeadas.  
Exemplo 6.5:  
/* Aloca memória para uma string dinamicamente, solicita */
/* a entrada do usuário e, em seguida, imprime a string */ 6.4.1 LISTAS SINGULARMENTE ENCADEADAS 
/* de trás para frente. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
45  Linguagem de Programação C  46  Linguagem de Programação C 
   

  Uma  lista  singularmente  encadeada  requer  que  cada  item  de  informação  contenha  um  elo    A construção de uma lista duplamente encadeada é semelhante à de uma lista singularmente 
como o próximo da lista. Cada item de dado geralmente consiste em uma estrutura que inclui campos  encadeada, exceto pelo fato de que dois elos devem ser  mantidos. Utilizando a estrutura ponto, será 
de informação e ponteiro de enlace (ou de ligação).  mostrado a declaração de um nodo de lista duplamente encadeada. 

  Antes, precisa‐se definir a estrutura de dados que contenha a informação e os elos. Considere  struct ponto {


int x,y,;
um exemplo de armazenamento de coordenadas cartesianas (x,y,z) para representação de uma figura  struct ponto *prox;
geométrica. A estrutura de dados para cada elemento é definido aqui:  struct ponto *ant;
  };
struct ponto {
int x,y,z; typedef struct ponto figura;
struct ponto *prox;
} figura1;   Usando figura como o item de dado básico, a função seguinte constrói uma lista duplamente 
typedef struct ponto figura; encadeada. Esta função inclui um novo dado no fim da lista: 

void incfim(figura *i, figura **fim) {


  Na  estrutura  acima,  é  declarado  um  apontador  para  a  próxima  estrutura,  por  isso  declara‐se  if (!*fim)
um apontador para a própria estrutura  (auto‐referência). Após a  declaração, foi definido que figura é  *fim = i; /* é o primeiro item da lista */
um tipo, que representa uma struct ponto, o qual pode ser utilizado em todo o programa.  else
(*fim)->prox = i;
i->prox = NULL;
  A  função  inclui()  constrói  uma  lista  singularmente  encadeada  colocando  cada  novo  item  no  i->ant = *fim;
final da lista. Deve ser passado um ponteiro para uma estrutura do tipo ponto, ponteiro para o primeiro  *fim = i;
elemento e ponteiro para o último elemento.  }

void inclui(figura *i, figura **inicio, figura **fim) {   Para armazenagem de um dados em uma posição específica a função abaixo realiza a inclusão 


if (!*fim) {/* Primeiro elemento da lista */ em ordem crescente pelo eixo x de uma lista duplamente encadeada. 
*fim = i;
*inicio = i;
} else { void incord(figura *i, /* novo elemento */
(*fim)->prox = i; figura **inicio, /* primeiro elemento da lista */
*fim = i; figura **fim) { /* ultimo elemento da lista */
} if (!*fim) { /* é o primeiro item da lista */
} i->prox = NULL;
i->ant = NULL;
*inicio = i;
  Os parâmetros início e fim da função têm dois asteriscos porque  representam uma indireção  *fim = i;
múltipla.  Isto  é,  são  apontadores  para  apontadores  da  estrutura  figura.  Isto  é  necessário  para  poder  } else {
figura *old, *p;
implementar a passagem de parâmetros por referência.  p = *inicio;
old = NULL;
  Apagar um item de uma lista singularmente encadeada pode ocorrer em três situações: apagar  while (p && (p->x < i->x)) {
o primeiro item, apagar  um item intermediário e apagar o último item. A função a seguir excluirá um  old = p;
p = p->prox;
item de uma lista de estruturas do tipo ponto:  }
if (!old) {
void exclui( i->prox = p; /* inserir no inicio da lista */
figura *p, /* item anterior */ i->ant = NULL;
figura *i, /* item a apagar */ p->ant = i;
figura **inicio, /* início da lista */ *inicio = i;
figura **ultimo) { /* final da lista */ } else {
if (p) if (p->ant) { /* inserir em uma posição */
p->next = i->next; p->ant->prox = i; /* intermediária da lista */
else i->prox = p;
*start = i->next; i->ant = p->ant;
if (i==*last && p) p->ant = i;
*last = p; } else {
} old->prox = i; /* inserir no fim da lista */
i->prox = NULL;
  Listas  singularmente  encadeadas  têm  uma  desvantagem  é  que  a  lista  não  pode  ser  lida  em  i->ant = old;
*fim = i;
ordem inversa.  }
}
}
6.4.2 LISTAS DUPLAMENTE ENCADEADAS 
  Como  o  ponteiro  de  início  e  fim  de  lista  podem  ser  alterados,  a  função  incord()  atualiza 
  Consistem em dados e elos para o próximo item e para o item precedente. Um novo elemento  automaticamente estes ponteiros através das variáveis inicio e fim. 
pode  ser  inserido  em  uma  lista  duplamente  encadeada  de  três  maneiras:  inserir  um  novo  primeiro 
elemento, inserir um elemento intermediário ou inserir um novo último elemento.  
47  Linguagem de Programação C  48  Linguagem de Programação C 
   

  Há três casos a considerar ao excluir um elemento de uma lista duplamente encadeada: excluir    A chamada desta função é realizada desta forma: 
o primeiro item, excluir um item intermediário ou excluir o último item. 
if (!rt)
void delord(figura *i, /* item a apagar */ rt = stree(rt, rt, info);
figura **inicio, /* primeiro elemento da lista */ else
figura **fim) { /* ultimo elemento da lista */ stree (rt, rt, info);
figura *old, *p;
p = *inicio;   Dessa  forma,  tanto  o  primeiro  quanto  os  elementos  subseqüentes  podem  ser  inseridos 
old = NULL;
while (p && (p->x != i->x) &&(p->y != i->y)) {
corretamente. A função stree() é um algoritmo recursivo.  
old = p;
p = p->prox;   Existem  três  formas  de  acessar  os  dados  de  uma  árvore:  ordenada,  preordenada  e  pós‐
} ordenada.  Onde  a  ordenada,  é  visitado  a  subárvore  da  esquerda,  a  raiz  e  em  seguida  a  subárvore  da 
if ((p->x = i->x) &&(p->y = i->y)) { direita. Na preordenada, visita‐se a raiz, subárvore da esquerda e, em seguida, a subárvore da direita. 
if (!old) /* exclusão único elemento da lista */ Na pós‐ordenada, visita‐se a subárvore da esquerda, subárvore da direita e, depois, a raiz. 
*inicio=*fim=NULL;
else {
if (!p->prox) { /* exclusão do ultimo elemento da lista */ d
old->prox = NULL;
*fim = old;
} else { /* excluir item de uma posição */
old->prox = p->prox; /* intermediária da lista */ b f
p->prox->ant = old;
}
}
free(p); a c e g  
}
} Figura 6.2 Exemplo de uma árvore binária 

6.5 ÁRVORES BINÁRIAS  Utilizando a figura 14.1, a ordem de acesso á árvore usando cada método é 

  A  estrutura  utilizada  para  a  construção  de árvores  binárias é  semelhante a listas  duplamente  ordenada  a  b  c  d  e  f  g 


encadeadas. A diferença está na política de organização das mesmas. O Exemplo 6.7 mostra uma função  preordenada  d  b  a  c  f  e  g 
que constrói uma árvore binária ordenada recursivamente.  pós‐ordenada a  c  b  e  g  f  d 

Exemplo 6.7    Para  o  acesso  de  forma  ordenada,  pelas  formas  descritas  anteriormente,  pode‐se  utilizar  as 
struct tree {
char info;
funções descritas abaixo: 
struct tree *esq;
struct tree *dir; void inorder(arvore *raiz) {
}; if(!raiz)
return;
typedef struct tree arvore; inorder(raiz->esq);
printf(“%c”, raiz->info);
arvore *stree ( inorder(raiz->dir);
arvore *raiz; }
arvore *r;
char info) { void preorder(arvore *raiz) {
if (!r) { if(!raiz)
r = (arvore *) malloc(sizeof(arvore)); return;
if (!r) { printf(“%c”, raiz->info);
printf(“Sem memória \n”); preorder(raiz->esq);
exit(0); preorder(raiz->dir);
} }
r->esq = NULL;
r->dir = NULL; void postorder(arvore *raiz) {
r->info = info; if(!raiz)
if (!raiz) return;
return r; /* primeira entrada */ postorder(raiz->esq);
if (info < raiz->info) postorder(raiz->dir);
raiz->esq = r; printf(“%c”, raiz->info);
else }
raiz->dir = r;
return r;   Para  exclusão  de  um  nó  de  uma  árvore  tem  que  ser  verificado  se  o  nó  é  a  raiz,  um  nodo 
}
if (info < r->info) esquerdo ou direito e que os mesmos podem ter subárvores ligadas a ele. Na função a seguir é realizada 
stree(r,r->esq, info); uma exclusão recursiva observando as restrições delineadas anteriormente. 
else
stree(r,r->dir, info); arvore *dtree(arvore *raiz, char key) {
}
49  Linguagem de Programação C  50  Linguagem de Programação C 
   

arvore *p, *p2;


if (raiz->info==key) { /* apagar a raiz */ 7 E/S COM ARQUIVO 
if (raiz->esq== raiz->dir) { /*não tem filhos */
free(raiz);
return NULL;   São grupos de dados armazenados em meio não volátil (disco, fita, entre outros). São utilizados 
} else para armazenar dados de forma permanente.  
if (raiz->esq == NULL) {
p = raiz->dir;
free(raiz);   A linguagem C não contém nenhum comando de E/S. Ao contrário, todas as operações de E/S 
return p; ocorrem  através  de  chamadas  a  funções  da  biblioteca  C  padrão.  Essa  abordagem  faz  o  sistema  de 
} else
if (raiz->dir == NULL) { arquivos de C extremamente poderoso e flexível. O sistema de E/S de C é único, porque dados podem 
p = raiz->esq; ser transferidos na sua representação binária interna ou em um formato de texto.  
free(raiz);
return p; 7.1 E/S ANSI X E/S UNIX 
} else {
p2 = raiz->dir;
p = raiz->dir;   O padrão ANSI define um conjunto completo de funções de E/S que pode ser utilizado para ler 
while (p->esq) e escrever qualquer tipo de dado. Em contraste, o antigo padrão C UNIX contém dois sistemas distintos 
p = p->esq;
p->esq = raiz->esq; de rotinas que realizam operações de E/S. O primeiro método é denominado de sistema de arquivo com 
free(raiz); buffer (algumas vezes os termos formatado ou alto nível são utilizados para referenciá‐lo). O segundo é 
return p2; o  sistema  de  arquivo  tipo  UNIX  (algumas  vezes  chamado  de  não  formatado  ou  sem  buffer)  definido 
} apenas sob o antigo padrão UNIX.  
}
if (raiz->info < key)
raiz->dir = dtree(raiz->dir, key); 7.2 STREAMS 
else
raiz->esq = dtree(raiz->esq, key);
return raiz;   A  linguagem  C  oferece  uma  abstração  da  interface  para  controle  de  E/S,  independente  do 
} dispositivo  real  (terminais,  acionadores  de  disco,  acionadores  de  fita,  entre  outros)  que  é  acessado. 
Sendo que o dispositivo real é chamado de arquivo. 
  Árvores  binárias  oferecem  grande  poder,  flexibilidade  e  eficiência  quando  usadas  em 
programas de gerenciamento  de  banco de dados. Principalmente pela sua política de organização e a    Existem  dois  tipos  de  streams:  texto  e  binária.  A  primeira  é  uma  seqüência  de  caracteres 
não limitação do tamanho (exceto aquela imposta pela memória).  organizados  em  linhas  e  terminadas  por  um  caractere  de  nova  linha  (depende  da  implementação).  A 
segunda é uma seqüência de bytes com uma correspondência de um para um com aqueles encontrados 
6.6 EXERCÍCIOS  no dispositivo externo ‐ isto é, não ocorre nenhuma tradução de caracteres. 

1. Escreva um programa que leia vários nomes e endereços, rearranje os nomes em ordem alfabética  7.3 ARQUIVOS 


e, depois, imprima a lista em ordem alfabética. Utilize várias estruturas. 
  Um  arquivo  pode  ser  qualquer  coisa,  desde  um  arquivo  em  disco  até  um  terminal  ou  uma 
2. Escreva  um  programa  que  gerencie  uma  pilha.  O  mesmo  deve  conter  a  função  de  empilhar  e  impressora. Associa‐se  uma  stream  com um arquivo  específico  realizando  uma  operação  de abertura. 
desempilhar  para  o  usuário  os  quatro  tipos  de  dados  básicos  da  linguagem  C  (char,  float,  int,  Todos as streams são iguais, mas não todos os arquivos. Isto é, um arquivo disco pode suportar acesso 
double).  aleatório enquanto um teclado não pode. 

3. Escreva um programa que gerencie uma fila circular do tipo FIFO (Primeiro que entra é o primeiro    Cada  stream associada  a um  arquivo  tem uma estrutura  de  controle  de  arquivo do tipo FILE. 
que sai).  Essa estrutura é definida no cabeçalho STDIO.H. 

9. Faça  um  programa  que  leia  o  número  de  alunos;  construa  uma  matriz  dinamicamente  alocada  de    Todos os arquivos são fechados automaticamente quando o programa termina, normalmente 
tamanho  N  X  4,  onde  N  é  o  número  de  alunos  e  4  as  respectivas  notas  de  cada  aluno.  Calcule  a  com main() retornando ao sistema operacional ou uma chamada à exit(). Os arquivos não são fechados 
média e mostre na tela conforme descrição a seguir:  quando um programa quebra (crash). 

ALUNO N1 N2 N3 N4 MEDIA 7.4 SISTEMA DE ARQUIVOS 


1 8.5 7.0 9.5 7.0 8.0
2 7.5 7.0 6.5 7.0 7.0
  O  sistema  de  arquivos  ANSI  é  composto  de  diversas  funções  inter‐relacionadas.  As  mais 
  comuns são mostradas na Tabela 7.1. Essas funções exigem que o cabeçalho STDIO.H seja incluído em 
qualquer programa em que são utilizadas. 
Tabela 7.1 ‐ Funções mais comuns do sistema de arquivo com buffer 
Nome  Função 
fopen() Abre um arquivo
fclose()  Fecha um arquivo 
putc() Escreve um caractere em um arquivo
51  Linguagem de Programação C  52  Linguagem de Programação C 
   

fputc()  O mesmo que putc()  ab Anexa a um arquivo binário


getc()  Lê um caractere de um arquivo  r+  Abre um arquivo texto para leitura/escrita 
fgetc()  O mesmo que getc()  w+ Cria um arquivo texto para leitura/escrita
fseek()  Posiciona o arquivo em um byte específico  a+  Anexa ou cria um arquivo texto para leitura/escrita 
fprintf()  É para um arquivo o que printf() é para o console r+b ou rb+ Abre um arquivo binário para leitura/escrita
fscanf()  É para um arquivo o que scanf() é para o console  w+b ou wb+  Cria um arquivo binário para leitura/escrita 
feof()  Devolve verdadeiro se o fim de arquivo for atingido a+b ou ab+ Anexa a um arquivo binário para leitura/escrita
ferror()  Devolve verdadeiro se ocorreu um erro  Exemplo 7.2 
rewind()  Repõe o indicador de posição de arquivo no início do arquivo FILE *arq; /* ponteiro de arquivo */
arq = fopen(“dados.dat”,”wb”);
remove()  Apaga um arquivo 
fflush()  Descarrega um arquivo    Se ao abrir um arquivo para leitura o mesmo não existir a função fopen retorna um ponteiro 
nulo (NULL). 
  O arquivo cabeçalho STDIO.H fornece os protótipos para as funções de E/S e define estes três 
tipos: size_t, fpos_t e FILE. O tipo size_t é essencialmente o mesmo que um unsigned, assim  como o  arq = fopen(“dados.dat”,”rb”);
fpos_t. O tipo FILE é discutido na próxima seção.  if (arq == NULL)
arq=fopen(“dados.dat”,”wb”);
  STDIO.H  define  várias  macros  como:  EOF,  SEEK_SET,  SEEK_CUR  e  SEEK_END.  A  macro  EOF  é 
geralmente  definida  como  ‐1  e  é  o  valor  quando  uma  função  de  entrada  tenta  ler  além  do  final  do  7.7 FECHAMENTO DE ARQUIVO 
arquivo. As outras macros são usadas com fseek(), que é uma função que executa acesso aleatório em 
um arquivo.    A  função  fclose()  fecha  uma  stream  que  foi  aberta  através  de  uma  chamada  à  fopen().  Esta 
função tem a seguinte sintaxe: 
7.5 ESTRUTURA FILE 
Sintaxe: 
  Para a manipulação de arquivos é utilizado a declaração de ponteiro (ponteiro de arquivo). Isto  int fclose(FILE *fp);
é, um ponteiro para informações que definem vários dados sobre o arquivo, como o seu nome, status, e 
a  posição  atual  do  arquivo.  Um  ponteiro  de  arquivo  é  uma  variável  ponteiro  do  tipo  FILE  .  Todas  as  onde fp é o ponteiro de arquivo devolvido pela chamada à fopen(). O valor de retorno 0 significa uma 
funções são realizadas utilizando o ponteiro. Para a declaração de um ponteiro de arquivo utiliza‐se a  operação de fechamento bem‐sucedida. 
seguinte sintaxe: 
7.8 VERIFICANDO FIM DE ARQUIVO 
Sintaxe: 
FILE *<var> A  função  feof()devolve  verdadeiro quando  for  encontrado  o  fim  de  arquivo.  O  protótipo  desta 
função está declarado abaixo: 
Exemplo 7.1 
FILE *fp;
Sintaxe: 
7.6 ABERTURA DE ARQUIVOS  int feof(FILE *fp);

  Esta função pode ser aplicada tanto para arquivo texto como para arquivos binários. 
  A função fopen() abre uma stream para uso e associa um arquivo a ela. Retorna o ponteiro de 
arquivo associado a esse arquivo.  7.9 CONDIÇÕES DE ERRO 
Sintaxe: 
FILE *fopen(const char * <nome_arquivo>, const char * <modo_abertura>);   Para determinar se um erro ocorreu utiliza‐se a função ferror(), mas esta informação não basta 
para  a  solução  por  parte  do  usuário.  Para  isso  utiliza‐se  a  função  perror()  em  conjunto  com  a  função 
  O modo de abertura define a forma como é feito o acesso aos dados (somente leitura, leitura e  ferror().  O  argumento  de  perror()  é  uma  string  fornecida  pelo  programa  que  normalmente  é  uma 
escrita, etc). As forma principais são apresentadas na Tabela 7.2.  mensagem de erro que indica em que parte do programa ocorreu erro. 

Tabela 7.2 ‐ Os modos de abertura válidos  Sintaxe: 
Modo  Significado  void perror (const char *str);
r  Abre um arquivo texto para leitura 
  Se for detectado um erro de disco, ferror() retornará um valor verdadeiro (não zero) e perror() 
w  Cria um arquivo texto para escrita  imprimirá a seguinte mensagem 
a  Anexa a um arquivo texto 
Erro de Busca: Bad data
rb  Abre um arquivo binário para leitura 
wb  Cria um arquivo binário para escrita 
53  Linguagem de Programação C  54  Linguagem de Programação C 
   

  A  primeira  parte  da  mensagem  é  fornecida  pelo  programa,  e  a  segunda  parte,  pelo  sistema  if(argc !=2) {
printf(“Você esqueceu de entrar o nome do arquivo \n”);
operacional.  exit(1);
}
7.10 STREAMS PADRÃO 
if((fp=fopen(argv[1],”w”))==NULL) {
printf(“Arquivo não pode ser aberto\n”);
  Sempre que um programa é iniciado três streams padrões são abertas automaticamente:   exit(1);
}
• stdin   (entrada padrão ‐ teclado);  do {
ch = getchar();
• stdout   (saída padrão ‐ tela);  putc(ch,fp);
• stderr  (saída de erro padrão ‐ tela);  } while(ch!=‘$’);
• stdaux  (saída auxiliar padrão ‐ porta serial);  fclose(fp);
return 0;
• stdprn  (impressora padrão ‐ impressora paralela).  }

  Essas streams podem ser utilizadas normalmente para executar operações de E/S bufferizada.     O programa complementar descrito a seguir lê qualquer arquivo ASCII e mostra o conteúdo na 
tela. 
7.11 LEITURA E GRAVAÇÃO DE CARACTERES 
Exemplo 7.5 
#include <stdio.h>
  Para  as  operações  de  leitura  e  gravação  são  utilizadas  duas  funções  padrões:  getc()  e  putc()  #include <stdlib.h>
(consideradas tecnicamente macros) . Para cada uma destas funções existem duas equivalentes: fgetc()  int main(int argc, char *argv[]) {
e  fputc().  Nesta  seção  são  apresentadas  as  funções  declaradas  padrão  ANSI  (as  duas  primeiras).  As  FILE *fp;
char ch;
outras funções têm a mesma sintaxe que suas equivalentes.  if (argc !=2) {
printf(“Você esqueceu de entrar o nome do arquivo \n”);
  Para a escrita de caracteres em um arquivo utilizam‐se as funções putc() e fputc(), as quais são  exit(1);
}
equivalentes (putc() é uma macro). O protótipo para essa função é  if ((fp=fopen(argv[1],”r”))==NULL) {
printf(“Arquivo não pode ser aberto\n”);
Sintaxe:  exit(1);
int putc(int ch, FILE *fp); }
ch = getc(fp);
while (ch!=EOF) {
onde fp é um ponteiro de arquivo devolvido por fopen() e ch é o caractere a ser escrito.  putchar(ch);
ch = getc(fp);
  Se  a  operação  putc()  foi  bem‐sucedida,  ela  devolve  o  caractere  escrito.  Caso  contrário,  ela  }
fclose(fp);
devolve EOF.  return 0;
}
  Para leitura de caracteres em um arquivo utilizam‐se as funções getc() e fgetc(), as quais são 
equivalentes (getc() é uma macro). O protótipo para essa função é  7.12 TRABALHANDO COM STRINGS 

Sintaxe:    Para a gravação e leitura de strings de caractere para e de um arquivo em disco são utilizadas 
int getc(FILE *fp);
as funções fgets() e fputs(), respectivamente. São os seguintes os seus protótipos: 
onde fp é um ponteiro de arquivo devolvido por fopen(). 
Sintaxe: 
int fputs(const char *str, FILE *fp);
  Se a operação getc() foi bem‐sucedida, ela devolve o caractere lido. Caso contrário, ela devolve  char *fgets(char *str, int length, FILE *fp);
EOF.  O  Exemplo  7.3  mostra  um laço  que realiza uma leitura de um arquivo texto  até que  a marca de 
final de arquivo seja lida.    A  função  fputs()  opera  como  puts(),  mas  escreve  a  string  na  stream  especificada.  EOF  é 
Exemplo 7.3  devolvido se ocorre um erro.
do {
ch = getc(fp);   A função fgets() lê uma string da stream especificada até que um caractere de nova linha seja 
} while (ch!=EOF);
lido  ou  que  length‐1  caracteres  tenham  sido  lidos.  Se  uma  nova  linha  é  lida,  ela  será  parte  da  string 
(diferente  de  gets()).  A  string  resultante  será  terminada  por  um  nulo.  A  função  devolve  um  ponteiro 
  As  funções  fopen(),  getc(),  putc()  e  fclose()  constituem  o  conjunto  mínimo  de  rotinas  de 
para str se bem‐sucedida ou um ponteiro nulo se ocorre um erro. 
arquivos. O programa a seguir lê  caracteres do teclado e os escreve em um arquivo em disco até que o 
usuário digite um cifrão ($). O nome do arquivo é passado pela linha de comando.    O  programa  a  seguir  lê  strings  do  teclado  e  escreve‐as  no  arquivo  chamado  frase.dat.  A 
Exemplo 7.4  condição  de  saída  é  uma  linha  em  branco.  Como  gets()  não  armazena  o  caractere  de  nova  linha,  é 
#include <stdio.h> adicionado  um  antes  que  a  string  seja  escrita  no  arquivo  para  que  o  arquivo  possa  ser  lido  mais 
#include <stdlib.h>
int main(int argc, char *argv[]) { facilmente. 
FILE *fp; Exemplo 7.6 
char ch; #include <stdio.h>
55  Linguagem de Programação C  56  Linguagem de Programação C 
   

#include <stdlib.h> Sintaxe: 


#include <string.h> int ferror(FILE *fp);
int main() {
char str[80];
FILE *fp;   Ela  retorna  verdadeiro  se  ocorreu  um  erro  durante  a  última  operação  no  arquivo;  caso 
if ((fp=fopen(“frase.dat”,”w”))==NULL) { contrário, retorna falso. 
printf(“Arquivo não pode ser aberto\n”);
exit(1);
} 7.13.3 REMOVE() 
do {
printf(“entre uma string (CR para sair): \n”);
gets(str);   A função remove() apaga o arquivo especificado. Seu protótipo é: 
strcat(str,”\n”);
fputs(str,fp);
} while (*str != ‘\n”);
Sintaxe: 
fclose(fp); int remove(char *nome_arq)
return 0;
}   Ela  devolve  zero  caso  seja  bem‐sucedido  e  um  valor  diferente  de  zero  caso  contrário.  O 
Exemplo 7.8 apresenta um trecho de programa que apaga o arquivo dados.dat.
7.13 FUNÇÕES DE TRATAMENTO DE ARQUIVOS  Exemplo 7.8 
if (remove(“dados.dat”)) {
printf(“Arquivo não pode ser apagado\n”);
  Nas próximas seções serão vistas algumas funções utilizadas em operações com arquivos com 
exit(1);
buffer.  }

7.13.1 REWIND()  7.13.4 FFLUSH() 


  Esta  função  reposiciona  o  indicador  de  posição  de  arquivo  no  início  do  arquivo  especificado    Para  se  esvaziar  o  conteúdo  de  uma  stream  de  saída,  deve‐se  utilizar  a  função  fflush(),  cujo 
como seu argumento. Seu protótipo é:  protótipo é mostrado a seguir: 

Sintaxe:  Sintaxe: 
void rewind(FILE *fp); int fflush(FILE *fp);

  O Exemplo 7.7 reposiciona o indicador de posição do arquivo do programa anterior e mostra o    Essa função escreve o conteúdo de qualquer dado existente no buffer arquivo associado a fp. 
conteúdo do mesmo. Se fflush() devolve 0 para indicar sucesso; caso contrário, devolve EOF.  
Exemplo 7.7 
#include <stdio.h>
#include <stdlib.h> 7.13.5 ACESSO ALEATÓRIO: FSEEK() 
#include <string.h>
int main() {   Operações  de  leitura  e  escrita  aleatórias  podem  ser  executadas  utilizando  o  sistema  de  E/S 
char str[80];
FILE *fp; bufferizado  com  a  ajuda  de  fseek(),  que  modifica  o  indicador  de  posição  de  arquivo.  Seu  protótipo  é 
if((fp=fopen(“frase.dat”,”w”))==NULL) { mostrado aqui: 
printf(“Arquivo não pode ser aberto\n”);
exit(1); Sintaxe: 
}
int fseek (FILE *fp, long numbytes, int origem);
do {
printf(“entre uma string (CR para sair): \n”);
gets(str);   Onde,  numbytes,  um  inteiro  longo,  é  o  número  de  bytes  a  partir  de  oriem,  que  se  tornará  a 
strcat(str,”\n”); nova posição corrente, e origem é uma das seguintes macros definidas em STDIO.H. 
fputs(str,fp);
} while (*str != ‘\n”); Tabela 7.3 – Macros definidas em STDIO.H para as posições permitidas na função fseek(). 
rewind(fp); /* reinicializa o file pointer */
while(!feof(fp)) { Origem  Nome da Macro 
fgets(str, 79, fp); Início do arquivo  SEEK_SET 
printf(str); Posição atual  SEEK_CUR 
}
fclose(fp);
Final do arquivo  SEEK_END 
return 0;
}   A  função  fseek()  devolve  0  se  a  operação  for  bem‐sucedida  e  um  valor  diferente  de  zero  se 
ocorre um erro. 
7.13.2 FERROR() 
  O Exemplo 7.9 mostra a utilização da função fseek() num programa que recebe pela linha de 
  A função ferror() determina se uma operação com arquivo produziu um erro. A função ferror()  comando o deslocamento a ser realizado a partir do início do arquivo (SEEK_SET). 
tem esse protótipo:   Exemplo 7.9 
57  Linguagem de Programação C  58  Linguagem de Programação C 
   

#include <stdio.h> #include <stdio.h>


#include <stdlib.h> #include <stdlib.h>
#include <io.h>
int main(int argc, char *argv[]) { int main() {
FILE *fp; FILE *fp;
char ch; char s[80];
if(argc !=3) { int t;
printf(“Uso: SEEK nomearq byte \n”); if((fp=fopen(“teste”,”w”))==NULL) {
exit(1); printf(“Arquivo não pode ser aberto\n”);
} exit(1);
if((fp=fopen(argv[1],”r”))==NULL) { }
printf(“Arquivo não pode ser aberto\n”); printf(“entre com uma string e um número: “ );
exit(1); fscanf(stdin, “%s%d”, s, &t); /* le do teclado */
} fprintf(fp, “%s %d”, s, t); /* escreve no arquivo */
if(fseek(fp, atol(argv[2]), SEEK_SET)) { fclose(fp);
printf(“erro na busca\n”); if((fp=fopen(“teste”,”w”))==NULL) {
exit(1); printf(“Arquivo não pode ser aberto\n”);
} exit(1);
printf(“O byte em %ld é %c\n”, atol(argv[2]), getc(fp)); }
fclose(fp); fscanf(fp, “%s%d”, s, &t); /* le do arquivo */
return 0; fprintf(stdout, “%s %d”, s, t); /* imprime na tela */
} return 0;
}
  No  Exemplo  7.9  verifica‐se  a  utilização  da  função  atol(),  que  tem  por  função  converter  uma 
string em um inteiro longo. No printf() utiliza‐se o modo de formatação %ld que é para apresentação de    A  formatação  realizada  para  leitura  e  escrita  de  dados  pode  comprometer  a  velocidade  de 
um decimal longo ou um inteiro longo.  processamento do programa. 

7.15 LENDO E GRAVANDO REGISTROS 
7.13.6 FTELL() 
  As funções fread() e fwrite() possibilitam uma maneira de transferir blocos de dados do disco 
  Esta função retorna a posição do ponteiro de um arquivo binário em relação ao seu começo.  para a memória do computador e vice‐versa. A grande vantagem destes comandos é poder ler e gravar 
Esta  função  aceita  um  único  argumento,  que  é  o  ponteiro  para  a  estrutura  FILE  do  arquivo.  Seu  dados maiores que um byte e que formem estruturas complexas (vetor, matriz, ou um registro, ou até 
protótipo é mostrado aqui:  um vetor de registros). 

Sintaxe: 
long ftell (FILE *fp); 7.15.1 ESCRITA DE UM BLOCO DE DADOS 

  Retorna um valor do tipo long, que representa  o número de bytes do começo do arquivo até a    Para se gravar um bloco de dados maiores que um byte o sistema de arquivo ANSI C fornece a 
posição atual.  função fwrite(). Seu protótipo está definido a seguir: 

  A  função  ftell()  pode  não  retornar  o  número  exato  de  bytes  se  for  usada  com  arquivos  em  Sintaxe: 
modo texto, devido à combinação CR/LF que é representada por um único caractere em C.  size_t fwrite(void *buffer, size_t num_bytes, size_t count, FILE *fp);

7.14 COMANDO DE GRAVAÇÃO EM MODO TEXTO FORMATADO  onde buffer é um ponteiro para uma região de memória que contém as informações que serão escritas 


no  arquivo.  O  número  de  bytes  para  gravar  é  especificado  por  num_bytes.  O  argumento  count 
  Como  extensão  das  funções  básicas  de  E/S  já  discutidas,  o  sistema  de  E/S  com  buffer  inclui  determina quantos itens serão gravados. E, finalmente, fp é um ponteiro de arquivo para uma stream 
fprintf()  e  fscanf().  Essas  funções  se  comportam  exatamente  como  printf()  e  scanf()  exceto  por  aberta anteriormente. 
operarem em arquivos. Os protótipos de fprintf() e fscanf() são 
  Esta  função  devolve  o  número  de  itens  escritos.  O  número  retornado  pode  ser  menor  que 
Sintaxe:  count quando o final de arquivo for atingido ou ocorrer um erro de gravação. 
int fprintf(FILE *fp, const char *control_string, ...);
Exemplo 7.11 
int fscanf(FILE *fp, const char *control_string, ...); int var_int;
FILE *arq;
onde fp é um ponteiro de arquivo devolvido por uma chamada à fopen(). fprintf() e fscanf() direcionam  arq = fopen(“dados.dat”,”wb”);
suas operações de E/S para o arquivo apontado por fp.   var_int = 5;
fwrite(&var_int,sizeof(var_int),10,arq);
A fscanf() devolve o número de elementos lidos, convertidos e armazenados. No caso de fim de 
arquivo o fscanf() devolve o valor EOF. 

  O Exemplo 7.10 mostra um programa que lê uma string e um inteiro do teclado, escreve‐os em  7.15.2 LEITURA DE UM BLOCO DE DADOS 


um arquivo em disco e em seguida lê e mostra a informação na tela. 
Exemplo 7.10 
59  Linguagem de Programação C  60  Linguagem de Programação C 
   

  Para  se  ler  um  bloco  de  dados  maiores  que  um  byte  o  sistema  de  arquivo  ANSI  C  fornece  a    A função memchr() procura, no vetor apontado por buffer, pela primeira ocorrência de ch nos 
função fread(). Seu protótipo está definido a seguir:  primeiros  count  caracteres.  Devolve  um  ponteiro  para  a  primeira  ocorrência  de  ch  em  buffer  ou  um 
ponteiro nulo se ch não for encontrado. O protótipo da função é 
Sintaxe: 
size_t fread(void *buffer, size_t num_bytes, size_t count, FILE *fp); Sintaxe: 
void *memchr(const void*buffer, int ch, size_t count);
onde buffer é um ponteiro para uma região de memória que receberá os dados do arquivo. O número  Exemplo 7.14 
de  bytes  para  ler  é  especificado  por  num_bytes.  O  argumento  count  determina  quantos  itens  serão  #include <stdio.h>
lidos. E, finalmente, fp é um ponteiro de arquivo para uma stream aberta anteriormente.  #include <string.h>
int main() {
char *p;
  Esta função devolve o número de itens lidos. O número retornado pode ser menor que count  p = memchr(“isto e um teste”. ´ ´,14);
quando o final de arquivo for atingido ou ocorrer um erro de leitura.  printf(p);
return 0;
Exemplo 7.12  }
int var_int;
FILE *arq;
arq = fopen(“dados.dat”,”rb”);   A função memcmp() compara os primeiros count caracteres das matrizes apontadas por buf1 e 
fread(&var_int,sizeof(var_int),1,arq); buf2. O valor devolvido segue os valores da função strcmp(). O protótipo da função é

Sintaxe: 
int memcmp(const void*buf1, const void*buf2, size_t count);
7.15.3 UTILIZANDO OS COMANDOS DE LEITURA E GRAVAÇÃO DE REGISTROS 
  A função memcpy() copia os primeiros count caracteres do vetor origem para o vetor apontado 
  Uma  das  mais  úteis  aplicações  de  fread()  e  fwrite()  envolve  ler  e  escrever  tipos  de  dados  por destino. Ela devolve um ponteiro para destino. O protótipo da função é 
definidos pelo usuário, especialmente estruturas.  
Exemplo 7.13  Sintaxe: 
#include <stdio.h> void *memcpy(void*destino, const void*origem, size_t count);
struct pto {
Exemplo 7.15 
int x,y; #include <stdio.h>
}; #include <string.h>
typedef struct pto ponto; #define SIZE 80
int main() { int main() {
FILE *fp; char buf1[SIZE], buf2[SIZE];
int i; strcpy(buf1, “Quando, no curso do ...”);
ponto coord[10]; memcpy(buf2, buf1, SIZE);
for (i=0; i < 10; i++) { printf(buf2);
printf(“Coordenada x:”); return 0;
scanf(“%d \n”,&coord[i].x); }
printf(“Coordenada y:”);
scanf(“%d \n”,&coord[i].y);   A  função  memmove()  copia  count  caracteres  do  vetor  apontado  por  origem  para  o  vetor 
} apontado  por  destino.  Se  as  matrizes  se  sobrepõem,  a  cópia  ocorrerá  corretamente,  colocando  o 
if ((fp=fopen(“figura.dat”,”w”))==NULL) {
conteúdo correto em destino, porém origem será modificado. Ela devolve um ponteiro para destino. O 
printf(“Arquivo não pode ser aberto\n”);
exit(1); protótipo da função é 
}
for (i=0; i < 10; i++) Sintaxe: 
fwrite(&coord[i], sizeof(ponto),1, fp);
rewind(fp); void *memmove(void*destino, const void*origem, size_t count); 
i = 0;
while(!feof(fp)) { Exemplo 7.16 
fread(&coord[i], sizeof(ponto), 1, fp); #include <stdio.h>
printf(“Coordenadas (x,y) = (%d,%d),coord[i].x,coord[i].y); #include <string.h>
} #define SIZE 80
fclose(fp);
return 0; int main() {
} char buf1[SIZE], buf2[SIZE];
strcpy(buf1, “Quando, no curso do ...”);
memmove(buf2, buf1, SIZE);
7.16 FUNÇÕES PARA MANIPULAÇÃO DE BUFFERS  printf(buf2);
}
  Para trabalhar com buffers utilizam‐se algumas funções especializadas que são independentes 
de tipo.    A  função  memset() copia o  byte menos significativo de ch nos primeiros count caracteres do 
vetor apontado por buf. Ela devolve buf. é muito utilizado na inicialização de uma região de memória 
com algum valor conhecido. O protótipo da função é
61  Linguagem de Programação C  62  Linguagem de Programação C 
   

Sintaxe: 
void *memset(void*buf, int ch, size_t count);
Exemplo 7.17  A. TABELA ASCII 
/* Inicializa com nulo os 100 primeiros bytes */
memset(buf, ’\0’, 100); /*do vetor apontado por buf */
Cód Car Cód Car Cód Car Cód Car Cód Car Cód Car Cód Car Cód Car
/* Inicializa com X os 10 primeiros bytes */
memset(buf, ’X’, 10); 0 32 64 @ 96 ‘ 128 Ç 160 á 192 └ 224 Ó
printf(buf); 1 ☺ 33 ! 65 A 97 a 129 ű 161 í 193 ┴ 225 ß
2 ☻ 34 ” 66 B 98 b 130 é 162 ó 194 ┬ 226 Ô
7.17 EXERCÍCIOS  3 ♥ 35 # 67 C 99 c 131 â 163 ú 195 ├ 227 Ò
4 ♦ 36 $ 68 D 100 d 132 ä 164 ñ 196 ─ 228 õ
1. Faça um programa que escreva os números de 0 a 10 em um arquivo.  5 ♣ 37 % 69 E 101 e 133 à 165 Ñ 197 ┼ 229 Õ
6 ♠ 38 & 70 F 102 f 134 å 166 ª 198 ã 230 µ
2. Faça um programa que leia 11 números de um arquivo. 
7 • 39 ’ 71 G 103 g 135 ç 167 º 199 Ã 231 þ
3.  Escrever um programa em C que leia um número indeterminado de códigos (inteiro) e taxas de  8 40 ( 72 H 104 h 136 ê 168 ¿ 200 ╚ 232 Þ
consumo  (real),  em  Kw,  dos  consumidores  de  uma  cidade  e  grave  os  dados  em  um  arquivo  9 41 ) 73 I 105 i 137 ë 169 ® 201 ╔ 233 Ú
chamado medidas.txt. O programa pára de ler quando o código fornecido for zero.   10 42 * 74 J 106 j 138 è 170 ¬ 202 ╩ 234 Û
11 ♂ 43 + 75 K 107 k 139 ï 171 ½ 203 ╦ 235 Ù
4. Escrever um programa em C que leia o arquivo gerado no exercício 1 e mostre quantas taxas 
12 ♀ 44 , 76 L 108 l 140 î 172 ¼ 204 ╠ 236 ý
existem e o maior consumo. 
13 45 - 77 M 109 m 141 ì 173 ¡ 205 ═ 237 Ý
14 ♫ 46 . 78 N 110 n 142 Ä 174 « 206 ╬ 238 ¯
5. Escrever  um  programa  em  C  que  leia  um  arquivo  texto  (temp.txt)  e  grave  em  outro  arquivo 
(tempMax.txt). O arquivo origem possuiu um número indeterminado de linhas, onde cada linha  15 ☼ 47 / 79 O 111 o 143 Å 175 » 207 ¤ 239 ´
possui  5  colunas:  dia,  mês,  ano,  temperatura  mínima  (real),  temperatura  máxima  (real).  O  16 4 48 0 80 P 112 p 144 É 176 ░ 208 ð 240 ­
programa  deve  escrever  no  arquivo  tempMax.txt  o  dia,  mês,  ano,  e  a  temperatura  máxima  17 3 49 1 81 Q 113 q 145 æ 177 ▒ 209 Ð 241 ±
somente  quando  ela  for  maior  que  a  temperatura  máxima  do  dia  anterior.  Por  exemplo,  18 ↕ 50 2 82 R 114 r 146 Æ 178 ▓ 210 Ê 242 _
assuma que o arquivo temp.txt possua o seguinte conteúdo:  19 !! 51 3 83 S 115 s 147 ô 179 │ 211 Ë 243 ¾
10 10 2007 16.5 32.2 20 ¶ 52 4 84 T 116 t 148 ö 180 ┤ 212 È 244 ¶
11 10 2007 15.4 31.0 21 § 53 5 85 U 117 u 149 ò 181 Á 213 245 §
12 10 2007 17.5 32.7 22 ▬ 54 6 86 V 118 v 150 û 182 Â 214 Í 246 ÷
13 10 2007 18.6 32.4 23 ↕ 55 7 87 W 119 w 151 ù 183 À 215 Î 247 ¸
14 10 2007 17.5 30.3 24 ↑ 56 8 88 X 120 x 152 ÿ 184 © 216 Ï 248 °
15 10 2007 18.3 31.2 25 ↓ 57 9 89 Y 121 y 153 ö 185 ╣ 217 ┘ 249 ¨
16 10 2007 16.8 30.2 26 → 58 : 90 Z 122 z 154 Ü 186 ║ 218 ┌ 250 ·
17 10 2007 17.6 31.3 27 ← 59 ; 91 [ 123 { 155 ø 187 ╗ 219 █ 251 ¹
28 ¬ 60 < 92 \ 124 | 156 £ 188 ╝ 220 ▄ 252 ³
O programa deve gerar o arquivo tempMax.txt com o seguinte conteúdo:  29 ↔ 61 = 93 ] 125 } 157 Ø 189 ¢ 221 ¦ 253 ²
12 10 2007 32.7 30 t 62 > 94 ^ 126 ~ 158 × 190 ¥ 222 Ì 254
15 10 2007 31.2 31 u 63 ? 95 _ 127 Δ 159 ƒ 191 ┐ 223 ▀ 255
17 10 2007 31.3
 
63  Linguagem de Programação C 
 

8 BIBLIOGRAFIA 

BERRY, J. Programando em C++. São Paulo: Makron Books, 1991. 

ECKEL, B. C++. São Paulo: McGraw‐Hill, 1991. 

ELLIS, M. A. et alli C++: Manual de referência completo. Rio de Janeiro: Campus, 1993. 

IBPI. Dominando a Linguagem C. Rio de Janeiro: IBPI, 1993. 

MIZRAHI, V. V. Treinamento em Linguagem C. São Paulo: McGraw‐Hill, 1990. 

PAPPAS, C. H.; MURRAY, W. Turbo C++ completo e total. São Paulo: McGraw‐Hill, 1991. 

SCHILDT, H. C Completo e total. São Paulo: McGraw‐Hill, 1991. 
 

Você também pode gostar