Programar em C; Tipos de dados

tipos de dados
Spread the love

Programar em C; Tipos de dados

Em nosso último post, Variáveis em linguagem C, você só viu, e mesmo assim em forma de exemplo, as variáveis do tipo int, que servem para guardar números inteiros. Porém, os tipos de dados não param por aí.

A linguagem C possui outros tipos de dados, que vão além do tipo int, e que são fundamentais para o desenvolvimento do código.

É de extrema importância, que o iniciante em programação, faça um estudo bastante aprofundado desta seção, pois se não houver tal estudo, certamente a pessoa ficará bem perdida em matérias que se usa muito ponteiro, como é o caso da matéria de Estrutura de dados que depende muito do entendimento de declaração de variáveis.

Os Tipos de dados

A linguagem C possui tipos para números inteiros de vários tamanhos com e sem sinal, números de ponto flutuante, caracteres e estruturas (structs). C usa extensivamente ponteiros, um tipo muito simples de referência que guarda o endereço de memória da variável.

O ponteiro pode ser desreferenciado, uma operação que busca o objeto que se encontra na morada da memória que o ponteiro possui, morada essa que pode ser manipulada através de aritmética de ponteiros. Durante o tempo de execução, o ponteiro é simplesmente uma morada de máquina tais como aquelas manipuladas em Assembly, mas em tempo de compilação possui um tipo complexo que indica o tipo do objeto para onde ele aponta, permitindo que se verifique o tipo de expressões, incluindo ponteiros.

O tipo “string” (cadeia ou linha de texto) de C é simplesmente um ponteiro para um vetor de caracteres e alocação dinâmica de memória, descrita abaixo, é efetuada através de ponteiros.

Os ponteiros em C possuem um valor reservado especial, NULL, que indica que não estão a apontar para uma morada. Ao ser desreferenciado, o uso desse valor, causa comportamento não definido, isso devido a uma possível falha no sistema. Porém, e extremamento útil na construção de várias estruturas de dados.

Um ponteiro passa a ser chamado de ponteiro nulo quando possui ou apontar para o valor NULL. Os ponteiros são declarados com um * (asterisco), portanto o tipo int* denota um ponteiro para variáveis que comportam números inteiros.

A linguagem C também fornece um tipo especial de ponteiros, o void*, que se traduz num ponteiro que aponta para um objeto de tipo desconhecido.

Em memória, tais estruturas são posicionadas com as linhas uma depois da outra. O acesso a disposições de tipos é feito através de ponteiros e aritmética de ponteiros; o nome da disposição é tratado como se fosse um ponteiro que aponta para o início da disposição.

Como a linguagem C é regularmente usada em programação de baixo-nível de sistemas, há casos em que é necessário tratar um número inteiro como sendo um ponteiro, um número de ponto flutuante como sendo um número inteiro ou um tipo de ponteiro como sendo outro.

Para estes casos, a linguagem C fornece a capacidade de “moldagem” (também denominado “conversão de tipo” ou “casting”), uma operação que, caso seja possível, força a conversão de um objeto de um tipo para outro.

Tipos de dados – int

O tipo de dado int (inteiro) serve para armazenar valores numéricos inteiros. Existem vários tipos de inteiros, cada um de um tamanho diferente (dependendo do sistema operacional e/ou arquitetura do processador):

int, pode possuir 16 bits, 32 bits ou 64 bits

short int, deve possuir tamanho de no mínimo 16 bits e não pode ser maior que int

long int, deve possuir tamanho mínimo de 32 bits

long long int, deve possuir tamanho mínimo de 64 bits

Todos estes tipos de inteiros podem ainda ser declarados precedidos da cláusula unsigned, o que faz com que só suporte números positivos.

Isto faz com que, com o mesmo tamanho, uma variável suporte mais números positivos do que um signed (todos os inteiros são signed por omissão).

Tipos de dados – char

O tipo char ocupa 1 byte, e serve para armazenar caracteres ou inteiros. Isso significa que o programa reserva um espaço de 8 bits na memória RAM ou em registradores do processador para armazenar um valor (char de tamanho maior que 8 bits é permitido pela linguagem, mas os casos são raros). Com vetores do tipo char é possível criar cadeias de caracteres (strings).

Tipos de dados – float

