Há linguagens de programação onde as funcionalidades específicas para manipular strings, ficheiros, etc. estão disponíveis ao nível da própria linguagem e não em bibliotecas externas. São os casos das linguagens: Fortran, Cobol e Pascal, por exemplo.
Pelo contrário, noutra linguagens essas funcionalidades estão disponíveis em bibliotecas externas e não ao nível da linguagem. São o caso das linguagens: Java, OCaml, C, C++, etc.
Esta aula vai ser integralmente dedicada a estudar algumas das funcionalidades da linguagem C que foram remetidas para a sua biblioteca.
A funcionalidade da biblioteca padrão está disponível através de perto de três dezenas ficheiros de interface, dos quais destacamos os seguintes: <ctype.h>, <limits.h>, <locale.h>, <math.h>, <setjmp.h>. <signal.h>, <stdarg.h>, <stdbool.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>, <thread.h>.
A aula de hoje envolve temas que permitem a exploração de parte das funcionalidades que estão disponíveis na biblioteca padrão do C.
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <thread.h> #include <crypt.h> #include <glib-2.0/glib.h>
Depois de escrito o programa, na altura de compilar é preciso indicar quais as bibliotecas a ligar. Usa-se para isso a opção -l do compilador. Exemplo:
cc -o myprog myprog.c mym1.c mym2.c -lm -lthread -lcrypt -lglib-2.0
#include <stdio.h> #define EOF (-1) typedef struct{...} FILE; FILE *stdin, *stdout, *stderr; FILE *fopen(char *, char *); FILE *fclose(FILE *); int fgetc(FILE *); int fputc(int, FILE *); int getchar(void); int putchar(int); size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); int fprintf(FILE *, char *, ...); int fscanf(FILE *, char *, ...); int printf(char *, ...); int scanf(char *, ...);
#include <math.h> double sin(double); double asin(double); double acos(double); double sqrt(double);
#include <string.h> int strlen(char *); char *strcpy(char *, char *); char *strcat(char *, char *); int strcmp(char *, char *);
#include <stdlib.h> void *malloc(int); void free(void *); void *realloc(void *, int);
#include <stdarg.h> List listNew(int n, ...) { va_list va; List l = NULL; va_start(va, n); while( n-- ) l = ListPutAtEnd(l, va_arg(va, double)); va_end(va); return l; } |
int main(void) { List l = listNew(5, 1.2, 2.6, 3.7, 4.1, 5.0); listPrint(l); return 0; }Os argumentos anónimos não podem ser validados no ponto da chamada. Se, na chamada, forem passados argumentos a mais, os argumentos em excesso são ignorados pela função. Se forem passados argumentos a menos, ou argumentos de tipo errado alguns argumentos da função conterão valores indefinidos.
Para aprender mais sobre funções com número variável de argumentos, usar o comando man stdarg.
Alguns exemplos:
#include <stdio.h> #include <errno.h> void Error(char *errmesg) { fprintf(stderr, "%s!\n", errmesg); exit(1); } FILE *openFile(char *name) { FILE *f; if( (f = fopen(name, "w")) == NULL ) { switch( errno ) { case EMFILE: Error("Too many open files"); case ENAMETOOLONG: Error("Filename too long"); case ENFILE: Error("Too many open files in system"); case ENOENT: Error("No such file"); case EROFS: Error("Read-only file system"); default: Error("File error"); } } return f; } int main(void) { FILE *f = openFile("ola"); /* mais código ... */ fclose(f); return 0; } |
No exemplo anterior, tentou-se organizar bem o tratamento dos erros, mas mesmo assim restou o problema de ter ficado estabelecido que um erro causa a terminação imediata do programa. Quem chama a função openFile não tem a opção de atuar de outra maneira.
O módulo setjmp disponibiliza as funções setjmp e longjmp:
#include <stdio.h> #include <errno.h> #include <setjmp.h> static jmp_buf jbuf; FILE *openFile(char *name) { FILE *f; if( (f = fopen(name, "w")) == NULL ) longjmp(jbuf, errno); return f; } int main(void) { switch( setjmp(jbuf) ) { case 0: { /* código protegido */ FILE *f = openFile("ola"); /* mais código ... */ fclose(f); break; } case EMFILE: Error("Too many open files"); case ENAMETOOLONG: Error("Filename too long"); case ENFILE: Error("Too many open files in system"); case ENOENT: Error("No such file"); case EROFS: Error("Read-only file system"); default: Error("File error"); } return 0; } |
O seguinte exemplo ilustra a utilização da função signal e apanha todas as interrupções geradas a partir do teclado usando CTRL-C.
#include <stdio.h> #include <signal.h> static void interruptHandler(int sig) { if( sig == SIGINT ) { signal(SIGINT, SIG_IGN); printf("Interrupt\n"); signal(SIGINT, interruptHandler); } } int main(void) { int i; signal(SIGINT, interruptHandler); for( i = 0 ; i < 1000000000 ; i++ ) if( i % 100000000 == 0 ) printf("%d\n", i); return 0; } |
Em muitas implementações de C (e.g. GCC) o módulo setjmp inclui funções extra (sigsetjmp e siglongjmp) que permitem fazer isso. Mas esta funcionalidade não faz parte do padrão da biblioteca C. Faz sim parte do padrão POSIX para bibliotecas de acesso aos serviços do sistema operativo.
Note que estamos a abrir os dois ficheiros usando um modo "b" especial que indica processamento de dados binários e não de texto. Num processamento em modo "texto", podem ocorrer conversões de caracteres especiais. Por exemplo, no Windows, em modo "texto", a escrita de "\n" gera "\r\n" no ficheiro e a leitura de "\r\n" resulta num simples "\n".
#include <stdio.h> #include <stdbool.h> bool copyFile(char* dest, char* orig) { /* Copia ficheiro, byte a byte. */ FILE *f, *g; /* Em C os "canais" do ML chamam-se "ficheiros internos". */ int c; /* Tem de ser int porque fgetc retorna 257 valores diferentes. */ if( (f = fopen(orig, "rb")) == NULL ) return false; if( (g = fopen(dest, "wb")) == NULL ) return false; while( (c = fgetc(f)) != EOF ) fputc(c, g); fclose(f); fclose(g); return true; } int main(void) { char in[256], out[256]; printf("IN> "); scanf("%s", in); printf("OUT> "); scanf("%s", out); if( !copyFile(out, in) ) fprintf(stderr, "Copy failed!\n"); return 0; } |
Repare que aqui deixámos de usar o "b".
#include <stdio.h> #include <stdbool.h> #define MAX_LINE 1024 typedef char Line[MAX_LINE]; bool copyFile(char* dest, char* orig) { /* Copia ficheiro, linha a linha. */ FILE *f, *g; Line s; if( (f = fopen(orig, "r")) == NULL ) return false; if( (g = fopen(dest, "w")) == NULL ) return false; while( fgets(s, MAX_LINE, f) != NULL ) // Lê e copia uma linha de cada vez fputs(s, g); fclose(f); fclose(g); return true; } |
Aqui usa-se o "b".
#include <stdio.h> #include <stdbool.h> #define BUFFER_SIZE 1024 bool copyFile(char* dest, char* orig) { /* Copia ficheiro, bloco a bloco. */ FILE *f, *g; char buffer[BUFFER_SIZE]; int count; if( (f = fopen(orig, "rb")) == NULL ) return false; if( (g = fopen(dest, "wb")) == NULL ) return false; while( (count = fread(buffer, 1, BUFFER_SIZE, f)) > 0 ) { printf("%d\n", count); fwrite(buffer, 1, count, g); } printf("%d\n", count); fclose(f); fclose(g); return true; } |
char *str = "ola";O facto das strings terem todas uma marca de fim, faz com elas sejam muito flexíveis e práticas de usar. A sua flexibilidade é equivalente à dos vetores acompanhados, pois o programa pode controlar o comprimento duma string.
Um detalhe técnico: no exemplo anterior, a variável str fica apontar diretamente para uma zona de memória constante, onde o sistema coloca todos os literais de tipo string. Não é permitido alterar o valor dum literal.
Contudo, escrevendo assim
char str[] = "ola";a linguagem reserva espaço para a nova variável e copia para lá a string. Neste caso existe liberdade de alterar a cópia.
int count(char str[], char c) { int i, soma = 0; for( i = 0; str[i] != '\0' ; i++ ) if (str[i] == c) soma++; return soma; }A chamada count("hello",'l') produz o resultado 2.
A seguinte função acrescente um caráter no final duma string, assumindo que há espaço na string:
int append(char str[], char c) { int i, soma = 0; for( i = 0 ; str[i] != '\0' ; i++ ) /* procura o final da string. */ ; str[i] = c; /* escreve c por cima de '\0' */ str[i+1] = '\0'; /* acrescenta '\0' no final da string */ }Eis outra forma de programar as mesmas duas funções, desta vez usando apontadores em vez de indexação:
int count(char str[], char c) { int soma = 0; char *pt; for( pt = str ; *pt != '\0' ; pt++ ) if( *pt == c ) soma++; return soma; } int append(char str[], char c) { int i, soma = 0; char *pt; for( pt = str ; *pt != '\0' ; pt++ ) /* procura o final da string. */ ; pt[0] = c; /* escreve c por cima de '\0' */ pt[1] = '\0'; /* acrescenta '\0' no final da string */ }
#include <string.h>permite ganhar acesso a numerosas funções predefinidas de manipulação de strings. Eis as funções de biblioteca mais usadas:
size_t strlen(const char *s); char *strcat(char *dest, const char *src); char *strncat(char *dest, const char *src, size_t n); int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t n); |
char a[10], *aa = a, *b = "ola"; while( *aa++ = *b++ );Este código tem o mesmo efeito:
char a[10], *b = "ola"; strcpy(a, b);
#include <stdio.h> int printf(const char *format, ...); int fprintf(FILE *out_stream, const char *format, ...); int sprintf(char *out_str, const char *format, ...); #include <stdarg.h> int vprintf(const char *format, va_list ap); int vfprintf(FILE *stream, const char *format, va_list ap); int vsprintf(char *str, const char *format, va_list ap); |
#include <stdio.h> int main(void) { printf("Characters: %c %c\n", 'a', 67); printf("Decimals: %d %d\n", 1977, 650000); printf("Preceding with blanks: %10d\n", 9999); printf("Preceding with zeros: %010d\n", 9999); printf("Some different radixes: %d %x %o %#x %#o\n", 100, 100, 100, 100, 100); printf("floats: %4.2f %+.0e%E\n", 3.14159, 3.14159); printf("Width trick: %*d\n", 5, 10); printf("%s\n", "Hello world!"); return 0; } Characters: a C Decimals: 1977 650000 Preceding with blanks: 9999 Preceding with zeros: 0000009999 Some different radixes: 100 64 144 0x64 0144 floats: 3.14 +3e+000 Width trick: 10 Hello world!A função printf retorna o número de carateres escritos. Em caso de erro retorna um valor negativo.
System.out.printf("%d\n", 123); 123
# Printf.printf "%d\n" 123;; 123 - : unit = ()
#include <stdio.h> int scanf(const char *format, ...); int fscanf(FILE *stream, const char *format, ...); int sscanf(const char *str, const char *format, ...); #include <stdarg.h> int vscanf(const char *format, va_list ap); int vsscanf(const char *str, const char *format, va_list ap); int vfscanf(FILE *stream, const char *format, va_list ap); |
Note que todos os argumentos que se seguem à string de formatação são argumentos de saída, sendo portanto implementados usando apontadores. Eis um exemplo:
#include <stdio.h> int main (void) { char str[100]; int i; printf("Enter name: "); scanf("%s", str); printf("Enter age: "); scanf("%d", &i); printf("%s is %d years old.\n", str,i); printf("Enter hexadecimal number: "); scanf("%x", &i); printf("Value %#x (%d).\n", i, i); return 0; }A função scanf retorna o número de parâmetros lidos com sucesso; portanto, em caso de falhanço de emparelhamento, esse valor pode ser inferior ao esperado. Antes do primeiro argumento ter sido lido, caso o input termine subitamente durante emparelhamento que esteja a ser bem sucedido, então é retornado o valor EOF.
# Scanf.scanf "%d" (fun x->x);; 123 - : int = 123