Aulas de C

Aprendizado continuo. Linguagem antiga e moderna

Arquivos Mensais: novembro 2010

Uma aprofundada em operadores e lógica em C

OK.
Vamos dar uma pequena pausa para aprofudar uma teoria que é necessária para seguirmos em frente: os operadores.

Na nossa última “aula”, vimos um pouco sobre os operadores, em especial operadores matemáticos, lógicos e relacionais. Mas na verdade o C é composto por uma enorme gama de operadores, capazes de realizar muitas atividades. Aprender bem como usar operadores é algo importantíssimo para construir bons programas em C. Portanto, vamos dar uma pausa e reforçar essa teoria antes de seguirmos em frente.
A primeira coisa a entender é que temos vários tipos de operadores em C, que podemos dividir  em “grupos” para facilitar a compreenção:

  • Operadores de atribuição – são operadores usados para atribuir-se valores a variáveis;
  • Operadores aritméticos – com ele realizamos operações matemáticas simples usando dois valores, os operandos;
  • Operadores relacionais – comparam os valores de dois operandos, verificando se eles representam verdades ou falsidades lógicas naquele momento;
  • Operadores lógicos – comparam os valores de dois operandos em termos lógicos, ou seja, comparações baseadas na lógica booleana (E, OU e NÃO);
  • Operadores de bits – Permitem realizar operações diretamente nos bits de um determinado valor (em geral, inteiro);
  • Operadores compostos – são operadores complexos que podem combinar as funções de dois operadores do tipos acima;
  • Operadores especiais -são operadores usados no desenvolvimento da aplicação;

Além disso, existe uma ordem de prioridade dos operadores que iremos mostrar no final desse post. Você não precisa decorar essa tabela: você pode utilizar esse post como referência ou então facilmente pegar uma “cola” dessa tabela na Internet em vários sites de referência.
Bem, vamos começar então com o Operador de Atribuição.

Operador de atribuição – =

O operador de atribuição = serve para indicar ao C que desejamos armazenar (ou atribuir) a uma determinada variável um valor. Já vimos nos programas anteriores vários exemplos de atribuições, em especial quando inicializamos variáveis como fizemos no programa de Entrada de Dados. Uma coisa, porém, que é importante dizer é que o C permite fazer-se múltiplas atribuições, desde que os tipos sejam compatíveis. Por exemplo, o trecho de código abaixo:

int a,c;
float b,d;
a = b = c = d = 0;

É válido, uma vez que b e d, ainda que sejam do tipo float, podem receber valores inteiros (por causa do autocast – lembra que vimos quando mostramos o programa de Entrada de Dados?). Basicamente, esse comando faz a seguinte salada na atribuição:

  • Atribui 0 a d – como d é float, dá o autocast do 0 (que nesse caso é tratado como inteiro) para float;
  • Em seguida, atribui a c o valor de dd está inicializado como 0.0 (0 no tipo float). Ao receber o valor para c, o sistema faz o autocast do valor, truncando-o para 0 (inteiro);
  • Após isso, atribui a b o valor de c – repete-se o caso da atribuição para d;
  • E por fim, atribui a a o valor de b – repetindo o que aconteceu quando atribui-se a c o valor de d;

Importante notar que os autocasts são sempre executados no momento da atribuição do valor a uma variável. Por isso que, se dividimos dois inteiros e precisamos de um valor float, mesmo atribuindo o resultado, precisamos forçar o cast de um dos inteiros para float antes: se usarmos a divisão normalmente, ele irá tratá-la como divisão entre dois inteiros e dará um resultado inteiro, que será convertido depois da operação em float. Ao forçar um deles como float, indicamos que precisamos de um resultado em ponto flutuante e, dando o autocast no outro operando, o programa realizará uma divisão em ponto flutuante e retornará um valor float.
Aproveitando que entramos no assunto typecast, existe uma prioridade nos autocasts: isso é feito com o objetivo de impedir que os resultados percam valor (por exemplo, ao converter-se um valor de um tipo para um outro tipo cujo tamanho seja menor em bits e, portanto, capaz de representar uma gama inferior de valores). Tenha isso em mente ao recorrer ao autocast: prefira, se possível, fazer você mesmo o typecast, pois isso irá garantir que você sabe qual será o tipo resultante.
Dito isso, vamos para o próximo conjunto de operadores.

Operadores aritméticos

Já falamos sobre eles antes: são operadores que realizam contas matemáticas simples entre dois operandos (valores), “devolvendo” um deles como valor da expressão aritmética (em C, uma expressão aritmética tem o mesmo significado que na matemática, e o termo expressão é extrapolado a partir daí como a representação de uma operação lógico-matemática que será resolvida em um determinado momento). Esse valor pode ser usado em uma atribuição ou em qualquer lugar onde exija-se um valor. Isso é interessante pois pode-se executar operações matemáticas, por exemplo, antes de operações lógicas, obviamente respeitando as prioridades determinadas pelo C (veremos abaixo as prioridades e como alterar a ordem de execução das operações).
Basicamente, as operações matemáticas são:

Operador

*
/
%

+
Operação

Multiplicação
Divisão
Resto da Divisão Inteira (mod)
Subtração
Soma

Prioridade

1
1
1
2
2

Na coluna Prioridade, fazemos uma referência a como o C prioriza as operações aritméticas entre si. No caso, primeiro o C executa divisões, multiplicações e resto de divisões conforme apareçam da esquerda para direita (como é feito na matemática). Em seguida, o C executa somas e subtrações (também da esquerda para a direita, conforme a matemática). Existem formas de alterar a prioridade da execução de fórmulas complexas, que veremos mais abaixo. Mas, basicamente, essas são as operações matemáticas possíveis de serem feitas com o C. Uma ressalva: diferentemente de outras linguagens de programação, C não possui um operador matemático para potenciação.
Em C, a divisão é palco de uma controversa. Normalmente em C, é feita divisão inteira entre valores se ambos forem do tipo int, mesmo que a atribuição do resultado seja feita para uma variável de valor flutuante. Isso deve-se ao fato de que o C considera dois momentos: (1) quando a operação de divisão é feita e (2) quando o valor resultante é atribuido à variável de ponto flutuante. Porém, se no momento da execução da divisão, um dos valores for de tipo flutuante (seja por ser uma varíavel float ou por um cast), o C irá pegar o termo restante, forçar o cast (autocasting) do mesmo para um tipo flutuante e retornar o resultado da operação com um valor de tipo flutuante. Isso é algo a manter-se em mente.
O operando % (resto da divisão inteira) é um caso a parte: ele vai transformar ambos os termos em inteiros, uma vez que o conceito de resto só existe em matemática quando falamos de número inteiros (mesmo nos casos de dízimas periódicas ou não periódicas, a matemática parte do princípio que, cedo ou tarde podemos descobrir o “último dígito” de um valor). Essa conversão é feita truncando-se o valor decimal do número com tipo flutuante, tornando-o inteiro. Porém, isso pode provocar inexatidão em resultados, podendo afetar os resultados finais do programa. Para isso, o C oferece uma biblioteca especializada em funções matemáticas, math.h, sobre a qual ainda iremos falar de maneira mais aprofundada.
Bem, dito isso, vamos seguir falando dos operadores. No caso, vamos falar dos operadores relacionais.

Operadores relacionais

O nome meio que diz tudo: operadores relacionais envolvem a relação entre dois valores específicos de maneira lógica, comparando-os entre si e vendo se a relação em questão é verdadeira ou não. Basicamente eles devolvem 1 caso a relação seja verdadeira, e 0 caso a mesma seja falsa. (Lembrando que, como vimos na nossa última “aula” não existe no C um tipo booleano, então ele adota 0, “” (string vazia) ou o símbolo null como convenções para falso e quaisquer outros valores como verdadeiro). Para ter uma noção do funcionamento do mesmo, rode o seguinte programa (retirado do material do Curso de C da UFMG):

#include <stdio.h>
int main()
{
    int i, j;

    printf("\nEntre com dois numeros inteiros: ");
    scanf("%d%d", &i, &j);

    printf("\n%d == %d é %d\n", i, j, i==j);
    printf("\n%d != %d é %d\n", i, j, i!=j);
    printf("\n%d <= %d é %d\n", i, j, i<=j);
    printf("\n%d >= %d é %d\n", i, j, i>=j);
    printf("\n%d < %d é %d\n", i, j, i<j);
    printf("\n%d > %d é %d\n", i, j, i>j);
    return(0);
}

Não existe muito a ser dito sobre novos comandos como fizemos em outros casos. O importante aqui é estudar o comportamento dos operadores relacionais, que estamos listando abaixo, já indicando também sua prioridade entre si:

Operador


>= 

<=
== 
!=
Operação

Maior do que 
Maior ou igual a 
Menor do que 
Menor ou igual a 
Igual a 
Diferente de

Prioridade

1
1
1
1
2
2

Aqui voltaremos a enfatizar:

NÃO EXISTE TIPO BOOLEANO EM C! EM C, QUALQUER VALOR DIFERENTE DE 0 OU DA STRING VAZIA “” OU DE NULL É CONSIDERADO VERDADEIRO!
Além disso, vamos reenfatizar outra coisa: nunca confunda o operador relacional de igualdade (==) com o de atribuição (=). Isso irá provocar erros de lógica de aplicação que são de difícil depuração.
Pois bem, esses três primeiros tópicos, falando de operadores de atribuição, aritméticos e relacionais são apenas uma revisão do que vimos anteriormente. A partir de agora iremos ver operadores que vimos por alto ou não vimos anteriormente, a começar pelos…

Operadores lógicos

Operadores lógicos são basicamente operadores que realizam comparações. De certa forma, são um subgrupo dos operadores relacionais, mas que trabalham apenas com valores segundo a lógica booleana. De uma forma rápida, podemos dizer que eles podem comparar se dois valores são verdadeiros ao mesmo tempo (E/AND), se pelo menos um entre dois valores pode ser considerado verdadeiro (OU/OR) e se um determinado valor é falso naquele momento (NÃO/NOT). A vantagem de usar-se operadores lógicos é que, combinando-os com os operadores relacionais, podemos construir condições complexas, em especial em situações de controle de fluxo. Por exemplo, vamos relembrar um trecho de código de nossa última “aula”:
      do
        {
          printf(“Quantas tentativas você acha que precisa para descobrir ele? “);
          scanf(“%d”,&maximoTentativas);

          if (maximoTentativas<1)
             printf(“Você precisa tentar ao menos uma vez! 😛 \n”);
          if (maximoTentativas>limiteTentativas)
             printf(“%d tentativas? Tá querendo demais também! 😛 \n”,maximoTentativas);
        } while (maximoTentativas<1 || maximoTentativas>limiteTentativas);

Relembrando: o do…while executa enquanto a condição indicada no final do bloco de código for considerada verdadeira. No caso, nossa intenção foi garantir que o usuário não entrasse com nenhum número negativo e nem fosse além de um limite de tentativas estipulado previamente. Para isso, utilizamos duas condições relacionais (maximoTentativas<1 e maximoTentativas>limiteTentativas), cada uma atuando em uma das situações que determinamos. Para “unirmos” as duas situações, usamos o operador lógico OU (|| em C) que indica que, enquanto pelo menos uma dessas condições relacionais for verdadeira, a condição lógica será verdadeira e, portanto, o programa permanecerá executando esse bloco de código.
C implementa os principais operadores relacionais, que são o E (AND), OU (OR), e NÃO (NOT), conforme representado abaixo:

Operador

!
&&
||
Operação

NÃO Lógico
E Lógico
OU Lógico

Prioridade

1
2
3

O programa abaixo, também retirado do material do Curso de C da UFMG poderá ilustrar melhor o comportamento de E, OU e NÃO:

#include <stdio.h> <br />int main()<br />{<br />   int i, j;<br />   printf("informe dois números(cada um sendo 0 ou 1): ");<br />   scanf("%d%d", &i, &j);<br />   printf("%d AND %d é %d\n", i, j, i && j);<br />   printf("%d OR %d é %d\n", i, j, i || j);<br />   printf("NOT %d é %d\n", i, !i);<br />}<br />

Na verdade, com o tempo esse conceito ficará bem arraigado na cabeça. Então vamos para os próximos operadores.
Antes, um parênteses: em vários momentos, e em especial quando falamos dos tipos de dados em C, mencionamos que em C, as strings de caracteres possuem um comportamento diferenciado em relação ao que vemos em outras linguagens de programação. Por isso, você NÃO DEVE utilizar os operadores lógicos e relacionais apresentados anteriormente com strings. Isso irá provocar certamente resultados espúrios. Para comparações envolvendo strings, a biblioteca padrão string.h oferece uma série de funções úteis, sobre as quais falaremos no futuro.

Operadores de bit (bitwise)

