De Objective Caml para C e C++
57 pág.

De Objective Caml para C e C++


DisciplinaEstrutura de Dados I9.024 materiais181.564 seguidores
Pré-visualização14 páginas
ou seja os valores são copiados
para instanciar os parâmetros formais. O fato de termos um parâmetro por referência faz com que é o próprio
argumento que é recebido pela função chamada, e não uma cópia do valor desse argumento.
O exemplo seguinte mostra um aplicação clássica de passagem por referência:
#include <cstdio>
void swap (int & a, int & b)
{
 int tmp;
 tmp = a;
 a = b;
 b = tmp;
}
int main ()
{
 int a = 1;
 int b = 2;
 printf(&quot;antes: a = %i, b = %i.\n&quot;, a, b);
Construção de tipos 47
 swap (a, b);
 printf(&quot;depois: a = %i, b = %i.\n&quot;, a, b);
}
A execução desse programa resultará na seguinte impressão na saída padrão:
antes: a = 1, b = 2.
depois: a = 2, b = 1.
Para fins de comparação, um programa equivalente em C, com ponteiros e sem referências C++, seria:
#include <stdio.h>
void swap (int * a, int * b)
{
 int tmp;
 tmp = * a;
 * a = * b;
 & b = tmp;
}
int main ()
{
 int a = 1;
 int b = 2;
 printf(&quot;antes: a = %i, b = %i.\n&quot;, a, b);
 swap (& a, & b);
 printf(&quot;depois: a = %i, b = %i.\n&quot;, a, b);
}
Inicialização de referências
 Quando não é um parâmetro de função, uma referência deve ser inicializada na sua declaração. Se omitirmos
essa inicialização, então o compilador emitirá uma mensagem de erro.
#include <cstdio>
int main ()
{
 int a = 1;
 int b = 2;
 int & r;
 printf(&quot;a = %i, b = %i, r = %i.\n&quot;, a, b, r);
}
A compilação desse código não foi bem-sucedida, e imprimiu a seguinte mensagem na tela:
prog.cpp: In function 'int main()':
prog.cpp:6: error: 'r' declared as reference but not initialized
Construção de tipos 48
Tipos uniões 
Tipos uniões agrupam diversos tipos em um mesmo tipo. Eles possuem portanto um papel muito similar aos tipos
variantes de Objective Caml. Infelizmente, a programação por tipos uniões de C e C++ não é tão simples quanto a
de tipos variantes.
A sintaxe para definir um tipo união é a seguinte:
union etiqueta {
 tipo1 nome1;
 tipo2 nome2;
 ...
 tipoN nomeN;
};
Essa sintaxe é muito similar à dos tipos registros. A semântica porém é bem diferente. Para explicar ela, vamos nos
basear em um exemplo:
typedef union Unumero {
 long inteiro;
 double real;
} Tnumero;
Tnumero numero.
Então Tnumero é o nome de um tipo união e numero é uma variável do tipo Tnumero. Os valores da variável
numero podem ser interpretados como sendo, ou do tipo long ou do tipo double. Para intepretar numero
como um long, devemos utilizar o operador de seleção . (um ponto) da seguinte forma: numero.inteiro.
Para interpretar numero como um double, utilizamos o operador de seleção com o nome correspondente:
numero.real. Segue um pequeno programa que ilustra esses conceitos:
#include <cstdio>
int main ()
{
 typedef union Unumero {
 long inteiro;
 double real;
 } Tnumero;
 Tnumero numero;
 numero.inteiro = 2l;
 numero.inteiro *= numero.inteiro;
 printf(&quot;numero.inteiro = %li.\n&quot;, numero.inteiro);
 numero.real = 3.0;
 numero.real *= numero.real;
 printf(&quot;numero.real = %lf.\n&quot;, numero.real);
}
No programa, definimos uma variável numero do tipo Tnumero que é atribuída primeiro um valor inteiro, e
segundo um valor flutuante. A variável só pode armazenar um 'único valor' de um desses tipos. Assim, a execução
desse programa resulta na seguinte impressão na saída padrão:
Construção de tipos 49
numero.inteiro = 4.
numero.real = 9.000000.
O leitor sagaz deve questionar-se agora, como pode saber se o valor que a variável armazena é de um tipo ou de
outro? Ou seja qual das duas interpretações possíveis é correta? O exemplo mostra que cada interpretação é
correta em trechos distintos do código. Inicialmente, a variável é atribuída é um valor inteiro. O que aconteceria se
o programa fosse interpretar esse valor como um valor flutuante? E será que é legal fazer isso? Podemos construir
um pequeno programa para discutir as respostas as essas duas perguntas:
#include <cstdio>
typedef union Unumero {
 long inteiro;
 double real;
} Tnumero;
int main ()
{
 Tnumero numero;
 numero.real = 3.0;
 printf(&quot;numero.inteiro = %li.\n&quot;, numero.inteiro);
}
Aqui, a variável numero recebe um valor flutuante, que é interpretado como um número inteiro na chamada à
função de impressão na saída padrão. Nesse caso, o compilador gera um programa sem emitir nenhuma mensagem
de erro, nem sequer um aviso. Efetivamente, esse programa é perfeitamente legal. A saída gerada pela execução do
programa é:
numero.inteiro = 1074266112.
Opa!?? O que acontece? Bom, a forma como os bits são arranjados para representar 3.0 do tipo double não
tem nada a ver como a forma como o valor 3 do tipo long é representado... O que aparece na tela é o valor que a
representação binária de 3.0 tem quando é interpretado como sendo tipo long. Mais precisamente, como o tipo
long é representado por quatro bytes, e o tipo double com oito, o que aparece na tela a interpretação como
valor do tipo long são os quatro primeiros bytes da representação binária do tipo double...
Então, como esse tipo de manipulação é legal tanto em C quanto em C++, é um recurso que podemos utilizar
quando precisamos utilizar as representações binárias. Mas se precisamos trabalhar de forma mais abstrata,
utilizando mecanismos semelhantes ao dos tipos variantes em Objective Caml, como podemos fazer?
Programação de tipos variantes
Considere o seguinte tipo Objective Caml
type number = Inteiro of int | Flutuante of float | Erro
e funções para combinar valores desses tipos, como por exemplo a soma:
let number_sum (n1: number) (n2: number) =
 match n1, n2 with
 _, Erro | Erro, _ -> Erro
 | Inteiro i1, Inteiro i2 -> let positive n = n > 0 in
Construção de tipos 50
 if positive (i1 + i2) <> positive i1 
 then Flutuante ((float_of_int i1) +. (float_of_int i2)) 
 else Inteiro(i1 + i2)
 | Inteiro i, Flutuante f -> Flutuante ((float_of_int i) +. f)
 | Flutuante f, Inteiro i -> Flutuante (f +. (float_of_int i))
 | Flutuante f1, Flutuante f2 -> Flutuante (f1 +. F2)
Introdução à programação orientada a objetos
Introdução
Basicamente, a Programação Orientada a Objetos (POO) consiste em imaginar os problemas em pequenos pedaços,
que funcionam separadamente, mas como parte de um todo. Cada pedaço é chamado objeto, e ele possui
métodos(coisas que ele pode fazer, ou &quot;habilidades&quot;) e atributos(propriedades que ele possui, ou &quot;características&quot;).
Para deixar isso claro, vamos imaginar uma simulação virtual de uma casa. O que uma casa faz, e o que uma casa
contém? Na nossa &quot;Casa padrão&quot;, teremos a habilidade de deixar pessoas entrarem, e as características serão os
cômodos, portas e janelas da nossa casa. É uma casinha bem simples.
Com isso, temos nossa casa... ou não, por que ainda não dissemos ao computador o que é uma porta, nem janela,
nem comodo!
Associação
Esse tipo de situação é bastante frequente na Programação Orientada a Objetos. É que objetos criados por você se
utilizem de outros objetos. Uma vantagem disso, é que esses outros objetos não precisam ser criados por você: Uma
das vantagens da POO é a reutilização de código, que poupa trabalho ao programador, deixando tempo livre para que
ele se dedique ao que realmente importa no seu problema. Se você estivesse projetando uma casa de verdade, o que
faria: Fabricaria suas próprias portas e janelas, ou compraria elas prontas?
Nessa logica, vamos deixar a programação da janela e da porta para um terceiro. Assumindo elas prontas, vamos
apenas se utilizar delas.
As classes (&quot;matéria-prima dos objetos&quot;) podem se associar a outras classes de duas formas: Associação Simples (ou
&quot;Usa um&quot;, porque um objeto dessa classe contêm um objeto da outra), ou Herança (ou &quot;É um&quot;, pois um objeto dessa
classe