Aula 8 – E/S pelo Console

C não define nenhuma palavra chave para E/S, e sim contém um sistema muito grande que envolve diversas funções. Tecnicamente há pouca distinção entre E/S pelo console e através de arquivo, porém o conceito é muito diferente. Nesta aula veremos entrada e saída pelo console, quanto que na próxima veremos E/S de arquivo.

Com uma exceção veremos nesta aula apenas funções E/S padrão ANSI C que não define nenhuma função para controle de elementos gráficos, pois isso varia muito para cada ambiente. As operações E/S tratadas aqui se referem a entrada pelo teclado e saída pela tela, porém elas poderiam ser redirecionadas a outros dispositivos como veremos na próxima aula.

Lendo e Escrevendo Caracteres

As funções mais simples de E/S pelo console são a getchar(), que lê um caractere do teclado e putchar() que escreve um caractere na tela.

O programa abaixo converte caracteres minúsculos em maiúsculos e vice-e-versa.

#include
#include

void main(void)
{
    char ch;
    printf("Entre com algum texto (ponto para sair): \n");
    do{
        ch = getchar();

        if (islower(ch)) ch = toupper(ch);
        else ch = tolower(ch);

        putchar(ch);
    } while(ch != '.');
}

Um Problema com getchar()

O padrão ANSI C definiu getchar() como sendo compatível com a versão original de C para UNIX. Isso quer dizer que ela armazena um buffer na entrada até que seja pressionado ENTER. Isso acontece porque sistemas UNIX originais tinham um buffer de linha para os terminais de entrada, o que dificulta um ambiente interativo, visto que o usuário deve sempre pressionar a tecla ENTER para enviar uma informação ao programa.

Alternativas para getchar()

Se nosso compilador não implementar getchar() de maneira interativa, como também é permitido pelo padrão ANSI, podemos utilizar uma função diferente para fazer leitura do teclado. Apesar do padrão ANSI não estabelecer nenhuma função interativa, os compiladores geralmente implementam suas próprias funções de entrada interativas.

Observação: até agora estava adaptdando todos os exemplos do livro para funcionar com Linux, porém cheguei a conclusão de que vou disponibilizar aqui o fonte tanto para linux quanto windows quando estes forem muito distintos. Acredito que assim mais usuários poderão se beneficiar do conteúdo aqui disponibilizado.

O programa abaixo não aguarda qualquer tecla ser pressionada, e ao fazê-lo, faz a conversão entre maiúscula ou minúscula, igual ao programa acima. Este programa é compatível apenas com Windows, pois usa a biblioteca conio.h qual não existe no linux.

#include
#include
#include

int main(void)
{
    char ch;
    printf("Entre com algum texto (digite um ponto para sair).\n");
    do {
        ch = getch();
        if (islower(ch)) ch = toupper(ch);
        else ch = tolower(ch);
        putchar(ch);
    } while (ch != '.');
}
Usando getch() no linux

Como dito anteriormente, a biblioteca conio.h não está disponível no linux. Uma alternativa a ela é a ncurses que contém uma implementação de getch(). Porém pelos testes que fiz, é necessário sempre atualizar a página para que funcione, e não é o que desejo em meu programa. Pesquisando na internet encontrei uma implementação de getch() usando o cabeçalho termios.h . Segue abaixo a versão para Linux do programa acima:

#include
#include
#include
#include

int mygetch() {
    struct termios oldt,
            newt;
    int ch;
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    ch = getchar();
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    return ch;
}

int main(void) {
    char ch;

    printf("Entre com algum texto (ponto para sair): \n");
    do {
        ch = mygetch();

        if (islower(ch)) ch = toupper(ch);
        else ch = tolower(ch);

        printf("%c\n", ch);
    } while (ch != '.');
}

Lendo e Escrevendo Strings

O livro traz o uso das funções gets() e puts() para fazer leitura e escrita de uma string, respectivamente. Ao compilarmos e exemplo do livro usando gets() o compilador gera o seguinte alerta:

