Programar em C/Estudo

Origem: Wikilivros, livros abertos por um mundo aberto.

Tabela de conteúdo

[editar] PROGRAMAR EM C

Manual autodidacta, …

[editar] Começar a programar em C

Vamos buscar o programa que escrevemos anteriormente. É melhor memorizar estas linhas.


/* o meu primeiro programa */
#include <stdio.h>
#include <stdlib.h>
int main()
{
    printf("This is output from my first program!\n");
    system ("pause");
    return 0;
}



#include <stdio.h> O símbolo “#” é uma chamada de atenção ao compilador – quando ele fizer o seu trabalho terá um cuidado exclusivo com esta linha. Depois temos o “include” (que basicamente diz para incluirmos). Incluir o quê? O arquivo-cabeçalho stdio.h (std = standard, padrão em inglês; io = Input/Output, entrada e saída ==> stdio = Entrada e saída padronizadas; entrada temos o keyboard e saída temos o ecrã). A terminação .h vem de header e normalmente tratam-se de bibliotecas. Existem outros arquivos para além do stdlib.h, temos a biblioteca de matemática, a de tempo, etc …. Podemos ver na parte dos anexos algumas que existem e com as funções de cada uma. Mas, nós próprios podemos criar uma biblioteca. E até podemos comprar bibliotecas que existem por aí. Bibliotecas são códigos que alguns programadores fizeram antes. As que citamos primeiramente são as standard, são aquelas que têm as funcionalidades básicas. Veja você que precisamos da biblioteca até para escrever no ecrã (std + out) que nos permite utilizar a função printf. Eu recorri á biblioteca do stdlib.h para poder utilizar a função system (“pause”).

Caso contrário, o programa era executado e corrido e nós não víamos nada, a janela abria e fechava-se e nós não perceberíamos a saída "Hello World".



int main() É a função principal. Em programação em C temos funções atrás de funções. Esta função

main() é a tal com que vamos chamar as outras. Por isso mesmo ela é obrigatória em

qualquer programa. Mais ela é a primeira função a correr e quando o programa acabar de

correr a função main o programa acaba! é a primeira e ultima.

o int significa que a função vai retornar um inteiro. São a variedade de tipos. Temos

  • int que é a abreviatura de inteiro
  • char que é a abreviatura de character
  • float que é a abreviatura de floating number, ou seja um número real

temos mais mas por enquanto vamos continuar.(ver a secção resumo e quadros -anexos…)

Note que todas as funções têm ( ), só assim é que o compilador reconhece que é

uma função. A idéia de ter funções é permitir o encapsulamento de uma idéia ou operação, dar

um nome a isso e depois chamar essa operação de várias partes do programa simplesmente usando

o seu nome. Nós podemos chamar funções já existentes do C e também criar as nossas próprias funções

dando nomes a elas. Uma das grandes vantagens nisto é a economia de código que se pode ter durante a

construção de um programa.


{} O compilador ao ver as chavetas compreende aquilo como um bloco. O código que estiver dentro

das chaves será executado sequencialmente.



printf () Podemos utilizar esta função apenas porque pusemos no header a biblioteca stdio . O que esta função nos permite é enviar o que temos entre aspas para o monitor (out).

Mas termos a hipótese de poder formatar o texto enviado, por exemplo se acrescentássemos

\n a meio tipo: printf("This is output \n from my first program! "), o resultado seria

chegar ao fim do output e teriamos uma nova linha. experiementem!



return 0 faz com que a função retorne o valor zero que é o fim da execução do programa. repare-se que

podíamos ter posto return (0) que até seria mais correcto, mas como o valor é zero, o

compilador aceita.



; o ponto e virgula é essencial para a programação em C, funciona como o ponto final: separa as frases e contextos. No C, o ponto e vírgula está no final da maioria das linhas e determina o final de cada "operação". Na sua ausência teremos erro enquanto compilamos o programa.



/*o meu primeiro programa*/ Como qualquer outra linguagem, o C nos permite inserir comentários entrelaçados no código-fonte do programa, ou melhor permite escrever o que pretendermos entre /* e */ que na hora da compilação estes serão descartados: não têm utilidade em nada para o compilador, no entanto são bons para nós programadores, pois pode nos auxiliar a não ficarmos perdidos durante a programação de um programa de código-fonte muito extenso.



[editar] 2º Programa

#include <stdio.h>                  /*ao evocarmos esta biblioteca podemos utilizar a função printf e scanf*/
#include <stdlib.h>                  /*ao evocarmos esta biblioteca podemos utilizar a função system (“pause”)*/
int main()		              /*declaramos que a função “main” vai retornar um  inteiro*/
{
int a, b, c;		              /*estamos a declarar 3 variáveis que vão ter valores inteiros */
a = 5;			              /*aqui estamos a alocar o valor 5 á variável a*/
printf (“digite um valor”);	      /*é escrito no ecrã a frase – digite um valor*/
scanf (“%d”, &b);		      /*a função lê algo que se espera que esteja no formato de inteiro e associa ao endereço da variável b, ie, escreve o valor recebido na variável b o endereço vem do & */
c = a + b;			      /*aqui vai pegar no valor da variável a e b vai proceder á operação soma e o resultado vai ser colocado na variável */
printf("%d + %d = %d\n", a, b, c);   /*aqui a função printf vai imprimir o texto entre aspas, mas antes vai substituir o %d pelo valor das várias variavies*/
system (“pause”);		      /* com esta função podemos ver o resultado podemos fazer uma se antes de encerrar  */
return 0;			      /*a função main vai retornar o valor zero e encerra o programa*/

}

[editar] Declarando variáveis

Vai-nos ocorrer a necessidade do programa guardar certos valores. Assim vamos querer

declarar uma variável para depois guardar um valor e mais tarde poder ser utilizado

a sua forma geral é: tipo_da_variável lista_de_variáveis;

Para poder declarar uma variável podemos escrever

int a; Neste caso apenas estamos a declarar uma variável que chamamos “a”. O que é que está a acontecer? Estamos a pedir ao computador para reservar um espaço da sua

memória. e que depois associe esse espaço de memória ao nome “a”. Assim o computador tem de

saber onde aloca as coisas. É como tivéssemos um quadro de duas colunas, uma para os nomes e

outra para o valor.

a=5; Neste caso estamos a fazer com que o computador guarde o valor 5 no espaço alocado á

variável a;

Como primeiro pedimos para reservar memória e depois escrevemos o valor, por isso é que

temos que dizer o quanto espaço de memória é que deve ser reservado daí o int antes de a; Agora o que aconteceria se puséssemos a=5,555.. não sei qual é que seria o resultado. Mas ou

não compilaria, ou se isso acontecesse estaríamos a alocar mais memória do que pedimos e

portanto poderia acontecer estarmos a escrever em memória que pertencia a outro programa,

logo podemos estar a corromper os nossos dados, penso que não é coisa que se queira.

Podemos declarar uma variável ao mesmo tempo que lhe damos um valor, por exemplo: int a=5;

Podemos ainda declarar quantas variáveis queiramos ao mesmo tempo; int a, b,c,d; têm é que ter as vírgulas no meio e ponto e virgula no final


Podemos declarar várias variáveis ao mesmo tempo e associar os respectivos valores? int a=5, b=6; sim também podemos fazer isso, não esquecer as virgulas.


• Lembre-se sempre que é necessário declarar a variável antes de atribuirmos um valor,

caso contrário esse valor irá para um sítio qualquer na memória e é bem possível alterar uma

memória já atribuída. • E declarar a variável antes de utilizarmos. • O nome das variáveis terão de ser únicas, como é lógico! • Há diferença entre termos maiúsculas e minúsculas (pois elas têm um valor diferente

no código ASCII) logo ter – nome e NOME ou mesmo Nome são todas variáveis diferentes – o C É

CASE SENSITIVE • Por fim não podem ter o nome das palavras reservadas que são os nomes das funções e

tipologia, (ver quadro) • Utiliza-se as letras números e o sublinhado (_) • Variáveis até 32 caracteres são aceites • é pratica comum usar letras minúsculas para nomes de variáveis e maiúsculas para

nomes de constantes.


[editar] Palavras Reservadas do C

for continue break switch else case return goto default do while if

struct typedef sizeof union extern enum

int double long char float short unsigned void signed

const register volatile static auto

[editar] Tipologia de variáveis

C tem 5 tipos básicos:

  1. char,
  2. int,
  3. float,
  4. void,
  5. double

Quando declaramos uma variável teremos de indicar de que tipologia a variável é. A razão

disto tem a ver que cada tipologia ocupa um número de bits diferentes. e o computador agrega

cada tipologia em tabelas distintas, por exemplo, uma para ints outra para floats, etc. ou seja uma das razões é realmente a optimização da memória. mas também existe uma outra

razão que tem a ver com as operações. isto é, se tivermos um dado int, e se passarmos para o

seguinte nós vamos passar para o próximo int. ou seja asseguramos que o número seguinte é um

int. convêm ver a tabela das tipologias.

Tipo	Num de bits	Formato para leitura com scanf	Intervalo
                                         Inicio	Fim
char	                 8	%c	-128	        127
unsigned char	         8	%c	0	        255
signed char	         8	%c	-128	        127
int	                16	%i	-32.768	        32.767
unsigned int	        16	%u	0	        65.535 
signed int	        16	%i	-32.768 	32.767
short int	        16	%hi	-32.768	        32.767
unsigned short int     16	%hu	0	        65.535
signed short int       16	%hi	-32.768	        32.767 
long int	        32	%li	-2.147.483.648	2.147.483.647
signed long int        32	%li	-2.147.483.648	2.147.483.647
unsigned long int      32	%lu	0	        4.294.967.295
float	                32	%f	3,4E-38	        3.4E+38 
double	                64	%lf	1,7E-308	1,7E+308
long double	        80	%Lf	3,4E-4932 	3,4E+4932

para os números reais temos as seguintes variantes: float double long double

para os inteiros: int short int long int unsigned signed

[editar] printf ()

A função printf () está dentro da biblioteca stdio.h. O que ela faz é enviar para “out”, no nosso caso o ecrã o que está dentro das aspas.


printf(“amor é mio”);	

resultado: amor é mio


printf (“a variável a = %d”, a);	

Aqui vai novamente imprimir no ecrã o texto que está entre aspas, mas vai fazer umas

substituições antes de o fazer. Vê que existe o símbolo % e vai substituir pelo valor da variável “a” que era 5. O “d” a seguir ao %, é uma formatação do valor que vai ser substituído. O d é de inteiro.

Por isso, independentemente da tipologia que seja o valor “a” (quer seja inteiro, float…)

ele vai ser substituído pelo seu valor como inteiro.


printf (“%c\t%f\t%d\t”, a, a ,a);

Aqui o que acontece é pedirmos para fazer a substituição do primeiro % pelo valor da

primeira variável, o segundo % pela segunda, etc e por aí adiante. No exemplo tínhamos o

valor 5 na variável a, e aqui pedimos para escrever o 5 em c de carácter, em f de float e em

d de inteiro.



[editar] Formatações do printf

Constantes de barra invertida

Código	Significado
\b 	Retrocesso ("back")
\f  	Alimentação de formulário ("form feed")  
\n  	Nova linha ("new line")  
\t  	Tabulação horizontal ("tab")  
\"  	Aspas  
\'  	Apóstrofo  
\0   	Nulo (0 em decimal)  
\\  	Barra invertida  
\v  	Tabulação vertical  
\a 	Sinal sonoro ("beep")  
\N  	Constante octal (N é o valor da constante)  
\xN	Constante hexadecimal (N é o valor da constante)
Código	Formato
%c	Um caracter (char)
%d	Um número inteiro decimal (int)
%i	O mesmo que %d
%e	Número em notação científica com o "e"minúsculo
%E	Número em notação científica com o "e"maiúsculo
%f	Ponto flutuante decimal
%g	Escolhe automaticamente o melhor entre %f e %e
%G	Escolhe automaticamente o melhor entre %f e %E
%o	Número octal
%s	String
%u	Decimal "unsigned" (sem sinal)
%x	Hexadecimal com letras minúsculas
%X	Hexadecimal com letras maiúsculas
%%	Imprime um %
%p	Ponteiro 

É possível também indicar o

  1. . Tamanho do campo,
  2. . Justificação e o
  3. . Número de casas decimais

%5d estamos indicando que o campo terá cinco caracteres de comprimento no mínimo

  • Se o inteiro precisar de mais de cinco caracteres para ser exibido então o campo terá o

comprimento necessário para exibi-lo.

  • Se o comprimento do inteiro for menor que cinco então o campo terá cinco de comprimento e

será preenchido com espaços em branco.

  • Se se quiser um preenchimento com zeros pode-se colocar um zero antes do número
  • O alinhamento padrão é à direita. Para se alinhar um número à esquerda usa-se um sinal -

antes do número de casas. Então %-5d será o nosso inteiro com o número mínimo de cinco

casas, só que justificado a esquerda. %10.4f indica um ponto flutuante de comprimento total dez e com 4 casas decimais.

Código	Imprime
printf ("%-5.2f",456.671);	| 456.67|
printf ("%5.2f",2.671);	| 2.67|
printf ("%-10s","Ola");	|Ola       |

[editar] Operações

o C permite realmente fazer umas operações entre valores. Ou seja com estas armas podemos

fazer operações entre valores, variáveis (e os seus valores) e ainda os endereços das variáveis. temos os operadores aritméticos, os relacionais e lógicos e os específicos para os bits.

Operadores Aritméticos e de Atribuição

Operador	Ação
+	Soma (inteira e ponto flutuante)
-	Subtração ou Troca de sinal (inteira e ponto flutuante) 
*	Multiplicação (inteira e ponto flutuante) 
/	Divisão (inteira e ponto flutuante) 
%	Resto de divisão (de inteiros) 
++	Incremento (inteiro e ponto flutuante) 
--	Decremento (inteiro e ponto flutuante)


Operadores Relacionais e Lógicos

Operador	Ação
>	Maior do que
>=	Maior ou igual a
<	Menor do que
<=	Menor ou igual a
==	Igual a
!=	Diferente de

(repare no valor = = quando queremos comparar. no C o = é associativo, ie, é como dizer

atribui o valor. por isso temos o igual igual para fazer a distinção quando queremos é

comparar os valores. Ver o quadro dos operadores lógicos, especialmente os símbolos: && o ||

e o ! que são “and”, “or”, e “not”, respectivamente – que são os mais estranhos.)


Operador	Ação
&&	AND (E)
||	OR (OU)
!	NOT (NÃO)

Operadores Lógicos Bit a Bit

Operador	Ação
&	AND
|	OR
^	XOR (OR exclusivo)
~	NOT
>>	Deslocamento de bits à direita
<<	Deslocamento de bits à esquerda
valor>>número_de_deslocamentos

Pergunta: mas como é que impriminos um nº na forma binária, uma vez que temos operadores

binários??? ou melhor: ainda não vi nada sobre os bits.


para os operadores de aritmética convém dizer que o C permite algumas abreviaturas, o que me

parece ser uma estupidez permiti-las uma vez que elas não poupam assim muito, e claro, têm o

inconveniente de obrigar a memorizar.

Expressão Original	Expressão Equivalente
x=x+k;	                 x+=k;
x=x-k;	                 x-=k;
x=x*k;	                 x*=k;
x=x/k;	                 x/=k;
x=x>>k;	         x>>=k;
x=x<<k;	         x<<=k;
x=x&k;	                 x&=k;
etc...a seguir abreviatura de abreviatura
X++       equivale a´   x+=1



#include <stdio.h>
#include <stdlib.h>
main()
{
     int a,b;
     a = b = 5;
     printf("%d\n", ++a+5);
     printf("%d\n", a);
     printf("%d\n", b++ +5);
     printf("%d\n", b);
     system (“pause”);
     return 0;
}


Este exemplo mostra que não é bem igual ter ++a ou a++. este ultimo adiciona quando for

passar para a linha seguinte.

Convém ainda dizer que nesta linguagem toda do C há certos símbolos que têm maior

precedência. isto é perfeitamente normal ter de esclarecer uma vez que admitimos operações

aritméticas. e a sequencia nem sempre deve ser seguida.

