Implementação da programação modular I - 1 Implementação da programação modular I Aula 10 Agenda Espaços de dados Tipos de dados Tipos definidos pelo usuário Imposição de tipos Objetivos Apresentar os conceitos relacionados com espaços de dados e tipagem de espaços de dados. Referência Capítulo 6 Aula10-ProgramacaoModular-i-2006-1.doc 26-mar-2006
Implementação da programação modular I - 2 Espaços de dados Espaços de dados armazenam dados código não deixa de ser uma forma de dado Espaços de dados simples (contíguos) possuem: um meio de armazenamento uma função de acesso a forma mais simples dessa função é o endereço do espaço em memória real pode ser uma expressão complexa: TabSimb[ ObterHash( Simbolo )]->ValorElemento acessa o espaço ocupado pelo valor do 1o. elemento da lista de colisão de Simbolo uma extensão espaço ocupado em bytes sizeof Os espaços de dados podem ser realizados em diferentes meios de armazenamento memória principal sistema de arquivos bases de dados memória virtual memória segmentada máquinas remotas
Implementação da programação modular I - 3 Espaços de dados Cada espaço de dados possui um nome que o referencia nome de uma variável referencia o espaço designado pelo nome uma referência calculada através de uma função de acesso A[j] *pa é o espaço de dados apontado por pa robj é o espaço de dados referenciado por robj pelemtabsimb * ObterElemTabSimb(char * pszsimbolo) *( ObterElemTabSimb( "um_simbolo" )) é o espaço de dados associado ao símbolo "um_simbolo" Amarração (binding) é a operação de associar um espaço de dados a um determinado nome ou função de acesso
Aninhamento de espaços de dados Implementação da programação modular I - 4 Espaços de dados podem ser aninhados estruturas (struct) podem conter elementos que por sua vez também são estruturas Exemplos de espaços de dados aninhados cada um dos elementos de um vetor e o próprio vetor como um todo cada elemento de um objeto é um espaço de dados, e também o objeto como um todo cada atributo de um registro de um arquivo, cada registro de um arquivo e o próprio arquivo Exemplo de structs aninhados em C typedef struct { char Nome[ DIM_NOME ] ; char SobreNome[ DIM_NOME ] ; } tpnome ; typedef struct { tpnome Nome ; tpendereco Endereco ; } tppessoa ;
Espaços de dados simples e compostos Implementação da programação modular I - 5 Espaços de dados podem ser simples: todo o valor está contido em um espaço contíguo variável struct vetor lista contida em vetor compostos: o valor completo requer vários espaços de dados, simples ou compostos, para ser armazenado lista encadeada árvore base de dados bases de dados distribuídas Espaços de dados compostos também podem ser tratados como um todo através de um nome nome que designa uma lista cabeça da lista contendo dados relativos à lista como um todo sujeito a restrições, o nome poderia ser o ponteiro para a origem da lista nome que referencia uma árvore cabeça da árvore contendo dados relativos à árvore como um todo sujeito a restrições, o nome poderia ser o ponteiro para a raiz da árvore nome simbólico que referencia um arquivo nome ODBC que referencia uma base de dados como um todo handle que identifica uma janela
Realização física de espaços compostos Implementação da programação modular I - 6 Forma de assegurar um nome no nível físico: Usar cabeça de estrutura (structure head) Todos os clientes referenciam a cabeça A cabeça encapsula as referências internas necessárias para manipular eficientemente a estrutura O módulo encapsula os tipos Exemplo lista Cabeça da lista Lista Corrente Fim Exemplo árvore Cabeça da árvore raiz corrente
Implementação da programação modular I - 7 Tipos de dados Do ponto de vista do computador (quase todos eles) o conteúdo de um espaço de dados é uma seqüência de bits. A interpretação do conteúdo do espaço de dados depende do código executado ao acessá-lo. O tipo de dado de um espaço de dados determina como interpretar o conteúdo de um espaço de dados a organização de baixo nível deste espaço a codificação o tamanho em bytes o conjunto de valores permitidos Tipos computacionais são definidos pela linguagem de programação int char char * Tipos do usuário são declarados pelo programador. Em C e C++ enum struct union typedef Em C++ e Java class
Implementação da programação modular I - 8 Espaços de dados e tipos Para o computador o conteúdo da memória não possui interpretação específica espaços de dados são interpretados através das funções (métodos) que os manipulam O tipo de um espaço de dados determina como deve ser interpretado o seu conteúdo Cada espaço de dados tem um tipo pode ser declarado explicitamente através de código de declaração C, C++, Java pode ser declarado implicitamente por contexto Scheme, Lua C permite a declaração implícita de funções pode ser determinado através de um atributo contido no espaço de dados identificador do tipo Lua Identificadores do tipo de um espaço potencialmente polimorfo em C++ e Java pode ser estabelecido através do código usado assembler
Implementação da programação modular I - 9 Linguagens tipadas Em linguagens tipadas, ao amarrar um nome a um espaço de dados é explicitamente declarado o seu tipo Linguagens tipadas asseguram, por construção, a correta interpretação dos espaços de dados sempre associam tipos aos nomes as funções de acesso sempre conhecem o tipo do item acessado O compilador produzirá código em conformidade com o tipo declarado para o nome o tipo associado ao resultado da avaliação função de acesso não necessita determinar o tipo em tempo de execução Assumindo que os espaços de dados referenciados existem: typedef struct tgelemlista { char szsimbolo[ DIM_SIMBOLO ] ; unsigned IdSimbolo ; struct tgelemlista * pprox; ) tpelemlista; tpelemlista* Tabela[ DIM_TABELA ] ; ( Tabela[ ObterHash( szsimbolodado )]->pprox)->szsimbolo ou *(*( Tabela[ ObterHash( szsimbolodado )]).pprox ).szsimbolo acessam o mesmo vetor de caracteres o segundo elemento da lista
Implementação da programação modular I - 10 Tipos do usuário struct define agregações heterogêneas de dados cada campo contém um valor de um tipo específico pode ser diferente dos demais tipos. Em C o nome do tipo é struct tagnome typedef struct tagnome { tipo 1 campo 1 ; tipo 2 campo 2 ;... tipo n campo n ; struct tagnome * pproxnome ; } tpnome ; Em C++ o nome do tipo é o tag name struct tpnome { tipo 1 campo 1 ; tipo 2 campo 2 ;... tipo n campo n ; tpnome * pproxnome ; } ;
Implementação da programação modular I - 11 Tipos do usuário union define tipos alternativos para um mesmo espaço de dados possibilita acessar uma mesma área de dados utilizando diferentes tipos de um conjunto específico typedef union tagnome { tipo 1 NomeInterpretação 1 ; tipo 2 NomeInterpretação 2 ;... tipo n NomeInterpretação n ; } tpnome ; ao acessar o espaço de dados da union usando NomeInterpretação i será utiliza o tipo tipo i para interpretar o espaço o tamanho do espaço de dados da union tpnome será igual ao maior dos tamanhos dos tipos o uso descuidado desta estrutura pode trazer conseqüências desastrosas
Problemas de alinhamento em struct e union Implementação da programação modular I - 12
Implementação da programação modular I - 13 Imposição de tipos Podem-se forçar diferentes interpretações para um mesmo espaço de dados através da imposição de tipos (type casts) Uma imposição de tipos é a uma union na qual o conjunto dos possíveis tipos não é definido a priori Em C (e também em C++) ( short int * ) Vetor estabelece que o espaço de dados designado por Vetor deve ser interpretado como um ponteiro para um short int independentemente do tipo com que foi declarado A imposição de tipos em C/C++ pode violar as regras de controle de tipos, por exemplo vetor de 4 caracteres pode receber a interpretação vírgula flutuante *((float *) VetorChar ) ) Ao programar em C++ utilize as construções de imposição de tipo que a linguagem oferece ao invés da sintaxe C apresentada anteriormente: const_cast< tipo >( expressão ) static_cast< tipo >( expressão ) dynamic_cast< tipo >( expressão ) reinterpret_cast< tipo >( expressão ) Em Java existe uma forma limitada e segura de imposição de tipos impor o tipo de uma superclasse a um determinado objeto impor um tipo de classe herdeira a um objeto neste caso Java verifica se o objeto foi construído a partir da classe herdeira similar a dynamic_cast de C++
Exemplo do efeito da imposição de tipos #include <stdio.h> #include <memory.h> void main( void) { char Vet[ 5 ] ; int c0, c1, c2, c3 ; Implementação da programação modular I - 14 /* Definir o valor contido no espaço de dados */ memset( Vet, 0, sizeof( Vet )) ; memcpy( Vet, "ABCD", 4 ) ; /* Exibir várias interpretações do espaço de dados */ } printf( "\nlong hexa %lx", *((long * ) Vet) ) ; printf( "\nshort hexa %4x %4x", *(( short int * ) Vet ), *(( short int * ) &( Vet[ 2 ])) ) ; c0 = Vet[ 0 ] ; c1 = Vet[ 1 ] ; c2 = Vet[ 2 ] ; c3 = Vet[ 3 ] ; printf( "\nchar hexa %2x %2x %2x %2x", c0, c1, c2, c3 ); printf( "\nlong int %li", *((long * ) Vet) ) ; printf( "\nfloat %f", *((float *) Vet) ) ; printf( "\nstring %s", Vet ) ;
Exemplo do efeito da imposição de tipos Implementação da programação modular I - 15 Resultados da execução (em máquina Intel): long hexa 44434241 short hexa 4241 4443 char hexa 41 42 43 44 long int 1145258561 float 781.035217 string ABCD Dependendo da máquina (Intel, Motorola, etc.) o resultado pode ser diferente se o conteúdo de Vet fosse executado em uma máquina Intel, seriam executadas as 4 instruções: INC CX; INC DX; INC BX; e INC SP
Implementação da programação modular I - 16 Quando usar imposição de tipos Justifica-se o uso de imposição de tipos irrestrita ao implementar funções de gerenciamento de memória tpmeutipo * ptmeutipo ;... ptmeutipo = ( tpmeutipo * ) malloc( sizeof( tpmeutipo )) ; Ao programar em C++ use sempre os operadores new e delete e jamais as funções malloc e free mesmo para structs Ao desenvolver módulos genéricos muitas vezes é necessário utilizar referências para estruturas ou objetos genéricos Freqüentemente isto requer ponteiros para um tipo indefinido (void *) Existem mecanismos que permitem evitar o uso de void * No módulo de definição C/C++ inclua exatamente: typedef struct tpxxx * tppxxx ; No módulo de implementação inclua: struct tpxxx {... } ; Em C++ crie uma estrutura de herança a partir da qual se derivam todas as classes da família genérica
Imposição versus conversão de tipos Implementação da programação modular I - 17 Conversão de tipos converter um valor inteiro para um valor vírgula flutuante faz toda a computação usando aritmética de inteiros e manipulação de bits ao terminar, atribui o valor resultante a uma variável flutuante Imposição de tipos e conversão de tipos são operações distintas. Na imposição de tipos muda-se simplesmente a interpretação do espaço de dados, sem alterar o seu conteúdo consiste meramente em uma instrução para o compilador Na conversão, opera-se sobre o conteúdo do espaço de dados transforma o valor no tipo origem para um valor no tipo destino preserva a semântica do valor int j ; char ch ; ch = 'A' ; j = ch ; /* converte o caractere 'A' para um inteiro com valor igual à sua codificação decimal: 65 */ j = 81 ; ch = j ; /* converte o valor 81 para o caractere com esta codificação: 'a' */ /* Esta conversão pode perder bits */ j = StrToInt( "1234" ) ; /* converte o string para o valor binário equivalente */ j = StrToInt( "abcd" ) ; /* sinaliza erro de conversão */ j = ( int )"abcd" ; /* impõe a interpretação int ao string */
Cuidados com a imposição de tipos Implementação da programação modular I - 18 Evite o uso de imposições irrestritas de tipos Quando utilizar justifique muito bem inclua comentários no programa contendo esta justificativa Evite de todas as maneiras o uso de imposição de tipos indiscriminada ao programar orientado a objetos vale impor para superclasse, é automático vale impor para classe herdeira somente se tiver certeza que objeto havia sido criado na subclasse imposta ou em uma classe herdeira dela
Implementação da programação modular I - 19 Tipos genéricos em C insira um campo que servirá de identificador de tipo como primeiro campo das estruturas que formam uma família de estruturas genéricas. Antes de impor o tipo, verifique o tipo identificado no valor estrutura sendo processada struct( tpidtipo idtipo,... ) ; em C++ pode-se usar o operador typeid para determinar o tipo específico de um objeto genérico, somente depois efetue a imposição para este tipo em C++ pode-se usar templates para criar estruturas que requerem parâmetros do tipo tipo em Java use o operador instanceof, o método padrão isinstance, ou o método padrão getname para determinar o tipo específico de um objeto genérico.