










Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Encontra documentos específicos para os exames da tua universidade
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Este tutorial fornece uma introdução ao c, mostrando como fazer a conexão com o pascal e como converter programas escritos em outras linguagens. Além disso, aborda as diferenças entre os dois idiomas e fornece dicas para evitar erros comuns.
Tipologia: Notas de estudo
1 / 18
Esta página não é visível na pré-visualização
Não perca as partes importantes!











é uma linguagem fácil de aprender, especialmente se você já conhece Pascal ou alguma outra linguagem procedural. Todo conceito em Pascal leva diretamente a um conceito em C: as idéias são exatamente as mesmas, mas você usa palavras diferentes para expressá-las. O C pode parecer difícil, às vezes, porque dá mais liberdade ao programador e, portanto, é mais fácil cometer erros ou criar bugs que são difíceis de descobrir. Este tutorial faz uma introdução ao C mostrando como fazer a conexão com o Pascal. Também introduz vários conceitos que não existem em Pascal. A maior parte desses conceitos trata dos ponteiros. Os leitores que possuem uma formação em
Fortran, Cobol, BASIC, etc., perceberão como o código do Pascal é fácil de ler. Eu acredito que a única maneira de aprender C (ou qualquer outra linguagem) é escrever e ler muito código. Uma boa maneira de conseguir experiência em C é pegar programas escritos em outras linguagens e convertê-los. Dessa forma, se o programa não funcionar em C, você sabe que é a tradução que está causando o problema e não o código original. Existe uma grande diferença entre o Pascal e o C que causa vários problemas: o C não permite procedures aninhadas, assim será necessário remover todas elas para converter qualquer programa em Pascal. O melhor é que você substitua estas
procedures no código Pascal. Desta forma, você pode testar novamente o programa antes de traduzi-lo para C. Também repare que o C é case sensitive. Os compiladores entendem que XXX, xxx e Xxx são três nomes diferentes. Por convenção, as constantes em C são escritas em maiúscu- la, enquanto as variáveis em minúsculas ou combinadas. As palavras-chave em C são sempre em minúscula. Neste tutorial, todas as instruções de compilação e referências às páginas “man” assumem que você está trabalhando numa estação Unix normal. Se você não estiver, terá de usar os arquivos de ajuda do seu sistema para mapear as instruções para o seu ambiente.
Abaixo temos um programa simples em C que encontra o fator de 6. Abra seu editor favorito e digite-o. Salve o programa com algum nome como exemp.c. Se você esquecer do .c, terá um erro “Bad Magic Number” quando fizer a compilação. Assim, nunca se esqueça da extensão.
/* Programa para encontrar o fator de 6 / #include <stdio.h> #define VALUE 6 int i,j; void main() { j=1; for (i=1; i<=VALUE; i++) j=ji; printf(“The factorial of %d is %d\n”,VALUE,j); }
Na maioria dos sistemas Unix, você encontrará um programa chamado C Beautifier, que irá formatar o código para você. Para compilar esse código, digite cc
exemp.c. Para dar o run, digite a.out. Se ele não compilar ou não o fizer correta- mente, edite-o novamente e veja o que deu errado. Agora, vamos olhar o código Pascal equivalente:
{ Programa para encontrar o fator de 6 } program samp; const value=6; var i,j:integer; begin j:=1; for i:=1 to value do j:=j*i; writeln(‘The factorial of ‘,value,’ is ‘,j); end.
Você pode notar uma correspondência quase total. A única diferença real é que o código em C começa com #include <stdio.h>. Essa linha inclui a biblioteca Standard I/O no seu programa; assim
você pode ler e escrever valores, trabalhar com arquivos texto e assim por diante. C tem um número grande de bibliotecas Standard como stdio, incluindo bibliotecas de texto, tempo e matemática. A linha #define cria uma constante. Duas variáveis globais são declaradas usando a linha int i,j;. Outros tipos de variáveis comuns são float (para números reais) e char (para caracteres), ambas podendo ser declaradas da mesma forma que int. A linha main() declara a função principal. Todo programa em C deve ter uma função chamada main em algum lugar do código. Em C, { e } substituem o begin e o end do Pascal. Da mesma forma, = substitui o operador de atribuição do Pascal :=. A estrutura de repetição for e a declaração printf são estranhas, mas elas fazem as mesmas funções que suas contrapartes no Pascal. Note que o C usa aspas duplas em vez de simples para strings. A declaração printf em C é mais fácil de usar do que a versão Pascal, uma vez que você se acostuma com ela. A porção em aspas é chamada de format string e
descreve como o dado deve ser formatado quando impresso. A format string contém string literais como The factorial of e \n para retorno de linha e operadores como marcação para variáveis. Os dois operado- res na format string indicam que os valores integer encontrados mais tarde na lista de parâmetros serão colocados na string exatamente nestes pontos. Outros operadores incluem floating point, para caracteres e strings. Você pode digitar man printf para ajuda em opções de formatação. Na declaração printf, é extremamente importante que o número de operadores na format string corresponda exatamente ao número e tipo das variáveis existentes. Por exemplo, se a format string contém três operadores, ela deve ser seguida exatamente por três parâmetros de mesmo tipo, na mesma ordem em que os especificados pelos operadores. Esse programa é bom, mas seria melhor se pudesse ler os valores em vez de usar uma constante. Edite o arquivo, remova a constante VALUE e declare uma variável como um integer global (mudando todas as referências em minúsculas porque o valor agora é uma variável). Coloque, então, as duas linhas seguintes no começo do programa:
printf(“Enter the value:”); scanf(“%d”,&value);
O código equivalente para isso em Pascal é:
write(‘Enter a value:’); readln(value);
Faça as mudanças, compile e rode o programa para ter certeza de que ele funciona. Note que scanf usa o mesmo tipo de format string que printf (digite man scanf para mais informações). Note também o sinal & na frente de value. Este é o address operator em C. Ele retorna o endereço da variável, mas isso não fará nenhum sentido até discutirmos pontei- ros. Você precisa usar o operador & em scanf independente da variável ser tipo char, int ou float. Se você esquecer o operador &, você receberá um erro quando rodar o programa.
As declarações e estruturas while em C baseiam-se nas idéias das expressões Booleanas, da mesma forma que em Pascal. Em C, entretanto, não existe o tipo Booleano: você usa integers no lugar. O integer value 0 em C é false, enquanto qualquer outro valor é true. Aqui temos uma tradução simples do Pascal para o C. Primeiro o código em Pascal:
if (x=y) and (j>k) then z:= else q:=10; A tradução para o C parece muito similar, mas há diferen- ças importantes, que nós vamos discutir em breve. if ((x==y) && (j>k)) z=1; else q=10;
Perceba que = em Pascal virou == em C. Esta é uma diferença importante, porque o C irá aceitar um = simples quando você compilar, mas irá se comportar de forma diferente quando você rodar o programa. O and em Pascal vira && em C. Perceba também que z=1; em C tem um ponto e vírgula, que C elimina o then, e que a expressão Booleana precisa ser completa- mente cercada por parênteses. O seguinte quadro mostra a tradução de operadores Booleanos do Pascal para o C:
O sinal == é um problema porque é comum esquecermos e digitarmos somente =. Como os integers substituem os Booleanos, a seguinte declaração é legal em C:
void main() { int a; printf(“Enter a number:”); scanf(“%d”, &a); if (a) { blah blah blah } }
se a for qualquer coisa diferente de 0, o código que blah blah blah representa será executado. Vejamos agora a seguinte declaração em Pascal:
if a=b then
incorretamente traduzida para C como:
if (a=b) /* teria que ser “if (a==b)” */
Em C, esta declaração significa “Atribua b para a e depois teste o valor Booleano de a”. Então, se a for 0, a declaração if é falsa; senão, ela será verdadeira. O valor de a também muda. Este não é um comportamento previsto (apesar desta característica ser importante quando usada corretamente), então seja cuidado- so com as conversões entre = e ==. As declarações while são bem fáceis de traduzir. Por exemplo, vejamos o seguin- te código em Pascal:
while a < b do begin blah blah blah end;
em C vira:
while (a < b) { blah blah blah }
C também possui uma estrutura “do- while” para substituir o “repeat-until” do Pascal como mostrada abaixo:
Os operadores em C são similares aos operadores em Pascal como mostrado abaixo:
O operador / faz uma divisão integer se ambos os operadores são integers e divide os pontos flutuantes. Por exemplo:
void main() { float a; a=10/3; printf(“%f\n”,a); }
Esse código imprime um valor de ponto flutuante a partir do momento em que a é declarado como um tipo flutuante, mas a será 3. porque o código fez uma divisão entre integers. A precedência do operador em C é também similar ao do Pascal. Como em Pascal, os parêntesescontrolamaprecedência.
O C permite que você faça conversões de
tipo facilmente. É possível fazer isso especialmente quando usando ponteiros. Typecasting também ocorre durante a operação de distribuição para certos tipos. Por exemplo, no código acima, o valor do integer foi automaticamente convertido para um ponto flutuante. Você faz typecasting em C ao colocar o nome do tipo entre parênteses e deixan- do-o em frente do valor que você quer mudar. Pois, no código acima, ao trocar a linha a=10/3; por a=(float)10/3; produza 3,333333 em a porque 10 é convertido para um valor de ponto flutuante antes da divisão.
Tipos Você declara tipos definidos pelo usuário em C com a declaração typedef. O seguinte exemplo mostra um tipo que aparece freqüentemente no código C:
#define TRUE 1 #define FALSE 0 typedef int boolean; void main() { boolean b; b=FALSE; blah blah blah }
Esse código permite que você declare tipos Booleanos em programas C. Se você não gosta da palavra “flutuante” para números reais, você pode dizer:
typedef float real;
and then later say:
real r1,r2,r3;
Você pode colocar declarações typedef em qualquer lugar num programa C, mas eles devem estar antes de serem usados no código. Não é necessário agrupá-los nem dê nenhuma palavra especial para marcar o início do bloco como em Pascal.
Você declara arrays ao inserir um tama- nho de array após uma declaração normal, como mostrado abaixo:
int a[10]; /* array of integers / char s[100]; / array of
characters (a C string) / float f[20]; / array of reals / struct rec r[50]; / array of records */
Incrementando Versão longa Curta versão i=i+1; i++; i=i-1; i—; i=i+3; i+=3; i=ij; i=j;
A maioria das linguagens permite a criação de procedures e funções, ou ambos. O C permite somente funções, apesar de ser possível criar procedures criando funções que não retornam nada. As funções em C podem aceitar um número ilimitado de parâmetros. Como mencionado na introdução, elas não podem ser aninhadas. Em geral, em C não importa em que ordem você coloca suas funções no programa. Nós já falamos um pouco sobre as funções. A função rand é muito simples. Ela não aceita nenhum parâmetro e retorna um integer como resultado:
int rand() /* from K&R - produces a random number between 0 and 32767.*/ { rand_seed = rand_seed * 1103515245 +12345; return (unsigned int)(rand_seed / 65536) % 32768; }
A linha int rand() declara a função rand para o resto do programa e especifica que rand não irá aceitar nenhum parâmetro e retornará um resultado integer. Esta função não tem nenhuma variável local, mas se ela necessitas- se de locais, elas teriam que estar logo após o { de abertura. (O C na verdade permite que você declare variáveis após qualquer {. Essas variáveis desaparecem assim que o } de fechamento seja atingido. Enquanto existi- rem, elas ficam guardadas no sistema).
div / mod %
Perceba que não existe nenhum ; após o () na primeira linha. Se você colocar algum acidentalmente, receberá de volta uma série grande de erros sem sentido. Note também que apesar de não ter nenhum parâmetro, você precisa usar o (), pois eles dirão ao compilador que você está declarando uma função em vez de simplesmente declarar um int. A declaração de return é importante para qualquer função que retorna um resulta- do. Ela dá à função o valor para retornar e causa uma saída imediatamente. Isso significa que você pode colocar múltiplas declarações de retorno na função para conseguir múltiplos pontos de saída. Se você não colocar uma declaração de return numa função, a função retornará quando ela chegar } e dará erro (muitos compiladores avisarão se você esquecer de retornar um valor). Em C, uma função pode retornar valores de qualquer tipo. Há diversos métodos corretos para se chamar uma função rand - por exemplo: x=rand();. O x é o valor retornado nesta declaração. Note que você precisa usar () na chamada da função, mesmo se nenhum parâmetro for passado. Você também pode chamar a função rand desta forma:
if (rand() > 100)
Ou desta maneira:
rand();
Em último caso, o valor retornado pelo rand será descartado. Talvez você nunca queira fazer isso com rand, mas muitas funções retornam algum tipo de código de erro através do nome da função, e se você não se preocupar com códigos de erros (por exemplo, porque você sabe que um erro é impossível), você pode descartar. Você pode criar procedures (na forma do Pascal) ao dar à função um tipo de retorno vazio. Por exemplo:
void print_header() { printf(“Program Number 1\n”); printf(“by Marshall Brain\n”); printf(“Version 1.0, released 12/26/91\n”); }
Esta função não retorna nenhum valor, então é um procedure. Você pode chamá- lo com a seguinte declaração:
print_header();
Você pode incluir () na chamada. Se você não fizer isso, a função não será chamada, apesar de ser compilado corretamente em vários sistemas. As funções em C podem aceitar parâmetros de qualquer tipo. Por exem- plo:
int fact(int i) { int j,k; j=1; for (k=2; k<=i; k++) j=j*k; return j; }
Retorna o fator de i, que é passado como um parâmetro integer. Separe múltiplos parâmetros com vírgulas:
int add (int i, int j) { return i+j; }
O C avançou muito nesses anos. Você verá freqüentemente funções escritas no “velho estilo” como as que mostramos abaixo:
int add(i,j) int i; int j; { return i+j; }
É importante que seja possível ler o código escrito no estilo antigo. Não há diferenças na sua execução; é somente uma forma diferente de se fazer a notação. É melhor usar o estilo novo, (conhecido como “ANSI C”) com o tipo declarado como parte da lista de parâmetro, a menos que você vá enviar este código para alguém que só tem acesso a compiladores antigos (non-ANSI). Agora é considerado correto usar os protótipos de função para todas as funções em seu programa. Um protótipo declara o nome da função, seu parâmetro e seu tipo de retorno para o resto do programa de uma maneira similar à declaração forward em Pascal. Para entender porque os protótipos de funções são úteis, entre com
o seguinte código e dê um run:
#include <stdio.h> void main() { printf(“%d\n”,add(3)); } int add(int i, int j) { return i+j; }
Este código compila sem lhe dar um aviso, apesar de add esperar dois parâmetros mas receber um só, porque o C não checa parâmetros. Você pode perder muito tempo debugando o código quando o problema é que você está simplesmente passando muitos ou poucos parâmetros. O código acima compila corretamente, mas ele produz uma resposta errada. Para resolver este problema, o C permite que você coloque protótipos de funções no começo (na verdade, em qualquer lugar) do progra- ma. Se você fizer isso, o C checa os tipos e counts de todas as listas de parâmetros. Tente compilar o seguinte código:
#include <stdio.h> int add (int,int); /* function prototype for add */ void main() { printf(“%d\n”,add(3)); } int add(int i, int j) { return i+j; }
O protótipo faz com que o compilador mostre um erro na declaração printf. Coloque um protótipo para cada função no começo do seu programa. Eles podem evitar bastante tempo de debugação e podem resolver o problema que você consegue, quando compila com funções que você usa antes de eles serem declara- dos. Por exemplo, o seguinte código não irá compilar:
#include <stdio.h> void main() { printf(“%d\n”,add(3)); } float add(int i, int j)
extern int rand(); extern void bubble_sort(int,int []);
Essas duas linhas lembram os protótipos de funções. A palavra “externa” em C representa funções que serão linkados depois. Num compilador antigo, remova os parâmetros da lista do bubble_sort. Digite o seguinte código num arquivo chamado util.c.
/* util.c / #include “util.h” int rand_seed=10; int rand() / from K&R - produces a random number between 0 and 32767.*/ { rand_seed = rand_seed * 1103515245 +12345; return (unsigned int)(rand_seed / 65536) % 32768; } void bubble_sort(int m,int a[]) { 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; } }
Note que o arquivo inclui seu próprio arquivo header (util.h) e que usa aspas em vez dos símbolos < e >, que são usados somente para bibliotecas de sistema. Como você pode ver, isso é normal no código C. Veja também que a variável rand_seed por não estar no arquivo header, não pode ser visto ou modificado por um programa usando esta biblioteca. Chamamos isto de ocultação de informação. Adicionando a palavra static na frente de int faz isso. Digite o seguinte programa e salve-o ocmo main.c.
#include <stdio.h> #include “util.h” #define MAX 10 int a[MAX]; void main()
int i,t,x,y; /* fill array / for (i=0; i < MAX; i++) { a[i]=rand(); printf(“%d\n”,a[i]); } bubble_sort(MAX,a); / print sorted array */ printf(“——————————\n”); for (i=0; i < MAX; i++) printf(“%d\n”,a[i]); }
Compilando e rodando uma Biblioteca Para compilar uma biblioteca, digite o seguinte comando (assumindo que você está usando um Unix):
cc -c -g util.c
O -c leva o compilador a produzir um arquivo de objeto para a biblioteca. O arquivo contém o código de máquina da biblioteca. Ele não pode ser executado até ser linkado a um arquivo de programa que contenha a função main. O código de máquina reside num arquivo separado chamado util.o. Para compilar o programa principal, digite o seguinte:
cc -c -g main.c
Essa linha cria um arquivo chamado main.o, que contém o código de máquina para o programa principal. Para criar um arquivo executável que contenha o código de máquina para o programa inteiro, faça o link de dois arquivos de objetos digitando o seguinte:
cc -o main main.o util.o
Linka main.o e util.o para formar um executável chamado main. Para rodá-lo, digite main. Pode ser extremamente incômodo digitar todas as linhas cc todas às vezes, especial- mente se você estiver fazendo diversas mudanças no código e ele tiver diversas bibliotecas. A facilidade make resolve este problema.Você pode usar o seguinte makefile para substituir a seqüência de compilação acima:
main: main.o util.o
cc -o main main.o util.o main.o: main.c util.h cc -c -g main.c util.o: util.c util.h cc -c -g util.c
Insira isso num arquivo chamado makefile e digite make para construir o executável. Note que você precisa preceder todas as linhas cc com um tab. (Oito espaços não são suficientes - é necessário usar tab). Este makefile contém dois tipos de linhas. As linhas alinhadas à esquerda são linhas de dependência. As linhas precedidas por um tab são executáveis, que podem conter qualquer comando Unix válido. Uma linha de dependência diz que algum arquivo é dependente de algum outro arquivo. Por exemplo, main.o: main.c util.h diz que o arquivo main.o é dependente dos arquivos main.c e util.h. Se ambos os arquivos mudarem, a seguinte linha executável deve ser executada para recriar main.o. Note que o executável final produzido pelo makefile inteiro é main, na linha 1 do makefile. O resultado final do makefile deveria ir sempre na primei- ra linha, que é onde o makefile diz que o arquivo main é dependente do main.o e util.o. É possível colocar linhas múltiplas para serem executadas abaixo de uma linha de dependência - todas elas devem começar com um tab. Um programa comprido pode ter várias bibliotecas e um programa main. O makefile recompila automatica- mente tudo que precisa ser recompilado por causa das mudanças.
Os arquivos texto em C são retos e fáceis de entender. Eles trabalham da mesma forma que os arquivos de texto em Pascal. Todas as funções e tipos dos arquivos de texto em C vêm da biblioteca stdio. Quando você precisa do texto I/O num programa C, precisa somente de uma fonte para incluir informação e um repositório para saída de informação. Você pode usar stdin (standard in) e stdout (standard out). Então, você usa o redirecionamento input e output na linha de comando para mover diferentes pedaços de informação através do programa. Há seis diferentes comandos I/O em <stdio.h>
que você pode usar com stdin e stdout:
printf imprime output formatada para stdout. scanf lê input formatado para stdin. puts imprime uma string para stdout. gets lê uma string de stdin. putc imprime um caracter para stdout. getc, getchar lê um caracter de stdin.
A vantagem de stdin e stdout é que eles são fáceis de usar. Por isso, a habilidade de redirecionar I/O é muito poderosa. Por exemplo, pode ser que você queira criar um programa que leia de stdin e conta o número de caracteres:
#include <stdio.h> #include <string.h> void main() { char s[1000]; int count=0; while (gets(s)) count += strlen(s); printf(“%d\n”,count); }
Entre com esse código e rode-o. Ele espera por umaentradadostdin,entãodigiteumaspoucas linhas.Quandoestiverpronto,pressioneCtrl-Dpara assinalarumend-of-file(eof).getslêumalinhaaté detectareof,entãoretornaa0deformaquea repetiçãowhiletermina.QuandovocêpressionaCtrl- D,vocêvêacontagemdonúmerodecaracteresem stdout(atela). Agora,suponhaquevocêqueiracontaros caracteresnumarquivo.Sevocêcompilouo programa para a.out, você pode digitar o seguinte: a.out < filename
Em vez de aceitar a entrada de dado do teclado, o conteúdo do arquivo nomeado filename será usado.Você pode conseguir o mesmo resultado usando pipes:
cat < filename | a.out
Você também pode redirecionar a saída para um arquivo:
a.out < filename > out
Esse comando coloca o contador de caracteres produzido pelo programa num arquivo de texto chamado out. Às vezes, você precisa usar um arquivo de texto diretamente. Por exemplo, você poderia precisar abrir um nome de arquivo específico e ler ou escrever. Você pode querer administrar diversos pedaços de entrada ou saída, ou criar um programa como um editor de texto que pode salvar e recupe- rar os dados ou os arquivos de configuração.
fopen abre um arquivo de texto fclose fecha um arquivo de texto feof detecta uma marca de fim de arquivo fprintf imprime uma saída formatada para um arquivo fscanf lê uma entrada formatada de um arquivo fputs imprime uma string para um arquivo fgets lê uma string de um arquivo fputc imprime um caractere para um arquivo fgetc lê um caractere de um arquivo
Você usa fopen como reset e rewrite em Pascal. Isso abre um arquivo para um módulo específico (os três mais comuns são r, w e a, para ler, escrever e adicionar). Ele então retorna um ponteiro de arquivo que você usa para acessar o arquivo. Por exemplo, suponha que você queira abrir um arquivo e escrever os números de 1 a 10 nele. Você poderia usar o seguinte código:
#include <stdio.h> #define MAX 10 void main() { FILE *f; int x; f=fopen(“out”,”w”); for(x=1; x<=MAX; x++) fprintf(f,”%d\n”,x); fclose(f); }
A declaração fopen aqui abre um arquivo chamado out com o módulo w. Este é um modo de escrita destrutivo, o que significa que se o out não existe, ele é criado, mas se ele já existe, é destruído e um novo arquivo é criado em seu lugar. O comando fopen retorna um ponteiro para o arquivo, que fica guardado na
variável f. Esta variável é usada para se referir ao arquivo. Se este arquivo não pode ser aberto por alguma razão, f irá conter NULL. Adeclaraçãofprintfdeveparecerbastantefamiliar. É igual a printf, mas usa um arquivo ponteiro comoseuprimeiroparâmetro.Adeclaraçãofclose fechaoarquivoquandovocêterminar. Para ler um arquivo, abra-o com o módulo r. Em geral, não é uma boa idéia usar fscanf para ler. A não ser que o arquivo esteja perfeitamente formatado, fscanf não irá tratá-lo corretamente. No lugar, use fgets para ler cada linha e faça o parse dos pedaços necessários. O seguinte código demonstra o processo de leitura de um arquivo e apresenta seu conteúdo para a tela:
#include <stdio.h> void main() { FILE *f; char s[1000]; f=fopen(“infile”,”r”); while (fgets(s,1000,f)!=NULL) printf(“%s”,s); fclose(f); }
A declaração fgets retorna um valor NULL na marca do fim do arquivo. Ele lê uma linha (até 1.000 caracteres neste caso) e então imprime para stdout. Perceba que a declaração printf não inclui \n no format string, porque fgets adiciona \n ao final de cada linha que lê. Por isso, você pode descobrir se uma linha não é completa quando ele ultrapassa o limite máximo de especificado no segundo parâmetro do fgets.
próxima linha após serem deletados
problema, senão você mesmo terá de fazer a checagem. Você não deve passar um valor NULL para qualquer função que não aceite isto especificamente. Cheque sempre a documentação. Outras violações que podem resultar em exceções serão tratadas na próxima seção.
Como evitar violações não previstas É preciso também parar as exceções que forçam o SO a matar nossa aplicação. Você pode colocar um try/catch em cada linha que começa uma função. Isso vai evitar que qualquer exceção mate sua aplicação, mas não é a melhor forma, porque não é possível documentar corretamente as exceções. Tudo que será possível dizer é “ocorreu algum erro desconhecido!”. Isso não é aceitável num aplicativo profissio- nal. Ao contrário, você deve pegar todas as exceções, assim que elas acontecem; isso irá permitir que você informe exatamente o que causou essa exceção. Bibliotecas externas que você usar “deveriam” ter suas exceções documentadas, mas obvia- mente você sabe que isso nem sempre funciona corretamente. Para levantar estes erros o mais rápido possível, siga essas regras de exceção:
Você segue todas as regras acima e agora o seu código só travará onde você esquecer de seguir as regras. Isso é muito bom, mas seu código ainda não faz exatamente o que
deveria. Antes, você podia, pelo menos, ver a explosão quando algo ia errado. Agora, isso não funciona mais. Isso porque você precisa tratar e informar todos os erros! Ignorar erros não fará com que eles desapareçam! Você verá um código como esse: try { DoSomeFunction(); // ignoring return code } catch(...) { // ignoring exception }
Quando esses erros são ignorados, você terá erros silenciosos. Aqui vou expor algumas das regras básicas de tratamento de erros:
Escrita por Christopher McGee
A linguagem C e suas implementações típicas são desenhadas para serem utilizadas com facilidade. A linguagem é sucinta e expressiva. Não foi desenhada para tratar os erros dos usuários. Normal- mente, os erros são “premiados” por um efeito que não se relaciona à causa do erro. Neste tutorial, nós iremos ver estes “prêmios” inesperados. Por serem inesperados, pode ser impossível classificá-los completamente. Apesar disso, nós vamos tentar. Assumimos que o leitor tem, ao menos, um bom conheci- mento em C.
A primeira parte de um compilador é geralmente chamada de lexical analyzer ou analisador léxico. Ele olha a seqüência de caracteres que formam o programa e os quebra em tokens (estruturas de retorno). Um token é uma seqüência de um ou mais caracteres que possuem um significado (relativamente) uniforme na linguagem a ser compilada. Em C, por exemplo, o toke -> tem um significado que é bem diferente dos caracteres que o formam e isso acontece independentemente do contexto em que -> aparece. Para outro exemplo, considere a declaração:
if (x > big) big = x;
Cada caractere nesta declaração é um token separado, exceto a palavra-chave if e os dois exemplos do identificado big. Na verdade, programas em C são quebra- dos em tokens duas vezes. Primeiro, o pré-processador lê o programa. Ele precisa “tokenize” o programa para poder encontrar os identificadores, pois alguns
podem representar macros. Depois, ele troca cada invocação de macros com o resultado da avaliação daquela macro. Finalmente, o resultado da troca da macro é realocada num stream de caracteres que é enviado ao compilador. O compilador quebra esta stream em tokens novamente.
Linguagens de programação derivadas do Algol, como Pascal e Ada, usam := para atribuição e = para comparação. O C, por outro lado, usa = por atribuição e == para comparação. Devido a atribuição ser mais freqüente que a comparação, então o uso mais freqüente ganha o símbolo menor. Além do mais, o C trata a atribuição como um operador, assim atribuições múltiplas (como a=b=c) podem ser escritas facil- mente e os atributos podem ser inseridos em expressões maiores. Essa conveniência causa um problema potencial: alguém pode escrever inadver- tidamente uma atribuição quando queria uma comparação. Assim, esta declaração, que parece estar checando se x é igual a y:
if (x = y) foo();
na verdade acaba setando x para o valor de y e depois checa se aquele valor é diferente de zero. Ou considere este loop (estrutura de repetição) que serve para pular vazios, tabs e novas linhas num arquivo:
while (c == ’ ’ || c = ’\t’ || c == ’\n’) c = getc (f);
O programador usou de forma errada o =
no lugar de == na comparação com o ’\t’. Esta “comparação” na verdade atribui ’\t’ a c e compara o (novo) valor de c a zero. Como ’\t’ não é zero, a “comparação” será sempre verdadeira e o loop irá comer o arquivo inteiro. O que ele faz dependerá do programa, se esta implementação particular permitir que o programa continue lendo, mesmo depois do eof (fim do arquivo). Se for assim, o loop rodará para sempre. Alguns compiladores C tentam ajudar o usuário dando avisos para as condições do formato e1 = e2. Para evitar estas mensa- gens destes compiladores, quando você quer atribuir um valor para uma variável e checar se a variável é zero, considere a possibilidade de fazer uma comparação explícita. Em outras palavras, em vez de usar:
if (x = y) foo(); escreva: if ((x = y) != 0) foo();
Isso permitirá que suas intenções fiquem claras.
É fácil deixar passar uma substituição inadvertida de = por == porque muitas linguagens usam = para comparações. Também é fácil trocar & e && ou | e ||, especialmente porque os operadores & e | em C são diferentes das suas contrapartes em algumas outras linguagens.
1.3 Tokens com multi-caracteres Alguns tokens em C, como /, * e =, são formados por apenas um caractere.
Lógica similar aplica-se para tipos de ponteiros e de funções. Por exemplo, float ff(); significa que a expressão ff () é um float e, portanto, que ff é uma função que retorna um float. Analogamente, float *pf; significa que *pf é um float e, portanto, que pf é um ponteiro para um float. Essas formas se combinam em declara- ções da mesma forma que fazem em expressões. Assim
float g(), (h)(); dizem que g() e (*h) (); são expressões de float. Desde que () é melhor que , g() significa a mesma coisa que (g()):g é uma função que retornar um ponteiro para um float, e h é um ponteiro para uma função que retorna um float. Quando nós sabemos como declarar uma variável para um tipo dado, é fácil escre- ver para aquele tipo: só remova o nome da variável e o ponto e vírgula da declara- ção e encapsular tudo entre parênteses. Assim, desde que float g(); declare g como uma função que retorna um ponteiro para um float, (float * ()) é isso. Armado com esse conhecimento, nós agora estamos preparados para atacar ((void()())0) (). Nós podemos analisar essa declaração em duas partes. Primeiro, suponha que nós temos uma variável fp que contém um ponteiro de função e nós queremos chamar a função para a qual fp aponta. Fazemos isso desta forma: (fp) (); Se fp é um ponteiro para uma função, fp é a própria função, assim (fp) () é a forma de invocá-la. Os parênteses em (fp) são essenciais porque a expressão seria, de outra forma, interpretada como (fp()). Nós, agora, reduzimos o problema para a busca de uma expressão para substituir fp. Este problema é a segunda parte da nossa análise. Se o C pudesse ler nossas mentes com relação aos tipos, nós poderíamos escrever: (0)(); Isso não funciona porque o operador * insiste em ter um apontador como seu operando. Além disso, o operando precisa ser um apontador para uma função cujo resultado de * possa ser chamado. Assim, nós precisamos cast 0 num tipo vagamen-
te descrito como “ponteiro para função retornando vazio.” Se fp é um ponteiro para uma função retornando vazio, então (fp) () é um valor vazio, e sua declaração seria assim: void (fp)(); Assim, nós poderíamos escrever: void (fp)(); pagando o preço de declarar uma variável tola. Mas, uma vez que nós sabemos como declarar a variável, nós sabemos como colocar uma constante para aquele tipo: só tire o nome da declaração da variável. Assim, nós podemos colocar 0 num “ponteiro para uma função retornando vazio” só escrevendo: (void()()) e nós podemos substituir fp por (void () ()) 0: ((void()())0)(); O ponto e vírgula no final transforma a expressão numa declaração. Quando resolvemos este problema, não existia nada parecido com uma declaração typedef. Com ela, podemos resolver o problema mais facilmente: typedef void (funcptr)(); (* (funcptr) 0)();
Suponha que a constante FLAG é uma integer com exatamente um bit transfor- mado na sua representação binária e você quer testar se a variável integer flags tem o bit on. O método usual para escrever isso seria: if (flags & FLAG) ... O significado disso é simples para a maioria dos programadores em C: uma declaração if testa se a expressão entre parênteses é 0 ou não. Pode ser legal fazer este teste mais explícito para documentação: if (flags & FLAG != 0) ... A declaração agora é mais fácil de enten- der. Também é errado, porque != liga mais que &, então a interpretação agora é: if (flags & (FLAG != 0)) ... Isso vai funcionar (por coincidência) se FLAG é 1 ou 0 (!), mas não para qualquer outro resultado. Suponha que você tenha duas variáveis integer, h e 1, cujos valores estão entre 0 e 15 inclusive, e você quer setar r para um valor de 8 bits cujos bits low-order são aqueles de 1 e os de high-order são
aqueles de h. O jeito natural disso ser escrito é: r = h<<4 + l; Infelizmente, isto está errado. A adição prevalece, então este exemplo seria equivalente a r = h << (4 + l); Aqui há dois modos de escrever de forma correta: r = (h << 4) + l; r = h << 4 | l; Uma forma de evitar esses problemas é colocar parênteses em tudo, mas expres- sões com muitos parênteses são difíceis de entender, assim provavelmente seria útil lembrar os níveis precedentes em C. Infelizmente, há quinze níveis, o que torna a memorização difícil. Pode ser fácil, entretanto, classificá-los em grupos. Os operadores com precedência são aqueles que não são verdadeiramente operadores: subscripting, chamadas de funções e seleção de estruturas. Logo depois, vêm os operadores unários. Eles têm a mais alta precedência em comparação com qualquer operador verdadeiro. Pois as chamadas de funções têm prefe- rência a operadores unários. Você precisa escrever (*p) () para chamar uma função que é apontada por p; *p() implica que p é uma função que retorna um ponteiro. Operadores unários são associados pela direita, assim *p++ é interpretado como (p++) e não como (p)++. Em seguida, temos os verdadeiros operadores binários. Os operadores aritméticos têm a mais alta precedência, depois os operadores shift, os relacionais, os lógicos, os de atribuição e, finalmente, os condicionais. As duas coisas mais importantes para guardar são:
nível abaixo dos outros operadores relacionais. Isto permite que vejamos, por exemplo, se a e b estão na mesma ordem que c e d na expressão a < b == c < d Dentro dos operadores lógicos, nenhum tem a mesma precedência. Os operadores bitwise têm precedência sobre os seqüenciais, cada operador and tem precedência sobre o operador or e o operador bitwise exclusiver or (^) fica entre o bitwise and e o bitwise or. Os operadores condicionais ternários estão abaixo de todos os que menciona- mos acima. Isso permite que a expressão de seleção contenha combinações lógicas de operadores relacionais, como em z = a < b && b < c? d : e Este exemplo também mostra que faz sentido que as atribuições tenham uma precedência menor que os operadores condicionais. Além do mais, todos os operadores de atribuição compostos possuem a mesma precedência e todos se agrupam da direita para a esquerda, assim a = b = c significa o mesmo que b = c; a = b; No último degrau está o operador vírgula. É fácil lembrar disso, porque a vírgula é normalmente usada como um substituto para o ponto e vírgula quando se necessita de uma expressão no lugar de uma declaração. A atribuição é outro operador que freqüentemente envolve em misturas sobre as precedências. Considere, por exemplo, o seguinte loop, cujo objetivo é copiar um arquivo para o outro: while (c=getc(in) != EOF) putc(c,out); O jeito que a expressão na declaração while está escrita faz com que ela pareça como se para c fosse atribuído o valor de getc(in) e depois comparado com EOF para terminar com o loop. Infelizmente, a atribuição tem uma precedência menor que qualquer operador de comparação, então o valor de c será o resultado da comparação de getc(in), cujo valor será descartado e EOF. Assim, a “cópia” do arquivo consistirá numa stream de bytes cujos valores serão 1. Não é muito difícil ver que o exemplo acima deveria ter sido escrito assim: while ((c=getc(in)) != EOF)
putc(c,out); Entretanto, erros deste tipo podem ser difíceis de perceber em expressões mais complicadas. Por exemplo, diversas versões do programa lint distribuído com o sistema Unix possuem a seguinte linha errada: if( (t=BT YPE(pt1->aty)==ST RT Y ) || t==UNIONT Y ){ O objetivo era atribuir um valor para t e ver se o t é igual a ST RT Y ou UNIONT Y. O verdadeiro efeito é bem diferente. A precedência dos operadores lógicos de C existem por uma razão histórica. B, o antecessor de C, tinha operadores lógicos que correspondiam por cima com os operadores & e | do C. Apesar de eles estarem definidos para agir sobre o bits, o compilador os tratava como && e || se estivessem num contexto condicional.
Um ponto e vírgula extra num programa em C normalmente não faz muita dife- rença: tanto pode ser uma declaração nula, que não tem efeito, como levar a uma mensagem de diagnóstico do compilador, que facilita a retirada do ponto e vírgula. Uma exceção importante é quando ela aparece depois de uma cláusula if e while, que deve ser seguida por uma declaração. Considere este exemplo:
if (x[i] > big); big = x[i];
O ponto e virgula da primeira linha não atrapalhará o compilador, mas esse fragmento de programa significa alguma coisa bem diferente de:
if (x[i] > big) big = x[i]; A primeira é equivalente a: if (x[i] > big) { } big = x[i]; que, obviamente, é equivalente a: big = x[i];
(a não ser que x, i, ou big é um macro com efeitos colaterais). Outro lugar que um ponto e vírgula pode fazer grande diferença é no final de uma declaração logo antes da definição de função. Considere o seguinte fragmento:
struct foo { int x; } f() {
... }
Está faltando um ponto e vírgula entre o primeiro } e o f que vem logo depois. O resultado disso é declarar que a função f retorna um struct foo, que é definido como parte desta declaração. Se o ponto e vírgula estivesse presente, f seria definido por default como retornando um integer.
O C é diferente nos casos em que a declaração switch pode passar para alguma outra. Considere, por exemplo, o seguinte fragmento de programa em C e Pascal:
switch (color) { case 1: printf (“red”); break; case 2: printf (“yellow”); break; case 3: printf (“blue”); break; } case color of 1: write (’red’); 2: write (’yellow’); 3: write (’blue’) end
Ambos os fragmentos de programas fazem a mesma coisa: imprimem red, yellow ou blue (sem iniciar uma nova linha), dependendo se a variável color é 1, 2 ou 3. Os fragmentos do programas são exatamente análogos, com uma exceção: o programa em Pascal não tem nenhuma das partes que correspondem à declara- ção break em C. A razão para isso é que os case labels em C se comportam como verdadeiros labels: o controle pode fluir desimpedido através dos case labels. Olhando de outra forma, suponha que o fragmento em C parecesse mais como o fragmento em Pascal:
switch (color) { case 1: printf (“red”); case 2: printf (“yellow”); case 3: printf (“blue”);
acontece com muita freqüência. Um arquivo de um programa conterá uma declaração como: char filename[] = “/etc/passwd”; e a outra conterá esta declaração: char *filename; Apesar de arrays e ponteiros comporta- rem-se de forma similar em alguns contextos, eles não são a mesma coisa. Na primeira declaração, filename é o nome de uma array de caracteres. Apesar de que o uso do nome irá gerar um pontei- ro para o primeiro elemento daquele array, este ponteiro é gerado quando necessário e não é mantido quando necessário. Na segunda declaração, filename é o nome de um ponteiro. Este ponteiro aponta para onde o programa-
dor quiser. Se o programador não der um valor, ele terá um valor zero (null) por default. As duas declarações de filename são guardadas de forma diferente; elas não podem coexistir. Uma forma de evitar erros desta natureza é usar uma ferra- menta como lint se estiver disponível. Para conseguir checar estes erros entre partes de um programa compiladas separadamente, é preciso ter algum programa que possa ver todas as partes ao mesmo tempo. O compilador típico não faz isso, mas o lint faz. Outra forma de evitar esses problemas é colocar declarações externas nos arquivos include. Desta forma, o tipo de um objeto externo só aparece uma vez.
Escrita por: Andrew Koening
ACESSO NATIVO AO
KAZAA COM LINUX
Mesmo com outras opções disponiveis, a rede do Kazaa (FastTrack) concerteza é a mais popular, chegando a reunir milhões de pessoas para troca de todo tipo de arquivo pela rede. Antigamente os usuários linux tinham um pequeno programa distribuido pelo site do Kazaa que permitia o ingresso a rede, misterio- samente esse programa foi extinto e aos usuários do linux coube apenas a opção de rodar o cliente do Kazaa pelo wine.
Agora não é preciso mais emular o cliente Kazaa do windows, pois temos o giFT que nos dá acesso as redes OpenFT, Gnutella e FastTrack (com o plugin giFT- FastTrack).
Primeiro instale o giFT, giFT-FastTrack e giFTcurs (todos na categoria Linux do CD desta Geek) no diretório /usr/local/src :
root@slack:/usr/local/src# tar xvjf giFT-0.11.1.tar.bz root@slack:/usr/local/src# cd giFT-0.11. root@slack:/usr/local/src/giFT- 0.11.1# ./configure
No final o ./configure vai te mostrar as opções que o programa vai ser compilado, veja se o suporte a ao imagemagick esta ativo
giFT 0.11.
—————————— core —-
libgift.................: yes gift daemon.............: yes use ltdl................: yes
——————— protocols —-
OpenFT..................: yes Gnutella................: yes
——————— extensions —-
use zlib................: yes use perl................: no (support temporarily deprecated) use libdb...............: yes
———— meta data tools —-
use libvorbis...........: yes use ImageMagick.........: yes use libmagic............: no
——————————————-
Note que no meu sistema ele esta ativo, se no seu sistema estiver também execute esse comando antes de dar o make
root@slack:/usr/local/src/giFT- 0.11.1# ln -s /usr/X11R6/ include/magick/ /usr/include/ root@slack:/usr/local/src/giFT- 0.11.1# make root@slack:/usr/local/src/giFT- 0.11.1# make install
root@slack:/usr/local/src/giFT-0.11.1# cd /usr/local/src/ root@slack:/usr/local/src# tar xvzf giFT- FastTrack-0.8.2.tar.gz root@slack:/usr/local/src# cd giFT- FastTrack-0.8. root@slack:/usr/local/src/giFT-FastTrack- 0.8.2# export PKG_CONFIG_PAT H=”/ usr/local/lib/ pkgconfig:$PKG_CONFIG_PAT H” root@slack:/usr/local/src/giFT-FastTrack- 0.8.2# ./configure root@slack:/usr/local/src/giFT-FastTrack- 0.8.2# make root@slack:/usr/local/src/giFT-FastTrack- 0.8.2# make install
root@slack:/usr/local/src/giFT-FastTrack- 0.8.2# cd /usr/local/src/ root@slack:/usr/local/src# tar xvzf giFTcurs-0.6.0.tar.gz root@slack:/usr/local/src# cd giFTcurs- 0.6. root@slack:/usr/local/src/giFTcurs-0.6.0# ./configure root@slack:/usr/local/src/giFTcurs-0.6.0# make root@slack:/usr/local/src/giFTcurs-0.6.0# make install
Agora é necessário que você crie os arquivos de configuração para o giFT, isso é feito com o comando giFT-setup, note que não é preciso ser superusuário (root) para que o giFT funcione. Eu recomendo fortemente usar um usuário normal para usar o giFT.
VERME@SLACK:~$ GIFT-SETUP
Agora vão ser feitas uma série de pergun- tas, você pode dar enter a cada uma delas para usar os valores padrões
Terminado tudo você vai ter um diretório /home/user/.giFT/ onde vai ficar a configuração recem criada
verme@slack:~$ cd .giFT/ verme@slack:~/.giFT$ ls gift.conf OpenFT/ ui/
Agora basta editar o arquivo gift.conf e alterar as seguintes linhas
———————————————————————————- setup = 0
Para
setup = 1 —-—————————————————————————————