é melhor dar um exmplo: se eu tiver a=10+2*3. se eu fizesse isto sequencialmente daria a=12*3 e

a=36

se eu der agora precedência de operadores, e portanto é a hierarquia dos operadores que vai

dar o resultado. ficaria a=10+6=16.

apanharam o ponto! espero que sim porque é bom ter em mente sempre esta tabela. se uma

pessoa tiver duvidas pode sempre recorrer ao topo e ao fim da hierarquia e fazer sempre eg.

a=10+(2*3). em caso de igualdade de precedência predomina a sequencia de enumeração, ou seja o que vem

antes …

Maior precedência	
 () [] ->
 ! ~ ++ -- . -(unário) (cast) *(unário) &(unário) sizeof
 * / %
 + -
 <<  >>
 <<=  >>=
 == !=
 &
 ^
 |
 &&
 ||
 ?
 =  +=  -=  *=  /=
 ,
Menor precedência	



#include <stdio.h>
#include <stdlib.h>
main()
  {
     int i,j;
     float f;
     i = 5; j = 2;
     f = 3.0;
     f = f + j / i;
     printf("value of f is %f\n", f);
     system (“pause”);
     return 0;
  }

[editar] . Modeladores (Cast)

Ora bem já temos a tipologia de variáveis e agora mesmo as operações aritméticas, convém

agora explicar o ponto comum entre as duas. Se eu tiver a=10/3, o resultado sei que é 3,(3). Ou seja a divisão de dois

números inteiros deu um número real.

acontece que se eu declarar int a; Estou a dizer que ele será inteiro, o resultado seria 3. mesmo que eu diga float a; o resultado continua a ser 3 mas desta vez, 3,0000 ou seja tenho tenho de converter um dos inteiros (pelo menos) em float, (que o outro se

converte).

então eu poderia fazer a=(float)10/3;

forma geral é:

(tipo)expressão

mas existem umas conversões automáticas:

int f(void)
{
   float f_var;
   double d_var;
   long double l_d_var;
   f_var = 1; d_var = 1; l_d_var = 1;
   d_var = d_var + f_var;		/*o float é convertido em double*/
   l_d_var = d_var + f_var;		/*o float e o double convertidos em long double*/	
   return(l_d_var);
}

repare-se que a conversão é feita para o maior. é possível fazer a conversão ao contrário de um tipo com mais bits para um com menos bits e

isso é truncar.

[editar] . scanf ()

O scanf() é uma função que está também na biblioteca stdio.h o que faz é ler o que

foi inserido, por exemplo, via teclado e guardar o que foi escrito.

scanf (“%d”, &a);

Neste caso estamos a dizer para a variável ler o que foi teclado, eg, que vai ser um

inteiro, ou melhor em formato inteiro e depois colocar esse valor no endereço da variável

“a” Na verdade confesso que não percebo bem esta parte o símbolo & é símbolo de endereço físico. Ou seja nós criamos a variável a antes. e agora estamos a dizer para alocar o valor que for

teclado nesse espaço. assim porque escrever & ,quando nos bastaria escrever a? Isto é um pouco intrigante. mas vamos ter a resposta daqui a um pouco.

scanf (“%d%c%f”, &a,&b,&c);

Tal como na função printf, podemos receber quantos valores quisermos, neste caso, estamos á

espera de um inteiro e associamos ao primeiro endereço, o 2º ao 2º endereço, e por aí

adiante, repare-se que têm de estar separados por virgulas


Respondendo á questão intrigante que coloquei aqui, penso que a resposta pode ser esta:

utiliza-se um intermediário. ou seja, ao escrevermos vai ser reservado um espaço pelo

sistema operativo e vai colocar esse valor lá nesse espaço reservado por ele, nós depois

como queremos dar o nome de a, vamos fazer com que ele aponte para o valor de a. Não percebo bem a vantagem disto mas…até faz sentido porque asim não temos de fazer o copy

para a nova morada e eliminar a antiga. é uma etapa extra sem qualquer necessidade

Faço aqui uma questão: vamos imaginar que imprimíamos 12.5 e estávamos á espera de receber um int. o que é que vai

acontecer. vamos testar.


