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

Introdução editar

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

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

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

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

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

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

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

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

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

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
  • Utilizando operadores de combinação bit a bit, escreva uma função que dada dois inteiros   e   retorna  .

Impressão de valores inteiros editar

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

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

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

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