O C oferece um conjunto de operadores lógicos voltados exclusivamente para o “acesso direto” aos bits. Mais exatamente, para realizar operações lógicas com bits. Chamamos esses operadores de operadores de bit, bit-a-bit ou bitwise, dependendo do autor do livro.
C foi uma linguagem originalmente projetada para facilitar o desenvolvimento de aplicações em baixo nível, como sistemas operacionais, firmware (software de baixo nível para dispositivos eletrônicos, como celulares e players de DVD), compiladores, com o objetivo de substituir o Assembler e oferecer um mínimo de portabilidade de código, ainda que mantendo os benefícios do Assembler de acesso direto ao hardware. Como o software de um appliance, o firmware, se comunica com o hardware por pulsos eletrônicos interpretados pela CPU como bits 0s e 1s específicos em determinados locais de memória, é interessante que C possua comandos capazes de trabalhar com esses bits, que chamamos em programação de flags. Para isso, utiliza-se os operadores de bit.
Os operadores de bit lembram um pouco os operadores aritméticos e um pouco os operadores lógicos, pois eles irão realizar a operação lógica em cada bit de um determinado número, usando outro, e retornando um terceiro. Por exemplo, imaginemos que precisamos obter de um conjunto de flags (normalmente representados por um int) se o 5° bit dessa conjunto de flags está ativo. Uma forma é aplicar o bitwise AND (E bit-a-bit) contra o número 8 (representado 00001000). Esse valor (que alguns autores chamam de bitmask – máscara de bits) atua de tal forma que zera todos os outros bits do int em questão: lembrando que ambos os bit tem que ser 1 – verdadeiro – para que o bit na saída seja 1. Como os demais bits no bitmask são 0, o valor de tais bits é zerado. Se o bit na flag estiver zerado, o resultado será 0, caso contrário o valor de saída será 8.
Existem também operadores de movimentação de bits: algumas operações matemáticas e lógicas são resolvidas facilmente quando utilizamos essa movimentação de bits, “empurrando” os bits à esquerda ou direita, com isso aumentando e diminuindo seus valores. Como exemplo: uma forma “rápida e suja” de fazer-se a potência de 2 em um número é “empurrando” seus bits uma casa para a direita. Como em numeração binária um bit é sempre uma potência de 2 acima do bit à direita ao seu, ao empurrar-se os bits à direita temos o efeito da potência de 2. (PS: esse método não faz as checagens matemáticas de casos especiais como potências de 0 e 1 e nem verificam estouros do tamanho de variável). Ao empurrar-se os bits, o último bit na direção para a qual os bits foram “empurrados” é eliminado e o primeiro bit da direção oposta é preenchido com 0.
Os operadores de bit são:

Operador

!
<<
>>
&
^
|
Operação

NÃO por bit a bit
Deslocamento de bits à esquerda
Deslocamento de bits à direita
E bit a bit
OU Exclusivo (um e apena um bit)
OU bit a bit

Prioridade

1
2
2
3
4
5

Uma coisa a salientar de diferente entre os operadores bit-a-bit e os operadores lógicos é a existência do OU-Exclusivo (eXclusive OR, XOR) bit-a-bit. Nessa situação, ao comparar-se os dois números bit a bit, um bit na saída só será um se UM E APENAS UM dos bits comparados for 1. Caso contrário (ambos 0 ou ambos 1) o bit será zero.
Bem, esse é um tema complexo e pouco útil para nós. Vamos falar agora de…

Operadores Compostos

Operadores Compostos englobam operadores que atuam ao mesmo tempo como dois tipos diferentes de operadores, em geral uma operação bit-a-bit ou aritmética e uma atribuição. Além desses, existe o operador de atribuição condicional ?.
Vamos englobar em três subgrupos os operadores compostos: os de atribuição “aditiva”, os de incremento e decremento em um e o de atribuição condicional.
Os de atribuição “aditiva” são operadores que realizam uma determinada operação envolvendo um parâmetro com uma variável e atribuem à mesma variável o resultado da operação. Por exemplo, a expressão a+=4 é equivalente em termos de programação a a=a+4. Esses operadores existem para ambientes onde o espaço para a leitura do código fonte é pequeno e determinadas redundâncias de digitação comprometeriam a compilação. Alguns autores consideram que o uso desse tipo de operador torna o programa mais legível, enquanto outros defendem que esse tipo de operador provoca a ofuscação do código (ou seja, torna a leitura do código complexa). A lista dos operandos compostos segue abaixo:

Operador

/=
*=
%=
+=
-=
<<
>>
&
^
|
Operação

Divisão com atribuição
Multiplicação com atribuição
Resto da divisão inteira com atribuição
Soma com atribuição
Subtração com atribuição
Deslocamento de bits à esquerda com atribuição
Deslocamento de bits à direita com atribuição
E bit a bit com atribuição
OU Exclusivo (um e apena um bit) com atribuição
OU bit a bit com atribuição

Prioridade

1
1
1
2
2
3
3
4
4
4

Esse é um subtipo complexo, e o próximo também demanda atenção, que são os operadores de incremento e decremento em um. Na verdade, esses operadores são indicados por ++ e e indicam que a variável onde eles estão sendo usados terá seu valor somado ou subtraído em 1. Porém, existe um comportamento que deve ser levado em conta ao usar-se tais operadores, que é o de prefixação e pós-fixação. Para usar-se os operadores desse tipo, você pode colocar o operador antes ou depois da varíavel. A posição onde o colocar, porém, irá afetar o comportamento sobre qual será o valor realmente usado.
Ao prefixar o operador antes da variável (ou seja, colocar ++ ou antes da variável), o valor será modificando antes de qualquer uso. Por exemplo, se você usar a=++b, e b for 4, acontecerá o seguinte: (1) b será incrementada em 1, para 5 e (2) a receberá o novo valor de b, 5. Ao pós-fixar o operador, acontecerá o contrário: o valor será usada para qualquer outro fim antes de ser modificado. Mantendo o exemplo anterior, imagine que você, ao invés de usar a=++b, utilizou a=b++. Nesse caso (1) a receberá o valor atual de b, 4 e depois é que (2) b será incrementado em 1. Essa diferença é importante de ter-se em mente, pois ela pode provocar bugs (em especial quando utiliza-se esse operador em conjutnto com comparações relacionais), mas é uma ferramenta muito poderosa.
Por exemplo: nas “brincadeiras” da nossa última “aula” sugeri:

  • Da mesma forma que existe o operador ++, existe o operador , que subtrai um da variável que o antecede. Considerando isso e o funcionamento do laço for, tente reconstruir o laço para não ter uma condição no sentido exato da palavra. Dica: lembre-se que C trata 0 como falso;

A forma para fazer isso é lembrar que o for, na comparação, exige um “booleano” verdadeiro (ou seja, qualquer valor diferente de 0). Portanto, se usarmos o operador de decremento em 1 pós-fixado, podemos fazer com que o número de tentativas vá se reduzindo até que, na última tentativa, ele chegue na condição em 0 e, sendo “falso”, saia do laço. Portanto, basta substituir a linha:

for (i=1; i<=maximoTentativas;i++)

no programa da nossa última “aula” por:

for (i=1; maximoTentativas–; i++)

e teremos feito a mágica. Na realidade, se não usarmos a variável i para exibir o número da tentativa, podemos simplesmente reduzir o comando para:

for (; maximoTentativas–;)

e o código terá o mesmo comportamento, sem precisar de uma variável de controle.

Mas, ATENÇÃO: isso só funcionará bem se você usar o decremento pós-fixado. Se tentar prefixar o decremento, ele irá, na prática, tirar uma tentativa do usuário, ao ponto de, se o usuário pedir apenas uma tentativa o sistema nem pedir o número: no caso, ele irá subtrair 1 de maximoTentativas (que será 1), reduzindo-o para 0 e tornando a condição “falsa”.
Bem, aqui podemos dizer que terminamos de falar do incremento e decremento de um. Vamos falar, para acabar com os atributos compostos, da atribuição condicional.
Chamamos de atribuição condicional um operador que funciona como o seguinte tipo de lógica:

if (condicao)
   var=x;
else
   var=y;

O C oferece um operador que permite-nos fazer esse tipo de condição diretamente na atribuição, o operador ?. Esse é um operador ternário (ou seja, exige três operandos), sendo composto por <condicao>?<atr_verdadeiro>:<atr_falso>. Nesse caso, caso condicao seja verdadeira (diferente de 0), o operador irá devolver atr_verdadeiro, enquanto que, caso condicao seja falsa (igual a zero), ele irá devolver o valor de atr_falso. Por exemplo, imagine que você irá precisa do módulo de um número, ou seja, do valor sem sinal do mesmo. Ao invés de construir um if só para isso, você pode usar uma atribuição condicional:

modulo=(numero<0)?numero*-1:numero;

O que quer dizer que modulo irá receber o valor de numero vezes -1 caso numero seja menor que 0; caso contrário, ele irá receber o valor normal de numero.

Controle de Fluxo e Operandos Lógicos

Olá!
Vamos continuar então a programar em C.

Os dois primeiros programas, Hello World e o de Entrada de Dados, tinham como ponto comum o fato de serem, vamos dizer assim, diretos. Eles eram executados, realizavam cada instrução em sequencia e se encerravam.
Isso em si não é ruim: muitos programas simples e interessantes podem ser feitos assim. Porém, é perceptível que eles não possuem nenhuma “inteligência”: eles não conseguem executar instruções dependendo das entradas que lhe são oferecidas. Isso se deve ao fato de eles não possuírem nenhum controle de fluxo. Em programação, esse termo é adotado para definir situações onde o programa pode mudar a sequencia de execução do programa conforme condições estipuladas no momento do desenvolvimento da aplicação. Por exemplo: no programa de Entrada de Dados, poderíamos estipular valores limite para primeiro e segundo que não os limites do tipo int.
Pois bem, veremos agora um programa com um pouco mais de “inteligência”, pois iremos falar mais sobre os comandos de controle de fluxo em C e sobre os operadores lógicos, além de questões sobre o tipo booleano (ou melhor, sobre a ausência de um tipo booleano) em C.
No caso, vamos escrever um outro programa comum, o “adivinhe o número”. Mas vamos adicionar alguma “inteligência” a ele e tornar ele um pouco mais desafiador:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* Definindo o número limite para a pessoa tentar descobrir */

#define LIMITE 10
#define ENCERRA printf(“Pressione qualquer tecla para continuar!\n”); while(!getchar());

int main (int argc, char** argv)
{
  const int
limiteTentativas=(LIMITE/2)-1;
  int numeroPensado=0, numeroDoUsuario=0, maximoTentativas=0,i;
 
  srand(time(NULL)); // Serve para modificar a tabela de números pseudo-aleatórios
  numeroPensado=rand()%LIMITE+1;

  printf(“OK! Pensei em um número entre 1 e %d e te desafio a achar ele!\n”, LIMITE);
  do
    {
      printf(“Quantas tentativas você acha que precisa para descobrir ele? “);
      scanf(“%d”,&maximoTentativas);

      if (maximoTentativas<1)
         printf(“Você precisa tentar ao menos uma vez! :P\n”);

      if (maximoTentativas>limiteTentativas)
         printf(“%d tentativas? Tá querendo demais também! :P\n”,maximoTentativas);
    } while (maximoTentativas<1 || maximoTentativas>limiteTentativas);

  for (i=1; i<=maximoTentativas;i++)
    {
      printf(“Vamos lá! %da. tentativa! “,i);
     
      scanf(“%d”,&numeroDoUsuario);

      if (numeroDoUsuario==numeroPensado)
      {
         printf(“Parabéns! Você acertou!\n”);
         ENCERRA
         return(0);
      }
         else
         {
           printf(“Não pensei em %d. “, numeroDoUsuario);
           if (numeroDoUsuario<numeroPensado)
              printf(“Pensei em um número maior.\n”);
           if (numeroDoUsuario>numeroPensado)
              printf(“Pensei em um número menor.\n”);
         }
    }

    printf(“Que pena! Eu pensei em %d! Melhor sorte da próxima vez! \n”,numeroPensado);
    ENCERRA
    return(0);
}

Como de costume, iremos ignorar algumas coisas que já vimos anteriormente. Portanto, se você ficar com alguma dúvida, dê uma relida nos posts anteriores para fixar bem o conteúdo que já lidamos. No caso, vamos falar sobre os trechos de código que estão com destaque colorido no fonte àcima.

#define e os “números mágicos”

Logo de cara incluímos duas novas bibliotecas em C: stdlib.hSTanDard Library (biblioteca padrão), que contêm uma série de funções cotidianas em C – e time.h, que contem funções para lidar com tempo. Dessas, a stdlib.h em especial é muito importante, pois uma grande quantidade de comandos C muito usados está incluída nela. No caso, estamos muito interessados nas funções relacionadas à geração de números randômicos, mas existe muito mais nela e no futuro falaremos muito sobre essa biblioteca.
Logo após, vemos dois novos comandos do pré-processador (ou macros, como são chamados tecnicamente):

