De Objective Caml para C e C++/Os tipos básicos

Origem: Wikilivros, livros abertos por um mundo aberto.
Saltar para a navegação Saltar para a pesquisa

Introdução[editar | editar código-fonte]

O sistema de tipos de uma linguagem de programação é composta por:

  • Tipos básicos, que são os elementos de base para representar informações mais simples e construir tipos mais complexos
  • Construtores de tipos, que tem como papel combinar tipos mais elementares para construir tipos mais complexos;
  • Regras de conversão entre tipos, que definem se e como valores de um tipo podem ser convertidos para um outro tipo.

O sistema de tipos também possui os construtores de tipos, que são apresentados em um módulo específico sobre . Ainda possui regras que definem quando, e como valores de um tipo podem ser convertidos entre se.

Os tipos básicos de C e de C++ são apresentados nesse módulo. Primeiro lembramos os tipos básicos de Objective Caml são unit, bool, char, int, float e string. Os tipos básicos de C++ são:

A seguinte tabela provê uma correspondência entre os tipos básicos de C e C++ e os de Objective Caml.

Objective Caml C e C++
- void
unit -
char char
int short int, long int, long long int
float float, double, long double
string - (*)

(*) As linguagens C e C++ possuem construções para representar textos (string), mas essas não fazem parte conjunto dos tipos básicos. A biblioteca padrão da linguagem C fornece funções de tratamento de textos utilizando uma técnica baseada em ponteiros para caracteres. Essas funções são disponíveis na linguagem C++ que ainda fornece, através de sua biblioteca padrão, um tipo nomeado string e funções que manipulam valores desse tipo.

O tipo vazio[editar | editar código-fonte]

Em Objective Caml, o tipo unit é o tipo das computações seqüênciais e possui apenas um valor, denotado (). Em C e C++, existe um tipo similar, embora mais simples ainda! Trata-se do tipo void, que não possui valor algum. Ele é usado para definir funções que não retornam resultado.

Booleanos[editar | editar código-fonte]

A linguagem C++ possui um tipo para representar os booleanos. Tem como nome bool e os valores true e false, representando respectivamente verdadeiro e falso.

Os operadores booleanos são:

  • negação: !,
  • conjunção: &&,
  • disjunção: ||.

É importante notar que os operadores booleanos binários possuem uma ordem pré-definida para a avaliação de seus operandos.

  • A avaliação da expressão c1 && c2 começa pela avaliação da sub-expressão c1. Se a mesma for igual ao valor false, a avaliação da expressão completa é terminada e resulta no valor false. Caso contrário, a segunda sub-expressão c2, é avaliada e o resultado sera o da expressão completa.
  • Similarmente, na avaliação de uma disjunção c1 || c2, caso a avaliação da sub-expressão resulte no valor true, então é concluída a avaliação da expressão completa, resultando no valor true. Caso contrário, o valor da expressão completa é o da sub-expressão c2.

Existe um quarto operador, que pode ser chamado operador condicional. É similar à construção if...then...else de Objective Caml. A sintaxe é ... ? ... : ..., onde cada ... representa um argumento. Possui três argumentos, sendo que o primeiro é uma condição e os dois últimos devem ter tipos compatíveis. O valor da expressão é o valor do segundo argumento se a condição for verdadeira, do terceiro argumento caso contrário. Considere, como exemplo, a seguinte expressão:

 i >= 0 ? 1 : -1

O valor dessa expressão será caso o valor de i for maior ou igual a zero, e caso contrário (>= é o operador maior ou igual).

No caso do operador condicional, o segundo argumento é avaliado apenas se o resultado da avaliação do primeiro for true, enquanto que o terceiro argumento será avaliado apenas quando o resultado da avaliação do primeiro for false.

Booleanos na linguagem C[editar | editar código-fonte]

Na versão inicial da linguagem C, não existia tipo para os booleanos. Em C, as condições possuem o tipo inteiro, onde a condição falso é representada pelo valor 0, e a condição verdadeiro por qualquer valor não nulo. A biblioteca padrão da linguagem C, versão 1999, existe uma definição para o tipo booleano que é idêntica à da linguagem C++. Para ter acesso a essa definição, o programador deve incluir um arquivo utilizando o comando seguinte no início do arquivo:

#include <stdbool.h>

Impressão de valores booleanos[editar | editar código-fonte]

Para imprimir um valor do tipo booleano, pode-se utilizar a função printf e o operador <<. Em impressão no estilo C, temos então:

