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.txt
com “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
fgetc
retorna 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
fscanf
crachou 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
.0
apó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 aceitachar
porque é 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.