#define LIMITE 10
#define ENCERRA printf(“Pressione qualquer tecla para continuar!\n”); while(!getchar());

A macro #define permite ao programador definir um símbolo que o pré-processador irá substituir por uma informação qualquer no momento da compilação. No caso, definimos dois símbolos:

  • LIMITE – um número com valor 10;
  • ENCERRA – uma sequencia de comandos C;

No caso, você pode se perguntar sobre a utilidade de definir-se símbolos. Esse sistema de símbolos permite:

  1. Criar “comandos” simples (caso do ENCERRA);
  2. Criar situações de compilação condicional (um tópico mais avançado, veremos no futuro);
  3. Evitar os chamados “números mágicos”;

“Números mágicos?! Virou Hogwarts agora?”
Na verdade não. Em programação, chamamos de números mágicos determinados números ou expressões que são colocadas no código para realizar determinadas funções. No nosso caso, precisamos limitar o maior número no qual nosso programa irá “pensar”. Poderíamos simplesmente escrever ele no nosso programa de maneira direta, mas como esse número iria se repetir várias vezes em vários trechos de código aparentemente sem relação um com o outro, com certeza a leitura e análise de erros do programa (que em programação é chamada de depuração) seria complicada. Por isso chamamos tais números de números mágicos: uma vez que ele entra, temos dificuldades para entender o que eles fazem.
No caso, ao criarmos um símbolo (LIMITE), podemos usar ele no nosso código que o próprio compilador (através do pré-processador) irá substituir todas as entradas do símbolo LIMITE pelo valor desejado (no caso, 10). Isso torna o programa mais legível e mais simples de ser modificado.
O mesmo vale para ENCERRA. Com ENCERRA, criamos um “pseudo-comando”, um símbolo que será substituído por uma série de comandos C. No caso, o comando escrito em ENCERRA ajuda alguns usuários de IDEs (em especial no Windows) a visualizarem os resultados dos seus programas.
Existe mais sobre #define que ainda iremos ver, em especial na questão dos “pseudo-comandos”, mas esse básico deve ajudar a compreender melhor as coisas até termos uma oportunidade de nos aprofundarmos nele (em especial, quando falarmos no futuro de outras macros de pré-processador, quando revisitaremos #include e #define).

Variáveis Constantes:

Após as macros temos uma declaração de variáveis: logo de cara, temos uma que é um pouco diferente do que o que vimos quando falamos dos tipos de variáveis e como as declaramos:

  const int limiteTentativas=(LIMITE/2)-1;

A principal diferença está na palavra reservada const antes da declaração do tipo int da variável limiteTentativas. O que essa palavra faz é indicar ao compilador que essa variável na verdade é uma constante do tipo desejado, portanto não poderá mais ser modificada uma vez que seja inicializada, o que acontece em seguida. No caso, a variável constante limiteTentativas tem seu valor inicializado com o resultado de (LIMITE/2)-1, ou seja, na divisão do valor do nosso símbolo LIMITE por 2, menos 1 (no caso atual, o valor final é 4).
Você deve estar se perguntando: “usar um símbolo com #define não seria mais interessante?”
Bem, nesse caso não. Perceba que o limite de tentativas é determinado baseando-se no valor de LIMITE, e o #define não realiza per se contas ou qualquer processamento: tudo o que ele faz é dizer que (1) existe um símbolo e (2) qual seu valor. Se modificarmos o valor de LIMITE, teríamos que modificar o valor desse novo #define manualmente.
Você pode pensar então: “por que não tornar a fórmula um símbolo via #define?”. A ideia parece boa, mas ela cai em um problema: quando você substituir o símbolo, você obrigaria o sistema a repetir várias vezes uma determinada conta. Podemos pensar em desktops com gigas de poder de processamento e isso aparentemente ser uma boa ideia, mas pense em um sistema embarcado e você verá que uma situação como essa iria provocar redução na velocidade do programa.
Já com a variável constante, temos uma situação onde a variável não poderá ser modificada mas ainda assim será calculada apenas uma vez, apenas exigindo uma recompilação no caso de uma mudança de LIMITE (o que iria acontecer de qualquer maneira), pois ao compilar, LIMITE seria substituído pelo seu valor.
“Eu tenho que OBRIGATORIAMENTE inicializar uma variável constante no momento em que a declaro?”, você deve estar se perguntando. Na realidade, da mesma forma que ocorre com as variáveis comuns, as variáveis constantes PODEM ser inicializadas após a declaração. O que é impedido é que uma variável constante receba OUTROS VALORES após a inicialização. Como constantes são normalmente usadas para delimitar de alguma forma o comportamento do programa (no nosso caso, utilizaremos limiteTentativas para delimitar o máximo de tentativas que uma pessoa terá de adivinhar o número “pensado” pelo computador), é uma boa prática inicializá-la antes de qualquer entrada de dados.
Bem, acho que fechamos aqui a questão das variáveis constantes, à exceção de uma coisa: por que logo abaixo vem uma segunda linha de declarações int?
Quando você coloca várias declarações e/ou inicializações de variáveis em uma mesma linha, o compilador irá entender que todas elas tem o mesmo comportamento. Se colocássemos as int em questão junto com a declaração const int de limiteTentativas, essas variáveis também seriam declaradas constantes, o que não é o comportamento desejado. Nesse caso, a regra é clara: declarações de constantes devem ser separadas das declarações de variáveis comuns.
Bem, agora que vimos o bastante sobre constantes para entendermos o programa, sigamos em frente.

rand(), srand() e o gerador de números pseudo-aleatórios:

Vamos dar uma olhada nesse bloco de código:

srand(time(NULL)); // Serve para modificar a tabela de números pseudo-aleatórios
numeroPensado=rand()%LIMITE+1;

A primeira linha utiliza duas funções: a primeira é srand(), da stdlib.h, que recebe um long int como entrada. Essa função serve para modificar a tabela de números pseudo-aleatórios do sistema, usando o long int de entrada como parâmetro para essa modificação. No caso, usamos a função time(NULL), da biblioteca time.h, que devolve o horário do sistema atual em UNIX TimeStamp (o número de segundos passados desde as 0:00 do dia 01/01/1970 até o momento atual). Na verdade, ela retorna o epoch time (outra forma pela qual pode ser chamado o UNIX TimeStamp) para qualquer data passada de maneira correta (não entraremos nesse detalhe aqui).
Por que chamamos os números do sistema de pseudo-aleatórios?
Porque o computador não consegue, pela própria natureza lógica, gerar um padrão 100% aleatório. O que pode ser feito é utilizar-se algoritmos que gerem números, considerando-se a probabilidade de sua ocorrência em sequencia, que se aproximem da aleatoriedade e montar uma tabela com ela. O problema é que, ao se “pegar” um número nessa tabela é que, caso ela não seja “inicializada” de maneiras diferentes entre cada execução, o valor deixará de ser aleatório, ainda que continue sendo não-determinístico (ou seja, você não consegue, a partir das sequencias de números, determinar com precisão as fórmulas matemáticas que as geraram). Para evitar esse problema, utilizamos um comando, o srand(), que irá modificar a semente da tabela de números pseudo-aleatórios, o que irá garantir que, a cada execução, como o Timestamp será diferente, a semente da tabela será diferente e, por sua vez, a saída da mesma será diferente (se quiser saber mais sobre a idéia de geração de números pseudo-aleatórios, dê uma olhada nesse artigo da Wikipedia). De qualquer modo, sabemos que o srand() irá garantir que os números “aleatórios” tenham esse comportamento o mais próximo possível da realidade.
Na linha seguinte, utilizamos rand() para obter um número aleatório. Porém, esse comando não nos permite definir qual o valor máximo a ser obtido, normalmente obtendo qualquer valor entre 0 e uma definição chamada RAND_MAX (normalmente o limite de um unsigned int). A opção padrão é obter o resto da divisão inteira entre o maior número que desejamos e o valor retornado. Nesse caso, obtemos um número que vai de 0 ao limite estipulado menos 1. No nosso caso, queremos um número entre 1 e o LIMITE estipulado, por isso adicionamos 1 ao valor obtido (lembre-se da prioridade de execução da operação de resto em relação à soma).
Bem, acho que já falamos demais sobre rand(), até porque não tem como aprofundar nesse caso sem entrar uma teoria extremamente complexa e totalmente fora do nosso escopo atual (e muito provavelmente futuro). Tudo que é preciso saber é que precisaos usar srand() antes de rand() para garantir o comportamento aleatório e que precisamos utilizar a operação resto para “limitar” o valor de rand().

Laço de repetição condicional – do {…} while e operações relacionais e lógicas:

Em seguida, vemos que ele irá apresentar o desafio ao usuário e entrará no bloco de código abaixo:

  do
    {
      printf(“Quantas tentativas você acha que precisa para descobrir ele? “);
      scanf(“%d”,&maximoTentativas);

      if (maximoTentativas<1)
         printf(“Você precisa tentar ao menos uma vez! :P\n”);
      if (maximoTentativas>limiteTentativas)
         printf(“%d tentativas? Tá querendo demais também! :P\n”,maximoTentativas);
    } while (maximoTentativas<1 || maximoTentativas>limiteTentativas);

No nosso caso, nos focaremos nos comandos em ciano. Eles representam um tipo de controle de fluxo que chamamos de repetição (ou iteração, em alguns lugares) condicional. O nome pomposo apenas quer dizer que o laço em questão vai ser executado enquanto uma determinada condição for cumprida. No nosso caso, esse comando funciona assim:

  do
    {
       <…>
    } while (<condicao>);

O que fazemos aqui é indicar que, enquanto a condicao estipulada for verdadeira, o programa continuará no laço.

Mas como construir uma condição lógica? Para isso, C nos fornece os operadores lógicos, que comparam valores e retornam “verdadeiro” ou “falso” conforme a situação.
Aqui cabe um parenteses antes de entrarmos nos operadores: o C não possui um tipo booleano (ou seja, verdadeiro ou falso) específico. Para o C, um valor 0, NULL (que é definido por padrão como 0) ou “” (string vazia) como falso e qualquer outro valor como verdadeiro. Isso é importante pois existe um bug em C muito comum que iremos discutir adiante.
Bem, dito isso, os operadores lógicos em C padrão são: 

Operador  

>= 

<= 
== 
!=
&&
||
!
Ação  
Maior do que
Maior ou igual a
Menor do que 
Menor ou igual a 
Igual a 
Diferente de
E lógico
OU lógico
NÃO lógico

(fonte: curso de C da UFMG)
Os operadores lógicos são escritos de maneira similar aos matemáticos, sempre comparando um valor a outro. No caso, por exemplo, do comando acima, temos três expressões:

  • primeiro, uma comparação maximoTentativas<1;
  • depois, uma comparação maximoTentativas>limiteTentativas;
  • e por fim, uma operação lógica ou entre os dois;

Como isso é resolvido no C? Primeiro, o sistema irá analisar se o maximoTentativas<1. Caso seja, ele nem irá fazer a segunda comparação maximoTentativas>limiteTentativas, pois o fato de a comparação lógica ser OU vai fazer o programa aceitar como verdadeira a condição e continuará no laço (chama-se a isso short-circuit logical analysis – análise lógica de curto-circuito, e parte do princípio que uma vez que tenhamos garantido que uma condição é sabidamente verdadeira ou falsa, não há necessidade de continuar-se processando a lógica em questão). Caso contrário, ele irá tentar a segunda comparação, maximoTentativas>limiteTentativas, para verificar se ela é verdadeira ou falsa e, portanto, a condição o será.
Parece meio confuso, mas é só pensar com calma que você irá entender. Procure imaginar valores para maximoTentativas e pense em qual será a resposta (lembre-se: no caso atual, limiteTentativas é 4).
O que acontecerá se o valor não fizer nenhuma das duas condições estipuladas ser verdadeira? Simples: a expressão como um todo será falsa (0) e o laço do…while irá se encerrar normalmente, com o programa seguindo adiante. Ou seja, somente quanto o usuário entrar com um número máximo de tentativas maior que 1 e menor ou igual que o limite de tentativas estipulado (no caso, 4), o laço será interrompido.
Temos uns ifs engraçadinhos dentro desse laço, mas não falaremos sobre ele agora. Vamos então seguir em frente.
Para fecharmos esse tópico, existe uma versão do do…while chamada simplesmente while. Ela é representada assim:

while(<condicao>)
{
    <…>
}

e sua única diferença em relação ao seu “irmão” é que, caso ao chegar na entrada do laço a condição for satisfeita, o sistema sequer irá entrar no bloco de código em questão. O do…while irá executar ao menos uma vez, uma vez que a condição é testada no momento da saída do laço, diferentemente do while, que é executada quando da entrada no laço.

