Programar em C/Capa: mudanças entre as edições
imported>Joaodaveiro |
imported>Joaodaveiro (→exit) |
||
Linha 1 827: | Linha 1 827: | ||
Protótipo é: | Protótipo é: | ||
void exit (int codigo_de_retorno); | 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. | 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. | ||
Edição das 18h24min de 9 de fevereiro de 2006
PROGRAMAR EM C
Manual autodidacta, …
Onde é que posso arranjar um compilador
O C é uma linguagem de programação, o que significa que podemos criar instruções para o computador seguir. Para criarmos as instruções vamos criar linhas de código ou linguagem que é em c, que depois vão ser traduzidas em linguagem binária por forma a poderem ser executadas pelo computador. Para isso eu vou precisar de um compilador. Os compiladores dependem do sistema operativo, portanto:
Para o WINDOWS/ DOS temos:
- Bloodshed Dev 4.0
(este é um compilador, mas que tem estética, ou seja não é daqueles compiladores rudes, tem um aspecto Windows, por isso para as funções basta-nos clicar em botões em vez de andar a escrever outro código. utilizei este para este trabalho. primeiro quero aprender a programar em c e depois vejo a linguagem de compilação mais tarde, uma coisa de cada vez!!)
- Borland
(é um compilador por comando, é o tal que para usarmos o compilador temos de saber os comandos. prefiro deixar isso para mais tarde, primeiro c depois os comandos de compilação).
- DJGPP
(é baseado em DOS).
- Microsoft Visual C++
COMO É QUE FUNCIONA
Em primeiro lugar, vamos escrever um código que está na linguagem C e vamos gravar esse código todo num ficheiro, que é uma quantidade de memória no computador. Esse ficheiro fica com a terminação “.c”. chama-se a este conjunto de linhas que escrevemos como SOURCE CODE (pode ter a terminação “.c”, “.cpp”, e “.cc”)
Depois vamos traduzir esse código em linguagem c para a linguagem binária, por forma a que o código se transforme num executável, tem a terminação “.exe”. Só assim é que o sistema operativo reconhece o programa como independente.
Nós aqui podemos dizer, mas porra olha que quando temos o ficheiro em “.c”, ele está na forma binária, portanto o que é que estás a falar!!! ok. repare-se que o computador funciona em “passa energia ou não passa energia” e temos um relógio e a partir daqui construímos tudo. Aquilo que chamamos informação, que o computador trabalha em informação não passa de uma interpretação. Nós vemos aquilo de uma maneira e damos significado. Por isso, apesar do ficheiro em “.c” estar na linguagem binária precisamos de o tratar de forma que o sistema operativo o consiga interpretar.
Mas se quisermos saber o processo de compilação aglomera várias etapas: Temos a compilação o linking. ao processo de pegar no source code e transforma-lo num executável chama-se buid. O linking é após a compilação ligar esses objectos todos que estão em linguagem binária.
uma das vantagens do c é que ususfrue de uma estrutura multi-file. a linguagem permite compilação separada, onde partes do programa total podem estar numa ou mais source files e estes podem ser compilados independentemente de cada um. a ideia é que o processo de compilação produz files que depois podem ser linked together usando um editor de link ou loades que o sistema provem.
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 para quando ele fizer o seu trabalho para ter atenção.
Depois temos o “include” (que basicamente diz para incluirmos). incluir o quê?
Deve incluir 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. 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. Mais, nós próprios podemos criar uma biblioteca. E até podemos comprar bibliotecas existem por aí.
Mas o que é que é isto de bibliotecas. É código que alguém fez antes. As que enunciamos antes são as standard, são aquelas que têm as funcionalidades básicas. Repare-se 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 se calhar nem topávamos.
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…)
Repare-se que todas as funções têm ( ), só assim é que o compilador reconhece que é uma função. a ideia de ter funções é permitir o encapsulamento de uma ideia ou operação, dar um nome a isso e depois chamar essa operação de várias partes do progrma simplesmente usando o seu nome.
{} 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 é importante nesta linguagem toda, funciona tipo o ponto final, separa as frases e contextos. Repare que apenas as funções é que não têm e os headers.
/*o meu primeiro programa*/
O c permite inserir comentários, ou melhor permite escrever o que pretendermos entre /* e fim */ que na hora da compilação eles serão eliminados, não servem para nada para o compilador, apenas são bons para nós pois permite-nos escrever o que quisermos
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*/ }
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.
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
Tipologia de variáveis
C tem 5 tipos básicos:
- char,
- int,
- float,
- void,
- 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
printf ()
A função printf () está dentro da livraria 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.
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
- . Tamanho do campo,
- . Justificação e o
- . 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 |
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; }
. 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.
. scanf ()
O scanf() é uma função que está também na livraria 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
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++)
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!
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); }
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.
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); }
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
O Comando break
O break faz com que a execução do programa continue na primeira linha seguinte ao loop ou bloco
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.
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; ....
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
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
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
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!
The idea of a function is, of course, to allow you to encapsulate one idea or operation, give it a name, then to call that operation from various parts of the rest of your program simply by using the name. In well designed, properly structured programs, it should be possible to change the way that a function does its job (as long as the job itself doesn't change) with no effect on the rest of the program.
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:
- . 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.
- . 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
- . 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.
- . 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.
FUNCTION PROTOTYOPES
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.
VOID
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. e isto parece-me que é uma confusão generalizada. o main() é especial, tem de retornar um int no mínimo.
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.
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
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
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!
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"); }
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; }
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!!
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
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 < >.
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).
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.
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.
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
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.
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
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.
LIBRARIES – LIVRARIAS
As livrarias 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 livrarias têm 2 partes:
- . header file (que temo “h” sufixo – contem informação sobre a livraria. de uma forma gera, contém constantes, types e prototipods de funções disponibilizadas pela livraria.
- . e o file do código efectivo
Como eu quero criar uma livraria para a função do bublesort, vou fazer o seguinte
- . 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 []);
- . 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; } }
- . 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 livraria que criamos, sempre e quando quisermos.
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.
TEXT FILES
Existem 6 comandos de I/O na livraria <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 livraria conio.h. A 2ª função imprime no ecrã depois de ler.
sprintf
vamos fazer agora apenas umas notas a estas funções:
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)
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
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.
getc
Retorna um caractere lido do arquivo. Protótipo:
int getc (FILE *fp);
strcpy
strcpy (string_destino,string_origem);
strcat
strcat (string_destino,string_origem); A string de origem permanecerá inalterada e será anexada ao fim da string de destino.
strlen
strlen (string); retorna o comprimento da string fornecida. O terminador nulo não é contado.
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.
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
- . fopen - opens a text file
- . fclose - closes a text file
- . feof - detects end-of-file marker in a file
- . fgets - reads a string from a file
- . fputs - prints a string to a file
- . ferror e perror
- . fread
- . fwrite
- . fseek
- . rewind
- . remove
- . fprintf - prints formatted output to a file
- . fscanf - reads formatted input from a file
- . fputc - prints a character to a file
- . fgetc - reads a character from a file
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.
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; }
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.
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 */ }
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);
5. fputs
Protótipo:
char *fputs (char *str,FILE *fp);
Escreve uma string num arquivo.
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);
}
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.
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.
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.
10. rewind
A função rewind() de protótipo
void rewind (FILE *fp);
retorna a posição corrente do arquivo para o início.
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);
}
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.
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); }
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.
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.
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.
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.
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.
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
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 vamoa avançar mais. Apenas para ficarem com o conhecimento.
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.
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.
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.
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)].
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; }
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; }
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); }
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); ... }
Tipos de Dados Definidos Pelo Usuário
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.
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).
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.
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 ...
Estruturas - Segunda parte
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 !!!
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.
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?
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.
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.
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); }
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.
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.
Exemplos de programas interessantes
Exercício 1
/* Dumb program that generates prime numbers.*/ #include <stdio.h> #include <stdlib.h> main() { int this_number, divisor, not_prime; this_number = 3; while(this_number < 10000) { divisor = this_number / 2; not_prime = 0; while(divisor > 1) } if(this_number % divisor == 0) { not_prime = 1; divisor = 0; } else divisor = divisor-1; if(not_prime == 0) { printf("%d is a prime number\n", this_number); this_number = this_number + 1; } system (“pause”); exit(EXIT_SUCCESS); }
Exercício 2
#include <stdio.h> #include <stdlib.h> main() { int ch; ch = getchar(); while(ch != 'a') { if(ch != '\n') { printf("ch was %c, value %d\n", ch, ch); ch = getchar(); } system (“pause”); exit(EXIT_SUCCESS); }
Exercício 3
#include <stdio> #include <stdlib.h> #define ARSIZE 10 main(){ int ch_arr[ARSIZE],count1; int count2, stop, lastchar; lastchar = 0; stop = 0; /* * Read characters into array. * Stop if end of line, or array full. */ while(stop != 1) { ch_arr[lastchar] = getchar(); if(ch_arr[lastchar] == '\n') stop = 1; else lastchar = lastchar + 1; if(lastchar == ARSIZE) stop = 1; } lastchar = lastchar-1; /* * Now the traditional bubble sort. */ count1 = 0; while(count1 < lastchar) { count2 = count1 + 1; while(count2 <= lastchar)
{
if(ch_arr[count1] > ch_arr[count2])
{
/* swap */ int temp; temp = ch_arr[count1]; ch_arr[count1] = ch_arr[count2]; ch_arr[count2] = temp; } count2 = count2 + 1; } count1 = count1 + 1; } count1 = 0; while(count1 <= lastchar) { printf("%c\n", ch_arr[count1]); count1 = count1 + 1; } exit(EXIT_SUCCESS); }
Exercício 4
#include <stdio.h> #include <stdlib.h> #define BOILING 212 /* degrees Fahrenheit */ main() { float f_var; double d_var; long double l_d_var; int i; i = 0; printf("Fahrenheit to Centigrade\n"); while(i <= BOILING)
{
l_d_var = 5*(i-32); l_d_var = l_d_var/9; d_var = l_d_var; f_var = l_d_var; printf("%d %f %f %lf\n", i, f_var, d_var, l_d_var); i = i+1; }
system (“pause”);
return 0; }
Exercício 5
#include <limits.h> /*repare que agora temos esta livraria*/ #include <stdio.h> #include <stdlib.h> main() { char c; c = CHAR_MIN; while(c != CHAR_MAX)
{
printf("%d\n", c); c = c+1; } system (“pause”); return 0; }
Exercício 6
#include <stdio.h> #include <stdlib.h> main() { int this_char, comma_count, stop_count; comma_count = stop_count = 0; this_char = getchar(); while(this_char != EOF)
{
if(this_char == '.') stop_count = stop_count+1; if(this_char == ',') comma_count = comma_count+1; this_char = getchar(); } printf("%d commas, %d stops\n", comma_count,stop_count);
system (“pause”);
return 0; }
Exercício 7
#include <stdio.h> #include <stdlib.h> main() { int i; for(i = 0; i <= 10; i++)
{
switch(i)
{
case 1: case 2: printf("1 or 2\n"); break; case 7: printf("7\n"); break; default: printf("default\n"); } } exit(EXIT_SUCCESS); }
reparar: se não tivessemos colocado o break o program continuaria
Exercício 8
#include <stdio.h> #include <stdlib.h> main() { int i, j; for(i=0, j=0; i <= 10; i++, j = i*i) { printf("i %d j %d\n", i, j); } /* * In this futile example, all but the last * constant value is discarded. * Note use of parentheses to force a comma * expression in a function call. */ printf("Overall: %d\n", ("abc", 1.2e6, 4*3+2)); system (“pause”); return 0; }
Exercício 9
#include <stdio.h> #include <stdlib.h> main() { void pmax(); /* declaration */ int i,j; for(i = -10; i <= 10; i++) { for(j = -10; j <= 10; j++) { pmax(i,j); } } system (“pause”); return 0; } /* * Function pmax. * Returns: void * Prints larger of its two arguments. */ void pmax(int a1, int a2){ /* definition */ int biggest; if(a1 > a2) { biggest = a1; } else { biggest = a2; } printf("larger of %d and %d is %d\n", a1, a2, biggest); }
isto vai dar erro por falta de especificação dos argumentos da função pmax
Exercício 10
#include <stdio.h> #include <stdlib.h> main(){ void pmax(int first, int second); /*declaration*/ int i,j; for(i = -10; i <= 10; i++){ for(j = -10; j <= 10; j++){ pmax(i,j); } } system (“pause”); return 0; } void pmax(int a1, int a2) { /*definition*/ int biggest; if(a1 > a2){ biggest = a1; } else{ biggest = a2; } printf("largest of %d and %d is %d\n",a1, a2, biggest); }
Exercício 11
#include <stdio.h> #include <stdlib.h> #define DELTA 0.0001 main() { double sq_root(double); /* prototype */ int i; for(i = 1; i < 100; i++) { printf("root of %d is %f\n", i, sq_root(i)); } system (“pause”); return 0; } double sq_root(double x) { /* definition */ double curr_appx, last_appx, diff; last_appx = x; diff = DELTA+1; while(diff > DELTA) { curr_appx = 0.5*(last_appx + x/last_appx); diff = curr_appx - last_appx; if(diff < 0) diff = -diff; last_appx = curr_appx; } return(curr_appx); } void called_func(int, float); main() { called_func(1, 2*3.5); exit(EXIT_SUCCESS); } void called_func(int iarg, float farg) { float tmp; tmp = iarg * farg; }
Exercício 12
#include <stdio.h> #include <stdlib.h> main() { void changer(int); int i; i = 5; printf("before i=%d\n", i); changer(i); printf("after i=%d\n", i); system (“pause”); return 0; } void changer(int x) { while(x) { printf("changer: x=%d\n", x); x--; } }
/*
- Recursive descent parser for simple C expressions.
- Very little error checking.
- /
Exercício 13
#include <stdio.h> #include <stdlib.h> int expr(void); int mul_exp(void); int unary_exp(void); int primary(void); main() { int val; for(;;) { printf("expression: "); val = expr(); if(getchar() != '\n') { printf("error\n"); while(getchar() != '\n'); /* NULL */ }
else
{ printf("result is %d\n", val); } } system (“pause”); return 0; } int expr(void) { int val, ch_in; val = mul_exp(); for(;;)
{
switch(ch_in = getchar())
{
default: ungetc(ch_in,stdin); return(val); case '+': val = val + mul_exp(); break; case '-': val = val - mul_exp(); break; } } } int mul_exp(void) { int val, ch_in; val = unary_exp(); for(;;)
{
switch(ch_in = getchar()) { default: ungetc(ch_in, stdin); return(val); case '*': val = val * unary_exp(); break; case '/': val = val / unary_exp(); break; case '%': val = val % unary_exp(); break; } } } int unary_exp(void) { int val, ch_in; switch(ch_in = getchar())
{
default: ungetc(ch_in, stdin); val = primary(); break; case '+': val = unary_exp(); break; case '-': val = -unary_exp(); break; } return(val); } int primary(void) { int val, ch_in; ch_in = getchar(); if(ch_in >= '0' && ch_in <= '9')
{
val = ch_in - '0'; goto out; } if(ch_in == '(')
{
val = expr(); getchar(); /* skip closing ')' */ goto out; } printf("error: primary read %d\n", ch_in); exit(EXIT_FAILURE); out: return(val); }