// exemplo de programa C++ que imprime os dois valores do tipo bool, utilizando uma função de impressão herdada de C
#include <cstdio>
int main ()
{
  fprintf(stdout, "verdadeiro: %i\n", true);
  fprintf(stdout, "falso: %i\n", false);
}

Um programa de impressão de valores booleanos no estilo C++ seria:

// exemplo de programa C++ que imprime os dois valores do tipo bool, utilizando o operador de impressão do C++
#include <iostream>
using namespace std;
int main ()
{
  cout << "verdadeiro: " << true << endl;
  cout << "falso: " << false << endl;
}

A execução de ambos programas resultará na seguinte impressão na saída padrão:

verdadeiro: 1
falso: 0

Caracteres[editar | editar código-fonte]

As linguagens C e C++ possuem ambas um mesmo tipo para representar caracteres: trata-se do tipo denominado char. Os valores desse tipo podem ser denotados colocando o caractere entre aspas simples. Nesse aspecto Objective Caml é muito similar, pois adota exatamente as mesmas convenções.

A linha seguinte possui alguns valores do tipo char:

'a' 'A' '0' '\n' '\\' '\042'

Esses valores representam, respectivamente, a letra a minúsculo, a letra a maiúsculo, o algarismo zero, o caractere especial de quebra de linha (caracteres especiais são precedidos de uma contra-barra), o caractere de contra-barra, e o caractere cujo código ASCII é 42 (o caractere de aspa dupla).

Na verdade, as linguagens C e C++ tratam o tipo char como um tipo inteiro, onde oito bits são representados para representar os valores. São portanto 256 valores diferentes. Existem duas variações:

  • signed char, correspondendo aos números de -128 até 127;
  • unsigned char, correspondendo aos números de 0 até 255.

Isso significar que 'a' + 1 é uma expressão legal, tanto em C quanto em C++, e ela é igual a 'b'.

Impressão de caracteres[editar | editar código-fonte]

Para imprimir um valor do tipo char, pode-se utilizar a função printf e o operador <<. Em impressão no estilo C, temos então:

// exemplo de programa C++ que imprime um valor do tipo char, utilizando uma função de impressão herdada de C
#include <cstdio>
int main ()
{
  fprintf(stdout, "%i - %c\n", 'a', 'a');
}

Note que o texto de impressão possui duas diretivas de formatação. A primeira é %i: manda imprimir o (primeiro) valor 'a' como se fosse um valor do tipo int. A segunda é %c e manda imprimir o (segundo) valor 'a' como se fosse um valor do tipo char. A saída resultante é:

97: a

No parágrafo anterior, observe nosso grifo em como se fosse. O que acontece efetivamente é o seguinte. A diretiva %i indica que o argumento correspondente é um valor do tipo int. No caso, o valor é do tipo char. O que acontece aqui é que o compilador insere uma conversão do valor do tipo char para o tipo int. Em C, ou em C++, essa conversão é realizada automaticamente, e o compilador nem emite um aviso que foi feita essa conversão. Isto é um exemplo bastante esclarecedor da diferença de rigor entre o sistema de tipo de C e C++ e o de Objective Caml, onde seria exigido a aplicação de uma função para converter os valores desses tipos.

Um programa de impressão de valores booleanos no estilo C++ seria:

// exemplo de programa C++ que imprime os dois valores do tipo bool, utilizando o operador de impressão do C++
#include <iostream>
using namespace std;
int main ()
{
  cout << 'a' << endl;
}

A execução desse programa resultará na seguinte impressão na saída padrão:

a

Números inteiros[editar | editar código-fonte]

As linguagens C e C++ possuem uma variedade de tipos inteiros. Esses tipos são pré-definidos e variam em função de dois aspectos:

  • presença ou não de sinal;
  • tamanho da representação binária, que vai de um até oito bytes.

Vamos olhar para o exemplo mais simples, que é o tipo char, já discutido na seção sobre caracteres. Um char é um tipo inteiro codificado com um byte, ou seja oito bits. Possui portanto valores diferentes. São duas variantes para esse tipo: com sinal ou sem sinal. Na variante com sinal, denominada signed char, ou simplesmente char os valores vão de -128 até 127. Na variante sem sinal, denominada unsigned charos valores ficam na faixa de 0 até 255. Você pode verificar quem, em ambos casos, temos 256 valores diferentes.

Existem quatro tamanhos possíveis que são um, dois, quatro e oito bytes. Acabamos de (re)ver o caso do tamanho de um byte. Os demais três tamanhos são nomeados short int, long int e long long int. Também são chamados de short, long e long long. Todos possuem variantes com sinal e sem sinal que são acessíveis utilizando os prefixos signed e unsigned, sendo que a ausência de tal prefixo corresponde ao tipo com sinal.