O tipo de dado float serve para armazenar números de ponto flutuante, ou seja, com casas decimais. O padrão mais utilizado nos últimos 10 anos é o IEEE 754-1985.

Tipos de dados – double

O tipo de dado double serve para armazenar números de ponto flutuante de dupla precisão, normalmente tem o dobro do tamanho do float e, portanto, o dobro da capacidade. O padrão mais adotado também é o IEEE 754-1985.

Tipos de dados – struct

Em C podem ser usadas estruturas (chamados de registos em outras linguagens de programação). As estruturas são grupos de variáveis organizadas arbitrariamente pelo programador. Uma estrutura pode criar um novo tipo de variável caso typedef seja usado em sua declaração.

Explicando bits e bytes

Os computadores, assim como tantos outros dispositivos eletrônicos, trabalham com cargas elétricas. Em cada uma dessas cargas podemos uma certa corrente elétrica: ou possui corrente ou não possui corrente: se tem corrente elétrica, para facilitar o nosso entendimento, podemos associar o valor 1, se não possui corrente, associamos o valor 0. Assim, temos a linguagem binária de zeros e uns.

Após entendermos este breve conceito de sistema binário, podemos fazer combinações, se tivermos posição de zeros e uns, da direita para a esquerda.

00000000 1ª Combinação

00000001 2ª Combinação

00000010 3ª Combinação

00000011 4ª Combinação

00000100 5ª Combinação

00000101 6ª Combinação

00000110 7ª Combinação

00000111 8ª Combinação

Desta forma, podemos fazer um número quase infinito de combinações binarias. Porém, o que acontece, é que nos basta pouco menos de 256 combinações (8 bits ordenados) para termos uma combinação para cada letra, maiúscula e minúscula, número, pontos de exclamação, interrogação dentre outras, e isso era o suficiente para a nossa comunicação.

Buscando uma maneira de facilitar o consenso para que uma dada combinação desse um dado símbolo, foi que surgiu a tabela ASCII.

Portanto com 8 bits ou 8 casas conseguíamos ter qualquer símbolo que utilizamos. A esse conjunto de 8 bits chamamos de byte, mais convenientemente. Portanto, um byte tem 8 casas de zeros/uns, ou seja 2 elevado a 8 dá as 256 combinações. E o byte é a unidade básica que a linguagem C consegue operar e é representado pelo tipo char.

Pergunta: Quando tivermos mais do que 256 bytes acrescenta-se um outro byte?

 Sim. Com dois bytes o número de combinações é 256*256.

Pergunta: Qual a razão do computador usar apenas bytes como medida mínima? Será que não seria possível utilizar 7 bits ou 5 bits?

 A razão para isso é que a infraestrutura física (hardware) dos computadores atuais apenas aceita bytes nativamente. Porém seria possível projetar um novo tipo de computador que calculasse mais eficientemente utilizando se números diferentes de bits.

tipos de dados

Números inteiros

Os dados do tipo inteiro, são representados por 2 bytes, desta forma, pode-se formar 65.536 combinações, pois 2 bytes elevado a 16bits, temos 65.536 combinações. Assim se quisermos apenas os positivos com o zero temos de [0,65535].

Se quisermos ter números negativos e positivos podemos dividir esse valor a meio e dá 32.768 para cada lado positivo e negativo, mas como temos de ter o zero vamos roubar um valor ao lado positivo e então ficamos com o intervalo [-32768, 32767]. E ficamos com as mesmas 65.536 combinações.

Apresentamos inteiro com 2 bytes, mas eles podem ter 4 bytes, isso vai depender do processador do computador, e, com quantos bytes consegue ele lidar ao mesmo tempo.

Também existem outros tipos, como short (ou short int), que serve para inteiros menores, long (ou long int) para inteiros maiores.

Qualquer tipo inteiro pode ser precedido por unsigned (o signed para COM negativos), para cortar os números negativos, permitindo maior capacidade de armazenamento de números positivos.

Alguns compiladores aceitam o long long, para aumentar ainda mais o tamanho da variável, alguns desses só aceitam para o tipo int, outros também para o tipo double.

Podemos alterar a maneira como os dados são guardados com os modificadores de tipo. Você pode modificar os tipos de duas maneiras.

Tamanho: short e long

Você pode modificar o tamanho de uma variável usando os modificadores de tipo, que são dois: short e long. Note que float e char não podem ser modificados em tamanho.