Iterações – o comando for:

Após nosso usuário ter determinado quantas tentativas ele quer ter para acertar o número mágico, vamos então dar a chance a ele.

  for (i=1; i<=maximoTentativas;i++)
    {
      printf(“Vamos lá! %da. tentativa! “,i);
     
      scanf(“%d”,&numeroDoUsuario);

      if (numeroDoUsuario==numeroPensado)
      {
         printf(“Parabéns! Você acertou!\n”);
         ENCERRA
         return(0);
      }
         else
         {
           printf(“Não pensei em %d. “, numeroDoUsuario);
           if (numeroDoUsuario<numeroPensado)
              printf(“Pensei em um número maior.\n”);
           if (numeroDoUsuario>numeroPensado)
              printf(“Pensei em um número menor.\n”);
         }
    }

Aqui temos um número delimitado de tentativas por máximo de tentativas. Desse modo, podemos utilizar um outro comando de laço, o for, que é um outro caso de laço de repetição ou iteração. Diferentemente do do…while, porém, o for é uma iteração não-condicional. Na verdade, ele é condicional, mas ele é mais usado em situações na qual se espera que ele se execute um determinado número de vezes (pode-se usar ele até como um while diferente, mas não é boa prática e não falaremos sobre isso aqui).
O for tem como estrutura a seguinte:
  for (<inicializacao>; <condicao>; <iteracao>)
    {
       <…>
    }

No caso, ele é um pouquinho complexo, portanto vamos dar uma olhada no seu comportamento:

  • no momento em que o programa chega no for, a primeira coisa que é feita é executar os comandos em <inicializacao>. No nosso caso, ele inicializa a variável i em 1. Essa inicialização pode ser feita como desejado, mas restringindo-se a um comando (ou bloco de código);
  • Em seguida, o bloco de código do for será executado;
  • Ao terminar de executar-se o bloco de código do for, ele executa o comando que está em <iteracao>. No nosso caso, utilizamos um operador matemático ++, que é utilizado em C para adicionar-se um ao valor da variável ao qual ela sucede. Na verdade ele se comporta de uma maneira mais complexa, mas para o momento basta entender que i++ seria o equivalente a i=i+1;
  • Depois de executar a <iteracao>, o sistema irá verificar se a <condicao> colocada é verdadeira. Caso o seja, ele irá interromper o laço for da mesma forma que o while, caso contrário ele entrará e executará uma nova iteração. No nosso caso, testamos se i ainda é menor ou igual ao número de limiteTentativas. Lembrando que C não possui tipo booleano e que basta que o valor seja 0 ou “” para ser considerado falso e, portanto, o laço for seja interrompido;

Bem, acho que isso deve ter deixado claro como o for funciona. O primeiro printf do programa deve deixar claro que você vai passando por várias iterações até acertar o “número mágico” “pensado” pelo nosso programa (o do rand() do início do programa). Mas como o programa saberá quando fomos bem-sucedidos?
Para isso existe o nosso próximo comando.

Execução condicional – o comando if:

OK… Paramos falando sobre como o nosso programa saberá que fomos bem sucedido. Veja que temos um scanf lendo o que o personagem entrou naquela tentativa. Precisamos de um comando para decidir fomos bem sucedidos ou não em acertar a descoberta do número “mágico”  “pensado” pelo programa. Quem cuida disso é comando if do bloco abaixo:

      if (numeroDoUsuario==numeroPensado)
      {
         printf(“Parabéns! Você acertou!\n”);
         ENCERRA
         return(0);
      }
      else
      {
         printf(“Não pensei em %d. “, numeroDoUsuario);
         if (numeroDoUsuario<numeroPensado)
            printf(“Pensei em um número maior.\n”);
         if (numeroDoUsuario>numeroPensado)
            printf(“Pensei em um número menor.\n”);
      }

O if é uma estrutura importante de programação, pois ele executa blocos de código caso a condição determinada seja verdadeira:

      if (<condicao>)
      {
        <…>
      }
      else if (<outra_condicao>)
      {
        <…>
      }
      else
      {
        <…>
      }

O if irá executar o bloco de código abaixo da instrução caso condicao seja verdadeira. Caso contrário ele pode:

  1. Testar outras condições. Para isso, utiliza-se else if, com uma nova condição e um bloco de código adequado;
  2. Executar um código para exceção. Para isso, utiliza-se else e o bloco de código adequado;
  3. Não fazer nada;
Agora, vamos tentar ler esse trecho de código. A primeira coisa que ele testa é se numeroDoUsuario==numeroPensado, ou seja, se o número que o usuário entrou é igual ao número “pensado” pelo sistema. Caso seja, ele indica que foi bem sucedido e encerra o mesmo (perceba que temos um return(0) dentro do if. Isso é permitido pelo C). Perceba que o operador de relação de igualdade é ==. NUNCA CONFUNDA COM O OPERADOR DE ATRIBUIÇÃO, =. Esse é um bug (falha de programação) muito comum que é provocado por falta de atenção na programação. No caso de colocar =, o valor de numeroDoUsuario seria substituído pelo de numeroPensado. A não ser que numeroPensado fosse 0 (o que é impossível, devido à lógica do programa) o programa acusaria verdadeiro independentemente do valor realmente ser (lembre-se, qualquer valor diferente de 0 ou “” ou NULL é considerado pelo C como verdadeiro em relações lógicas).
Bem, dito isso, vamos continuar a analisar nosso programa. Caso a condição acima não seja verdadeira, ele irá ignorar o bloco abaixo de if e irá ver que temos um else com um bloco de código. Ele então entrará nesse bloco de código. A primeira coisa que ele irá fazer é dizer que não pensou no numeroDoUsuario e testar se numeroDoUsuario<numeroPensado. Caso seja verdadeiro, ele irá imprimir na tela uma mensagem dizendo que pensou em um número maior que o que o usuário entrou. Caso contrário, irá verificar se numeroDoUsuario>numeroPensado. Se verdadeiro (provavelmente será, se tudo mais deu errado), irá imprimir que pensou em um número menor que o que o usuário pensou.
Os ifs dessas linhas possuem uma característica interessante que é o fato de terem comandos diretamente abaixo deles, e não dentro de blocos de código. Na realidade isso deve-se ao fato que if, while, for e afins, todos eles possuem uma característica de executar na realidade apenas um comando. A diferença é que, para o compilador C, um bloco de código (lembre-se: blocos de código são comandos isolados pelos colchetes {}) são considerados comandos isolados. Portanto, um bloco de código == um comando.
Lendo com calma o código você irá compreender muito bem o mesmo. Leia e releia o códigoo e faça exercícios mentais para entender o código do if.

Quando tudo dá errado – considerações:

OK… Mas e caso o usuário não acerte mesmo depois de ter tentado o número de vezes que ele desejou (maximoTentativas).
Isso ocorre quando a variável i, após a iteração do for, ficar com um valor maior que maximoTentativas, tornando a condição  i<=maximoTentativas falsa e saindo do laço for (ao atingir o limite de iterações estipulado). Para encerrar o programa, nós fazemos o sistema imprimir uma mensagem dizendo qual o número no qual ele pensou e saindo do mesmo.
Antes de irmos para as “brincadeiras” finais, algumas considerações:

  • Os operadores relacionais que mostramos anteriormente só servem para valores numéricos ou para o tipo char. Em especial para strings, a biblioteca string.h traz funções que nos permite testar igualdade entre textos. Veremos eles melhor no futuro;
  • Ainda não esgotamos o assunto controle de fluxo. Em especial falaremos ainda sobre um tipo de execução condicional para múltiplos valores e sobre como “quebrar” laços como o for e o while;
  • É possível aninhar ifs ccom múltiplos else if e afins. Porém, tome cuidado ao usá-los: além de tornar o código de difícil leitura, você pode ter problemas dependendo do compilador (normalmente o C exige no mínimo 8 “níveis” de ifs aninhados, mas esse valor não é obrigatório);

Bem, dito isso, vamos fazer algumas sugestões para “brincadeiras” com o nosso código:

  • Comente ou remova o srand() e veja o comportamento do gerador de números pseudo-aleatórios;
  • Tente reescrever o código para tornar maximoTentativas uma constante. Lembre-se que não é necessário inicializar o valor imediatamente, podendo ser inicializado em um momento futuro;
  • No caso de quando o usuário erra o número “pensado”, você pode ter percebido que os dois ifs são redundantes. Tente reescrever o código usando apenas um if;
  • Da mesma forma que existe o operador ++, existe o operador , que subtrai um da variável que o antecede. Considerando isso e o funcionamento do laço for, tente reconstruir o laço para não ter uma condição no sentido exato da palavra. Dica: lembre-se que C trata 0 como falso;
  • Para ter uma idéia do problema que pode ocorrer quando se confunde o operador de igualdade com o de atribuição, modifique o código removendo um dos sinais de igual de if (numeroDoUsuario==numeroPensado) e veja o que acontece com o programa. Para maior clareza, coloque um printf que apresente o número “pensado” antes do usuário entrar o seu número e digite outro completamente diferente. Coloque também um printf mostrando o numeroDoUsuario após o bug;
  • Para entender a questão das definições, altere o valor de LIMITE e veja como o programa se comporta. Como dica, o cálculo do limiteTentativas baseia-se na idéia de buscar-se o número sempre indo na metade do que é válido. Por exemplo: computador escolhe 6. Na primeira interação, tento 5, e ele me fala que pensou um maior. Tento 7 (metade do bloco “acima de cinco”) e ele me diz que pensou um menor. Tento 6 e acerto;

Bem, semana que vem iremos reeforçar a teoria dos operadores (lista completa e exemplos) e daremos uma terminada na questão dos controles de fluxo. Até lá, divirtam-se!

Tipos de Dados e Variáveis

Olá a todos!

Devem ter “brincado” bastante com o HelloWorld.c, não? Então vamos começar agora a estudar de maneira mais aprofundada algumas coisas que já vimos nele. No caso, falaremos sobre os tipos de dados e variáveis. Portanto, vamos ver um programa um pouco mais complexo que o HelloWorld.c. Novamente digite o programa abaixo como ele está apresentado:

#include <stdio.h>
#include <stdlib.h>

/*
* (c) 2010 GPL
*/

int main (int argc, char** argv)
{
  int primeiro=0, segundo=0, soma=0, resto=0, divisaoInteira=0;
  float divisaoFlutuante=0;
  char nome[80];
  char palavra[20];
  /*
  * O comando abaixo inicializa as strings nome e palavra
  */
  memcpy(nome,”\0”,sizeof(nome));
  memcpy(palavra,”\0”,sizeof(palavra));

  printf(“Olá! Digite seu nome, por favor!\n”);
  fgets(nome,sizeof(nome),stdin);

  printf(“Digite uma palavra que devo exibir e dois números para eu fazer umas contas.\n”);
  scanf(“%s %d %d”,palavra,&primeiro,&segundo);

  getc(stdin);

  soma=primeiro+segundo;
  divisaoInteira=primeiro/segundo;
  resto=primeiro%segundo;
  divisaoFlutuante=(float)primeiro/(float)segundo;

  printf(“Olá, %s\n”,nome);
  printf(“Você me pediu para exibir %s e os números %d e %d\n”,palavra,primeiro,segundo);
  printf(“Algumas continhas!\n%d+%d=%d\n”,primeiro,segundo,soma);
  printf(“%d:%d=%d(Resto %d)”,primeiro,segundo,divisaoInteira,resto);
  printf(” ou %7.2f\n”,divisaoFlutuante);
  printf(“Divertido, não? Aperte qualquer tecla para continuar…”);
  getc(stdin);
  return(0);
}

Como fizemos no HelloWorld.c, vamos dar uma “dissecada” no código. Porém, diferentemente do HelloWorld.c, não iremos explicar algumas partes do código, pois elas serão similares ao que vimos no Hello World. Perceba que o código guarda similaridades como a função main e o #include , portanto o que dissemos lá continua valendo. Vamos às explicações “únicas”.

Variáveis – declarando e inicializando:

Como você deve ter reparado, adicionamos um novo #include. No caso, estaremos incluindo a biblioteca string.h, que também é parte da biblioteca-padrão do C. string.h é uma biblioteca que é voltada, como o nome diz, à manipulação de strings, ou seja, cadeias de caracteres. Porém, como veremos logo, as strings em C são bem diferentes das suas similares em outras linguagens de programação, e seu comportamento deve ser bem avaliado.
Logo após main(), temos um trecho de código que não vimos no Hello World

int primeiro=0, segundo=0, soma=0, resto=0, divisaoInteira=0;
float divisaoFlutuante=0;
char nome[80];
char palavra[20];

