Bibliotecas Úteis
Até agora só usamos duas bibliotecas em nossos porgramas em C, e não vimos nem 10% dessas, então para deixar seu conhecimento mais completo aqui vamos mostrar algumas funções dessas duas bibliotecas, além de outras que também podem ser bem interessantes.
stdio.h
#include <stdio.h>
Como já vimos as funções scanf, printf, putchar, puts, getchar, gets, fprintf e fgets, iremos ignorá-las.
O std significa exclusivamente “standard” (”padrão” em português), i é de “input” (entrada) e o o de “output” (saída), portanto entrada e saída de dados padrão.
I/O em arquivos
Como o stdio serve para entrada e saída de dados, óbviamente também é usada para manipulação de arquivos, usada tanto para ler (input), quanto para escrever (output) neles.
Para ler um arquivo precisamos criar um ponteiro do tipo FILE
FILE * arquivo;
Leitura
crie um arquivo
j.txtcom “joao” escrito dentro
Para abrir o arquivo j.txt no nosso programa, é só usar a função fopen.
arquivo = fopen("j.txt", "r");
O
"r"no segundo parametro é o modo desse arquivo, nesse caso, abrimos um arquivo em modo leitura.
Para ler e exibir o que foi lido é só usar o fgetc:
char caractere = fgetc(arquivo);
O
fgetcretorna um caractere de cada vez, e quando le o caractere, quando você for ler de novo ele lerá somente o próximo, exemplo:
// suponha que o arquivo "texto.txt" tem "abc" escrito dentro
FILE *f = fopen("texto.txt", "r");
char a, b, c;
b = fgetc(f); // -> "a"
c = fgetc(f); // -> "b"
a = fgetc(f); // -> "c"
print("%c %c %c\n", a, b, c);
Saída:
c a b
Lembre-se que o fim de um arquivo é demarcado por uma constante chamada de EOF (significa “end of file” ou “fim do arquivo”), logo, se voce usar um loop, para ler o tal arquivo, usem o EOF como “flag”
“flag” é a condição de interrupção
char caractere;
do {
caractere = fgetc(arquivo); // -> <caractere> = fgetc(<arquivo>)
putchar(caractere);
} while (caractere != EOF);
E depois de terminar de usar, assim como você tem que liberar a memória com o free trabalhando com ponteiros, você tem que fechar o arquivo, ou ele vai ficar ocupando memória à toa
fclose(arquivo);
E ficaria assim:
FILE * arquivo;
arquivo = fopen("j.txt", "r");
char caractere;
do {
caractere = fgetc(arquivo);
putchar(caractere);
} while (caractere != EOF);
fclose(arquivo);
Na minha humilde opnião é muito melhor ler os dados caractere por caractere porque assim se tem mais controle dos dados, mas existem outras funções que auxiliam nisso.
O fscanf lê dados do arquivo e joga na variável estipulada:
FILE *arquivo = fopen("texto.txt", "r");
char * texto_do_arquivo = malloc(10);
fscanf(arquivo, "%s", texto_do_arquivo);
printf ("%s", texto_do_arquivo);
fclose(arquivo);
free(texto_do_arquivo);
Não usei a forma de array aqui porque aparentemente o
fscanfcrachou comigo, mas sinta-se a vontade para testar se isso acontece com você também...
O fscanf vai ler até o primeiro espaço ou a primeira quebra de linha (\n).
Também dá pra fazer com o já conhecido fgets:
FILE *f = fopen("f.txt", "r");
char str [100];
fgets (f, 100, str); // -> fgets ( <arquivo>, <tamanho da string>, <string> )
fclose(f);
E por fim com o fread, que é uma forma mais direta de ler os dados, mas para usar o fread você tem que saber o limite da leitura ( que no nosso caso é o fim do arquivo), e para descobrir isso, nós vamos usar duas funções chamadas fseek e ftell.
fseek(arquivo, 0, SEEK_END); // mudando o "cursor" para o fim do arquivo
size_t tamanho_arquivo = ftell(arquivo); // pegando a posição do cursor
fseek(arquivo, 0, SEEK_SET); // colocando o cursor no inicio de novo
Feito isso é só ler usando o fread
fread( texto, sizeof (char), tamanho_arquivo, arquivo );
// ( <ponteiro>, <tmh do tipo do ptr>, <limite da leitura em bytes>, <arquivo> )
E vai ficar assim:
FILE *arquivo = fopen("f.txt", "r");
fseek(arquivo, 0, SEEK_END); // mudando o "cursor" para o fim do arquivo
size_t tamanho_arquivo = ftell(arquivo); // pegando a posição do cursor
fseek(arquivo, 0, SEEK_SET); // colocando o cursor no inicio de novo
char * texto = malloc( tamanho_arquivo );
fread( texto, sizeof (char), tamanho_arquivo, arquivo );
free(texto);
fclose(arquivo);
O conteúdo do arquivo vai ser escrito na variável
texto
Escrita
Para abrir um arquivo em modo escrita ao invés de colocar o r no parametro do fopen, colocamos um w:
FILE *arquivo = fopen("texto.txt", "w");
Nesse caso, se o arquivo não existir, ele será criado, mas se existir um arquivo ele perderá todos os seus dados.
Para escrever um char em um arquivo usamos a função fputc
fputc('a', arquivo); // -> fputc( <char>, <arquivo> )
Para escrever uma string use o fputs
fputs("string com coisas", arquivo); // -> fputs(<string>, <arquivo>)
Ou se quiser escrever um dados formatado use o já estudado fprintf
fprintf ( arquivo, "%i > %i = %s", 4, 3, (4 > 3? "True": "False"));
E por fim você pode usar o irmão do fread, o fwrite:
char texto [] = "texto aleatorio para colocar no arquivo";
fwrite( texto, sizeof (char), sizeof(texto), arquivo );
Mas esses não são os únicos modos de abertura de um arquivo
"r" // -> read: somente leitura
"w" // -> write: somente escrita, mas apaga o conteúdo do arquivo antes de escrever
"a" // -> append: somente escrita
"r+" // -> read/write: leitura e escrita
"w+" // -> read/write: leitura e escrita, mas apaga o conteúdo do arquivo antes de escrever
"a+" // -> read/append: leitura e escrita
Faça seus testes com cada um deles, para ver funcionando na prática
Você já deve ter percebido que as mesmas funções que usamos em arquivos, são usadas na stdout, stdin e stderr, não é mesmo?
Isuuso acontece, porque essas 3 variáveis são arquivos, e por esse motivo você pode usar todas as funções usadas em arquivos colocando elas no lugar, mas lembre-se, o stdout e o stderr estão em modo "w", enquanto o stdin está em modo "r", portanto, você só pode escrever no stdout e no stderr, e só pode ler o stdin. Divirta-se!
Posicionamento em arquivos
Para “finalizar” este assunto, existem algumas funções que podem ser úteis na manipulação de arquivos, como o fseek (que já foi visto de maneira superficial)
Não vamos finalizar totalmente porque ainda faltam algumas funções, que agente vai ver no capítulo de
stdarg.h
FILE *j = fopen("j.txt", "r");
fseek( j, 0, SEEK_SET); // passa o cursor para o inicio do arquivo
fseek( j, 0, SEEK_CUR); /* passa o cursor para a posição atual do ponteiro
se ja tiver lido 3 caracteres, o cursor volta para
o caractere 3
*/
fseek( j, 0, SEEK_END);
Caso queira retornar para o inicio do arquivo, você pode usar a versão simplificada do fseek que se chama rewind
FILE * arquivo = open("j.txt", "r");
rewind( arquivo );
A função ftell, também já vista retorna a posição atual do cursor
FILE *j = fopen("j.txt", "r");
char c;
while ((c = fgetc(j)) != 'a')
putchar(c);
printf("\n%li\n", ftell(j));
Mas caso você precise de mais controle nesse posicionamento é só usar as funções fgetpos e fsetpos
// j.txt -> "abcdefghijklmnop"
FILE * arquivo = fopen("j.txt", "r+");
fpos_t posicao; // tem que ser deste tipo para funcionar
fgetpos(arquivo, &posicao); // pegando a posição
printf("posicao: %p\ncaractere: %c", &posicao, fgetc(arquivo));
fseek( arquivo, 0, SEEK_SET );
fsetpos(arquivo, &posicao + 4); // mudando posição para o 4 caractere
fgetpos(arquivo, &posicao); // pegando a posição de novo
getchar();
printf("posicao: %p\ncaractere: %c\n", &posicao, fgetc(arquivo));
fclose ( arquivo );
getchar();
Saída:
posicao: 0x7ffddf294270
caractere: a
posicao: 0x7ffddf294270
caractere: e
Operações com arquivos
Para apagar o arquivo é só usar a função remove
remove("j.txt");
E para renomear é só usar rename
rename( "j.txt", "joao.txt");
// ( <nome antigo>, <nome novo>)
A função reopen é muito útil para mudar o destino de arquivos, exemplo:
freopen("j.txt", "w", stdout);
fprintf("joao é uma pessoa!!\n", stdout); // o resultado não será impresso na tela, mas no arquivo "j.txt"
Além de todas essas, lembra de quando imprimimos mensagens na saída de erro (stderr) com fprintf? na stdio.h existe uma que faz isso automaticamente; é o perror
perror("ferrou!!");
Para outras informações sobre a biblioteca veja a referência que está no wikibooks sobre ela.
stdlib.h
#include <stdlib.h>
A stdlib.h é com certeza uma das bibliotecas mais importantes do C, portanto, merece ser dicecada aqui.
E as funções que já vimos dela foram as de gerenciamento de memória (malloc, free, realloc), logo, não iremos revê-las.
Conversões entre string e outros tipos
double d = atof ("8.9"); // atof(<valor>): de string para double
int i = atoi ("89"); // atoi(<valor>): de string para inteiro
long l = atol ("999"); // atol(<valor>): de string para long
long long ll = atoll ("99"); // atoll(<valor>): de string para long long
Sistema
Caso queira abortar o programa, você pode usar a função exit, e assim como no return do main, você escolhe o valor que quer retornar para o SO
int i;
scanf("%i", &i);
if (i%2)
exit(0); // se for impar saia
else
exit(1); // senao saia e retorne um erro
Outra função relacionada ao fechamento do programa é a função atexit, que registra funções que serão executadas quando o programa finalizar, sendo que estas funções não podem retornar valores e nem receber parametros.
#include <stdio.h>
#include <stdlib.h>
void tmp_file_remove (void){
remove("/tmp/at_exit_lock");
}
int main(){
FILE * tmp = fopen("/tmp/at_exit_lock", "w");
atexit(tmp_file_remove);
// pausando a execução
puts("não click em enter ainda... olhe se há um arquivo \"at_exit_lock\" na pasta /tmp/");
getchar();
puts("agora veja se ainda está lá");
fclose (tmp);
return 0;
}
Outra semelhante à atexit é a at_quick_exit, que vai ser executada quando o programa for interromido usando a função quick_exit
#include <stdio.h>
#include <stdlib.h>
void tmp_file_remove (void){
remove("/tmp/at_exit_lock");
}
int main(){
FILE * tmp = fopen("/tmp/at_exit_lock", "w");
atexit(tmp_file_remove);
// pausando a execução
puts("não click em enter ainda... olhe se há um arquivo \"at_exit_lock\" na pasta /tmp/");
getchar();
fclose (tmp);
quick_exit(0);
// essa parte não vai executar
puts("agora veja se ainda está lá");
return 0;
}
Outra variável de sistema muito útil é a getenv, que retorna o valor de uma variável de ambiente.
char path = getenv("PATH"); // caminhos para executáveis no linux ($PATH)
E as mais úteis de todas, com essas você vai conseguir executar comandos do sistema operacional
system( "echo hello mundo!" ); // system( <comando> )
Mas a system executa e manda o resultado para a stdout, se você quiser acessar o valor de retorno, tem que usar a função popen (que retorna um stream, logo, você vai ter que tratá-la como um arquivo)
A função popen não funciona no C99, se seu compilador usa C99, não irá compilar.
FILE *response = popen("echo hello mundo!", "r"); // popen( <comando> )
char comando [20];
fgets(comando, 20, response);
printf("o a resposta do comando usado foi:\n%s\n", comando);
Ainda faltam algumas funções mas essas são as mais importantes (contando com as de alocamanto de memória), para outras informações sobre a biblioteca, consulte a referência feita por alguém na wikipedia.
math.h
#include <math.h>
Com certeza toda linguagem que se presa tem uma biblioteca de matemática, a math.h tem diversas funções para resolução de problemas matemáticos desde arredondamento até trigonométricos.
Funções de arredondamento
Digamos que o valor de uma operação dê 1.7, se quisermos arredondá-lo para cima usamos a função ceil:
printf("%f\n", ceil(1.7));
Mas se quisermos arredondá-lo para um número menor usamos a função floor:
printf("%f\n", floor(1.7));
E se quiser apenas cortar a parte decimal use o trunc:
printf("%f\n", trunk(1.7));
Outra opção é arredondar para o número inteiro mais próximo, seja ele acima ou abaixo:
printf("%f\n", round(1.7));
A função round tem algumas variações como o lround que arredonda para um long int e o llround que arredonda para um long long int.
Potencia e radiciação
Para realizar uma potenciação é só usar a função pow
printf("40 ao quadrado é %.0f", pow(40, 2));
E caso queira fazer uma raiz quadrada é só usar a função sqrt
float n = pow(40, 2);
printf("a raiz quadrada de %.0f é %.0f", n, sqrt(n) );
E raiz cúbica é cbrt
float n = pow(40, 3);
printf("a raiz quadrada de %.0f é %.0f", n, cbrt(n) );
E caso você queira fazer uma raiz de índice 5, 4 ou qualquer outro número, lemre-se que uma radiciação é apenas uma potencia elevada à um expoente “ao contrário”:
2 normal é igual a 2/1, 2 ao contrário é igual a 1/2
// vou usar o expoente 2 mas funciona com qualquer valor
int numero = pow(5, 2); // 25
int outro_numero = pow(numero, 1.0/2.0); // 5
printf("5² = %i\n√25 = %i\n", numero, outro_numero);
E não esqueça dos
.0após o número se você não fizer isso o valor do expoente vai ser um inteiro e portanto, será0, lunca se esqueça de checar os tipos primitivos...
E existe uma rotina exclusiva para cálculo de hipotenusa:
int cateto_oposto = 8, cateto_adjacente = 6;
int hipotenusa = hypot( cateto_oposto, cateto_adjacente );
A biblioteca de matemática tem diversas outras funções, logo, caso necessite fazer algoritmos matemáticos consulte a referência da math.h feita pela UFRGS
stdarg.h
#include <stdarg.h>
A stdarg.h é uma biblioteca para tratamento de argumentos (ou parametros) de funções.
Até aqui você deve está se perguntando, “como fazer funções como o printf ou o scanf que recebem um número indeterminado de argumentos?”, exatamente usando esta biblioteca, mas preste atenção para entender como você pode usá-la em seus algorítmos.
Para essa biblioteca, vou explicar de uma maneira diferente, aqui nós vamos criar o print, que assim como o printf, irá esquever coisas na tela.
Como vai ser a chamada do
// print ( <formato>, <dados> );
print( "isfsf", 90, " + ", 8.3, " = ", 90.0 + 8.3 );
// i -> %i/%d/%li
// s -> %s
// f -> %f/%lf
Na declaração da função tem que ter pelo menos 1 argumento fixo, e no nosso caso é o formato, todos os outros argumentos serão substituídos por um ...
void print( char * formato, ... );
Para acessar os dados no ... nós primeiro temos que guardar eles em uma variável do tipo va_list
void print( char * formato, ... ){
va_list argumentos;
}
Esse va_list é um ponteiro com todos os argumentos, mas para pegarmos os certos temos que dizer para ele de onde começar a pesquisar usando o va_start
void print( char * formato, ... ){
va_list argumentos;
va_start( argumentos, formato );
}
Agora iremos checar quantos dados estamos esperando, e depois pegar-los com a função va_arg
Caso for usar valores em
char, na hora de usar ova_arg, usem comint, ele não aceitacharporque é muito pequeno.
#include <string.h> // -> strlen
void print( char * formato, ... ){
va_list argumentos;
va_start( argumentos, formato );
int argc = strlen(formato); // pegando a qntd de caracteres da string
for (int i = 0; i<argc; i ++){
if (formato[ i ] == 'i') // caso o dado esperado for um int
printf( "%li", va_arg( argumentos, long int ) );
// va_arg( <lista de args>, <tipo> ) );
if (formato[ i ] == 'f') // float
printf( "%lf", va_arg( argumentos, double ) );
if (formato[ i ] == 's') // string
printf(va_arg( argumentos, char *));
}
putchar('\n');
va_end( argumentos ); // fechando os argumentos
}
O
va_arg, assim como ofgetf, retorna o dado e passa para o próximo automaticamente.
E fim, essas são as únicas funções que existem nessa biblioteca. Mas como eu prometi no capítulo sobre stdio.h agora eu irei explicar sobre as funções que usam o va_list da stdarg.h
As funções do stdio.h que usam va_list fazem o mesmo que as outras, só que aceitam esse tipo de argumento, como o vprintf
void escreva_numeros ( int qntd, ... ){
va_list args;
va_start( args, qntd );
char * formato = malloc( qnt*3+1 );
for (int i=0; i<qntd; i+=2){
formato[i] = '%';
formato[i+1] = 'i';
formato[i+2] = 'i';
}
formato[ qnt*3 ] = '\n';
vprintf(formato, args);
va_end( args );
free(formato);
}
E funciona da mesma maneira com as funções vscanf (scanf), vsscanf (sscanf), vfscanf (fscanf) ...
string.h
#include <string.h>
Esta é mais uma das bibliotecas que eu já falei, mas não me aprofundei, portanto irei ignorar as funções já mencionadas (strlen, strcpy).
A primeira função interessante é a strncpy, que ao invés de copiar a string inteira, copia apenas um número de caracteres
char str[10];
strncpy(str, "joao e maria", 4); // copia até o 4 caractere
str[4] = '\0'; // setando o fim da string
puts(str);
Saída:
joao
Outra que também é bacana é a strcat, que serve para concatenar strings
char str[] = "joao";
strcat(str, " e maria"); // strcat( <destino>, <destinatario> );
E existe a variação strncat, que concatena até um certo número de caracteres
char str[] = "joao";
strncat(str, " e maria rosa", 8);
Uma função muito útil dessa biblioteca é a strcmp que compara duas strings
char str [] = "joao", str2 [] = "maria";
int res = strcmp( str, str2 ); // strcmp( <str>, <str2> )
if ( res == 0 )
puts("as strings são iguais");
else if ( res < 0 )
puts("\"%s\" é menor que \"%s\"", str, str2);
else if ( res > 0 )
puts("\"%s\" é maior que \"%s\"", str, str2);
E também existe a strncmp que funciona da mesma forma que a anterior, mas compara só até um certo caractere.
char str [] = "joao", str2 [] = "joao e maria";
int res = strncmp( str, str2, 4 ); // strncmp( <str>, <str2>, <numero> )
if (res == 0)
puts("os primeiros 4 caracteres da string 2 são iguais aos da string 1");
Outras opções são usar funções de pesquisa em strings, como o strchr que irá retornar um ponteiro para a primeira ocorrencia de um caractere
char j[] = "abcdefghijklmnop;joao\0";
puts(strchr(j, 'g'));
puts(strchr(j, ';')+1);
char * f = strchr(j, ';');
* f = '\0'; /* finalizando a string
na primeira ocorrencia
de ";" */
puts(j);
Saída:
ghijklmnop;joao
joao
abcdefghijklmnop
Outra bem bacana é a strcspn onde você passa uma certa lista de caracteres e ela irá retornar a primeira ocorrencia de qualquer caractere da lista
char str[] = "bcdefgh";
printf("A vogal \"%c\" está na %iª posição de \"%s\"\n",
str[ strcspn(str, "aeiou") ], strcspn(str, "aeiou")+1, str);
Saída:
A vogal "e" está na 4ª posição de "bcdefgh"
Uma semelhante a strchr é a strstr, que retorna a string da primeira ocorrencia de um caractere até o seu fim
char str[] = "joao maria ronaldo";
puts(strstr(str, "maria"));
ctype.h
#include <ctype.h>
Esta biblioteca possui funções para reconhecimento de tipos de caractere (char), e carrega diversas funções para esse reconhecimento.
isnum ('2'); // se é numerico
isalpha ('s'); // se é alfabético
isblank ('\t'); // se é vazio
iscntrl ('\n'); // se é caractere especial
isdigit ('4'); // se é numero decimal
isgraph ('!'); // se tem representação gráfica
isprint ('2'); // se dá para escrever na tela
ispunct ('.'); // se é pontuação
isspace ('\v'); // se é um espaço branco
isxdigit ('0'); // se é hexadecimal
islower ('a'); // se é letra minúscula
isupper ('A'); // se é letra maiúscula
tolower ('A'); // transforma em letra minúscula
toupper ('a'); // transforma em letra maiúscula
É necessário que eu esclareça alguns pontos, caracteres como ou ç são grandes demais para caber em um char, caso precise desses caracteres unicode, use strings para representá-los.
conio.h
#include <conio.h>
Esta é uma biblioteca exclusiva para sistemas baseados em MS-DOS, como o windows, ou o reactOS, e serve para a entrada e saída de dados com o console.
A função mais interessante é sem dúvidas a getch, que basicamente espera um caractere.
char c;
printf("digite um caractere");
c = getch();
Ele não espera um “enter”, ele apenas recebe o caractere e pronto.
Outra forma legal de usar esta função é pra controlar a entrada do usuário, por exemplo, quero ler um texto eternamente e parar quando a pessoa clicar na tecla esc.
#include <conio.h>
#include <stdio.h>
int main(){
puts("digite coisas aqui:");
char c, *str = malloc(1);
int i = 0;
while ((c = getch()) != 27){ /* enquanto o caractere lido
for diferente da
tecla <ESC>
*/
putchar(c);
str = realloc(str, sizeof str + sizeof c);
str[i++] = c;
}
printf("\n%s\n", str);
free(str);
return 0;
}
Bom, basicamente é isso, todas as outras funções são apenas alternativas a já existentes como o cgets que funciona igual a gets do stdio.h, ou a cprintf... enfim.
Considerações finais
Espero que este livro tenha ajudado você, este não é o fim definitivo, os seus estudos não devem acabar aqui, então tenha paciência e antes de ir embora vou da um bônus para você se animar e começar seus projetos em C.