short diminui o espaço necessário para guardar a variável (diminuindo também a gama de valores que esta pode assumir). Só pode ser usado com int.

long aumenta o espaço tomado pela variável, e, portanto, aumenta seu valor máximo e/ou sua precisão. Pode ser usado com int e double.

O padrão C de 1999 adicionou um terceiro modificador, suportado pelos compiladores mais recentes, inclusive o gcc: long long, que aumentaria ainda mais a capacidade da variável. Alguns deles suportam esse modificador apenas para o tipo int, e outros suportam também para double.

Uma observação é necessária: segundo o padrão, não existe nenhuma garantia de que uma variável short int é menor que uma variável int, nem que long int é maior que int. Apenas é garantido que int não é maior que long nem menor que short.

De fato, nos sistemas x86 de 32 bits (ou seja, a maioria dos computadores pessoais atualmente), o tamanho de int é igual ao de long. Geralmente, int será o tamanho nativo do processador — ou seja, 32 bits num processador de 32 bits, 16 bits num processador de 16 bits etc.

Sinal: signed e unsigned

Existe outro tipo de modificador, que define se o número vai ser guardado com sinal ou não. São os modificadores signed e unsigned, suportados pelos tipos inteiros apenas.

signed diz que o número deve ser guardado com sinal, ou seja, serão permitidos valores positivos e negativos. Esse é o padrão, portanto esse modificador não é muito usado.

unsigned diz que o número deve ser guardado sem sinal. Com isso, o valor máximo da variável aumenta, já que não teremos mais valores negativos. Por exemplo, com uma variável char podemos guardar valores de -128 a 127, mas com uma variável unsigned char pode guardar valores de 0 a 255.

Para usar esses modificadores, devemos colocá-los antes do nome do tipo da variável, sendo que o modificador de sinal deve vir antes do modificador de tamanho caso ambos sejam usados. Por exemplo:

tipos de dados

Números de ponto flutuante

Os números de ponto flutuante são uma tentativa para guardar números reais, como 3,1415 (pi), -2,3333,

0,00015, 6,02 × 1023. Ao contrário dos números reais, os números representáveis pelo hardware são finitos. A maneira como os tipos de ponto flutuante é armazenada é abstrata para o programador, entretanto, o hardware segue o padrão IEEE 754 (Standard for Floating-Point Arithmetic).

O armazenamento é feito usando notação científica binária.

Decimal NotationScientific NotationE Notation
123.451.2345 x 1021.2345E2
0.00515.1 x 10-35.1E-3
1,200,000,0001.2 x 1091.2E9

Os tipos float e double servem para guardar números de ponto flutuante. A diferença entre os dois é, além do intervalo de dados, a precisão. Geralmente, o tipo float guarda dados (com sinal positivo ou negativo) de 3,4E-38 a 3,4E+38 (além do zero). Já double suporta números tão pequenos quanto 1,7E308 e no máximo 1,7E+308.

tipos de dados

Nota: O tipo long double trabalha em máquinas x64 no padrão LP64 (Mac OS X e Unix)

Bool

Este tipo surgiu porque muitas vezes apenas se quer ter 2 valores: sim/não; verdadeiro/falso. Tem o tamanho de um byte e tem apenas dois valores 0 e 1 que corresponde a true e false.

Por que guardar um bool num byte quando se pode utilizar apenas um bit? A razão é que o computador usa no mínimo o byte, não o bit.

Endereços

Os vários locais na memória são identificados por um endereço, que tem uma lógica sequencial numerada. São necessários 16 bits ou 2 bytes para guardar o endereço de um byte. Isso quer dizer que se guardarmos os endereços de todos os bytes, só temos 1/3 da memória disponível para guardar valores.

Bem isto é um pouco estranho, mas repare-se que apenas vamos guardar os endereços das variáveis reservadas. Isso porque as variáveis nem sempre são de 1 byte, sendo assim, iremos guardar o endereço do primeiro byte, ao invez de todos.

Compatibilidade de dados na atribuição de valor

Se tentarmos colocar um valor diferente do tipo esperado da variável? Temos um problema de compatibilidade de dados:

 Caso 1: Declaramos um int e colocamos uma letra

Aqui não teremos problemas. Os literais de caracteres são, nativamente, do tipo int. O resultado será um inteiro que contém o valor ASCII do caractere dado.

 Caso 2: Declaramos um int e colocamos uma string (sequência de caracteres)