Vale salientar que existe um tipo int que corresponde a short int em plataformas computacionais com processador de 16 bits (ou seja 2 bytes) e a long int em plataformas computacionais com um processador de 32 bits (ou seja 4 bytes).

É importante salientar que, como em Objective Caml, a aritmética implementada é a aritmética do relógio, ou aritmética módulo. Quando uma computação resulte em um valor ultrapassa os limites do tipo, então que não pode ser representado, o resultado da computação será igual a esse valor módulo o tamanho do tipo do resultado dessa computação.

Constantes inteiros[editar | editar código-fonte]

Os valores máximos e mínimos de cada um dos tipos possuem nomes. A tabela seguinte faz um resumo dos mesmos.

SCHAR_MIN -127 valor mínimo para um objeto do tipo signed char
SCHAR_MAX +127 valor máximo para um objeto do tipo signed char
UCHAR_MAX +255 valor máximo para um objeto do tipo unsigned char
SHRT_MIN -32767 valor mínimo para um objeto do tipo signed short
SHRT_MAX +32767 valor máximo para um objeto do tipo signed short
USHRT_MAX +65535 valor máximo para um objeto do tipo unsigned short
INT_MIN -32767 valor mínimo para um objeto do tipo int
INT_MAX +32767 valor máximo para um objeto do tipo int
UINT_MAX +65535 valor máximo para um objeto do tipo unsigned int
LONG_MIN -2147483647 valor mínimo para um objeto do tipo long int
LONG_MAX +2147483647 valor máximo para um objeto do tipo long int
ULONG_MAX +4294967295 valor máximo para um objeto do tipo unsigned long int
LLONG_MIN -9223372036854775807 valor mínimo para um objeto do tipo long long int
LLONG_MAX +9223372036854775807 valor máximo para um objeto do tipo long long int
ULONG_MAX +18446744073709551615 valor máximo para um objeto do tipo unsigned long long int

Em C, acesso a esses nomes é realizado colocando a seguinte diretiva no cabeçalho do arquivo:

#include <limits.h>

Em C++, o acesso é realizado com a diretiva seguinte de inclusão:

#include <climit>

Existem outras definições pertinentes agrupadas no arquivo stdint.h, mas que não detalharemos aqui.

Operadores inteiros[editar | editar código-fonte]

Os valores de todos os tipos inteiros podem ser combinados através de operadores aritméticos como a adição, subtração, etc. São eles:

  • adição: +,
  • subtração: -,
  • multiplicação: *,
  • divisão inteira: /,
  • resto da divisão inteira: %,
  • negação: -.

No caso dos operadores de divisão, se o segundo operando for nulo, o resultado é indefinido. Esse conceito de resultado indefinido não existe em Objective Caml. Isso quer dizer que qualquer coisa pode acontecer... Para se ter uma idéia da gravidade disso, imagine então que o disco rígido seja reformatado cada vez que é realizada uma divisão por zero. Soa absurdo para você? Bem, esse comportamento é legal tanto em C quanto em C++! Em conclusão, é a tarefa do programador evitar que os operadores / e % sejam aplicados com o seu segundo argumento igual a zero. Diferente de Objective Caml, o sistema de exceções da linguagem não vai tratar isso.

Também existem operadores de comparação que podem ser aplicados aos tipos inteiros:

  • igualdade: ==;
  • diferença: !=;
  • menor que: <;
  • menor ou igual a: <=;
  • maior que: >;
  • maior ou igual a: >=.

Finalmente, existem operadores que não possuem equivalentes em Objective Caml e que permitem manipular valores em nível de bits. São eles:

  • deslocamento a esquerda: <<;
  • deslocamento a direita: >>;
  • conjunção bit a bit: &;
  • disjunção bit a bit: |;
  • disjunção exclusiva bit a bit: ^;
  • negação bit a bit: ~.

Para entender o funcionamento desses operadores, devemos colocar-mo-nós ao nível dos bits. Considere uma variável c do tipo unsigned char. c é representada por um byte, ou seja oito bits. Supondo que o valor de c seja , os oito bits serão então . O sentido de cada um dos operador será apenas explicado através de exemplos, agrupados na seguinte tabela:

