Tipos de Dados Tipagem forte e fraca Representação de tipos Equivalência, conversão e inferência de tipos Classes x Tipos Composição de tipos Extensão de tipos Tipagem forte e fraca Linguagens fortemente tipadas: o compilador e o ambiente de execução se encarregam de fazer a verificação de tipos Verificação de tipos: operandos, operadores, atribuições, subprogramas Verificação estática: compilador Verificação dinâmica: ambiente de execução Uma linguagem que não garante a ausência de erros de tipos, pelo menos de forma estática (em tempo de compilação) é dita fracamente tipada. Violação de tipos Um dos principais objetivos da utilização de sistemas de tipos em linguagens de programação é permitir a detecção de erros (de tipos). Considera-se um erro toda e qualquer violação das regras definidas pela linguagem de programação. A detecção de erros pode ser feita de forma estática, ou de forma dinâmica, caso seja possível a detecção somente durante a execução de programa. Exemplos: Estática: violação de tipo por atribuição Dinâmica: violação de tipo por leitura, com valor proveniente do meio externo, somente poderá ser detectada pelo ambiente de execução. Exemplo: tipos de dados em Pascal Como representar? primitivos: simples - integer - real - char - boolean enumeração restrição TIPOS Como compatibilizar? registro/ registro variante estruturados array set arquivo Exemplo de violação estática e dinâmica program tt; type natural=1..maxint; var i:integer; s:shortint; n:natural; begin compilador acusa i:=10; erro de tipo s:=-10; n:=i; n:=s; {violação dinâmica de tipo} writeln(n); n:= -10; {violação estática de tipo} i:=maxint; writeln(i); s:=i; {violação dinâmica de tipo} writeln(s); end. Exemplo de violação estática e dinâmica program tt; type natural=1..maxint; resultados: var i:integer; -10 s:shortint; 32767 n:natural; begin -1 i:=10; s:=-10; n:=i; n:=s; {violação dinâmica de tipo} writeln(n); n:= -10; {violação estática de tipo} i:=maxint; writeln(i); s:=i; {violação dinâmica de tipo} writeln(s); end. Portabilidade x representação de tipos Pessoas que apregoam não se preocupar com portabilidade geralmente fazem isto porque usam um único sistema e sentem que podem dar-se ao luxo de achar que “a linguagem é aquilo que meu compilador implementa” Esta é uma visão restrita e míope. Se seu programa é um sucesso, é muito provável que seja portado, de modo que alguém vai ter que procurar e corrigir problemas relacionados com características dependentes da implementação. Além disto, freqüentemente é necessário compilar programas com outros compiladores para o mesmo sistema e mesmo uma versão futura de seu compilador favorito pode fazer algumas coisas de maneira diferente da maneira atual. Bjarne Stroustrup, criador de C++ Tamanhos de tipos: C++ Os tamanhos dos objetos de C++ são expressos em múltiplos do tamanho de um char Um char pode armazenar um caractere do conjunto de caracteres da máquina É garantido que um char tem pelo menos 8 bits, um short pelo menos 16 bits e um long pelo menos 32 bits Por definição, o tamanho de char é 1 O tamanho de um objeto ou tipo pode ser obtido usando o operador sizeof 1 sizeof(char) sizeof(short) sizeof(int) sizeof(long) Representação de tipos A representação interna pode ser direta, refletindo a própria estrutura lógica do hardware, ou indireta, quando a variável tem sua representação interna associada a um descritor de tipo A representação interna depende do projeto da linguagem de programação e da arquitetura da máquina onde é feita a implementação da linguagem. Por exemplo, a representação de reais em FORTRAN é direta em LISP um real é representado por uma seqüência de bits associada a um descritor que especifica que aquela seqüência deve ser tratada como um número real Representação direta e indireta Direta: exemplo de ponto flutuante (padrão IEEE) expoente bit sinal fração decimal 8 bits 23 bits Indireta: apontador para a representação e/ou descritor de tipo Tipo de dado: vetor descritor Limite inferior : Li Limite superior : Ls Tipo dos elementos Apontador para a representação dados Equivalência, compatibilidade e inferência A equivalência de tipos existe quando dois valores possuem o mesmo tipo Exemplo: type natural: 1.. maxint; var n1, n2: natural; A compatibilidade define quando um tipo pode ser usado em lugar de outro em um natural ou determinado contexto integer? Exemplo: var i: integer; ........ i := n1 + 10; A inferência de tipos define o tipo de uma expressão com base nos tipos de operandos envolvidos e a operação a ser realizada Equivalência de tipos Tipos estruturados e tipos definidos pelo usuário: quando são considerados equivalentes? Formas de equivalência: Equivalência estrutural: mesma representação Equivalência de nome: mesmo nome de tipo Equivalência de declaração: mesma declaração Exemplo: type V1: array[1..10] of integer; V2: array[1..10] of integer; A : V1; B, C : V2; D : V2; Exemplo em Pascal: parte 1 program equitipos; type V1= array[1..10] of integer; V2= array[1..10] of integer; procedure testaV(var vet1:V1; vet2:V2); var i: byte; begin for i:=1 to 10 do vet2[i]:=vet1[i]; end; {function} Exemplo em Pascal: parte 2 var A : V1; equivalência D : V2; de nome vetT:array[1..10] of integer; ind:byte; begin for ind:=1 to 10 do begin A[ind]:= ind * 10; D[ind]:= A[11 - ind]; end; testaV(A, D); Compilação: testaV(D,A); testaV(A,vetT); erro de tipo end. Exemplo de equivalência de registro type doiscampos = record a, b: integer; end; type doiscampos = record a: integer; b: integer; end; ? type doiscampos = record b: integer; a: integer; end; Exemplo em Pascal: objetos e registros program doiscampos; var r1: record a,b:integer; end; r2: object b, a: integer; end; begin r1.a := 10; r1.b := 20; { r2:= r1; compilação: erro de tipo} r1 := r1; end. Compatibilidade de tipos Existem situações em que valores de um determinado tipo são esperados: Atribuição: var = expressão Operação: var1 / var2 Parâmetros: fun (arg1, arg2) Questão: como as LP lidam com a compatibilidade de tipos? Compatibilidade de tipos Quando existe compatibilidade de tipos, podese misturar valores de tipos diferentes em expressões, ou mesmo usar um tipo A em lugar de um tipo B, sem que haja violação das regras de segurança de tipos (erro de tipo) Exemplos com tipos primitivos: Seja i um inteiro e f um real e fun (inteiro, real) São válidas as atribuições: i := r e r:= i? É válida a invocação: fun(r, i)? Compatibilidade: exemplo em Pascal program testacomp; type dd=byte; procedure testaDias(dias:byte); begin writeln(dias); end; var diasr:1..31; diasi:integer; diasb:dd; begin diasr:=14; diasi:=15; diasb:=16; {testaDias(diasr); erro estático} {testaDias(diasi); erro estático} {testaDias(1234); erro estático} testaDias(diasb); end. Conversão de tipos Quando os tipos envolvidos em uma expressão não são equivalentes, a linguagem oferece a possibilidade de conversão de tipos A conversão implícita ou automática é feita com base nas regras definidas sem a interferência do programador A conversão explícita é codificada diretamente pelo programador. Termos em inglês: casting, coercion Conversão de tipos primitivos A conversão de tipos primitivos é ditada pelas regras da linguagem de programação Normalmente admite a conversão de um tipo de representação menor para um tipo de representação maior Por exemplo, em Java, a conversão entre tipos primitivos - exceto o tipo bool que não admite conversão - considera o tamanho do tipo (8, 16, 32, 64 bits) Conversão: menor para maior de para byte short,int,long,float,double short int,long,float,double char int,long,float,double int long,float,double long float,double float double Formas de conversão A conversão pode ser feita de três maneiras: por atribuição, ou conversão implícita para o tipo do lado esquerdo do comando de atribuição por promoção aritmética para o tipo de resultado esperado da operação (inferência) por conversão explícita (casting). Seja float f, int i, float r; exemplo de atribuição: f = i; exemplo de promoção: r = f / i; exemplos de casting: i = (int) f ; r = (float) i/f; Classes como tipos de dados Uma definição de classe cria um novo tipo de dado Membros de uma classe: variáveis (campos) e métodos (funções) Formato simplificado de definição de classe Java class NomeDaClasse { // inicia corpo da definição // definição dos campos // definição das funções (opcionais) } // fim da classe NomeDaClasse Registros em Java = Classes Não existem estruturas de registros como records (pascal) e struct (C) Uma estrutura de registro deve ser definida como uma classe, sendo: nome da classe = nome da estrutura variáveis da classe = campos do registro instâncias da classe = variáveis do tipo registro class Ator { String nome; int idade; char sexo; } Ator nome | idade | sexo Pascal x Java type Tnome = object sobre:Tstring; meio: Tstring; prim: Tstring; end; var meuNome,teuNome: Tnome; ..... {referência:} meuNome.prim:= .... class Tnome { String sobre; String meio; String prim; } ..... Tnome meuNome, teuNome; ...... // referência: meuNome.prim = ....; Exemplo em Java registro class DoisCampos{ int a, b; } class TestaDoisCampos{ public static void main(String args[]){ DoisCampos r1,r2; r1 = new DoisCampos(); instâncias r2 = new DoisCampos(); r1.a=10; r1.b=20; System.out.println("r1: "+ r1.a +"\t"+ r1.b); System.out.println("r2: "+ r2.a +"\t"+ r2.b); }} Equivalência de nome: exemplo class Pessoa { String nome; int idade; char sexo; } class Aluno { String nome; int idade; char sexo; } Composição de tipos Composição de tipos: quando um componente de um tipo estruturado é também um tipo estruturado, como por exemplo, arrays de registros ou arrays de arrays. Utilizam a definição de tipos como base da composição Úteis em estruturas de dados mais complexas, permitindo: reduzir o esforço de programação e aumentar a legibilidade de programas Composição: exemplo em Pascal begin r1.a := 10; r1.b := 20; r3:=r1; writeln(r3.a,tab,r3.b); r2.a:= r1.a; 10 20 r2.b:= r1.b; 10 20 10 20 r2.c := r3; writeln(r2.a,tab,r2.b,tab,r2.c.a,tab,r2.c.b); end. type t1= record a,b:integer; end; t2= record b, a: integer; c: t1; end; var r1,r3: t1; r2: t2; Hierarquia de composição de registros type Tstring = string[20]; Tsexo = (masculino, feminino); Tnome = record sobrenome : Tstring; meio: Tstring; primeiro: Tstring; end; Tpessoa= record nome:Tnome; sexo:Tsexo; email:Tstring; end; Tpessoa nome sobrenome string meio string primeiro string sexo email masc/fem string Composição: vários níveis Tpessoa= record nome: Tnome; sexo: Tsexo; email: Tstring; end; Taluno= record matric:Tstring; ident: Tpessoa; end; Taluno TString Tnome sobre Tpessoa Tsexo meio Referência: Taluno.Tpessoa.Tnome.prim Tstring prim Composição: exemplo de uso var Turma: array[1..2] of Taluno; Professor1, Professor2: Tpessoa; begin Professor1.nome.sobrenome:= ' '; Turma[1].ident.nome.sobrenome := ' '; Professor1.email:= ' inf.ufrgs.br'; Professor2.email := Professor1.email; Turma[2]:=Turma[1]; end. instanciação referência qualificada cópia: mesmo TIPO { Professor1 := Turma[2]; >>> Erro de tipo } { Turma[2] := Professor1; >>> Erro de tipo } Composição de tipos com classes class Pessoa{ String nome; int idade;} class Novela{ Pessoa autor; String titulo; } String nome idade Pessoa autor classe campo titulo Novela Extensão de tipos Extensão de tipos: quando um um tipo estruturado usa como base outro tipo estruturado Utilizam o mecanismo de herança um mecanismo de criação de novos tipos de dados Úteis em estruturas de dados mais complexas, permitindo: reduzir o esforço de programação aumentar a reutilização de código e aumentar a legibilidade de programas Extensão: exemplo em Pascal Tnome = object sobrenome : Tstring; meio: Tstring; primeiro: Tstring; end; Tpessoa= object(Tnome) sexo:Tsexo; email:Tstring; end; Taluno= object(Tpessoa) matric: Tstring; end; Questão: é correto derivar Tpessoa de Tnome? todos os campos de Tnome + sexo + email todos os campos de Tpessoa + matric Extensão de tipos em Pascal: instanciação type Taluno = object(Tpessoa) Tstring = string[20]; matric: Tstring; Tsexo = (masculino, feminino); end; Tnome = object Tfunc = object(Tpessoa) sobrenome : Tstring; secao:Tstring; meio: Tstring; anoadm:integer; primeiro: Tstring; end; end; var Tpessoa = object(Tnome) Nome: Tnome; sexo:Tsexo; Pessoa:Tpessoa; email:Tstring; Aluno1, Aluno2: Taluno; end; Funcionario: Tfunc; Extensão: utilização var Mensagem: Tstring; Turma: array[1..2] of Taluno; Professor1, Professor2: Tpessoa; begin Mensagem := ' Modelo de Objetos'; Professor1.sobrenome:= ' Pardal '; Turma[1].sobrenome := ' Luizinho '; Professor1.email:= ' inf.ufrgs.br'; Professor2.email := Professor1.email; Turma[2]:=Turma[1]; end. instanciação Professor2 := Turma[1]; writeln(Professor2.sobrenome); referência qualificada cópia: tipo/subtipo Extensão de tipos em Java: relacionamentos entre classes class TNome { String primeiro="Pedro",meio="Alvares", sobrenome="Cabral";} Extensão ou Composição? class TPessoa extends TNome{ String email= "pessoa@"; char sexo='m';} class TAluno extends TPessoa{ int matricula=9999;} class TAtor extends TPessoa{ public String contrato= "MTV";} Extensão de tipos em Java: instanciação class Pessoas { public static void main (String[] args){ /* declarando objetos */ TNome nome; TPessoa pessoa; TAluno aluno; TAtor ator; /* criando objetos */ nome = new TNome(); pessoa = new TPessoa(); aluno= new TAluno(); ator = new TAtor(); System.out.println(" Nome " + nome); System.out.println(" Pessoa " + pessoa); System.out.println(" Aluno " + ator); System.out.println(" Ator " + aluno); Extensão de tipos: instanciação System.out.println(" System.out.println(" System.out.println(" System.out.println(" Nome Pessoa Ator Aluno Pedro Pedro Pedro Pedro Nome " + nome); Pessoa " + pessoa); Ator " + ator); Aluno " + aluno); Alvares Cabral Alvares Cabral pessoa@ m Alvares Cabral pessoa@ m MTV Alvares Cabral pessoa@ m 9999 Modelo de Objetos: equivalência Herança estabelece uma Um subtipo “é-um” tipo hierarquia (ou família) de tipos para fins de equivalência Atribuições válidas somente de subtipos Oa Tipo para tipos. A Exemplos válidos Oaa Oa = Oab Oab Subtipo Subtipo Oa = Oabc AA AB Exemplos inválidos Oabc Subtipo ABC Oaa = Oab Oabc = Oab Conversão (implícita) na hierarquia Seja a hierarquia: Atribuições válidas de objetos: nome=pessoa pessoa=ator pessoa=aluno Atribuições inválidas de objetos: pessoa=nome ator=aluno Nome upcast Pessoa Ator downcast Aluno Exemplos de conversão implícita pessoa.primeiro="Pero"; pessoa.meio="Vaz"; pessoa.sobrenome="Caminha"; nome= pessoa; System.out.println(" Nome " + nome); pessoa= aluno; pessoa= ator; aponta para um tipo TPessoa Nome Pero Vaz Caminha pessoa@ m Conversões explícitas hierarquia Questão: como tornar válidas as atribuições de objetos como os abaixo? aluno = pessoa; ator=aluno Solução: usar “casting” de tipo Pessoa pessoaRef; Aluno alunoRef; alunoRef = (Aluno) pessoaRef; Nome Pessoa Ator Aluno Exemplo de conversão explícita pessoa = (TPessoa)nome; System.out.println(" Pessoa " + pessoa); aponta para um tipo TPessoa Nome Pero Vaz Caminha pessoa@ m Agrupando objetos equivalentes 0 1 2 3 Nome Pessoa Nome Pessoa Ator Aluno Princípio: equivalência de tipos Ator Aluno Agrupando objetos equivalentes Agrupando em „arrays‟: mesmo tipo/subtipo Exemplo: Nome grupo[]=new Nome[4]; 0 1 2 3 Pessoa Ator Aluno é um Nome é um Nome é um Nome Nome Nome Exemplo de grupo TNome grupo[]= new TNome[4]; grupo[0]=nome; grupo[1]=pessoa; grupo[2]=ator; grupo[3]=aluno; for(int i=0;i<4;i++){ System.out.println(grupo[i]);} Fim Pedro Pedro Pedro Pedro Alvares Cabral Alvares Cabral pessoa@ m Alvares Cabral pessoa@ m MTV Alvares Cabral pessoa@ m 9999