Essas linhas fazem a declaração de algumas variáveis. Como o assunto é extenso, vamos gastar algum tempo falando sobre ele aqui.
Em C, as variáveis representam espaços de memória que o compilador irá preparar para determinadas funções para uso do programa. Toda variável tem um tipo pré-determinado (ou estático) e só pode receber valores daquele tipo pré-determinado. No caso, em C, os principais tipos são:

Tipo Num de bits Intervalo
Inicio Fim
char 8 -128 127
unsigned char 8 0 255
signed char 8 -128 127
int 16 -32.768 32.767
unsigned int 16 0 65.535
signed int 16 -32.768 32.767
short int 16 -32.768 32.767
unsigned short int 16 0 65.535
signed short int 16 -32.768 32.767
long int 32 -2.147.483.648 2.147.483.647
signed long int 32 -2.147.483.648 2.147.483.647
unsigned long int 32 0 4.294.967.295
float 32 3,4E-38 3.4E+38
double 64 1,7E-308 1,7E+308
long double 80 3,4E-4932 3,4E+493
A bem da verdade, nessa tabela os números de bits são indicados conforme a padronização C exige. Porém, existem casos em que essa padronização não é seguida e isso pode comprometer o uso de determinados tipos . Veremos uma solução ainda ao estudarmos esse programa.
De qualquer modo, podemos dividir os tipos de variáveis em C em três grupos fundamentais: 

  • Caracteres;
  • Inteiros e;
  • Números de ponto flutuante;

Na realidade, os caracteres são entendidos como um subgrupo dos inteiros, mas em 99% das aplicações, não utiliza-se os caracteres como números, embora alguns truques interessantes apareçam daí (veremos algum deles em posts futuros, quando precisarmos lidar com condições lógicas em C).
Aqui é importante ser feita uma ressalva MUITO SÉRIA que não foi feita no Hello World porque não era exatamente o momento: a linguagem C é considerada CASE-SENSITIVE.
“Que diabo é isso?“, você deve estar se perguntando. Isso quer dizer que todo compilador C diferencia as letra maiúsculas das minúsculas. Isso quer dizer que para ele main e MAIN e Main são três coisas diferentes. Isso gera algumas coisas estranhas: por exemplo, o comando return, como vimos no Hello World, é uma palavra reservada e, portanto, não pode ter nenhum outro comando com esse nome. Porém, se você quiser criar uma função Return e outra RETURN, você PODE, uma vez que para o compilador C, cada uma dessas é diferente uma das outras e por sua vez são diferentes de return. Para facilitar a vida, existem certas convenções que são adotadas para evitar confusões, sendo que a principal é: variáveis e funções devem ser nomeadas em minúsculas, sendo que, caso sejam usadas duas palavras, as opções são (1) usar-se hifen () ou underscore (_) para separá-las ou (2) colocar a primeira letra de cada palavra à exceção da primeira em maiúsculo.
Dito isso, vamos a mais uma regrinha rápida: como criar um nome de variável válido. O C considera válido um nome de variável que obedeça às seguintes regras:

  1. Tenha no máximo 32 caracteres de tamanho: na realidade, para o código fonte o nome pode ser maior. Porém, apenas os primeiros 32 caracteres são considerados como nome da variável. Portanto, se o seu programa tiver umaVariavelCujoNomeEhGigantePorqueOProgramadorAchouLegal e tiver também umaVariavelCujoNomeEhGigantePorqueOProgramadorTambemAchouLegal, ou compilador não irá apresentar mensagens de erro, mas você terá erros de lógica, pois para o compilador o nome de ambas as variáveis será umaVariavelCujoNomeEhGigantePorq.
  2. Seja composto exclusivamente por:
  • Letras sem acentos ou caracteres específicos (a-zA-Z);
  • Números;
  • Underscore (_);
  1. Não pode ser iniciados por número, embora underscore seja aceito;
  2. Não seja nome de variável ou função previamente declarada ou ainda de palavra reservada;

Considerando-se essas regras, para o compilador qualquer nome é válido. Isso porque o nome da variável serve apenas para identificar ao programador o valor a ser manipulado (na realidade, o compilador faz uso dos nomes de variáveis para traduzir os nomes em endereços a serem vistos quando o programa for compilado e executado. Veremos mais sobre isso quando entramos no assunto de ponteiros). Porém, é interessante que o programador tome cuidado ao dar os nomes de variáveis de modo a poder compreender o que elas fazem: embora abc123 seja um nome de variável aceitável para o compilador, notaDoAluno é um nome tão válido quanto e mais claro para o programado, então deveria ser uma opção mais acertada em um programa que a opção anterior.
Dito isso, vamos voltar aos tipos de variáveis: como dissemos, existem dois tipos numéricos importantes, os inteiros e os números de ponto flutuante. No caso, os inteiros são sempre identificado como int (à exceção de char e byte, que são reconhecidos como ponteiros, ainda que normalmente não sejam usados como tal), embora possam receber modificadores. Esses têm a ver com o tamanho em bits do mesmo (o que também afeta o valor máximo que o mesmo pode representar) e como o fato de ter sinal ou não (o sinal é sempre representado por um bit, e portanto pode modificar o valor máximo que o mesmo pode representar). O int básico é signed (tem sinal) e short (em seu menor tamanho). Na tabela anterior perceba que int, short int, signed int e signed short int tem o mesmo valor em número de bits (16) e valores mínimos e máximos (de -32768 a 32767).
Comparemos primeiro o int básico com o unsigned int (inteiro sem sinal). O número de bits é o mesmo, mas o valor mínimo muda para 0 (ou seja, essa variável não aceita valores negativos). Se compararmos o int com o long int (inteiro grande), embora o valor mínimo seja negativo, em ambas as pontas ele é maior que o int normal por ter um maior número de bits. É importante ter isso em mente ao criar-se um programa: em certas situações (por exemplo, contagem de mercadorias) um long int pode ser mais útil que um int. Em outras aplicações (por exemplo, uma aplicação de votação), usar um unsigned int pode permitir a você usar com maior eficiência a memória do sistema (ao não ser obrigado, por exemplo, a usar um long int).
Na parte dos números de ponto flutuante, o C utiliza float, double e long double. Em todos os casos, o C tem seus limites definidos em potências de 10. Porém, devido às conversões de base e perdas de precisão, é importante ter em mente que valores de ponto flutuante perderão precisão conforme o tipo utilizado: quanto mais bits, mais preciso o valor, e portanto menor chance de erros de cálculo serão sentidas. Porém, é importante lembrar que existe consumo de memória, e que dependendo da aplicação isso deve ser levado em conta.
De qualquer forma, existe pouca coisa a comentar a mais sobre os tipos de variáveis. Voltemos agora ao nosso código de exemplo:

int primeiro=0, segundo=0, soma=0, resto=0, divisaoInteira=0;
float divisaoFlutuante=0;
char nome[80];
char palavra[20];

São declaradas cinco variáveis inteiras (int), com os nomes primeiro, segundo, soma, resto e divisaoInteira. Se você só declarar uma variável, o valor inicial dela será aleatório. Não é considerado uma boa prática, ainda que permitido pelo C, declarar-se uma variável sem inicializá-la, pois isso pode ter conseqüências estranhas. No caso, as variáveis inteiras em questão foram inicializadas em 0, ao colocar-se diante delas um sinal de igual. O sinal de igual em C é um operando de atribuição de valor, ou seja, ele indica que a varíavel colocada antes do sinal de igual irá receber o valor colocado depois do sinal de igual. É importante deixar claro por causa dos operadores lógicos, que veremos em outro exemplo, e como funciona as comparações lógicas em C.
Uma coisa que é importante dizer é que uma variável pode ser declarada em uma linha e inicializada em outra. Embora o código como mostrado seja considerado mais claro e seja uma melhor prática em C, o código abaixo:

int primeiro, segundo, soma, resto, divisaoInteira;
primeiro=0;
segundo=0;
soma=0;
resto=0;
divisaoInteira=0;

Embora menos “elegante” é tão correto quanto no caso da linha int primeiro=0, segundo=0, soma=0, resto=0, divisaoInteira=0;. Essa prática de inicializar variáveis no momento da declaração, porém, impede de cometer-se erros em outros pontos, em especial quando utilizamos as variáveis de ponteiro, que veremos futuramente.
Bom, vamos continuar adiante. Após declarar os cinco inteiros, é declarado uma váriavel de ponto flutuante float divisaoFlutuante=0. Alguns autores sugerem que crie-se a prática de inicializar variáveis de ponto flutuante com 0.0, pois essa é, digamos assim, a forma de indicar um 0 de ponto flutuante sem provocar typecast (veremos isso adiante). Particularmente, não acho essa uma boa opção. O ganho de desempenho não é tão alto e a legibilidade fica um pouco confusa. Mas tem aplicações, quando você possui sistemas com baixa potência (microprocessadores, por exemplo), isso pode te oferecer um ganho de performance.
Após isso, temos duas strings sendo declaradas, uma chamada nome de 80 caracteres e uma chamada palavra de 20 caracteres. Aqui na realidade não estamos declarando uma strings, e sim uma matriz de caracteres. O C lida com tipos discretos, não possuindo tipos “compostos”, como uma string. No C, uma string é composta pelos caracteres e um símbolo especial de terminação, chamado caracter nulo, ou null-character em inglês. Essa declaração apenas separa o espaço necessário para armazenar 80 caracteres (incluindo o terminador null-character). Veremos mais sobre isso em matrizes e ponteiros. Por enquanto, você sabe que essa é a forma de criar uma string em C e que você não consegue criar uma string “dinâmica” (ou seja, de tamanho ilimitado) em C.

memcpy, sizeof e cuidados com memória:

OK…. Até agora vimos a declaração de variáveis e sua inicialização, assim como a questão das strings em C. Vamos seguir adiante que esse programa ainda tem muita coisa a ser vista.
Após as declarações de variáveis, vemos um novo comando: 

/*
* O comando abaixo inicializa as strings nome e palavra
*/
memcpy(nome,”\”,sizeof(nome));
memcpy(palavra,”\”,sizeof(palavra));

O memcpy é um comando da biblioteca string.h que copia uma determinada string para outra um determinado número de vezes (memcpy vem de MEMory CoPY – Cópia de memória). Esse comando exige três parâmetros:

  • O primeiro é o nome da váriável para a qual serão copiadas as informações (na verdade a leitura é um tanto mais complexa que isso. Iremos nos aprofundar ao lidar com ponteiros);
  • O segundo é a string a ser copiada;
  • O terceiro é o número de vezes que ela será copiada;

Na primeira linha, o primeiro parâmetro é a variável nome. O segundo parâmetro é “\”, uma string de um caracter só. No caso, estamos utilizando um caracter de controle similar ao \n do printf (que vimos no Hello World). No caso, “” é o caracter de controle que representa o terminador nulo que indica o fim de uma string.
O terceiro parâmetro é interessante de ser avaliado com calma, uma vez que ele mostra uma nova palavra reservada do C, chamada sizeof. O comando sizeof recebe como parâmetro uma váriavel e devolve, em bytes, o tamanho da mesma. No caso, estamos usando sizeof para obter esse valor em bytes e usá-lo como número de vezes em que o caracter nulo deverá ser executado.
Você pode estar se perguntando: “qual a vantagem disso, se sabemos o tamanho da string palavra?” O problema é que o C, em seu padrão, apenas RECOMENDA tamanhos mínimos para os tipos de variável, não os OBRIGA. Desse modo, quando precisamos de ter certeza, como nesse caso, é interessante que utilizemos sizeof para que seu valor seja correto durante a execução, tornando-o mais portável e evitando “números mágicos” de difícil análise em caso de erro. Isso é ainda mais importante quando temos que usar, por exemplo, alocação de memória dinâmica (que veremos no futuro) com tipos numéricos cujo tamanho pode variar de máquina para máquina (máquinas para processamento científico podem usar tipagens numéricas com um grande número de bytes para alcançar níveis de precisão condizentes às necessidades científicas). Por agora, basta saber que o uso de sizeof para determinar tamanhos de variáveis é considerado uma boa prática de programação.

Entrada de dados – fgets,scanf, entrada padrão, ponteiros e o operando de endereçamento &:

Uma vez inicializadas as variáveis, estamos em “ambiente seguro” para seguirmos em frente. Como a maioria dos programas, precisamos entrar alguns dados. No caso faremos isso de algumas formas diferentes, usando comandos diferentes mostrados no trecho de código abaixo:

printf(“Olá! Digite seu nome, por favor!\n”);
fgets(nome,sizeof(nome),stdin);

A linha em roxo mostra uma forma de ler-se informação de uma entrada de dados. No caso, utilizamos o comando fgets, que faz parte da biblioteca stdio.h. Esse comando possui três parâmetros:

  • O primeiro parâmetro é o nome da variável que irá receber as informações (no nosso caso, nome);
  • O segundo é o número máximo de caracteres a serem lidos (no caso, novamente usamos sizeof para obter o número máximo de bytes);
  • O terceiro é um nome que indica qual a fonte dos dados a ser usada. No caso nosso, estamos usando o nome “stdin“.

fgets irá ler da fonte de dados indicada todos os caracteres possíveis até que (1) seja alcançado o limite de caracteres determinados no segundo parâmetro seja alcançado ou (2) um terminador nulo seja lido ou (3) seja lido um caracter de nova linha (\n). Na prática, essa última condição quer dizer que o ENTER do teclado tenha sido pressionado, sendo que o caracterdo ENTER (ASCII 13, ou \n) será armazenado.
O nome stdin, mostrado no terceiro parâmetro, é um símbolo padronizado do C, definido dentro do stdio.h. Esse símbolo representa a entrada padrão do programa C. Normalmente, a entrada padrão de um programa é o teclado, da mesma forma que a saída padrão é o monitor de vídeo. Porém, em muitos sistemas operacionais é possível utilizar-se estruturas e comandos que redirecionam a entrada e/ou a saída padrão para outras fontes (como a saída ou entrada de um programa anterior ou posterior, no que é chamado de piping). No nosso caso, estamos usado stdin para que o sistema leia o que vier do teclado do operador.
Você deve se perguntar: “Putz… que complicado… não dá para facilitar?”. Na realidade, até dá, mas aqui estamos adotando uma prática segura que é usar comandos em C que apenas utilizem a memória que foi determinada.
O C por natureza não controla o uso de memória. No caso das strings, isso é muito importante: embora declaremos um tamanho para a string, isso não quer dizer que esse tamanho será obedecido. Isso se deve ao fato de o C considerar que uma string é uma matriz ou um ponteiro de caracteres e o C não fazer nenhum controle de onde o ponteiro irá ir. Na realidade, entraremos mais aprofundadamente nesse conceito quando falarmos especificamente de ponteiros, mas aqui cabe o “parênteses”.
Existe uma função da biblioteca stdio.h, chamada gets. Ela exige apenas um parâmetro, o nome da variável que irá receber as informações, sendo muito mais simples e rápida que fgets. Porém, como ela não possui algo que diz a ela o tamanho dessa variável (na verdade, da matriz), ela irá escrever as informações onde der, inclusive sobrescrevendo qualquer coisa que esteja além da memória utilizada pela matriz uma vez que essa esteja “cheia”, não importa o que seja. Isso pode comprometer o sistema das mais diversas formas: como não há como saber o que está “imediatamente depois” da sua matriz na memória, você não tem como saber se, por exemplo, o seu código não está gravando sobre trechos de programas do sistema carregados em memória, dados de outras pessoas, etc… Por isso nossa opção por usar um comando mais complexo, mas que garanta que o programa funcionará como esperado.
Bem, acho que já demos a atenção devida ao fgets. Não se preocupe se não compreendeu totalmente o que dissemos agora: mais para frente voltaremos a esse assunto, quando tivermos com alguns conceitos mais bem detalhados, e aí as coisas ficarão mais claras.
Vamos à nossa segunda entrada:

printf(“Digite uma palavra que devo exibir e dois números para eu fazer umas contas.\n”);
scanf(“%s %d %d”,palavra,&primeiro,&segundo);

Aqui estamos usando outro comando de entrada de dados: no caso, estamos usando o scanf, que como os demais comandos de entrada de dados é parte da biblioteca stdio.h. scanf lê as informações da linha digitada e tenta encontrar coisas que casem com o formato desejado, e armazena essa informações nas variáveis desejadas. No caso, ele utiliza símbolos de formato similares aos de printf para “quebrar” a entrada da maneira adequada. No comando apresentado, ele lê uma string (%s) que irá acabar no primeiro espaço lido (importante notar isso), e em seguida lerá dois inteiros (%d), separados por espaço, e armazenará os valores lidos nas variáveisa palavra, primeiro e segundo.
Aqui, você deve ter reparado nos e-comerciais (&) na frente de primeiro e segundo. Isso se deve pelo fato de scanf precisar dos endereços das variáveis em questão, e para isso, precisamos passar ponteiros. Em C, chamamos de ponteiro uma variável que, ao invés de armazenar um conteúdo per se, armazena o endereço da memória onde esse conteúdo se encontra (na realidade, o conceito é mais complexo, mas não vamos aprofundar nele agora). Como primeiro e segundo são variáveis int comuns (ou seja, armazenam conteúdos, e não o endereço), precisamos obter o endereço de memória onde esse conteúdo está., ou seja, o endereço de memória das variáveis Para isso, usamos o operador & antes do nome da varíavel. O operador & é chamado de operador de derreferenciamento, e sua função é indicar que, naquele momento, utilizaremos o endereço da variável em questão, e não o seu conteúdo.
No caso de palavra, porém, não é necessário o operador &, pois toda matriz em C também pode ser usada como ponteiro. Na verdade, no memcpy e no fgets utilizamos os nomes das matrizes como ponteiros. Portanto, uma regrinha de C que acabamos de aprender:

EM C, TODA MATRIZ PODE SER USADA COMO UM PONTEIRO.

Iremos aprofundar essa regrinha quando falarmos de ponteiros mais profundamente. Agora, porém, deve ter ficado claro a utilidade do e-comercial (&). Se não ficou, tudo bem (por agora): ainda veremos muitas vezes o & e logo tudo isso ficará claro.
O scanf, assim como o printf, possui uma grande gama de símbolos de formato. Abaixo deixamos uma tabela com os símbolos de formato do scanf (mais adiante ofereceremos uma para printf):

Código Formato
%c Um único caracter (char)
%d Um número decimal (int)
%i Um número inteiro
%hi Um short int
%li Um long int
%e Um ponto flutuante
%f Um ponto flutuante
%lf Um double
%h Inteiro curto
%o Número octal
%s String
%x Número hexadecimal
Bem, vamos seguir em frente. Esse ponto ficará mais claro com o uso constante do comando scanf, que faremos no futuro.
Antes de avançar, falaremos sobre o comando getc(stdin). Basicamente ele lê um caracter de uma entra indicada (no caso stdin) e devolvê-lo para uma variável. Como não usamos nenhuma atribuição, o valor é simplesmente eliminado do stdin. Usamos esse comando para que qualquer caracter indesejado (em especial, ENTERs não lidos).

Operações matemáticas, operandos matemáticos e typecasting:

OK… Já obtivemos as entradas de nosso usuário, então é hora de fazemos algo com tudo isso. No caso, faremos uma brincadeira boba que é fazer algumas contas com os dois números que o usuário passou, primeiro e segundo:

soma=primeiro+segundo;
divisaoInteira=primeiro/segundo;
resto=primeiro%segundo;
divisaoFlutuante=(float)primeiro/(float)segundo;

Aqui temos um exemplo mais claro de atribuição: as variáveis soma, divisaoInteira, resto e divisaoFlutuante recebem os resultados de cada uma das contas que mandamos o C fazer. No caso, toda operação matemática em C é composta por:

atributo1 operação atributo2

Por exemplo: a varíavel soma irá receber o resultado de primeiro + segundo. Vejamos quais são os principais operadores matemáticos em C:

  • / , * e % = Divisão, multiplicação e resto da divisão inteira;
  • + e = Soma e subtração;
  • = = atribuição;

A seqüência foi propositamente colocada na ordem acima pois o C, assim como a matemática, possui uma ordem de prioridade nos operandos: os operandos / , * e % (Divisão, multiplicação e resto da divisão inteira) possuem uma prioridade mais alta que os operandos + e (Soma e subtração), portanto são executados primeiro em uma expressão complexa, com vários operandos executados ao mesmo tempo. É possível, com o uso de parênteses (), alterar a prioridade das expressões, gerando os valores corretos (de forma equivalente ao que acontece em equações matemáticas). Por exemplo: x * y + z é diferente de x * (y + z). Na primeira, o C (como na matemática) irá primeiro multiplicar x por y e depois somar o resultado a z. Na segunda expressão, primeiro é feita a soma de y e z e depois o resultado é multiplicado por x. Uma dica muito útil é: se você estiver em dúvida quanto à seqüência correta de operações, isole os termos com parênteses aos pares: embora afete um pouco a leitura do programa, é mais interessante que incorrer no risco de gerar resultados errados.
OK, vimos quais são os operandos matemáticos e como eles se comportam. Analisamos o código linha a linha, vemos que na primeira linha é executada uma soma entre os operandos primeiro e segundo e o resultado é atribuído a soma. Na segunda, é feita uma divisão entre os operandos primeiro e segundo. No caso, será feita uma divisão inteira, pois ambas são variáveis inteiras e a variável que que irá receber o resultado (divisaoInteira), também é um int. Em seguida, temos uma operação de resto entre primeiro e segundo que será armazenado em resto. Essa operação de resto (%) deve ser feito apenas com valores inteiros. Mas isso pode ser resolvido com um mecanismo que mostraremos abaixo.
A última linha apresenta uma estrutura importante que existe em C chamada typecast (mudança de tipo). Algumas vezes, precisamos fazer operações matemáticas (entre outras) com varíaveis cujo tipo originalmente não nos permite. Por exemplo, imagine que primeiro é 5 e segundo é 2. Embora indiquemos que divisaoFlutuante tem um tipo float, se não forçarmos o programa a enxergar ao menos um dos valores como float, o resultado armazenado em divisaoFlutuante será 2, ainda que armazenado em memória e com o mesmo comportamento de um número de ponto flutuante. Esse comportamento se deve ao fato de que o C pode converter operandos numéricos para outros tipos, mas apenas quando exista alguma diferença de armazenamento.  Se deixarmos os dois como inteiros, a divisão será feita como inteira (ou seja, dando o resultado 2) e só depois, na hora da atribuição é que o valor será convertido para float, o tipo correto a ser armazenado em divisaoFlutuante.
Para solucionarmos esse tipo de problema, podemos informar ao C que determinado valor, ainda que expressado de um tipo, deve ser tratado como outro naquele momento específico. A isso chama-se em programação de typecast. No caso da última linha, estamos dando typecast (ou, para resumir, cast) nas variável primeiro e segundo. Para dar-se um cast em um valor ou variável, basta anteceder o mesmo com o tipo que o sistema irá adotar naquele momento para o mesmo entre parênteses. No caso, para converter primeiro em float, utilizamos (float)primeiro. Importante dizer que não era necessário dar o cast em segundo, pois o programa, ao perceber que estaria tentando dividir um número de ponto flutuante por um inteiro não dividiria “laranjas com bananas”, por assim dizer, dando ele próprio o cast em segundo (o que é chamado em programação de autocast). Porém, para melhorar a leitura do programa, achei interessante dar o cast manualmente em segundo também. Muitas vezes, fazendo as coisas da maneira correta, você não precisará tanto de casts quanto aparente, pois o compilador irá ele próprio fazer ajustes para provocar autocast dependendo do caso. De qualquer modo, casts podem ajudar a leitura do programa, o que é bem importante.

printf, caracteres de controle e símbolos de formato:

Bem, agora que já fizemos continhas bobas, tá na hora de nosso programa mostrar o que ele fez:

printf(“Olá, %s\n”,nome);
printf(“Você me pediu para exibir %s e os números %d e %d\n”,palavra,primeiro,segundo);
printf(“Algumas continhas!\n%d+%d=%d\n”,primeiro,segundo,soma);
printf(“%d:%d=%d(Resto %d)”,primeiro,segundo,divisaoInteira,resto);
printf(” ou %7.2f\n”,divisaoFlutuante);
printf(“Divertido, não? Aperte qualquer tecla para continuar…”);
getc(stdin);
return(0);

OK, você deve estar se perguntando, temos um monte de printf que vimos lá no Hello World, e que faz saída formatada. E daí?
Bem… E daí que agora temos saídas que o sistema processou e que devemos mostrar ao usuário. Para isso, temos que indicar ao C o que vai ser publicado e como.
O printf, além dos caracteres de controle, aceita símbolos de formato. Lembra quando falamos acima, ao comentarmos o scanf, que ele exigia símbolos de formato para saber o que ler? Pois bem, o nosso amigo printf também aceita símbolos de formato para dizer como os dados deverão ser apresentados pelo sistema. Abaixo copiamos uma tabela de símbolos de formatos (site original – Curso de C da UFMG):

Código Formato
%c Um caracter (char)
%d Um número inteiro decimal (int)
%i O mesmo que %d
%e Número em notação científica com o “e”minúsculo
%E Número em notação científica com o “e”maiúsculo
%f Ponto flutuante decimal
%g Escolhe automaticamente o melhor entre %f e %e
%G Escolhe automaticamente o melhor entre %f e %E
%o Número octal
%s String
%u Decimal “unsigned” (sem sinal)
%x Hexadecimal com letras minúsculas
%X Hexadecimal com letras maiúsculas
%% Imprime um %
%p Ponteiro

