De Objective Caml para C e C++/Os tipos básicos
Introdução
editarO 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:
- void: o tipo vazio,
- bool: o tipo booleano,
- int: o tipo inteiro,
- char: o tipo dos caracteres,
- float e double são tipos para os números decimais.
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
editarEm 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
editarA 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
editarNa 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
editarPara 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
editarAs 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
editarPara 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
editarAs 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
editarOs 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
editarOs 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
editarA 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
editarAs 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
editarAs 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
editarOs 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.