Aqui teremos um erro de compilação, em que nos diz que não conseguimos converter “const char [5]” em “int”. Perceba com isso que o compilador tem alguns sistemas de conversão ― note o caso 3.

 Caso 3: Declaramos um int e colocamos um float

Neste caso, se colocarmos 77.33, irá ser apenas guardado o valor 77, perdendo-se a parte decimal.

 Caso 4: overflow ― declaramos um short e colocamos um valor maior que o máximo

Lembre-se que o tipo short guarda valores de –32767 a 32767. Se colocarmos 32768 (e o compilador não estender esses limites), não vai acontecer nenhum erro de compilação; o que resulta é que vai ser impresso um número negativo, –32767 (ou, como é comum em vários compiladores, –32768).

A lógica disto tem a ver com a maneira como o computador guarda números negativos. Mas também podemos fazer uma analogia com as horas. Imaginemos que vamos somar 6 horas com 7 horas. O resultado seria 13, mas como não existe 13 no relógio, iríamos dar a volta nas horas e chegar ao 1. Assim o resultado será 1.

 Caso 5: underflow ― declaramos um short e colocamos um valor inferior ao mínimo possível.

Aqui temos exatamente a mesma lógica do caso de overflow, mas desta vez é excedido o limite inferior e não o superior.

 Caso 6: declaramos um unsigned int e colocamos um número negativo

O que acontece aqui é semelhante a um underflow. Mas o que ocorre é que o número é guardado como seria se fosse um int comum, negativo. O que muda na prática é a interpretação desse número, de acordo com o tipo de dado que lhe está atribuído. Se tentarmos lê-lo como um unsigned int, obteremos um valor positivo obtido pela mesma lógica do overflow/underflow; se o lermos como um (signed) int, obteremos o mesmo valor negativo que lhe atribuímos.

Converter um tipo de variável

A conversão de uma variável consiste em converter o tipo de uma variável em um outro. Imagine que você esteja trabalhando com uma variável do tipo float e por alguma razão queira eliminar os números que estão depois da vírgula.

Esta operação pode ser realizada de duas maneiras.

Conversões do tipo implícita: Consiste em uma modificação do tipo de variável que é feita automaticamente pelo compilador.

Ex:

tipos de dados

Conversões do tipo explícita: Também chamada de operação cast, consiste em forçar a modificação do tipo de variável usando o operador cast “()”.

Ex:

tipos de dados

Veja um exemplo da conversão de tipo inteiro em caracteres. Aqui convertemos um número decimal em um caractere ASCII.

tipos de dados

Literais

Em programação, um literal é uma notação que representa um valor constante. Exemplos de literais em C são 415, 19.52, ‘C’, “João”. Esses exemplos representam os quatro tipos de literais em C: literais de inteiros, literais de reais, literais de caracteres e literais de strings. Só com esses exemplos já é possível deduzir como se usam os literais; mas é importante fazer algumas observações:

 Literais de inteiros podem ser especificados nas bases decimal, octal ou hexadecimal. Se o literal for prefixado com “0x” ou “0X”, ele será interpretado como hexadecimal; se o prefixo for apenas “0”, será interpretado como octal; ou se não houver prefixo, será interpretado como decimal.

 Literais de reais podem ser especificados na forma decimal (144.57) ou em notação científica (1.4457e+2). Lembre-se que o separador decimal é o ponto e não a vírgula, como seria usual.

 Literais de caracteres devem vir entre aspas simples (‘) e conter a representação de apenas um caractere1. Usos válidos seriam: ‘c’, ‘\n’, ‘\x1b’, ‘\033’. Se você quiser usar a aspa simples como caractere, preceda-a com uma barra invertida: ‘\”.

 Literais de strings devem vir entre aspas duplas (“). Para usar aspas duplas dentro de strings, preceda-as com barra invertida: “Ele disse \”Olá\”.”. Note que um literal de string adiciona o caractere nulo (\0) ao final da string, pois ele é, em C, a maneira de delimitar o final de uma string.

Na verdade, segundo o padrão C, literais de caracteres podem conter a representação de mais um caractere, mas o uso deles seria para representar números e não sequências de caracteres; é um aspecto pouco utilizado da linguagem C.

curso de flutter

Lisandro Viana


Spread the love