Na verdade, a formatação completa é um pouco mais complexa, pois após o % você pode colocar:

  • -, indicando que o preenchimento deve alinhar-se à direta (e não à esquerda);
  • +, indicando que, caso o valor tenha sinal, os símbolos de sinal são mantidos (normalmente sinais de positivo são ignorados);
  • um número, indicando o tanto de caracteres a serem exibidos. No caso de numéricos, se esse número for precedido por 0, os caracteres que normalmente não seriam preenchidos serão preenchidos por 0;
  • uma seqüência x.y indicando (para ponto flutuante) que deve-se exibir o número com um número de caracteres x, sendo que desses y serão casas decimais ou (para inteiros) deve-se exibir no mínimo x números e no máximo y. Vale a regra do 0 mostrada acima;

Existem muito mais complexidades envolvendo a saída formatada. Caso queira saber mais, esse artigo da Wikipedia traz muito mais informações que, na pior nas hipóteses, serão interessantes a título de curiosidade.
A primeira linha, printf(“Olá, %s\n”,nome);, irá imprimir na tela “Olá”, seguido pelo conteúdo da variável nome, e irá pular uma linha. Porém, na execução, você irá perceber que irão ser puladas duas linhas. A segunda linha deve-se ao fato de na leitura de dados por fgets o caracter \n (ENTER) será lido e interpretado pelo printf como parte da varíavel nome. Em outros tópicos ensinaremos uma técnica simples para remover esse \n e deixar a saída mais elegante. Por enquanto, somos obrigados a tratar essa segunda nova linha como um bug do programa.
A segunda linha do trecho que estamos estudando mostra uma característica do printf. Veja a linha em questão:

printf(“Você me pediu para exibir %s e os números %d e %d\n”,palavra,primeiro,segundo);

Essa linha irá exibir a palavra e os números que você digitou anteriormente. Como isso é feito?

Perceba que a linha em questão possui três símbolos de formato, %s, %d e %d. Esses símbolos indicam que o programa espera uma string e dois inteiros para exibí-los na tela. Logo após a nossa string formatada (“Você me pediu para exibir %s e os números %d e %d\n”), colocamos as variáveis que armazenam esses valores na seqüência pedida pela string (no caso, primeiro palavra – a string, depois primeiro e segundo – os dois inteiros), separados por vírgulas entre si e da string a ser formatada. Essa é a construção padrão de um comando printf: uma string a ser formatada, contendo tantos caracteres de controles e símbolos de formato quanto necessário, seguido pelas variáveis a terem seus conteúdos apresentadas em cada posição, na seqüência das mesmas e com um tipo adequado ao símbolo de formato. Colocar menos ou mais variáveis e/ou variáveis de tipo inadequado irá ocasionar erros no momento da compilação (porém, no caso dos numéricos, é permitido fazer o cast do conteúdo da variável para um tipo de dados adequado antes de apresentar o valor em questão).
Em seguida a outra linha possui uma construção propositalmente confusa na string a ser formatada. Vamos vê-la: 

Algumas continhas!\n%d+%d=%d\n

Lembrando que os valores que irão substituir os três símbolos %d são os das variáveis primeiro, segundo e soma. Qual será a saída dessa string:

  • A primeira coisa será imprimir Algumas continhas!, seguido por uma nova linha(\n);
  • Em seguida, irá exibir o conteúdo de primeiro (%d – imaginemos que seja um 5)…
  • … seguido por um sinal de maior (+)…
  • e pelo valor de segundo (%dimaginemos que seja um 2) …
  • … seguido por um sinal de igual (=)…
  • que precede o valor de soma (%d – que, como vimos anteriormente, é primeiro+segundo – nesse caso 5+2, dando um 7)…
  • Terminando por um outro nova linha (\n);
  • O que nos leva à conclusão que, na tela, irá aparecer (considerando os valores acima):

Algumas continhas!
5+2=7

Esse tipo de construção é permitida? Sim! Desde que se siga-se a regra de que tem que haver tantas variáveis quanto símbolos de formato e nos tipos adequados, nada impede que uma mesma string formatada gere 2, 3, até 1000 linhas (na verdade, existe outra regra que falaremos ao mencionar mais claramente strings).
Bem, a linha seguinte, printf(“%d:%d=%d(Resto %d)”,primeiro,segundo,divisaoInteira,resto);, lembra a anterior, à exceção que não terminamos essa linha com um nova-linha (\n). Da mesma forma que uma string a ser formatada pelo printf não precisa ter nenhum símbolo de formato (lembra do printf(“Hello World!\n”);?), o printf pode ter uma linha sem caracteres de controle. Na realidade, nenhum dos dois é obrigatório no comando printf. No nosso caso, ele irá formatar a string e exibir o seu conteúdo, mas sem pular linha. Isso irá gerar uma linha cujo conteúdo será contatenado na saída em tela com o conteúdo da próxima instrução printf que tiver, ou então com uma linha de comando do sistema ou similar caso o programa se encerre antes. Isso é importante de ser entendido, pois o printf não pula automaticamente linhas! Parece bobagem dizer isso, mas caso não seja indicado explicitamente que o comando deve pular uma linha (com o uso de \n), ele não irá o fazer.

OK, a linha seguinte tem um exemplo que é interessante e importante:

printf(” ou %7.2f\n”,divisaoFlutuante);

Parece muito com os demais printf que vimos nesse longo exemplo. Porém, a diferença tá no símbolo de formato: %7.2f. O que isso quer dizer? Esse símbolo indica que, na posição em questão, serão exibidos no máximo 7 caracteres, com no mínimo 2 deles sendo casas decimais (somando o ponto decimal, sobrariam portanto 4 casas para a parte inteira) e que o número será exibido como um flutuante decimal (f). Esse é um ponto importante que podemos resumir criando uma espécie de “modelo” de símbolos de formatação:
%[-+0][tamanho.[casas-decimais]]tipo
Desses, apenas tipo é obrigatório, sendo que podem ser usados quaisquer um dos tipos anteriormente mostrados na tabela de símbolos. O tamanho pode ser usado em qualquer condição, e casas-decimais pode ser usado em qualquer numérico, assim como os caracteres especiais , + e 0 antes do tamanho. Alguns exemplos:
  • %30s – apresenta apenas os primeiro trinta caracteres de uma string;
  • %10.2f – apresenta um ponto flutante escrito com 10 caracteres, sendo dois deles casas decimais: no caso dos pontos flutuantes, uma vez que se defina um número de casas decimais a serem exibidas ele é respeitado SEMPRE – valores com mais casas decimais tem seus valores decimais arredondados até o número de casas adequado e valores com menos casas têm as casas restantes preenchidas com 0;
  • %4h – esse é um interessante, pois mostra o conteúdo da variável inteira associada convertido para hexadecimal.  A base hexadecimal (ou base-16) é uma base numérica muito usada em eletrônica e programação avançada, e por isso C prevê o uso dessa base como forma de facilitar a entrada e saída de valores;
  • %-30s – como o anterior, mas irá alinhar o texto à direita, preenchendo os caracteres que sobrem (caso aplicável), com espaços;
  • %+020.2f“COMO ASSIM BIAL?”, você deve estar pensando… Bem, esse é nosso exemplo mais complexo: ele vai (1) imprimir um ponto flutuante com (2) 20 caracteres sendo (3) duas casas decimais e (4) preenchendo os caracteres que sobrem (caso aplicável) com 0 e (5) mostrando sempre um símbolo de sinal, independente de o resultado ser positivo ou negativo. Esse pode ser um exemplo estranho, mas existem aplicações científicas onde a leitura do sinal na saída dos dados é importante (por exemplo, coordenadas geográficas e ou indicadores de sinais em astrofísica);
Bem creio que com essa última linha acabamos de explicar o importante do programa. Salve ele, compile-o (já vimos como compilar um programa no Hello World) e brinque um pouco, executando-o várias vezes e procurando pensar em como o programa se comporta com os diversos valores.

Como fizemos com o Hello World, aqui também vou sugerir algumas “brincadeiras” com o programa:

  • Pra começar, tente modificar os tipos de variáveis no momento da declaração para ver o comportamento do programa. Aqui muitas coisas poderão simplesmente não funcionar, mas o objetivo por incrível que pareça é esse mesmo: procurar entender o que funciona e o que não;
  • Para entender as mecânicas de typecasting e autocasting, remova um dos casts para float na divisaoFlutuante, e depois remova o outro. Veja o comportamento em ambas as situações, e procure reparar como o programa irá reagir em cada uma dessa situações. PS: para que o exemplo fique mais claro, escolha valores para primeiro e segundo cuja divisão não seja exata (por exemplo, 8 e 3 ou 5 e 2);
  • Tente modificar as formatações na saída do programa. Utilize tamanhos fixos e no caso dos números experimente acrescentar o 0 antes dos tamanhos do mesmo. Utilize para alinhar valores à direita;
  • No momento em que for digitar o nome e a palavra a serem exibidas, tente utilizar valores com tamanhos muito maiores que os que as variáveis suportam. Verifique qual será a saída resultante. Lembrando sempre que a maior palavra da Língua Portuguesa é anticonstitucionalissimamente e que um bom nome gigante é o de Dom Pedro II (Pedro de Alcântara João Carlos Leopoldo Salvador Bibiano Francisco Xavier de Paula Leocádio Miguel Gabriel Rafael Gonzaga) , o que os tornam ótimos valores para esses testes;
  • Após fazer esses testes, modifique o programa para que ele use gets ao invés de fgets e repita os testes. Verifique o que pode acontecer ao entrar valores que “estourem” o tamanho da variável nome e o comportamento no sistema operacional quando isso ocorrer;
  • Tente adicionar ao programa um comando que determine a área de um triângulo de base primeiro e altura segundo. Declare e inicialize uma variável para guardar esse valor e um comando printf para exibir o resultado final;
Bem, esse foi um exemplo bem mais longo, pois tivemos que tratar muitos assuntos aqui. Semana que vem a idéia é entrar em comandos de controle de fluxo. Então, até lá!

O primeiro programa: HelloWorld.c

Olá todos!
Bem, agora vamos para algo de programação.

OK… Na realidade não é nada muito fantástico, mas a ideia é permitir que você tenha uma noção da estrutura de um programa C típico e de como ele é composto. Depois iremos explicar tudo de maneira mais detalhada.
Existe uma tradição em programação que o primeiro programa de computador para qualquer linguagem deve apresentar apenas uma mensagem na tela, normalmente “Hello World!“. Na realidade, se você usar o Google para pesquisar o termo “Hello World” em programação, você verá sites com coletâneas enormes de HelloWorlds nas mais diversas linguagens de programação, antigas ou modernas.
De qualquer modo, vamos ver nosso Hello World em C. Abra um editor de texto (como o Notepad do Windows ou quaisquer um dos editores de texto do Linux) e digite o que está abaixo as-is. (iremos explicar melhor logo) Se estiver usando uma IDE, abra-a e crie um arquivo com o conteúdo abaixo:

#include <stdio.h>

/* Nosso Hello World! (c) 2010 GPL */

int main (int argc, char** argv)
{

    printf(“Hello World!\n”); // Trouxemos ele do stdio.h
    return (0);
}

Não se preocupe em copiar a formatação do código: apenas digite-o como ele está. As cores são apenas para algumas explicações que serão dadas sobre o programa em questão.
Vamos começar então a destrinchar os que digitamos.
Repare na primeira linhas:

#include <stdio.h>

