Buscar

Tipos de Dados II: Uniões e Ponteiros

Prévia do material em texto

GCC 105 – LINGUAGENS DE 
PROGRAMAÇÃO I
AULA 4 – Tipos de Dados II
1º Semestre de 2015
Prof. Janderson Rodrigo de Oliveira
Universidade Federal de Lavras
Departamento de Ciência da Computação
Uniões
• Uma união é um tipo cujas variáveis podem armazenar 
diferentes valores de tipos em vários momentos durante a 
execução de um programa.
• Em outras palavras, com uma união é possível suportar dados 
diferentes, alocados no mesmo espaço de memória, em 
momentos diferentes.
• Fortran, C e C++ fornecem construções para representar 
uniões nas quais não existe um suporte da linguagem para 
verificação de tipos. As uniões nessas linguagens são 
chamadas uniões livres.
Uniões
• Exemplo de união em C:
union tipo{
int inteiro;
float real;
};
union tipo elemento;
...
elemento.real = 1.35;
elemento.inteiro = 27;
Uniões
• Exemplo de união em C sem a verificação de tipo:
union tipo{
int inteiro;
float real;
};
union tipo elemento;
float x;
...
elemento.inteiro = 27;
x = elemento.real;
Uniões
• A última atribuição não é verificada em relação ao seu tipo, 
porque o sistema não pode determinar o tipo atual do valor 
de elemento, então ele atribui o valor 27 para a variável float
x, o que obviamente não faz sentido.
• A verificação de tipos união requer que cada construção de 
união inclua um indicador de tipo. Tal indicador é chamado de 
etiqueta ou discriminante, e uma união com um discriminante 
é chamada de união discriminada.
– Primeira linguagem a oferecer união discriminada: ALGOL 68.
Uniões
• Uniões podem ser potencialmente inseguras em algumas 
linguagens, por não permitirem que as referências para suas 
uniões sejam verificadas em relação aos seus tipos (Fortran, C 
e C++).
• Exemplo de linguagem que utiliza uniões de forma segura: 
Ada.
• Java e C# não incluem uniões.
Uniões – Exemplo em C
#include <stdio.h>
union Tipo{
int valorInteiro;
float valorReal;
};
intmain(){
//Parte 1
union Tipo exemplo;
exemplo.valorInteiro = 10;
printf("Mensagem 1: %d\n", exemplo.valorInteiro);
exemplo.valorReal = 3.5;
printf("Mensagem 2: %.2f\n", exemplo.valorReal);
printf("Mensagem 3: %d\n", exemplo.valorInteiro);
return 0;
}
Uniões – Exemplo em C
• Resultado
Uniões – Exemplo em C
#include <stdio.h>
union Tipo{
int valorInteiro;
float valorReal;
};
intmain(){
//Parte 1: ...
//Parte 2
printf("Endereco 1: %d\n", &exemplo.valorInteiro);
printf("Endereco 2: %d\n", &exemplo.valorReal);
return 0;
}
Uniões – Exemplo em C
• Resultado
Uniões – Exemplo em C
#include <stdio.h>
union Tipo{
int valorInteiro;
float valorReal;
};
intmain(){
//Parte 1: ...
//Parte 2: ...
//Parte 3
exemplo.valorInteiro = 1.0;
printf("Mensagem 4: %d\n", exemplo.valorInteiro);
printf("Mensagem 5: %.2f\n", exemplo.valorReal);
return 0;
}
Uniões – Exemplo em C
• Resultado
Ponteiros e Referências
• Um tipo ponteiro é um no qual as variáveis têm uma faixa de 
valores que consistem em endereços de memória e um valor 
especial, nil.
• O valor nil não é um endereço válido e é usado para indicar 
que um ponteiro não pode ser usado atualmente para 
referenciar uma célula de memória.
• Ponteiros são projetados para dois tipos de uso.
Ponteiros e Referências
• Ponteiros são projetados para dois tipos de uso:
1. Endereçamento indireto, frequentemente usado em 
programação de linguagem de montagem.
2. Gerenciamento de armazenamento dinâmico.
• Um ponteiro pode ser usado para acessar uma posição na 
área onde o armazenamento é dinamicamente alocado, 
chamado de heap.
• As variáveis dinamicamente alocadas a partir do heap são 
chamadas de variáveis dinâmicas do heap.
Ponteiros e Referências
• Ponteiros não são tipos estruturados, apesar de serem 
definidos usando um operador de tipo (* em C e C++ e access
em Ada).
• São diferentes de variáveis escalares porque são usados para 
referenciar alguma outra variável e não para armazenar dados 
de algum tipo.
• Linguagens que fornecem um tipo ponteiro incluem duas 
operações fundamentais: atribuição e desreferenciamento.
Ponteiros e Referências
• Atribuição:
– Modifica o valor de uma variável de ponteiro para algum 
endereço útil. 
– Deve existir um operador explícito ou um subprograma pré-
definido para obter o endereço de uma variável.
• Um ocorrência de ponteiro em uma expressão pode ser 
interpretada de duas maneiras:
1. Referência ao conteúdo da célula de memória a qual está 
vinculada.
2. Referência ao valor dentro da célula de memória apontado 
pela célula a qual a variável de ponteiro está vinculada 
(desreferenciar).
Ponteiros e Referências
• O desreferenciamento de ponteiros pode ser tanto explícito 
quanto implícito.
• No Fortran 95, é implícito, mas em outras linguagens 
contemporâneas, ocorre apenas quando explicitamente 
especificado.
• Em C++, ele é explicitamente especificado pelo símbolo 
asterisco (*). Exemplo:
j = *ptr;
Ponteiros e Referências
Ponteiros e Referências – Exemplo em 
C
#include <stdio.h>
intmain(){
//Parte 1
int aux;
int *auxPtr;
printf("PARTE 1\n");
printf("Mensagem 1: %d\n", aux);
printf("Mensagem 2: %d\n", &aux);
printf("Mensagem 3: %d\n", auxPtr);
printf("Mensagem 4: %d\n", *auxPtr);
return 0;
}
Ponteiros e Referências – Exemplo em 
C
Ponteiros e Referências – Exemplo em 
C
#include <stdio.h>
intmain(){
//Parte 1: ...
//printf("Mensagem 4: %d\n", *auxPtr);
aux = 13;
auxPtr = &aux;
printf("\nPARTE 2\n");
printf("Mensagem 5: %d\n", aux);
printf("Mensagem 6: %d\n", &aux);
printf("Mensagem 7: %d\n", auxPtr);
printf("Mensagem 8: %d\n", *auxPtr);
return 0;
}
Ponteiros e Referências – Exemplo em 
C
Ponteiros e Referências
• Quando os ponteiros apontam para registros, a sintaxe das 
referências para os campos desses registros varia entre as 
linguagens.
• Linguagens que fornecem ponteiros para o gerenciamento de 
armazenamento dinâmico devem incluir uma operação 
explícita de alocação.
• A alocação é algumas vezes especificada como um 
subprograma, como malloc em C.
• Em C++, a alocação é feita com o operador new.
Problemas com Ponteiros
• Principal causa: algumas linguagens não permitem a 
liberação implícita da memória alocada.
• Problemas comuns:
– Ponteiros soltos – ponteiro que contém o endereço de uma 
variável dinâmica já liberada.
– Variáveis dinâmicas perdidas – variável dinâmica alocada que 
não está mais acessível para os programas de usuário.
Problemas com Ponteiros
• Ponteiros soltos
– A posição sendo apontada pode ter sido realocada para alguma 
outra variável dinâmica.
– Se o ponteiro solto é usado para modificar a variável dinâmica, o 
valor da nova variável é destruído.
– A liberação explícita de variáveis dinâmicas é a causa dos 
ponteiros soltos.
Problemas com Ponteiros
• Ponteiros soltos – Exemplo em C++:
int *vetor1 = new int[100];
int *vetor2;
vetor2 = vetor1;
delete [] vetor1;
//Agora, vetor2 é solto, porque o armazenamento para o qual 
ele estava apontando foi liberado
Problemas com Ponteiros
• Variáveis dinâmicas perdidas
– Variáveis dinâmicas perdidas são frequentemente chamadas de 
lixo, pois não são mais úteis para seus propósitos gerais e não 
podem ser realocadas para algum novo uso no programa.
– Usualmente são criadas pela seguinte sequência de operações:
1. O ponteiro p1 é configurado para uma variável dinâmica 
recém-alocada;
2. p1 é posteriormente configurado para apontar para outra 
variável dinâmica recém-criada.
Tipos de Referência
• Uma variável do tipo de referênciaé similar a um ponteiro, 
com uma diferença fundamental:
– Um ponteiro se refere a um endereço em memória;
– Uma referência indica um objeto ou um valor em memória.
• Permitem liberação implícita da memória.
• Todas as variáveis nas linguagens orientadas a objetos 
Smalltalk, Python, Ruby e Lua são referências. 
– Elas são sempre implicitamente desreferenciadas e seus valores 
diretos não podem ser acessados.
Verificação de Tipos
• A verificação de tipos é a atividade de garantir que os 
operandos de um operador são de tipos compatíveis.
• Um tipo compatível ou é legal para o operador, ou é permitido 
a ele, dentro das regras da linguagem, ser implicitamente 
convertido pelo código gerado pelo compilador (ou pelo 
interpretador) para um tipo legal.
• A conversão automática é chamada de coerção.
Verificação de Tipos
• Exemplo:
– Se uma variável int e uma variável float são adicionadas em 
Java, o valor da variável inteira sofre uma coerção para float e 
uma adição de ponto flutuante é realizada.
• Um erro de tipo é a aplicação de um operador a um operando 
de um tipo não apropriado.
• Dois tipos de verificação:
– Verificação de tipos estática;
– Verificação de tipos dinâmica.
Verificação de Tipos
• Verificação de tipos estática:
– Todas as vinculações de variáveis a tipos são estáticas;
– Tempo de compilação;
– Em C++ todas as variáveis são estaticamente vinculadas a tipos.
• Verificação de tipos Dinâmica:
– As vinculações de variáveis a tipos são dinâmicas;
– Tempo de execução;
– JavaScript e PHP só permitem verificação de tipos dinâmicas.
Tipagem Forte
• Uma linguagem de programação é fortemente tipada se erros 
de tipos são sempre detectados.
• Requer que os tipos de todos os operandos possam ser 
determinados, em tempo de compilação ou em tempo de 
execução.
• Habilidade de detectar usos incorretos de variáveis que 
resultam em erros de tipo.
Tipagem Forte
• Linguagem que não são fortemente tipadas:
– Fortran 95: Equivalence;
– C e C++: union.
• Linguagens “quase” fortemente tipadas
– Ada, Java e C#;
– Não existem maneiras implícitas pelas quais os erros de tipos 
possam passar despercebidos;
– Os tipos podem ser convertidos explicitamente, o que pode 
gerar um erro de tipos.
• Linguagens fortemente tipadas: ML.
Equivalência de Tipos
• Regras de compatibilidade ditam os tipos de operandos 
aceitáveis para cada um dos operadores e especificam os 
possíveis tipos de erros da linguagem.
• O tipo de um operando pode ser convertido implicitamente 
pelo compilador ou pelo sistema de tempo de execução para 
torná-lo aceitável ao operador.
• Dois tipos são equivalentes se um operando de um em uma 
expressão é substituído por um de outro, sem coerção. 
Equivalência de Tipos
• A equivalência de tipos é uma forma estrita de 
compatibilidade – compatibilidade sem coerção.
• Existem duas abordagens para definir equivalência de tipos:
– Por nome;
– Por estrutura;
Equivalência de Tipos
• Equivalência de tipos por nome 
– Duas variáveis são equivalentes se são definidas na mesma 
declaração ou em declarações que usam o mesmo nome de 
tipo.
– Fácil de implementar, mas é mais restritiva.
– Uma variável cujo tipo é uma subfaixa dos inteiros não seria 
equivalente a uma variável do tipo inteiro. Por exemplo, em 
Ada:
type IndiceTipo is 1..100;
contador : Integer;
indice : IndiceTipo;
Equivalência de Tipos
• Equivalência de tipos por estrutura 
– Duas variáveis são equivalentes se seus tipos têm estruturas 
idênticas.
– Mais flexível, mas mais difícil de implementar.
– Questões: dois registros são equivalentes se têm a mesma 
estrutura, mas nomes de campos diferentes? Dois vetores são 
equivalentes se têm o mesmo tipo de elemento, mas faixas de 
índices de 0..10 e 1..11?
Equivalência de Tipos
• Equivalência de tipos por estrutura 
– Outra dificuldade: não permite diferenciar tipos com a mesma 
estrutura. Exemplo em Ada:
– Os tipos destas variáveis são equivalentes sob a equivalência de 
tipos por estrutura, permitindo que sejam misturados em 
expressões, o que é claramente indesejável nesse caso.
type Celsius = Float;
Fahrenheit = Float;
Equivalência de Tipos
• C usa tanto a equivalência de tipos por nome quanto por 
estrutura.
– Tipos struct, enum e union criam um novo tipo que não é 
equivalente a nenhum outro. 
– Então, a equivalência de tipos por nome é usada para os tipos 
que representam estruturas, enumerações e uniões.
– Tipos não escalares usam equivalência de tipos por estrutura. 
Tipos matrizes são equivalentes se tiverem componentes do 
mesmo tipo.
– Se um tipo matriz tiver um tamanho constante, ele é 
equivalente ou a outras matrizes com mesmo tamanho ou 
àquelas sem um tamanho constante.
Teoria e Tipos de Dados
• A teoria de tipos é uma ampla área de estudo em matemática, 
lógica, ciência da computação e filosofia.
• Em Ciência da Computação, existem dois ramos de teoria de 
tipos:
– Prático – se preocupa com tipos de dados em linguagens de 
programação comerciais;
– Abstrato – foca principalmente em cálculo lambda tipado, uma 
área de pesquisa intensa por parte de cientistas da computação 
teóricos.

Continue navegando