c >> 2 23 >> 2 00010111 >> 2 00000101 5
c << 1 23 >> 1 00010111 << 1 00101110 46
c & 24 23 & 24 00010111 & 00011000 00010000 16
c ^ 24 24 ^ 24 00010111 >> 0001100 00001011 11
~c ~23 ~00010111 11101000 232
Exercícios[editar | editar código-fonte]
  • Utilizando operadores de combinação bit a bit, escreva uma função que dada dois inteiros ${\displaystyle n} e $p retorna ${\displaystyle n\times 2^{p}}.

Impressão de valores inteiros[editar | editar código-fonte]

A impressão de um valor inteiro utilizando a função fprintf é realizada através de uma diretiva de formatação que pode ter como componentes uma diretiva de tamanho e uma diretiva de sinal. A diretiva de tamanho é hh para char, h para short, omitida para int, l para long e ll para long long. A diretiva de sinal é i para tipos com sinal e u para tipos sem sinal. Assim o programa seguinte permite imprimir valores constantes inteiros disponibilizados no arquivo climits:

#include <climits>
#include <cstdio>
int main ()
{
  fprintf(stdout, "SCHAR_MIN = %hhi\n", SCHAR_MIN);
  fprintf(stdout, "SCHAR_MAX = %hhi\n", SCHAR_MAX);
  fprintf(stdout, "UCHAR_MAX = %hhu\n", UCHAR_MAX);
  fprintf(stdout, "SHRT_MIN = %hi\n", SHRT_MIN);
  fprintf(stdout, "SHRT_MAX = %hi\n", SHRT_MAX);
  fprintf(stdout, "USHRT_MAX = %hu\n", USHRT_MAX);
  fprintf(stdout, "INT_MIN = %i\n", INT_MIN);
  fprintf(stdout, "INT_MAX = %i\n", INT_MAX);
  fprintf(stdout, "UINT_MAX = %u\n", UINT_MAX);
  fprintf(stdout, "LONG_MIN = %li\n", LONG_MIN);
  fprintf(stdout, "LONG_MAX = %li\n", LONG_MAX);
  fprintf(stdout, "ULONG_MAX = %lu\n", ULONG_MAX);
  fprintf(stdout, "LLONG_MIN = %lli\n", LLONG_MIN);
  fprintf(stdout, "LLONG_MAX = %lli\n", LLONG_MAX);
  fprintf(stdout, "ULLONG_MAX = %llu\n", ULLONG_MAX);
}

A execução desse programa resulte na impressão seguinte na saída padrão:

SCHAR_MIN = -128
SCHAR_MAX = 127
UCHAR_MAX = 255
SHRT_MIN = -32768
SHRT_MAX = 32767
USHRT_MAX = 65535
INT_MIN = -2147483648
INT_MAX = 2147483647
UINT_MAX = 4294967295
LONG_MIN = -2147483648
LONG_MAX = 2147483647
ULONG_MAX = 4294967295
LLONG_MIN = -9223372036854775808
LLONG_MAX = 9223372036854775807
ULLONG_MAX = 18446744073709551615

Graças à sobrecarga do operador de impressão, C++ não precisa das diretivas de formatação: ele adapta-se automaticamente em função do tipo do valor a ser impresso. Assim, a mesma saída pode ser obtida com o seguinte programa:

#include <climits>
#include <iostream>
using namespace std;
int main ()
{
  cout << "SCHAR_MIN = " << SCHAR_MIN << endl
    << "SCHAR_MAX = " << SCHAR_MAX << endl
    << "UCHAR_MAX = " << UCHAR_MAX << endl
    << "SHRT_MIN = " << SHRT_MIN << endl
    << "SHRT_MAX = " << SHRT_MAX << endl
    << "USHRT_MAX = " << USHRT_MAX << endl
    << "INT_MIN = " << INT_MIN << endl
    << "INT_MAX = " << INT_MAX << endl
    << "UINT_MAX = " << UINT_MAX << endl
    << "LONG_MIN = " << LONG_MIN << endl
    << "LONG_MAX = " << LONG_MAX << endl
    << "ULONG_MAX = " << ULONG_MAX << endl
    << "LLONG_MIN = " << LLONG_MIN << endl
    << "LLONG_MAX = " << LLONG_MAX << endl
   << "ULLONG_MAX = " << ULLONG_MAX << endl;
}

Números decimais[editar | editar código-fonte]

As linguagens C e C++ possuem três tipos reais, chamados tipos flutuantes reais, para representar números decimais. São eles: float, double e long double, em ordem crescente de tamanho e precisão.

Constantes decimais[editar | editar código-fonte]

