Novo mapa mental
Substitui o href no imagemap para ver se funciona.
Este é o blog de relacionamento com alunos de Fábio Nakano.
Desejo testar se esta mídia facilita a comunicação e aprendizado de conteúdo.
Gostaria que vocès dessem notas mais altas para posts que ajudaram mais a entender o assunto (e não por outro critério, por exemplo o melhor escrito ou o mais "bonito")
fabionakano at usp dot br
Prédio A1, segundo andar - Sala 204E
Caso precise do mapa do Campus:http://each.uspnet.usp.br/site/mapa.php
Siga-me por email preenchendo a caixa abaixo.
{ byte[] A={21, 9, 77, 32, 44}; //1 for (int i=0;i<5;i++) { //2 System.out.println ("A["+i+"]="+A[i]); //3 } }
A linha 1 cria o array e a referência com identificador A. A linha 2 repete o bloco de código 5 vezes, com i indo de 0 a 4, quando i==5 o loop termina e o bloco não é executado. A linha 3 é o bloco de código repetido 5 vezes e é executado uma vez para cada valor de i. Executado para i==0, imprime na tela o valor armazenado em A[0], para i==1, imprime o valor A[1], e assim por diante.
Sabemos como definir arrays de uma dimensão. Como seriam arrays de duas ou mais dimensões? ... ela segue a mesma idéia das referências, ou seja, se
int[]declara uma referência para inteiros, essa referência "aponta" para o conteúdo.
int[][]declara uma referência para referências para inteiros. A primeira "aponta" para um array de referências e cada elemento desse array aponta para o conteúdo correspondente.
int[][][]declara uma referência para referências para referências para inteiros. A primeira "aponta" para um array de referências e cada elemento desse array aponta para seu respectivo array de referëncias cada elemento deste aponta para o conteúdo correspondente. As três situações são diagramadas abaixo.
int[] A={21, 9, 77, 32, 44}; // uma dimensão
int[][] M={{21, 9, 77, 32, 44}, {1, 19, 37, 92, 48}}; // duas dimensões
int[][][] C={ {{21, 9, 77, 32, 44}, {1, 19, 37, 92, 48}}, // tres dimensões {{-21, -9, -77, -32, -44}, {-1, -19, -37, -92, -48}}, {{2, 9, 7, 3, 4}, {21, 1, 3, 9, 8}} };
* embora possa conter erros (e por isso requerer leitura crítica), Wikipedia contém bons exemplos.
Apresentar o primeiro tipo abstrato de dados.
Chamamos tipos abstratos de dados (ADT ou TAD) os tipos de dados que não são os primitivos. Arrays e Strings são tipos abstratos presentes na maioria das linguagens de programação. Outros tipos podem ser criados pelos usuários ou pelos mantenedores da linguagem. Por exemplo, o tipo que representa datas em C (struct tm no arquivo time.h). No sentido estrito, TAD se refere apenas às variáveis, e não aos métodos.
struct tm { int tm_sec; /* Segundos */ int tm_min; /* Minutos */ int tm_hour; /* Horas */ int tm_mday; /* Dia do mes */ int tm_mon; /* Meses */ int tm_year; /* Anos */ int tm_wday; /* Dia da semana */ int tm_yday; /* Dia do ano */ int tm_isdst; /* DST. [-1/0/1]*/ }
A extensão do conceito de tipo abstrato, para incluir métodos, ou seja, associar métodos aos dados, originou a idéia de objeto. Como Java é uma linguagem orientada a objetos, torna-se difícil ilustrar o conceito de tipo abstrato, pois Java já parte do conceito estendido.
Os tipos abstratos Arrays ou arranjos representam conjuntos homogêneos (por exemplo números) distintos por sua posição relativa. Estes são usados em computação para representar vetores e matrizes, e são essencialmente a mesma estrutura da informação.
Além do seu uso em física e matemática, para representar pontos, transformações de coordenadas e sistemas de equações, são utilizados para representar listas, imagens, grafos. As imagens que se formam na tela de um computador, independente de serem texto ou imagem, estão armazenadas em uma matriz, fotos digitais também são matrizes. Os algoritmos de roteamento (do GPS) trabalham sobre uma representação da cidade como um grafo.
Em Java, um array de bytes com cinco elementos é declarado e inicializado com:
byte[] A={21, 9, 77, 32, 44};
O que indica que A é um array (e não um byte) são os colchetes após o tipo. Esse array contém os elementos 21, 9, 77, 32 e 44, que são acessados usando A[0], A[1], A[2],A[3],A[4], tanto para escrita quanto para leitura, ou seja:
System.out.println (A[3]); // imprime 32 A[2]=22; // escreve 22 no lugar de 77
Podemos considerar a memória do computador um array (de 2G posições de um byte cada), também iniciando pelo índice zero.
Os elementos de um array são colocados na memória de forma contígua, ou seja, o elemento zero é imediatamente anterior ao elemento um, que é imediatamente anterior ao elemento dois e assim por diante. Supondo que o endereço do elemento de índice zero seja 0x0378 (em hexadecimal)
Endereço | Conteúdo |
0x378 | 21 |
0x379 | 9 |
0x37A | 77 |
0x37B | 32 |
0x37C | 44 |
No caso acima, um endereço da memória armazena exatamente um valor do tipo byte, logo, passar para o próximo elemento equivale a passar para o próximo endereço, mas em outros tipos, como short (2 bytes) ou int (4 bytes), temos que "pular" de dois em dois ou de quatro em quatro. Isso é resolvido automaticamente pelo compilador/máquina virtual.
Este talvez seja o ponto menos intuitivo: em A está armazenada a referência para os dados. Referências são números que são convertidos em endereços de memória.
O comando
int[] A;informa ao compilador/JVM que deve-se alocar memória para uma variável do tipo int[] com identificador (nome) A. Este tipo é uma referência para um inteiro. Quando não inicializadas, essas variáveis não se referenciam a dados válidos.
O comando
{21, 9, 77, 32, 44};aloca um bloco de memória e armazena os elementos nesse bloco.
O comando de atribuição armazena na posição de memória cujo endereço corresponde a A, o endereço do bloco de memória.
Assim, A é inicializada com a referência para os dados. Complementando o mapa de memória, suponha que a variável A seja alocada no endereço 0x500. Que valor ela conterá? (resposta na tabela).
Endereço | Conteúdo |
0x378 | 21 |
0x379 | 9 |
0x37A | 77 |
0x37B | 32 |
0x37C | 44 |
... | ... |
0x500 | 0x378 |
Certamente há outras formas de criar e usar arrays e outros tipos abstratos, inclusive formas que prescindem do uso de referências, mas esta solução tem vantagens (a que voltaremos em mais detalhes quando tratarmos sobre objetos):
Voltando ao array, como acessamos seus elementos? Por exemplo A[2]? Bem, o acessamos tomando o endereço base (0x378) e acrescentando o offset (2* oTamanhoDe(int)), o que resulta em 0x37A. Desta forma, a que offset corresponde o endereço base?
Em geral, quando usamos um array, este será percorrido de alguma forma, e é o programador quem deve definir como isso será feito. Por exemplo, se quisermos escrever na tela o conteúdo de A, poderia ser uma boa resposta:
{ byte[] A={21, 9, 77, 32, 44}; for (int i=0;i<5;i++) { System.out.println ("A["+i+"]="+A[i]); } }
Por favor, explique o que o código acima faz, e como.
Métodos na especificação da linguagem Java
Tutorial sobre métodos em Java
Sendo rigoroso, há diferenças, embora sutis, entre cada um desses nomes. Estas tem a ver com o paradigma de linguagem de programação.
Paradigma é o tipo de linguagem, por exemplo, imperativo, em que ordenamos ao computador o que fazer; funcional, em que o código dentro da função não modifica o estado fora dela; procedural, em que o código dentro do procedimento pode modificar o estado fora dela e orientado a objetos, em que todas as variáveis são objetos.
Em linguagens de máquina e de montagem, sub-rotinas são chamadas através de dois comandos cujos mnemônicos são GO SUB ou GO TO. Isso foi herdado para versões iniciais de COBOL e BASIC - nessas linguagens também usa-se sub-rotina e elas são chamadas.
Em linguagens funcionais, quis se imitar o funcionamento das funções matemáticas, que modificam exclusivamente as variáveis definidas dentro delas. Acredito que atualmente a palavra funcional seja utilizada num outro sentido.
Em linguagens procedurais, os procedimentos são funções que podem modificar variáveis definidas fora delas. Funções e procedimentos são chamados. São linguagens procedurais PASCAL, C, FORTRAN.
Em linguagens orientadas a objetos, estes têm seu comportamento, ou ação, definido por métodos. Métodos não são chamados, são invocados (embora o funcionamento seja o mesmo). A diferença é que estes estão associados a objetos, ou seja, invocamos o método de um objeto.
Não diretamente, usando tipos primitivos, mas isso é possível, definindo seus próprios objetos ou usando arrays.
Sim!!!. É completamente diferente. Imprimir na tela é uma invocação de método e "só" faz uns pontos na tela apagar e outros acender. Retornar um valor significa usar return e ainda "dizer" o que deve ser retornado.
Use variáveis globais.
A partir da semana que vem falaremos sobre tipos abstratos. Para entender seu funcionamento, precisamos saber, até certo ponto, como o computador funciona, em especial, como as variáveis são armazenadas na memória. Podemos estudar isso usando o Hipo. Este é um simulador de kit de microprocessador. Ele é programado em linguagem de máquina (lembra do sanduíche de mortadela??). Com os comandos dados, além de operações lógicas, aritméticas e relacionais, é possível implementar if, switch, while, do..while, for e sub-rotinas simples, sem passagem de parâmetro (pode-se usar variáveis globais). A tarefa para a próxima aula é rodar pelo menos um exemplo da documentação do HIPO. Ele é distribuído em um arquivo tipo JAR, que é executado diretamente pela JVM usando java -jar hipo.jar. Talvez seja útil entender o que são Máquinas de Von Neumann. Nossos computadores são esse tipo de máquina.
Apresentar procedimentos/funções/métodos/subrotinas.
Métodos na especificação da linguagem Java
Tutorial sobre métodos em Java
class Taximetro { static double x=7.0; static double f (double x) { return x; } public static void main (String[] args) { double x=5.0; System.out.println (f(2.0)); } }
Respostas:
A regra para saber qual variável será usada é proximidade de escopo, ou seja, será usada a variável do escopo mais próximo da linha que a utiliza.
Apresentar procedimentos/funções/métodos/subrotinas.
Métodos na especificação da linguagem Java
Tutorial sobre métodos em Java
class Taximetro { static double f (double x) { return 5+0.5*x; } public static void main (String[] args) { System.out.println ("ola"); double w=5.0; System.out.println (f(w)); } }
class Taximetro { static double f (double x, int b) { double r; if (b==1) { r=5+0.5*x; } else { r=5+1.0*x; } return r; } public static void main (String[] args) { System.out.println ("ola"); double w=5.0; int bandeira=2; System.out.println (f(w,bandeira)); } }
Voltando à matemática, existe o conceito de composição de funções, por exemplo g(f(x)). Que significa que aplicamos f sobre x e sobre o resultado, aplicamos g.
Como exemplo, temos uma empresa que envia seus funcionários para passar o dia em seus clientes e ao final do dia os traga de volta, de taxi. Para essa empresa, é interessante calcular o valor de duas corridas, mais o preço da espera do motorista, que é de R$80.00. Usemos a função mais simples de cálculo de valor de corrida.
f(x)=5+0.5*x, onde x é a distância percorrida em quilômetros.
e g(z)=80+2*z, onde z é o valor da corrida.
O código completo correspondente é:
static double f (double x) { return 5+0.5*x; } static double g (double z) { return 80.0+2.0*z; } public static void main (String[] args) { System.out.println ("ola"); double w=5.0; System.out.println (g(f(w))); }
Quando trabalhamos com várias funções simultaneamente, precisamos ter claro como as variáveis funcionam nesta situação. Por exemplo, se definirmos em main uma variável x, ela é a mesmo que usamos na função f(x)?
Chamamos escopo da variável o intervalo do código em que essa variável é acessível. Em geral, uma variável é acessível no bloco onde é declarada e nos blocos contidos por ele. Por exemplo, se declararmos uma variável dentro da classe e fora de qualquer função, ela pode ser acessada por qualquer função:
class Taximetro { static String mensagem="Táxis Ipê, calculando o valor da sua corrida..."; static double f (double x, int b) { System.out.println (mensagem); double r; if (b==1) { r=5+0.5*x; } else { r=5+1.0*x; } return r; } public static void main (String[] args) { System.out.println ("ola"); double w=5.0; int bandeira=2; System.out.println (f(w,bandeira)); } }
A variável mensagem está definida dentro da classe, assim como os métodos f e main. Logo, ela pode ser usada dentro deles.
A variável mensagem foi declarada no escopo da classe. Em linguagens orientadas a objeto, ela é chamada atributo da classe (em C seria variável da classe;na documentação de java , é chamada member variable). Como até agora trabalhamos exclusivamnte em um contexto estático, a variável tem que ser estática também.
Quando trabalhamos com uma única classe em escopo estático, os atributos podem ser chamados "variáveis globais", em referência a tais variáveis no paradigma procedural.
A existência de vários escopos (e a escolha dos desenvolvedores da linguagem), permite declarar em diferentes escopos, variáveis de mesmo nome (no começo pode gerar um pouco de confusão, mas com a prática, veremos que é muito útil).
Uma ilustração deste caso é:
class Taximetro { static double x=7.0; static double f (double x) { return x; } public static void main (String[] args) { double x=5.0; System.out.println (f(2.0)); } }
Antes de ir para a próxima página, responda:
Métodos na especificação da linguagem Java
Tutorial sobre métodos em Java
Simplesmente incluir a definição de uma função no código não implica que a função será usada. Para usá-la, ela precisa ser "chamada". Para isto precisamos colocar o nome da função e seus parâmetros dentro de alguma função que seja executada - neste caso, em main.
public static void main (String[] args) { System.out.println ("ola"); double w=5.0; System.out.println (f(w)); /**/ }
Relembrando: a JVM executa um programa a partir do método main. No método acima, o primeiro comando executado é a chamada ao método println com parâmetro "ola". O comando seguinte é a declaração da variável w. O comando seguinte é a atribuição do valor 5.0 a w. Na terceira linha, faz-se como em expressões com parêntesis - executa-se primeiro o que está dentro. No caso: Recupera-se o valor contido em w, chama-se f, que retorna um valor, que é passado para println. Finalmente, executa-se println.
Este ponto é chave: Quando executamos f(w), o valor de w é copiado para x e então executa-se o bloco de código que define f. No exemplo, w vale 5.0 então 5.0 é copiado para x e então executa-se return 5.0+0.5*x.
Podemos escrever funções a várias variáveis. No exemplo do taxi, o valor do quilometro muda de acordo com o horário e dia da semana. Na bandeira 1, que é usada de segunda a sábado das 6h às 20h, o valor do quilômetro é R$0.50; na bandeira 2, usada nos outros horários, o valor do quilômetro é R$1.00.
Notando a bandeira com a variável b, o valor da corrida agora é f(x,b), onde f(x,b)=5+0,5*x, se b==1 e f(x,b)=5+1,0*x, se b==2 . Você conseguiria ajustar o código anterior para fazer este novo cálculo?? Tente fazer e depois siga em frente.
double f (double x)Esta é a declaração da função. Nela definimos como se usa, mas ainda não definimos o que ela faz - que é um bloco de código:
{ double r; r=5+0.5*x; return r; }Neste bloco, definimos a variável r, que armazenará o resultados da conta, fazemos a conta e retornamos o valor contido em r.
static double f (double x) { double r; r=5+0.5*x; return r; }Esta não é a única forma de escrever esta função, além de diferenças na indentação, podemos "economizar" variáveis e código:
static double f (double x) { return 5+0.5*x; }Se simplesmente incluirmos (CTRL-C CTRL-V) esta definição de função no código do OlaMundo e executarmos, o que o programa imprime na tela muda? Uma questão menos aparente: a função é utilizada?
Variáveis são posições de memória que armazenam valores. O identificador (nome) de uma variável serve como um rótulo, é necessário para compilação e desnecessário para execução.
Para declarar uma variável, sem inicializá-la (atribuir-lhe valor), usa-se:
<tipo> <identificador> ;
O operador de atribuição é
=e é usado quando queremos atribuir valor a uma variável de tipo primitivo. Ele é lido "recebe" e não "igual" para distinguir atribuição de comparação. São exemplos de atribuição:
a = 5 // a recebe 5 casa = 15.4 // casa recebe 15.4 letra = 'x' // letra recebe xis
Para declarar uma variável e inicializá-la, usa-se:
<tipo> <identificador> = <literal> ;
Há um tutorial Java que explica isso em detalhes e apresenta outros exemplos de uso de variáveis.
+ // soma
- // subtração
* // produto
/ // divisão
A estes, em linguagens de programação acrescenta-se o operador mod, que calcula o resto da divisão inteira.
% // mod
Todos são operadores binários, ou seja envolvem dois operandos. em algumas referências vocês vão encontrar
A <op> B //onde A e B são operandos e op é qualquer operador binário.
Apenas para satisfazer a curiosidade (este é um assunto que será abordado com mais cuidado em ach2043 e em ach2087), a definição destes operadores em linguagens de programação usa notação e regras que facilitam a construção de compiladores, e é a notação usada na especificação da linguagem. Estes operadores são apresentados no capítulo 15 seção 17.
Especificação da linguagem Java
Nos computadores digitais atuais, podemos dizer que a representação interna dos números é binária, ou seja, números são armazenados na base 2. As operações lógicas bit a bit operam cada um dos bits dos números. São elas:
Lista de Operadores Lógicos bit a bit
~ & | ^
A operação ~ é unária e complementa o número bit a bit. A operação & é binária e faz um E bit a bit. A operação | é binária e faz OU bit a bit. A operação ^ é binária e faz OU exclusivo (XOU, ou XOR) bit a bit - nesta operação o resultado é 1 somente se os dois operandos são diferentes.
Para entender essas operações, comecemos com números de um bit. Eles podem assumir valores zero ou um. Construiremos tabelas com todas as possibilidades de operandos de um bit.
Não | E | OU | OU exclusivo | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
Para aplicar essas operações sobre números inteiros, temos que converter os números em sua representação binária, aplicar a operação bit a bit, a partir do bit menos significativo e converter o resultado de volta para decimal. Por exemplo: 19 & 6
2^4 | 2^3 | 2^2 | 2^1 | 2^0 | |||
A | 19 | 1 | 0 | 0 | 1 | 1 | |
B | 6 | 0 | 0 | 1 | 1 | 0 | |
S | 0 | 0 | 0 | 1 | 0 | 2 |
Outro exemplo: 19 | 6
2^4 | 2^3 | 2^2 | 2^1 | 2^0 | |||
A | 19 | 1 | 0 | 0 | 1 | 1 | |
B | 6 | 0 | 0 | 1 | 1 | 0 | |
S | 1 | 0 | 1 | 1 | 1 | 23 |
Outro exemplo: 19 ^ 6
2^4 | 2^3 | 2^2 | 2^1 | 2^0 | |||
A | 19 | 1 | 0 | 0 | 1 | 1 | |
B | 6 | 0 | 0 | 1 | 1 | 0 | |
S | 1 | 0 | 1 | 0 | 1 | 21 |
Há operadores de deslocamento bit a bit, que são listados abaixo, mas não serão apresentados agora.
Lista de Operadores de deslocamento bit a bit
<< >> >>>
Você pode perguntar por que operadores lógicos como esses são importantes. Há vários motivos, dentre eles, os componentes básicos para construção dos computadores funcionam desta forma. Conectando milhões destes componentes que construímos o microprocessador, memória, chipset,...
Operadores relacionais são aqueles que testam uma relação, por exemplo a de igualdade, e retornam como resultado se essa relação é verdadeira ou falsa. São operadores relacionais em Java:
Lista de Operadores Relacionais
> <== <= >= !=
! && ||
O operador ! é unário, o restante é binário. Os da primeira linha da lista podem ser aplicados diretamente a variáveis e literais de tipo primitivo. Os da segunda linha servem para associar logicamente os da primeira, resultando em expressões mais complexas e poderosas.
12 == 12 // retorna true pois os números são iguais
12 <= 12 // retorna true pois um números é menor ou igual a ele mesmo
12 >= 17 // retorna false
17 < 17 // retorna false pois um número não pode ser menor que ele mesmo
5 > -3 // retorna true
5 != 7 // retorna true pois 5 é diferente de 7
Os operadores da segunda linha são NÃO, E e OU relacionais. Esses operadores se aplicam exclusivamente a operandos do tipo boolean. Relembrando, em Java, o tipo boolean só pode assumir dois valores: true ou false, se fizermos a correspondência true=1 e false=0, seu funcionamento é igual ao dos operadores lógicos bit a bit correspondentes.
Talvez o que há de mais poderoso e mais compicado a respeito dos operadores relacionais acontece quando ao invés de usar literais, usamos variáveis, e os nomes (identificadores) dessas variáveis são palavras com significado para nós. As expressões lógicas resultantes dessa combinação são o cerne do que entendi que os alunos daqui chamam de lógica de programação.
"Eu digo que ehIdoso quando a idade for maior que 65 anos."
O período acima pode ser escrito como uma expressão lógica em que o resultado é armazenado em ehIdoso e os operandos são idade e 65.
boolean ehIdoso=idade > 65
"Eu digo que naoEntra quando a idade for menor que 18 anos."
O resultado é armazenado em naoEntra e os operandos são idade e 18
boolean naoEntra=idade < 18
Podemos, a partir de naoEntra, escrever entra. Eu digo que entra quando naoEntra for falso.
boolean entra= naoEntra==false
Podemos escrever a mesma coisa dizendo: Eu digo que entra quando não naoEntra.
boolean entra= !naoEntra
"Eu digo que vaiChover quando a umidade for maior que 85% e a temperatura for maior que 27C."
O período acima pode ser escrito como uma expressão lógica em que o resultado é vaiChover e os operandos são umidade temperatura, 85 e 27. Vamos separar as operações relacionais:
a umidade for maior que 85% corresponde à expressão lógica umidade > 85.
a temperatura for maior que 27C corresponde à expressão lógica temperatura > 27.
As duas operações são "conectadas" por um e, que corresponde ao operador lógico &&. Logo obtemos:
boolean vaiChover=(umidade > 85) && (temperatura > 27)
"Eu digo que ehBixo quando estiverPintado"
O resultado é ehBixo, e os operandos? Este caso é um pouco mais complicado pois há informação implícita: estiverPintado é um operando - qual é o seu tipo e o que significa? Neste caso, convém definir que estiverPintado é um boolean, que vale true quando a pessoa estiver pintada e false quando a pessoa não estiver pintada. Há duas expressões equivalentes (e pode haver outras):
boolean ehBixo = estiverPintado
boolean ehBixo = estiverPintado==true
"Eu digo que tem poucaChance quando foiAtropelado e a idade for menor que 3 ou maior que 60 anos, ou se tem cancerOsseo."
Neste caso, a expressão é mais complicada, e a precedência das operações não é clara (pois na linguagem natural não há como especificá-la). Nesta expressão, numa análise superficial, só há uma ordem para as operações que faz sentido. Vamos perceber quando tentarmos escrever a expressão lógica:
foiAtropelado é um operando e vale true se o indivíduo foi atropelado, false caso não foi atropelado.
idade menor que 3 anos corresponde a idade < 3
idade maior que 60 anos corresponde a idade > 60
As duas expressões acima são conectadas or um OU, resultando em (idade < 3) || (idade > 60)
A expressão acima é conectada à primeira por um E, resultando em foiAtropelado && ((idade < 3) || (idade > 60)).
cancerOsseo é um operando e vale true se o indivíduo tem a doença, false caso não tenha. É conectado ao restante por um OU e o resultado é atribuído a poucaChance, o que resulta em:
boolean poucaChance = cancerOsseo || (foiAtropelado && ((idade < 3) || (idade > 60)))
"Eu digo que ehBixo quando estiverPintado e o cabelo estiver meioRaspado e a roupa estiver cortada."