Baixe o app para aproveitar ainda mais
Prévia do material em texto
PARADIGMAS DE LINGUAGENS DE PROGRAMAÇÃO Professor Alessandro Larangeiras UNIDADE 1: CONCEITOS PRELIMINARES 1.1 Razões para estudar conceitos de linguagens de programação 1.2 Domínios de programação 1.3 Critérios de avaliação de linguagens 1.4 Influências no projeto de linguagens 1.5 Categorias de linguagens 1.6 Trade-offs no projeto de linguagens 1.7 Métodos de implementação 1.8 Ambientes de programação CONTEÚDO UNIDADE 2: NOMES, VINCULAÇÕES E ESCOPO 2.1 Nomes 2.2 Variáveis 2.3 O conceito de vinculação 2.4 Escopo 2.5 Escopo e tempo de vida 2.6 Ambientes de referenciamento 2.7 Constantes nomeadas UNIDADE 3: TIPOS DE DADOS 3.1 Tipos de dados primitivos 3.2 Cadeias de Caracteres, Tipos enumeração, Tipos de Matrizes, Matrizes associativas, Registros, Tuplas, Listas, Uniões, Ponteiros e referências, Verificação de tipos, Teoria e tipos de dados 3.3 Dados abstratos e encapsulamento CONTEÚDO UNIDADE 4: EXPRESSÕES E SENTENÇAS DE ATRIBUIÇÃO 4.1 Introdução 4.2 Expressões aritméticas 4.3 Operadores sobrecarregados 4.4 Conversões de tipos 4.5 Expressões relacionais e booleanas 4.6 Avaliação em curto-circuito 4.7 Sentenças de atribuição 4.8 Atribuição de modo misto UNIDADE 5: SUBPROGRAMAS 5.1 Introdução 5.2 Fundamentos dos subprogramas 5.3 Questões de projeto para subprogramas 5.4 Ambientes de referenciamento local 5.5 Métodos de passagem de parâmetros CONTEÚDO UNIDADE 6: IMPLEMENTAÇÃO DE SUBPROGRAMAS 6.1 A semântica geral de chamadas e retornos 6.2 Implementação de subprogramas simples UNIDADE 7: SUPORTE A PROGRAMAÇÃO ORIENTADA A OBJETOS 7.1 Introdução 7.2 Programação orientada a objetos 7.3 Questões de projeto para linguagens orientada as objeto 7.4 Suporte para programação orientada a objetos em linguagens específicas 7.5 Implementação de construções orientadas a objetos CONTEÚDO UNIDADE 8: CONCORRÊNCIA 8.1 Introdução 8.2 Introdução à concorrência em nível de subprograma 8.3 Semáforos 8.4 Monitores 8.5 Passagem de mensagem 8.6 Linha de execução em Java e em C# UNIDADE 9: TRATAMENTO DE EXCEÇÕES E TRATAMENTO DE EVENTOS 9.1 Introdução ao tratamento de exceções 9.2 Tratamento de exceções em C++, Java e Python 9.3 Introdução ao tratamento de eventos 9.4 Tratamento de Eventos em Java e C# CONTEÚDO RAZÕES PARA ESTUDAR CONCEITOS DE LINGUAGENS DE PROGRAMAÇÃO ✓Aumento da capacidade de expressar ideias, com lógica e sistematização (SEBESTA, 2018, p. 2-32). // RECEITA DE BOLO SIMPLES NA FORMA DE ALGORITMO. // Reserve os ingredientes. acucar = 2 xícaras de chá de açúcar; farinhaDeTrigo = 3 xícaras de chá de farinha de trigo; margarina = 4 colheres de sopa de margarina; sacoDeOvos = 3 ovos; leite = 1 e 1/2 xícara de chá de leite; fermento = 1 colher de sopa de fermento em pó; formaDeBolo = VAZIO; // Separe claras e gemas. PARA CADA ovo DO sacoDeOvos FAÇA claras = claras + pegaClara(ovo); gemas = gemas + pegaGema(ovo); // Bata as claras em neve e reserve. clarasEmNeve = bateClarasEmNeve(claras); // Misture as gemas, a margarina e o açúcar até obter uma massa homogênea. massa = gemas + margarina + acucar; PARA contagem = 1; ENQUANTO contagem <= 5; FAÇA mistura(massa); // Acrescente o leite e a farinha de trigo aos poucos, sem parar de bater. PARA contagem = 1; ENQUANTO contagem <= 4; FAÇA bate(massa, (leite /4), (farinhaDeTrigo /4)); // Adicione as claras em neve e o fermento. bate(massa, clarasEmNeve, fermento); // Despeje a massa em uma forma grande de furo central untada e enfarinhada. unta(formaDeBolo); enfarinha(formaDeBolo); formaDeBolo = massa; // Asse em forno médio, 180°C, preaquecido, por aproximadamente 40 minutos. assa(formaDeBolo, MEDIO, 180, PREAQUECIDO, 40); ✓Escolha da linguagem mais adequada ao projeto, com base em suas virtudes e fraquezas (SEBESTA, 2018, p. 2-32). ✓Aumento da habilidade de aprender novas linguagens, a partir dos conceitos e recursos das já conhecidas (SEBESTA, 2018, p. 2-32). ✓Melhor entendimento da importância que tem os detalhes de implementação adotados em uma linguagem no uso mais inteligente desta linguagem e na identificação e correção de certos erros presentes em programas escritos com esta linguagem (SEBESTA, 2018, p. 2-32). ✓Melhor emprego de linguagens já conhecidas, devido à possibilidade de explorar toda a sua extensão, complexidade e recursos (SEBESTA, 2018, p. 2-32). ✓Avanço geral da computação, quanto à popularidade e à adesão dos recursos das linguagens (SEBESTA, 2018, p. 2-32). DOMÍNIOS DE PROGRAMAÇÃO Os computadores são utilizados em uma infinidade de tarefas, desde controlar usinas nucleares até disponibilizar jogos eletrônicos em telefones celulares. Por causa dessa diversidade de uso, linguagens de programação com objetivos muito diferentes foram desenvolvidas. SEBESTA, 2018, p. 5. Aplicações científicas. Os primeiros computadores digitais, surgidos no final dos anos 1940, início dos anos 1950, foram desenvolvidos e usados para aplicações científicas. Normalmente, as aplicações científicas daquela época utilizavam estruturas de dados relativamente simples, mas exigiam diversos cálculos aritméticos de ponto flutuante. As estruturas de dados mais comuns eram os vetores e as matrizes; as estruturas de controle mais comuns eram os laços de contagem e as seleções. As primeiras linguagens de programação de alto nível inventadas para aplicações científicas foram projetadas para suprir tais necessidades. [...] A primeira linguagem para aplicações científicas foi FORTRAN. ALGOL 60 e a maioria de seus descendentes também tinham a intenção de serem usados nessa área, apesar de terem sido projetados também em áreas relacionadas. Para aplicações científicas nas quais a eficiência é a principal preocupação, como as que eram comuns nos anos 1950 e 1960, nenhuma linguagem subsequente é significativamente melhor que Fortran, o que explica por que ela ainda é usada. SEBESTA, 2018, p. 5. Aplicações empresariais. O uso de computadores para aplicações comerciais começou nos anos 1950. Computadores especiais foram desenvolvidos para esse propósito, com linguagens especiais. A primeira linguagem de alto nível para negócios a ser bem-sucedida foi o COBOL [...], com sua primeira versão aparecendo em 1960. COBOL provavelmente ainda é a linguagem mais utilizada para tais aplicações. Linguagens de negócios são caracterizadas por facilidades para a produção de relatórios elaborados, maneiras precisas de descrever e armazenar números decimais e caracteres, e a habilidade de especificar operações aritméticas decimais. SEBESTA, 2018, p. 5-6. Inteligência artificial. A Inteligência Artificial (IA) é uma ampla área de aplicações computacionais caracterizadas pelo uso de computações simbólicas em vez de numéricas. Computações simbólicas são aquelas nas quais símbolos, compostos de nomes em vez de números, são manipulados. Além disso, a computação simbólica é feita de modo mais fácil por meio de listas ligadas de dados do que por meio de vetores. Esse tipo de programação algumas vezes requer mais flexibilidade do que outros domínios de programação [...]. A primeira linguagem de programação amplamente utilizada, desenvolvida para aplicações de IA, foi a linguagem funcional Lisp [...], que apareceu em 1959. A maioria das aplicações de IA desenvolvidas antes de 1990 foi escrita em Lisp ou em um de seus parentes próximos. No início dos anos 1970, entretanto, uma abordagem alternativa a algumas dessas aplicações apareceu – programação lógica usando a linguagem Prolog [...]. Mais recentemente, algumas aplicações de IA foram escritas em linguagens de sistemas como C, Scheme [...] (um dialeto de Lisp), e PROLOG [...]. SEBESTA, 2018, p. 6. Software para a Web. A World Wide Web é mantida por uma eclética coleção de linguagens, que vão desde linguagens de marcação, como HTML, que não é de programação, até linguagens deprogramação de propósito geral, como Java. Dada a necessidade universal de conteúdo dinâmico na Web, alguma capacidade de computação geralmente é incluída na tecnologia de apresentação de conteúdo. Essa funcionalidade pode ser fornecida por código de programação embarcado em um documento HTML. Tal código é normalmente escrito com uma linguagem de scripting, como JavaScript ou PHP [...]. SEBESTA, 2018, p. 6. CRITÉRIOS DE AVALIAÇÃO DE LINGUAGENS LEGIBILIDADE: facilidade com que códigos fonte podem ser lidos e entendidos. Deve ser considerada no contexto do domínio do problema. ✓Simplicidade: capacidade de ser simples, com poucos componentes (palavras, símbolos e códigos) básicos. “Programadores que precisam usar uma linguagem extensa aprendem um subconjunto dessa linguagem e ignoram outros recursos.” (SEBESTA, 2018, p. 8); ✓Ortogonalidade: capacidade de combinar entre si, sem restrições, os componentes básicos da linguagem para construir estruturas de controle e de dados. “Por exemplo, em uma linguagem de programação que suporta ponteiros, deve ser possível definir um ponteiro que aponte para qualquer tipo específico nela definido. Entretanto, se não for permitido aos ponteiros apontar para vetores, muitas estruturas de dados potencialmente úteis [...] não poderão ser definidas.” (SEBESTA, 2018, p. 9); ✓Tipos de dados: capacidade de apresentar mecanismos adequados para definir tipos e estruturas de dados. “Por exemplo, suponha que um tipo numérico seja usado como uma flag porque não existe nenhum tipo booleano na linguagem. Em tal linguagem, poderíamos ter uma atribuição como: timeOut = 1. O significado dessa sentença não é claro. Em uma linguagem que inclui tipos booleanos, teríamos: timeOut = true. O significado dessa sentença é perfeitamente claro.” (SEBESTA, 2018, p. 11); ✓Projeto da sintaxe: clareza da sintaxe (palavras reservadas adotadas, forma e significado das sentenças). FACILIDADE DE ESCRITA: facilidade com que a linguagem pode ser usada para criar programas para determinado domínio. Deve ser considerada no contexto do domínio do problema. ✓Simplicidade e ortogonalidade: excesso de componentes (palavras, símbolos e códigos) pode ocasionar uso incorreto de alguns recursos e subutilização de outros; excesso de ortogonalidade pode ser prejudicial, pois, se quaisquer combinações de componentes básicos forem válidas, erros em programas podem ser gerados; ✓Expressividade: capacidade de expressar mais com menos. “Por exemplo, em C, a notação count++ é mais conveniente e menor que count = count + 1.” (SEBESTA, 2018, p. 13). CONFIABILIDADE: capacidade de ser confiável, conforme suas especificações em todas as condições. ✓Verificação de tipos: capacidade de verificar erros de tipos de dados em um programa, em tempo de compilação (mais desejável) e em tempo de execução (mais dispendiosa); ✓Tratamento de exceções: capacidade de interceptar erros em tempo de execução, tomar medidas corretivas e manter o funcionamento do programa; ✓Apelidos (aliases): possibilidade de ter um ou mais nomes capazes de acessar a mesma célula de memória. “A maioria das linguagens permite algum tipo de apelido – por exemplo, dois ponteiros (ou referências) configurados para apontar para a mesma variável [...].” (SEBESTA, 2018, p. 14); ✓Legibilidade e facilidade de escrita: “Tanto a legibilidade quanto a facilidade de escrita influenciam a confiabilidade. Um programa escrito em uma linguagem que não suporta maneiras naturais de expressar os algoritmos exigidos necessariamente usará estratégias artificiais. É menos provável que estratégias artificiais estejam corretas para todas as situações possíveis. Quanto mais fácil é escrever um programa, maior a probabilidade de ele estar correto.” (SEBESTA, 2018, p. 15); CUSTO: custos para treinar equipes, para escrever programas, para compilar programas, para executar programas, de implementação (licenciamento, dependência de plataforma de hardware específica), de baixa confiabilidade. INFLUÊNCIAS NO PROJETO DE LINGUAGENS ARQUITETURA DE COMPUTADORES: “A arquitetura básica dos computadores tem um efeito profundo no projeto de linguagens. A maioria das linguagens populares dos últimos 60 anos tem sido projetada considerando a principal arquitetura de computadores, chamada de arquitetura de von Neumann [...]. Elas são chamadas de linguagens imperativas [...]. Devido à arquitetura de von Neumann, os recursos centrais das linguagens imperativas são as variáveis, que modelam as células de memória; as sentenças de atribuição, baseadas na operação de envio de dados e instruções (piping); e a forma iterativa de repetição nessa arquitetura.” (SEBESTA, 2018, p. 17). METODOLOGIAS DE PROJETO DE PROGRAMAS: O final de 1960 e o início de 1970 trouxeram uma pesquisa intensa, iniciada em grande parte pelo movimento da programação estruturada, tanto para o desenvolvimento de software quanto para o projeto de linguagens de programação. Uma razão importante para essa pesquisa foi a mudança do custo de computação do hardware para o software, pois os custos de hardware declinavam e os de programação aumentavam [...]. As novas metodologias de desenvolvimento de software que emergiram como um resultado da pesquisa de 1970 foram chamadas de projeto descendente (top-down) e de refinamento passo a passo [...]. No final de 1970, iniciou-se uma mudança nas metodologias de projeto de programas, da orientação a procedimentos para a orientação a dados. Os métodos orientados a dados enfatizam a modelagem de dados, concentrando- se no uso de tipos abstratos para solucionar problemas. Para que as abstrações de dados sejam usadas efetivamente no projeto de sistemas de software, elas devem ser suportadas pelas linguagens utilizadas para a implementação [...]. O último grande passo na evolução do desenvolvimento de software orientado a dados, que começou no início de 1980, é o projeto orientado a objetos. As metodologias orientadas a objetos começam com a abstração de dados, que encapsula o processamento com os objetos de dados, controla o acesso aos dados e também adiciona mecanismos de herança e vinculação dinâmica de métodos [...]. (SEBESTA, 2018, p. 19-20). 1. Por que é útil para um programador ter alguma experiência no projeto de linguagens, mesmo que ele nunca projete uma linguagem de programação? 2. Quais são os principais critérios de avaliação para a adoção de uma linguagem de programação? 3. Qual é a desvantagem de ter recursos demais em uma linguagem? 4. Explique com suas palavras a influência que a expressividade tem na facilidade de escrita de códigos em uma linguagem. 5. O que significa uma linguagem ser confiável? EXERCÍCIOS CATEGORIAS DE LINGUAGENS [...] Programação IMPERATIVA é um paradigma de programação que descreve a computação como ações, enunciados ou comandos que mudam o estado (variáveis) de um programa. Muito parecido com o comportamento imperativo das linguagens naturais que expressam ordens, programas imperativos são uma sequência de comandos para o computador executar. O nome do paradigma, imperativo, está ligado ao tempo verbal imperativo, onde o programador diz ao computador: faça isso, depois isso, depois aquilo... Este paradigma de programação se destaca pela simplicidade, uma vez que todo ser humano, ao programar, o faz imperativamente, baseado na ideia de ações e estados [...]. PROGRAMAÇÃO IMPERATIVA, 2019. [...] Programação FUNCIONAL é um paradigma de programação que trata a computação como uma avaliação de funções matemáticas e evita estados ou dados mutáveis. Ela enfatiza a aplicação de funções, ao contrário da programação imperativa, que enfatiza mudanças no estado do programa. [...] Na programação funcional parecem faltar diversas construções frequentemente [...] consideradas essenciais em linguagens imperativas, como C ou Pascal. Por exemplo, numa programação estritamente funcional, não há alocaçãoexplícita de memória, nem declaração explícita de variáveis. No entanto, essas operações podem ocorrer automaticamente quando a função é invocada; a alocação de memória ocorre para criar espaço para os parâmetros e para o valor de retorno, e a declaração ocorre para copiar os parâmetros dentro deste espaço recém-alocado e para copiar o valor de retorno de volta para dentro da função que a chama. Ambas as operações podem ocorrer nos pontos de entrada e saída da função, então efeitos colaterais no cálculo da função são eliminados [...]. Os laços, outra construção de programação imperativa, estão presentes por meio da construção funcional mais geral de recursividade. Funções recursivas invocam a si mesmas, permitindo que uma operação seja realizada várias vezes. Recursividade em programação funcional pode assumir várias formas e é em geral uma técnica mais poderosa que o uso de laços. Por essa razão, quase todas as linguagens imperativas também a suportam (sendo Fortran 77 e COBOL exceções notáveis). PROGRAMAÇÃO FUNCIONAL, 2019. Programação LÓGICA é um paradigma de programação que faz uso da lógica matemática [...]. A programação lógica é uma ideia que tem sido investigada no contexto da inteligência artificial pelo menos desde o momento em que John McCarthy (1958) propôs “programas para manipular sentenças instrumentais comuns apropriadas à linguagem formal (muito provavelmente uma parte do cálculo de predicado)”. O programa básico formará conclusões imediatas a partir de uma lista de premissas. Essas conclusões serão tanto sentenças declarativas quanto imperativas. Quando uma sentença imperativa é deduzida, o programa toma uma ação correspondente [...]. PROGRAMAÇÃO LÓGICA, 2019. A ORIENTAÇÃO A OBJETOS é uma maneira (paradigma) de se pensar e desenvolver programas. Ao desenvolver um programa orientado a objeto, você deve pensar em como a situação que o programa está automatizando funciona no mundo real, ou seja, quais os objetos envolvidos na situação e como eles se relacionam. A partir disso você deve se preocupar em modelar e implementar o programa de maneira que ele espelhe a situação existente no mundo real, ou seja, deve criar e manipular as representações de objetos existentes no mundo real. CUNHA, 2019, slide 1. TRADE-OFFS NO PROJETO DE LINGUAGENS Os critérios de avaliação de linguagens de programação descritos [antes] [...] fornecem um modelo para o projeto de linguagens. Infelizmente, esse modelo é contraditório. Em seu brilhante artigo sobre o projeto de linguagens, Hoare (1973) afirma que “existem tantos critérios importantes, mas conflitantes, que sua harmonização e satisfação estão entre as principais tarefas de engenharia”. Dois critérios conflitantes são a confiabilidade e o custo de execução. Por exemplo, a linguagem Java exige que todas as referências aos elementos de um vetor sejam verificadas para garantir que os índices estejam em suas faixas válidas. Esse passo aumenta muito o custo de execução de programas Java que contenham um grande número de referências a elementos de vetores. C não exige a verificação da faixa de índices - dessa forma, os programas em C executam mais rápido que programas semanticamente equivalentes em Java, apesar de estes serem mais confiáveis. Os projetistas de Java trocaram eficiência de execução por confiabilidade. SEBESTA, 2018, p. 21. [...] O conflito entre a facilidade de escrita e a legibilidade é comum no projeto de linguagens. Os ponteiros de C++ podem ser manipulados de diversas maneiras, o que possibilita um endereçamento de dados altamente flexível. Devido aos potenciais problemas de confiabilidade com o uso de ponteiros, eles não foram incluídos em Java. Exemplos de conflitos entre critérios de projeto e de avaliação de linguagens são abundantes; alguns sutis, outros óbvios. Logo, é claro que a tarefa de escolher construções e recursos ao projetar uma linguagem de programação exige muito comprometimento e trade-offs. SEBESTA, 2018, p. 21. MÉTODOS DE IMPLEMENTAÇÃO ✓Implementação baseada em COMPILAÇÃO. Exemplos: C, C++, Delphi, VB, COBOL. ✓Implementação baseada em INTERPRETAÇÃO PURA. Exemplos: PHP, JavaScript. ✓Implementação HÍBRIDA. Exemplos: Java, C#.NET, VB.NET. ✓Implementação baseada em PRÉ-PROCESSADOR (processamento imediatamente antes da compilação). Exemplo: instrução de pré-processador em C. AMBIENTES DE PROGRAMAÇÃO Um ambiente de programação é a coleção de ferramentas usadas no desenvolvimento de software. Essa coleção pode consistir em apenas um SISTEMA DE ARQUIVOS, um EDITOR DE TEXTOS, um LIGADOR e um COMPILADOR. Ou pode incluir uma grande coleção de ferramentas integradas, cada uma acessada por meio de uma interface de usuário uniforme [(IDE - Integrated Development Environment)]. Neste último caso, o desenvolvimento e a manutenção de software são enormemente melhorados. Logo, as características de uma linguagem de programação não são a única medida da capacidade de desenvolvimento de um sistema. SEBESTA, 2018, p. 29. PRÓS do uso de ambientes de programação: ✓Facilidade na escrita do código fonte, com o realce de palavras chave e a identificação prematura de erros de sintaxe; ✓Agilidade no desenvolvimento, com o uso de ferramentas. CONTRAS do uso de ambientes de programação: ✓Dependência dos recursos que facilitam e agilizam o desenvolvimento; ✓Dificuldade de portabilidade, por acoplamento com recursos específicos. NOMES OU IDENTIFICADORES Na maioria das linguagens de programação, os nomes têm o mesmo formato: uma letra seguida por uma cadeia de letras, dígitos e sublinhados (_). Apesar de o uso de sublinhados ter sido disseminado nos anos 1970 e 1980, a prática é menos popular atualmente. Nas linguagens baseadas em C [(C, Objective-C, C++, Java e C#)], eles foram, em grande medida, substituídos pela assim chamada notação camelo (CamelCase), [por exemplo: MyStack, ou Low Camel Case, por exemplo: myStack] [...]. Note que o uso de sublinhados e de caixa mista em nomes é uma questão de estilo de programação, não de projeto de linguagem. SEBESTA, 2018, p. 199. Palavras especiais são palavras que compõem a sintaxe de uma linguagem de programação. São divididas em: ✓ PALAVRAS-CHAVE: palavras que são especiais somente em determinado contexto. Exemplos em Fortran: INTEGER real // Variável de tipo INTEGER e nome REAL. REAL integer // Variável de tipo REAL e nome INTEGER. ✓ PALAVRAS RESERVADAS: palavras que são exclusivas da sintaxe, ou seja, não podem ser usadas para nomear variáveis. Exemplos em Java: abstract, boolean, break, byte, case, catch, char, class, const, continue, default, do, double, else etc. VARIÁVEIS ✓ Nome ou identificador: rótulo que identifica a variável, ou seja, seu nome propriamente dito; ✓ Endereço: endereço da célula de memória no qual a variável está armazenada; ✓ Tipo de dado: faixa de valores que a variável pode armazenar e o conjunto de operações suportado. O tipo de dado pode ser PRIMITIVO (fornecido por padrão pela linguagem, por exemplo, int, char, double, boolean) ou DEFINIDO PELO PROGRAMADOR (por exemplo, classes orientadas a objeto); ✓ Valor: conteúdo da célula de memória associada à variável, ou seja, o dado armazenado na variável. Se subdivide em L-VALUE e R-VALUE. Por exemplo, para a expressão a = b + c, a é uma l-value e b + c, uma r-value; ✓ Tempo de vida: prazo em que a variável permanece alocada na memória, podendo ser GLOBAL (com efeito no trecho completo programa) ou LOCAL (com efeito somente em um trecho específico do programa); ✓ Escopo: trecho do programa de efeito da variável. VINCULAÇÃO (BINDING OU LINKING) Vinculação é a associação entre um atributo e uma entidade, como entre uma variável e seu tipo ou valor, ou entre uma operação e um símbolo. O momento no qual uma vinculação ocorre é chamado de TEMPO DE VINCULAÇÃO.A vinculação e os tempos de vinculação são conceitos proeminentes na semântica das linguagens de programação. As vinculações podem ocorrer em tempo de projeto da linguagem, em tempo de implementação da linguagem, em tempo de compilação, em tempo de carga, em tempo de ligação ou em tempo de execução. Por exemplo, o símbolo de asterisco (*) é geralmente ligado à operação de multiplicação em tempo de projeto de uma linguagem. Um tipo de dados, como int em C, é vinculado a uma faixa de valores possíveis em tempo de implementação da linguagem. Em tempo de compilação, uma variável em um programa Java é vinculada a um tipo de dados em particular. Uma variável pode ser vinculada a uma célula de armazenamento quando o programa é carregado na memória [(tempo de carga)]. A mesma vinculação não acontece até o tempo de execução, em alguns casos, como as variáveis declaradas em métodos Java. Uma chamada a um subprograma de uma biblioteca [(DLL, em Windows, SO, em Linux)] é vinculada ao código do subprograma em tempo de ligação. SEBESTA, 2018, p. 203. Por exemplo, a execução da expressão A = B + C ocorreria conforme a seguinte sequência de vinculação (OLIVETE JÚNIOR, 2019, slide 22): 1. Obtém-se os endereços de memória de A, B e C; 2. Obtém-se os dados dos endereços de memória B e C; 3. Computa-se B + C; 4. Armazena-se o resultado da computação de B + C no endereço de A. Vinculação de atributos a variáveis. Uma vinculação é estática se ocorre pela primeira vez ANTES do tempo de execução e PERMANECE INALTERADA ao longo da execução do programa. Se a vinculação ocorre pela primeira vez DURANTE o tempo de execução ou PODE SER MUDADA ao longo do curso da execução do programa, é chamada de dinâmica. SEBESTA, 2018, p. 203. Vinculações estática (a) e dinâmica (b). Fonte: PIRKLE, 2013, p. 22 (a) (b) Vinculação de tipos. Antes de uma variável poder ser referenciada em um programa, ela deve ser vinculada a um tipo de dados. Os dois aspectos importantes dessa vinculação são: como o tipo é especificado e quando a vinculação ocorre. Os tipos podem ser especificados ESTATICAMENTE por alguma forma de declaração explícita ou implícita. [...] Uma declaração explícita é uma sentença em um programa que lista nomes de variáveis e especifica que elas são de certo tipo. Uma declaração implícita é uma forma de associar variáveis a tipos por meio de convenções padronizadas, em vez de por sentenças de declaração [...]. A maioria das linguagens de programação amplamente usadas, que emprega exclusivamente vinculação estática a tipos e foram projetadas desde meados dos anos 1960, exige declarações explícitas de todas as variáveis (Visual Basic e ML são duas exceções). SEBESTA, 2018, p. 204. Por exemplo: // Declaração explícita em Java. int numero; // Declaração implícita em C# (inferência de tipo). // Fonte: SEBESTA, 2018, p. 204. var sum = 0; var total = 0.0; var name = “Fred”; Lembre-se de que essas variáveis são tipadas estaticamente – seus tipos são fixos durante toda a vida da unidade na qual são declaradas. (SEBESTA, 2018, p. 204). A vinculação de tipo de variável implícita é feita pelo processador da linguagem, por um compilador ou interpretador. Existem várias bases diferentes para as vinculações implícitas de tipo de variável. A mais simples delas é a convenção de atribuição de nomes. Nesse caso, o compilador ou o interpretador vincula uma variável a um tipo com base na forma sintática do nome da variável. Apesar de serem uma pequena conveniência para os programadores, as declarações implícitas podem ser prejudiciais à confiabilidade, pois impedem o processo de compilação de detectar alguns erros de programação e de digitação. SEBESTA, 2018, p. 204. Com a vinculação de tipos DINÂMICA, o tipo de uma variável não é especificado por uma sentença de declaração, nem pode ser determinado pelo nome da variável. Em vez disso, a variável é vinculada a um tipo quando é atribuído um valor a ela em uma sentença de atribuição. Quando a sentença de atribuição é executada, a variável que recebe um valor atribuído é vinculada ao tipo do valor da expressão no lado direito da atribuição. Tal atribuição também pode vincular a variável a um endereço e a uma célula de memória, pois diferentes valores de tipo podem exigir diferentes quantidades de armazenamento. Qualquer variável pode receber qualquer valor de tipo. Além disso, o tipo de uma variável pode mudar qualquer número de vezes durante a execução do programa. É importante perceber que o tipo de uma variável, quando é vinculado dinamicamente, pode ser temporário. SEBESTA, 2018, p. 205. Por exemplo: // Vinculação de tipo dinâmica em PHP. $nome = 'Joao'; $idade = 50; $pi = 3.14159265; $sim = true; As linguagens nas quais os tipos são vinculados dinamicamente são drasticamente diferentes daquelas nas quais os tipos são vinculados estaticamente. A principal vantagem da vinculação dinâmica de variáveis a tipos é que ela oferece maior flexibilidade ao programador. Por exemplo, um programa para processar dados numéricos em uma linguagem que usa a vinculação de tipos dinâmica pode ser escrito como um programa genérico; ou seja, ele será capaz de tratar dados de quaisquer tipos numéricos. Qualquer tipo de dados informado será aceitável, porque a variável na qual os dados serão armazenados pode ser vinculada ao tipo correto quando eles forem atribuídos às variáveis após a entrada [...]. SEBESTA, 2018, p. 205. Antes de meados dos anos 1990, a maioria das linguagens de programação comumente usadas empregava vinculação de tipos estática; as principais exceções eram algumas linguagens funcionais, como Lisp. Contudo, desde então houve uma mudança significativa para linguagens que usam vinculação dinâmica de tipos. Em Python, Ruby, JavaScript e PHP, a vinculação de tipos é dinâmica [...]. A opção de vinculação dinâmica de tipos foi incluída no C# 2010. Uma variável pode ser declarada para usar vinculação de tipos dinâmica, com inclusão da palavra reservada dynamic em sua declaração, como no exemplo a seguir: dynamic any; [...] Em linguagens orientadas a objetos puras – por exemplo, Ruby –, todas as variáveis são referenciadas e não possuem tipos; todos os dados são objetos e qualquer variável pode referenciar qualquer objeto. De certo modo, nessas linguagens as variáveis são todas do mesmo tipo – elas são referências [...]. SEBESTA, 2018, p. 205-206. Vinculações de armazenamento: ✓ Alocação: reserva da célula de memória à qual uma variável será vinculada a partir das células disponíveis. ✓ Liberação ou desalocação: devolução da célula de memória ao conjunto de células disponíveis, quando a variável não mais necessitar dela. Tempo de vida: prazo em que a variável permanece vinculada à uma célula de memória. As classificações, por tempo de vida, são: ✓ Variáveis estáticas: “vinculadas a células de memória antes da execução, permanecendo na mesma célula de memória durante toda a execução. Exemplos: variáveis em FORTRAN 77, variáveis estáticas em C (cláusula static)” (OLIVETE JÚNIOR, 2019, slide 45). ✓ Variáveis dinâmicas da pilha (stack): “vinculações são criadas quando suas sentenças de declaração são efetuadas, mas o tipo é estaticamente vinculado. Ex: em Java, as declarações de variáveis que aparecem no início de um método são elaboradas quando o método é chamado e as variáveis definidas por essas declarações são liberadas quando o método completa sua execução” (OLIVETE JÚNIOR, 2019, slide 47). ✓ Variáveis dinâmicas do monte (heap) explícitas: “[...] são células de memória não nomeadas (abstratas), que são alocadas e liberadas por instruções explícitas, especificadas pelo programador, que tem efeito durante a execução. Essas variáveis, alocadas a partir do monte e liberadas para o monte podem ser referenciadas por ponteiros. Exemplos: objetos dinâmicos em C e tudo em JAVA” (OLIVETE JÚNIOR,2019, slide 49-50). ✓ Variáveis dinâmicas do monte (heap) implícitas: “[...] são vinculadas ao armazenamento no monte (heap) apenas quando são atribuídos valores a elas. Exemplo [em] PHP: $alunos = (2, 5, 6, 1); // independente do que a variável $alunos foi usada, agora ela passa ser um vetor de inteiros com 4 valores numéricos” (OLIVETE JÚNIOR, 2019, slide 52). ESCOPO [...] O escopo de uma variável é a faixa de sentenças nas quais ela é visível. Uma variável é visível em uma sentença se ela pode ser referenciada ou atribuída nessa sentença. As regras de escopo de uma linguagem definem como determinada ocorrência de um nome é associada a uma variável ou, no caso de uma linguagem funcional, como um nome é associado a uma expressão. Em particular, as regras de escopo determinam como referências a variáveis declaradas fora do subprograma ou bloco em execução são associadas às suas declarações e, logo, aos seus atributos [...]. Portanto, um claro entendimento dessas regras para uma linguagem é essencial para a habilidade de escrever ou ler programas nela. Uma variável é LOCAL a uma unidade ou a um bloco de programa se for declarada lá. As variáveis NÃO LOCAIS de uma unidade ou de um bloco de programa são aquelas visíveis dentro da unidade ou do bloco de programa, mas não declaradas nessa unidade ou nesse bloco. Variáveis GLOBAIS são uma categoria especial de variáveis não locais [...]. SEBESTA, 2018, p. 211. ALGOL 60 introduziu o método de vincular nomes a variáveis não locais, chamado de escopo estático [(ou escopo léxico)], copiado por muitas linguagens imperativas subsequentes e também por muitas linguagens não imperativas. O escopo estático é assim chamado porque o escopo de uma variável pode ser determinado estaticamente ou seja, – ou seja, ANTES DA EXECUÇÃO. Isso permite a um leitor de programas humano (e a um compilador) determinar o tipo de cada variável no programa simplesmente examinando seu código fonte. Existem duas categorias de linguagens de escopo estático: aquelas nas quais os subprogramas PODEM SER ANINHADOS, as quais criam escopos estáticos aninhados, e aquelas nas quais os subprogramas NÃO PODEM SER ANINHADOS. Na última categoria, os escopos estáticos também são criados para subprogramas, mas os aninhados são criados apenas por definições de classes aninhadas ou de blocos aninhados. Ada, JavaScript, Common Lisp, Scheme, Fortran 2003+, F# e Python permitem subprogramas aninhados, mas as linguagens baseadas em C [(C, Objective-C, C++, Java e C#)] não. SEBESTA, 2018, p. 211. Considere a função [de] JavaScript a seguir, big, na qual as duas funções sub1 e sub2 são aninhadas: function big() { function sub1() { var x = 7; sub2(); } function sub2() { var y = x; } var x = 3; sub1(); } De acordo com o escopo estático, a referência à variável x em sub2 é para x declarado no procedimento big. Isso é verdade porque a busca por x começa no procedimento no qual a referência ocorre, sub2, mas nenhuma declaração para x é encontrada lá. A busca continua no pai estático de sub2, big, onde a declaração de x é encontrada. O x declarado em sub1 é ignorado porque não está nos acestrais estáticos de sub2. SEBESTA, 2018, p. 212. Em linguagens que usam escopo estático, independentemente de ser permitido o uso de subprogramas aninhados ou não, algumas declarações de variáveis podem ser ocultadas de outros segmentos de código. Por exemplo, considere mais uma vez a função [de] JavaScript big. A variável x é declarada tanto em big quanto em sub1, aninhado dentro de big. Dentro de sub1, toda referência simples a x é para o x local. Portanto, o x mais externo é ocultado de sub1. SEBESTA, 2018, p. 213. Blocos. Muitas linguagens permitem que novos escopos estáticos sejam definidos no meio do código executável. Esse poderoso conceito, introduzido em ALGOL 60, permite a uma seção de código ter suas próprias variáveis locais, cujo escopo é minimizado. Tais variáveis são dinâmicas da pilha, de forma que seu armazenamento é alocado quando a seção é alcançada e liberado quando a seção é abandonada. Essa seção de código é denominada bloco. Os blocos dão origem à frase linguagem estruturada por blocos. As linguagens baseadas em C [(C, Objective-C, C++, Java e C#)] permitem que quaisquer sentenças compostas (uma sequência de sentenças envoltas em chaves correspondentes - {}) tenham declarações e, dessa forma, definam um novo escopo. Tais sentenças compostas são denominadas blocos. Por exemplo, se list fosse um vetor de inteiros, alguém poderia escrever o seguinte: if(list[i] < list[j]) { int temp; temp = list[i]; list[i] = list[j]; list[j] = temp; } SEBESTA, 2018, p. 213. Os escopos criados por blocos, que podem ser aninhados em blocos maiores, são tratados exatamente como aqueles criados por subprogramas. Referências a variáveis em um bloco e que não estão declaradas lá são conectadas às declarações por meio da busca pelos escopos que o envolvem (blocos ou subprogramas), por ordem de tamanho (do menor para o maior). Considere o esqueleto da função em C: void sub() { int count; ... while(...) { int count; count++; ... } ... } A referência a count no laço de repetição while é para count local do laço. Nesse caso, o count de sub é ocultado do código que está dentro do laço while [...]. SEBESTA, 2018, p. 213. Ordem de Declaração. Em C89, bem como em outras linguagens, todas as declarações de dados em uma função, exceto aquelas em blocos aninhados, devem aparecer no início da função. Contudo, algumas linguagens por exemplo, C99, C++, Java, – ou seja, JavaScript, and C# – permitem que declarações de variáveis apareçam em qualquer lugar possível de uma instrução aparecer em uma unidade do programa. Declarações podem criar escopos que não estejam associados com instruções compostas ou subprogramas. Por exemplo, em C99, C++ e Java, o escopo de todas as variáveis locais vai do ponto de suas declarações até o fim do bloco no qual essas declarações aparecem. SEBESTA, 2016, p. 239. Em C#, o escopo de qualquer variável declarada em um bloco pertence ao bloco inteiro, independente da posição da declaração no bloco, desde que não esteja em um bloco aninhado. O mesmo acontece para métodos [...]. [...] Em JavaScript, variáveis locais podem ser declaradas em qualquer lugar de uma função, mas o escopo de tal variável pertence sempre à função inteira. Se usada antes de sua declaração na função, tal variável tem o valor undefined. A referência não é ilegal. As instruções for de C++, Java e C# permitem definições de variáveis em suas expressões de inicialização. Nas primeiras versões de C++, o escopo de tal variável ia de sua definição até o fim do menor bloco delimitador. Na versão padrão, contudo, o escopo está restrito ao construtor do for, como é o caso de Java e C#. [...] SEBESTA, 2016, p. 240. Escopo Global. Algumas linguagens, incluindo C, C++, PHP, JavaScript e Python, permitem que a estrutura de um programa seja uma sequência de definições de função, na qual as definições de variáveis podem aparecer fora das funções. Definições fora de funções de um arquivo criam variáveis globais, que podem estar potencialmente visíveis àquelas funções. C e C++ têm declarações e definições de dados globais. Declarações especificam tipos e outros atributos, mas não causam alocação de armazenagem [na memória]. Definições especificam atributos e causam alocação de armazenagem [na memória]. Para um nome global específico, um programa em C pode ter qualquer quantidade de declarações compatíveis, mas somente uma única definição. Uma declaração de uma variável fora das definições da função especifica que a variável está definida em um arquivo diferente. Uma variável global em C está implicitamente visível a todas as funções subsequentes do arquivo, exceto aquelas que incluem a declaração de uma variável local com o mesmo nome. [...] SEBESTA, 2016, p.241. Em C++, uma variável global que esteja oculta por uma local de mesmo nome pode ser acessada usando-se o operador de escopo (::). Por exemplo, se x é uma variável global oculta em uma função por uma local chamada x, a global poderia ser referenciada como ::x. Instruções PHP podem ser intercaladas com definições de função. Variáveis em PHP são implicitamente declaradas quando aparecem em instruções. Qualquer variável que esteja implicitamente declarada fora de qualquer função é uma variável global; variáveis implicitamente declaradas dentro de funções são variáveis locais. O escopo de variáveis globais se estende de suas declarações até o fim do programa, mas ignora quaisquer definições de função subsequentes [...]. [...] As variáveis globais de JavaScript são muito similares às de PHP, exceto que não há modo de se acessar uma variável global em uma função que tenha declarado uma variável local com o mesmo nome. As regras de visibilidade de variáveis globais em Python são incomuns. Variáveis não são normalmente declaradas, como em PHP. Elas são implicitamente declaradas quando aparecem como objetos de instruções de atribuição. Uma variável global pode ser referenciada em uma função, mas só pode ser atribuída em uma função se tiver sido declarada como global da função. [...] SEBESTA, 2016, p. 241-242. Avaliação de Escopo Estático. O escopo estático provê um método de acesso não local que funciona bem em muitas situações. Contudo, ele não está livre de problemas. Primeiro, na maioria dos casos, ele permite mais acesso a variáveis e subprogramas que o necessário. É simplesmente uma ferramenta muito grosseira para especificar de modo conciso tais restrições. Segundo, e talvez mais importante, é um problema relacionado à evolução do programa. O software é altamente dinâmico - programas utilizados regularmente mudam continuamente. Essas mudanças muitas vezes resultam em restruturação, destruindo, assim, a estrutura inicial que restringia o acesso de variáveis e subprogramas em uma linguagem como escopo estático. Para evitar a complexidade de se manter essas restrições de acesso, os desenvolvedores muitas vezes descartam a estrutura quando ela fica no caminho. Deste modo, contornar as restrições de escopo estático pode levar a projetos de programas que têm pouca semelhança com o original, até em áreas do programa nas quais as mudanças não foram feitas. Projetistas são encorajados a usar muito mais [variáveis globais] que o necessário. Todos os subprogramas pode terminar sendo aninhados no mesmo nível, no programa principal, usando-se [variáveis] globais em vez de níveis mais profundos de aninhamento. [...] Além disso, o projeto final pode ser desajeitado e artificial, e pode não refletir o projeto conceitual em questão [...]. Uma alternativa ao uso de escopo estático para controlar acesso a variáveis e subprogramas é um construtor de encapsulamento, o qual está incluído em muitas linguagens mais recentes [...]. SEBESTA, 2016, p. 244. Escopo Dinâmico. O escopo de variáveis em APL, SNOBOL4 e nas primeiras versões de Lisp é dinâmico. Perl e Common Lisp também permitem que variáveis sejam declaradas de modo a ter escopo dinâmico, embora o mecanismo de escopo padrão nestas linguagens seja estático. O escopo dinâmico está baseado na sequência de chamadas de subprogramas, não em seu relacionamento espacial entre si. Deste modo, o escopo [dinâmico] pode ser determinado somente em TEMPO DE EXECUÇÃO. SEBESTA, 2016, p. 244. Considere de novo a função big [...], reproduzida aqui, menos as chamadas às funções: function big() { function sub1() { var x = 7; } function sub2() { var y = x; var z = 3; } var x = 3; } Assuma que as regras de escopo dinâmico se aplicam a referências não locais. O significado do identificador x referenciado em sub2 é dinâmico não pode ser determinado em tempo de compilação. Ele pode – ou seja, referenciar a variável a partir da declaração de x, dependendo da sequência de chamada. SEBESTA, 2016, p. 244-245. Avaliação de Escopo Dinâmico. O efeito do escopo dinâmico na programação é profundo. Quando o escopo dinâmico é usado, os atributos corretos das variáveis não locais visíveis à instrução de um programa não pode ser determinada estaticamente. Além do mais, uma referência ao nome de tal variável nem sempre é para a mesma variável. Uma instrução em um subprograma que contenha uma referência a uma variável não local pode se referir a variáveis não locais diferentes durante execuções diferentes do subprograma. Vários tipos de problemas de programação resultam diretamente do escopo dinâmico. SEBESTA, 2016, p. 245. Primeiro, durante o intervalo de tempo que começa quando um subprograma inicia sua execução e termina quando essa execução termina, as variáveis locais do subprograma estão todas visíveis para qualquer outro subprograma em execução, independente de sua proximidade textual ou de como a execução chegou ao subprograma atualmente em execução. Não há como proteger variáveis locais dessa acessibilidade. Os subprogramas são sempre executados no ambiente de todos os subprogramas previamente chamados que ainda não concluíram suas execuções. Como resultado, o escopo dinâmico resulta em programas menos confiáveis do que o escopo estático. Um segundo problema com o escopo dinâmico é a incapacidade de digitar referências de verificação a [variáveis] estaticamente não locais. Esse problema resulta da incapacidade de encontrar estaticamente a declaração de uma variável referenciada como não local. O escopo dinâmico também torna os programas mais difíceis de se ler, porque a sequência de chamadas dos subprogramas deve ser conhecida para determinar o significado das referências às variáveis não locais. Essa tarefa pode ser virtualmente impossível para um leitor humano. Finalmente, acessos a variáveis não locais em linguagens de escopo dinâmico gastam mais tempo que acessos a [variáveis] não locais quando o escopo estático é usado [...]. SEBESTA, 2016, p. 245-246. Por outro lado, o escopo dinâmico não é destituído de mérito. Em muitos casos, os parâmetros passados de um subprograma para outro são variáveis que estão definidas no chamador. Nenhuma delas precisa ser passada em uma linguagem de escopo dinâmico, porque estão implicitamente visíveis no subprograma chamado. Não é difícil entender por que o escopo dinâmico não é amplamente usado quanto o escopo estático. Programas em linguagens de escopo estático são mais fáceis de se ler, mais confiáveis, e executam mais rápido que programas equivalentes em linguagens de escopo dinâmico. Foi precisamente por essas razões que o escopo dinâmico foi substituído pelo escopo estático na maioria dos dialetos recentes de Lisp [...]. SEBESTA, 2016, p. 246. Escopo e Tempo de Vida. Às vezes, o escopo e o tempo de vida de uma variável parecem estar relacionados. Por exemplo, considere uma variável que esteja declarada em um método Java que não contenha chamadas a métodos. O escopo de tal variável vai da sua declaração até o fim do método. O tempo de vida daquela variável é o período de tempo que começa quando se entra no método e termina quando a execução do método termina. Embora o escopo e o tempo de vida da variável claramente não sejam o mesmo, porque o escopo estático é um conceito textual ou espacial, enquanto o tempo de vida é um conceito temporal, eles pelo menos parecem estar relacionados neste caso. Essa aparente relação entre escopo e tempo de vida não se sustenta em outras situações. Em C e C++, por exemplo, uma variável que está declarada em uma função usando o especificador static é estaticamente vinculada ao escopo dessa função e também é estaticamente vinculada ao armazenamento [na memória]. Então, seu escopo é estático e local para a função, mas seu tempo de vida se estende por toda a execução do programa do qual faz parte. SEBESTA, 2016, p. 246. Ambientes de Referenciamento.O ambiente de referenciamento de uma instrução é a coleção de todas as variáveis que estão visíveis na instrução. O ambiente de referenciamento de uma instrução em uma linguagem de escopo estático é as variáveis declaradas em seu escopo local mais a coleção de todas as variáveis de seus escopos ancestrais que estão visíveis. Em tal linguagem, o ambiente de referenciamento de uma instrução é necessário enquanto essa instrução está sendo compilada, então, estruturas de código e dados podem ser criadas para permitir referências a variáveis de outros escopos durante o tempo de execução [...]. Em Python, os escopos podem ser criados por definições de função. O ambiente de referenciamento de uma instrução inclui as variáveis locais, além de todas as variáveis declaradas nas funções nas quais a instrução está aninhada (excluindo variáveis em escopos não locais ocultos por declarações em funções mais próximas). Cada definição de função cria um novo escopo e, deste modo, um novo ambiente. SEBESTA, 2016, p. 247. Considere o seguinte programa esquelético de Python: g = 3; # A global def sub1(): a = 5; # Creates a local b = 7; # Creates another local . . . <------------------------------ 1 def sub2(): global g; # Global g is now assignable here c = 9; # Creates a new local . . . <------------------------------ 2 def sub3(): nonlocal c: # Makes nonlocal c visible here g = 11; # Creates a new local . . . <-------------------------- 3 Os ambientes de referenciamento dos pontos do programa indicado são os seguintes: Point Referencing Environment 1 local a and b (of sub1), global g for reference, but not for assignment 2 local c (of sub2), global g for both reference and for assignment 3 nonlocal c (of sub2), local g (of sub3) SEBESTA, 2016, p. 247. Constantes Nomeadas. Uma constante nomeada é uma variável que é vinculada a um valor apenas uma vez. Constantes nomeadas são úteis como auxílio para a legibilidade e a confiabilidade do programa. A legibilidade pode ser melhorada, por exemplo, usando o nome pi em vez da constante 3.14159265. Outro uso importante de constantes nomeadas é para parametrizar um programa. Por exemplo, considere um programa que processa uma quantidade fixa de valores de dados, digamos 100. Tal programa geralmente usa a constante 100 em uma quantidade de locais [diferentes], para declarar intervalos subscritos de arranjos e limites de controle de loops. [...] SEBESTA, 2016, p. 248-249. TIPOS DE DADOS PRIMITIVOS Os tipos de dados que NÃO são definidos em termos de outros tipos são chamados de tipos de dados primitivos. Praticamente todas as linguagens de programação fornecem um conjunto de tipos de dados primitivos. Alguns dos tipos primitivos são meramente reflexos do hardware - por exemplo, a maioria dos tipos inteiros. Outros requerem apenas um pouco de suporte não-hardware para a sua implementação. Para se especificar tipos estruturados, os tipos de dados primitivos de uma linguagem são usados junto com um ou mais construtores de tipo. SEBESTA, 2016, p. 262. Tipos numéricos Algumas das primeiras linguagens de programação só tinham tipos primitivos numéricos. Os tipos numéricos ainda desempenham um papel central entre as coleções de tipos suportados por linguagens contemporâneas. SEBESTA, 2016, p. 262. A seguir, são apresentados alguns tipos numéricos. Inteiro [(integer)]. O tipo de dados numérico primitivo mais comum é inteiro. O hardware de muitos computadores suporta vários tamanhos de inteiros. Esses tamanhos [...], e geralmente alguns outros, são suportados por algumas linguagens de programação. Por exemplo, o Java inclui quatro tamanhos inteiros SINALIZADOS [(signed - positivos e negativos)]: byte, short, int e long. Algumas linguagens, por exemplo, C++ e C#, incluem tipos inteiros NÃO SINALIZADO [(unsigned - apenas positivos)] [...]. Um valor inteiro sinalizado é representado em um computador por uma sequência de bits, com um dos bits (normalmente o mais à esquerda) representando o sinal. A maioria dos tipos inteiros é suportada diretamente pelo hardware. Um exemplo de um tipo inteiro que não é suportado diretamente pelo hardware é o tipo inteiro LONGO [(long)] do Python (F# também fornece tais inteiros). Valores desse tipo podem ter comprimento ilimitado. Valores inteiros longos podem ser especificados como literais, como no exemplo a seguir: 243725839182756281923L[.] Operações aritméticas inteiras no Python que produzem valores grandes demais para serem representados com o tipo int os armazenam como valores de tipo inteiro longo. Um inteiro negativo pode ser armazenado em notação de magnitude de sinal, no qual o bit de sinal é definido para indicar o sinal negativo e o restante da sequência de bits representa o valor absoluto do número. A notação de magnitude de sinal, contudo, não se presta à aritmética computacional. A maioria dos computadores agora usa uma notação chamada COMPLEMENTO DE DOIS [(twos complement)] para armazenar inteiros negativos, o que é conveniente para a adição e a subtração [...]. SEBESTA, 2016, p. 262-263. Ponto flutuante [(floating-point)]. Os tipos de dados de ponto flutuante modelam os números reais, mas as representações são APENAS APROXIMAÇÕES para muitos dos valores propriamente ditos [...]. Na maioria dos computadores, números do tipo ponto flutuante são armazenados em binário, o que agrava o problema. Por exemplo, mesmo o valor 0,1 em decimal não pode ser representado por um número finito de dígitos binários. Outro problema com os tipos de ponto flutuante é a perda de precisão em operações aritméticas [...]. Os valores de ponto flutuante são representados como frações e expoentes [...]. [...] [A maioria dos computadores mais novos] usa o formato padrão IEEE Floating-Point 754 [para representá-los]. Os implementadores de linguagem usam qualquer representação suportada pelo hardware. A maioria das linguagens inclui dois tipos de ponto flutuante, geralmente chamados de float e double. O tipo float é o tamanho padrão, geralmente armazenado em quatro bytes de memória. O tipo double é fornecido para situações em que partes fracionárias maiores e/ou intervalos de expoentes maiores são necessários. Variáveis de precisão dupla geralmente ocupam o dobro do espaço de armazenamento das variáveis float e fornecem pelo menos o dobro do número de bits da fração. A coleção de valores [...] representados por um tipo de ponto flutuante é definida em termos de precisão e intervalo. PRECISÃO é a precisão da parte fracionária de um valor, medida em termos de número de bits. INTERVALO é uma combinação do intervalo de frações e, mais importante, do intervalo de expoentes [...]. SEBESTA, 2016, p. 263. Complexos. Algumas linguagens de programação suportam um tipo de dados complexos - por exemplo, Fortran e Python. Valores complexos são representados como pares ordenados de valores de ponto flutuante [...]. As linguagens que suportam um tipo complexo incluem operações para a aritmética de valores complexos. Decimal [(decimal)]. A maioria dos computadores maiores projetados para oferecer suporte a aplicativos de sistemas de negócios possui suporte de hardware para tipos de dados decimais. Tipos de dados decimais armazenam um número fixo de dígitos decimais, com o ponto decimal implícito em uma posição fixa no valor. Esses são os principais tipos de dados para o processamento de dados de negócios e, portanto, são essenciais para COBOL. C# e F# também possuem tipos de dados decimais. Tipos decimais têm a vantagem de ser capaz de armazenar precisamente valores decimais, pelo menos aqueles dentro de um intervalo restrito, o que não pode ser feito com ponto flutuante. Por exemplo, o número 0,1 (em decimal) pode ser exatamente representado em um tipo decimal, mas não em um tipo de ponto flutuante [...]. As desvantagens dos tipos decimais são que o intervalo de valores é restrito, porque nenhum expoente é permitido,e sua representação na memória é ligeiramente perdulária [...]. SEBESTA, 2016, p. 264. Tipos booleanos Os tipos booleanos são talvez os mais simples de todos os tipos. Seu intervalo de valores tem apenas dois elementos: um para verdadeiro e um para falso. Eles foram introduzidos no ALGOL 60 e incluídos na maioria das linguagens de propósito geral projetadas desde 1960. Uma exceção popular é C89, em que expressões numéricas são usadas como condicionais. Em tais expressões, todos os operandos com valores diferentes de zero são considerados verdadeiros e zero é considerado falso. Embora C99 e C++ tenham um tipo booleano, eles também permitem que expressões numéricas sejam usadas como se fossem booleanas. Este não é o caso das linguagens subsequentes, Java e C#. Tipos booleanos são frequentemente usados para representar switches ou flags em programas. Embora outros tipos, como inteiros, possam ser usados para esses propósitos, o uso de tipos booleanos é mais legível. Um valor booleano pode ser representado por um único bit, mas pelo fato de um único bit de memória não pode ser acessado de modo eficiente em muitas máquinas, elas são frequentemente armazenadas na menor célula de memória eficientemente endereçável, normalmente um byte. SEBESTA, 2016, p. 265. Tipos caracteres Os dados de caracteres são armazenados em computadores como códigos numéricos. Tradicionalmente, a codificação mais usada era o código de 8 bits ASCII (American Standard Code for Information Interchange), empregando os valores 0 a 127 para codificar 128 caracteres diferentes. O ISO 8859-1 é outro código de caracteres de 8 bits, mas permite 256 caracteres diferentes. Devido à globalização dos negócios e à necessidade de os computadores comunicarem- se entre si em todo o mundo, o conjunto de caracteres ASCII tornou-se inadequado. Em resposta, em 1991, o Unicode Consortium publicou o padrão UCS-2, um conjunto de caracteres de 16 bits, frequentemente chamado de Unicode. O Unicode inclui os caracteres da maioria dos idiomas naturais do mundo, por exemplo, o alfabeto cirílico, usado na Sérvia, e os dígitos tailandeses. Os primeiros 128 caracteres do Unicode são idênticos aos do ASCII. O Java foi a primeira linguagem popular a usar o [...] Unicode, desde então, seguida por JavaScript, Python, Perl, C# e F#. Depois de 1991, o Unicode Consortium, em cooperação com a International Standards Organization (ISO), desenvolveu um código de 4 bytes chamado UCS-4, ou UTF-32, descrito na norma ISO/IEC 10646, publicada em 2000. Para fornecer os meios de processar codificações de caracteres únicos, a maioria das linguagens de programação inclui um tipo primitivo para elas. No entanto, o Python suporta caracteres únicos apenas como sequências de caracteres de comprimento 1. SEBESTA, 2016, p. 265-266. TIPOS DE DADOS DE CADEIAS DE CARACTERES (STRINGS) Um tipo de cadeia de caracteres [(string)] é aquele em que os valores consistem em sequências de caracteres. As constantes de cadeia de caracteres são usadas para rotular a saída e a entrada e saída de todos os tipos de dados se dão frequentemente em termos de seqüências de caracteres. Naturalmente, as cadeias de caracteres também são um tipo essencial para todos os programas que fazem manipulação de caracteres. SEBESTA, 2016, p. 266. Questões de projeto. As duas questões de projeto mais importantes, específicas para tipos de cadeia de caracteres, são as seguintes: • As sequências devem ser um tipo especial de arranjo de caracteres ou um tipo primitivo? • As strings devem ter comprimento estático ou dinâmico? SEBESTA, 2016, p. 266. Cadeias de caracteres e suas operações. As operações de strings mais comuns são ATRIBUIÇÃO, CONCATENAÇÃO, REFERÊNCIA DE SUBSTRING, COMPARAÇÃO E CORRESPONDÊNCIA DE PADRÕES. Uma referência de substring é uma referência a uma substring de determinada string. As referências de substring são discutidas no contexto mais geral de arranjos, em que são chamadas de fatias [(slices)]. Em geral, as operações de atribuição e comparação em cadeias de caracteres são complicadas pela possibilidade de haver operandos de strings de diferentes comprimentos. Por exemplo, o que acontece quando uma string mais longa é atribuída a uma string mais curta ou vice- versa? Geralmente, escolhas simples e sensatas são feitas nessas situações, embora os programadores frequentemente tenham dificuldade de lembrar delas. Em algumas linguagens, a correspondência de padrões é suportada diretamente. Em outras, é fornecida por meio de uma função ou biblioteca de classes. SEBESTA, 2016, p. 266. Se as strings não forem definidas como um tipo primitivo, os dados da string serão geralmente armazenados em arranjos de caracteres únicos e referenciados como tal na linguagem. Essa é a abordagem adotada em C e C++, que usam arranjos do tipo char para armazenar cadeias de caracteres. Essas linguagens fornecem uma coleção de operações de strings por meio de bibliotecas padrão. Muitos usuários de strings e muitas das funções de biblioteca usam a convenção de que as cadeias de caracteres são terminadas com um caractere especial, null [(\0)], representado por zero. Esta é uma alternativa para manter o comprimento das variáveis do tipo string. As operações de biblioteca simplesmente executam suas operações até que o caractere nulo apareça na string que está sendo operada. Funções de biblioteca que produzem strings frequentemente fornecem o caractere nulo. SEBESTA, 2016, p. 266. Opções de comprimento de cadeias de caracteres (strings). Existem várias escolhas de projeto relacionadas ao comprimento dos valores de strings. Primeiro, o comprimento pode ser estático e definido quando a string é criada. Tal string é chamada de STRING DE COMPRIMENTO ESTÁTICO. Essa é a escolha para as strings do Python, os objetos imutáveis da classe String do Java, bem como das classes similares da biblioteca de classes padrão do C++, a classe String interna do Ruby e a biblioteca de classes .NET disponível para C# e F#. A segunda opção é permitir que as strings tenham comprimento variável até o máximo declarado e fixo, determinado na definição da variável, como exemplificado pelas strings em C e pelas strings do tipo C em C++. Essas são chamadas de STRINGS DE COMPRIMENTO DINÂMICO LIMITADO. Tais variáveis do tipo string podem armazenar qualquer quantidade de caracteres entre zero e o máximo [...]. A terceira opção é permitir que as strings tenham comprimento variável sem máximo, como em JavaScript, Perl e na biblioteca de C++ padrão. Essas são chamadas de STRINGS DE COMPRIMENTO DINÂMICO. Essa opção requer o custo de alocação e desalocação dinâmica de armazenagem [de memória], mas oferece flexibilidade máxima. SEBESTA, 2016, p. 268-269. Avaliação. Tipos de strings são importantes para a capacidade de escrita de uma linguagem. Lidar com strings como arranjos pode ser mais complicado do que com um tipo de string primitivo. Por exemplo, considere uma linguagem que trata strings como arranjos de caracteres e não possui uma função predefinida que faça o mesmo que strcpy do C. Então, a simples atribuição de uma string a outra exigiria um loop. A adição de strings como um tipo primitivo para uma linguagem não é dispendiosa em termos de complexidade de linguagem ou compilador. Portanto, é difícil justificar a omissão de tipos primitivos de strings em algumas linguagens contemporâneas. Naturalmente, fornecer strings por meio de uma biblioteca padrão é quase tão conveniente quanto tê- las como um tipo primitivo. As operações de strings, tais como a correspondência de padrão simples e concatenação, são essenciais e devem ser incluídas para valores do tipo string. Embora as strings de comprimento dinâmico sejam obviamente as mais flexíveis, o custo de sua implementação deve ser confrontado com essa flexibilidade adicional. SEBESTA, 2016, p. 269. Implementação de tipos de string de caracteres.Os tipos de strings de caracteres poderiam ser suportados diretamente no hardware; mas, na maioria dos casos, o software é usado para implementar o armazenamento, a recuperação e a manipulação de strings. Quando os tipos de strings de caracteres são representados como arranjos de caracteres, a linguagem frequentemente fornece poucas operações. [...] As strings dinâmicas limitadas de C e C++ não exigem descritores de tempo de execução, porque o final de uma string é marcado com o caractere nulo. Eles não precisam do comprimento máximo, porque os valores de índice nas referências do arranjo não são verificados no intervalo dessas linguagens. As strings de comprimento estático e de comprimento dinâmico limitado não requerem alocação de armazenamento dinâmico especial. No caso de strings de comprimento dinâmico limitado, o armazenamento suficiente para o comprimento máximo é alocado quando a variável do tipo string é vinculada ao armazenamento, então somente um único processo de alocação está envolvido. As strings de comprimento dinâmico exigem gerenciamento de armazenamento mais complexo. O comprimento de uma string e, portanto, o armazenamento ao qual ela está vinculada, deve aumentar e diminuir dinamicamente. SEBESTA, 2016, p. 269-270. Há três abordagens para suportar alocação e desalocação dinâmicas de strings de comprimento dinâmico. Primeiro, elas podem ser armazenadas em uma lista encadeada, de modo que, quando crescer, as células recém-requeridas possam vir de qualquer lugar da pilha. As desvantagens são o armazenamento extra dos encadeamentos da lista e a complexidade necessária das operações de string. A segunda é armazenar strings como arranjos de ponteiros para caracteres individuais, alocados na memória heap. Esse método ainda usa memória extra, mas o processamento pode ser mais rápido do que na abordagem de lista encadeada. A terceira alternativa é armazenar strings completas em células de armazenamento adjacentes. O problema surge quando a string cresce: como o armazenamento adjacente às células existentes pode continuar a ser alocado [...]? Frequentemente, esse armazenamento não está disponível. Em vez disso, é encontrada uma nova área de memória que possa armazenar a nova string completa e a parte antiga é movida para essa área. Então, as células de memória usadas para a string antiga são desalocadas. Esta última abordagem é a normalmente usada [...]. Embora o método de lista encadeada exija mais armazenamento, os processos de alocação e desalocação associados são simples. Contudo, algumas operações de string são atrasadas por causa da busca do ponteiro requerido. Por outro lado, usar memória adjacente para strings completas resulta em operações mais rápidas e requer significativamente menos armazenamento, mas os processos de alocação e desalocação são mais lentos. SEBESTA, 2016, p. 270-271. TIPOS DE ENUMERAÇÃO Um tipo de enumeração é aquele no qual todos os valores possíveis, que são constantes nomeadas, são fornecidos, ou enumerados, na definição. Os tipos de enumeração fornecem um modo de definir e agrupar coleções de constantes nomeadas, chamadas de constantes de enumeração. A definição de um tipo de enumeração típico é mostrada no seguinte exemplo C#: enum days { Mon, Tue, Wed, Thu, Fri, Sat, Sun }; As constantes de enumeração, em geral, são explicitamente atribuídas a valores inteiros: 0, 1..., mas pode ser explicitamente atribuída a qualquer literal inteira, na definição do tipo. SEBESTA, 2016, p. 271. Questões de projeto. As questões de projeto para tipos de enumeração são os seguintes: • É permitida que uma constante de enumeração apareça em mais de uma definição de tipo e, em caso afirmativo, como é o tipo de uma ocorrência dessa constante na verificação do programa? • Valores de enumeração são forçados para o tipo inteiro? • Existem outros tipos forçados para um tipo de enumeração? Todos essas questões de projeto estão relacionadas à verificação de tipos. Se uma variável de enumeração for forçada para um tipo numérico, então haverá pouco controle sobre seu intervalo legal de operações ou seu intervalo de valores. Se um valor de tipo int for forçado para um tipo de enumeração, então uma variável de tipo de enumeração poderia ser atribuída a qualquer valor inteiro, independentemente de representar uma constante de enumeração ou não. SEBESTA, 2016, p. 271. Projetos. Em linguagens que não possuem tipos de enumeração, os programadores geralmente os simulam com valores inteiros. Por exemplo, suponha que precisássemos representar cores em um programa C e o C não tivesse um tipo de enumeração. Poderíamos usar 0 para representar azul, 1 para representar vermelho e assim por diante. Esses valores podem ser definidos da seguinte maneira: int red = 0, blue = 1; Agora, no programa, poderíamos usar vermelho e azul como se fosse de um tipo de cor. A problema com essa abordagem é que, pelo fato de não termos definido um tipo propriamente dito para nossas cores, não há verificação de tipo quando forem utilizadas [...]. C e Pascal foram as primeiras linguagem amplamente utilizadas a incluir tipos de dados de enumeração. O C++ inclui tipos de enumeração do C. SEBESTA, 2016, p. 271-272. Avaliação. Os tipos de enumeração podem fornecer vantagens quanto a legibilidade e confiabilidade. A legibilidade é aprimorada muito diretamente: os valores nomeados são reconhecidos facilmente, enquanto os valores codificados não. Na área da confiabilidade, os tipos de enumeração de C#, F# e Java 5.0 fornecem duas vantagens: (1) nenhuma das operações aritméticas é válida em tipos de enumeração; isso previne a adição de dias da semana, por exemplo, e (2) [...] a nenhuma variável de enumeração pode ser atribuído um valor de fora do seu intervalo definido. Se o tipo de enumeração cores tiver 10 constantes de enumeração e usar 0..9 como valores internos, nenhum número maior que 9 pode ser atribuído a uma variável deste tipo. Pelo fato de o C tratar variáveis de enumeração como variáveis inteiras, ele não fornece nenhuma dessas duas vantagens. C++ é um pouco melhor. Valores numéricos podem ser atribuídos a variáveis do tipo enumeração somente se forem convertidos para o tipo da variável atribuída. Valores numéricos atribuídos a variáveis do tipo enumeração são verificados para determinar se estão no intervalo dos valores internos do tipo de enumeração. Infelizmente, se o usuário usar um intervalo amplo de valores explicitamente atribuídos, essa verificação não será efetiva [...]. SEBESTA, 2016, p. 273. TIPOS DE ARRANJOS (VETORES E MATRIZES) Um arranjo (array) é um agregado homogêneo de elementos de dados no qual um elemento individual é identificado por sua posição no agregado, em relação ao primeiro elemento. Os elementos de dados individuais de um arranjo são do mesmo tipo. Referências a elementos de arranjos individuais são especificadas usando expressões subscritas. Se quaisquer das expressões subscritas de uma referência incluir variáveis, então a referência exigirá um cálculo adicional em tempo de execução para determinar o endereço da localização de memória que está sendo referenciada. Em muitas linguagens, como C, C ++, Java e C#, todos os elementos de um arranjo precisam ser do mesmo tipo. Nessas linguagens, os ponteiros e as referências são restritos para apontar ou referenciar um único tipo. Então, os objetos ou valores de dados que estão sendo apontados ou referenciados também são de um único tipo. Em algumas outras linguagens, como JavaScript, Python e Ruby, as variáveis são referências sem tipo a objetos ou valores de dados. Nesses casos, os arranjos ainda consistem em elementos de um único tipo, mas tais elementos podem referenciar objetos ou valores de dados de tipos diferentes. Esses arranjos ainda serão homogêneas, porque seus elementos serão do mesmo tipo. C# e Java 5.0 fornecem arranjos genéricos, isto é, arranjos cujos elementossão referências a objetos, por meio de suas bibliotecas de classes. SEBESTA, 2016, p. 274. Questões de Projeto. As principais questões de projeto específicas para arranjos são as seguintes: • Quais tipos são válidos para os subscritos? • As expressões de subscrição no intervalo de referências de elementos são verificadas? • Quando os intervalos dos subscritos são vinculados? • Quando ocorre a alocação do arranjo? • São permitidos arranjos multidimensionais irregulares ou retangulares, ou ambos? • Os arranjos podem ser inicializados quando tiverem seu armazenamento alocado? • Quais tipos de fatias são permitidos, se houver? SEBESTA, 2016, p. 274. Arranjos e Índices. Os elementos específicos de um arranjos são referenciados por meio de um mecanismo sintático de dois níveis, em que a primeira parte é o nome agregado e a segunda parte é um seletor possivelmente dinâmico que consiste em um ou mais itens, conhecidos como subscritos ou índices. Se todos os índices de uma referência forem constantes, o seletor será estático; caso contrário, será dinâmico. A operação de seleção pode ser considerada como um mapeamento do nome do arranjo e do conjunto de valores de índices para um elemento da agregação. De fato, os arranjos são às vezes chamados de mapeamentos finitos. Simbolicamente, esse mapeamento pode ser mostrado como nomeDoArranjo(listaDeValoresSubscritos) --> elemento SEBESTA, 2016, p. 274-275. A sintaxe das referências do arranjo é razoavelmente universal: o nome do arranjo é seguido pela lista de índices, envolvida por PARÊNTESES OU COLCHETES. Em algumas linguagens que fornecem arranjos multidimensionadas como arranjos de arranjos, cada índice aparece em seus próprios colchetes. Um problema com o uso de parênteses para incluir expressões subscritas é que elas frequentemente são usadas também para incluir os parâmetros de chamadas a subprogramas; esse uso faz com que as referências a arranjos se pareçam exatamente como essas chamadas. Por exemplo, considere a seguinte declaração de atribuição Ada: Soma := Soma + B(I); Pelo fato de os parênteses serem usados para parâmetros de subprogramas e para os índices de arranjos no Ada, os leitores do programa e os compiladores são forçados a usar outras informações para determinar se B(I) nessa atribuição é uma chamada de função ou uma referência a um elemento do arranjo. Isso resulta em legibilidade reduzida. [...] A maioria das linguagens que não o Fortran e o Ada usa colchetes para delimitar seus índices de arranjos. SEBESTA, 2016, p. 275. Vinculações dos Índices e Categorias de Arranjos. A vinculação do tipo de índice a uma variável do arranjo é geralmente estática, mas os intervalos de valores dos índices são às vezes vinculados dinamicamente. Em algumas linguagens, o limite inferior do intervalo dos índices é implícito. Por exemplo, nas linguagens baseadas em C, o limite inferior de todos os intervalos dos índices é fixado em 0. Em algumas outras linguagens, os limites inferiores dos intervalos dos índices devem ser especificados pelo programador [...]. Um arranjo estático (static array) é aquele no qual os intervalos dos índices são estaticamente vinculados e a alocação de armazenamento é estática (feita antes do tempo de execução). A vantagem dos arranjos estáticos é a eficiência: nenhuma alocação ou desalocação dinâmica é necessária. A desvantagem é que o armazenamento para o arranjo é fixo durante todo o tempo de execução do programa. Um arranjo dinâmico de pilha fixa (fixed stack-dynamic array) é aquele em que os intervalos dos índices são estaticamente vinculados, mas a alocação é feita no momento da elaboração da declaração, durante a execução. A vantagem dos arranjos dinâmicos de pilha fixa sobre os arranjos estáticos é a eficiência do espaço. Um arranjo grande em um subprograma pode usar o mesmo espaço de um arranjo grande em um subprograma diferente, desde que ambos os subprogramas não estejam ativos ao mesmo tempo [...]. A desvantagem é o tempo necessário de alocação e desalocação. SEBESTA, 2016, p. 276. Um arranjo dinâmico de monte fixo (fixed heap-dynamic array) é semelhante a um arranjo dinâmico de pilha fixa (fixed stack-dynamic array), em que os intervalos dos índices e a vinculação de armazenamento são fixos após o armazenamento ser alocado. As diferenças são que os intervalos dos índices e do armazenamento são feitos quando o programa usuário os solicita durante a execução e o armazenamento é alocado no monte (heap), em vez de na pilha (stack). A vantagem dos arranjos dinâmicos de monte fixo é a flexibilidade: o tamanho do arranjo sempre se ajusta ao problema. A desvantagem é o tempo de alocação do monte (heap), que é maior que o tempo de alocação da pilha (stack). Um arranjo dinâmico de monte (heap-dynamic array) é aquele em que a vinculação de intervalos dos índices e a alocação de armazenamento é dinâmica e pode ser alterada em qualquer quantidade de vezes durante a vida útil do arranjo. A vantagem dos arranjos dinâmicos de monte sobre os outros é a flexibilidade: os arranjos podem crescer e diminuir durante a execução do programa, a medida que a necessidade de espaço é alterada. A desvantagem é que a alocação e a desalocação demoram mais e podem acontecer muitas vezes durante a execução do programa [...]. SEBESTA, 2016, p. 276. Inicialização de Arranjos. Algumas linguagens fornecem meios para inicializar arranjos no momento em que seu armazenamento é alocado. C, C ++, Java e C# permitem a inicialização de seus arranjos. Considere a seguinte declaração em C: int list [] = { 4, 5, 7, 83 }; A lista do arranjo é criada e inicializada com os valores 4, 5, 7 e 83. O compilador também define o comprimento do arranjo. Esta deve ser uma conveniência, mas não sem custo. Ela efetivamente remove a possibilidade de o sistema detectar alguns tipos de erros do programador, como, por engano, deixar um valor fora da lista. SEBESTA, 2016, p. 278. [...] Cadeias de caracteres em C e C++ são implementadas como arranjos de char. Esses arranjos podem ser inicializados com constantes do tipo string, como em char name [] = "freddie"; O arranjo name terá oito elementos, porque todas as strings são terminadas com um caractere nulo (zero), que é fornecido implicitamente pelo sistema para constantes do tipo string. Arranjos de strings em C e C++ também podem ser inicializadas com literais do tipo string. Por exemplo, char *names [] = { "Bob", "Jake", "Darcie" }; Este exemplo ilustra a natureza das literais de caracteres em C e C++. No exemplo anterior, de uma string literal sendo usada para inicializar o arranjo do tipo char name, a literal é considerada como uma arranjo do tipo char. Mas, no último exemplo (names), as literais são consideradas como ponteiros para caracteres, então, o arranjo é um arranjo de ponteiros para caracteres. Por exemplo, names[0] é um ponteiro para a letra 'B' no arranjo de caracteres literais que contém os caracteres 'B', 'o', 'b' e o caractere nulo. Em Java, uma sintaxe semelhante é usada para definir e inicializar uma matriz de referências a objetos String. Por exemplo, String [] nomes = ["Bob", "Jake", "Darcie"]; SEBESTA, 2016, p. 278. Operações de Arranjos. Uma operação de arranjos é aquela que opera em um arranjo como uma unidade e as mais comuns são ATRIBUIÇÃO, CONCATENAÇÃO, COMPARAÇÃO PARA IGUALDADE E DESIGUALDADE, e FATIAS [...]. As linguagens baseadas em C não fornecem nenhuma operação de arranjo, exceto por meio dos métodos de Java, C++ e C#. Perl suporta atribuições de arranjos, mas não oferece suporte a comparações. Os arranjos do Python são chamados de listas, embora tenham todas as características dos arranjos dinâmicos [...]. Tal como o Python, os elementos dos arranjos do Ruby são referências a objetos [...]. F# inclui muitos operadores de arranjos em seu módulo Array. Entre eles estão Array.append, Array.copy e Array.length. SEBESTA, 2016,
Compartilhar