Entrada e Saída de Dados via Dispositivos Padrão O Modelo de Fluxo de Caracteres A grande maioria das linguagens de programação adota um modelo de fluxo de caracteres para as operações de entrada e saída de dados representados de forma simbólica. Conceitualmente estes fluxos podem ser considerados como fitas potencialmente infinitas com os caracteres registrados sequencialmente. No modelo conceitual, associado a um fluxo, temos um cabeçote de leitura ou escrita, de acordo com a natureza do fluxo (i.e., se ele é de entrada ou de saída). No caso de um fluxo de entrada, o cabeçote se encontra entre o último caractere consumido pela operação anterior e o seu sucessor no fluxo. Quando o fluxo for de saída, o cabeçote encontra-se após o último caractere registrado.Nestes fluxos podemos ter todo tipo de caractere, inclusive os de controle e de formatação de texto, como caracteres de tabulação e nova linha. A apresentação bidimensional de texto é efetuada por um dispositivo de saída (como uma impressora ou um monitor) para o qual é direcionado um fluxo de caracteres de acordo com os caracteres de controle nele registrados. Um fluxo tem, assim, uma natureza linear por ser constituído de uma sequência de caracteres que visualmente é apresentada de forma bidimensional para o usuário por dispositivos de saída. Um fluxo aparecerá no monitor como Na representação acima, o CR e o LF representam caracteres de controle de formatação, denominados carriage return e line feed, respectivamente. O primeiro retorna o cursor para o início da linha corrente e o segundo avança uma linha. Na linguagem C, as operações de entrada e saída padrão encontram-se definidas na biblioteca stdio.h e, para usá-las, é preciso incluir, no código de um programa, a seguinte linha no início do arquivo: #include <stdio.h> Entrada via Dispositivo Padrão O fluxo de entrada padrão é identificado na linguagem C por sdtin e está associado ao teclado, via de regra. Tudo que digitarmos no teclado durante a execução de um programa vai para tal fluxo. Ao pressionarmos a teclaENTER (nova linha), ela gera um ou dois caracteres, dependendo do sistema operacional. No caso do Windows, dois caracteres são gerados: o de mudança de linha (LF: line feed, de índice 10 na tabela ASCII) e o de "retorno de carro" (CR: carriage return, de índice 13). A tecla ENTER sinaliza uma "nova linha"e é representado, na linguagem C, por\n. Para a entrada elementar de dados através do dispositivo padrão de entrada, que existem as funções getchar escanf. A função getchar é usada sem parâmetros e ela retorna o próximo caractere no fluxo de entrada padrão. Exemplo: c = getchar(); A função scanf requer dois parâmetros: um formato entre aspas e um nome de uma variável precedida pelo caractere &. Tal caractere indica que queremos que a função scanf altere o valor da variável mencionada na lista de parâmetros. Os formatos possíveis são: Especificador de Formato Tipo %d int %f float %lf double %c char %s cadeia de caracteres (sequência de caracteres terminada por um branco ou por uma marca de "nova linha") Exemplo: scanf("%d", &numeroDeParcelas); A função scanf busca um valor do tipo indicado pelo formato. Se encontrado, ele é convertido em um valor interno. No caso de scanf("%d", &numeroDeParcelas), a função scanf procura por uma sequência de caracteres que representem os dígitos de um inteiro possivelmente precedidos por um sinal + ou -. Neste caso são descartados eventuais caracteres brancos e, a partir do primeiro caractere não branco, a operação de leitura assume que encontrou uma cadeia de caracteres que está em acordo com a sintaxe de inteiros. A linguagem C, contudo, não é muito seletiva. Aceita qualquer coisa e gera como resultado um valor inteiro qualquer (lixo). A operação de leitura continua a consumir caracteres até que encontre novamente um caractere branco. Caso fornecida uma representação simbólica de um inteiro válido, então, durante o processo de leitura, a sequência de caracteres lida é convertida em um valor binário equivalente ao valor correspondente à representação simbólica que acaba de ser "consumida". Se tivermos, por exemplo, um fluxo de entrada e o comando scanf("%d", &k); for executado, onde k é uma variável inteira, então o fluxo ficará na seguinte situação após a operação de leitura disparada pelo comando e à variável k estará associada ao valor binário 10010. Os caracteres anteriores ao cabeçote representado pelo triângulo já foram consumidos e não podem ser lidos novamente. Acima falamos que caracteres brancos são descartados. Isto não só ocorre estritamente com o caractere branco (o caractere de índice 32 na tabela ASCII), mas também com caracteres de formatação de texto como o de tabulação (o de índice 25), o de mudança de linha (LF: line feed, de índice 10) e o de "retorno de carro" (CR: carriage return, de índice 13). Portanto, quando falamos no descarte de caracteres brancos, estamos falando no "sentido amplo" que abrange também aqueles na categoria de caracteres de formatação de texto. Voltando ao exemplo acima, se digitarmos um caractere branco, um caractere 1, um caractere 2 e pressionarmos a tecla ENTER, supormos que temos uma variável inteira m e executarmos o comando scanf("%d", &m); então o fluxo ficará no seguinte estado após a operação de leitura e à variável m estará associado internamente o valor binário 10100. O esquema acima funciona para valores de tipos primitivos, exceto quando da leitura de caracteres para variáveis do tipo char. Neste caso, os caracteres no fluxo de entrada são consumidos sequencialmente sem o descarte dos caracteres branco no "sentido amplo." Isto é, se for solicitada a leitura de um caractere e o próximo no fluxo de entrada for um branco no "sentido amplo," então ele é lido e ele representa o resultado desta particular operação de leitura. Quando queremos "esvaziar" o fluxo de entrada, isto é, descartar os caracteres ainda não consumidos do fluxo, então utilizamos a função fflush da seguinte forma: fflush(stdin); Suponhamos que agora digitássemos o caractere S e pressionássemos, logo a seguir, a tecla ENTER. Teríamos, então, a seguinte situação no fluxo de entrada: Se executássemos, agora, o comando c = getchar(); ou o comando equivalente scanf("%c", &c); onde c representa uma variável do tipo char, então seria lido o caractere logo após o cabeçote e c assumiria o valor correspondente ao caractere CR ao invés do caractere S, como provavelmente pretendíamos. Portanto, para simplesmente "descartar o resto do fluxo de entrada" e posicionar o cabeçote no "início do fluxo", executamos o comando fflush(stdin). A seguir digitamos o caractere S e pressionássemos, logo a seguir, a tecla ENTER. O fluxo assume, então, a seguinte configuração: Para ler o valor S, portanto, escreveríamos um código como: fflush(stdin); c = getchar(); Após a execução desse trecho de código, a configuração do fluxo de entrada seria a seguinte: Quando misturamos a leitura de valores do tipo char (como quando utilizamos um caractere para representar uma seleção de uma opção em relação a um leque de alternativas, por exemplo) com valores de outros tipos, precisamos, portanto, tomar cuidado. Suponhamos que foi lido um inteiro que o usuário forneceu ao sistema, via teclado, seguido por um ENTER, e que após esta operação queiramos ler uma opção como S ou N fornecida em decorrência da solicitação "Você que continuar (s/n)?", por exemplo. Se o primeiro dado foi lido com uma operação scanf("%d",&n), os caracteres de controle que indicam uma "nova linha" que seguem o inteiro lido, isto é, o CR e o LF, continuam lá no fluxo de entrada e a seguir é lido o CR ao invés do S ou N como gostaríamos. Para eliminar o problema acima, precisamos esvaziar o fluxo de entrada com a operação fflush(stdin). Ao digitarmos dados via teclado eles são "ecoados" na tela do monitor do computador, isto é, eles são mostrados na tela conforme vão sendo digitados. Enquanto não pressionarmos a tecla ENTER, o processo de leitura não é disparado. Caso não estejam disponíveis mais caracteres no fluxo de entrada, o programa suspende a execução da operação de leitura que está demandando dados do usuário. Ao ocorrer o disparo via o pressionar da tecla ENTER, a execução da aplicação é retomada no ponto em que foi suspensa. Saída via Dispositivo Padrão O fluxo padrão de saída é chamado de stdout na linguagem C e é, normalmente, associado ao monitor do computador. Saídas elementares neste dispositivo são geradas através das funções putchar e printf. Estas operações adicionam caracteres ao fluxo de dados associado ao dispositivo padrão, onde são devidamente visualizados. A funcção recebe um parâmetro do tipo char. Exemplo: putchar('a'); putchar(letra); onde 'a' representa a uma constante equivalente à letra a e letra representa um variável do tipo char. A função printf recebe dois ou mais parâmetros. O primeiro é o formato que descreve como os valores associados às variáveis que se seguem devem ser colocados no fluxo de saída. A cadeia de caracteres que compõem o formato contém, além de caracteres a serem colocados no fluxo de saída padrão, especificadores de formato (um para cada variável que se seguem ao formato associados na mesma ordem). Seguem-se os especificadores mais usuais utizados para especificar o formato de uma função printf: Especificador de Formato Descrição %d inteiro %o octal %x hexadecimal %f número em ponto flutuante %c caractere %s cadeia de caracteres %% exibe um % Se quisermos incluir uma marca de "nova linha" no fluxo de entrada, então precisamos indicar isto no formato através da inclusão de \n no ponto desejado. Suponha que a variável k contenha o valor 18 (10010, em binário) e a variável m o valor 20 (10100, em binário). Se você executar o comando printf("%d%d", k, m); e o cursor se encontrar no início de uma linha, então os valores binários das variáveis são convertidos para a sua representação simbólica e você terá, nesta linha, o seguinte texto onde _ representa a posição do cursor após a execução de tais comandos. Conforme você pode observar, os dois valores encontram-se justapostos. Se você quiser evitar isto, você terá que ajustar o formato (o primeiro parâmetro da função): printf("%d %d", k, m); Na situação acima, você obterá a seguinte linha de saída: Se você quisesse que o cursor fosse posicionado para o início da linha seguinte após a operação, então o comando deveria ser: A saída correspondente então seria Como você pode observar nos exemplos acima, o espaço para representar os valores dos parâmetros na sua forma simbólica é a menor possível e, portanto, o espaço ocupando depende de tais valores. Se você quiser ter mais controle sobre o espaço a ser ocupado, você usa parâmetros de formatação. Por exemplo, o comando printf("%5d %3d\n", k, m); indica que para a apresentação simbólica do valor de k será utilizado um "campo" com, no mínimo, 5 posições e para a apresentação simbólica do valor de m será utilizado um "campo" de, no mínimo, 3 posições. Em cada campo também é acomodado o sinal - caso o valor seja negativo. Caso menos do que 5 posições forem necessárias para a representação do valor de k, por exemplo, então a representação é alinhada junto à margem direita do "campo" e as posições não ocupadas à esquerda são "preenchidas" com caracteres brancos. Para reais, além de um possível valor para indicar um comprimento mínimo do "campo", podemos ter outro precedido por um ponto para indicar a precisão com que será escrito o valor no fluxo de saída padrão, isto é, quantas casas decimais após o "ponto decimal" serão usados. Um campo acomoda o sinal do número, a parte inteira, o ponto decimal e a parte fracionária. Suponha que x contenha o valor 17.527. Se o cursor estiver no início de uma linha e você executar o comando printf("%5.2f\n",x); então você obterá o seguinte resultado: Como no caso de inteiros, valores que não ocuparem todo o campo alocado são precedidos por caracteres brancos. Exercícios 1. Crie um programa que gere uma tabela de duas colunas, onde os elementos da primeira coluna variam de 1 a 10 e cada elemento da segunda coluna representa o valor correspondente da primeira coluna multiplicado porn. O valor de n deve ser fornecido pelo usuário. O seu programa gera colunas alinhadas? 2. Crie um programa que conte quantos caracteres contém uma linha de entrada fornecida pelo usuário e informe tal valor. 3. Adapte o programa acima para que ele consulte o usuário, após cada geração de um resultado, para verificar se este quer ou não repetir uma outra operação de contagem com uma nova linha de dados.