Ela, por incrível que pareça, ainda não é código C… Na verdade não exatamente. Todo “comando” que comece com o # (sharp, tralha, cerquilha, lasanha, entre outras formas de dizer que já ouvi por aí) é um comando para o pré-processador, que indica que o compilador deve tomar alguma atitude especial. Nesse caso, o #include adiciona ao arquivo em questão o conteúdo de um outro arquivo, no caso o arquivo stdio.h. O fato de ele estar entre os sinais de maior-menor tem uma função especial: ele faz o compilador buscar esse arquivo entre os arquivos da biblioteca-padrão do C. A biblioteca-padrão do C representa comandos que normalmente espera-se que façam parte do compilador independentemente da plataforma, versão ou tipo do compilador específico e, embora não sejam necessariamente obrigatórios, eles são quase sempre encontrados em qualquer bom compilador. É possível programar-se em C sem as bibliotecas-padrão (na realidade, em alguns casos é desejável, como no caso de programar-se C para microprocessadores), mas em geral um bom compilador irá incluir as bibliotecas padrão do C. Em alguns casos, o maior-menor irá representar a inclusão de arquivos de outras bibliotecas que fazem parte dos includes do compilador. Falaremos mais sobre isso quando entrarmos na questão de programação com múltiplos objetos e afins.
O stdio.h vêm de STandarD I/O Headers (Cabeçalhos de Entrada e Saída Padrão). Ele faz referências a funções de entrada e saída padronizadas pelo C. No caso, os cabeçalhos indicam ao compilador que tipo de funções o programa irá usar e como ele irá se comportar nesse caso. Veremos mais no futuro, mas o que é importante saber é que, sem essa linha, esse programa (e qualquer outro que exija funções de entrada e saída padrão) irá dar erro no momento da sua geração (ou compilação).
Chega de falar dessa linha. Vamos falar sobre uma coisa muito importante em C: comentários.
Comentários são basicamente isso: comentários. Em geral, comentários são apenas texto informativo que é inserido em um programa para documentá-lo, sendo sumariamente ignorado pelo compilador (veremos mais sobre o compilador ainda nesse post). Em alguns casos, com o uso de ferramentas certas, comentários podem ser muito úteis, ajudando a documentar um programa de maneiras muito inteligentes. Uma utilidade também dos comentários é que eles podem ser usados como método de depuração (correção de problemas): basta comentar-se o trecho de código onde existam problemas e verificar se os mesmos persistem uma vez que o programa seja re-executado.
No C, existem dois tipos de comentários:

  • O padrão mais antigo, onde começa-se o comentário com /* e termina com */: nesse caso, é considerado comentário tudo que estiver entre esses dois símbolos. A vantagem desse tipo de comentário é que ele é multi-line, ou seja, você pode adicionar linhas e linhas de comentário, desde que todos os comentários estejam entre esses dois símbolos;
  • Um padrão mais recente (parte do padrão chamado C99), que é usar-se duas barras (//): na verdade, esse padrão já usado a um bom tempo, em especial em compiladores que compilem código de C++ além de C (veremos C++ em tópicos mais avançados). Ele permite comentários in-line, ou seja, comentários a partir de um determinado ponto de uma linha de código. Para o compilador, tudo entre as duas barras e o final da linha onde elas estão será considerado comentário;

Bem, não precisamos falar mais dos comentários, então vamos voltar à nossa  Repare nas linhas comentadas em azul:

int main (int argc, char** argv)
{
<…>
}

Aqui estamos utilizando uma função main para colocar o código principal do nosso programa. O C trabalha com o conceito de função para separar os trechos de códigos, sendo que uma função é basicamente um bloco de código (um trecho de código separado pelos colchetes { }) com um nome. Veremos mais sobre isso quando entramos em funções. O importante agora é saber que main é o nome que damos a essa função onde colocamos nosso código (o texto em preto no programa acima).
A função main, porém, é especial e exige um pouquinho de atenção: ela é considerada o ponto de entrada de um programa. Para ser exato, caso não exista uma função main dentro de um programa, o compilador irá dar erro ao compilar (existem situações em que você não usará main em um arquivo C. Veremos isso ao falarmos sobre a programação com múlltiplos fontes e bibliotecas). Ela também tem uma construção razoavelmente padronizada, que é int main (int argc, char** argv). Você pode criar seu main com outras definições (ou cabeçalhos… lembram lá de cima?), mas alguns compiladores poderão acusar um alerta (uma mensagem que não é de erro, mas indica detalhes que podem dar problema) de que o main em questão sai do padrão do C. Em geral, o mainmain será escrito dessa maneira como indicamos e, caso você use uma IDE, o arquivo principal do seu projeto quase sempre terá um como o acima (falaremos mais sobre esse conceito de projeto no futuro).
Toda função em C é composta de três fatores, que explicaremos por alto (voltaremos mais nesse assunto quando falarmos de variáveis e funções), e a main não escapa desse padrão:

  • Retorno: diz qual o valor final que a função irá devolver ao sistema uma vez que termine de executar;
  • Nome: como ela é identificada pelo programador (quando o programa for compilado, esse nome é substituído por informações que indicarão onde ela ficará na memória do computador no momento da execução);
  • Parâmetros: O que ela espera de informação de quem quer que a chame. Normalmente são representados por um tipo, que indica qual o tipo de valor que é esperado, e por um nome de parâmetro, que indica como esse parâmetro será usado pelo programador;

Na realidade, é possível especificar-se funções que não retornem nada e não tenham parâmetros. Veremos isso em funções e quando falarmos dos tipos de dados em C.
No caso específico do main, ela é do tipo int (que representa valores inteiros – sem casas decimais – tanto positivos quanto negativos), chama-se main e possui como parâmetros um int chamado argc e um char** chamado argv. Esses parâmetros, no caso de main representam:

  • argc: o número de argumentos passados pelo programa e;
  • argv: uma lista dos mesmos (na verdade, o argv é mais complexo que isso, mas não iremos aprofundar mais nele. Por enquanto, o importante é saber como ele trabalha);

OK… Até agora vimos que precisamos dizer ao C que funções de biblioteca iremos usar e qual é nossa função main, além de dizer a ele o que ele deve retornar ao sistema operacional e como ele deve receber parâmetros do sistema operacional. Agora vamos aos comandos reais.

    printf(“Hello World!\n”); // Trouxemos ele do stdio.h
    return (0);

Bem, esses dois comandos basicamente imprimem na tela o texto “Hello World!” na tela e encerram o programa. Mas esses comandos possuem detalhes que os diferenciam e que são importantes para entender o C:
O primeiro comando se chama printf (de PRINT Formatted, “imprimir com formatação”). Esse comando é parte das bibliotecas padrão do C, na verdade da stdio que já mencionamos anteriormente. Embora seja parte da biblioteca padrão, ele não é, vamos dizer assim, obrigatório para um compilador C. Na verdade, ele não é uma palavra reservada, ou seja, um comando que é obrigatório da existência em C e que não pode possuir outro igual. De certa forma, um desenvolvedor pode criar seu próprio comando printf e colocá-lo em uma biblioteca à parte, o que pode ser útil para, por exemplo, quando vai programar-se microprocessadores (que não possuem saídas visuais, portanto havendo necessidade de um printf). No caso, ele recebe um texto para imprimir na tela. Na verdade, esse texto pode possuir determinados conjuntos de caracteres especiais que indicam como deverá ser formatado o código em questão, aos quais chamamos de formatos e caracteres de controles (veremos mais adiante sobre isso). No caso, temos um caracter de controle (símbolos que representam caracteres que não são visualizáveis normalmente), o \n, que indica que deve-se pular uma linha ao terminar-se de imprimir esse texto. Portanto, o que printf vai fazer aqui é (1) escrever na tela “Hello World!” e (2) pular uma linha.
Quem leu até aqui com calma vai perceber que a “mágica” já está feita. Então o programa não acabaria aqui? Para que o segundo comando, o tal return (0)?
O comando return é parte das palavras reservadas do C. Uma palavra reservada normalmente representa um comando ou informação interna da linguagem de programação C cujo nome não pode ser usado para MAIS NADA a não ser pelo que é indicado. O padrão C determina um conjunto de palavras reservadas, como return, int ou float. Importante ver a diferença entre os dois: um programador poderia criar sua própria versão de printf, mas não poderia criar uma versão de return.
O return é um comando que normalmente encerra qualquer função, devolvendo o controle do programa para quem quer que tenha chamado a função. No caso de main, pode-se dizer que quem chama a função main (o programa principal) é o ssitema operacional, portanto é ele que vai receber de volta o controle quando o comando return for executado nesse caso, o que indica o final do programa. O 0 entre parênteses é um valor inteiro que o sistema pode usar para fazer testes (no caso de programas automatizados e afins). No nosso caso, como não existe, digamos assim, um “dar errado”, o valor 0 foi adotado mais por convenção, mas esse valor pode ser mudado se for necessário, embora não aprofundaremos esse tópico aqui. Importante a saber aqui é que (1) return é uma palavra reservada e não pode ter outro no C, (2) ele devolve o controle ao sistema operacional nessa situação e (3) ele pode devolver um valor que pode ser usado pelo sistema operacional. No momento, basta saber essa informação: nos aprofundaremos no funcionamento de return quando estudarmos funções.
OK… O programa está pronto. O que fazer com ele?
Diferentemente de algumas linguagens de programação mais atuais, como Python, PERL, PHP ou Ruby, o C é uma linguagem compilada. Isso quer dizer que precisamos usar um programa, chamado compilador, para traduzir as instruções do nosso programa (chamado de código fonte) para instruções que o computador compreenda e seja capaz de executar (chamado de código objeto ou binário). No caso, vamos utilizar o GCC para compilar o nosso programa: salve o código digitado como HelloWorld.c (na realidade, você pode escolher qualquer nome, mas é bom usar nomes claros, que identifiquem facilmente seu código) e abra um terminal (no Linux) ou um Prompt de Comando (no DOS/Windows). Entre no mesmo diretório onde você gravou o seu HelloWorld.c e digite o seguinte comando:

gcc -o HelloWorld HelloWorld.c

O comando em questão irá compilar seu código e irá gravar o binário em um arquivo. No caso do Linux especificamente, o padrão do GCC é gravar o binário em um arquivo de saída a.out. Utilizamos então a opção -o (output) pada gravar o código em um arquivo de saída chamado HelloWorld. Imaginando que tudo esteja correto, usamos o comando ./HelloWorld para executarmos nosso Hello World!
OK, você deve ter visto o Hello World! e se perguntado em algum momento “Não entendi!”. Algumas “brincadeiras” que você pode fazer para entender alguns conceitos que colocamos aqui:

  1. Comente ou remova a linha #include e veja o resultado ao compilar o programa;
  2. Modifique o nome main para alguma outra coisa e tente compilar o programa. Substitua o retorno int ou os parâmetros entre parênteses dele por void (sem retorno/parâmetro);
  3. Adicione mais linhas printf com o texto que você quiser e coloque ou não o caracter de controle \n e veja como o programa irá se comportar;

Essas “brincadeiras” irão permitir que você pegue alguns dos conceitos sobre os quais falamos até agora e irá divertir você. Caso tenha alguma dúvida sobre esse post, por favor, adicione nos comentários sua dúvida. Em especial, se a dúvida envolver erros no programa, copie a saída que deu na compilação e informe seu sistema operacional e compilador.
Semana que vem estarei postando mais coisas. A jornada de programação C apenas começou.

Começando as atividades

Olá todos!

A idéia desse blog é ser uma espécie de “super-curso” de programação em linguagem C. Inicialmente serão englobados tópicos básicos com a programação mais simples, mas no futuro a ideia é englobar os tópicos mais avançados, como orientação a objetos em C++, bancos de dados, GUI, bibliotecas dinâmicas, etc.
Algumas propostas desse blog:

  • Sempre trabalharemos em cima do ANSI C. Caso contrário, o post indicará se precisará ser usado um ambiente ou compilador específicos;
  • Sempre que possível, serão utilizadas ferramentas livres. Em especial, o GCC (GNU C Compiler), que possui versões para Windows, Linux e outras plataformas;
  • Embora nosso foco não seja ensinar a parte de Lógica de Programação, o que for necessário será explicado. Porém, se você nunca programou antes, talvez seja melhor procurar na Internet por informações sobre Lógica de Programação;
  • Eu não sou um professor profissional! Isso ficando claro, vou dar todo apoio que estiver ao meu alcance;
  • Eu sou um ser humano, portanto posso cometer erros. Comentários serão bem-vindos;
  • Serei agnóstico quanto a IDEs (Integrated Development Environment, Ambientes de Programação). Embora possa fazer recomendações e, em alguns tópicos utilizar uma ou outra específica, cada um deverá usar a que lhe aprouver;
  • Em especial quanto ao Windows: sou um usuário de Linux. Embora possa dar ajuda em tópicos específicos e recomendar ferramentas, não sou mais tão proficiente no uso de ferramentas no Windows como já fui. Para quem puder, o conselho é utilizar um SO livre, como o Linux ou o FreeBSD. Caso contrário, existem muitas informações para IDEs C em Windows. Particularmente recomendo o Dev-C++ da Bloodshed como IDE para C em Windows. Outra boa IDE é a Turbo C da Borland;
  • Guerras Santas (tipo VI e EMACS) não serão apoiadas aqui. Como disse anteriormente: poderei recomendar ferramentas, mas acredito que a boa ferramenta é aquela que funciona bem com cada um;

Quanto a mim, sou formado em Desenvolvimento de Software pelas faculdades ASMEC (Ouro Fino – MG). Trabalho com computadores a pelo menos uns 6 anos e aprendi programação C com o antigo curso online livre de programação C mantido pela UFMG. Embora ele não desse diploma, ele dava uma coisa ainda mias importante: conhecimento. Apesar de desenvolver atualmente em outras linguagens, tenho uma paixão pela programação em C, onde fiz alguns dos meus melhores trabalhos na época da faculdade.
O objetivo é que você que está lendo aprenda mais sobre C e melhore seu entendimento de como um computador funciona.
De qualquer modo, esse é apenas um post inicial. Logo veremos programação de verdade.