warning: the `gets’ function is dangerous and should not be used.

Isso acontece pois a função gets() tem problemas de “buffer Overflow”. Para detalhes de como isso acontece, veja aqui: http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?answer=1049157810&id=1043284351

Vamos supor o seguinte código fonte:

#include
#include

void main(void) {
    char str[5];
    gets(str);
    printf("O comprimento é %d\n", strlen(str));
}

Como vemos, o vetor de caracteres acima suporta até 5 caracteres. A função gets() retorna caracteres digitados na linha até que se pressione a tecla ENTER, e então substiui o ENTER por um terminador nulo \0. Sendo assim, o nosso programa acima suporta no máximo 4 caracteres.

O problema é que a função gets() recebe apenas o ponteiro para a matriz, e desta maneira ela não tem como calcular qual o tamanho da matriz, então enquanto houver caracteres para serem lidos ela irá tentar alocar no vetor qual recebeu o ponteiro como argumento. Isso significa que se digitarmos mais que quatro caracteres e pressionarmos ENTER, poderemos ter uma série de problemas em nosso código.

Portantanto, tanto em windows, quanto linux, não devemos mais usar a função gets(). Ok, e o que fazemos então? A resposta é simples: use fgets()! O programa abaixo é a versão certa do anterior:

#include
#include

void main(void) {
    char str[5];
    fgets(str, sizeof(str), stdin);
    printf("O comprimento é %d\n", strlen(str));
}

Agora sim, fgets() aceita apenas 4 caracteres, independente de quantos você digite, afinal ela avalia o tamanho do vetor str e limita a alocação dos caracteres recebidos, lembrando que o último é sempre o terminador nulo \0.

A função puts() escreve seu argumento na tela seguido de nova linha. Seu protótipo é:

int puts(char *s);

Por não trabalhar com números e conversões de formato, puts() é mais leve e rápida que printf(), por isso é comum usá-la quando é importante termos um código altamente otimizado. Abaixo um programa usando puts():

#include
#include

void main(void) {
    char str[80];
    strcpy(str, "paulo marcos trentin");
    puts(str);
}

A diferença básica entre a função puts() e fputs() é que a última pode escrever em qualquer dispositivo de saída, seja stdout ou um arquivo por exemplo.

O programa abaixo é um dicionário coputadorizado simples, que usa funções de entrada e saída (E/S – I/O). Quando o usuário entra com uma palavra ele verifica se esta já existe em sua base. Se positivo, exibe seu significado. Note o uso de indireção (ponteiro para ponteiro).

E/S Formatada pelo Console

Para trabalhar com entrada e saída de dados formatada, podemos usar a função scanf()printf() respectivamente. Elas nos permitem trabalhar com qualquer um dos tipos de dados: string, caracteres e números.

printf()

O protópio da printf() é
int printf(char *string_de_controle, lista_de_argumentos);
Este protótipo está em STDIO.H. A função printf()devolve o número de caracteres escritos ou um valor negativo quando ocorre um erro.

A string_de_controle conterá caracteres que serão impressos na tela e caracteres especiais, que serão substituídos pelos argumentos subsequentes. Os caracteres especiais são precedidos de % e o número de argumentos subsequentes deve ser o mesmo dos caracteres especiais. Vejamos um exemplo:

printf("Eu gosto %s de %c", "muito", 'C');

irá exibir: Eu gosto muito de C.

Abaixo uma lista completa dos caracteres especiais que podem ser usados:

Código Formato
%c Caractere
%d ou %i Inteiros decimais com sinal
%e Notação científica (e minúsculo)
%E Notação científica (E maiúsculo)
%f Ponto flutuante decimal
%g Usa %e ou %f, o que for mais curto
%G Usa %E ou %F, o que for mais curto
%o Octal
%s String de caracteres
%u Inteiros decimais sem sinal
%x Hexadecimal sem sinal (letras minúsculas)
%X Hexadecimal sem sinal (letras maiúsculas)
%p Apresenta um ponteiro
%n O argumento associado é um ponteiro para inteiro no qual o número de
caracteres escritos até esse ponto é colocado.
%% Escreve o símbolo %

Escrevendo Caracteres

Como visto no exemplo anterior, basta usar %c para escrever um caractere individual. Para escrever uma string, deve-se utilizar %s.

Escrevendo Números

Podemos usar tanto %d quanto %i para indicar um número decimal com sinal.
Para escrever um valor sem sinal, deve-se utilizar %u.
Para escrever um número em ponto flutuante, usamos o especificador de formato %f.
Os especificadores %e e %E são usados para mostrar um double em notação científica seguindo o formato: x.dddddE+/-yy.

Podemos deixar printf() decidir utilizar %f ou %e usando os comandos de format %g ou %G. Desta forma, printf() seleciona o especificador de formato que produz a saída mais curta. Vejamos o exemplo:

#include "stdio.h"
void main(void)
{
    double f;
    for (f = 1.0; f < 1.0e+10; f = f*10)
        printf("%g ", f);
}

Esse programa vai gerar a seguinte saída:
1 10 100 1000 10000 100000 1e+06 1e+07 1e+08 1e+09

Podemos também exibir inteiros sem sinal no formato octal ou hexadecimal utilizando %o ou %x, respectivamente.

#include "stdio.h"
void main(void)
{
    unsigned num;
    for (num = 0; num < 255; num++){
        printf("%o ", num);
        printf("%x ", num);
        printf("\n ", num);
    }
}

Mostrando um Endereço

Para mostrar um endereço de memória em C, utilizamos o especificador %p como o programa abaixo:

#include "stdio.h"
int sample;

void main(void)
{
    printf("%p", &sample);
}

O Especificador %n

Esse especificador é bem diferente dos outros vistos até aqui. Ele faz com que printf() carregue na variável por ele apontada, o número de caracteres que já foram escritos. Para melhor entender vejamos o código abaixo:

#include "stdio.h"

void main(void)
{
    int count;
    printf("Isso%n é um teste\n", &count);
    printf("%d", count);
}

O código acima irá exibir:
Isso é um teste
4

O número 4 é a contagem de caracteres até encontrarmos %n, ou seja a palavra “Isso” que contém 4 caracteres. Simples não?

Modificadores de Formato

O modificador de formato fica entre o sinal de percentagem e o código propriamente dito. Serve para especificar, por exemplo, uma largura mínima para um campo.

O Especificador de Largura Mínima de Campo

Um valor colocado entre o % e o código irá fazer com que o valor impresso tenha um mínimo tamanho. Por exemplo, %05d preencherá no mínimo 5 dígitos em um valor inteiro. Caso este seja menor que isso, então complementa com 0s.

include "stdio.h"
void main(void)
{
double item;
item = 10.12304;
printf("%f\n", item);
printf("%10f\n", item);
printf("%012f\n", item);
}

O programa acima irá gerar a seguinte saída:
10.123040
10.123040 (temos um espaço, pois não inserimos nada entre % e 10f\n)
00010.123040

Esse modificador de largura geralmente é usado para exibir tabelas que possui colunas alinhadas. Veja o código baixo:

#include "stdio.h"
void main(void)
{
    int i;
    //  Exibe uma tabela de quadrados e cubos
    for (i = 1; i < 8; i++)
        printf("%8d %8d %8d\n", i, i * i, i * i * i);
}

O programa acima irá gerar a seguinte saída:

1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343

O especificador de Precisão

Em valores flutuantes, por exemplo, %10.4f irá mostrar um número com pelo menos dez caracteres com quatro cadas decimais.
Em strings, podemos usar o especificador de precisão para determinar o comprimento máximo do campo. Para mostrarmos uma string de pelo menos cinco até sete caracteres, usaremos %5.7s. Em inteiros, esse especificador é usado para informar um número mínimo de dígitos a serem impressos até completar o número exigido.

#include "stdio.h"
void main(void)
{
    printf("%.4f\n", 123.1234567);
    printf("%3.8d\n", 1000);
    printf("%10.15s\n", "Esse e um teste simples.");
}

O código acima irá gerar a seguinte saída:
123.1235
00001000
Esse e um teste

Justificando a Saída

Por padrão, toda saída é justificada à direita. Para justificarmos à esquerda, basta usar um sinal de menos antes do %. Para justificar à esquerda um número em ponto flutuante com duas casas decimais em um campo de 10 caracteres, usamos %-10.2f

#include "stdio.h"
void main(void)
{
    printf("justificado a direita: %8d\n", 100);
    printf("justificado a esquerda: %-8d\n", 100);

}

O programa acima irá gerar a seguinte saída:
justificado a direita: 100
justificado a esquerda: 100

Manipulando Outros Tipos de dados

Podemos usar o modificador %ld para exibir um long int e também o modificador %hu para exibir um short unsigned int por exemplo.

Os Modificadores * e #

Se você usar # antes de g, f e e, a função printf() garantirá que haverá um ponto decimal, mesmo que não haja dígitos decimais.

Em números hexadecimais, podemos usar #x para exibir sempre 0x antes do número desejado.

Podemos também usar largura mínima de campos fornecida como um argumento de printf(). Isso pode ser útil caso essa largura seja dinâmica. Para isso usamos no lugar do valor o *. Na figura abaixo, printf() irá casar os valores de acordo com sua ordem. A largura mínima é 10 e a precisão é 4, o valor a ser exibido é 123,34:

Vejamos o oso desses modificadores:

#include "stdio.h"
void main(void)
{
    printf("%x  %#x\n", 10, 10);
    printf("%*.*f", 10, 4, 1234.34);
}

O programa acima irá gerar a seguinte saída:
a 0xa
1234.3400

scanf()

Funciona de forma inversa a printf(). Está incluída dentro do cabeçalho STDIO.H.  Seu protótipo é:
int scanf(char *string_de_controle, lista_de_argumento);

Especificadores de Formato

São precedidos de %, segue a tabela com os códigos:

Código Significado
%c Lê um único caractere
%d %i Lê um inteiro decimal
%e %f %g Lê um inteiro em ponto flutuante
%o Lê um número octal
%s Lê uma string
%x Lê um número hexadecimal
%p Lê um pointeiro
%n Recebe um valor inteiro, igual ao número de caracteres lidos até então
%u Lê um inteiro sem sinal
% Busca por um conjunto de caracteres

Inserindo Números

Um número decimal pode ser lido usando o especificador %d ou %i (ambos são iguais e mantidos por questão de compatibilidade).
Para ler um número em ponto flutuante, usamos %e, %f ou %g.

Para lermos valores hexadecimais e octais temos um programa de exemplo:

#include "stdio.h"
void main(void)
{
    int i, j;
    scanf("%o%x", &i, &j);
    printf("%o %x", i, j);
}

Inserindo Inteiros sem Sinal

Para lermos um inteiro sem sinal, basta usarmos o especificador de formato %u:

#include "stdio.h"
void main(void)
{
    unsigned int num;
    scanf("%u", &num);
}

Lendo Caracteres Individuais com scanf()

Assim como getchar(), scanf() também lê caracteres e armazena-os num buffer, tornando imprópria para ambientes interativos (jogos por exemplo). Basta usarmos o especificador de formato %c. Lembre-se que lendo caracteres, um espaço também será considerado. Portanto a entrada “x y” no código abaixo irá alocar x em a, um espaço em b e o y em c.

#include "stdio.h"
void main(void)
{
    char a, b, c;
    scanf("%c%c%c", &a, &b, &c);
}

Lendo Strings

Diferentemente da função gets(), a função scanf() lê uma string até que seja encontrado um espaço, uma tabulação ou claro um retorno de carro (enter).

#include "stdio.h"
void main(void)
{
    char str[80];
    printf("entre com uma string: ");
    scanf("%s", str);
    printf("eis sua string: %s\n", str);
}

Faça testes com o código acima e veja os possíveis retornos.

Inserindo um Endereço

O programa abaixo lê um endereço de memória e verifica o valor deste endereço.

#include "stdio.h"
void main(void)
{
    char *p;
    printf("Entre com um endereço: ");
    scanf("%p", &p);
    printf("Na posição %p há %c\n", p, *p);
}

O Especificador %n

Esse especificador faz com que scanf() atribua o número de caracteres lidos na stream de entrada, no lugar onde ele foi encontrado.

Utilizando um “Scanset”

O padrão ANSI adicionou à scanf() uma nova característica chamada scanset. Ele faz uma espécie de formatação de entrada, permitindo que você organize os dados inseridos pelo usuário em determinadas variáveis. Implemente o código abaixo e teste:

#include "stdio.h"
void main(void)
{
    int i;
    char str[80], str2[80];
    scanf("%d%[abcdefg]%s", &i, str, str2);
    printf("%d %s %s", i, str, str2);
}

Inserindo 123abbbcdctyyyel teremos a seguinte saída: 123 abbbcdc tyyyel. Assim que encontra um caractere fora do scanset, scanf() irá separar os dados.
É possível também especificarmos uma faixa de caracteres para ser lidos: %[“A-Z”] irá aceitar todos os caracteres de A à Z maiúsculos.

Deve-se Passar Endereços para scanf()

Temos sempre que passar endereços para uma chamada de scanf(). Desta forma, C faz uma chamada por referência, onde a função pode alterar o conteúdo do seu argumento. Para que scanf() leia um inteiro para a variável count, devemos fazer a seguinte chamada:

scanf("%d", &count);

Uma string é lida sempre para um matriz de caracteres em C. O nome da matriz sem qualquer índice corresponde ao primeiro endereço do primeiro elemento da matriz, portanto para lermos uma string de caracteres str faremos:

scanf("%s", str);

Modificadores de Formato

Assim como printf(), scanf() permite que alguns de seus especificadores de formato sejam modificados. Se desejarmos ler no máximo 20 caracteres de entrada podemos usar o seguinte código:

#include "stdio.h"
void main(void)
{
    char str[80];
    scanf("%20s", str);
    printf("Sua string: %s\n", str);
    scanf("%20s", str);
    printf("Sua string: %s\n", str);
}

Implemente o código acima e veja o que acontece. Se entrar com a string: ABCDEFGHIJKLMNOPQRSTUVWXYZ os primeiros caracteres até a letra “T” serão colocados em str, e na próxima chamada de scanf(“%20s”, str) as letras “UVWXYZ” serão colocadas em str, pois elas ainda não tinham sido usadas. Mantenha a atenção caso trabalhe com scanf().

Suprimindo a Entrada

Podemos pedir para scanf() ler um campo mas não usá-lo. Para isso basta usarmos o * antes do código do formato do campo.

#include "stdio.h"
void main(void)
{
    int i, j;
    scanf("%d%*c%d", &i, &j);
}

O programa acima aceita perfeitamente a entrada 10,10, porém ignora a vírgula. Isso é muito útil quando precisamos processar só uma parte da entrada do usuário.

3 Comments

    1. Oi Angélica,
      fico feliz que tenha gostado do curso, mas não pretendo continuá-lo.

      O retorno foi bastante baixo, e gastei bastante tempo para fazê-lo até aqui. Acredito também, que o básico de C ele já está trazendo. Para continuar os estudos recomendo comprar o livro, o qual os posts se basearam. Outra alternativa é brincar com Arduino, fazer experimentos, que também facilita muito o domínio da linguagem.

      Obrigado pelo retorno!

Leave a Reply