#include <stdio.h>
#include <stdlib.h>
int main ()
 {
   int a;
   printf ("digite um número”);
   scanf (“%d”, &a);
   printf ("\no nº impresso foi %d”, a);
   system (“pause”);
   return(0);
}


ele só vai pegar no 12!

vou retomar esta função mais tarde por causa do & que convém perceber!!!

Protótipo:

int scanf (char *str,...);

Devemos sempre nos lembrar que a função scanf() deve receber ponteiros como parâmetros. Isto

significa que as variáveis que não sejam por natureza ponteiros devem ser passadas

precedidas do operador &.

Código	Formato
%c	Um único caracter (char)
%d	Um número decimal (int)
%i	Um número inteiro
%hi	Um short int
%li	Um long int
%e	Um ponto flutuante
%f	Um ponto flutuante
%lf	Um double
%h	Inteiro curto
%o	Número octal
%s	String
%x	Número hexadecimal
%p	Ponteiro

[editar] Branching - IF

#include <stdio.h>			/*ao evocarmos esta biblioteca podemos utilizar a função printf e scanf*/
#include <stdlib.h>			/*ao evocarmos esta biblioteca podemos utilizar a função system (“pause”) */
int main()				/*declaramos que a função “main” vai retornar um inteiro*/
 {
 int a;				/*estamos a declarar 1 variáveis que vai ter valores inteiros */
 printf (“digite uma valor”);	        /*é escrito no ecrã a frase…*/
 scanf (“%d”, &a);		        /*a função lê algo que se espera que esteja no formato de inteiro e associa ao endereço da variável a, ie, escreve o valor recebido na variável b*/
 if (a<10)			        /*verifica a condição a<10, se for verdadeira lê o código do bloco*/
   printf ("o valor %d é menor que 10\n", a);	/*aqui a função printf vai imprimir o texto entre aspas,mas antes vai substituir o %d pelo valor das várias variareis/
 system (“pause”);		       /* com esta função podemos ver o resultado podemos fazer uma pause antes de encerrar  */
 return 0;			       /*a função main vai retornar o valor zero e encerra o programa*/
 }


A função if é muito parecida com a ideia que nós temos na linguagem comum, ou seja, se (isto)…então (aquilo). mas vamos ver algumas variantes, para dar mais flexibilidade. mas

o sistema vai ser sempre igual.

if (condição) declaração;

Se a condição for avaliada como zero, a declaração não será executada


if (condição) declaração_1; 
else declaração_2;

Se a condição for avaliada como diferente de zero, a declaração_1 será executada. caso


contrário será a declaração_2. Repare que estamos a garantir que uma das declarações será sempre executada

if (condição_1) declaração_1; 
else if (condição_2) declaração_2; 
….
else if (condição_n) declaração_n; 
else declaração_default

o programa começa a testar as condições começando pela 1 e continua a testar até que ele

ache uma expressão cujo resultado dê diferente de zero. Neste caso ele executa a declaração

correspondente. Só uma declaração será executada, ou seja, só será executada a declaração

equivalente à primeira condição que der diferente de zero. A última declaração (default) é a

que será executada no caso de todas as condições darem zero e é opcional.

Podemos ter ainda os if aninhados que basicamente é um if dentro do outro.

#include <stdio.h>
int main ()
{
  int num;
  printf ("Digite um numero: ");
  scanf ("%d",&num);
  if (num==10)
      	{
       	printf ("\n\nVoce acertou!\n");
       	printf ("O numero e igual a 10.\n");
      	}
  else
      	{
       	if (num>10)
              	  {
           		 printf ("O numero e maior que 10.");
              	  }
       	else
             	  {
               	 printf ("O numero e menor que 10.");
                 }
      	}
  return(0);
}


Mais abreviaturas!!! Continuo a dizer que é uma estupidez isto das abreviaturas para não

escrever uns poucos de caracteres!!

if (num!=0)				é equivalente a ter 	if (num)
if (num==0)				é equivalente a ter	if (!num)
for (i=0 ; string[i] !=’\0’ ; i++)	é equivalente a ter	for (i=0; string[i]; i++)

[editar] Comando "?"

Temos ainda o operador “?” que é mais outro tipo de abreviaturas

if (a>0)
       b=-150;
else
       b=150;

b=a>0?-150:150;

condição?expressão_1:expressão_2; esta porcaria abreviaturas é que torna isto horrível. obriga a um esforço elevado de

memorização sem ter um retorno. bem o retorno atrevo-me a dizer que até é negativo!

[editar] O Comando switch

A forma geral é

switch (variável)
{
    case constante_1: 
    declaração_1; 
  break; 
    case constante_2: 
    declaração_2; 
  break; 
. 
. 
. 
    case constante_n: 
    declaração_n; 
  break; 
    default 
    declaração_default; 
}

O comando break, faz com que o switch seja interrompido assim que uma das declarações seja

executada

#include <stdio.h>
int main ()
{
  int num;
  printf ("Digite um numero: ");
  scanf ("%d",&num);
  switch (num)
   {
    case 9:
              printf ("\n\nO numero e igual a 9.\n");
              break;
    case 10:
              printf ("\n\nO numero e igual a 10.\n");
              break;
    case 11:
              printf ("\n\nO numero e igual a 11.\n");
              break;
    default:
              printf ("\n\nO numero nao e nem 9 nem 10 nem 11.\n");
   }
  return(0);
}

[editar] LOOP- WHILE

O que é que é um loop. Basicamente é repetir um conjunto de linhas várias vezes até …ie,

sujeito a uma condição. O while() testa uma condição. Se esta for verdadeira a declaração é executada e faz-se o

teste novamente Se eu tiver.


while (a<b)
{
  printf (“%d é menor que %d”,a, b)
  a=a+1
}

Em português seria enquanto a condição for verdadeira repete o código do bloco. Repare-se que é como fosse um if, ie, se a condição for verdadeira faz isto. Só que o isto é

repetido enquanto a condição for verdadeira. Volta ao início do bloco. enquanto o if não!

segue sempre em frente.

[editar] LOOP – DO WHILE

do
{
  printf (“%d\n”, a);
  a=a+1;
}
 while (a<b);


A função do while é exactamente igual á do while só que põe a condição depois do bloco, o

que significa que o bloco é executado pelo menos uma vez.

do 
{ 
  declaração; 
} while (condição); 


  • O ponto-e- vírgula final é obrigatório.
  • A estrutura do-while executa a declaração, testa a condição e, se esta for

verdadeira, volta para a declaração.

  • Garante que a declaração será executada pelo menos uma vez.
  • Um dos usos da estrutura do-while é em menus


#include <stdio.h>
#include <stdlib.h>
int main ()
{
  int  i;
  do
   {
      	printf ("\n\nEscolha a fruta pelo numero:\n\n");
      	printf ("\t(1)...Mamao\n");
      	printf ("\t(2)...Abacaxi\n");
      	printf ("\t(3)...Laranja\n\n");
      	scanf("%d", &i); 
   } while ((i<1)||(i>3));
  switch (i)
       {
      	case 1:
               printf ("\t\tVoce escolheu Mamao.\n");
       	break;
      	case 2:
               printf ("\t\tVoce escolheu Abacaxi.\n");
       	break;
      	case 3:
               printf ("\t\tVoce escolheu Laranja.\n");
       	break;
       }
  system (“pause”);
  return(0);
}

[editar] LOOP – FOR

Este é para mim a melhor função, muito compacta e extremamente poderosa a sua forma geral é

for (inicialização;condição;incremento) declaração;


for (a=1; a<10; a=a+1)
{
código do bloco
}

o que diz é para “a” igual a 1, até que “a” seja menor que 10, executa o código do bloco e

depois executa o incremento de uma unidade a “a”. Volta a repetir o processo até que a fique

maior ou igual a 10. note que quando fazemos a=1, estamos a atribuir o valor 1 a “a”.


a função loop é tão versátil que podemos dar várias condições de iniciação, várias de

finalização e várias de incremento.

for (a=1, b=1; a<10 || b>20 ; a=a+1, b++)
{
código do bloco
}

Neste caso temos duas condições de iniciação. Repare que estão separadas por virgulas. Temos depois duas de finalização com o operador lógico “ou” a primeira das condições que se

tornar verdadeira irá parar o ciclo.

Se a condição for verdadeira ele executa a declaração, faz o incremento e volta a testar a

condição. Ele fica repetindo estas operações até que a condição seja falsa.

  • Um ponto importante é que podemos omitir qualquer um dos elementos do for, isto é, se não

quisermos uma inicialização poderemos omiti-la.

  • repare que se eliminarmos a condição,[ for (inicialização; ;incremento) declaração;] temos

um loop infinito

[editar] O Comando break

O break faz com que a execução do programa continue na primeira linha seguinte ao loop ou

bloco



[editar] O Comando continue

Quando o comando continue é encontrado, o loop pula para a próxima iteração, sem o abandono

do loop. é melhor ver o exemplo:

#include <stdio.h>
#include <stdlib.h>
int main()
{
  int opcao;
  while (opcao != 5)
   {
     printf("\n\n Escolha uma opcao entre 1 e 5: ");
     scanf("%d", &opcao);
     if ((opcao > 5)||(opcao <1)) continue;           /* se Opcao invalida: volta ao inicio 

do loop */

     switch (opcao)
      { 
         case 1: 
               printf("\n --> Primeira opcao..");
         	break; 
         case 2: 
         	printf("\n --> Segunda opcao..");
               break; 
         case 3: 
        	printf("\n --> Terceira opcao..");
         	break; 
         case 4: 
              printf("\n --> Quarta opcao..");
         	break; 
         case 5: 
              printf("\n --> Abandonando..");
         	break; 
         } 
      }
  system (“pause”);
  return(0);
}

recebe uma opção do usuário. Se esta opção for inválida, o continue faz com que o fluxo seja

desviado de volta ao início do loop. Caso a opção escolhida seja válida o programa segue

normalmente.


[editar] O Comando goto

O goto realiza um salto para um local especificado. Este local é determinado por um rótulo.

Portanto pode ser em qualquer parte do programa.

nome_do_rótulo: 
.... 
goto nome_do_rótulo; 
....

[editar] ARRAYS – VECTORES

Bem podemos dizer que ao entrar agora nesta parte vamos partir para uma segunda etapa, é

como tivéssemos a criar mais dimensões. Aqui vamos!

Aqui vamos declarar novamente variáveis. Só que desta vez vamos pensar como conseguiríamos

criar 1000 variáveis. Como vimos declarar variáveis era apenas

int a; 

Agora podemos fazer

int a[1000];

Funciona tipo um índice, ie, cria o a[0], a[1], a[2]….. a[999]. repare-se que começa no 0 e

não 1. Maior parte dos bugs vêm daqui – pensarmos que o índice vai até 1000!!!!

Podemos até ter

int a[i];
for (i=0; i<5; i++)
  a[i]=i;

Neste exemplo iríamos ter i até 5 e cada um com um valor já atribuído. Este exemplo

realmente está engraçado. Porque podíamos brincar …never mind

[editar] Matrizes

se nós pensarmos melhor, podemos declarar ainda mais variáveis

tipo_da_variável nome_da_variável [altura][largura];

Ter em atenção que:

  • Índice mais à direita varia mais rapidamente que o índice à esquerda.
  • Não esquecer os índices variam de zero ao valor declarado, menos um

[editar] multidimensionais

podemos ter ainda conjunto de variáveis multidimensionais.

tipo_da_variável nome_da_variável [tam1][tam2] ... [tamN];

onde a iniciação é:

tipo_da_variável nome_da_variável [tam1][tam2] ... [tamN] = {lista_de_valores};
float vect [6] = { 1.3, 4.5, 2.7, 4.1, 0.0, 100.1 };
int matrx [3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
char str [10] = { 'J', 'o', 'a', 'o', '\0' };
char str [10] = "Joao";
char str_vect [3][10] = { "Joao", "Maria", "Jose" };

Podemos, em alguns casos, inicializar matrizes das quais não sabemos o tamanho a priori. O

compilador C vai, neste caso verificar o tamanho do que você declarou e considerar como

sendo o tamanho da matriz. Isto ocorre na hora da compilação e não poderá mais ser mudado

durante o programa

[editar] FUNÇÕES

Como dissemos no início, as funções não são mais do que pedaços de código que estava na

função main() e que resolvemos tirar para fora do main e demos um nome. Isto tem duas

vantagens: Uma primeira porque temos blocos mais pequenos de código e permite uma melhor leitura e

detecção de erros. Por outro lado permite utilizar várias vezes ao longo do bloco do main, apenas chamando.

Só temos que cumprir com alguns requisitos: necessitamos de dizer que tipologia de retorno

vai ter e que parâmetros vão receber. Os parâmetros são as variáveis que colocamos dentro

das (). Isto facilita porque podemos necessitar do resultado de uma função para executar uma

outra.

Um aparte: Mas existe aqui um problema para mim. Que é: e quando tivermos uma função que irá retornar

vários valores de várias tipologias? Será que temos que criar funções separadas para cada

tipologia. Bem até pode fazer sentido!

A idéia de uma função está, naturalmente, permitir você encapsular um idéia ou operação,

dando um nome a ela, então chamar que operação de várias partes do resto de seu programa

simplesmente usando o seu nome.

Estando corretamente projetado e estruturado os programas, deverá ser possível a mudança do

caminho que uma função faz seu trabalho (como espera-se que o trabalho em si próprio não

muda) sem efeito no resto do programa.


Exemplo:imaginemos que tínhamos um código do tipo


#include <stdio.h>
#include <stdlib.h>
int main ()
{
printf ("Ola!\n ");
printf ("Eu estou vivo!\n");
system (“pause”);
return(0);
}


aqui a nossa função main () vai imprimir apenas duas frases! mas agora imaginemos que o

nosso código começava a ter muitas e muitas linhas e necessitávamos de colocar várias vezes

o printf ("Ola!\n "); então o que é que fazíamos? criávamos uma função. tirávamos para fora

do main e escrevíamos da seguinte maneira:


#include <stdio.h>
#include <stdlib.h>
int mensagem ()  			/* Função simples: só imprime Olá! */
{
printf ("Ola!\n ");
return(0);
}
int main ()
{
mensagem();
printf ("Eu estou vivo!\n");
system (“pause”);
return(0);
}


Ou seja criámos uma função sem argumentos. repare que são as chavetas “()” que dizem que

aquilo é uma função


vamos agora pegar num exemplo melhorzinho 2º Exemplo criação de função com 1 argumento


#include <stdio.h>
#include <stdlib.h>
int square (int x)			/* Calcula o quadrado de x */
{
printf ("O quadrado e %d\n\n ",(x*x));
return(0);
}
int main ()
{
int num;
printf ("Entre com um numero: ");
scanf ("%d",&num);
square(num);
system (“pause”);
return(0);
}

repare no seguinte: quando chamamos a função square() no main estamos a colocar a variável

do main! esse valor do num é copiado e vai entrar como input da função square. é realizado

as linhas de código da função square e quando estas terminarem voltamos para o main!


3º Exemplo criação de função com 3 argumento

#include <stdio.h>
#include <stdlib.h>
int mult (float a, float b,float c)	/* Multiplica 3 numeros */
{
printf ("%f",a*b*c);
return(0);
}
int main ()
{
float x,y;
x=23.5;
y=12.9;
mult (x,y,3.87);
system (“pause”);
return(0);
}

Neste exemplo temos inputs que podem ser constantes, por isso não é necessário serem

variáveis! mas convém dizer o seguinte:

  1. . Em primeiro lugar temos de satisfazer aos requisitos da função quanto ao tipo e à

quantidade de argumentos quando a chamamos. Apesar de existirem algumas conversões de tipo,

que o C faz automaticamente, é importante ficar atento.

  1. . Em segundo lugar, não é importante o nome da variável que se passa como argumento,

ou seja, a variável num, ao ser passada como argumento para square() é copiada para a

variável x. Dentro de square() trabalha-se apenas com x. Se mudarmos o valor de x dentro de

square() o valor de num na função main() permanece inalterado

  1. . Repare que, neste caso, os argumentos são separados por vírgula e que deve-se

explicitar o tipo de cada um dos argumentos, um a um. Note, também, que os argumentos

passados para a função não necessitam ser todos variáveis porque mesmo sendo constantes

serão copiados para a variável de entrada da função.

  1. . há ainda um outro requisito, é que a função main é aquela que é primeiro executada,

e é sequencial. Agora não sei bem porquê, mas as funções extras têm de ser enumeradas antes do main(). tal

como os headers. Penso que é porque o compilador tem de saber que tipo de retorno vai ter.

Daí surgiram os protótipos.

[editar] FUNCTION PROTOTYPE

Bem, isto não é mais do que aumentar um pouco a nossa flexibilidade. Nós nas funções temos

de declará-las antes da função main. Aqui as funções protótipo é pegar nas funções e colocá-las depois da função main, com a

grande particularidade de termos de copiar a primeira linha da declaração da função e

colocá-la antes da função main. Ou seja a Linguagem c necessita de saber que tem uma função

tal que vai retornar um inteiro, ou float… e vai receber de parâmetros x,y, e z que são

todos inteiros.

Isto tem ainda um outro benefício que não me apercebi de imediato que é a questão do C se

utilizarmos protótipos, irá confirmar os tipos e a lista de parâmetros.

um aparte. isto poderia ser muito bem contornado se em vez de se declarar nome_da_função() fazer tipo_da_função nome_da_função() dentro da função main()

ou seja temos a diferença entre declaração e definição de funções


exemplo de um prototype.

#include <stdio.h>
float Square (float a);
int main ()
{
float num;
printf ("Entre com um numero: ");
scanf ("%f",&num);
num=Square(num);
printf ("\n\nO seu quadrado vale: %f\n",num);
return 0;
} 
float Square (float a)
{
return (a*a);
}

A diferença entre parâmetros e argumentos: parametros são o nome das variaveis inputs uma função argumentos são os valores dos argumentos quando uma função é chamada.

[editar] VOID

Void significa vazia em ingles acho que posso agora inserir um ponto que é a tipologia void. como dissemos uma função

retorna um valor. e pode receber parâmetros. como uma boa execução do programa costuma ser zero. o void é utilizado da seguinte forma:

void função(void) { … }

o que aqui estamos a dizer é que temos uma função que não vai receber parâmetros nenhuns. e

não vai retornar qualquer valor. Ou melhor o void é uma explicitação do programador que

aquela função não vai receber nem dar nenhum valor. não quero saber dos valores .. o valor

da função é ignorado, mas a função realmente retorna um valor. por isso para que o resultado

seja interpretado como um erro e bom declarar void. Mais uma nota sobre o void. Não se pode utilziar void na função principal a main(), apesar de já ter visto isso em alguma bibliografia.

A função main() é especial, tem de retornar um int no mínimo.

[editar] Variáveis locais – globais e passagem de parâmetros por valor e referência

Quando declaramos as variáveis, nós podemos fazê-lo dentro de uma função ou fora de todas as

funções inclusive a main(). As primeiras são as designadas como locais: só têm validade dentro do bloco no qual são

declaradas. as ultimas são as globais, elas estão vigentes em qualquer uma das funções.

  • A palavra reservada do C auto serve para dizer que uma variável é local. Mas não

precisaremos usá-la pois as variáveis declaradas dentro de um bloco já são consideradas

locais

  • Quando uma função tem uma variável local com o mesmo nome de uma variável global a

função dará preferência à variável local.

quando chamamos por uma função e damos um parâmetro, no exemplo anterior, passámos a

variável float num. mas ao parâmetros formais é o float a. E estes existem independentemente das variáveis que

foram passadas. eles tomam apenas uma cópia do valor passado. ou seja não são alterados os

valores dos parâmetros fora da função. Este tipo de chamada de função é denominado chamada

por valor.


Isto é importante porquê? Acho aliás que é dos mais importantes. Crucial! percebendo isto

apanhamos os ponteiros com as pernas ás costas!

#include <stdio.h>
#include <stdlib.h>
float sqr (float num);			/*protótipo da função sqr()*/
int main ()
{
float num,sq;				/*declaro 2 varíaveis: num e sq*/
printf ("Entre com um numero: ");	
scanf ("%f",&num);				/*associo o valor inserido á variavel num*/
sq=sqr(num);					/*chamo a função sqr e passo o parametro 

num*/

printf ("\n\nO numero original e: %f\n",num);
printf ("O seu quadrado vale: %f\n",sq);
float a, b;
printf ("Entre com um numero: ");	
scanf ("%f",&a);
a=a*a;
b=a;
printf ("\n\nO numero original e: %f\n",a);
printf ("O seu quadrado vale: %f\n",b);
system (“pause”);
return 0;
}
float sqr (float num)			/*descrição da função sqr*/
{
num=num*num;
return num;				/*retorna o num*/
}

Este exemplo explica um detalhe que é a meu ver crucial, na compreensão disto tudo! quando a função main() é executada, ela chega a meio e vê uma chamada para a função sqr() e

onde é passado o parâmetro “num”. Ora ela já estava á espera pois viu o protótipo. ela então

vai executar a função que está depois da função do main() e o que acontece é que o num, vai ficar com o dobro do valor. Esse valor do main vai entrar

novamente no main.e é associado á variável “sq”. depois temos a impressão da variável “num”

e “sq”. Ora o que acontece é que o valor do “num” fica igual ao valor antes de entrar na função. eu faço a mesma coisa agora com a variável “a” e “b”, e vemos que agora a função a é

alterada. resumindo, o valor variável quando entra numa outra função não é alterado.!!


Quando o valor do parâmetro é alterado é denominado por chamada por referência. O C não faz

chamadas por referência. mas podemos simular isto com outra arma do C que são os ponteiros.

retornamos a este ponto após desta secção seguinte.

[editar] POINTERS

Os pointers supostamente são um bicho de 7 cabeças. É supostamente a parte mais difícil. E se uma pessoa compreender isto, torna-se craque em c.

Uma variável normal tem uma localização na memória que pode conter um valor. Por exemplo

quando declaramos int i; quatro bytes de memória são reservados e a esses 4 bytes chamamos

de name i.

Um pointer (ou ponteiro, em português) é uma variável que quando declarada, também é

reservado um espaço na memória, mas em vez de guardar um valor guarda o endereço de memória

de uma outra variável, guarda a direcção de uma outra variável.

  • Ponteiros guardam endereços de memória.
  • Um ponteiro também tem tipo

Qual é a vantagem dos pointers. Uma das vantagens é a situação, imaginemos que queremos colocar uma quantidade enorme de

dados para uma função. É muito mais fácil indicar a localização dos dados do que copiar cada

elemento dos dados.


Para declarar usamos:

tipo_do_ponteiro *nome_da_variável;

É o asterisco (*) que faz o compilador saber que aquela variável não vai guardar um valor

mas sim um endereço para aquele tipo especificado

int *pt;

se tivermos vários pointers, temos de colocar o asterisco em cada um;

int *pt1, *pt2;	

Ainda não foi inicializado. Isto significa que eles apontam para um lugar indefinido Para saber o endereço de uma variável basta usar o operador &.

int count=10;
int *pt;
pt=&count;
*pt=12;

Criamos um inteiro count com o valor 10 e um apontador para um inteiro pt. A expressão

&count nos dá o endereço de count, o qual armazenamos em pt. A última linha modifica o valor a 12 da variável onde o apontador está apontar, que é o

count


#include <stdio.h>
#include <stdlib>
int main()
{
int i=10;
int *p;   			/* a pointer to an integer */
p = &i;				/*associamos o endereço de i ao ponteiro p*/
*p=5;				/*atribuímos o valor 5 á variável que p aponta*/
printf("%d\t %d\t %p", i, *p, p);	/*imprimimos a variável i e para onde aponta p e ainda o endereço para onde aponta*/
system (“pause”);
return 0;
}

Aqui está o que acontece


Os erros mais comuns são:

  • A não iniciação dos pointers. dito de outra forma é dizer que realmente temos um pointer e

é reservada memória, mas que depois não dizemos o que queremos guardar nesse espaço. p=&i;

isso faz com que o p aponte para qualquer sítio na memória. E depois quando fazemos *p=12; estamos a pedir para guardar esse nº 12 nesse local da

memória que não sabemos onde é.


Se eu tiver dois ponteiros e mesmo depois de iniciados fizer p1=p2. Repare que estamos

fazendo com que p1 aponte para o mesmo lugar que p2. Se quisermos que a variável apontada

por p1 tenha o mesmo conteúdo da variável apontada por p2 devemos fazer *p1=*p2

[editar] Operações com ponteiros

p++;

Quando incrementamos um ponteiro ele passa a apontar para o próximo valor do mesmo tipo para

o qual o ponteiro aponta. Isto é, se temos um ponteiro para um inteiro e o incrementamos ele

passa a apontar para o próximo inteiro.

(*p)++;

Para incrementar o conteúdo da variável apontada pelo ponteiro p,

*(p+15);

o conteúdo do ponteiro 15 posições adiante

[editar] PONTEIROS COMO PARAMENTROS DE FUNÇÕES

Os ponteiros dão jeito! Porquê?

Vamos por um exemplo: eu tenho 2 variáveis e quero trocar o valor. eu poderia fazer como fiz no 1º programa em baixo ou se quisesse fazer uma função da

operação de troca faria com está no 2º programa.

1º PROGRAMA

#include <stdio.h>
#include <stdlib.h>
int  main()
{
  int a,b;
  a=5;
  b=10;
  printf("%d %d\n", a, b);
    int t;                  
    t=a;                       /*ao valor de a atribuímos á variavel t*/
    a=b;                       /*ao valor de b atribuímos á variavel a*/
    b=t;                       /*ao valor de t atribuímos á variavel a*/
  printf("%d %d\n", a, b);
  system (“pause”);
  return 0;
}

Agora o 2º PROGRAMA:

#include <stdio.h>
#include <stdlib.h>
void swap(int i, int j)
 {
   int t;
   t=i;
   i=j;
   j=t;
 }
int  main()
 {
   int a,b;
   a=5;
   b=10;
   printf("%d %d\n", a, b);
   swap(a,b);
   printf("%d %d\n", a, b);
   system (“pause”);
   return 0;
}


atão, o que é que está a acontecer??? Lembram-se da história da chamada por valor e chamada

por referência. Essa é a resposta.

No primeiro caso, a operação de troca faz-se na mesma função. No segundo, a operação faz-se

numa função extra, e o valor dos parâmetros não é alterado.

Afinal como é que nós podíamos fazer isto. Uma maneira era fazer a troca na função extra e fazer retornar o valor a duas novas

variáveis que depois voltaríamos a associar a “a” e “b”. Mas só estas trocas todas fazem dissuadir de uma pessoa utilizar funções extras, ie, fora do

main.

Mas existe uma alternativa! é Usando os ponteiros. Ou seja vamos passar ponteiros que

é mais fácil


#include <stdio.h>
#include <stdlib.h>
void Swap (int *i,int *j)              /*como vão ser passados os endereços, necessitamos 

de manipular os valores desses endereços daí o uso de "*" - de ponteiros */

 {
  int t;
  t=*i;
  *i=*j;
  *j=t;
 }
int main ()
 {
   int a,b;
   a=5;
   b=10;
   printf ("\n\nEles valem %d  %d\n",a,b);
   Swap (&a,&b);                                     /*estamos a passar o endereço em vez 

dos valores*/

   printf ("\n\nEles agora valem %d  %d\n",a,b);
   system (“pause”);
   return 0;
}

Os ponteiros são a "referência" que precisamos para poder alterar a variável fora da função.

O único inconveniente é que, quando usarmos a função, teremos de lembrar de colocar um & na

frente das variáveis que estivermos passando para a função.

Que está acontecendo é que passamos para a função Swap o endereço das variáveis “a” e “b”.

Estes endereços são copiados nos ponteiros “i” e “j”. Através do operador * estamos

acessando o conteúdo apontado pelos ponteiros e modificando-o. Mas, quem é este conteúdo?

Nada mais que os valores armazenados em num1 e num2, que, portanto, estão sendo modificados!


Espere um momento... será que nós já não vimos esta história de chamar uma função com as

variáveis precedidas de &? Já! É assim que nós chamamos a função scanf(). Mas porquê? Vamos

pensar um pouco. A função scanf() usa chamada por referência porque ela precisa alterar as

variáveis que passamos para ela! Não é para isto mesmo que ela é feita? Ela lê variáveis

para nós e portanto precisa alterar seus valores. Por isto passamos para a função o endereço

da variável a ser modificada!

[editar] Os Argumentos argc e argv

A função main() como dissemos antes é uma função especial. Mas a função main() também pode ter parâmetros formais. no entanto o programador não pode

escolher quais serão

int main (int argc,char *argv[]);
  • O argc (argument count) é um inteiro e possui o número de argumentos com os quais a função

main() foi chamada na linha de comando. Ele é, no mínimo 1, pois o nome do programa é

contado como sendo o primeiro argumento.

  • O argv (argument values) é um ponteiro para uma matriz de strings. Cada string desta matriz

é um dos parâmetros da linha de comando. O argv[0] sempre aponta para o nome do programa

(que, como já foi dito, é considerado o primeiro argumento). É para saber quantos elementos

temos em argv que temos argc.

Exemplo: Escreva um programa que faça uso dos parâmetros argv e argc. O programa deverá

receber da linha de comando o dia, mês e ano correntes, e imprimir a data em formato

apropriado. Veja o exemplo, supondo que o executável se chame data: data 19 04 99 O programa deverá imprimir: 19 de abril de 1999

#include <stdio.h> 
#include <stdlib.h> 
int main(int argc, char *argv[]) 
{ 
int mes; 
char *nome_mes [] =  {"Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho","Julho", 

"Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"};

if(argc == 4) /* Testa se o numero de parâmetros fornecidos está correcto, o primeiro 

parâmetro é o nome do programa, o segundo o dia, o terceiro o mes e o quarto os dois últimos

algarismos do ano */

  { 
   mes = atoi(argv[2]);  /* argv contem strings.A string referente ao mes deve ser 

transformada em um numero inteiro. A funcao atoi esta sendo usada para isto: recebe a string

e transforma no inteiro equivalente */

  if (mes<1 || mes>12)  		/* Testa se o mes e' valido */ 
    printf("Erro!\nUso: data dia mes ano, todos inteiros"); 
  else 
    printf("\n%s de %s de 19%s", argv[1], nome_mes[mes-1], argv[3]); 
 } 
  else printf("Erro!\nUso: data dia mes ano, todos inteiros"); 
}

[editar] Recursividade

Uma função pode chamar a si própria. Uma função assim é chamada função recursiva


#include <stdio.h>
#include <stdlib.h>
int fat(int n)
 {
   if (n) 
      return n*fat(n-1);
   else return 1;
 }
int main()
 {
   int n;
   printf("\n\nDigite um valor para n: ");
   scanf("%d", &n);
   printf("\nO fatorial de %d e' %d", n, fat(n));
   system ("pause");
   return 0;
 }

[editar] TYPEDEF

Também o typedef deve ser utilizado como header. Ele é muito parecido com o #define a

diferença é que trabalha com a tipologia das variáveis. (type+definition) que é (tipologia

de variáveis +definição)

typedef int boolean;

Isto vai dizer ao compilador que quando encontrar no código a palavra boolean a vai

substituir por int. Assim podemos escrever o código utilizando um nome que nos agrade mais.

mas repare que é apenas para tipologias de variáveis!!

[editar] DIRETIVAS DE COMPILAÇÃO

As Diretivas de Compilação O pré-processador C é um programa que examina o programa fonte escrito em C e executa certas

modificações nele, baseado nas Diretivas de Compilação As directivas de compilação são

comandos que não são compilados, sendo dirigidos ao pré-processador, que é executado pelo

compilador antes da execução do processo de compilação propriamente dito. Portanto, o pré-processador modifica o programa fonte, entregando para o compilador um

programa modificado. Todas as directivas de compilação são iniciadas pelo carácter #. As

directivas podem ser colocadas em qualquer parte do programa.


1. #include 2. #define 3. #undef 4. #ifdef e #endif 5. #ifndef 6. #if 7. #else 8. #elif


[editar] 1.A Diretiva #include

Ela diz ao compilador para incluir, na hora da compilação, um arquivo especificado

#include "nome_do_arquivo"

ou

#include <nome_do_arquivo>

A diferença entre se usar " " e < > é somente a ordem de procura nos directórios pelo

arquivo especificado. Se você quiser informar o nome do arquivo com o caminho completo, ou

se o arquivo estiver no directório de trabalho, use " ". Se o arquivo estiver nos caminhos

de procura pré-especificados do compilador, isto é, se ele for um arquivo do próprio sistema

(como é o caso de arquivos como stdio.h, string.h, etc...) use < >.

[editar] 2.A Diretiva #Define

#define nome_da_macro sequência_de_caracteres

Toda vez que ele encontrar o nome_da_macro no programa a ser compilado, ele deve

substituí-lo pela sequência_de_caracteres fornecida.

#include <stdio.h>
#define PI 3.1416
#define VERSAO "2.02"
int main ()
{
  printf ("Programa versao %s",VERSAO);
  printf ("O numero pi vale: %f",PI);
  return 0;
}

Outro exemplo:

#define max(A,B) ((A > B) ? (A):(B))
#define min(A,B) ((A < B) ? (A):(B))
x = max(i,j);
y = min(t,r);

Assim, a linha de código: x = max(i,j); Será substituída pela linha: x = ((i)>(j) ? (i):(j));

Quando você utiliza a diretiva #define nunca deve haver espaços em branco no identificador.

Por exemplo, a macro: #define PRINT (i) printf(" %d \n", i) não funcionará correctamente

porque existe um espaço em branco entre PRINT e (i).

[editar] 3. A Diretivas #undef

A diretiva #undef tem a seguinte forma geral:

#undef nome_da_macro

Ela faz com que a macro que a segue seja apagada da tabela interna que guarda as macros.O

compilador passa a partir deste ponto a não conhecer mais esta macro.

[editar] 4. As Diretivas #ifdef e #endif

Directivas de compilação condiciona,

#ifdef nome_da_macro 

sequência_de_declarações

#endif

A sequência de declarações será compilada apenas se o nome da macro estiver definido. A

directiva de compilação #endif é útil para definir o fim de uma sequência de declarações

para todas as directivas de compilação condicional.

[editar] 5. A Diretiva #ifndef

A diretiva #ifndef funciona ao contrário da diretiva #ifdef. Sua forma geral é:

#ifndef nome_da_macro

sequência_de_declarações

#endif

A sequência de declarações será compilada se o nome da macro não tiver sido definido

[editar] 6. A Diretiva #if

A diretiva #if tem a seguinte forma geral:

#if expressão_constante 

sequência_de_declarações

#endif 

A sequência de declarações será compilada se a expressão-constante for verdadeira. É muito

importande ressaltar que a expressão fornecida deve ser constante, ou seja, não deve ter

nenhuma variável.

[editar] 7. A Diretiva #else

A diretiva #else tem a seguinte forma geral:

#if expressão_constante 
sequência_de_declarações 
#else 
sequência_de_declarações 
#endif 

Ela funciona como seu correspondente, o comando else.

#define SISTEMA DOS
...
/*linhas de codigo..*/
...
#if SISTEMA == DOS
#define CABECALHO "dos_io.h"
#else
#define CABECALHO "unix_io.h"
#endif
#include CABECALHO

[editar] 8. A Diretiva #elif

A diretiva #elif serve para implementar a estrutura if-else-if. Sua forma geral é:

#if expressão_constante_1 
sequência_de_declarações_1 
#elif expressão_constante_2 
sequência_de_declarações_2 
#elif expressão_constante_3 
sequência_de_declarações_3 
. 
. 
. 
#elif expressão_constante_n 
sequência_de_declarações_n 
#endif 

O funcionamento desta estrutura é idêntico ao funcionamento apresentado anteriormente.

[editar] LIBRARIES – BIBLIOTECAS

As bibliotecas são tipo funções, ou conjunto de funções, que alguém já fez. Mas desta vez,

essas funções estão fora do nosso programa. Elas para além da vantagem, tal como nas funções, fora do main, permitem uma organização do

código, e que possam ser chamadas várias vezes ao longo do código, têm uma vantagem extra

que é que podem ser chamadas por vários códigos que façamos. Não necessitamos de fazer copy

e paste, de um código para outro código que façamos mais tarde, basta-nos chamar.

Por exemplo, vamos tentar criar a nossa própria biblioteca. Vamos criar 2 funções para alem

do main

#include <stdio.h>
#include <stdlib.h>
#define MAX 10					/*na hora da compilação vai substituir max 

por 10*/

int a[MAX];					/*definição de varivel global*/
int rand_seed=10;				/*definição de variavel global e afectação 

de valor*/

int rand()					/* 1º função*/
  {	
    rand_seed = rand_seed * 1103515245 +12345;
    return (unsigned int)(rand_seed / 65536) % 32768;
  }
void bubble_sort(int m) 			/* 2º função – repare que recebe um 

parâmetro/

  {
    int x,y,t;
    for (x=0; x < m-1; x++)
    for (y=0; y < m-x-1; y++)
    if (a[y] > a[y+1])
  {
t=a[y];
a[y]=a[y+1];
a[y+1]=t;
int main()
   {
     int i,t,x,y;
     for (i=0; i < MAX; i++)			/* fill array */
         {
           a[i]=rand();
           printf("%d\n",a[i]);
         }
     bubble_sort(MAX);
     printf("--------------------\n");
     for (i=0; i < MAX; i++)
            printf("%d\n",a[i]); 			/* print sorted array */
   }


Nós podemos generalizar o buble sort mais

#include <stdio.h>
#include <stdlib.h>
#define MAX 10
int a[MAX];
int rand_seed=10;
int rand()					
  {	
    rand_seed = rand_seed * 1103515245 +12345;
    return (unsigned int)(rand_seed / 65536) % 32768;
  }
void bubble_sort(int m, int a[]) 		/* recebe agora 2 parâmetros*/
  {
     int x,y,t;
     for (x=0; x < m-1; x++)
        for (y=0; y < m-x-1; y++)
           if (a[y] > a[y+1])
                 {
                    t=a[y];
                    a[y]=a[y+1];
                    a[y+1]=t;
                 }
  }
int main()
  {
     int i,t,x,y;
     for (i=0; i < MAX; i++) 
         {
            a[i]=rand();
            printf("%d\n",a[i]);
         }
     bubble_sort(MAX, a);		/* necessito de alterar, para receber 2 parametros 
  • /
     printf("--------------------\n"); 
     for (i=0; i < MAX; i++)
     printf("%d\n",a[i]);
  }

Todas as bibliotecas têm 2 partes:

  1. . header file (que temo “h” sufixo – contem informação sobre a biblioteca. de uma

forma gera, contém constantes, types e prototipods de funções disponibilizadas pela

biblioteca.

  1. . e o file do código efectivo


Como eu quero criar uma biblioteca para a função do bublesort, vou fazer o seguinte


  1. . Vamos colocar o seguinte código e gravar com o nome de util.h

as duas linhas que colocámos são protótipos. a palavra “extern” representa uma função que mais tarde será linked

extern int rand();			/*código das funções prototipo para o ficheiro 

header util.h */

extern void bubble_sort(int, int []);


  1. . agora vamos criar um outro ficheiro com o nome util.c

/* código efectivo para a criação do ficheiro util.c */ /* !!!repare que eu aqui já chamo o header criado, mas repare que agora tenho “” em vez de

<>*/

#include "util.h"			
int rand_seed=10;
int rand()				/*esta é a função rand()*/
{
rand_seed = rand_seed * 1103515245 +12345;
return (unsigned int)(rand_seed / 65536) % 32768;
}
void bubble_sort(int m,int a[])	/*esta é a função buble_sort()*/
{
int x,y,t;
for (x=0; x < m-1; x++)
for (y=0; y < m-x-1; y++)
if (a[y] > a[y+1])
{
 t=a[y];
 a[y]=a[y+1];
 a[y+1]=t;
 }
}



  1. . Agora coloquemos o seguinte programa e gravemos com o nome main.c
#include <stdio.h>
#include "util.h"				/* REPARE que eu agora chamo o header file. 
  • /
#define MAX 10
int a[MAX];
int main()
{
 int i,t,x,y;
 for (i=0; i < MAX; i++)			/* fill array */
  {
    a[i]=rand();
    printf("%d\n",a[i]);
  }
 bubble_sort(MAX,a);
 printf("--------------------\n");	/* print sorted array */
 for (i=0; i < MAX; i++)
   printf("%d\n",a[i]);

}


Como vimos agora é só chamar pela biblioteca que criamos, sempre e quando quisermos.

[editar] Entradas e Saídas Padronizadas

O sistema de entrada e saída da linguagem C está estruturado na forma de uma biblioteca de

funções

Quando apresentarmos uma função, vamos, em primeiro lugar, apresentar o seu protótipo.


Outro aspecto importante, quando se discute a entrada e saída na linguagem C é o conceito

de fluxo

Seja qual for o dispositivo de entrada e saída (discos, terminais, teclados, ...) que se

estiver trabalhando, o C vai tê-lo como um fluxo. Todos os fluxos são similares em seu

funcionamento e independentes do dispositivo ao qual estão associados. Assim, as mesmas

funções que descrevem o acesso aos discos podem ser utilizadas para se acessar um terminal

de vídeo.

Assim um ficheiro tal como outros componentes, apenas necessita de ser aberto e depois

fechado por forma a que o fluxo- troca de informação se possa realizar.

[editar] TEXT FILES

Existem 6 comandos de I/O na biblioteca <stdio.h>

printf      - prints formatted output to stdout 
puts        - prints a string to stdout 
putc        - prints a character to stdout 

scanf        - reads formatted input from stdin 
gets         - reads a string from stdin 
getc,        - reads a character from stdin 

outros: getchar putchar


getch, getche – lê um caractere do stdin podemos ver estas duas funções na biblioteca

conio.h. A 2ª função imprime no ecrã depois de ler. sprintf



vamos fazer agora apenas umas notas a estas funções:

[editar] a função gets

(get+string) tem como prototipo

char *gets (char *s);

a função lê a string do teclado, ou melhor, ai armazenar uma string s no ponteiro s:

gets(nome_da_string) mas existe um problema que pode ser perigoso.

#include <stdio.h>
int main()
{
  char buffer[10];
  printf("Entre com o seu nome");
  gets(buffer);
  printf("O nome é: %s", buffer);
  return 0;
}

se o usuário digitar mais do que 10 caracteres incluindo o "\0", os caracteres adicionais

serão colocados na área de memória subsequente à ocupada por ela, escrevendo uma região de

memória que não está reservada à string. Este efeito é conhecido como "estouro de buffer" e

pode causar problemas imprevisíveis. Uma forma de se evitar este problema é usar a função

fgets (vamos utilizá-la mais tarde)

[editar] Função sprintf e sscanf

sprintf e sscanf são semelhantes a printf e scanf. Porém, ao invés de escreverem na saída padrão ou lerem da entrada padrão, escrevem ou leem

em uma string.

#include <stdio.h>
#include <stdlib.h>
int main()
{
  int i;
  char string1[20];
  printf( " Entre um valor inteiro: ");
  scanf("%d", &i);
  sprintf(string1,"Valor de i = %d", i);            /*coloca na string1. a frase...*/
  puts(string1);
  system ("pause");
  return 0;
}

a variável i é "impressa" em string1. Além da representação de i como uma string, string1

também conterá "Valor de i=" .

#include <stdio.h>
#include <stdlib.h>
int main()
{
  int i, j, k;
  char string1[]= "10 20 30";
  sscanf(string1, "%d %d %d", &i, &j, &k);                 /*lê para o string1*/
  printf("Valores lidos: %d, %d, %d", i, j, k);            /*imprime no ecrã*/
  system ("pause");
  return 0;
}

foi utilizada a função sscanf para converter a informação armazenada em string1 em seu valor

numérico

[editar] Função putc

A função putc é a primeira função de escrita de arquivo que veremos. Seu protótipo é:

int putc (int ch,FILE *fp);

Escreve um caractere no arquivo.O programa a seguir lê uma string do teclado e escreve-a,

caractere por caractere em um arquivo em disco (o arquivo arquivo.txt, que será aberto no

diretório corrente).

#include <stdio.h>
#include <stdlib.h>
int main()
{
  FILE *fp;
  char string[100];
  int i;
  fp = fopen("arquivo.txt","w");   		/* Arquivo ASCII, para escrita */
  if(!fp)
   {
     printf( "Erro na abertura do arquivo");
     exit(0);
   }
  printf("Entre com a string a ser gravada no arquivo:");
  gets(string);
  for(i=0; string[i]; i++) putc(string[i], fp); /* Grava a string, caractere a caractere */
  fclose(fp);
  system ("pause");
  return 0;
}

Depois de executar este programa, verifique o conteúdo do arquivo arquivo.txt (você pode

usar qualquer editor de textos). Você verá que a string que você digitou está armazenada

nele.

[editar] getc

Retorna um caractere lido do arquivo. Protótipo:

               int getc (FILE *fp);


[editar] strcpy

strcpy (string_destino,string_origem);

[editar] strcat

strcat (string_destino,string_origem); A string de origem permanecerá inalterada e será anexada ao fim da string de destino.

[editar] strlen

strlen (string); retorna o comprimento da string fornecida. O terminador nulo não é contado.

[editar] strcmp

strcmp (string1,string2); compara a string 1 com a string 2. Se as duas forem idênticas a função retorna zero. Se elas

forem diferentes a função retorna não-zero.


[editar] Abrindo e Fechando um Arquivo

arquivos pré-definidos:

  • stdin: dispositivo de entrada padrão (geralmente o teclado)
  • stdout: dispositivo de saída padrão (geralmente o vídeo)
  • stderr: dispositivo de saída de erro padrão (geralmente o vídeo)
  • stdaux: dispositivo de saída auxiliar (em muitos sistemas, associado à porta serial)
  • stdprn : dispositivo de impressão padrão (em muitos sistemas, associado à porta

paralela) O sistema de entrada e saída do ANSI C é composto por uma série de funções, cujos protótipos

estão reunidos em stdio.h .

Todas estas funções trabalham com o conceito de "ponteiro de arquivo". Este não é um tipo

propriamente dito, mas uma definição usando o comando typedef. Esta definição também está no

arquivo stdio.h. Podemos declarar um ponteiro de arquivo da seguinte maneira: FILE *p; p será então um ponteiro para um arquivo. É usando este tipo de ponteiro que vamos poder

manipular arquivos no C. pelo o que eu estou a perceber o nome “FILE” é tipo um int ou um float ou ainda um typedef


  1. . fopen - opens a text file
  2. . fclose - closes a text file
  3. . feof - detects end-of-file marker in a file
  4. . fgets - reads a string from a file
  5. . fputs - prints a string to a file
  6. . ferror e perror
  7. . fread
  8. . fwrite
  9. . fseek
  10. . rewind
  11. . remove
  12. . fprintf - prints formatted output to a file
  13. . fscanf - reads formatted input from a file
  14. . fputc - prints a character to a file
  15. . fgetc - reads a character from a file

[editar] 1. fopen

Esta é a função de abertura de arquivos. Seu protótipo é: FILE *fopen (char *nome_do_arquivo,char *modo); O nome_do_arquivo determina qual arquivo deverá ser aberto. Este nome deve ser válido no

sistema operacional que estiver sendo utilizado. O modo de abertura diz à função fopen() que

tipo de uso você vai fazer do arquivo. A tabela abaixo mostra os valores de modo válidos:

Modo	Significado
"r"- read	Abre um arquivo texto para leitura. O arquivo deve existir antes de ser 

aberto.

"w"-write	Abrir um arquivo texto para gravação. Se o arquivo não existir, ele será 

criado. Se já existir, o conteúdo anterior será destruído.

"a"-append	Abrir um arquivo texto para gravação. Os dados serão adicionados no fim do 

arquivo ("append"), se ele já existir, ou um novo arquivo será criado, no caso de arquivo

não existente anteriormente.

"r+"	Abre um arquivo texto para leitura e gravação. O arquivo deve existir e pode ser 

modificado.

"w+"	Cria um arquivo texto para leitura e gravação. Se o arquivo existir, o conteúdo 

anterior será destruído. Se não existir, será criado.

"a+"	Abre um arquivo texto para gravação e leitura. Os dados serão adicionados no fim do 

arquivo se ele já existir, ou um novo arquivo será criado, no caso de arquivo não existente

anteriormente.

"rb"	Abre um arquivo binário para leitura. Igual ao modo "r" anterior, só que o arquivo é 

binário.

"wb"	Cria um arquivo binário para escrita, como no modo "w" anterior, só que o arquivo é 

binário.

"ab"	Acrescenta dados binários no fim do arquivo, como no modo "a" anterior, só que o 

arquivo é binário.

"r+b"	Abre um arquivo binário para leitura e escrita. O mesmo que "r+" acima, só que o 

arquivo é binário.

"w+b"	Cria um arquivo binário para leitura e escrita. O mesmo que "w+" acima, só que o 

arquivo é binário.

"a+b"	Acrescenta dados ou cria uma arquivo binário para leitura e escrita. O mesmo que 

"a+" acima, só que o arquivo é binário

Poderíamos então, para abrir um arquivo binário para escrita, escrever:

FILE *fp;				/* Declaração da estrutura*/
fp=fopen ("exemplo.bin","wb");  /* o arquivo se chama exemplo.bin e está localizado no 

diretório corrente */

if (!fp)
  printf ("Erro na abertura do arquivo.");


A condição !fp testa se o arquivo foi aberto com sucesso porque no caso de um erro a função

fopen() retorna um ponteiro nullo (NULL). Uma vez aberto um arquivo, vamos poder ler ou escrever nele utilizando as funções que serão

apresentadas nas próximas páginas.


Exemplo:

#include <stdio.h>
#define MAX 10
int main()
{
FILE *f;			/* está criado um ponteiro para o arquivo */
int x;
f=fopen("out","w");		/*a função abre o file out no modo w*/
if (!f)			
return 1;
for(x=1; x<=MAX; x++)
fprintf(f,"%d\n",x);	         /*a a função printf para file daí fprintf */
fclose(f);			
return 0;
}


Vai abrir um file chamado de out e escrever os números de 1 a 10. Depois fecha o file

.Repare que abrir um ficheiro no modo w é destrutivo, ou seja, se o ficheiro não existe ele

vai ser criado, mas se existe um outro ficheiro o novo ficheiro fica no seu lugar. este comando fopen retorna um pointer ao ficheiro que é guardado na variável f. se não é

possível abrir o ficheiro o f fica com o valor null.


Exemplo 2:

#include <stdio.h>
#include <stdlib.h>
int main()
{
 FILE *f;
 char s[1000];
 f=fopen("infile","r");		/*abre o file chamado “infile” em modo r*/
 if (!f)				/* no caso de abrir mal*/
 return 1;
 while (fgets(s,1000,f)!=NULL)	        /* enquando for diferente de zero,ie, até o enter*/
  printf("%s",s); 		        /*fgets – em vez do fscanf*/
 fclose(f);
 system (“pause”);  
 return 0;
}

para ler o file infile mas agora no modo r- de read. Repare que utilizamos o fgets em vez do fscanf porque este requere que o texto esteja

perfeitamente formatado. o fgets ainda tem a vantagem de acrescentar um 1n em cada linha que

lê. ele vai ler até encontar o eof-end of file marker.no exemplo ele vai ler 1000 caracteres

vamos ver os comandos melhor.

[editar] exit

Protótipo é:

void exit (int codigo_de_retorno);

Para utilizá-la deve-se colocar um include para o arquivo de cabeçalho stdlib.h. Esta função

aborta a execução do programa. Pode ser chamada de qualquer ponto no programa e faz com que

o programa termine e retorne, para o sistema operacional, o código_de_retorno. A convenção

mais usada é que um programa retorne zero no caso de um término normal e retorne um número

não nulo no caso de ter ocorrido um problema.


#include <stdio.h>
#include <stdlib.h> 		/* Para a função exit() */
main (void)
{
 FILE *fp;
 ...
 fp=fopen ("exemplo.bin","wb");
 if (!fp)
  {
   printf ("Erro na abertura do arquivo. Fim de programa.");
   exit (1);
  }
 ...
 return 0;
}

[editar] 2. fclose

Acabámos de usar um arquivo que abrimos, devemos fechá-lo. Para tanto usa-se a função

fclose(): int fclose (FILE *fp); O ponteiro fp passado à função fclose() determina o arquivo a ser fechado. A função retorna

zero no caso de sucesso. Fechar um arquivo faz com que qualquer caracter que tenha permanecido no "buffer" associado

ao fluxo de saída seja gravado. Mas, o que é este "buffer"? Quando você envia caracteres

para serem gravados em um arquivo, estes caracteres são armazenados temporariamente em uma

área de memória (o "buffer") em vez de serem escritos em disco imediatamente. Quando o

"buffer" estiver cheio, seu conteúdo é escrito no disco de uma vez. A razão para se fazer

isto tem a ver com a eficiência nas leituras e gravações de arquivos. Se, para cada caracter

que fossemos gravar, tivéssemos que posicionar a cabeça de gravação em um ponto específico

do disco, apenas para gravar aquele caracter, as gravações seriam muito lentas. Assim estas

gravações só serão efetuadas quando houver um volume razoável de informações a serem

gravadas ou quando o arquivo for fechado. A função exit () fecha todos os arquivos que um programa tiver aberto.

[editar] 3. feof

EOF ("End of file") indica o fim de um arquivo. Às vezes, é necessário verificar se um

arquivo chegou ao fim. Para isto podemos usar a função feof(). Ela retorna não-zero se o

arquivo chegou ao EOF, caso contrário retorna zero. Seu protótipo é: int feof (FILE *fp); Outra forma de se verificar se o final do arquivo foi atingido é comparar o caractere lido

por getc com EOF. O programa a seguir abre um arquivo já existente e o lê, caracter por

caracter, até que o final do arquivo seja atingido. Os caracteres lidos são apresentados na

tela:


#include <stdio.h>
#include <stdlib.h>
int main()
 {
  FILE *fp;
  char c;
  fp = fopen("arquivo.txt","r");   /* Arquivo ASCII, para leitura */
  if(!fp)
   {
    printf( "Erro na abertura do arquivo");
    exit(0);
   }
  while((c = getc(fp) ) != EOF)      /* Enquanto não chegar ao final do arquivo */
    printf("%c", c);                 /* imprime o caracter lido */
  fclose(fp);
  return 0;
 }


Verifique o exemplo.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
  FILE *p;
  char c, str[30], frase[80] = "Este e um arquivo chamado: ";
  int i;
  printf("\n\n Entre com um nome para o arquivo:\n");
  gets(str); 					/* Le um nome para o arquivo a ser aberto: 
  • /
  if (!(p = fopen(str,"w")))  	/* Caso ocorra algum erro na abertura do arquivo..*/
   {                           
     printf("Erro! Impossivel abrir o arquivo!\n");
     exit(1); 			/* o programa aborta automaticamente */
   }
  strcat(frase, str);
  for (i=0; frase[i]; i++)
    putc(frase[i],p);
  fclose(p); 				/* Se nao houve erro,imprime no arquivo e o fecha 

...*/

  p = fopen(str,"r");			/* Abre novamente para  leitura  */
  c = getc(p);				/* Le o primeiro caracter */
  while (!feof(p))        		/* Enquanto não se chegar no final do arquivo */
   {    
    printf("%c",c); 		/*   Imprime o caracter na tela */
    c = getc(p);    		/* Le um novo caracter no arquivo */
   }
  fclose(p);              		/* Fecha o arquivo */
}

[editar] 4. fgets

Para se ler uma string num arquivo podemos usar fgets() cujo protótipo é: char *fgets (char *str, int tamanho,FILE *fp); A função recebe 3 argumentos: a string a ser lida, o limite máximo de caracteres a serem

lidos e o ponteiro para FILE, que está associado ao arquivo de onde a string será lida. A

função lê a string até que um caracter de nova linha seja lido ou tamanho-1 caracteres

tenham sido lidos. Se o caracter de nova linha ('\n') for lido, ele fará parte da string, o

que não acontecia com gets. A função fgets é semelhante à função gets(), porém, além dela poder fazer a leitura a partir

de um arquivo de dados e incluir o caracter de nova linha na string, ela ainda especifica o

tamanho máximo da string de entrada. Como vimos, a função gets não tinha este controle, o

que poderia acarretar erros de "estouro de buffer". Portanto, levando em conta que o

ponteiro fp pode ser substituído por stdin, como vimos acima, uma alternativa ao uso de gets

é usar a seguinte construção:

               fgets (str, tamanho, stdin);

[editar] 5. fputs

Protótipo:

               char *fputs (char *str,FILE *fp);

Escreve uma string num arquivo.

[editar] 6. ferror e perror

Protótipo de ferror:

int ferror (FILE *fp);

A função retorna zero, se nenhum erro ocorreu e um número diferente de zero se algum erro

ocorreu durante o acesso ao arquivo. se torna muito útil quando queremos verificar se cada

acesso a um arquivo teve sucesso, de modo que consigamos garantir a integridade dos nossos

dados. Na maioria dos casos, se um arquivo pode ser aberto, ele pode ser lido ou gravado.

Porém, existem situações em que isto não ocorre. Por exemplo, pode acabar o espaço em disco

enquanto gravamos, ou o disco pode estar com problemas e não conseguimos ler, etc. Uma

função que pode ser usada em conjunto com ferror() é a função perror() (print error), cujo

argumento é uma string que normalmente indica em que parte do programa o problema ocorreu.


#include <stdio.h>
#include <stdlib.h>
int main()
{
  FILE *pf;
  char string[100];
  if((pf = fopen("arquivo.txt","w")) ==NULL) 
   {
     printf("\nNao consigo abrir o arquivo ! ");
     exit(1);
   }
  do 
   {
     printf("\nDigite uma nova string. Para terminar, digite <enter>: ");
     gets(string);
     fputs(string, pf);
     putc('\n', pf);
     if(ferror(pf))
      {
        perror("Erro na gravacao");
        fclose(pf);
        exit(1);
      }
   }while (strlen(string) > 0);
   fclose(pf);

}

[editar] 7. fread

Podemos escrever e ler blocos de dados. Para tanto, temos as funções fread() e fwrite(). O

protótipo de fread() é: unsigned fread (void *buffer, int numero_de_bytes, int count, FILE *fp); O buffer é a região de memória na qual serão armazenados os dados lidos. O número de bytes é

o tamanho da unidade a ser lida. count indica quantas unidades devem ser lidas. Isto

significa que o número total de bytes lidos é: numero_de_bytes*count A função retorna o número de unidades efetivamente lidas. Este número pode ser menor que

count quando o fim do arquivo for encontrado ou ocorrer algum erro. Quando o arquivo for aberto para dados binários, fread pode ler qualquer tipo de dados.

[editar] 8. fwrite

A função fwrite() funciona como a sua companheira fread(), porém escrevendo no arquivo. Seu

protótipo é:

unsigned fwrite(void *buffer,int numero_de_bytes,int count,FILE *fp);

A função retorna o número de itens escritos. Este valor será igual a count a menos que

ocorra algum erro. O exemplo abaixo ilustra o uso de fwrite e fread para gravar e posteriormente ler uma

variável float em um arquivo binário.


#include <stdio.h>
#include <stdlib.h>
int main()
 {
     FILE *pf;
     float pi = 3.1415;
     float pilido;
     if((pf = fopen("arquivo.bin", "wb")) == NULL) /* Abre arquivo binário para escrita */
           {
                 printf("Erro na abertura do arquivo");
                 exit(1);    
           }
     if(fwrite(&pi, sizeof(float), 1,pf) != 1)     /* Escreve a variável pi */
              printf("Erro na escrita do arquivo");
     fclose(pf);                                    /* Fecha o arquivo */
     if((pf = fopen("arquivo.bin", "rb")) == NULL) /* Abre o arquivo novamente para leitura 
  • /
           {
              printf("Erro na abertura do arquivo");
              exit(1);
           }
     if(fread(&pilido, sizeof(float), 1,pf) != 1)  /* Le em pilido o valor da variável 

armazenada anteriormente */

           printf("Erro na leitura do arquivo");
     printf("\nO valor de PI, lido do arquivo e': %f", pilido);
     fclose(pf);
     return(0);
 }


Note-se o uso do operador sizeof, que retorna o tamanho em bytes da variável ou do tipo de

dados.

[editar] 9. fseek

Para se fazer procuras e acessos randômicos em arquivos usa-se a função fseek(). Esta move a

posição corrente de leitura ou escrita no arquivo de um valor especificado, a partir de um

ponto especificado. Seu protótipo é: int fseek (FILE *fp,long numbytes,int origem); O parâmetro origem determina a partir de onde os numbytes de movimentação serão contados. Os

valores possíveis são definidos por macros em stdio.h e são:

Nome	        Valor	Significado
SEEK_SET	0	Início do arquivo
SEEK_CUR	1	Ponto corrente no arquivo
SEEK_END	2	Fim do arquivo


Tendo-se definido a partir de onde irá se contar, numbytes determina quantos bytes de

deslocamento serão dados na posição atual.

[editar] 10. rewind

A função rewind() de protótipo

void rewind (FILE *fp);

retorna a posição corrente do arquivo para o início.

[editar] 11. remove

Protótipo:

               int remove (char *nome_do_arquivo);

Apaga um arquivo especificado. O exercício da página anterior poderia ser reescrito usando-se, por exemplo, fgets() e

fputs(), ou fwrite() e fread(). A seguir apresentamos uma segunda versão que se usa das

funções fgets() e fputs(), e que acrescenta algumas inovações.


#include <stdio.h>
#include <string.h>
#include <stdlib.h> 
int main()
 {
   FILE *p;
   char str[30], frase[] = "Este e um arquivo chamado: ", resposta[80];
   int i;
   printf("\n\n Entre com um nome para o arquivo:\n");          /* Le um nome para o 

arquivo a ser aberto: */

   fgets(str,29,stdin);					/* Usa fgets como se fosse 

gets */

   for(i=0; str[i]; i++) if(str[i]=='\n') str[i]=0;	        /* Elimina o \n da string 

lida */

   if (!(p = fopen(str,"w")))  				/* Caso ocorra algum erro na 

abertura do arquivo..*/

     {                         				/* o programa aborta 

automaticamente */

 	printf("Erro! Impossivel abrir o arquivo!\n");
 	exit(1);
     }                                             /* Se nao houve erro, imprime no 

arquivo, e o fecha ...*/

   fputs(frase, p);
   fputs(str,p);
   fclose(p);                                        
   p = fopen(str,"r");                          /* abre novamente e le */
   fgets(resposta, 79, p);
   printf("\n\n%s\n", resposta);
   fclose(p);			                /* Fecha o arquivo */
   remove(str);			        /* Apaga o arquivo */
   return(0);

}

[editar] fprintf

A função fprintf() funciona como a função printf(). A diferença é que a saída de fprintf() é

um arquivo e não a tela do computador. Protótipo:

int fprintf (FILE *fp,char *str,...);

Como já poderíamos esperar, a única diferença do protótipo de fprintf() para o de printf() é

a especificação do arquivo destino através do ponteiro de arquivo.

[editar] fscanf

A função fscanf() funciona como a função scanf(). A diferença é que fscanf() lê de um

arquivo e não do teclado do computador. Protótipo: int fscanf (FILE *fp,char *str,...); Como já poderíamos esperar, a única diferença do protótipo de fscanf() para o de scanf() é a

especificação do arquivo destino através do ponteiro de arquivo. Talvez a forma mais simples de escrever o programa da página 97 seja usando fprintf () e

fscanf(). Fica assim:


#include <stdio.h>
#include <stdlib.h>
int main()
{
  FILE *p;
  char str[80],c;
  printf("\n\n Entre com um nome para o arquivo:\n");       /* Le um nome para o arquivo a 

ser aberto: */

  gets(str);
  if (!(p = fopen(str,"w")))  		                     /* Caso ocorra algum erro na 

abertura do arquivo..*/

    {                           		             /* o programa aborta 

automaticamente */

       printf("Erro! Impossivel abrir o arquivo!\n");
       exit(1);
    }
  fprintf(p,"Este e um arquivo chamado:\n%s\n", str);
  fclose(p);                                                /* Se nao houve erro, imprime 

no arquivo, fecha ...*/

  p = fopen(str,"r");                                 /* abre novamente para a leitura  */
  while (!feof(p))
   {
      fscanf(p,"%c",&c);
      printf("%c",c);
   } 
  fclose(p);
  return(0);
}

[editar] Tipos de Dados Avançados

Já vimos que uma variável é declarada como tipo_da_variável lista_de_variáveis; Vimos também que existem modificadores de tipos. Estes modificam o tipo da variável

declarada. Destes, já vimos os modificadores signed, unsigned, long, e short. Estes

modificadores são incluídos na declaração da variável da seguinte maneira: modificador_de_tipo tipo_da_variável lista_de_variáveis; Vamos discutir agora outros modificadores de tipo. Modificadores de Acesso Estes modificadores, como o próprio nome indica, mudam a maneira com a qual a variável é

acessada e modificada.


[editar] const

O modificador const faz com que a variável não possa ser modificada no programa. Como o nome

já sugere é útil para se declarar constantes. Poderíamos ter, por exemplo: const float PI=3.141; Podemos ver pelo exemplo que as variáveis com o modificador const podem ser inicializadas.

Mas PI não poderia ser alterado em qualquer outra parte do programa. Se o programador tentar

modificar PI o compilador gerará um erro de compilação. O uso mais importante de const não é declarar variáveis constantes no programa. Seu uso mais

comum é evitar que um parâmetro de uma função seja alterado pela função. Isto é muito útil

no caso de um ponteiro, pois o conteúdo de um ponteiro pode ser alterado por uma função.

Para tanto, basta declarar o parâmetro como const. Veja o exemplo:


#include <stdio.h>
int sqr (const int *num);
main (void)
 {
   int a=10;
   int b;
   b=sqr (&a);
 }
int sqr (const int *num)
 {
   return ((*num)*(*num));
 }


No exemplo, num está protegido contra alterações. Isto quer dizer que, se tentássemos fazer *num=10; Dentro da função sqr() o compilador daria uma mensagem de erro.

[editar] volatile

O modificador volatile diz ao compilador que a variável em questão pode ser alterada sem que

este seja avisado. Isto evita "bugs" seríssimos. Digamos que, por exemplo, tenhamos uma

variável que o BIOS do computador altera de minuto em minuto (um relógio por exemplo). Seria

muito bom que declarássemos esta variável como sendo volatile.


extern float sum;
int RetornaCount (void)
  {
    return count;
  }


Assim, o compilador irá saber que count e sum estão sendo usados no bloco mas que foram

declarados em outro.

[editar] static

O funcionamento das variáveis declaradas como static depende se estas são globais ou locais. Variáveis globais static funcionam como variáveis globais dentro de um módulo, ou seja, são

variáveis globais que não são (e nem podem ser) conhecidas em outros modulos. Isto é util se

quisermos isolar pedaços de um programa para evitar mudanças acidentais em variáveis

globais. Variáveis locais static são variáveis cujo valor é mantido de uma chamada da função para a

outra. Veja o exemplo: int count (void) { static int num=0; num++; return num; } A função count() retorna o número de vezes que ela já foi chamada. Veja que a variável local

int é inicializada. Esta inicialização só vale para a primeira vez que a função é chamada

pois num deve manter o seu valor de uma chamada para a outra. O que a função faz é

incrementar num a cada chamada e retornar o seu valor. A melhor maneira de se entender esta

variável local static é implementando. Veja por si mesmo, executando seu próprio programa

que use este conceito.


[editar] register

O computador tem a memória principal e os registradores da CPU. As variáveis (assim como o

programa como um todo) são armazenados na memória. O modificador register diz ao compilador

que a variável em questão deve ser, se possível, usada em um registrador da CPU. Vamos agora ressaltar vários pontos importantes. Em primeiro lugar, porque usar o register?

Variáveis nos registradores da CPU vão ser acessadas em um tempo muito menor pois os

registradores são muito mais rápidos que a memória. Em segundo lugar, em que tipo de

variável usar o register? O register não pode ser usado em variáveis globais. Isto

implicaria que um registrador da CPU ficaria o tempo todo ocupado por conta de uma variável.

Os tipos de dados onde é mais aconselhado o uso do register são os tipos char e int, mas

pode-se usá-lo em qualquer tipo de dado. Em terceiro lugar, o register é um pedido que o

programador faz ao compilador. Este não precisa ser atendido necessariamente. Um exemplo do uso do register é dado:


main (void)
 {
   register int count;
   for (count=0;count<10;count++)
       {
       ...
       }
   return 0;
 }


O loop for acima será executado mais rapidamente do que seria se não usássemos o register.

Este é o uso mais recomendável para o register: uma variável que será usada muitas vezes em

seguida.

[editar] Conversão de Tipos

Em atribuições no C temos o seguinte formato: destino=orígem; Se o destino e a orígem são de tipos diferentes o compilador faz uma conversão entre os

tipos. Nem todas as conversões são possíveis. O primeiro ponto a ser ressaltado é que o

valor de origem é convertido para o valor de destino antes de ser atribuído e não o

contrário. É importante lembrar que quando convertemos um tipo numérico para outro nós nunca ganhamos

precisão. Nós podemos perder precisão ou no máximo manter a precisão anterior. Isto pode ser

entendido de uma outra forma. Quando convertemos um número não estamos introduzindo no

sistema nenhuma informação adicional. Isto implica que nunca vamos ganhar precisão. Abaixo vemos uma tabela de conversões numéricas com perda de precisão, para um compilador

com palavra de 16 bits: De Para Informação Perdida unsigned char char Valores maiores que 127 são alterados short int char Os 8 bits de mais alta ordem int char Os 8 bits de mais alta ordem long int char Os 24 bits de mais alta ordem long int short int Os 16 bits de mais alta ordem long int int Os 16 bits de mais alta ordem float int Precisão - resultado arredondado double float Precisão - resultado arredondado long double double Precisão - resultado arredondado



[editar] Modificadores de Funções

A forma geral de uma função é, como já foi visto,

tipo_de_retorno nome_da_função (declaração_de_parâmetros) 
 { 
   corpo_da_função 
 } 

Uma função pode aceitar um modificador de tipo. Este vai modificar o modo como a função

opera na passagem de parâmetros. A forma geral da função ficaria então:

modificador_de_tipo tipo_de_retorno nome_da_função (declaração_de_parâmetros) 
  { 
     corpo_da_função 
  } 

lembram-se do casting que tínhamos que fazer para a tipologia das variaveis, (quando

tinhamos um int a dividir por um int que dava um número real e só nos aparecia o resultado

da divisão como um int, em vez de ser um float. ), pois bem aqui é parecido mas com as

funções. nós não vamos avançar mais. Apenas para ficarem com o conhecimento.

[editar] Ponteiros para Funções

O C permite que acessemos variáveis e funções através de ponteiros! Podemos então fazer

coisas como, por exemplo, passar uma função como argumento para outra função. Um ponteiro

para uma função tem a seguinte declaração: tipo_de_retorno (*nome_do_ponteiro)(); ou tipo_de_retorno (*nome_do_ponteiro)(declaração_de_parâmetros);

Repare nos parênteses que devem ser colocados obrigatoriamente. Se declaramos: tipo_de_retorno * nome(declaração_de_parâmetros); Estaríamos, na realidade, declarando uma função que retornaria um ponteiro para o tipo

especificado. Porém, não é obrigatório se declarar os parâmetros da função. Veja um exemplo do uso de

ponteiros para funções:

#include <stdio.h>
#include <string.h>
void PrintString (char *str, int (*func)(const char *));
main (void)
 {
   char String [20]="Curso de C.";
   int (*p)(const char *);	/* Declaracao do ponteiro para função Funcao apontada e' 

inteira e recebe como parametro uma string constante */

   p=puts;		/* O ponteiro p passa a apontar para a função puts que tem o 

seguinte prototipo: int puts(const char *) */

   PrintString (String, p);	/* O ponteiro é passado como parametro para PrintString */
   return 0;
 }
void PrintString (char *str, int (*func)(const char *))
 {
   (*func)(str);		/* chamada a função através do ponteiro para função */
   func(str);			/* maneira também válida de se fazer a chamada a função puts 

através do ponteiro para função func */

 }

Veja que fizemos a atribuição de puts a p simplesmente usando: p = puts; Disto, concluímos que o nome de uma função (sem os parênteses) é, na realidade, o endereço

daquela função! Note, também, as duas formas alternativas de se chamar uma função através de

um ponteiro. No programa acima, fizemos esta chamada por:

(*func)(str); e func(str);

Estas formas são equivalentes entre si. Além disto, no programa, a função PrintString() usa uma função qualquer func para imprimir a

string na tela. O programador pode então fornecer não só a string mas também a função que

será usada para imprimí-la. No main() vemos como podemos atribuir, ao ponteiro para funções

p, o endereço da função puts() do C. Em síntese, ao declarar um ponteiro para função, podemos atribuir a este ponteiro o endereço

de uma função e podemos também chamar a função apontada através dele. Não podemos fazer

algumas coisas que fazíamos com ponteiros "normais", como, por exemplo, incrementar ou

decrementar um ponteiro para função.

[editar] Alocação Dinâmica

A alocação dinâmica permite ao programador alocar memória para variáveis quando o programa

está sendo executado. Assim, poderemos definir, por exemplo, um vetor ou uma matriz cujo

tamanho descobriremos em tempo de execução. O padrão C ANSI define apenas 4 funções para o

sistema de alocação dinâmica, disponíveis na biblioteca stdlib.h: No entanto, existem diversas outras funções que são amplamente utilizadas, mas dependentes

do ambiente e compilador. Neste curso serão abordadas somente estas funções padronizadas.

pelo que eu percebi de dados dinâmicos é utilizar uma memória que é dinâmica, ie, quando

utilizamos um programa é utilizada essa memória e depois quando chamamos outro programa a

mesma memória que utilizou o programa anterior é agora utilizada para o novo programa. mas

ressalve que os dados importantes do resultado do programa anterior são gravados. isto

basicamente é a historia de memória ram e rom.


[editar] malloc

A função malloc() serve para alocar memória e tem o seguinte protótipo:

void *malloc (unsigned int num);

A função toma o número de bytes que queremos alocar (num), aloca na memória e retorna um

ponteiro void * para o primeiro byte alocado. O ponteiro void * pode ser atribuído a

qualquer tipo de ponteiro. Se não houver memória suficiente para alocar a memória

requisitada a função malloc() retorna um ponteiro nulo. Veja um exemplo de alocação dinâmica

com malloc():


#include <stdio.h>
#include <stdlib.h>	/* Para usar malloc() */
main (void)
{
 int *p;
 int a;
 int i;
 ... 							/* Determina o valor de a em algum 

lugar */

 p=(int *)malloc(a*sizeof(int));		/* Aloca a números inteiros 
						   p pode agora ser tratado como um vetor 

com a posicoes */

 if (!p)
  {
    printf ("** Erro: Memoria Insuficiente **");
    exit;
  }
 for (i=0; i<a ; i++)		/* p pode ser tratado como um vetor com a posicoes */
     p[i] = i*i;
 ...
 return 0;
}


No exemplo acima, é alocada memória suficiente para se armazenar a números inteiros. O

operador sizeof() retorna o número de bytes de um inteiro. Ele é util para se saber o

tamanho de tipos. O ponteiro void* que malloc() retorna é convertido para um int* pelo cast

e é atribuído a p. A declaração seguinte testa se a operação foi bem sucedida. Se não tiver

sido, p terá um valor nulo, o que fará com que !p retorne verdadeiro. Se a operação tiver

sido bem sucedida, podemos usar o vetor de inteiros alocados normalmente, por exemplo,

indexando-o de p[0] a p[(a-1)].

Os programas utilizam muito esta função que reserva um bloco de memória que podemos utilizar

á vontade para o nosso programa e quando o bloco de código é executado ele é reciclado pelo

sistema operativo.

int main()
{
  int *p;
  p = (int *)malloc(sizeof(int));
  if (p == 0)
   {
      printf("ERROR: Out of memory\n");
      return 1;
   }
  *p = 5;
  printf("&d\n", *p);
  free(p);
  return 0;
}

Vamos começar por tentar explicar esta linha de código. p = (int *)malloc(sizeof(int)); a função malloc pegunta ao Heap (pretence ao sistema operativo) “existe memória disponível

para um bloco de memória deste tamanho? e que tamnaho é esse? esse valor é depreendido pela

função sizeof(int). Como é um int está a pedir 4 bytes. Assim a função malloc retorna 0 se

não consegue obter o tal espaço de memória e 1 se consegue. Se consegue então aloca um

pointer á variável p

A seguinte linha de código mostra o valor a nós pelo ecrã qual o valor retornado pela função

malloc, se consegui arranjar o espaço de memoria ou não.

vamos ver agora um exemplo;

int main()
 {
  int *p, *q;
  p = (int *)malloc(sizeof(int));
  q = p;
  *p = 10;
  printf("%d\n", *q);
  *q = 20;
  printf("%d\n", *q);
}

Outro exemplo

int main()
 {
   int *p, *q;
   p = (int *)malloc(sizeof(int)); 		/*podemos simplificar por  p = (int 
  • )malloc(4) */
   q = (int *)malloc(sizeof(int));
   *p = 10;
   *q = 20;
   *p = *q;
   printf("%d\n", *p);
 }

  • o compilador aceita *p=*q porque são ambos int.
  • o compilador aceita também p=q porque são ambos pointes e apontam para a mesma tipologia


Podemos simplificar p = (int *)malloc(sizeof(int)); por p = (int *)malloc(4); mas como temos

sistemas operativos de 16,32, 64 bytes a primeira declaração torna as coisas mais portáveis.

Repare que utilizamos o typecasting (int *) que força a conversão do pointer retornado do

malloc que seja um pointer para um int.

[editar] calloc

A função calloc() também serve para alocar memória, mas possui um protótipo um pouco

diferente: void *calloc (unsigned int num, unsigned int size); A funçao aloca uma quantidade de memória igual a num * size, isto é, aloca memória

suficiente para um vetor de num objetos de tamanho size. Retorna um ponteiro void * para o

primeiro byte alocado. O ponteiro void * pode ser atribuído a qualquer tipo de ponteiro. Se

não houver memória suficiente para alocar a memória requisitada a função calloc() retorna um

ponteiro nulo. Veja um exemplo de alocação dinâmica com calloc():


#include <stdio.h>
#include <stdlib.h>			                        /* Para usar calloc() */
main (void)
 {
   int *p;
   int a;
   int i;
   ... 							/* Determina o valor de a em 

algum lugar */

   p=(int *)calloc(a,sizeof(int));		                /* Aloca a números inteiros 

p pode agora ser tratado como um vetor com a posicoes */

   if (!p)
    {
      	printf ("** Erro: Memoria Insuficiente **");
      	exit;
    }
   for (i=0; i<a ; i++)		                        /* p pode ser tratado como 

um vetor com a posicoes */

    p[i] = i*i;
   ...
   return 0;
}


No exemplo acima, é alocada memória suficiente para se colocar a números inteiros. O

operador sizeof() retorna o número de bytes de um inteiro. Ele é util para se saber o

tamanho de tipos. O ponteiro void * que calloc() retorna é convertido para um int * pelo

cast e é atribuído a p. A declaração seguinte testa se a operação foi bem sucedida. Se não

tiver sido, p terá um valor nulo, o que fará com que !p retorne verdadeiro. Se a operação

tiver sido bem sucedida, podemos usar o vetor de inteiros alocados normalmente, por exemplo,

indexando-o de p[0] a p[(a-1)].

[editar] realloc

A função realloc() serve para realocar memória e tem o seguinte protótipo: void *realloc (void *ptr, unsigned int num); A funçao modifica o tamanho da memória previamente alocada apontada por *ptr para aquele

especificado por num. O valor de num pode ser maior ou menor que o original. Um ponteiro

para o bloco é devolvido porque realloc() pode precisar mover o bloco para aumentar seu

tamanho. Se isso ocorrer, o conteúdo do bloco antigo é copiado no novo bloco, e nenhuma

informação é perdida. Se ptr for nulo, aloca size bytes e devolve um ponteiro; se size é

zero, a memória apontada por ptr é liberada. Se não houver memória suficiente para a

alocação, um ponteiro nulo é devolvido e o bloco original é deixado inalterado.


#include <stdio.h>
#include <stdlib.h>				/* Para usar malloc()  e realloc*/
main (void)
{
  int *p;
  int a;
  int i;
... 					         /* Determina o valor de a em algum lugar */
  a = 30;
  p=(int *)malloc(a*sizeof(int));		 /* Aloca a números inteiros p pode agora 

ser tratado como um vetor com a posicoes */

  if (!p)
   {
     printf ("** Erro: Memoria Insuficiente **");
     exit;
   }
 for (i=0; i<a ; i++)			           /* p pode ser tratado como um vetor com a 
 posicoes */
  p[i] = i*i;                                     /* O tamanho de p deve ser modificado, 

por algum motivo ... */

 a = 100;
 p = realloc (p, a*sizeof(int));
 for (i=0; i<a ; i++)			    /* p pode ser tratado como um vetor com a 

posicoes */

   p[i] = a*i*(i-6);
...
 return 0;
}

[editar] free

Quando alocamos memória dinamicamente é necessário que nós a liberemos quando ela não for

mais necessária. Para isto existe a função free() cujo protótipo é: void free (void *p); Basta então passar para free() o ponteiro que aponta para o início da memória alocada. Mas

você pode se perguntar: como é que o programa vai saber quantos bytes devem ser liberados?

Ele sabe pois quando você alocou a memória, ele guardou o número de bytes alocados numa

"tabela de alocação" interna. Vamos reescrever o exemplo usado para a função malloc() usando

o free() também agora:

#include <stdio.h>
#include <stdlib.h>	/* Para usar malloc e free */
main (void)
{
  int *p;
  int a;
  ...
  p=(int *)malloc(a*sizeof(int));
  if (!p)
   {
     printf ("** Erro: Memoria Insuficiente **");
     exit;
   }
  ...
  free(p);
  ...
  return 0;
}

[editar] Alocação Dinâmica de Vetores e Matrizes

Alocação Dinâmica de Vetores A alocação dinâmica de vetores utiliza os conceitos aprendidos na aula sobre ponteiros e as

funções de alocação dinâmica apresentados. Um exemplo de implementação para vetor real é

fornecido a seguir:

#include <stdio.h>
#include <stdlib.h>
float *Alocar_vetor_real (int n)
{
  float *v;        			           /* ponteiro para o vetor */
  if (n < 1) 
   {  			                           /* verifica parametros recebidos */
     printf ("** Erro: Parametro invalido **\n");
     return (NULL);
   }
 v = (float *) calloc (n, sizeof(float));         /* aloca o vetor */
 if (v == NULL) 
  {
    printf ("** Erro: Memoria Insuficiente **");
    return (NULL);
  }
 return (v);    			           /* retorna o ponteiro para o vetor */
}
float *Liberar_vetor_real (float *v)
{
  if (v == NULL) return (NULL);
  free(v);        			          /* libera o vetor */
  return (NULL);  			          /* retorna o ponteiro */
}
int main (void)
{
  float *p;
  int a;
  ...    				/* outros comandos, inclusive a inicializacao de a 
  • /
  p = Alocar_vetor_real (a);
  ...    				/* outros comandos, utilizando p[] normalmente */
  p = Liberar_vetor_real (p);
}

[editar] Alocação Dinâmica de Matrizes

A alocação dinâmica de memória para matrizes é realizada da mesma forma que para vetores,

com a diferença que teremos um ponteiro apontando para outro ponteiro que aponta para o

valor final, ou seja é um ponteiro para ponteiro, o que é denominado indireção múltipla. A

indireção múltipla pode ser levada a qualquer dimensão desejada, mas raramente é necessário

mais de um ponteiro para um ponteiro. Um exemplo de implementação para matriz real

bidimensional é fornecido a seguir. A estrutura de dados utilizada neste exemplo é composta

por um vetor de ponteiros (correspondendo ao primeiro índice da matriz), sendo que cada

ponteiro aponta para o início de uma linha da matriz. Em cada linha existe um vetor alocado

dinamicamente, como descrito anteriormente (compondo o segundo índice da matriz).


#include <stdio.h>
#include <stdlib.h>
float **Alocar_matriz_real (int m, int n)
  {
     float **v;  						/* ponteiro para a matriz */
     int   i;    						/* variavel auxiliar      */
     if (m < 1 || n < 1) 
       { 					       /* verifica parametros recebidos */
         printf ("** Erro: Parametro invalido **\n");
         return (NULL);
       }                                                   /* aloca as linhas da matriz */
     v = (float **) calloc (m, sizeof(float *));		/*Um vetor de m ponteiros 

para float */

     if (v == NULL) 
       {
         printf ("** Erro: Memoria Insuficiente **");
         return (NULL);
       }					
     for ( i = 0; i < m; i++ )                             /* aloca as colunas da matriz */
       {
         v[i] = (float*) calloc (n, sizeof(float));	/* m vetores de n floats */
         if (v[i] == NULL) 
             {
                printf ("** Erro: Memoria Insuficiente **");
                return (NULL);
             }
        }
   return (v); 					/* retorna o ponteiro para a matriz 
  • /
  }
float **Liberar_matriz_real (int m, int n, float **v)
  {
    int  i;  						/* variavel auxiliar */
    if (v == NULL) return (NULL);
    if (m < 1 || n < 1) 
      {  				/* verifica parametros recebidos */
       printf ("** Erro: Parametro invalido **\n");
       return (v);
      }
    for (i=0; i<m; i++) free (v[i]); 		/* libera as linhas da matriz */
    free (v);      					/* libera a matriz (vetor de 

ponteiros) */

    return (NULL); 					/* retorna um ponteiro nulo */
  }
int main (void)
  {
   float **mat;  			/* matriz a ser alocada */
   int   l, c;   			/* numero de linhas e colunas da matriz */
   int i, j;
   ...           			/* outros comandos, inclusive inicializacao para l e 

c */

   mat = Alocar_matriz_real (l, c);
   for (i = 0; i < l; i++)
     for ( j = 0; j < c; j++)
        mat[i][j] = i+j;
   ...           			/* outros comandos utilizando mat[][] normalmente */
   mat = Liberar_matriz_real (l, c, mat);
   ...
 }

[editar] Tipos de Dados Definidos Pelo Usuário

[editar] Estruturas - Primeira parte

Uma estrutura agrupa várias variáveis numa só. Funciona como uma ficha pessoal que tenha

nome, telefone e endereço. A ficha seria uma estrutura. A estrutura, então, serve para

agrupar um conjunto de dados não similares, formando um novo tipo de dados.

struct rec
{
   int a,b,c;
   float d,e,f;
};
struct rec r;

o c permite que nós agreguemos várias variáveis, chamando essas variáveis todas por um nome

apenas. no nosso exemplo chamámos de “rec” Podemos compactar da seguinte forma. são equivalentes.

struct rec
{
  int a,b,c;
  float d,e,f;
} r;

podemos atribuir o valor á variável a do grupo fazendo:

r.a=5;

Confesso que não entendi bem isto. não vejo grande vantagem. mas admito que pode ser útil

quando temos um código muito grande e temos muitas variáveis, e convém agrega-las numa forma

lógica.

[editar] Criando

Para se criar uma estrutura usa-se o comando struct. Sua forma geral é:

struct nome_do_tipo_da_estrutura 
{ 
tipo_1 nome_1; 
tipo_2 nome_2; 
... 
tipo_n nome_n; 
} variáveis_estrutura; 


O nome_do_tipo_da_estrutura é o nome para a estrutura. As variáveis_estrutura são opcionais

e seriam nomes de variáveis que o usuário já estaria declarando e que seriam do tipo

nome_do_tipo_da_estrutura. Um primeiro exemplo:

struct est{
   int i;
   float f;
} a, b;


Neste caso, est é uma estrutura com dois campos, i e f. Foram também declaradas duas

variáveis, a e b que são do tipo da estrutura, isto é, a possui os campos i e f, o mesmo

acontecendo com b. Vamos criar uma estrutura de endereço:


struct tipo_endereco
{
  char rua [50];
  int numero;
  char bairro [20];
  char cidade [30];
  char sigla_estado [3];
  long int CEP;
};


Vamos agora criar uma estrutura chamada ficha_pessoal com os dados pessoais de uma pessoa:


struct ficha_pessoal
 {
   char nome [50];
   long int telefone;
   struct tipo_endereco endereco;
 };


Vemos, pelos exemplos acima, que uma estrutura pode fazer parte de outra ( a struct

tipo_endereco é usada pela struct ficha_pessoal).

[editar] Usando

Vamos agora utilizar as estruturas declaradas na seção anterior para escrever um programa

que preencha uma ficha.


#include <stdio.h>
#include <string.h>
struct tipo_endereco
  {
    char rua [50];
    int numero;
    char bairro [20];
    char cidade [30];
    char sigla_estado [3];
    long int CEP;
  };
struct ficha_pessoal
  {
    char nome [50];
    long int telefone;
    struct tipo_endereco endereco;
  };
main (void)
  {
    struct ficha_pessoal ficha;
    strcpy (ficha.nome,"Luiz Osvaldo Silva");
    ficha.telefone=4921234;
    strcpy (ficha.endereco.rua,"Rua das Flores");
    ficha.endereco.numero=10;
    strcpy (ficha.endereco.bairro,"Cidade Velha");
    strcpy (ficha.endereco.cidade,"Belo Horizonte");
    strcpy (ficha.endereco.sigla_estado,"MG");
    ficha.endereco.CEP=31340230;
    return 0;
  }


O programa declara uma variável ficha do tipo ficha_pessoal e preenche os seus dados. O

exemplo mostra como podemos acessar um elemento de uma estrutura: basta usar o ponto (.).

Assim, para acessar o campo telefone de ficha, escrevemos:

ficha.telefone = 4921234;

Como a struct ficha pessoal possui um campo, endereco, que também é uma struct, podemos

fazer acesso aos campos desta struct interna da seguinte maneira:

ficha.endereco.numero = 10;
ficha.endereco.CEP=31340230;

Desta forma, estamos acessando, primeiramente, o campo endereco da struct ficha e, dentro

deste campo, estamos acessando o campo numero e o campo CEP.

[editar] Matrizes de estruturas

Uma estrutura é como qualquer outro tipo de dado no C. Podemos, portanto, criar matrizes de

estruturas. Vamos ver como ficaria a declaração de um vetor de 100 fichas pessoais: struct ficha_pessoal fichas [100]; Poderíamos então acessar a segunda letra da sigla de estado da décima terceira ficha

fazendo: fichas[12].endereco.sigla_estado[1]; Analise atentamente como isto está sendo feito ...


[editar] Estruturas - Segunda parte

[editar] Atribuindo

Podemos atribuir duas estruturas que sejam do mesmo tipo. O C irá, neste caso, copiar uma

estrutura, campo por campo, na outra. Veja o programa abaixo:


struct est1 
  {
    int i;
    float f;
  };
int main() 
  {  
    struct est1 primeira, segunda;         /* Declara primeira e segunda como structs do 

tipo est1 */

    primeira.i = 10;
    primeira.f = 3.1415;
    segunda = primeira;                             /* A segunda struct e' agora igual a 

primeira */

    printf(" Os valores armazenasdos na segunda struct sao :  %d  e  %f ", segunda.i , 

segunda.f);

  } 


São declaradas duas estruturas do tipo est1, uma chamada primeira e outra chamada segunda.

Atribuem-se valores aos dois campos da struct primeira. Os valores de primeira são

copiados em segunda apenas com a expressão de atribuição: segunda = primeira; Todos os campos de primeira serão copiados na segunda. Note que isto é diferente do que

acontecia em vetores, onde, para fazer a cópia dos elementos de um vetor em outro, tínhamos

que copiar elemento por elemento do vetor. Nas structs é muito mais fácil! Porém, devemos tomar cuidado na atribuição de structs que contenham campos ponteiros. Veja

abaixo:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct tipo_end
 { 
   char *rua;     			/* A struct possui um campo que é um ponteiro */
   int numero;
 };
int main()
 {
   struct tipo_end end1, end2;
   char buffer[50];
   printf("\nEntre o nome da rua:");
   gets(buffer);         			/* Le o nome da rua em uma string de buffer 
  • /
   end1.rua = (char *) malloc((strlen(buffer)+1)*sizeof(char));  /* Aloca a quantidade de 

memoria suficiente para armazenar a string */

   strcpy(end1.rua, buffer);   		/* Copia a string */
   printf("\nEntre o numero:");
   scanf("%d", &end1.numero);
   end2 = end1;      			/* ERRADO end2.rua e end1.rua estao apontando para a 

mesma regiao de memoria */

   printf("Depois da atribuicao:\n Endereco em end1 %s %d  \n Endereco em end2 %s %d", 

end1.rua,end1.numero,end2.rua, end2.numero);

   strcpy(end2.rua, "Rua Mesquita"); 		/* Uma modificacao na memoria apontada por 

end2.rua causara' a modificacao do que e' apontado por end1.rua, o que, esta' errado !!!

  • /
   end2.numero = 1100;				/* Nesta atribuicao nao ha problemas */
   printf(" \n\nApos modificar o endereco em end2:\n Endereco em end1 %s %d \n Endereco em 

end2 %s %d", end1.rua, end1.numero, end2.rua, end2.numero);

 }


Neste programa há um erro grave, pois ao se fazer a atribuição end2 = end1, o campo

rua de end2 estará apontando para a mesma posição de memória que o campo rua de end1. Assim,

ao se modificar o conteúdo apontado por end2.rua estaremos também modificando o conteúdo

apontado por end1.rua !!!

[editar] Passando para funções

No exemplo apresentado no ítem usando, vimos o seguinte comando: strcpy (ficha.nome,"Luiz Osvaldo Silva"); Neste comando um elemento de uma estrutura é passado para uma função. Este tipo de operação

pode ser feita sem maiores considerações.

Podemos também passar para uma função uma estrutura inteira. Veja a seguinte função:

void PreencheFicha (struct ficha_pessoal ficha)
{
...
}

Como vemos acima é fácil passar a estrutura como um todo para a função. Devemos observar

que, como em qualquer outra função no C, a passagem da estrutura é feita por valor. A

estrutura que está sendo passada, vai ser copiada, campo por campo, em uma variável local da

função PreencheFicha. Isto significa que alterações na estrutura dentro da função não terão

efeito na variável fora da função. Mais uma vez podemos contornar este pormenor usando

ponteiros e passando para a função um ponteiro para a estrutura.

[editar] Ponteiros

Podemos ter um ponteiro para uma estrutura. Vamos ver como poderia ser declarado um ponteiro

para as estruturas de ficha que estamos usando nestas seções:

struct ficha_pessoal *p;

Os ponteiros para uma estrutura funcionam como os ponteiros para qualquer outro tipo de

dados no C. Para usá-lo, haveria duas possibilidades. A primeira é apontá-lo para uma

variável struct já existente, da seguinte maneira:

struct ficha_pessoal ficha;
struct ficha_pessoal *p;
p  = &ficha;

A segunda é alocando memória para ficha_pessoal usando, por exemplo, malloc():

#include <stdlib.h> 
main()
{
   struct ficha_pessoal *p;
   int a = 10;                             /* Faremos a alocacao dinamica de 10 fichas 

pessoais */

   p = (struct ficha_pessoal *) malloc (a * sizeof(struct ficha_pessoal));
   p[0].telefone = 3443768;                /* Exemplo de acesso ao campo telefone da 

primeira ficha apontada por p */

   free(p);
}


Há mais um detalhe a ser considerado. Se apontarmos o ponteiro p para uma estrutura qualquer (como fizemos em p = &ficha; ) e

quisermos acessar um elemento da estrutura poderíamos fazer:

(*p).nome

Os parênteses são necessários, porque o operador . tem precedência maior que o operador * .

Porém, este formato não é muito usado. O que é comum de se fazer é acessar o elemento nome

através do operador seta, que é formado por um sinal de "menos" (-) seguido por um sinal de

"maior que" (>), isto é: -> . Assim faremos:

p->nome

A declaração acima é muito mais fácil e concisa. Para acessarmos o elemento CEP dentro de

endereco faríamos:

p->endereco.CEP

Fácil, não?


[editar] Declaração Union

Uma declaração union determina uma única localização de memória onde podem estar armazenadas

várias variáveis diferentes. A declaração de uma união é semelhante à declaração de uma

estrutura:


union nome_do_tipo_da_union 
{ 
  tipo_1 nome_1; 
  tipo_2 nome_2; 
  ... 
  tipo_n nome_n; 
  } variáveis_union; 

Como exemplo, vamos considerar a seguinte união:

union angulo
 {
    float graus;
    float radianos;
 };


Nela, temos duas variáveis (graus e radianos) que, apesar de terem nomes diferentes, ocupam

o mesmo local da memória. Isto quer dizer que só gastamos o espaço equivalente a um único

float. Uniões podem ser feitas também com variáveis de diferentes tipos. Neste caso, a

memória alocada corresponde ao tamanho da maior variável no union. Veja o exemplo:


#include <stdio.h> 
#define GRAUS 'G' 
#define RAD 'R' 
union angulo 
  { 
    int graus; 
    float radianos; 
  }; 
int main() 
  { 
    union angulo ang; 
    char op; 
    printf("\nNumeros em graus ou radianos? (G/R):"); 
    scanf("%c",&op); 
    if (op == GRAUS) 
       { 
         ang.graus = 180; 
         printf("\nAngulo: %d\n",ang.graus); 
       } 
    else if (op == RAD) 
       { 
          ang.radianos = 3.1415; 
          printf("\nAngulo: %f\n",ang.radianos); 
       } 
    else printf("\nEntrada invalida!!\n"); 
 } 


Temos que tomar o maior cuidado pois poderíamos fazer:


#include <stdio.h>
union numero
 {
     char Ch;
     int I;
     float F;
 };
main (void)
 {
     union numero N;
     N.I = 123;
     printf ("%f",N.F);	/* Vai imprimir algo que nao e' necessariamente 123 ...*/
     return 0;
 }


O programa acima é muito perigoso pois você está lendo uma região da memória, que foi

"gravada" como um inteiro, como se fosse um ponto flutuante. Tome cuidado! O resultado pode

não fazer sentido.

[editar] Enumerações

Numa enumeração podemos dizer ao compilador quais os valores que uma determinada variável

pode assumir. Sua forma geral é: enum nome_do_tipo_da_enumeração {lista_de_valores} lista_de_variáveis; Vamos considerar o seguinte exemplo: enum dias_da_semana {segunda, terca, quarta, quinta, sexta, sabado, domingo}; O programador diz ao compilador que qualquer variável do tipo dias_da_semana só pode ter os

valores enumerados. Isto quer dizer que poderíamos fazer o seguinte programa:


#include <stdio.h>
enum dias_da_semana {segunda, terca, quarta, quinta, sexta,sabado, domingo};
main (void)
  {
    enum dias_da_semana d1,d2;
    d1=segunda;
    d2=sexta;
    if (d1==d2)
      {
        printf ("O dia e o mesmo.");
      }
    else
      {
        printf ("São dias diferentes.");
      }
    return 0;
}

Você deve estar se perguntando como é que a enumeração funciona. Simples. O compilador pega

a lista que você fez de valores e associa, a cada um, um número inteiro. Então, ao primeiro

da lista, é associado o número zero, o segundo ao número 1 e assim por diante. As variáveis

declaradas são então variáveis int.

[editar] O Comando sizeof

O operador sizeof é usado para se saber o tamanho de variáveis ou de tipos. Ele retorna o

tamanho do tipo ou variável em bytes. Devemos usá-lo para garantir portabilidade. Por

exemplo, o tamanho de um inteiro pode depender do sistema para o qual se está compilando. O

sizeof é um operador porque ele é substituído pelo tamanho do tipo ou variável no momento

da compilação. Ele não é uma função. O sizeof admite duas formas:

sizeof nome_da_variável
sizeof (nome_do_tipo)

Se quisermos então saber o tamanho de um float fazemos sizeof(float). Se declararmos a

variável f como float e quisermos saber o seu tamanho faremos sizeof f. O operador sizeof

também funciona com estruturas, uniões e enumerações. Outra aplicação importante do operador sizeof é para se saber o tamanho de tipos definidos

pelo usuário. Seria, por exemplo, uma tarefa um tanto complicada a de alocar a memória para

um ponteiro para a estrutura ficha_pessoal, criada na primeira página desta aula, se não

fosse o uso de sizeof. Veja o exemplo:


#include <stdio.h> 
struct tipo_endereco 
       { 
       char rua [50]; 
       int numero; 
       char bairro [20]; 
       char cidade [30]; 
       char sigla_estado [3]; 
       long int CEP; 
       }; 
struct ficha_pessoal 
       { 
       char nome [50]; 
       long int telefone; 
       struct tipo_endereco endereco; 
       }; 
int main(void) 
{ 
struct ficha_pessoal *ex; 
ex = (struct ficha_pessoal *) malloc(sizeof(struct ficha_pessoal)); 
... 
free(ex); 
}

[editar] O Comando typedef

O comando typedef permite ao programador definir um novo nome para um determinado tipo. Sua

forma geral é: typedef antigo_nome novo_nome; Como exemplo vamos dar o nome de inteiro para o tipo int: typedef int inteiro; Agora podemos declarar o tipo inteiro. O comando typedef também pode ser utilizado para dar nome a tipos complexos, como as

estruturas. As estruturas criadas no exemplo da página anterior poderiam ser definidas como

tipos através do comando typedef. O exemplo ficaria:


#include <stdio.h> 
typedef struct tipo_endereco 
       { 
       char rua [50]; 
       int numero; 
       char bairro [20]; 
       char cidade [30]; 
       char sigla_estado [3]; 
       long int CEP; 
       } TEndereco; 

typedef struct ficha_pessoal 
       { 
       char nome [50]; 
       long int telefone; 
       TEndereco endereco; 
       }TFicha; 
int main(void) 
{ 
TFicha *ex; 
... 
} 


Veja que não é mais necessário usar a palavra chave struct para declarar variáveis do tipo

ficha pessoal. Basta agora usar o novo tipo definido TFicha.

[editar] Uma aplicação de structs: as listas simplesmente encadeadas

Várias estruturas de dados complexas podem ser criadas utilizando simultaneamente structs e

ponteiros. Uma destas estruturas é a lista encadeada. Uma lista encadeada é uma seqüência de

structs, que são os nós da lista, ligados entre si através de ponteiros. Esta seqüência pode

ser acessada através de um ponteiro para o primeiro nó, que é a cabeça da lista. Cada nó

contém um ponteiro que aponta para a struct que é a sua sucessora na lista. O ponteiro da

última struct da lista aponta para NULL, indicando que se chegou ao final da lista. Esta

estrutura de dados é criada dinamicamente na memória (utiliza-se malloc() e free()), de modo

que se torna simples introduzir nós nela, retirar nós, ordenar os nós, etc. Não vamos entrar

em detalhes sobre todos os algoritmos que poderíamos criar em uma lista encadeada, pois isto

geralmente é feito em cursos de algoritmos e estruturas de dados, não se incluindo no escopo

deste curso. Aqui, veremos somente formas de se criar uma lista encadeada em C e também

maneiras simples de percorrer esta lista. Supondo que queiramos criar uma lista encadeada para armazenar os produtos disponíveis em

uma loja. Poderíamos criar um nó desta lista usando a seguinte struct:

struct Produto 
 {
   int codigo;                        /* Codigo do produto */
   double preco;                      /* Preco do produto */
   struct Produto *proximo;           /* Proximo elemento da lista encadeada de Produtos */
 };

Note que esta struct possui, além dos campos de dados codigo e preco, um campo adicional que

é um ponteiro para uma struct do tipo Produto. É este campo que será utilizado para apontar

para o próximo nó da lista encadeada. O programa a seguir faz uso desta struct, através de

um novo tipo criado por um typedef, para criar uma lista de produtos de uma loja:


#include <stdio.h>
#include <stdlib.h>
typedef struct tipo_produto 
  {                    /* Estrutura que será usada para criar os nós da lista */
    int codigo;                                   /* Codigo do produto */
    double preco;                                 /* Preco do produto */
    struct tipo_produto *proximo;                  /* Proximo elemento da lista encadeada 

de Produtos */

 }  TProduto;
void inserir(TProduto **cabeca);                   /* Prototipos das funcoes para inserir e 

listar produtos */

void listar (TProduto *cabeca);
int main()
{
   TProduto *cabeca = NULL;                        /* Ponteiro para a cabeca da lista */
   TProduto *noatual;                              /* Ponteiro a ser usado para percorrer a 

lista no momento de desalocar seus elementos*/

   char q;                          		    /* Caractere para receber a opcao do 

usuario */

   do 
     {
       printf("\n\nOpcoes: \nI -> para inserir novo 
          produto;\nL -> para listar os produtos; \nS -> para sair \n:");
       scanf("%c", &q);     					/* Le a opcao do usuario */
       switch(q) 
            {
               case 'i': case 'I': inserir(&cabeca); break;
               case 'l': case 'L': listar(cabeca); break;
               case 's': case 'S': break;
               default: printf("\n\n Opcao nao valida");
            }
       fflush(stdin);    						/* Limpa o buffer de 

entrada */

     } while ((q != 's') && (q != 'S') );
   noatual = cabeca;                                     /* Desaloca a memoria alocada para 

os elementos da lista */

   while (noatual != NULL)
        {
            cabeca = noatual->proximo;
            free(noatual);
            noatual = cabeca;
        }
}
                                    
void listar (TProduto *noatual)                      /* Lista todos os elementos presentes 

na lista encadeada */

{
   int i=0;
   while( noatual != NULL)                      /* Enquanto nao chega no fim da lista */
   {
       i++;
       printf("\n\nProduto numero %d\nCodigo: %d \nPreco:R$%.2lf", i, noatual->codigo, 

noatual->preco);

       noatual = noatual->proximo;                     /* Faz noatual apontar para o 

proximo no */

   }
}
void inserir (TProduto **cabeca)              /* Funcao para inserir um novo no, ao final 

da lista */

{
   TProduto *noatual, *novono;
   int cod;
   double preco;
   printf("\n Codigo do novo produto: ");
   scanf("%d", &cod);
   printf("\n Preco do produto:R$");
   scanf("%lf", &preco);
   if (*cabeca == NULL)                                      /* Se ainda nao existe nenhum 

produto na lista */

   {
       *cabeca = (TProduto *) malloc(sizeof(TProduto));             /* cria o no cabeca */
       (*cabeca)->codigo = cod;
       (*cabeca)->preco = preco;
       (*cabeca)->proximo = NULL;
   }
   else
   {
       noatual = *cabeca;                                   /* Se ja existem elementos na 

lista, deve percorre-la ate' o seu final e inserir o novo elemento */

       while(noatual->proximo != NULL)
           noatual = noatual->proximo;                      /* Ao final do while, noatual 

aponta para o ultimo no */

       novono = (TProduto *) malloc(sizeof(TProduto));      /* Aloca memoria para o novo no 
  • /
       novono->codigo = cod;
       novono->preco = preco;
       novono->proximo = NULL;
       noatual->proximo = novono;     		             /* Faz o ultimo no apontar para 

o novo no */

   }
}

É interessante notar que, no programa anterior não existe limite para o número de produtos

que se vai armazenar na lista. Toda vez que for necessário criar um novo produto, memória

para ele será alocada e ele será criado no final da lista. Note que a função inserir recebe

o endereço do ponteiro cabeça da lista. Qual a razão disto? A razão é que o endereço para o

qual a cabeça da lista aponta poderá ser modificado caso se esteja inserindo o primeiro

elemento na lista. Tente entender todos os passos deste programa, pois ele possui várias das

características presentes em programas que manipulam listas encadeadas. Também é importante

notar que várias outras estruturas de dados complexas podem ser criadas com structs contendo

ponteiros que apontam para outras structs.