Rascunho de aulas Disciplina: Estudo de Linguagem de Montagem / Laboratório de Linguagens de Montagem Ciência da Computação – 3o semestre Professor: José Cláudio Vahl Júnior ([email protected]) Aulas 04 (14/03/06) Classes e Objetos Uma classe é um componente de programa que descreve a "estrutura" e o "comportamento" de um grupo de objetos semelhantes — isto é, as informações que caracterizam o estado desses objetos e as ações (ou operações) que eles podem realizar. Os objetos de uma classe — também chamados de instâncias da classe — são criados durante a execução do programa. Uma classe é formada, essencialmente, por construtores de objetos dessa classe, variáveis e métodos. A criação de um objeto dessa classe consiste na criação de cada uma das variáveis do objeto, especificadas na classe. Os valores armazenados nessas variáveis determinar o estado do objeto. Uma variável de um objeto é também chamada de um "atributo" desse objeto. Objetos podem receber mensagens, sendo uma mensagem basicamente uma chamada a um método específico de um objeto, que realiza uma determinada operação, em geral dependente do estado desse objeto. A execução de uma chamada a um método de um objeto pode modificar o estado desse objeto, isto é, modificar os valores dos seus atributos, e pode retornar um resultado. O programa abaixo, ilustra a definição de classes, a criação de objetos e a chamada de métodos em Java. public class Ponto { int x, y; Ponto ( int a, int b ) { x = a; y = b; } void move ( int dx, int dy ) { x += dx; y += dy; } double distancia ( Ponto p ) { int dx = this.x - p.x; int dy = this.y - p.y; double dblDistancia = Math.sqrt( dx * dx + dy * dy ); return dblDistancia; } } 1 public class TestePonto { public static void main( String[] args ) { Ponto p1 = new Ponto( 0, 0 ); Ponto p2 = new Ponto( 10, 20 ); p1.move( 3, 25 ); p2.move( 1, 14 ); System.out.println( p1.distancia( p2 ) ); } } Algoritmo 1 : Exemplo de definição de classe Declaração de classe O programa apresentado no Algoritmo 1 define 2 classes: a classe Ponto e a classe TestePonto. A classe Ponto especifica atributos e métodos para a representação de pontos no plano cartesiano: os atributos x e y, que representam as coordenadas de um ponto, e os métodos move e distancia. A classe TestePonto define um único método: main. A execução de um programa Java é sempre iniciada com a execução do método main, que deve estar definido em alguma classe do programa. O método main, no exemplo acima, contém comandos que ilustram a criação de objetos da classe Ponto e a chamada de métodos de objetos dessa classe. Uma declaração (ou definição) de classe é introduzida pela palavra reservada class, seguida do nome e do corpo da classe. O corpo da classe consiste em um trecho de programa delimitado pelos símbolos "{" e "}", contendo declarações de variáveis (atributos) e declarações de métodos. Uma declaração de método é composta por um cabeçalho e pelo corpo do método. O cabeçalho de uma declaração de método especifica as seguintes informações: • • • o tipo do valor retornado em uma chamada a esse método; o nome do método; a lista (possivelmente vazia) dos parâmetros do método, delimitada por parênteses — cada parâmetro tem um nome e um tipo, o qual determina o conjunto dos valores que podem ser atribuídos a esse parâmetro, em uma chamada de método. A informação sobre os tipos dos parâmetros de um método, juntamente com o tipo do valor retornado pelo método, é denominada de assinatura do método. A assinatura de um método determina sua "interface", ou seja, em que contextos e com quais argumentos o método pode ser usado. O método move, na classe Ponto, tem dois argumentos, dx e dy, ambos do tipo int. Esses parâmetros representam os deslocamentos a serem adicionados às coordenadas x e y, de maneira que o ponto se mova para uma nova posição. O tipo do valor retornado pelo método move é void — isso significa que, de fato, uma 2 chamada a esse método não retorna nenhum valor, mas apenas modifica o estado do objeto que recebe uma mensagem comandando a execução desse método. O método distancia tem apenas um parâmetro, p, do tipo Ponto. Um argumento usado em uma chamada a esse método deve ser, portanto, uma expressão cujo valor é uma referência a um objeto da classe Ponto (ou a um objeto de uma subclasse dessa classe, como veremos mais adiante). O tipo Ponto é um exemplo de um tipo-classe — toda declaração de classe em um programa introduz também um novo tipo, que pode ser usado como tipo de variáveis, ou tipo de parâmetros ou do valor de retorno de métodos, declarados nesse programa. Uma chamada ao método distancia retorna um valor do tipo double, que representa a distância entre o objeto que recebeu a chamada a esse método e o objeto referenciado pelo argumento usado nessa chamada. O cabeçalho da declaração do método main, na classe TestePonto, tem a forma padrão da assinatura desse método em qualquer programa Java: public static void main( String[] args ) { Essa assinatura especifica o nome do método (main), o tipo do valor retornado por uma chamada a esse método (void) e a lista de parâmetros do método (( String[] args )). O parâmetro do método main, embora não seja usado nesse exemplo, deve ser sempre declarado, obrigatoriamente, na assinatura desse método. O tipo desse parâmetro — String[] —, assim como o seu uso, serão explicados mais adiante. As palavras reservadas static e public especificam modificadores de classes, variáveis ou métodos. Por enquanto, basta saber que o modificador static indica, nesse caso, que o método main não é associado a nenhum objeto, diferentemente, por exemplo, dos métodos move e distancia, da classe Ponto, que são associados a objetos dessa classe. Diz-se, nesse caso, que main é um método de classe, ou um método estático. Declarações de métodos estáticos, em Java, equivalem a declarações de funções ou de procedimentos (esse último caso, se o método, tal como main, não retorna nenhum valor). Criação de objetos O corpo do método main do programa mostrado no algoritmo 1 começa com duas declarações de variáveis: de nomes p1 e p2, e de tipo Ponto. Em Java, uma declaração de variável é também um comando, que especifica a criação da variável declarada. A execução do método main começa, portanto, com a execução da declaração de p1, seguida da execução da declaração de p2. Cada uma dessas declarações especifica também o valor a ser armazenado na variável (objeto) quando ela é criada. O valor a ser armazenado em p1 é o valor resultante da avaliação da expressão de criação de objeto "new Ponto()". A 3 avaliação dessa expressão cria um objeto da classe Ponto, e retorna um valor que é uma referência para o objeto criado — ou seja, o endereço de memória onde esse objeto é armazenado. A figura 1 abaixo ilustra a representação de um objeto da classe Ponto e uma variável p que contém uma referência para esse objeto. Objeto da classe Ponto p x 0 y 0 Figura 1 : Representação de objetos Para cada objeto da classe Ponto que é criado, são criadas duas variáveis, x e y, internamente a esse objeto. Variáveis como x e y são chamadas de variáveis de instância. Conforme já vimos, a especificação de um valor inicial para a variável — comumente chamada de inicialização da variável — é parte opcional da sua declaração. Se nenhum valor inicial for especificado, é usado, implicitamente, um determinado valor padrão (também chamado de valor default), que depende do tipo da variável. Para tipos-classe, o valor inicial padrão é null, que indica referência a nenhum objeto. A inicialização das variáveis p1 e p2 usa o que é chamado, em Java, de um construtor de objetos (ou simplesmente, construtor). O construtor Ponto, usado na inicialização de p1 e p2, é declarado na classe Ponto. Um construtor pode ser visto como um método de criação de um objeto. A execução de uma chamada a new tem o efeito de criar um objeto e, em seguida, executar o corpo do construtor especificado, fornecendo como resultado uma referência ao objeto criado. O uso de um construtor deve ter o mesmo nome da classe, e defere da declaração de um método, além disso, por não especificar nenhum tipo para o resultado. Chamada de método Os três últimos comandos do método main, no algoritmo 1, ilustram chamadas de métodos. Uma chamada de método especifica um objeto — denominado de objeto alvo da chamada —, um método a ser executado, e argumentos usados na execução desse método. Por exemplo, em "p1.move( 3, 25 )", o objeto alvo é p1, o método é move (da classe Ponto) e os argumentos são dados pelas expressões 3 e 25. O objeto alvo 4 de uma chamada de um método passa a ser denominado, durante a execução desse método, de objeto corrente. A execução de uma chamada a um determinado método m, em Java, consiste em avaliar as expressões que fornecem os valores dos argumentos passados para m nessa chamada, atribuir esses valores aos parâmetros de m correspondentes e, finalmente, executar o bloco de comandos que constitui o corpo desse método. Esse mecanismo de passagem de parâmetros é conhecido como passagem de parâmetros por valor. Uma chamada de método é semelhante a uma chamada de procedimento ou função. A diferença fundamental é que, no caso de uma chamada de método: 1. Qualquer uso de atributos durante a execução desse método é referente a atributos do objeto corrente, isto é, do objeto alvo da chamada. Por exemplo, na execução da chamada de método "p1.move( 3, 25 )", a atribuição de x += dx faz com que a variável x do objeto referido por p1 seja modificada (passando a conter o valor contido anteriormente em x, acrescido de 3), e a atribuição y += dy faz com que a variável y desse objeto seja modificada (passando a conter o valor antes contido em y, acrescido de 25). 2. Existe uma expressão que, durante a execução de um método, denota o objeto alvo da chamada a esse método. Em Java, essa expressão consiste da palavra-chave this. Por exemplo, durante a execução da chamada de método "p1.distancia( P2 )", cada ocorrência da expressão this representa o objeto referido por p1; a expressão this.x representa a variável x do objeto referido por p1 (objeto corrente), e p.x denota a variável x do objeto referido por p (no caso, o objeto referido por p2). O valor retornado por uma chamada a um método é especificado, no corpo desse método, por meio do comando na forma "return e", onde e é uma expressão, que denota o valor a ser retornado. O tipo dessa expressão deve ser compatível com o tipo do valor retornado pelo método, especificado pela assinatura desse método. Por exemplo, a expressão "Math.sqrt( dx * dx + dy * dy)", usada no comando return do método distancia, é uma expressão de tipo double. Essa expressão usa o método sqrt, definido na classe Math, para cálculo da raiz quadrada do valor dado por dx * dx + dy * dy, passado como argumento. A classe math é uma classe predefinida em Java, que oferece diversas funções matemáticas para uso em programas escritos na linguagem. Finalmente, o último comando do método main imprime a distância do ponto p2 até o ponto p1 — retornada pela expressão "p1.distancia( p2 )". A impressão desse valor é feita por meio de uma chamada ao método println, enviada ao objeto System.out. Atribuição de objetos Como já foi dito, uma variável de um determinado tipo-classe contém uma referência a um objeto dessa classe, e não esse próprio objeto. O efeito de um 5 comando de atribuição que tenha uma variável de tipo-classe como alvo é ilustrado pelo exemplo a seguir, no qual as variáveis a e b são do tipo-classe Ponto. Ponto a = new Ponto( 1, 2 ); Ponto b; b = a; b.y = b.x; Após a atribuição b = a, essas duas variáveis, a e b, contêm referências para um mesmo objeto. Desse modo, qualquer mudança no valor de um atributo do objeto referido por b, tal como no comando "b.y = b.x", irá afetar o valor desse atributo no objeto referenciado por a (e vice-versa). O uso de uma variável de tipo-classe em uma expressão, tal como em "b.x", envolve um acesso indireto (diz-se também que envolve uma "derreferenciação") ao valor armazenado na variável x. O acesso a esse valor, nessa expressão, envolve obter o endereço do objeto referenciado por b, armazenado na variável b, e usar esse endereço para o acesso ao valor inteiro contido em x. Note que uma atribuição a a ou b não modifica o estado do objeto. Considere o exemplo: Ponto a = new Ponto( 0, 0 ); Ponto b; b = a; b = null; Nesse exemplo, a atribuição "b = null" não modifica o objeto anteriormente referenciado por b (que no caso é o mesmo objeto referenciado por a), mas apenas muda o objeto referenciado por b: depois dessa atribuição, b contém null, significando que b não "aponta" para nenhum objeto, enquanto a continua a "apontar para" o mesmo objeto criado pela avaliação da expressão "new Ponto( 0, 0 )", como ilustra a figura 2 abaixo: Estado antes de "b = null;" x 0 y 0 a b Estado depois de "b = null;" x 0 y 0 a b null Figura 2 : Atribuição de variáveis do tipo-classe 6 Variáveis e métodos de objeto e de classe Variáveis e métodos podem ser estáticos ou não, isto é, podem ser variáveis e métodos de classes ou de objetos. A declaração de uma variável ou método estático é feita mediante o uso do modificador static na sua declaração. O exemplo a seguir ilustra a declaração de variáveis e métodos estáticos e de objetos: public class C { static int x; int y; static void m1 ( int p ) { x = p + 1; } void m2 ( int p ) { x = p + 2; y = p + 1; } } Nessa classe, x é uma variável estática, ou uma variável de classe, ao contrário de y, que é uma variável de instância, ou uma variável de objeto. De maneira análoga, m1 é um método estático, ao passo que m2 não. Uma variável estática, declarada em uma determinada classe, é compartilhada por todos os objetos dessa classe, sendo criada uma única vez durante a execução do programa. Uma variável de objeto, ao contrário, é criada a cada vez que um novo objeto dessa classe é criado. Uma variável estática x, definida em uma classe C, pode ser usada na forma C.x, assim como na forma obj.x, onde obj é um objeto da classe C. Naturalmente, uma variável que não é estática só pode ser usada nessa última forma. Um método estático apenas pode usar variáveis estáticas. Por exemplo, y não poderia ser usado no método estático m1, no exemplo acima. Ao contrário, um método que não seja estático pode usar qualquer variável declarada na classe em que ele é declarado. Chamadas a métodos estáticos podem ser feitas segundo a mesma forma usada para variáveis estáticas. Por exemplo, supondo que um método estático m, sem parâmetros, é declarado em uma classe C, uma chamada a m pode ser feita na forma "C.m()" ou "obj.m()", onde obj é um objeto da classe C. Como no caso de acesso a variáveis não-estáticas, uma chamada a um método não estático só pode ser feita nessa segunda forma. O uso do modificador static em uma declaração de variável pode alterar o comportamento de um programa, como ilustra o exemplo a seguir, em que o comportamento do programa é diferente dependendo da presença ou não do modificador static na declaração da variável x. 7 public class C { static int x; public static void main ( String[] args ) { C c1 = new C(); C c2 = new C(); c1.x = 1; System.out.println( c2.x ); } } O resultado impresso por esse programa é igual a 1, pois a atribuição "c1.x = 1;" modifica o valor de c2.x, uma vez que x é uma variável compartilhada por todos os objetos da classe C. Se for removido o modificador static na declaração de x, o resultado impresso pelo programa será igual a 0, que é o valor inicial atribuído à variável x, do objeto c2, quando esse objeto é criado. Exercícios 1. Quais são os valores armazenados nas variáveis x e y ao final da execução do seguinte trecho de programa? int x; int y = x = y * while ( x = x y = y } 10; 3; x > y ) { - 5; + 1; 2. Qual é o valor da variável s ao final da execução do seguinte trecho de programa, nos dois casos seguintes: (a) as variáveis a e b têm, inicialmente, valores 5 e 10, respectivamente; (b) as variáveis a e b têm, inicialmente, valores 8 e 2, respectivamente. int s = 0; if ( a > b ) { s = ( a + b ) / 2; while ( a <= b ) { s = s + a; a = a + 1; b = b - 2; } } 8 Exercício prático A classe Calc apresentada abaixo, será utilizada em exercícios posteriores e define métodos que resolvem alguns problemas como: - dado um número inteiro, retorna o seu quadrado; - dados dois números inteiros, retornar a soma dos seus quadrados; - dados três números inteiros, determinar se são todos iguais ou não. public class Calc { /** * Método que retorna o quadrado de um número */ public static int quadrado ( int x ) { int iQuadrado = x * x; return iQuadrado; } /** * Método que retorna a soma dos quadrados de dois números */ public static int somaDosQuadrados ( int x, int y ) { int iSomaQuadrados = quadrado( x ) + quadrado ( y ); return iSomaQuadrados; } /** * Método que retorna se três números são iguais ou não */ public static boolean tresIguais ( int a, int b, int c ) { boolean bolIguais = ( ( a == b ) && ( b == c ) ); return bolIguais; } /** * Método usado para testar os métodos implementados nessa classe */ public static void main( String[] args ) { System.out.println("Quadrado de 5: " + quadrado( 5 ) ); System.out.println("Soma dos quadrados de 3 e 4: " + somaDosQuadrados( 3, 4 ) ); System.out.println("Os valores 3, 3 e 3 são iguais? " + tresIguais( 3, 3, 3 ) ); } } Implemente nessa mesma classe, métodos para resolver os seguintes problemas: - dados dois números inteiros, retornar o máximo entre eles (max); - dados três números inteiros, retornar o máximo entre eles (max3); - dados três números inteiros, a, b e c, determinar se eles podem representar os lados de um triângulo ou não (isto é, se existe um triângulo com lados de comprimentos iguais a a, b e c). (ehTriang) 9 Alguns elementos úteis para a elaboração dos programas: 1. Uma chamada a um método estático pode ser feita da seguinte forma: Calc.quadrado( 10 ); ou seja, o nome da classe, Calc, na qual o método é definido, seguido do símbolo "." (ponto) e do nome do método e, finalmente, da lista dos argumentos usados na chamada. Quando a chamada ocorre na mesma classe em que o método é definido, o nome da classe (seguido de ".") não precisa ser especificado. Essa chamada pode ser escrita, então, apenas como: quadrado( 10 ); Java: 2. Tabela com os operadores de comparação (operadores relacionais) do Operador = = != < > <= >= Significado Igual a Diferente de Menor que Maior que Menor ou igual a Maior ou igual a Exemplo 1 = = 1 1 != 1 1 < 1 1 > 1 1 <= 1 1 >= 1 Resultado true false false false true true 3. Tabela com os operadores booleanos em Java: Operador ! & e && | e || ^ Significado Negação ("não") Conjunção estrita e não-estrita ("e") Disjunção estrita e não-estrita ("ou") Disjunção exclusiva ("ou exclusivo"), estrita. É igual a true se um dos argumentos, mas não ambos, for igual a true. 4. Tabela com os operadores aritméticos do Java: Operador + * / Significado Adição Subtração Multiplicação Divisão % Resto Exemplo 2 + 1 2 – 1 2 * 3 5 / 2 5 / 2.0 5 % 2 Resultado 3 1 6 2 2.5 1 10 5. Tabela com a precedência dos operadores comumente usados em Java (o símbolo "≡" indica equivalência): Operadores *, /, % +, >, <, >=, <= ==, != & ^ | && || ? : Precedência maior Exemplos i * j / k ≡ ( i * j ) / k i+j*k≡ i + ( j * k ) i<j+k≡ i < ( j + k ) b == i < j ≡ b == ( i < j ) b & b1 == b2 ≡ b & ( b1 == b2 ) b1 ^ b2 & b ≡ b1 ^ ( b2 & b ) i < j | b1 & b2 ≡ ( i < j ) | ( b1 & b2 ) i + j != k && b1 ≡ ( ( i + j ) != k ) && b1 i1 < i2 || b && j < k ≡ (i1 < i2) || (b && (j < k)) i < j || b ? b1 : b2 ≡ ( ( i < j ) || b ) ? b1 : b2 Precedência menor Mais Exercícios práticos 1. Implemente um método que determine se um dado valor inteiro positivo representa um ano bissexto ou não. No calendário gregoriano, usado atualmente, um ano é bissexto se for divisível por 4 e não for divisível por 100, ou se for divisível por 400. 2. Defina um método somaD3 que, dado um número inteiro representado com até três algarismos, fornece como resultado a soma dos números representados por esses algarismos. Exemplo: somaD3(123) deve fornecer resultado 6. 3. Defina um método inverteD3 que, dado um número representado com até três algarismos, fornece como resultado o número cuja representação é obtida invertendo a ordem desses algarismos. Por exemplo: o resultado de inverteD3(123) deve ser 321. 4. Implemente um método que retorne o comprimento aproximado de uma circunferência dado o seu raio. Dica: defina uma constante com o valor de pi. Em Java isso pode ser feito da seguinte forma: final float PI = 3.1416f; 5. Implemente um método que, dados cinco números inteiros, retorne true se o conjunto formado pelos 2 últimos números é subconjunto daquele formado pelos 3 primeiros, e false caso contrário. 11 6. Implemente um método que, dado um valor inteiro não-negativo que representa a nota de um aluno em uma disciplina, retorna o caractere que representa o conceito obtido por esse aluno nessa disciplina, de acordo com a tabela: Nota 0 a 59 60 a 74 75 a 89 90 a 100 Conceito R C B A 7. Implemente um método que, dados 4 valores inteiros a, b, c, e d, fornece como resultado a soma das frações a/b e c/d. 12