As constantes dos tipos flutuantes podem ser escritos em base 10 e 16 (utilizando o prefixo 0x). Uma constante flutuante possui até três partes:

  • O significante é uma seqüência de dígitos, com possívelmente um ponto para separar a parte inteira da parte decimal.
  • O expoente é iniciado com o caractere e ou Eseguido de uma seqüência de dígitos para os números escritos em base 10. Para os números flutuantes escritos em base 16, o expoente inicia com o caractere p ou P e o expoente é aplicado ao número 2 ao invés de 10. Se o significante contem um ponto, então o expoente é opcional.
  • O sufixo flutuante que indica qual é o tipo da constante. Quando é ausente, o tipo é double. Pode ser f ou F para indicar que é do tipo float e pode ser l ou L para indicar que é do tipo long double.

Não podemos esquecer de assinalar que tanto o significante como o expoente podem iniciar opcionalmente com um sinal positivo (caractere +) ou negativo (caractere -).

Seguem alguns exemplos de como escrever o número :

3.14 /* tipo: double */
.314e1/* tipo: double */
314e-2 /* tipo: double */
314e-2f /* tipo: float */
314e-2L /* tipo: long double */

Operadores decimais[editar | editar código-fonte]

Os valores de todos os tipos flutuantes podem ser combinados através de operadores aritméticos como a adição, subtração, etc. São eles:

  • adição: +,
  • subtração: -,
  • multiplicação: *,
  • divisão: /,
  • negação: -.

No caso dos operadores de divisão, se o segundo operando for nulo, o resultado é indefinido. Repare que, diferente de Objective Caml, que provê operadores diferentes para os tipos int e float, tanto em C e C++, os operadores aritméticos são os mesmos tanto para os tipos inteiros quanto para os tipos flutuantes.

A biblioteca padrão de C fornece funções que implementam os operadores matemáticos clássicos. Para utilizar essas funções, deve-se fazer a seguinte diretiva:

#include <cmath>

Ou, se for programa em C:

#include <math.h>

Segue uma tabela com apenas parte das funcionalidades disponíveis. A cada operador matemático, são associadas três funções, cada um correspondendo a um dos três tipos flutuantes:

funcionalidade Tipo double Tipo float Tipo long double
seno: double sin(double x); float sinf(float x); long double sinl(long double x);
cosseno: double cos(double x); float cosf(float x); long double cosl(long double x);
tangente: double tan(double x); float tanf(float x); long double tanl(long double x);
arccosseno: double acos(double x); float acosf(float x); long double acosl(long double x);
arcseno: double asin(double x); float asinf(float x); long double asinl(long double x);
arctangente: double atan(double x); float atanf(float x); long double atanl(long double x);
expoente em base : double exp(double x); float expf(float x); long double expl(long double x);
expoente em base 2: double exp2(double x); float exp2f(float x); long double exp2l(long double x);
logaritmo em base (logaritmo natural): double log(double x); float logf(float x); long double logl(long double x);
logaritmo em base : double log2(double x); float log2f(float x); long double log2l(long double x);
logaritmo em base : double log10(double x); float log10f(float x); long double log10l(long double x);
raiz quadrada. double sqrt(double x); float sqrtf(float x); long double sqrtl(long double x);
raiz cúbica: double cbrt(double x); float cbrtf(float x); long double cbrtl(long double x);
valor absoluto: double fabs(double x); float fabsf(float x); long double fabsl(long double x);
potência: double pow(double x, double y); float powf(float x, float y); long double powl(long double x, long double y);
arredondamento para cima: double ceil(double x); float ceilf(float x); long double ceill(long double x);
arredondamento para baixo: double floor(double x); float floorf(float x); long double floorl(long double x);
arredondamento para o inteiro mais próximo: double round(double x); float roundf(float x); long double roundl(long double x);
arredondamento para o mais próximo int: int rint(double x); int rintf(float x); int rintl(long double x);
arredondamento para o mais próximo long int: long int lrint(double x); long int lrintf(float x); long int lrintl(long double x);
arredondamento para o mais próximo long long int: long long int llrint(double x); long long int llrintf(float x); long long int llrintl(long double x);
truncamento: double trunc(double x); float truncf(float x); long double truncl(long double x);

Apresentamos de forma muito superficial os tipos flutuantes. A biblioteca padrão oferece outras funcionalidades, em particular permite um controle muito afinado de problemas de precisão e de estouro. As funções apresentadas devem porém devem satisfazer 99,9% das necessidades de programação.

Conversão entre tipos[editar | editar código-fonte]