Baixe o app para aproveitar ainda mais
Prévia do material em texto
GCC 105 – LINGUAGENS DE PROGRAMAÇÃO I AULA 7 – Avaliação de Expressões 1º Semestre de 2015 Prof. Janderson Rodrigo de Oliveira Universidade Federal de Lavras Departamento de Ciência da Computação Introdução • Expressões são os meios fundamentais de especificar computações em uma linguagem de programação. • É crucial para um programador entender tanto a sintaxe quanto a semântica de expressões da linguagem sendo utilizada. • Para entender a avaliação de expressões, é necessário estar familiarizado com as ordens de avaliação de operadores e operandos, ditadas pelas regras de associatividade e de precedência da linguagem. Introdução • Outras questões relacionadas à semântica de expressões são: – As diferenças de tipos; – Coerções; – Avaliação em curto-circuito. • A essência de linguagens imperativas é o papel dominante das sentenças de atribuição. O propósito de uma sentença de atribuição é modificar o valor de uma variável. • Uma parte integral de todas as linguagens imperativas é o conceito de variáveis cujos valores mudam durante a execução de programas. Expressões Aritméticas • A avaliação de expressões aritméticas similar àquelas encontradas na matemática, na ciência e nas engenharias foi um dos principais objetivos das primeiras linguagens de programação de alto nível. • A maioria das características das expressões aritméticas em linguagens de programação foi herdada de convenções que evoluíram na matemática. Expressões Aritméticas • Em linguagens de programação, as expressões aritméticas consistem em: – Operadores; – Operandos; – Parênteses; – Chamadas a funções. • Um operador pode ser: – Unário, por possuir um único operando; – Binário, por possuir dois operandos; – Terciário, por possuir três operandos. Expressões Aritméticas • Na maioria das linguagens de programação imperativas, os operandos binários usam a notação convencional (infixa). Uma exceção é Perl, possuindo alguns operadores pré-fixados. • O propósito de uma expressão aritmética é especificar uma computação aritmética. Tal processo necessita realizar duas ações: – Obter os operandos, normalmente a partir da memória; – Executar nos operandos as operações aritméticas especificadas. Ordem de Avaliação de Operadores • A ordem de avaliação dos operadores de uma linguagem é definida pelas: – Regras de precedência dos operadores; – Regras de associatividade dos operadores. • Precedência – O valor de uma expressão depende ao menos parcialmente da ordem de avaliação dos operadores da expressão. Considere a expressão: a + b * c Ordem de Avaliação de Operadores • Precedência – Em vez de simplesmente avaliar os operadores em uma expressão da esquerda para a direita ou da direita para a esquerda, os matemáticos desenvolveram o conceito de colocar os operadores em uma hierarquia de prioridades de avaliação. – As regras de precedência de operadores para avaliação de expressões definem a ordem pela qual os operadores de diferentes níveis de precedência são avaliados (conforme visto pelo projetista da linguagem). – As regras para as linguagens imperativas comuns são todas iguais, porque são baseadas nas da matemática. Ordem de Avaliação de Operadores • Precedência – De modo geral, tem-se que: a exponenciação tem a mais alta precedência, seguida pela multiplicação e divisão no mesmo nível, depois pela adição e subtração binária no mesmo nível. – Muitas linguagens também incluem versões unárias da adição e da subtração. • Adição unária: não tem operação associada e não faz efeito em seu operando; • Subtração unária: modifica o sinal de seu operando. Pode aparecer no início de uma expressão ou em qualquer lugar dentro dessa, desde que seja entre parênteses para prevenir que fique ao lado de outro operador. Ordem de Avaliação de Operadores • Precedência – As precedências dos operadores aritméticos de algumas linguagens de programação bastante usadas são: – A precedência diz respeito a apenas algumas das regras para a ordem de avaliação de operadores; as regras de associatividade são outras. Ruby Linguagens Baseadas em C Ada Mais alta ** ++ e --pós-fixados **, abs + e – unários ++ e -- pré-fixados, + e - unários *, /, mod, rem *, /, % *, /, % + e – unários Mais baixa + e - binários + e - binários + e - binários Ordem de Avaliação de Operadores • Associatividade – Considere a expressão: – Se os operadores de adição e subtração têm o mesmo nível de precedência, as regras de precedência nada dizem a respeito da ordem de avaliação dos operadores nessa expressão. – Quando uma expressão contém duas ocorrências adjacentes de operadores com mesmo nível de precedência, a questão sobre qual operador é avaliado primeiro é respondida pelas regras de associatividade da linguagem. a - b + c - d Ordem de Avaliação de Operadores • Associatividade – Um operador pode ter: • Associatividade à direita: a ocorrência mais a direita é avaliada primeiro; • Associatividade à esquerda: a ocorrência mais a esquerda é avaliada primeiro. – A associatividade nas linguagens imperativas mais usadas é da esquerda para a direita, exceto que o operador de exponenciação (quando fornecido) associa da direita para a esquerda. Ordem de Avaliação de Operadores • Associatividade – Na expressão em Java: – o operador esquerdo é avaliado primeiro. Mas a exponenciação em Fortran e Ruby é associativa à direita, então na expressão: – o operador direito é avaliado primeiro. – Em Visual Basic, o operador de exponenciação (^) é associativo à esquerda. a - b + c A ** B ** C Ordem de Avaliação de Operadores • Associatividade – As regras de associatividade para algumas linguagens imperativas bastante utilizadas são: Linguagem Regra de Associatividade Ruby Esquerda: *, /, +, - Direita: ** Linguagens Baseadas em C Esquerda: *, /, %, + binário, - binário Direita: ++, --, - unário, + unário Ada Esquerda: Todos, exceto ** Não associativo: ** Ordem de Avaliação de Operadores • Parênteses – Os programadores podem alterar as regras de precedência e de associatividade colocando parênteses em expressões. – Uma parte com parênteses de uma expressão têm precedência em relação às suas partes adjacentes sem parênteses. – Por exemplo, apesar da multiplicação ter precedência em relação à adição, na expressão – a adição será avaliada primeiro. (A + B) * C Ordem de Avaliação de Operadores • Parênteses – Linguagens que permitem parênteses em expressões aritméticas podem dispensar todas as regras de precedência e simplesmente associar todos os operadores da esquerda para a direita ou da direita para a esquerda. – O programador seria responsável por especificar a ordem desejada da avaliação. – Vantagem: simplicidade – nem o autor nem os leitores dos programas precisariam se lembrar de regras de precedência ou de associatividade. Ordem de Avaliação de Operadores • Parênteses – Desvantagem: torna a escrita de expressões mais tediosa e compromete seriamente a legibilidade do código. – Mesmo assim, essa foi a escolha feita pelo projetista da linguagem APL. Ordem de Avaliação de Operadores • Expressões condicionais – Sentenças if-then-else podem ser usadas para realizar uma atribuição baseada em expressão condicional. Por exemplo: – Nas linguagens baseadas em C, esse código poderia ser especificado mais convenientemente em uma sentença de atribuição usando uma expressão condicional na forma: if (contador == 0) media = 0; else media = soma/contador; Ordem de Avaliação de Operadores • Expressões condicionais – onde expressão_1 é interpretada como uma expressãobooleana. Se a expressão_1 for avaliada como verdadeira, o valor da expressão inteira é o valor da expressão_2; caso contrário, será o valor da expressão_3. – No nosso exemplo teríamos: expressão_1 ? expressão_2 : expressão_3 media = (contador == 0) ? 0 : soma/contador; Ordem de Avaliação de Operadores • Expressões condicionais – Note que o sinal ? é usado em expressões condicionais como um operador ternário. – Expressões condicionais podem ser usadas em qualquer lugar de um programa (em uma linguagem baseada em C) nos quais qualquer outra expressão possa ser usada. – Além das linguagens baseadas em C, expressões condicionais são fornecidas em Perl, JavaScript e Ruby. Ordem de Avaliação de Operandos • Uma característica de projeto de expressões menos discutida é a ordem de avalição dos operandos. • As variáveis em expressões são avaliadas por meio da obtenção de seus valores a partir da memória. • As constantes são algumas vezes avaliadas da mesma maneira. Em outros casos, podem ser parte da instrução de linguagem de máquina e não requerer uma busca em memória. Ordem de Avaliação de Operandos • Se um operando é uma expressão entre parênteses, todos os operadores que ela contém devem ser avaliados antes de seu valor poder ser usado como um operando. • Se nenhum dos operandos de um operador tiver efeitos colaterais, a ordem de avaliação dos operandos é irrelevante. Ordem de Avaliação de Operandos • Efeitos colaterais – Um efeito colateral de uma função, chamado de um efeito colateral funcional, ocorre quando a função modifica um de seus parâmetros ou uma variável global. – Considere a expressão: – Se fun não tem o efeito colateral de modificar a, a ordem de avaliação dos dois operandos não tem efeito no valor da expressão. a + fun(a) Ordem de Avaliação de Operandos • Efeitos colaterais – No entanto, se funmodifica a, existe um efeito. Considere a seguinte situação: fun retorna 10 e modifica o valor de seu parâmetro para 20. Suponha que tivéssemos o seguinte: – Se o valor de a for obtido primeiro, seu valor é 10 e o valor da expressão é 20. – Se o segundo operando for avaliado primeiro, o valor de a é 20 e o valor da expressão é 30. a = 10 b = a + fun(a) Ordem de Avaliação de Operandos • Efeitos colaterais – O seguinte exemplo em C ilustra o mesmo problema quando uma função modifica uma variável global. int a = 5; int fun(){ a = 17; return 3; } //Fim de fun voidmain(){ a = a + fun(); printf(“Mensagem: %d\n”, a); } //Fim de man Ordem de Avaliação de Operandos • Efeitos colaterais – Execução: – Funções matemáticas não tem efeitos colaterais, porque não existe a noção de variáveis na matemática. O mesmo é verdade para linguagens de programação funcionais puras. Ordem de Avaliação de Operandos • Efeitos colaterais – Existem duas opções para o problema de avaliação de operandos e efeitos colaterais: 1. Proibir efeitos colaterais funcionais – difícil de implementar e elimina alguma flexibilidade para o programador. Por exemplo: proibiria o acesso a variáveis globais em funções; 2. Definir que os operandos em expressões sejam avaliados em uma ordem especifica – impede que técnicas de otimização de código reorganizem a avaliação de operandos. Ordem de Avaliação de Operandos • Transparência referencial e efeitos colaterais – O conceito de transparência referencial está relacionado e é afetado pelos efeitos colaterais funcionais. – Um programa tem a propriedade de transparência referencial se quaisquer duas expressões no programa que têm o mesmo valor puderem ser substituídas uma pela outra em qualquer lugar no programa, sem afetar a ação deste. – Considere o exemplo a seguir. Ordem de Avaliação de Operandos • Transparência referencial e efeitos colaterais – Se a função fun não tem efeitos colaterais, resultado1 e resultado2 serão iguais. – Entretanto, suponha que fun tem o efeito colateral de adicionar 1 a b ou a c. Então resultado1 não será igual a resultado2. – Esse efeito colateral viola a transparência referencial. resultado1 = (fun(a) + b)/(fun(a) - c ) temp = fun(a); resultado2 = (temp + b)/(temp - c) Ordem de Avaliação de Operandos • Transparência referencial e efeitos colaterais – Existem vantagens de se ter programas transparentes referencialmente. – Principal vantagem: torna a semântica muito mais fácil de entender do que a dos programas não transparentes referencialmente. – Ser transparente, referencialmente, torna uma função equivalente a uma função matemática, em termos de facilidade de entendimento. Operadores Sobrecarregados • Operadores aritméticos são usados para mais de um propósito. • Por exemplo, o sinal de + é usado para especificar a adição tanto de inteiros quanto de valores de ponto flutuante. • Algumas linguagens – Java, por exemplo – também o usam para concatenação de cadeias. • O uso múltiplo de um operador é chamado de sobrecarga de operadores, uma prática considerada aceitável, desde que nem legibilidade e confiabilidade sejam comprometidas. Operadores Sobrecarregados • Algumas linguagens de programação capazes de suportar tipos abstratos de dados, como C++ e C#, permitem ao programador sobrecarregar símbolos de operadores. • Por exemplo, suponha que um usuário quer definir o operador * entre um inteiro escalar e uma matriz de inteiros de forma a significar que cada elemento da matriz seja multiplicado por esse escalar. • Tal operador pode ser definido escrevendo um subprograma chamado * responsável por essa nova operação. Operadores Sobrecarregados • O compilador escolherá o significado correto quando um operador sobrecarregado é especificado, baseado nos tipos dos operandos, assim como ocorre com os operadores sobrecarregados definidos pela linguagem. • Quando usada com bom-senso, a sobrecarga de operadores definida pelo usuário pode melhorar a legibilidade. Adic_Matrizes(Multipl_Matrizes(A,B), Multipl_Matrizes (C,D)) A * B + C * D Operadores Sobrecarregados • Por outro lado, a sobrecarga definida pelo usuário também pode ser prejudicial à legibilidade. • Por exemplo, nada previne que um usuário defina + como sendo uma multiplicação. • Outra consideração é o processo de construir um sistema de software a partir de módulos criados por grupos diferentes. Grupos diferentes podem sobrecarregar um mesmo operador de formas diferentes. Conversões de Tipos • As conversões de tipos são de duas categorias: – Estreitamento: uma conversão de estreitamento converte um valor para um tipo que não pode armazenar aproximações equivalentes a todos os valores do tipo original. Por exemplo, converte um double para float em Java. – Alargamento: converte um valor para um tipo que pode incluir ao menos aproximações de todos os valores do tipo original. Por exemplo, converter um int para float em Java. Conversões de Tipos • Conversões de alargamento são quase sempre seguras, mantendo a magnitude do valor convertido. • Conversões de estreitamento nem sempre são seguras – algumas vezes a magnitude do valor convertido é modificada no processo. • Apesar de as conversões de alargamento serem normalmente seguras, elas podem resultar em uma precisão reduzida. Conversões de Tipos • Conversões de tipo podem ser explícitas ou implícitas (coerções). • Coerção em expressões – Uma das decisões de projeto relacionadas às expressões aritméticas é se um operador pode ter operandos de tipos diferentes. – Linguagens que permitem tais expressões, chamadas de expressões de modo misto, devem definir convenções para conversões de tipo para operandos implícitas porque os computadores não tem operações bináriasque recebam operandos de tipos diferentes. Conversões de Tipos • Coerção em expressões – Os projetistas de linguagens não entraram em um acordo a respeito da questão das coerções em expressões aritméticas. – Contra: preocupação com problemas de confiabilidade que podem resultar de tais coerções, porque elas reduzem os benefícios da verificação de tipos. – A favor: preocupação com a flexibilidade que resulta de tais restrições. – A questão é: os programadores devem se preocupar com essa categoria de erros ou o compilador deve detectá-los? Conversões de Tipos • Coerção em expressões – Exemplo: de quem é a responsabilidade por reconhecer o seguinte erro? int a; float b, c, d; ... d = b * c; int a; float b, c, d; ... d = b * a; Desejado: Implementado: Conversões de Tipos • Conversão de tipo explícita – A maioria das linguagens fornece alguma capacidade para a realização de conversões explícitas, tanto de alargamento quanto de estreitamento. – Nas linguagens baseadas em C, as conversões de tipo explícitas são chamadas de casts. – Para especificar um cast, o tipo desejado é colocado entre parênteses imediatamente antes da expressão a ser convertida. (int) angle
Compartilhar