Aulas de C

Aprendizado continuo. Linguagem antiga e moderna

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á!

16 Respostas para “Tipos de Dados e Variáveis

  1. Sérgio 16/11/2010 às 10:39

    Parabéns pela iniciativa Fábio.
    Vou add no meu http://planeta.berlotto.blog.br e vou acompanhar de perto !
    Tenho bastante interesse nesta linguagem também.

    Valeu!

  2. Elder Marco 16/11/2010 às 12:06

    Meu caro, interessante seu blog. Queria apenas deixar a dica de que você pode inserir códigos em muitas linguagens de programação no wordpress de maneira mais adequada.Existe o suporte pra isso, veja: http://en.support.wordpress.com/code/posting-source-code/
    🙂

  3. Marcelo Gondim 16/11/2010 às 13:26

    Oi Fábio,

    Muito massa a sua iniciativa. 🙂 Você pretende ao longo das aulas ir montando um livro em pdf sobre tudo isso? Seria bem legal também. Vou tentar ir passando essas aulas aqui pro editor e depois no final gerar um pdf dele. 🙂

    []´s

    • Fábio Emilio Costa 16/11/2010 às 14:15

      Ainda não tenho essa intenção. Como as coisas estão começando, pensei em engatilhar um ritmo tranqüilo (1 post por semana no máximo) e dar tempo para o povo absorver as coisas. Mas pode ser algo a se pensar.

  4. Marcelo Gondim 16/11/2010 às 16:32

    Fábio,

    Você acha que hoje o C++ está tão viável quanto C para quem quer desenvolver drivers e sistemas para embarcado?
    Pergunto isso porque vejo muita gente falar que C++ evoluiu muito em vários aspectos e que hoje seria mais vantagem aprender C++. Isso procede?

    []´s e obrigado pela resposta anterior

    • Fábio Emilio Costa 16/11/2010 às 16:49

      Nao creio que em embarcados C++ seja melhor que C, pois existem questões de redundância e do comportamento de sistemas embarcados. Vejo C++ como mais interessante, por exemplo, em aplicações Desktop, uma vez que ele oferece uma metodologia mais limpa de desenvolvimento que, por exemplo, o C puro.

      Acho que aprender C++ é muito interessante, em especial devido às bibliotecas STL, mas mais importante é o aprendizado do C básico, que permite um aproveitamento muito melhor depois do que se aprende em C++.

  5. André Caldas 17/11/2010 às 0:19

    Colega, ou você ou eu estamos entendendo errado o funcionamento do memcpy.

    Em C, os cuidados com a memória são muito importantes. Mas precisamos conhecer bem esses cuidados, e não só aplicá-los como uma receita de bolo. No seu caso, por exemplo,
    memcpy( nome, "", sizeof(nome) )
    está dizendo pra copiar sizeof(nome) bytes da string “”, que tem um só byte!!! Ou seja, fatalmente a função memcpy irá fazer acesso ilegal a alguma parte da memória.

    Imagino que o que você queira seja algo como
    memset( nome, '', sizeof(nome) )

    Quando digo que cuidados NÃO SÃO receita de bolo, e que é preciso entender tais cuidados, estou me referindo a acreditar que o memset irá tornar o seu código mais seguro. É uma boa-prática, sem dúvida. No entanto, o uso do scanf é que torna o código inseguro. O uso de
    scanf( "%s", palavra )
    é a maior fonte de falhas de segurança de toda a história da humanidade.😉

    Com esse scanf, o usuário pode fazer o “buffer overflow” que bem entender e executar o código que quiser. Em especial, se você usar esse tipo de código quando lendo, não do terminal, mas de um “cliente remoto”, estará possiblitando que o “cliente remoto” execute códigos. Se for um programa para ler um arquivo JPG, por exemplo, será possível que se crie um vírus embutido em uma imagem. O simples ato de “abrir” a imagem fará com que o código do vírus seja executado.

    Minha sugestão é que você ensine primeiro coisas mais simples, para que seus leitores aprendam bem como se trabalha com ponteiros e etc. Ordenação de string e tal são uma boa coisa. A parte mais chata de “programação” é a interface com o usuário.🙂

    Referência:
    http://en.wikipedia.org/wiki/Scanf#Security

    Boa sorte,
    André Caldas.
    PS: Hífen é o sinal de menos! Não pode entrar no nome da variável!

    • André Caldas 17/11/2010 às 0:24

      Oops… o (barra invertida 0) não apareceu. Parece que o wordpress não gosta da barra invertida.😦

      Seria legal um botão de “preview”. Se souber como põe um, me ensine que eu quero botar um botão de “preview” no meu, também.🙂

      • Fábio Emilio Costa 17/11/2010 às 8:52

        Quanto ao scanf, obrigado. Eu trealmente desconhecia. Mas cheguei a fazer um teste aqui (Linux com compilador GCC) e, ao tentar colocar uma entrada maior que o tamanho de palavra, ele pegava apenas as últimas letras. Talvez seja interessante nesse caso tentar limitar a entrada com

        scanf("%20s", palavra);
        

        E quanto ao , o WordPress tá de molecagem (ou ele ou o Scribefire)

  6. Pingback: Uma aprofundada em operadores e lógica em C « Aulas de C

  7. Wagner Macedo 11/01/2011 às 11:15

    Olá, Fábio!

    Mais uma vez parabéns pelo site, eu estou só acompanhando as aulas agora.

    O motivo dessa minha mensagem é que encontrei uns erros de revisão no seu texto:
    * No código, é incluso , mas texto você fala que foi incluso .
    * No texto, quando você cita a parte do memcpy o escape do nulo “” saiu como “\” ou “”.

  8. Magno 12/01/2011 às 10:44

    Olá, eu estou começando a programar e não consegui compilar o código dessa aula, ele mostra esse erro:
    mpiling: /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c: In function ‘main’:
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:17: warning: incompatible implicit declaration of built-in function ‘memcpy’
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:17: error: stray ‘\342’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:17: error: stray ‘\200’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:17: error: stray ‘\235’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:17: error: stray ‘\’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:17: error: stray ‘\342’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:17: error: stray ‘\200’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:17: error: stray ‘\235’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:18: error: stray ‘\342’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:18: error: stray ‘\200’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:18: error: stray ‘\235’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:18: error: stray ‘\’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:18: error: stray ‘\342’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:18: error: stray ‘\200’ in program
    /home/magno/Cursos/Curso de C/atividades/Tipos de dados e vaiaveis.c:18: error: stray ‘\235’ in program
    Process terminated with status 1 (0 minutes, 0 seconds)
    14 errors, 1 warnings
    Como sou novato nisso eu não tenho ideia de como resolver esse problema.

    Meu S.O. é Gnu/Linux e eu usei o programa Code:Block para tentar compilar esse código.
    Gostei muito desse site e já assinei seu Feed, li os outros 2 artigos e ele não deu esse erro n HelloWorld.c. Parabéns pela iniciativa.

  9. Wagner Macedo 12/01/2011 às 15:08

    Fábio,

    O meu outro comentário sofreu do bug do wordpress também. Repetindo, sem bug (espero):

    No código, é incluso stdlib.h, mas no texto você fala que foi incluso string.h.

    • Fábio Emilio Costa 12/01/2011 às 23:43

      Bem…

      Quanto aos erros de \342 e \200, eles devem-se ao copiar-e-colar. Troque as aspas vindas do copiar e colar por aspas duplas normais, digitadas por você.

      Quanto ao string.h, você realmente está certo: no caso, realmente o header importado é o string.h

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: