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

De Objective Caml para C e C++


DisciplinaEstrutura de Dados I8.853 materiais180.911 seguidores
Pré-visualização14 páginas
associar a cada nome um valor, em C/C++ essa associação é feita pela posição. Uma
outra diferença sútil é que os valores dos diferentes campos devem ser separados por vírgulas (é um ponto e vírgula
em Objective Caml). Assim, o exemplo seguinte lembra como pode-se escrever um valor de um tipo registro em
Objective Caml.
let origin = { x = 0.0 ; y = 0.0 }
O equivalente em C/C++ seria:
point origin = { 0.0 , 0.0 };
[ ADICIONAR UM PARÁGRAFO SOBRE PONTEIROS PARA REGISTROS]
Ponteiros 
Introdução
Uma variável C/C++ é a abstração de um trecho de memória que guarda um valor de um certo tipo, onde um trecho
de memória é uma seqüëncia de posições contíguas na memória. Um trecho de memória é definido pelo endereço da
primeira posição e o número de posições no trecho. Mais precisamente, qualquer lvalue (variável, campo de variável
de tipo registro, posição de variável de tipo arranjo) é a abstração de um trecho de memória. Na seqüência,
empregaremos o termo variável ao invés de lvalue.
Para cada variável, o compilador deve associar algum trecho de memória para aquela variável. Para evitar qualquer
problema de superposição e conflito, se o escopo de duas variáveis tem alguma interseção, os trechos de memória
aos quais essas variáveis correspondem devem ser disjunAssim, uma variável de tipo long long int vai
corresponder a um trecho de oito bytes na memória. O tamanho do tipo vai então depender diretamente do tipo de
dados a ser representado. Por exemplo, quando temos um tipo registro, o tamanho de memória necessário para a
representação de valores desse tipo é a soma dos tamanhos para representar cada campo do registro. Quando temos
um tipo arranjo, o tamanho de memória necessário é igual a produto do número de elementos do arranjo com o
tamanho necessário para armazenar cada elemento do arranjo.
As linguagens C e C++ possuem um operador unário, prefixado, nomeado sizeof que, aplicado a uma variável,
ou a uma expressão de tipo (entre parênteses), retorna o tamanho em bytes do trecho de memória necessário para
representar essa variável, ou valores do dado tipo. O pequeno programa seguinte ilustra os conceitos discutidos:
#include <cstdio>
int main ()
{
 int a;
 struct point {
 double x;
 double y;
 } p;
 float tab [4];
 printf(&quot;tamanho de a = %lu\n&quot;, sizeof a);
 printf(&quot;tamanho de p = %lu\n&quot;, sizeof p);
Construção de tipos 41
 printf(&quot;tamanho de p.x = %lu\n&quot;, sizeof p.x);
 printf(&quot;tamanho de p.y = %lu\n&quot;, sizeof p.y);
 printf(&quot;tamanho de tab = %lu\n&quot;, sizeof tab);
 printf(&quot;tamanho de tab[0] = %lu\n&quot;, sizeof tab[0]);
 printf(&quot;tamanho de tab[1] = %lu\n&quot;, sizeof tab[1]);
 printf(&quot;tamanho de tab[2] = %lu\n&quot;, sizeof tab[2]);
 printf(&quot;tamanho de tab[3] = %lu\n&quot;, sizeof tab[3]);
}
A execução desse programa resulte na seguinte impressão na saída padrão:
tamanho de a = 4
tamanho de p = 16
tamanho de p.x = 8
tamanho de p.y = 8
tamanho de tab = 16
tamanho de tab[0] = 4
tamanho de tab[1] = 4
tamanho de tab[2] = 4
tamanho de tab[3] = 4
O operador de endereço
Em C e C++, há um operador que permite saber qual o endereço do trecho de memória associado a uma variável: é o
operador &. É um operador unário, e é prefixado. Segue um pequeno programa que ilustra o uso desse operador.
#include <cstdio>
int main ()
{
 int a;
 struct point {
 double x;
 double y;
 } p;
 float tab [4];
 printf(&quot;endereco de a = %p\n&quot;, &a);
 printf(&quot;endereco de p = %p\n&quot;, &p);
 printf(&quot;endereco de p.x = %p\n&quot;, &p.x);
 printf(&quot;endereco de p.y = %p\n&quot;, &p.y);
 printf(&quot;endereco de tab = %p\n&quot;, &tab);
 printf(&quot;endereco de tab[0] = %p\n&quot;, &tab[0]);
 printf(&quot;endereco de tab[1] = %p\n&quot;, &tab[1]);
 printf(&quot;endereco de tab[2] = %p\n&quot;, &tab[2]);
 printf(&quot;endereco de tab[3] = %p\n&quot;, &tab[3]);
 printf(&quot;tab = %p\n&quot;, tab);
}
Observe que foi usada a diretiva de formatação %p para imprimir um endereço. A execução desse programa resulte
na seguinte impressão na saída padrão:
Construção de tipos 42
endereco de a = 0xbffff8d8
endereco de p = 0xbffff8e0
endereco de p.x = 0xbffff8e0
endereco de p.y = 0xbffff8e8
endereco de tab = 0xbffff8f0
endereco de tab[0] = 0xbffff8f0
endereco de tab[1] = 0xbffff8f4
endereco de tab[2] = 0xbffff8f8
endereco de tab[3] = 0xbffff8fc
tab = 0xbffff8f0
Algumas observações devem ser feitas:
\u2022\u2022 os endereços são impressos em base hexadecimal.
\u2022\u2022 em um registro, campos sucessivos ficam em endereços sucessivos
\u2022\u2022 o endereço de um registro é o mesmo endereço do primeiro campo do registro
\u2022\u2022 em um arranjo, posições sucessivas ficam em endereços sucessivos
\u2022\u2022 o endereço de um arranjo é o mesmo endereço da primeira posição
\u2022\u2022 um arranjo nada mais é que o endereço da primeira posição desse arranjo (como discutimos no parágrafo sobre
arranjos como parâmetros de funções).
O leitor atento e curioso pode então fazer a seguinte pergunta. No programa exemplo, quando aplico o operador de
endereço a uma variável ou a qualquer lvalue, qual é o tipo dessa expressão? A resposta é que trata-se de um tipo
ponteiro, para o qual reservamos o parágrafo seguinte.
Os tipos ponteiros
O conceito de ponteiro é fundamental em programação. Um valor de algum tipo ponteiro é o (endereço de) um
trecho de memória que corresponde a alguma variável. Portanto um ponteiro é algo que indica onde fica algo,
digamos então que aponta para algo. Se uma variável v tem o tipo t, a expressão &v é portanto uma expressão de
tipo ponteiro para t, que é denotada t * (é o mesmo símbolo que o da multiplicação aritmética).
A sintaxe para declarar uma variável de um tipo ponteiro é a seguinte:
tipo * nome;
Nesse caso, nome é o nome da variável, e tipo é o tipo do valor que está na locação de memória apontada.
Assim, o trecho de código seguinte fornece exemplos de uso dessa sintaxe:
int * a;
float x;
float * pf = &x;
Foram declaradas três variáveis:
\u2022 A variável a é um ponteiro para um trecho de memória que guarda um valor do tipo int. Observe que não tem
inicialização e o valor de a é qualquer. Como uma variável de qualquer tipo, a variável a deverá ser inicializada
antes de ser lida, caso contrário o comportamento do programa é indefinido.
\u2022 A variável x é uma variável não ponteiro, do tipo float.
\u2022 A variável pf é uma variável de tipo ponteiro para float, ou float *. É inicializada com o endereço da
variável x. Diz-se que pf aponta para x.
Oo símbolo * é associado a nome e não a tipo. Assim, se quisermos declarar mais de um ponteiro para o mesmo
tipo de uma vez só, devemos repetir * antes de cada nome. Por exemplo:
Construção de tipos 43
int * p1, * p2;
Ponteiros e Objective Caml
Em Objective Caml, um conceito similar, embora mais geral, é o de referência. O conceito de referência OCaml
corresponde ao de uma variável C/C++ e também ao de ponteiro. Assim, em OCaml, um valor de tipo int ref é
alguma locação de memória que contem um valor do tipo int e que pode ser alterado (dizemos que é mutável). Ou
seja é exatamente a mesma coisa que uma variável C ou C++ do tipo int. Tal variável pode ser lida em expressões e
alterada com atribuições. O equivalente a um ponteiro C para um inteiro, ou seja a um valor do tipo int * seria,
em OCaml, um valor do tipo int ref ref: corresponde à memorização de um local onde um inteiro é
memorizado.
O operador de dereferenciamento
Dado um valor de algum tipo ponteiro, o operador de dereferenciamento permite acessar o valor do trecho de
memória apontado. É um operador prefixado, que se escreve *. Se p é um valor de tipo t * (ponteiro para o tipo
t), então a expressão * p é do tipo t. O valor dela é o valor guardado na memória no endereço guardado por p.
Por exemplo, o seguinte trecho de código
int n = 42;
int * pi = &n;
printf(&quot;O valor apontado por pi é %i.\n&quot;, *pi);
resultará na impressão da