Linguagens e paradigmas de programação

Acompanhe um programa desde o código escrito por uma pessoa até as instruções binárias executadas por um processador e veja como compiladores, interpretadores, bytecode e compiladores em tempo de execução lidam com essa tradução. Aprenda a distinguir a sintaxe de uma linguagem de seu significado, como as linguagens verificam tipos de dados e onde um programa em execução mantém seus dados e libera sua memória. Depois, compare quatro formas de escrever programas (imperativa, orientada a objetos, funcional e declarativa), resolvendo os mesmos problemas em cada uma, e aprenda a escolher uma linguagem adequada a um projeto.

BásicoAula 5 de 7 do curso Curso de Introdução à ciência da computação

O que você vai aprender

  1. Acompanhe um programa desde o código escrito por uma pessoa até as instruções binárias executadas por um processador e compare as formas de fazer essa tradução: antecipadamente, durante a execução do programa ou combinando as duas.
    • Comparar código de máquina, linguagem de montagem e linguagens de alto nível
      Descreva os três níveis em que programas podem ser escritos, mostre como uma linha de código de alto nível corresponde a várias instruções de máquina e explique por que a maior parte do software é escrita no nível mais alto.
    • Explicar o que um compilador faz
      Descreva como um compilador traduz um programa inteiro antecipadamente, nomeie suas principais etapas e explique o que acontece com os erros e com o executável concluído.
    • Explicar interpretadores, bytecode e compilação em tempo de execução
      Descreva como um interpretador executa um programa, como linguagens como Java e Python combinam compilação e interpretação e como avaliar os compromissos entre essas abordagens.
  2. Separe as regras de como o código deve ser escrito das regras sobre o que ele faz, reconheça as estruturas básicas compartilhadas pela maioria das linguagens de alto nível e acompanhe programas curtos para prever seus resultados.
    • Distinguir sintaxe de semântica
      Explique a diferença entre a forma de um programa e seu significado, diferencie erros de sintaxe, em tempo de execução e de lógica e identifique quando linguagens atribuem significados diferentes a códigos de mesma aparência.
    • Rastrear a execução das estruturas básicas comuns aos programas de alto nível
      Reconheça as estruturas encontradas em quase todas as linguagens de alto nível (variáveis, expressões, fluxo de controle e funções com seus escopos) e acompanhe um programa curto passo a passo para prever sua saída.
  3. Descreva como as linguagens classificam e verificam valores por tipo, onde um programa em execução mantém seus dados e como a memória é reservada e liberada, por quem programa ou automaticamente, com os compromissos de segurança e velocidade de cada escolha.
    • Comparar como as linguagens usam e verificam tipos de dados
      Explique o que é um tipo de dado, compare a tipagem estática e dinâmica e as regras de conversão estritas e flexíveis, e preveja como linguagens comuns tratam a mistura de tipos e os limites numéricos.
    • Descrever onde um programa em execução mantém seus dados
      Explique variáveis como posições de memória nomeadas, as funções da pilha de chamadas e da área de alocação dinâmica e a diferença entre copiar um valor e compartilhar uma referência.
    • Comparar o gerenciamento manual e automático de memória
      Explique como a memória é liberada em linguagens nas quais a pessoa que programa a gerencia e em linguagens com coleta de lixo ou regras de propriedade de valores, e relacione erros de memória a falhas de programas e vulnerabilidades de segurança.
  4. Descreva programas como sequências de comandos que alteram o estado armazenado e depois como conjuntos de objetos que reúnem dados e comportamento, e use classes, encapsulamento, herança e polimorfismo para organizar o código.
    • Descrever os estilos imperativo e procedural
      Explique a programação imperativa como comandos passo a passo que alteram o estado de um programa, mostre como procedimentos e fluxo de controle estruturado organizam esses comandos e reconheça os riscos de um estado compartilhado que muda.
    • Modelar dados e comportamentos com classes e objetos
      Explique como a programação orientada a objetos agrupa dados e as operações sobre eles em objetos, leia e projete uma classe simples e use o encapsulamento para proteger os dados de um objeto.
    • Reutilizar e estender classes com herança, polimorfismo e composição
      Use herança para criar classes especializadas, explique como o polimorfismo permite que uma mesma chamada se comporte de maneira diferente para objetos diferentes e decida quando combinar objetos em vez de usar herança.
  5. Crie programas com funções que retornam novos valores em vez de alterar os antigos e escreva programas que declaram o resultado desejado enquanto um mecanismo da linguagem decide como calculá-lo.
    • Criar programas com funções puras e dados imutáveis
      Explique funções puras, imutabilidade, funções de primeira classe e de ordem superior e recursão, e reescreva um laço como uma sequência de operações map, filter e reduce.
    • Descrever o resultado com linguagens declarativas
      Explique a programação declarativa como a descrição do resultado desejado, em vez de como obtê-lo, e leia exemplos em uma linguagem de consulta a bancos de dados, uma linguagem lógica e outras notações declarativas.
  6. Resolva o mesmo problema em estilos diferentes, reconheça que a maioria das linguagens modernas combina paradigmas e avalie os fatores técnicos e práticos que determinam qual linguagem é adequada a um projeto.
    • Resolver um problema usando vários paradigmas
      Escreva a mesma pequena tarefa nos estilos imperativo, orientado a objetos, funcional e declarativo, compare o que cada versão facilita e reconheça linguagens multiparadigma.
    • Escolher uma linguagem de programação para uma tarefa
      Avalie os fatores que determinam qual linguagem é adequada a um projeto (plataforma, domínio, desempenho, segurança, ecossistema e pessoas) e justifique uma escolha para um cenário concreto.

Guia de estudo de Paradigmas de programação

6 capítulos

1

Do código-fonte às instruções em execução

Um processador executa pequenas instruções binárias, enquanto as pessoas geralmente escrevem programas em linguagens construídas em torno do problema que querem resolver. Tradutores conectam esses dois níveis. Acompanhe o mesmo cálculo por essas camadas e depois compare a construção de um programa antes de sua execução com sua interpretação ou com a compilação de partes durante a execução. O caminho afeta a portabilidade, o tempo de inicialização, a velocidade e o momento em que um erro pode aparecer.

1.1 Três níveis de código de programa

Uma linha como x = a + b indica um cálculo sem mencionar um registrador do processador. A abstração torna isso possível: uma linguagem oculta detalhes de níveis inferiores para que quem programa possa se concentrar no trabalho que o programa deve fazer.

O que o processador executa

O código de máquina consiste em instruções codificadas como números binários. A arquitetura do conjunto de instruções (ISA) de um processador define suas instruções e suas codificações. x86-64 é comum em PCs e servidores; ARM é comum em celulares, tablets e muitos laptops. Código construído para uma ISA precisa de tradução ou emulação para funcionar em outra.

O sistema hexadecimal torna mais curta a exibição dos bytes de instruções. Em x86-64, B8 01 00 00 00 coloca 1 no registrador EAX. Os bytes 01 D8 somam o valor em EBX ao EAX. Registradores são pequenas posições de armazenamento dentro do processador. Cada instrução realiza uma pequena ação, como mover dados, somar, comparar ou saltar. O ciclo de busca, decodificação e execução executa essas instruções.

Nomes para as mesmas pequenas ações

A linguagem de montagem substitui os bytes das instruções por nomes legíveis chamados mnemônicos. Aquelas duas instruções x86-64 podem ser escritas:

mov eax, 1
add eax, ebx

Um montador converte a linguagem de montagem em código de máquina. Uma linha de instrução comum normalmente produz uma instrução de máquina. Os nomes dos registradores e as instruções ainda pertencem a uma ISA específica, então usar palavras em vez de bytes não torna a linguagem de montagem portável.

Descreva um problema em menos linhas

Uma linguagem de alto nível oferece variáveis, expressões, condições, laços e funções. Python, C, Java e JavaScript permitem descrever operações sem selecionar registradores. Uma instrução normalmente exige várias instruções de máquina. Uma tradução simplificada de x = a + b faria o seguinte:

  1. Carregar o valor de a em um registrador.
  2. Carregar o valor de b em outro registrador.
  3. Somar os valores dos registradores.
  4. Armazenar o resultado para x.

Um compilador real pode combinar ou reorganizar esses passos mantendo o comportamento exigido.

NívelO que a pessoa escreveO que cuida dos detalhes do hardware?
Código de máquinaInstruções codificadas do processadorA pessoa que programa ou o gerador de código
Linguagem de montagemMnemônicos e registradoresA pessoa que programa, com um montador codificando as instruções
Código de alto nívelVariáveis e operações sobre os dados do problemaUm compilador, um interpretador ou uma combinação dos dois

O código-fonte de alto nível é mais fácil de ler, alterar e verificar. Um código-fonte C portável pode ser compilado uma vez para um sistema x86-64 e novamente para um sistema ARM. Cada construção ainda precisa do suporte e das bibliotecas corretos do sistema operacional. A portabilidade do código-fonte não torna um executável nativo universal.

Por que os níveis mais baixos ainda importam

A linguagem de montagem continua útil em código de inicialização, algumas rotinas de sistemas operacionais e drivers, pequenos sistemas embarcados e análises de segurança de programas cujo código-fonte não está disponível. Para código de aplicativos comuns, compiladores modernos geralmente produzem código de máquina com desempenho comparável ao da linguagem de montagem escrita à mão.

A mudança para níveis mais altos ocorreu gradualmente. A linguagem de montagem se difundiu no fim da década de 1940 e no início da década de 1950. O sistema A-0 de Grace Hopper, em 1952, ajudou a avançar a tradução automática. A IBM lançou o FORTRAN em 1957; o COBOL veio em 1959, e o Lisp surgiu por volta de 1958–1960. “Alto nível” indica a distância dos detalhes do hardware, não a dificuldade de aprender a linguagem.

Principais conclusões

  • Um processador executa instruções de máquina definidas por sua ISA.
  • Um montador codifica instruções de montagem; a linguagem de montagem continua vinculada a uma ISA.
  • Instruções de alto nível ocultam passos do hardware e geralmente se tornam várias instruções.
  • Código-fonte portável pode ser traduzido para diferentes alvos; o código de máquina nativo é específico do alvo.
  • Código de baixo nível continua útil quando o acesso ao hardware ou sua análise o exige.

1.2 Como um compilador constrói um programa

Um compilador traduz código-fonte em código-alvo. O alvo pode ser código de máquina nativo ou uma forma intermediária, como bytecode. A compilação antecipada (AOT) acontece antes da execução do programa; a compilação em tempo de execução acontece durante a execução.

Do texto às instruções

Em uma construção antecipada, várias etapas transformam um arquivo-fonte em instruções:

  1. A análise léxica divide o texto em unidades léxicas: partes com significado, como nomes, operadores e números.
  2. A análise sintática verifica a gramática e constrói uma estrutura que mostra como as partes se encaixam.
  3. A análise semântica verifica regras de significado, incluindo declarações e combinações de tipos permitidas.
  4. A otimização encontra uma maneira mais eficiente de realizar o trabalho exigido.
  5. A geração de código produz as instruções-alvo.

Considere a instrução C total = price * 3;. Suas seis unidades léxicas são total, =, price, *, 3 e ;. Os nomes são identificadores, 3 é um literal inteiro e o ponto e vírgula encerra a instrução. A análise sintática registra que a multiplicação faz parte da atribuição: calcule price * 3 e depois armazene seu valor em total.

Essas etapas têm funções diferentes. Uma sequência de unidades léxicas corretamente escritas ainda pode violar a gramática. Uma instrução gramaticalmente correta ainda pode usar um tipo de maneira incorreta. Encontrar as unidades léxicas não basta para estabelecer que um programa é válido.

Faça o mesmo trabalho com menos recursos

Suponha que o código-fonte contenha seconds_per_day = 60 * 60 * 24. Os fatores são constantes, então o compilador pode multiplicar 60 por 60 para obter 3600 e depois multiplicar por 24 para obter 86400 durante a construção. Ele pode colocar o valor final na saída em vez de repetir a multiplicação em tempo de execução. Isso é dobramento de constantes.

Uma otimização deve preservar o comportamento exigido pela linguagem. Ela não pode substituir um cálculo por outro mais rápido que produza resultados diferentes dos exigidos. O objetivo é reduzir trabalho, tempo ou memória, mantendo o significado do programa.

Reúna as partes

Programas grandes geralmente são construídos a partir de muitos arquivos. Um compilador pode produzir um arquivo objeto para cada arquivo-fonte. Um arquivo objeto contém código traduzido que ainda pode precisar de conexões com outros códigos. Um ligador reúne as partes e resolve chamadas a rotinas de bibliotecas, como funções de impressão ou matemáticas.

Método de ligaçãoOrigem do código da biblioteca em tempo de execução
Ligação estáticaCódigo incluído no executável durante a construção
Ligação dinâmicaUma biblioteca compartilhada compatível carregada na inicialização ou durante a execução

O executável resultante é construído para uma plataforma-alvo, incluindo uma ISA e convenções de sistema operacional. O usuário não precisa do compilador antecipado para executá-lo. Bibliotecas compartilhadas ou componentes de execução ainda podem ser necessários.

Entenda o que uma construção bem-sucedida demonstra

Em Java, int count = "five"; falha em uma verificação de tipo antes da execução. Texto não pode inicializar essa variável inteira. O programa deve passar pelas verificações exigidas em tempo de compilação antes que uma versão executável esteja disponível.

Outras falhas sobrevivem à compilação. A divisão por um zero informado pelo usuário depende dos dados em tempo de execução. Um programa que deveria subtrair, mas soma, pode ser executado e retornar o valor errado. A compilação verifica as regras da linguagem; os testes também devem verificar se o resultado corresponde à tarefa pretendida.

Principais conclusões

  • A compilação traduz código-fonte em código-alvo, que não precisa ser código de máquina.
  • As etapas do compilador verificam unidades léxicas, estrutura e significado antes de produzir instruções-alvo.
  • A otimização preserva o comportamento exigido; o dobramento de constantes avalia constantes durante a construção.
  • Um ligador conecta arquivos objeto e código de bibliotecas.
  • Uma construção bem-sucedida ainda pode conter falhas em tempo de execução ou erros de lógica.

1.3 Interpretação, bytecode e compilação JIT

Um programa não precisa se tornar um executável nativo antes de o usuário iniciá-lo. Um interpretador executa o programa por meio de outro programa em execução. Muitas implementações combinam interpretação com compilação, então a pergunta útil é como esse sistema específico executa o código.

Execute por meio de um interpretador

Um interpretador precisa estar disponível sempre que executar o programa. Um REPL interativo repete quatro ações: ler uma entrada, avaliá-la, imprimir um resultado e repetir o ciclo para a próxima entrada. Ele oferece retorno rápido ao experimentar pequenas expressões.

Interpretar não significa necessariamente ler o código-fonte bruto uma linha de cada vez. O CPython, a implementação padrão do Python, primeiro compila um arquivo em bytecode. Uma máquina virtual de software executa então essas instruções internas. O bytecode pertence àquele ambiente de execução; ele não é o conjunto de instruções nativo do processador.

Por que alguns erros aparecem antes

Compare dois arquivos Python. Este tem sintaxe válida:

print("start")
x = 10
print(x + "5")

Ele imprime “start”, associa x a 10 e então gera TypeError. Somar esse inteiro a uma cadeia de caracteres falha apenas quando a expressão é executada. Uma operação semelhante em um ramo não percorrido pode passar despercebida até que o ramo seja executado.

Agora remova o parêntese de fechamento da última linha. O CPython não consegue compilar o arquivo porque sua sintaxe está incompleta. Ele informa um erro de sintaxe antes de qualquer instrução do arquivo ser executada, então “start” não é impresso. A verificação de sintaxe do arquivo inteiro e a verificação em tempo de execução de uma operação executada acontecem em momentos diferentes.

Compartilhe bytecode, especialize durante a execução

Java mostra um caminho híbrido comum. javac Hello.java produz Hello.class, que contém bytecode. java Hello inicia uma máquina virtual Java (JVM) compatível. Diferentes implementações de JVM permitem executar esse bytecode em diferentes famílias de processadores. O bytecode é portável entre ambientes de execução compatíveis; cada ambiente precisa funcionar em sua própria plataforma hospedeira. C# também tem como alvo uma linguagem intermediária usada pelo .NET.

Um compilador em tempo de execução (JIT) gera código de máquina enquanto o programa é executado. O ambiente de execução HotSpot de Java costuma começar com interpretação e compila métodos usados com frequência. Mecanismos JavaScript de navegadores também combinam técnicas de execução, incluindo compilação JIT. Compilar as partes mais usadas pode melhorar a velocidade sustentada, mas consome tempo e memória durante a execução.

CaminhoVantagem útilCusto a considerar
Construção nativa antecipadaO trabalho é traduzido antes da inicialização; o usuário não precisa de um compiladorConstruções compatíveis separadas para as plataformas-alvo
InterpretaçãoRetorno rápido no ciclo de edição e execução com um interpretador instaladoTrabalho repetido em tempo de execução pode tornar mais lento o código com muitos cálculos
Bytecode com máquina virtual e JITCódigo intermediário portável com otimização em tempo de execuçãoInstalação do ambiente de execução, aquecimento e uso adicional de memória

Um laço que soma um bilhão de valores geralmente é executado muito mais rápido em C compilado do que no CPython: o CPython realiza trabalho de interpretação e verificações de tipo em torno de muitas operações. Para um script que renomeia 20 arquivos, o esforço de desenvolvimento pode importar muito mais que essa diferença. Meça a carga de trabalho antes de usar a classificação de uma linguagem para prever sua velocidade.

Compilado e interpretado descrevem escolhas de implementação. O CPython usa interpretação de bytecode; o PyPy usa técnicas JIT. Uma linguagem pode ter mais de uma implementação, e uma implementação pode usar vários métodos de execução.

Principais conclusões

  • Um interpretador executa um programa e precisa estar presente para executá-lo.
  • O CPython compila um arquivo em bytecode antes de executá-lo.
  • Um erro de sintaxe pode impedir que um arquivo comece a ser executado; um erro em tempo de execução aparece quando sua operação é executada.
  • Um compilador JIT cria código nativo durante a execução, muitas vezes para partes usadas com frequência.
  • Os métodos de execução pertencem às implementações, e o desempenho depende da carga de trabalho.
2

Leia o que programas curtos fazem

Ler código envolve duas perguntas: se sua forma segue as regras da linguagem e o que suas operações significam. Uma expressão de mesma aparência pode se comportar de maneira diferente em duas linguagens. Aprenda a classificar erros e depois rastreie atribuições, decisões, laços e chamadas de funções na ordem de execução. Uma pequena tabela de valores que mudam torna visível a diferença entre o que a pessoa pretendia e o que o programa realmente faz.

2.1 Sintaxe, significado e três tipos de erro

A sintaxe estabelece as regras para um texto de programa válido. A semântica dá significado a esse texto. Um programa pode seguir todas as regras gramaticais e ainda calcular o resultado errado, então verificar sua aparência é apenas a primeira parte de sua leitura.

Reconheça as partes e a gramática

As unidades léxicas incluem palavras-chave como if, identificadores como total, literais como 3 ou "hello", operadores como + e == e sinais de pontuação. Uma gramática descreve como essas partes podem formar expressões e instruções.

Python exige dois-pontos após if x > 3: e usa a indentação para marcar as instruções controladas por essa condição. C, Java e JavaScript usam chaves para blocos; C e Java também exigem ponto e vírgula após muitas instruções. Nessas linguagens, a indentação ajuda a pessoa que lê, mas não seleciona o bloco.

Sintaxes diferentes podem expressar uma ação semelhante. Estas instruções imprimem uma saudação:

print("hello")
printf("hello\n");
System.out.println("hello");

A sequência de escape \n em C acrescenta uma quebra de linha. Conhecer a ideia de impressão ajuda a aprender a forma de expressá-la em cada linguagem.

Os mesmos símbolos podem significar coisas diferentes
Expressão e linguagemResultadoRegra
7 / 2 em Python ou JavaScript3.5A divisão produz um resultado fracionário
7 / 2 com operandos inteiros em C ou Java3A divisão inteira trunca em direção a zero
-7 // 2 em Python-4A divisão por piso arredonda para baixo
-7 / 2 com inteiros em C-3O truncamento ocorre em direção a zero
"3" * 4 em Python"3333"Uma cadeia de caracteres é repetida
"3" * 4 em JavaScript12O texto é convertido em número para a multiplicação

A aparência familiar de um símbolo não determina seu significado em todas as linguagens.

A ordem de avaliação também importa. A precedência faz a multiplicação e a divisão acontecerem antes da adição. Com a = 4 e b = 6, average = a + b / 2 divide 6 por 2 primeiro e depois soma 4, produzindo 7,0. Os parênteses em average = (a + b) / 2 fazem a soma acontecer primeiro e resultam em 5,0.

Diferencie os erros

Um erro de sintaxe desrespeita a gramática, como a falta de dois-pontos em Python. Um erro em tempo de execução ocorre durante uma operação executada, como uma divisão por zero em Python. Um erro de lógica permite a execução, mas fornece uma resposta não pretendida, como no cálculo incorreto da média.

Algumas expressões válidas pulam uma operação deliberadamente. Em Python, x != 0 and 10 / x > 2 usa avaliação de curto-circuito. Quando x é zero, a primeira condição é falsa, então o lado direito não é avaliado e nenhuma divisão ocorre. Inverter essas condições eliminaria essa proteção. Python escreve and, or e not; C, Java e JavaScript usam &&, || e !.

Atribuição e comparação são outra distinção importante. = armazena um valor; == testa a igualdade. C permite if (x = 5): ele atribui 5 e trata esse valor diferente de zero como verdadeiro. Python rejeita if x = 5: como sintaxe inválida. O mesmo erro pode, portanto, ser detectado em etapas diferentes em linguagens diferentes.

Principais conclusões

  • A sintaxe define a forma válida; a semântica define o significado.
  • Operadores podem ter significados diferentes em linguagens diferentes.
  • A precedência e os parênteses determinam quais operações são agrupadas.
  • Erros de sintaxe, em tempo de execução e de lógica exigem tipos diferentes de verificação.
  • A avaliação de curto-circuito pode pular uma operação insegura.
  • A atribuição armazena um valor; a comparação de igualdade testa valores.

2.2 Rastreie valores no fluxo de controle e nas chamadas

Um programa curto é mais fácil de entender quando você acompanha uma instrução executada de cada vez. Mantenha os valores atuais visíveis em vez de saltar da primeira linha para um resultado presumido.

Expressões e atribuições

Uma expressão produz um valor: price * 3, x > 10 e len(name) são exemplos. Uma instrução realiza uma ação, como atribuir um valor ou controlar um bloco. Em total = price * 3, a expressão à direita é avaliada antes de seu resultado ser atribuído a total.

Uma variável é um nome que se refere a um valor. A atribuição dá ao nome seu valor atual; ela não é uma afirmação matemática de que os dois lados sempre foram iguais. Se count contém 4, count = count + 1 primeiro lê 4, soma 1 e associa count a 5.

Acompanhe a próxima instrução executada

Três estruturas organizam o fluxo de controle:

  • A sequência executa instruções em ordem.
  • A seleção usa uma condição para escolher um caminho, como em if e else.
  • A iteração repete um bloco com um laço for ou while.

O teorema do programa estruturado, associado a Böhm e Jacopini em 1966, mostra que essas estruturas podem expressar qualquer algoritmo. Um laço for geralmente percorre os elementos de uma coleção. Um laço while se repete enquanto sua condição permanece verdadeira.

Considere este laço em Python:

total = 0
for n in [3, 8, 5]:
    total = total + n

A atribuição indentada é executada uma vez para cada elemento. Uma tabela de rastreamento registra o estado após cada atualização:

Momenton atualtotal
Antes do laçoAinda sem elemento0
Primeira atualização33
Segunda atualização811
Terceira atualização516

Cada novo total usa o anterior. Reiniciar total dentro do laço produziria um cálculo diferente, pois as somas anteriores seriam perdidas.

Entre em uma função e saia dela

Uma função agrupa uma tarefa sob um nome. Um parâmetro é um nome na definição; um argumento é um valor fornecido por uma chamada:

def area(width, height):
    return width * height

result = area(3, 4)

A chamada associa width a 3 e height a 4. A multiplicação produz 12, e return o envia a quem chamou a função. A execução continua na atribuição, então result recebe 12. Os nomes descrevem uma tarefa que pode ser reutilizada com outros argumentos.

Um nome tem um escopo

O escopo determina onde um nome pode ser usado. Nomes criados dentro de uma função normalmente são locais àquela chamada. Um nome no nível superior é global em seu módulo. Chamadas separadas têm associações locais separadas, mesmo quando usam nomes idênticos.

count = 7

def reset():
    count = 0

reset()
print(count)

Isso imprime 7. A atribuição dentro de reset cria um count local; ela não substitui a associação global. Uma função pode, em vez disso, retornar um resultado e deixar que quem a chamou atribua esse resultado.

Escopo e tempo de vida respondem a perguntas diferentes. Um nome local não fica disponível para código externo apenas porque o objeto ao qual se referia ainda existe. Objetos retornados podem permanecer ativos, e uma função aninhada retornada pode manter acesso a variáveis da chamada que a contém; esse ambiente preservado faz parte de um fechamento.

Principais conclusões

  • Avalie a expressão à direita antes de atualizar o destino de uma atribuição.
  • Rastreie sequência, seleção e iteração na ordem em que são realmente executadas.
  • Parâmetros são nomes na definição; argumentos são valores fornecidos.
  • Um valor de retorno passa um resultado de volta a quem fez a chamada.
  • Nomes locais e globais podem ter associações separadas; escopo difere de tempo de vida.
3

Tipos, referências e memória

Valores precisam de um tipo, de um lugar na memória e de um tempo de vida. Essas escolhas explicam por que um texto que parece numérico pode rejeitar operações aritméticas, por que dois nomes podem mostrar a mesma lista alterada e por que dados não usados às vezes continuam alocados. Compare as verificações feitas pelas linguagens, acompanhe uma chamada de função na memória e examine três formas de liberar armazenamento. Cada abordagem altera o equilíbrio entre controle, conveniência e segurança.

3.1 Tipos determinam quais operações fazem sentido

Um tipo de dado especifica os valores possíveis e as operações permitidas sobre eles. Um inteiro permite operações aritméticas com números inteiros; um valor booleano representa verdadeiro ou falso. Cadeias de caracteres, listas, registros e objetos combinam ou organizam outros valores. Os tipos também orientam a representação dos valores na memória, embora o tamanho de um valor possa depender do próprio valor e da implementação.

Quando os tipos são verificados?
AbordagemQuando ocorrem as verificaçõesExemplo
Tipagem estáticaAntes da execuçãoJava rejeita int count = "five";
Tipagem dinâmicaDurante a execuçãoPython verifica se uma operação executada é adequada a seus operandos

Em Python, count = 5 seguido de count = "five" é válido. O nome agora se refere a uma cadeia de caracteres. Avaliar count + 1 em seguida gera TypeError. A tipagem dinâmica dá tipos definidos aos valores; ela não elimina os tipos.

A tipagem estática não exige uma indicação de tipo visível em cada variável. A inferência de tipos permite que um compilador deduza um tipo a partir do código ao redor. Kotlin infere um inteiro para val n = 5, e Rust pode inferir um inteiro para let n = 5;. As anotações de tipo opcionais do Python são diferentes: ferramentas separadas, como o mypy, podem verificá-las, mas a execução comum do Python não as impõe.

Quando os valores são convertidos?

O momento da verificação e o rigor da conversão são questões separadas. Python rejeita "3" + 4; a conversão explícita em int("3") + 4 resulta em 7. JavaScript aplica regras de conversão automática diferentes para operadores diferentes:

  • "3" + 4 resulta na cadeia de caracteres "34".
  • "3" * 4 resulta no número 12.
  • "3" - 4 resulta no número -1.

A conversão automática é chamada de coerção de tipos. Os rótulos tipagem “forte” e “fraca” são formas informais de discutir o rigor da conversão, não outros nomes para tipagem estática e dinâmica. Python é dinâmica e estrita quanto à adição de tipos mistos acima; C é estática e ainda permite muitas conversões numéricas implícitas.

O input() do Python retorna uma cadeia de caracteres. Se uma pessoa digita “30”, o resultado ainda tem o tipo str. age + 1 falha, enquanto int(age) + 1 resulta em 31 quando age contém esse texto. O que aparece na tela não basta para determinar o tipo.

Números têm limites de representação

Um inteiro com sinal de 32 bits representa valores de −2.147.483.648 até 2.147.483.647. O int de Java retorna ciclicamente do máximo ao mínimo quando 1 é somado. O estouro de inteiro com sinal em C é um comportamento indefinido, então não se deve presumir o mesmo retorno cíclico em C. Inteiros em Python crescem conforme a memória permite: 2 ** 100 é exatamente 1.267.650.600.228.229.401.496.703.205.376.

Números de ponto flutuante representam a maioria das frações decimais de forma aproximada em binário. Em Python e JavaScript, 0.1 + 0.2 produz 0.30000000000000004. Comparar esse resultado diretamente com 0.3 resulta em falso.

Quando um cálculo permite um pequeno erro, compare os valores usando uma tolerância escolhida para a tarefa, por exemplo abs(a - b) < 1e-9. Uma tolerância absoluta tão pequena é adequada apenas a algumas escalas. Para cálculos contábeis decimais exatos, use quantidades inteiras da menor unidade ou um tipo decimal adequado. Alterar a comparação não pode tornar exata uma representação aproximada.

Principais conclusões

  • Um tipo define valores e operações permitidas.
  • Tipagem estática e dinâmica descrevem o momento da verificação, enquanto coerção diz respeito às regras de conversão.
  • A inferência de tipos apoia a tipagem estática; anotações de tipo em Python precisam de ferramentas de verificação separadas.
  • Inteiros de largura fixa podem sofrer estouro, com comportamento específico de cada linguagem.
  • O arredondamento em ponto flutuante afeta testes de igualdade e a escolha da representação numérica.

3.2 Acompanhe nomes, objetos e chamadas de funções na memória

A memória principal armazena os dados de um programa em execução. Cada byte tem um endereço. As linguagens oferecem nomes para que quem programa possa trabalhar com valores sem escrever endereços brutos para cada operação.

Chamadas usam registros de ativação; objetos têm tempos de vida

Em C, um int geralmente ocupa 4 bytes, e &x obtém o endereço de x. Linguagens de nível mais alto frequentemente ocultam endereços por trás de referências. A distinção importante é entre uma associação que nomeia um objeto e o próprio objeto.

A pilha de chamadas acompanha as chamadas de funções ativas. Cada chamada tem um registro de ativação com informações como parâmetros, associações locais e o ponto em que a execução deve continuar após o retorno. Se main chama area(3, 4), um registro de area é adicionado acima do registro de quem chamou a função. A função calcula 12, retorna e seu registro é removido. Em chamadas aninhadas comuns, a chamada mais recente retorna primeiro.

A recursão acrescenta uma chamada para cada nível ainda não concluído. Se as chamadas continuam se acumulando, o espaço disponível na pilha pode se esgotar. Python normalmente gera RecursionError após aproximadamente 1.000 chamadas aninhadas, com um limite que depende de suas configurações. Uma condição de parada precisa ser alcançada para que um cálculo recursivo finito termine.

A área de alocação dinâmica contém objetos alocados dinamicamente, como uma lista que cresce ou um objeto que dura mais que a função que o criou. A alocação reserva armazenamento. Uma lista retornada pode permanecer nessa área depois que a chamada que a criou terminou.

Copiar uma referência compartilha um objeto

Uma referência identifica um objeto. Um ponteiro em C ou C++ representa explicitamente um endereço usado para acessar dados. Copiar uma referência copia o acesso ao objeto, não todos os dados do objeto.

a = [1, 2]
b = a
b.append(3)

Após a primeira linha, há uma lista. A segunda linha associa outro nome a ela. A última linha altera essa lista, então tanto a quanto b mostram [1, 2, 3]. Esse compartilhamento é o compartilhamento por referências. Usar b = a.copy() criaria uma lista externa separada; para essa lista de inteiros, acrescentar um elemento à nova lista deixaria a inalterada.

Compare atribuição com mutação:

x = 5
y = x
y = y + 1

O inteiro 5 é imutável. A adição produz 6, e apenas y é associado a outro valor. x ainda se refere a 5. Números, cadeias de caracteres e tuplas em Python são imutáveis; listas e dicionários são mutáveis. O conteúdo fixo de uma tupla pode incluir uma referência a uma lista mutável, então a imutabilidade do contêiner externo não torna imutável todo objeto aninhado.

Aplique as mesmas regras aos argumentos

Quando Python passa uma lista a uma função, o parâmetro se refere ao mesmo objeto. Chamar items.append(4) dentro da função altera essa lista. Atribuir items = [] associa apenas o nome local do parâmetro a uma nova lista. O nome de quem chamou a função continua associado ao objeto original.

C passa os valores dos argumentos por valor. Para permitir que uma função atualize a variável de quem a chamou por meio de seu endereço, passe um ponteiro para ela. Em cada linguagem, faça duas perguntas separadas durante o rastreamento: “A qual objeto este nome se refere?” e “Esta operação altera esse objeto ou associa um nome a outra coisa?”.

Principais conclusões

  • A pilha de chamadas acompanha chamadas não concluídas; a área de alocação dinâmica contém objetos alocados dinamicamente.
  • Um objeto pode durar mais que a função que o criou.
  • Copiar uma referência compartilha um objeto; não duplica seus dados.
  • A mutação altera um objeto, enquanto uma nova associação altera aquilo a que um nome se refere.
  • A passagem de argumentos segue a mesma distinção entre objetos compartilhados e associações locais.

3.3 Libere memória sem perder dados ainda necessários

A memória alocada precisa, em algum momento, voltar a ficar disponível para uso. O desafio é liberar os dados após seu último uso e manter todos os objetos de que o programa ainda precisa. As linguagens atribuem essa responsabilidade a quem programa, a um sistema de execução ou a regras de propriedade verificadas pelo compilador.

Gerenciamento manual e suas falhas

Em C, malloc(n) solicita n bytes e retorna um ponteiro para o bloco se a operação for bem-sucedida. free(p) libera um bloco obtido por alocação. Quem programa deve usá-lo no momento certo e exatamente uma vez. C++ também permite o gerenciamento manual, embora suas ferramentas padrão possam automatizar a liberação por meio dos tempos de vida dos objetos.

Vários erros têm causas diferentes:

FalhaO que deu errado
Vazamento de memóriaO armazenamento alocado que não é mais necessário não é liberado
Uso após liberaçãoUm ponteiro é usado depois que o armazenamento para o qual aponta foi liberado
Liberação duplaO mesmo bloco alocado é liberado duas vezes
Estouro de bufferUma escrita ultrapassa os limites de um bloco de memória

Um ponteiro que continua se referindo a armazenamento liberado é pendente. O armazenamento pode ser reutilizado para outra coisa, então acessá-lo pode corromper dados ou causar uma falha no programa.

Para um array C int scores[10], as posições válidas vão de 0 a 9. Escrever em scores[10] ultrapassa o array, e C não oferece verificação automática de limites. Python gera IndexError quando o índice 10 é usado em uma lista de dez elementos, impedindo que esse acesso sobrescreva a memória adjacente.

Rastreie o alcance ou conte referências

Um coletor de lixo por rastreamento começa por raízes ativas, como referências ativas, segue ligações entre objetos e pode recuperar objetos inalcançáveis. Java, C#, Go e JavaScript usam abordagens por rastreamento. A coleta exige trabalho e memória em tempo de execução; dependendo do coletor, ela pode introduzir pausas.

A contagem de referências acompanha as referências a um objeto. Para uma lista Python recém-criada, a sequência simplificada é: a = [1, 2] fornece uma referência; b = a fornece uma segunda; del a remove uma; b = None remove a outra. O CPython normalmente recupera essa lista comum quando sua contagem chega a zero.

Um grupo de objetos pode manter as contagens uns dos outros acima de zero mesmo quando nenhuma parte ativa do programa consegue alcançar o grupo. Isso é um ciclo de referências. O CPython complementa a contagem de referências com a coleta de ciclos. Tanto a contagem de referências quanto o rastreamento são técnicas automáticas de gerenciamento de memória.

Verifique a propriedade antes da execução

Rust dá a cada valor um proprietário e verifica regras para o empréstimo de referências. Quando a propriedade termina, o código de limpeza gerado libera o valor. Rust seguro rejeita usos de referências que poderiam durar mais que o alvo válido, sem exigir um coletor de lixo por rastreamento. Swift usa contagem automática de referências inserida pelo compilador.

A segurança de memória importa além das falhas de programas. A Microsoft informou em 2019 que aproximadamente 70% das vulnerabilidades às quais atribuía CVEs a cada ano envolviam segurança de memória; o Chromium informou uma proporção semelhante de erros graves em 2020. Em dezembro de 2023, CISA, NSA, FBI e parceiros internacionais recomendaram que fabricantes de software planejassem transições para linguagens seguras quanto à memória.

A recuperação automática ainda mantém objetos alcançáveis. Uma lista global que cresce continuamente pode manter todos os registros antigos ativos mesmo depois que a aplicação terminou de usá-los. Um coletor não consegue decidir que esses registros alcançáveis se tornaram desnecessários para a tarefa; o programa precisa deixar de retê-los.

Principais conclusões

  • A alocação manual exige a liberação no momento correto.
  • Vazamentos, uso após liberação, liberação dupla e estouro de buffer têm causas distintas.
  • O rastreamento encontra objetos inalcançáveis; a contagem de referências reage à perda de referências.
  • O CPython acrescenta a coleta de ciclos à contagem de referências.
  • A propriedade de valores pode organizar a liberação sem um coletor por rastreamento.
  • A coleta automática não pode recuperar objetos que o programa ainda mantém.
4

Organize estados que mudam e objetos que cooperam

Uma forma de descrever um programa é como comandos que atualizam valores. Outra agrupa esses valores com as operações que os controlam. Essas visões muitas vezes trabalham juntas: um método de objeto pode conter um laço comum. Primeiro, rastreie as mudanças de estado; depois, crie um pequeno modelo de conta para ver como classes protegem regras e como herança, polimorfismo e composição permitem que objetos relacionados compartilhem uma interface sem assumir responsabilidades inadequadas.

4.1 Comandos, procedimentos e estado compartilhado

Um paradigma de programação é uma forma de organizar código e pensar sobre computação. Na programação imperativa, um programa fornece comandos que alteram seu estado: os valores atualmente mantidos por variáveis e objetos.

Torne visíveis as mudanças de estado

Um total acumulado é um cálculo imperativo simples:

total = 0
for n in [3, 8, 5]:
    total = total + n

O estado inicial é total = 0. Somar 3 o altera para 3. Somar 8 o altera para 11. Somar 5 o altera para 16. Cada atribuição usa o estado deixado pelo passo anterior, então a ordem faz parte do método.

Esse estilo corresponde às alterações passo a passo que o processador faz em registradores e memória. Ele também se adapta a algoritmos que atualizam uma coleção deliberadamente no próprio local, como uma rotina de ordenação que troca elementos.

Dê nomes às tarefas

A programação procedural organiza código imperativo em funções ou procedimentos. Um programa de relatórios pode chamar read_scores(), average(scores) e print_report(avg). Os nomes permitem que a pessoa veja a tarefa geral sem acompanhar todas as linhas internas de uma vez. Cada procedimento pode ser entendido e testado separadamente.

C, Pascal, Fortran e COBOL são linguagens procedurais clássicas. Python também oferece suporte a esse estilo: um script pode usar funções nomeadas sem definir nenhuma classe.

A programação estruturada organiza o fluxo de controle com sequência, seleção, laços e chamadas de procedimentos. Ela evita saltos arbitrários que tornam difícil prever a próxima instrução executada. A carta de Edsger Dijkstra de 1968, “Go To Statement Considered Harmful”, ajudou a popularizar essa abordagem. Saídas controladas como break, continue e return continuam úteis: elas permitem sair de uma iteração de laço, de um laço ou de uma função definidos.

Efeitos podem ir além de um valor de retorno

Um efeito colateral altera algo além do resultado retornado por uma função. Imprimir, salvar um arquivo, enviar uma mensagem pela rede e alterar dados compartilhados são efeitos colaterais. Programas precisam dessas ações para interagir com o mundo.

Problemas surgem quando códigos não relacionados alteram silenciosamente o mesmo estado. Suponha que apply_discount() reduza uma variável global price em 10%, e que add_shipping() acrescente 5. Partindo de 100, aplicar o desconto primeiro resulta em 90, e depois acrescentar o frete resulta em 95. Acrescentar o frete primeiro resulta em 105, e depois aplicar o desconto resulta em 94,5. Ambos os procedimentos realizam seus próprios passos corretamente, mas o resultado combinado depende de sua ordem.

Passar valores e retornar novos resultados pode tornar essa ordem visível:

price = add_shipping(apply_discount(100))

A chamada interna é executada primeiro: desconto, depois frete. Um aninhamento diferente expressa uma regra diferente. Um fluxo de dados claro ajuda a revelar se o próprio frete deve receber o desconto.

OrganizaçãoPergunta principal durante a leitura
Comandos imperativosQual valor muda em seguida?
Procedimentos nomeadosQual tarefa esta chamada realiza?
Atualizações globais compartilhadasQual ação anterior alterou este valor?

Código imperativo ainda é comum dentro de métodos de objetos e tratadores de eventos modernos. Escolher um projeto mais amplo orientado a objetos ou funcional não impede que uma pequena parte use comandos diretos quando esses passos tornam o algoritmo claro.

Principais conclusões

  • Código imperativo descreve passos que alteram o estado armazenado.
  • Procedimentos dão nomes e limites a tarefas coerentes.
  • O fluxo de controle estruturado torna os caminhos de execução mais fáceis de acompanhar.
  • Efeitos colaterais incluem alterações em dados compartilhados e interações com o mundo externo.
  • Atualizações compartilhadas podem fazer os resultados dependerem da ordem das chamadas.

4.2 Classes mantêm dados junto de suas regras

A programação orientada a objetos (OOP) modela um programa como objetos que cooperam entre si. Cada objeto combina dados com operações sobre esses dados. Uma conta pode conter seu titular e saldo, oferecendo operações de depósito e saque.

Separe o modelo de suas instâncias

Uma classe define os atributos e métodos oferecidos por seus objetos. Um objeto, ou instância, é um elemento específico dessa classe. BankAccount é o modelo; a conta de Ana com 150 e a conta de Ben com 40 são instâncias distintas. Seus saldos podem mudar independentemente.

Um atributo armazena dados associados a um objeto. Um método é uma operação chamada por meio de um objeto, como acct.deposit(50). self em um método de instância Python se refere ao objeto que recebe a chamada.

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("amount must be positive")
        self._balance = self._balance + amount

Chamar BankAccount("Ana", 100) constrói uma nova instância. Seu método __init__ inicializa os dados da instância. Um construtor é o processo de preparação de um novo objeto; Python separa a criação dessa etapa de inicialização. Chamar acct.deposit(50) nessa conta altera seu saldo de 100 para 150.

Coloque a regra no ponto de atualização

O encapsulamento reúne dados e as operações que mantêm suas regras. Suponha que uma conta nunca deva ter saldo negativo. O método de saque verifica a alteração proposta antes de realizá-la:

def withdraw(self, amount):
    if amount <= 0:
        raise ValueError("amount must be positive")
    if amount > self._balance:
        raise ValueError("insufficient funds")
    self._balance = self._balance - amount

Esse método pertence a BankAccount. Partindo de um saldo de 150, withdraw(200) gera um erro e mantém 150. withdraw(30) é bem-sucedido e deixa 120. A chamada que falha não subtrai primeiro para depois descobrir o problema; a verificação acontece antes da atualização.

Outros códigos devem usar esses métodos em vez de atribuir um saldo sem verificação. Java e C# podem restringir o acesso direto com private. O sublinhado inicial de Python em _balance indica um detalhe interno por convenção; ele não impõe privacidade.

A ocultação de informação vai além: quem chama depende das operações oferecidas por uma classe sem precisar conhecer sua representação interna. O saldo poderia ser armazenado de outra forma posteriormente, enquanto as operações públicas de depósito e saque manteriam seu significado.

Deixe o problema sugerir responsabilidades

Um sistema de biblioteca tem livros, membros e empréstimos. Esses substantivos sugerem possíveis classes ou atributos; ações como emprestar e renovar sugerem métodos. Escolha uma responsabilidade clara para cada classe. Uma classe de conta que também envia todos os e-mails e formata todos os relatórios se torna mais difícil de alterar porque trabalhos não relacionados estão misturados.

Classes e objetos surgiram em Simula 67 em 1967; Smalltalk ajudou a difundir a abordagem na década de 1970. Java, C#, C++ e Python oferecem suporte à programação orientada a objetos, mas usar uma dessas linguagens não obriga que todo programa use classes. A pergunta útil de projeto é quais dados e regras pertencem ao mesmo lugar.

Principais conclusões

  • Uma classe define atributos e métodos; cada instância tem seus próprios dados de instância.
  • Métodos atuam sobre o objeto que recebe a chamada.
  • O encapsulamento reúne validação e mudanças de estado.
  • Sublinhados em Python indicam atributos internos por convenção; algumas linguagens impõem acesso privado.
  • A ocultação de informação permite que quem chama dependa das operações públicas, em vez dos detalhes internos.
  • Uma classe deve ter uma responsabilidade coerente.

4.3 Especialize comportamentos e combine objetos

Objetos muitas vezes precisam compartilhar operações e, ao mesmo tempo, diferir em detalhes. Herança, polimorfismo e composição resolvem partes diferentes desse problema de projeto.

Especialize um tipo existente de objeto

A herança define uma subclasse a partir de uma superclasse. Em Python, class SavingsAccount(BankAccount): dá à classe especializada acesso ao comportamento herdado da conta. Ela pode acrescentar add_interest(rate): um saldo de 1.000 com acréscimo de juros de 2% se torna 1.020.

A relação pretendida é de tipo. Uma conta de poupança ainda é uma conta bancária e deve respeitar as regras esperadas de contas bancárias. Reutilizar algumas linhas não é razão suficiente para afirmar que um tipo de objeto é outro.

Uma subclasse pode sobrescrever um método com sua própria implementação. Suponha que uma conta corrente cobre uma tarifa de 1 por cada saque bem-sucedido. Com saldo de 100, sacar 30 custa 31 e deixa 69. Para preservar a regra da conta, o método deve verificar o saque mais a tarifa antes de alterar o saldo. Se esse custo combinado exceder o saldo, ele recusa toda a operação.

Faça uma chamada funcionar com objetos diferentes

O polimorfismo permite que a mesma operação funcione com tipos de objetos diferentes. Um círculo e um retângulo podem oferecer area():

for shape in shapes:
    print(shape.area())

O laço não precisa de um if para cada classe de forma. Cada objeto fornece sua própria implementação. Selecionar essa implementação durante a execução é o despacho dinâmico.

Para Circle(2), a área é de cerca de 12,57 unidades quadradas. Para Rectangle(3, 4), é de 12 unidades quadradas. A área combinada é de cerca de 24,57 unidades quadradas. Uma classe Triangle acrescentada depois pode integrar a coleção ao oferecer uma operação area() compatível; o próprio laço não precisa de outro ramo.

Uma interface declara as operações que um tipo deve oferecer. interface Shape { double area(); } em Java exige que as classes que a implementam forneçam o método de área. Uma classe abstrata pode fornecer parte da implementação compartilhada e deixar outros métodos para as subclasses. Python costuma usar tipagem por comportamento: um objeto pode funcionar no laço se oferecer a operação exigida, mesmo sem uma superclasse compartilhada.

Mantenha uma parte em vez de se tornar ela

A composição armazena outros objetos como partes. Um carro tem um motor e rodas. Ele pode chamar o método start() de seu motor sem herdar de Engine.

RelaçãoProjeto adequadoMotivo
Conta de poupança é uma contaHerançaEla preserva o comportamento esperado de uma conta
Carro tem um motorComposiçãoO motor é um componente, não a categoria do carro
Formas diferentes oferecem áreaOperação ou interface compartilhadaQuem chama pode solicitar a operação de modo uniforme

Cadeias profundas de herança podem espalhar os efeitos de uma alteração em uma superclasse por muitas classes. Uma subclasse que precisa desativar um comportamento herdado pode indicar uma relação de tipo inadequada. A composição muitas vezes mantém essas dependências menores e mais claras.

Os quatro pilares da programação orientada a objetos comumente citados se encaixam aqui: o encapsulamento protege dados por meio de operações, a abstração oculta detalhes de implementação, a herança especializa um tipo e o polimorfismo permite que quem chama use uma operação compartilhada em diferentes implementações.

Principais conclusões

  • A herança modela uma relação de tipo e deve preservar o comportamento esperado.
  • A sobrescrita substitui a implementação de um método em uma classe especializada.
  • O polimorfismo permite que uma operação invoque implementações diferentes.
  • Uma interface declara as operações exigidas; a tipagem por comportamento depende das operações oferecidas por um objeto.
  • A composição modela objetos que têm outros objetos como partes.
5

Calcule novos valores e descreva resultados desejados

Um programa pode se concentrar em transformações de valores ou no resultado que um mecanismo deve produzir. Funções puras tornam as dependências explícitas, enquanto dados imutáveis evitam alterações por meio de referências compartilhadas. Consultas declarativas permitem que um mecanismo especializado escolha os passos de execução. Acompanhe uma sequência de transformações de dados e uma consulta a um banco de dados para ver o que essas abordagens facilitam e onde entrada, saída e instruções concretas de máquina ainda entram em cena.

5.1 Funções puras e sequências de transformações de dados

A programação funcional constrói resultados combinando funções. Seu hábito central é produzir novos valores a partir de entradas e limitar a mutação compartilhada. Lisp introduziu ideias funcionais amplamente usadas por volta de 1958; Haskell, Erlang, Clojure, F# e OCaml desenvolvem essas ideias de maneiras diferentes.

Saiba o que torna uma função pura

Uma função pura fornece o mesmo resultado para os mesmos argumentos e não tem efeitos colaterais. def with_tax(price): return price * 1.2 ilustra a ideia: with_tax(50) resulta em 60,0. Uma função que também acrescenta algo a uma lista global ou imprime uma mensagem tem um efeito além de seu valor de retorno. Um resultado que depende da hora atual também depende de mais do que os argumentos.

Uma chamada pura pode ser substituída por seu valor de retorno sem alterar o comportamento do programa. Essa propriedade é a transparência referencial. Ela torna simples os testes isolados e permite que chamadas independentes sejam executadas em paralelo sem disputar a atualização de um estado compartilhado.

A imutabilidade mantém um valor existente inalterado. [n + 1 for n in nums] cria uma nova lista, deixando nums intacta. Um programa ainda pode representar informações que mudam produzindo novas versões; ele não precisa sobrescrever a versão antiga.

Passe comportamento como dados

Funções são de primeira classe quando podem ser atribuídas a nomes, passadas como argumentos e retornadas como valores. Uma função de ordem superior recebe uma função ou retorna uma. Uma expressão lambda é uma expressão de função sem nome, como lambda n: n * n.

Três operações comuns de ordem superior formam uma sequência:

  • filter mantém elementos que satisfazem um teste.
  • map transforma cada elemento restante.
  • reduce combina elementos em um resultado usando um acumulador.

Para somar os quadrados dos números pares de 1 até 6, a filtragem deixa 2, 4 e 6; elevar ao quadrado produz 4, 16 e 36; somar produz 56.

sum(map(lambda n: n * n,
        filter(lambda n: n % 2 == 0, range(1, 7))))

Aqui % 2 == 0 testa se um número é par, e range(1, 7) fornece os números de 1 até 6. Haskell expressa o mesmo cálculo como sum (map (^2) (filter even [1..6])).

Com reduce importado de functools do Python, reduce(lambda acc, n: acc + n, [3, 8, 5], 0) combina 0 com 3 para obter 3, depois com 8 para obter 11 e depois com 5 para obter 16. A sequência descreve transformações sem um laço externo que atualize um total compartilhado.

Preserve um ambiente ou chame um problema menor

def multiplier(k): return lambda x: k * x retorna uma função que mantém acesso a k. Assim, multiplier(3)(10) retorna 30 mesmo depois que a chamada externa terminou. A função retornada e o ambiente preservado formam um fechamento.

Na recursão, uma função chama a si mesma para um problema menor. O fatorial retorna 1 em n == 0; nos demais casos, retorna n * factorial(n - 1). Para 5, os produtos resultam em 120. Um cálculo finito precisa de um caso base alcançável. As chamadas aninhadas não resolvidas do Python acabam gerando RecursionError; linguagens com suporte adequado a chamadas de cauda podem reutilizar espaço na pilha para chamadas na posição final.

Usar funções por si só não torna o código funcional: funções procedurais podem alterar o estado. Programas funcionais reais ainda leem entradas e escrevem saídas, geralmente mantendo efeitos próximos às extremidades do programa e os cálculos centrais puros.

Principais conclusões

  • Funções puras dependem apenas de seus argumentos e não têm efeitos colaterais.
  • Atualizações imutáveis criam novos valores.
  • Funções de primeira classe podem ser passadas e retornadas; funções de ordem superior recebem ou retornam funções.
  • Filter seleciona, map transforma e reduce combina.
  • Um fechamento preserva seu ambiente de criação.
  • Um cálculo recursivo finito precisa de um caso base que ele realmente alcance.

5.2 Declare um resultado e deixe um mecanismo encontrá-lo

A programação declarativa descreve o resultado desejado e deixa os passos de execução a cargo de um mecanismo da linguagem. O código funcional geralmente é agrupado nessa ideia ampla, junto com linguagens de consulta e programação lógica.

Leia uma consulta como uma solicitação

Um banco de dados relacional armazena dados em tabelas. Suponha que students tenha as linhas de nome e nota Ana 92, Ben 75, Chen 80 e Dara 64:

SELECT name
FROM students
WHERE grade >= 80
ORDER BY name;

FROM indica a tabela, WHERE fornece a condição, SELECT escolhe o campo retornado e ORDER BY solicita ordem alfabética. Ana e Chen satisfazem a condição, incluindo a nota de Chen, exatamente 80. O resultado são seus nomes nessa ordem.

Um programa imperativo criaria uma lista de resultados, percorreria as linhas, testaria cada nota, acrescentaria os nomes que atendem à condição e ordenaria a lista. SQL fornece ao banco de dados o resultado exigido sem escrever esse laço.

Um otimizador de consultas escolhe um plano de execução. Ele pode varrer a tabela ou usar um índice disponível, uma estrutura de busca que pode acelerar pesquisas adequadas. Muitos índices usam estruturas ordenadas; outros usam organizações diferentes. Acrescentar um índice pode alterar o plano sem alterar a consulta. Mais dados ainda significam trabalho: declarar o resultado não garante que toda consulta exija poucos recursos.

Pergunte o que decorre de fatos e regras

A programação lógica descreve fatos e relações e depois procura valores que satisfaçam uma pergunta. Em Prolog:

parent(tom, bob).
parent(bob, ann).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).

A regra significa que X é avô ou avó de Z quando há um Y para o qual ambas as relações de pai ou mãe são verdadeiras. A consulta ?- grandparent(tom, Who). encontra Who = ann: Tom é pai de Bob e Bob é pai de Ann. Nomes iniciados por maiúsculas representam variáveis; os nomes em minúsculas identificam pessoas específicas.

Reconheça ferramentas declarativas ao seu redor

Uma fórmula de planilha =SUM(B2:B5) declara uma dependência. A planilha a recalcula quando células relevantes mudam. A expressão regular [0-9]{3}-[0-9]{4} descreve três algarismos, um hífen e quatro algarismos, como em “555-0199”. Um mecanismo de correspondência de texto realiza a busca.

CSS declara a aparência com uma regra como h1 { color: navy; }; HTML marca a estrutura da página. São notações declarativas, normalmente classificadas como linguagens de estilo e de marcação. Sua função difere da de uma linguagem de propósito geral que pode expressar uma ampla variedade de computações.

Tipo de linguagemFoco da tarefaExemplos
Linguagem específica de domínioUm tipo específico de trabalhoSQL, expressões regulares, CSS
Linguagem de propósito geralMuitos tipos de programasPython, Java, Haskell

O código declarativo pode permanecer próximo da descrição de um problema e permitir que um mecanismo melhore seu método. Ele também dá a quem programa menos controle direto sobre passos individuais, o que pode tornar mais difícil diagnosticar um plano lento. Instruções concretas ainda são executadas por trás: declarativo descreve a forma como a pessoa expressa a tarefa, não um processador que funciona sem passos.

Principais conclusões

  • Código declarativo declara um resultado desejado; um mecanismo escolhe os passos de execução.
  • SQL seleciona linhas e campos por meio de condições e da ordenação solicitada.
  • Um otimizador pode alterar um plano de consulta sem alterar a consulta.
  • A programação lógica deriva respostas de fatos e regras.
  • Linguagens específicas de domínio se destinam a tarefas específicas; linguagens de propósito geral oferecem suporte a muitos tipos de programas.
6

Escolha um estilo e uma linguagem para a tarefa

Estilos de programação diferentes podem produzir a mesma resposta e organizar o trabalho de maneiras diferentes. Compará-los em uma tarefa torna seus compromissos mais fáceis de perceber. A escolha de uma linguagem acrescenta restrições práticas: a plataforma-alvo, as bibliotecas disponíveis, os limites de memória e velocidade, as necessidades de segurança e as pessoas que mantêm o código. Um projeto com várias partes pode usar uma linguagem diferente e uma combinação diferente de estilos para cada parte.

6.1 Uma tarefa em vários estilos de programação

Um paradigma altera a forma como uma solução é expressa, não a resposta numérica que uma solução correta deve produzir. Considere pedidos com valores [120, 45, 300, 80]: encontre o total dos valores estritamente maiores que 100.

Acompanhe as mudanças de estado

A versão imperativa expõe o laço e o acumulador:

total = 0
for amount in amounts:
    if amount > 100:
        total = total + amount

O primeiro valor altera total para 120. O segundo o deixa inalterado. O terceiro o altera para 420, e o quarto o mantém nesse valor. O programa torna visível a ordem dos testes e das atualizações.

Transforme uma coleção

Uma expressão Python compacta no estilo funcional é sum(a for a in amounts if a > 100). Ela seleciona 120 e 300 e os combina em 420. Outra versão é sum(filter(lambda a: a > 100, amounts)). Aqui o predicado descreve quais valores pertencem ao conjunto selecionado, e sum os combina.

Uma expressão geradora ou uma chamada filter usa recursos multiparadigma do Python; sua presença não torna puras todas as partes da aplicação ao redor.

Coloque regras junto dos objetos

Em um projeto orientado a objetos, um Order armazena um amount e oferece is_large(), que retorna self.amount > 100. Um OrderBook contém objetos de pedidos e oferece large_total(). Esse método pode percorrer os pedidos em um laço, perguntar a cada um se atende à condição e somar os valores correspondentes.

O total continua sendo 420. A diferença está em onde ficam as responsabilidades: a regra para um pedido grande pertence a Order, e o trabalho no nível da coleção pertence a OrderBook. Outras funcionalidades podem reutilizar a regra sem copiar seu teste de limite.

Consulte o banco de dados

Se os dados já estiverem em um banco de dados relacional, SQL pode solicitar o total:

SELECT SUM(amount)
FROM orders
WHERE amount > 100;

O banco de dados escolhe o plano de busca e agregação. A consulta não instrui quem a chama a percorrer todas as linhas ou manter um acumulador local.

EstiloO que o exemplo enfatiza
ImperativoTestes e atualizações ordenados de um total
Orientado a objetosPedidos e objetos de coleção com responsabilidades definidas
FuncionalSeleção e combinação de valores
SQL declarativoO resultado exigido sobre os dados armazenados
Combine estilos onde eles ajudam

Python oferece suporte a técnicas procedurais, orientadas a objetos e funcionais. JavaScript fornece objetos, funções de primeira classe e fechamentos. C++, C#, Kotlin, Scala, Swift e Rust também combinam estilos. Java é fortemente associada à programação orientada a objetos, mas oferece suporte a expressões lambda desde Java 8, em 2014; Haskell é puramente funcional.

Uma aplicação web pode usar classes para usuários e pedidos, transformações funcionais para dados de relatórios, atualizações imperativas dentro de métodos e SQL para consultas ao banco de dados. Essas escolhas funcionam em camadas diferentes.

A programação orientada a eventos acrescenta outra ideia de organização: registrar um tratador que é executado quando um evento chega. Em JavaScript, button.addEventListener("click", showMenu) passa showMenu como um valor de função. Um clique posterior o invoca. O próprio tratador pode usar objetos, transformações puras ou passos imperativos. Sua função orientada a eventos indica quando ele é executado, não o único estilo que seu corpo pode usar.

Principais conclusões

  • Soluções corretas em paradigmas diferentes podem calcular o mesmo resultado.
  • Um estilo altera onde aparecem passos, estado, regras e responsabilidades.
  • Linguagens modernas geralmente oferecem suporte a vários paradigmas.
  • Um tratador de eventos é executado em resposta a um evento e pode usar outros estilos internamente.

6.2 Escolha considerando restrições e compromissos

Nenhuma linguagem de programação é a mais adequada para todo projeto. Linguagens de propósito geral geralmente são Turing completas: com tempo e memória ilimitados, podem expressar a mesma classe de computações. As escolhas reais dependem de a solução ser adequada à plataforma, aos recursos, às ferramentas e às pessoas envolvidas.

Comece pelo que precisa funcionar

A plataforma costuma ser uma restrição rígida. Navegadores executam JavaScript e podem executar módulos compilados por meio do WebAssembly. TypeScript acrescenta verificação estática e compila para JavaScript. Swift é uma escolha padrão para aplicativos das plataformas Apple; Kotlin é recomendada para Android. SQL é a linguagem usual para consultas relacionais.

Em seguida, considere o domínio e o ecossistema. Python tem bibliotecas de dados e aprendizado de máquina amplamente usadas, como NumPy e pandas. R se concentra em estatística. C e C++ são comuns em sistemas operacionais, motores de jogos e código embarcado. Java e C# são comuns em grandes sistemas empresariais; Bash é útil para automatizar tarefas do sistema operacional.

Essas são associações práticas, não territórios exclusivos. Uma biblioteca adequada pode economizar muito mais trabalho de desenvolvimento do que uma pequena diferença na sintaxe da linguagem.

Examine as necessidades de recursos e segurança

Um microcontrolador com apenas alguns kilobytes de RAM pode precisar de controle detalhado sobre a memória e a sobrecarga de execução. C, C++ ou Rust podem ser adequadas quando o alvo oferece suporte a elas. Componentes exigentes de jogos ou bancos de dados também costumam favorecer essas linguagens, mas motores e limites de recursos variam: Unity usa C#, e alguns bancos de dados usam Java ou Go.

Antes de escolher pela velocidade, encontre o gargalo: a parte que limita o desempenho. Um programa que espera principalmente por uma resposta da rede não ficará muito mais rápido apenas porque um pequeno laço local é compilado com mais eficiência. Meça uma carga de trabalho representativa na plataforma-alvo.

A segurança de memória reduz erros como acesso inválido à memória. Rust seguro verifica regras de propriedade em tempo de compilação; linguagens gerenciadas como Java, C#, Go e Python oferecem gerenciamento de memória em tempo de execução. A tipagem estática detecta muitas operações incompatíveis antes da execução, o que pode ajudar em uma base de código grande. Nem a segurança de memória nem a tipagem estática provam que a lógica do programa está correta.

Inclua o custo de manter o resultado

O conhecimento da equipe, os desenvolvedores disponíveis, depuradores, ferramentas de teste, bibliotecas, licenças e código existente afetam a decisão. Uma linguagem conhecida e com suporte pode reduzir os custos de entrega e manutenção. Reescrever um sistema que funciona cria trabalho de migração e riscos, mesmo que a nova linguagem tenha recursos atraentes.

Em uma rede meteorológica de uma cidade, componentes diferentes têm necessidades diferentes:

ComponenteEscolha razoável de linguagemMotivo
Firmware de pequenos sensoresC ou RustControle de memória e suporte aos alvos de hardware
Leituras armazenadasSQLFiltragem e agregação no banco de dados
Limpeza e análisePythonBibliotecas de processamento de dados
Exibição interativa no navegadorJavaScript ou TypeScriptSuporte do navegador e ferramentas de interface

Uma justificativa útil relaciona cada escolha a um requisito: “A equipe já mantém o serviço Java, e suas bibliotecas oferecem suporte à nova funcionalidade”. Uma posição em um ranking de popularidade, sozinha, não fornece esse raciocínio. Pesquisas podem indicar uma comunidade grande ou um grupo amplo de profissionais disponíveis para contratação; elas não estabelecem a melhor linguagem para uma carga de trabalho específica.

Principais conclusões

  • Verifique as restrições da plataforma antes das preferências.
  • Bibliotecas e ferramentas conectam uma linguagem a seu domínio de problema.
  • Meça o gargalo antes de escolher principalmente pela velocidade.
  • A segurança de memória e a verificação estática reduzem algumas falhas, mas a lógica ainda precisa de testes.
  • As habilidades da equipe, o código existente e os custos de manutenção fazem parte da decisão.
  • Componentes diferentes podem justificar linguagens diferentes.

Exercícios resolvidos de Paradigmas de programação

6 Lições Interativas

1

Acompanhe um programa do código-fonte à execução

13 passos
Acompanhe as camadas de tradução, examine uma construção e compare caminhos de execução.
  1. 1
    IntroduçãoUm cálculo atravessa várias camadas

    Você escreve um cálculo, mas um processador precisa de pequenas instruções codificadas chamadas código de máquina. Um tradutor fornece os detalhes que faltam.

    Acompanhe x = a + b do código-fonte para os níveis inferiores e depois acompanhe o programa durante a construção e a execução. Mantenha duas perguntas separadas: em que forma está o código e qual software o executa? Um compilador, um interpretador e um compilador JIT respondem à segunda de maneira diferente. Essa distinção explicará tanto a portabilidade quanto o momento em que os erros ocorrem.

  2. 2
    ExemploUm cálculo se torna várias ações

    Para x = a + b, imagine quatro ações de nível mais baixo:

    1. Carregar o valor de a em um registrador.
    2. Carregar o valor de b em outro registrador.
    3. Somar os valores dos dois registradores.
    4. Armazenar o resultado para x.

    A instrução de alto nível oculta a seleção de registradores e detalhes de armazenamento. As instruções geradas podem ser diferentes porque um compilador pode otimizar o trabalho. O cálculo exigido permanece o mesmo.

  3. 3
    ExplicaçãoInstruções legíveis ainda são destinadas a um processador

    A linguagem de montagem usa nomes de instruções como mov e add. Um montador os codifica como instruções de máquina.

    Para x86-64, mov eax, 1 coloca 1 em EAX; add eax, ebx soma EBX a ele. Esses registradores e codificações pertencem àquela arquitetura do conjunto de instruções. Substituir bytes por nomes legíveis ajuda uma pessoa, mas não transforma as instruções em uma descrição portável de alto nível.

  4. 4
    DesafioTransfira o programa para outro processador

    Uma equipe tem código-fonte C portável e um executável nativo x86-64. Ela precisa de um executável nativo ARM. Qual artefato pode usar como ponto de partida e que trabalho é necessário?

    ---reveal---

    Use o código-fonte e compile-o para o alvo ARM, com o suporte necessário da plataforma. O executável x86-64 contém instruções para uma ISA diferente. A portabilidade do código-fonte permite outra tradução; ela não transforma aquelas instruções de máquina existentes em código nativo ARM.

  5. 5
    ExemploIdentifique as unidades léxicas antes da estrutura

    Considere total = price * 3; em C.

    A análise léxica encontra seis unidades: total, =, price, *, 3 e ;. A análise sintática então agrupa price * 3 como a expressão cujo resultado é atribuído a total.

    São tarefas distintas. A análise léxica identifica as partes; a análise sintática estabelece como elas se encaixam. A análise semântica pode então verificar questões como se os nomes foram declarados e se os tipos dos operandos são compatíveis.

  6. 6
    ExemploTransfira cálculos constantes para a construção

    A expressão no código-fonte 60 * 60 * 24 usa apenas constantes. O dobramento de constantes pode avaliá-la antes da execução: primeiro 3.600, depois 86.400.

    A geração de código pode usar esse resultado em vez de instruções que repetem as multiplicações. Isso é uma otimização porque altera como o trabalho é realizado, preservando o resultado exigido. Substituir um valor por uma aproximação apenas conveniente não seria justificado por essa regra.

  7. 7
    ExplicaçãoConecte arquivos traduzidos a rotinas de biblioteca

    A compilação pode produzir arquivos objeto separados. Um ligador conecta esses arquivos e resolve referências a operações de bibliotecas.

    A ligação estática inclui código de biblioteca no executável construído. A ligação dinâmica depende de uma biblioteca compartilhada compatível carregada posteriormente. Nenhuma das abordagens exige que a máquina do usuário tenha o compilador antecipado, mas um executável ligado dinamicamente ainda precisa de suas dependências de execução.

  8. 8
    DesafioUma construção bem-sucedida dá a resposta errada

    Um programa C é construído com sucesso. Seu relatório deveria subtrair uma quantidade usada de uma quantidade inicial, mas o código as soma. O compilador deixou de verificar uma regra da linguagem?

    ---reveal---

    Não. A adição pode ter sintaxe válida e tipos compatíveis, mas expressar a tarefa errada. Isso é um erro de lógica. O compilador verifica as regras da linguagem e gera código; é necessário um teste com resultado esperado conhecido para revelar a diferença entre o cálculo e sua finalidade pretendida.

  9. 9
    ExplicaçãoUm ambiente de execução pode executar instruções intermediárias

    O CPython compila um arquivo Python válido em bytecode e depois executa esse bytecode em sua máquina virtual. O processador executa as instruções de máquina do ambiente de execução enquanto o ambiente realiza as operações Python.

    Java também usa bytecode: javac produz arquivos de classe para uma JVM compatível. Bytecode pertence a um modelo de instruções de um ambiente de execução, então não deve ser confundido com um conjunto universal de instruções de processador.

  10. 10
    ExemploCompare dois momentos em que a execução pode parar

    Neste arquivo Python válido, a primeira linha é executada:

    print("start")
    x = 10
    print(x + "5")
    

    Ele imprime “start” antes que a última linha gere TypeError. Agora remova o último parêntese de fechamento: o CPython não consegue compilar o arquivo, então nenhuma instrução desse arquivo é executada. Uma falha de sintaxe durante a preparação e uma falha de tipo durante uma operação executada param o programa em etapas diferentes.

  11. 11
    ExplicaçãoCompile código muito usado durante a execução

    Um compilador JIT produz código de máquina enquanto o programa é executado. Um ambiente de execução pode primeiro interpretar, observar métodos usados com frequência e compilá-los.

    Isso desloca os custos: a compilação usa tempo e memória durante a execução, enquanto a execução repetida pode ficar mais rápida depois. Um serviço de longa duração pode se beneficiar mais desse aquecimento do que um pequeno script executado uma única vez. O resultado depende da implementação e da carga de trabalho.

  12. 12
    DesafioSepare a portabilidade do bytecode da velocidade nativa

    Um arquivo de classe Java é executado por uma JVM compatível em duas ISAs diferentes. Durante cada execução, o ambiente compila um método usado com frequência. Por que um arquivo de classe pode ser compartilhado enquanto o código de máquina gerado é diferente?

    ---reveal---

    O arquivo de classe contém bytecode da JVM, que ambos os ambientes de execução compatíveis compreendem. Cada JIT tem como alvo seu próprio processador hospedeiro. A portabilidade vem do formato intermediário compartilhado e dos ambientes de execução compatíveis; as instruções de máquina produzidas durante a execução continuam específicas da plataforma.

  13. 13
    RecapitulaçãoMantenha separados os níveis e os caminhos de execução

    O código de máquina tem uma ISA como alvo; a linguagem de montagem dá nomes legíveis às suas instruções; o código-fonte de alto nível oculta mais detalhes do hardware.

    Um compilador traduz código, um interpretador o executa e um JIT compila durante a execução. Um ambiente de execução pode combinar esses métodos. Para explicar um sistema real, identifique suas formas de código-fonte, intermediária e nativa; depois, diga qual etapa verifica a sintaxe e qual executa a operação que pode falhar.

2

Rastreie o significado em um programa curto

10 passos
Leia a gramática, a ordem de avaliação, as mudanças de estado e os limites das funções.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoForma e significado respondem a perguntas diferentes
  2. 2
    ExemploCódigo válido pode agrupar o cálculo errado
  3. 3
    ExplicaçãoA primeira condição pode proteger a segunda
  4. 4
    DesafioPreveja se a divisão é executada
  5. 5
    ExplicaçãoUma atribuição lê antes de atualizar
  6. 6
    ExemploRastreie cada atualização do acumulador
  7. 7
    ExemploAcompanhe uma chamada e seu valor de retorno
  8. 8
    DesafioUma atualização local pode deixar quem chamou a função inalterado
  9. 9
    DesafioAcompanhe gramática e estado juntos
  10. 10
    RecapitulaçãoLeia o caminho executado
3

Acompanhe tipos e objetos compartilhados

14 passos
Compare o momento da verificação, acompanhe referências compartilhadas e decida quando a memória pode ser liberada.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoAcompanhe valores, objetos e armazenamento
  2. 2
    ExplicaçãoValores tipados podem passar de um nome a outro
  3. 3
    Ideia-chaveInferência e conversão respondem a perguntas diferentes
  4. 4
    DesafioLeia os algarismos como o valor realmente retornado
  5. 5
    ExplicaçãoA representação afeta os resultados numéricos
  6. 6
    ExplicaçãoUm registro de chamada pode terminar antes de um objeto
  7. 7
    ExemploDois nomes apontam para uma lista
  8. 8
    DesafioUm parâmetro pode alterar um objeto e depois apontar para outro
  9. 9
    ExplicaçãoIdentifique exatamente como o armazenamento é usado incorretamente
  10. 10
    ExemploAcompanhe as referências até a última desaparecer
  11. 11
    Ideia-chaveUm ciclo pode não ter ponto de acesso ativo
  12. 12
    ExplicaçãoA propriedade verifica o acesso antes da execução
  13. 13
    DesafioDescubra por que a coleta não pode recuperar uma lista
  14. 14
    RecapitulaçãoAcompanhe três decisões diferentes
4

Proteja regras e escolha relações entre objetos

12 passos
Rastreie comandos dependentes da ordem e depois use encapsulamento, sobrescrita de métodos e composição.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoPassos precisam de limites confiáveis
  2. 2
    ExemploA ordem das operações altera o estado compartilhado
  3. 3
    ExplicaçãoTorne útil o limite de um procedimento
  4. 4
    DesafioPreveja uma atualização em outra ordem
  5. 5
    ExplicaçãoDê a cada conta seu próprio estado
  6. 6
    ExemploValide antes de alterar um saldo
  7. 7
    DesafioLocalize uma regra contornada
  8. 8
    ExemploEspecialize sem quebrar o contrato
  9. 9
    ExplicaçãoDeixe o objeto escolher seu método de área
  10. 10
    ConexãoMantenha o componente que faz o trabalho
  11. 11
    DesafioEscolha duas relações em um novo projeto
  12. 12
    RecapitulaçãoAssocie limites a responsabilidades
5

Transforme uma coleção e consulte um resultado

13 passos
Acompanhe uma sequência de operações funcionais e uma consulta declarativa, verificando efeitos e execução.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoDeclare uma transformação ou uma solicitação
  2. 2
    ExplicaçãoA pureza descreve mais que o número retornado
  3. 3
    ExemploSelecione primeiro os valores pares
  4. 4
    ExemploTransforme e combine os valores selecionados
  5. 5
    DesafioAplique a sequência a um novo caso
  6. 6
    ExplicaçãoRetorne um comportamento que lembra seu fator
  7. 7
    ExemploReduza o problema até que um caso direto se aplique
  8. 8
    ExemploLeia SQL como uma solicitação sobre linhas
  9. 9
    Ideia-chaveMantenha o resultado enquanto altera o plano
  10. 10
    ExemploConecte dois fatos por meio de uma regra
  11. 11
    DesafioLeia uma nova consulta incluindo seu valor-limite
  12. 12
    DesafioCombine uma consulta com uma transformação pura
  13. 13
    RecapitulaçãoMantenha visível a relação entre entrada e resultado
6

Compare estilos e justifique a escolha de uma linguagem

10 passos
Mantenha uma tarefa fixa e depois escolha ferramentas para componentes com restrições diferentes.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoMantenha a tarefa fixa
  2. 2
    ExemploMantenha a tarefa fixa enquanto muda o estilo
  3. 3
    ExemploColoque a mesma regra em objetos ou em uma consulta
  4. 4
    DesafioDecida se um projeto tem um único estilo exclusivo
  5. 5
    ExplicaçãoElimine primeiro os alvos sem suporte
  6. 6
    ExemploEscolha para um sistema meteorológico completo
  7. 7
    DicaMeça o que consome o tempo
  8. 8
    DesafioDefenda uma escolha diante de restrições concorrentes
  9. 9
    DesafioConecte a escolha da linguagem à organização do programa
  10. 10
    RecapitulaçãoDê um motivo ligado ao requisito

Questões de Paradigmas de programação

50 Questões de Prática em 5 conjuntos

Conjunto 1: Leia código e identifique o caminho de execução10 Questões de Prática

Questão 1 de 10

Uma pessoa desenvolvedora substitui bytes brutos de instruções por nomes como mov e jmp. Em qual nível de linguagem o programa está escrito agora?
  1. Código de máquina
  2. Linguagem de montagem
  3. Linguagem de alto nível
  4. Linguagem de consulta a bancos de dados
Mostrar respostaOcultar resposta
Resposta: B
A linguagem de montagem escreve operações do nível do processador com mnemônicos e nomes de registradores, tornando-as mais fáceis de ler. Ela continua vinculada a uma arquitetura do conjunto de instruções. Nomes legíveis, por si só, não a tornam uma linguagem de alto nível, pois quem programa ainda especifica operações individuais de baixo nível.

Questão 2 de 10

Durante uma construção, uma ferramenta conecta vários arquivos objeto e resolve uma chamada a uma rotina de biblioteca. Qual ferramenta realiza essa tarefa?
  1. Um montador
  2. Um analisador sintático
  3. Um interpretador
  4. Um ligador
Mostrar respostaOcultar resposta
Resposta: D
Um ligador conecta partes traduzidas e resolve referências a código definido em outro lugar, incluindo rotinas de biblioteca. Um compilador pode ter produzido os arquivos objeto antes, durante a construção. A análise sintática verifica a estrutura do código-fonte; ela não conecta os arquivos resultantes em um programa completo.

Questão 3 de 10

Um arquivo Python contém uma instrução de impressão na primeira linha e um parêntese não fechado na última. O que acontece quando o CPython executa o arquivo?
  1. Ele informa um erro de sintaxe antes de executar a impressão.
  2. Ele imprime a primeira linha e depois informa um erro de sintaxe.
  3. Ele imprime a primeira linha e depois gera um erro de execução.
  4. Ele pula apenas a última linha e executa as linhas válidas.
Mostrar respostaOcultar resposta
Resposta: A
O CPython precisa compilar o arquivo antes de executar suas instruções, e o parêntese não fechado impede essa preparação. Portanto, a primeira impressão nunca é executada. Imprimir antes da falha seria compatível com um erro em tempo de execução em código válido, mas esse arquivo tem um erro de sintaxe detectado antes.

Questão 4 de 10

Um programa Python válido imprime a média errada porque soma um valor depois de reduzir o outro à metade. Que tipo de erro é esse?
  1. Um erro de sintaxe
  2. Um erro em tempo de execução
  3. Um erro de lógica
  4. Um erro de incompatibilidade de tipos
Mostrar respostaOcultar resposta
Resposta: C
As operações são concluídas, mas seu significado não corresponde à média pretendida. Isso torna a falha um erro de lógica. Um erro de sintaxe desrespeitaria a gramática da linguagem, enquanto um erro em tempo de execução envolveria a falha de uma operação executada; nenhum dos dois é mencionado nesse cálculo bem-sucedido, mas incorreto.

Questão 5 de 10

Qual valor final este código Python armazena em total?

total = 1
for n in [4, 7, 2]:
    total = total + n
  1. 13
  2. 3
  3. 15
  4. 14
Mostrar respostaOcultar resposta
Resposta: D
O total começa em 1, depois passa a 5 ao somar 4, a 12 ao somar 7 e a 14 ao somar 2. O laço mantém o resultado de cada atualização anterior. Obter 13 significaria ignorar o valor inicial e calcular apenas a soma da lista, que não é todo o cálculo escrito aqui.

Questão 6 de 10

Em Python, label = "18" é seguido de label = 18. O nome pode mudar de tipo, mas "18" + 2 gera um erro. Como esses dois fatos são descritos?
  1. Tipagem estática com regras estritas para adição de tipos mistos
  2. Tipagem dinâmica com regras estritas para adição de tipos mistos
  3. Tipagem dinâmica com adição numérica automática
  4. Tipagem estática com adição numérica automática
Mostrar respostaOcultar resposta
Resposta: B
Associar novamente um nome de texto a um inteiro é compatível com a tipagem dinâmica, enquanto a adição que falha mostra que essa operação não converte silenciosamente o texto em número. O momento da verificação e o comportamento da conversão são questões separadas. Tipagem dinâmica não significa que tipos incompatíveis sejam combinados automaticamente.

Questão 7 de 10

O que first contém após a execução deste código Python?

first = [4, 8]
second = first
second.append(6)
  1. [4, 8]
  2. [6]
  3. [4, 8, 6]
  4. [4, 8, [6]]
Mostrar respostaOcultar resposta
Resposta: C
Ambos os nomes se referem a uma lista porque atribuir a referência não copia seu conteúdo. Acrescentar um elemento por meio de second altera a lista compartilhada, então first também mostra o 6 acrescentado. Para manter [4, 8], seria necessária uma lista separada, como uma criada por cópia antes do acréscimo.

Questão 8 de 10

Um programa de relatórios é dividido em read_data(), calculate_total() e save_report(), cada uma contendo comandos ordenados. Que organização isso exemplifica?
  1. Programação procedural
  2. Programação lógica
  3. Programação funcional pura
  4. Herança de classes
Mostrar respostaOcultar resposta
Resposta: A
A programação procedural agrupa passos imperativos em tarefas nomeadas, exatamente como essas operações de relatório estão organizadas. Os nomes das funções, por si só, não caracterizam programação funcional pura. Em particular, ler e salvar são efeitos, e nada na descrição exige que os cálculos evitem alterações compartilhadas.

Questão 9 de 10

Uma função Python recursiva retorna 0 em n == 0; caso contrário, chama a si mesma com n + 1. Começando no valor positivo 2, por que o caso base escrito não faz o cálculo terminar?
  1. As entradas chegam a zero, mas retornar zero reinicia a recursão.
  2. O caso base garante a terminação mesmo com entradas crescentes.
  3. A recursão retorna em direção a zero antes de verificar o caso base.
  4. As entradas recursivas se afastam de zero em vez de alcançá-lo.
Mostrar respostaOcultar resposta
Resposta: D
As entradas sucessivas são 2, 3, 4 e assim por diante, então nenhuma satisfaz o teste de parada. Um caso base precisa ser realmente alcançado pela progressão recursiva; sua presença no código-fonte, por si só, não basta. Retornar 0 nesse ramo não alcançado não pode alterar os argumentos fornecidos pelas chamadas que são executadas.

Questão 10 de 10

Uma requisição de painel realiza dois passos em sequência: 180 ms esperando dados da rede e depois 20 ms formatando-os localmente. Uma nova linguagem reduziria a formatação para 5 ms sem alterar a espera pela rede. Qual é a melhor avaliação dessa mudança isolada?
  1. Ela economiza 15 ms, e a rede continua sendo o principal atraso.
  2. Ela economiza 150 ms, aplicando o aumento de velocidade de quatro vezes à requisição.
  3. Ela economiza 5 ms, pois o novo tempo de formatação é a economia.
  4. Ela economiza 20 ms, pois a compilação elimina a etapa de formatação.
Mostrar respostaOcultar resposta
Resposta: A
O trabalho sequencial descrito cai de 200 ms para 185 ms, economizando 15 ms. A espera de 180 ms pela rede não muda e continua dominando a requisição. Aplicar o aumento de velocidade de quatro vezes do formatador a toda a requisição pressuporia incorretamente que a mesma melhoria também afeta a espera pela rede.
Conjunto 2: Aplique regras de tipos, objetos e consultas10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Uma equipe pode compilar o mesmo código-fonte portável para duas ISAs diferentes. Qual é a relação correta entre o código-fonte e os resultados nativos?
  1. O código-fonte é compartilhado; ambos os resultados contêm código da mesma ISA.
  2. O código-fonte deve ser diferente; cada resultado tem sua própria ISA como alvo.
  3. O código-fonte é compartilhado; cada resultado tem sua própria ISA como alvo.
  4. O código-fonte deve ser diferente; ambos os resultados contêm código da mesma ISA.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Um ambiente de execução traduz um método executado com frequência em instruções nativas depois que a aplicação iniciou. Que técnica é essa?
  1. Compilação em tempo de execução
  2. Compilação antecipada
  3. Ligação estática
  4. Análise léxica
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Com a = 8 e b = 12 em Python, quais valores a + b / 2 e (a + b) / 2 produzem, respectivamente?
  1. 10,0 e 10,0
  2. 14,0 e 14,0
  3. 10,0 e 14,0
  4. 14,0 e 10,0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Uma função C libera um bloco alocado, mas um código posterior o lê por meio de um ponteiro antigo. Que falha essa leitura posterior cria?
  1. Um vazamento de memória
  2. Um uso após liberação
  3. Uma liberação dupla
  4. Um estouro de buffer
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Uma classe define um atributo balance e um método deposit. Duas contas são criadas com saldos iniciais diferentes. Onde ficam esses saldos distintos?
  1. Na definição compartilhada de depósito
  2. Nas duas instâncias de conta
  3. No próprio nome da classe
  4. Nos nomes dos parâmetros do construtor
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um objeto bicicleta armazena um objeto sistema de freios e solicita que ele pare as rodas. Qual relação corresponde melhor a esse projeto?
  1. Composição: a bicicleta tem um sistema de freios.
  2. Herança: a bicicleta é um sistema de freios.
  3. Sobrescrita: a bicicleta substitui o sistema de freios.
  4. Tipagem por comportamento: a bicicleta é uma subclasse do sistema de freios.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

A partir de [1, 4, 7, 10], uma sequência de operações mantém valores maiores que 3, subtrai 1 de cada valor mantido e depois os soma. Qual é o resultado?
  1. 21
  2. 17
  3. 18
  4. 22
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Um banco de dados pode executar uma consulta SQL inalterada varrendo linhas ou usando um índice disponível. O que escolhe entre esses planos?
  1. O analisador de consultas SQL
  2. O ligador do banco de dados
  3. O verificador de tipos do SQL
  4. O otimizador de consultas
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um método de classe em Python usa um laço para atualizar uma lista e uma função pura para transformar cada elemento. Como seus estilos devem ser descritos?
  1. Organização por objetos que exclui trabalho imperativo
  2. Organização funcional que torna todo método puro
  3. Organização por objetos com trabalho imperativo e funcional
  4. Organização imperativa que impede métodos de objetos
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Quais são os valores de x e y após este código Python?

x = [12]
y = x
x = x + [3]
y.append(6)
  1. x é [12, 3]; y é [12, 6].
  2. x é [12, 3, 6]; y é [12, 3, 6].
  3. x é [12, 3, 6]; y é [12].
  4. x é [12, 6]; y é [12, 3].
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 3: Rastreie chamadas, efeitos e contratos de objetos10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Um compilador substitui minutes = 60 * 12 pelo valor pré-calculado 720. Qual otimização é usada e o que ela deve preservar?
  1. Ligação dinâmica, preservando nomes de arquivos de biblioteca
  2. Análise léxica, preservando cada caractere do código-fonte
  3. Aquecimento JIT, preservando contagens de chamadas em execução
  4. Dobramento de constantes, preservando o comportamento exigido
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

O que este programa Python imprime?

score = 11
def change():
    score = 4
    return score + 2
print(change())
print(score)
  1. 6, depois 4
  2. 6, depois 11
  3. 4, depois 11
  4. 13, depois 11
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Qual é o valor de items após a chamada?

items = [3]
def revise(values):
    values.append(8)
    values = [1]
    values.append(5)
revise(items)
  1. [3, 8]
  2. [1, 5]
  3. [3, 8, 5]
  4. [3]
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Dois objetos se referem um ao outro. Todas as referências das raízes ativas do programa a esses objetos são removidas. Por que a contagem de referências sozinha pode deixar de detectá-los, enquanto o rastreamento pode recuperá-los?
  1. As contagens internas chegam a zero, então o rastreamento é necessário para liberar objetos com contagem zero.
  2. As contagens internas permanecem positivas, então ambos os objetos ainda devem ser alcançáveis.
  3. As contagens internas permanecem positivas, mas nenhum dos objetos é alcançável.
  4. As contagens internas são iguais ao número de raízes, então ambos os objetos têm contagem zero.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Partindo de 120, uma operação aplica um desconto de 25% e outra soma 8. Quais valores finais resultam de aplicar o desconto e depois somar, e de somar e depois aplicar o desconto?
  1. 96 e 98
  2. 98 e 98
  3. 98 e 96
  4. 96 e 96
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um método de conta verifica um valor de saque antes de alterar o saldo armazenado. Qual ideia de projeto mantém essa regra junto da atualização que ela controla?
  1. Encapsulamento
  2. Herança
  3. Dobramento de constantes
  4. Inferência de tipos
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma conta especializada cobra uma tarifa de 2 por cada saque bem-sucedido e nunca deve ter saldo negativo. Seu saldo é 43. O que deve acontecer com uma solicitação de saque de 42?
  1. Aceitar a solicitação e deixar o saldo em 1.
  2. Aceitar a solicitação e deixar o saldo em −1.
  3. Recusar a solicitação após reduzir o saldo para 1.
  4. Recusar a solicitação e manter o saldo em 43.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Uma função retorna um cálculo fixo a partir de seus argumentos e também imprime uma mensagem de registro. Ela é pura? Por quê?
  1. Sim; seu resultado é previsível.
  2. Não; imprimir é um efeito colateral.
  3. Sim; registrar mensagens não é um dado armazenado.
  4. Não; todo código tem efeitos colaterais.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Uma página web registra showDetails para um evento de clique. Por que esse mesmo tratador ainda pode usar métodos de objetos e atribuições imperativas?
  1. Eventos o tornam orientado a objetos apenas se ele alterar atributos.
  2. Eventos determinam quando ele é executado; seu corpo pode usar outros estilos.
  3. Eventos exigem funções de retorno de chamada puras, então atribuições devem ser movidas.
  4. Eventos o tornam declarativo, então o mecanismo escolhe suas atualizações.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Uma nova funcionalidade de análise precisa usar uma biblioteca consolidada disponível apenas em Python, e a equipe já mantém serviços Python dentro do limite de recursos exigido. Quais fatos informados apoiam a escolha de Python?
  1. Velocidade bruta de laços e ausência de custos de memória em execução
  2. Compatibilidade com navegadores e anotações de tipo Python impostas
  3. Aritmética exata em ponto flutuante e todos os alvos de hardware
  4. Suporte à biblioteca exigida e experiência existente da equipe
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 4: Raciocine entre limites de execução e de projeto10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Uma instrução de alto nível se torna uma quantidade diferente de instruções em duas construções otimizadas, mas ambas atendem ao mesmo comportamento da linguagem. A diferença na quantidade de instruções torna alguma das construções incorreta?
  1. Não; uma instrução não precisa corresponder a uma quantidade fixa de instruções.
  2. Sim; cada instrução deve corresponder a uma instrução de máquina.
  3. Sim; a portabilidade de alto nível exige resultados nativos idênticos.
  4. Não; resultados otimizados podem alterar os resultados exigidos.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Um executável nativo usa ligação dinâmica. A pessoa que o recebe não tem um compilador nem uma biblioteca compartilhada compatível exigida. Qual dessas ausências explica por que ele pode não iniciar?
  1. O compilador antecipado está ausente.
  2. O arquivo-fonte original está ausente.
  3. A biblioteca compartilhada exigida está ausente.
  4. A árvore de análise sintática do compilador está ausente.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Uma JVM compatível executa o mesmo arquivo de classe em duas ISAs e compila com JIT seu método mais usado em cada uma. Uma pessoa da equipe afirma que isso prova que o arquivo de classe já contém instruções nativas para ambos os processadores. Qual é o diagnóstico correto?
  1. O arquivo contém código nativo; a JVM apenas liga bibliotecas da plataforma.
  2. O arquivo contém bytecode; o resultado do JIT também independe da ISA.
  3. O arquivo contém texto-fonte; a JVM precisa recompilar o código-fonte original.
  4. O arquivo contém bytecode; cada JIT gera seu próprio código nativo.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Com n = 0 em Python, compare n != 0 and 36 / n > 4 com 36 / n > 4 and n != 0. O que acontece?
  1. Ambas são falsas porque contêm os mesmos dois testes.
  2. A primeira é falsa; a segunda gera um erro de divisão.
  3. Ambas geram um erro de divisão porque mencionam divisão.
  4. A primeira gera um erro; a segunda é falsa.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Qual valor final é armazenado em total?

def bump(x):
    return x + 2
total = 0
for n in [2, 5, 8]:
    if n > 3:
        total = total + bump(n)
  1. 17
  2. 15
  3. 19
  4. 21
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um compilador infere todos os tipos das variáveis locais antes da execução, embora quem programa não escreva anotações de tipo. Que abordagem de verificação isso demonstra?
  1. Tipagem dinâmica com inferência em tempo de execução
  2. Tipagem fraca sem informações de tipo
  3. Tipagem dinâmica com conversão explícita
  4. Tipagem estática com inferência de tipos
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma linguagem de tipagem estática infere um tipo inteiro a partir de um valor inicial e permite uma determinada conversão numérica implícita. O que se pode concluir sobre esses dois recursos?
  1. A inferência torna essas variáveis dinâmicas apesar da verificação estática.
  2. A conversão implícita significa que a variável inferida não tem tipo fixo.
  3. Inferência e conversão podem coexistir com a verificação estática.
  4. A verificação estática significa que a conversão deve ser escrita explicitamente.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Um relatório percorre objetos em um laço e chama item.render(). Uma nova classe sem relação com as outras fornece um método render() compatível. O código Python que faz a chamada não exige uma superclasse. Por que o objeto pode participar do laço sem um ramo específico para seu tipo?
  1. Uma herança comum é necessária para que seu método seja executado.
  2. A tipagem por comportamento aceita o objeto; o despacho seleciona seu método.
  3. Nomes de métodos iguais fazem todos os objetos executarem um corpo compartilhado.
  4. Quem chama deve selecionar a classe concreta antes da chamada.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Uma função cria e retorna outra função que multiplica sua entrada pelo fator fornecido à primeira. O fator foi 4, e a função retornada é chamada depois com 7. Qual resultado e mecanismo explicam isso?
  1. 28, usando um registro de ativação
  2. 11, usando um fechamento
  3. 28, usando um fechamento
  4. 28, usando uma conversão de tipo
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Uma equipe trata um ranking de popularidade como evidência suficiente de que uma linguagem pode ser executada no alvo de implantação exigido. Qual verificação de compatibilidade está faltando?
  1. Se a plataforma-alvo oferece suporte ao ambiente de execução necessário
  2. Se tutoriais usam a indentação preferida pela equipe
  3. Se seu nome ocupa uma posição alta em outra pesquisa de popularidade
  4. Se ele tem um histórico de lançamentos mais curto que o das alternativas
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 5: Diagnostique limites e combine as ideias10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Uma implementação baseada em interpretador gasta tempo executando bytecode. Uma segunda implementação da mesma linguagem acrescenta um JIT e fica mais rápida em um cálculo longo e repetido. Qual conclusão decorre disso sem alterar a definição da linguagem?
  1. A linguagem passou a ter tipagem estática porque ganhou um compilador.
  2. A estratégia de execução mudou, enquanto a semântica da linguagem pode permanecer igual.
  3. O primeiro ambiente executava o código-fonte diretamente, então o bytecode não foi compilado.
  4. A linguagem compartilhada precisa alterar o significado dos operadores para ganhar velocidade.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

O que este programa Python imprime?

value = 6
def step(value):
    value = value + 3
    return value
for n in [1, 2]:
    result = step(value)
print(value, result)
  1. 12 12
  2. 6 12
  3. 6 9
  4. 9 9
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Um serviço com coleta de lixo armazena cada registro processado em uma lista global que permanece alcançável. Remover variáveis locais não reduz a memória ocupada pelos registros mantidos. Qual mudança resolve a causa descrita?
  1. Remover da lista global as referências a registros obsoletos.
  2. Invocar a coleta repetidamente sem alterar o arquivo global.
  3. Excluir cada associação local e preservar as entradas do arquivo global.
  4. Substituir a lista de arquivo por uma cópia com os mesmos registros.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Dois procedimentos atualizam o mesmo valor global. double() o multiplica por 2; add_three() soma 3. Partindo do mesmo valor, qual é sempre a diferença entre dobrar e depois somar, e somar e depois dobrar?
  1. Dobrar e depois somar produz um resultado 3 maior.
  2. Somar e depois dobrar produz um resultado 6 maior.
  3. As duas ordens sempre produzem o mesmo resultado.
  4. Somar e depois dobrar produz um resultado 3 maior.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Uma classe Java marca seu campo de saldo como privado e oferece um método de saque com validação. Código em uma classe externa sem relação com ela tenta atribuir diretamente um saldo negativo. O que protege a regra da classe nesse ponto de acesso?
  1. O método de saque valida atribuições diretas.
  2. A atribuição cria um saldo local separado.
  3. O acesso privado permite escritas, mas bloqueia todas as leituras.
  4. O compilador rejeita o acesso ao campo privado.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Uma classe herda de uma fila apenas para reutilizar armazenamento, mas desativa a operação de remoção da fila. O código que espera uma fila utilizável falha. Qual reformulação resolve o problema de relação descrito?
  1. Manter a herança da fila e ocultar a remoção da interface da subclasse.
  2. Manter uma fila internamente e oferecer as operações prometidas pela classe.
  3. Manter a herança da fila e fazer a remoção não fazer nada silenciosamente.
  4. Manter a herança da fila e documentar que quem chama deve evitar a remoção.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma tabela readings tem as linhas (level, active): (50, true), (65, true), (20, false), (85, true) e (90, false). Uma consulta solicita SUM(level) onde active = true AND level > 50. Após a adição de um índice adequado, qual total é exigido e o plano de execução pode mudar?
  1. 150; o plano pode mudar, preservando o total.
  2. 200; o plano pode mudar, preservando o total.
  3. 240; o plano pode mudar, preservando o total.
  4. 150; o novo índice pode mudar o plano e o total.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Prolog tem parent(lea, noah). e parent(noah, sora)., além da regra grandparent(X, Z) :- parent(X, Y), parent(Y, Z).. O que grandparent(lea, Who) encontra?
  1. Who = noah
  2. Who = lea
  3. Who = sora
  4. Tanto Who = lea quanto Who = noah
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um laço imperativo, um método de objeto e uma consulta SQL devem somar os valores estritamente acima de 50 em [50, 65, 20, 85]. Quais devem ser seus resultados corretos?
  1. 200 nas três versões
  2. 150 nas três versões
  3. 150 para o laço, 200 para o método e a consulta
  4. 200 para o laço, 150 para o método e a consulta
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Uma função retorna uma lista recém-alocada, e quem a chamou armazena essa lista para uso posterior. A lista precisa desaparecer quando o registro de ativação da chamada é removido?
  1. Sim; encerrar a chamada anula a referência de quem a chamou.
  2. Sim; retornar copia seu conteúdo e a libera.
  3. Não; o registro que a criou deve permanecer ativo para mantê-la.
  4. Não; quem chamou a função ainda mantém uma referência à lista.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Travou em uma questão sua? Ajuda com Tarefas

Termos-Chave

94 Termos-Chave, cada um com sua definição

A

Abstração
Abstração é a ocultação de detalhes de implementação por trás de um modelo ou interface mais simples, para que quem programa possa se concentrar na tarefa expressa.
Uma variável é uma abstração que permite ao código nomear um valor sem especificar seu endereço físico de memória.
Alocação de memória
A alocação de memória reserva memória para dados do programa, e o armazenamento reservado permanece indisponível para reutilização até ser liberado.
Criar uma coleção que cresce pode exigir mais alocação à medida que ela armazena elementos adicionais.
Análise léxica
A análise léxica divide o texto-fonte em unidades léxicas significativas, incluindo identificadores, literais, operadores e sinais de pontuação.
Durante a análise léxica, price * 3 é separado em um nome, um operador de multiplicação e um número.
Análise sintática
A análise sintática verifica unidades léxicas em relação à gramática de uma linguagem e representa a estrutura de expressões e instruções.
A análise sintática registra que a multiplicação em total = price * 3; faz parte da atribuição.
Arquitetura do conjunto de instruções (ISA)
Uma arquitetura do conjunto de instruções define as operações oferecidas por uma família de processadores e as codificações usadas para representá-las.
Um executável x86-64 tem como alvo uma arquitetura do conjunto de instruções diferente da do código de máquina nativo ARM.
Atribuição
Atribuição é a associação de um nome ou destino de armazenamento a um valor, usando primeiro o estado atual para avaliar a expressão à direita.
A atribuição count = count + 1 lê a contagem antiga antes de substituí-la pelo valor incrementado.
Atributo
Um atributo é um dado ou membro nomeado associado a um objeto ou classe e acessado por meio dele.
O atributo de saldo de uma conta registra seu estado atual separadamente do saldo de outra conta.
Avaliação de curto-circuito
A avaliação de curto-circuito pula um operando lógico quando um operando anterior já determina o resultado da expressão.
A avaliação de curto-circuito permite que uma verificação de valor diferente de zero impeça uma divisão insegura no lado direito de and.

Á

Área de alocação dinâmica
A área de alocação dinâmica é a memória usada para objetos alocados dinamicamente, incluindo objetos que podem durar mais que a função que os criou.
Uma lista retornada pode permanecer na área de alocação dinâmica depois que a função que a criou terminou e seu registro de ativação foi removido.

B

Bytecode
Bytecode é uma forma intermediária de instruções projetada para um ambiente de execução de software, em vez da execução direta pelo processador hospedeiro.
O CPython gera bytecode a partir de um arquivo Python válido antes que sua máquina virtual execute o programa.

C

Caso base
Um caso base fornece diretamente um resultado recursivo, permitindo que um cálculo finito pare de fazer novas chamadas recursivas.
O caso base do fatorial retorna 1 para zero, permitindo que as chamadas de multiplicação pendentes terminem.
Classe
Uma classe define a estrutura e o comportamento de suas instâncias, descrevendo seus atributos e métodos disponíveis.
A classe Loan de uma biblioteca pode definir o membro, o livro, a data de devolução e a operação de renovação de cada empréstimo.
Código de máquina
Código de máquina é uma sequência de instruções de processador codificadas em binário que seguem uma arquitetura do conjunto de instruções específica.
O processador executa código de máquina depois que um compilador ou montador produz instruções compatíveis.
Coerção de tipos
Coerção de tipos é uma conversão implícita realizada pelas regras da linguagem quando uma operação precisa de um valor de outro tipo.
A coerção em JavaScript converte texto numérico para multiplicação, mas pode concatená-lo ao usar adição.
Coleta de lixo
A coleta de lixo identifica e recupera automaticamente o armazenamento de objetos que o programa em execução não consegue mais alcançar.
A coleta de lixo por rastreamento pode recuperar um grupo inalcançável mesmo quando seus objetos ainda se referem uns aos outros.
Compartilhamento por referências
O compartilhamento por referências ocorre quando nomes ou referências diferentes levam ao mesmo objeto, tornando suas alterações visíveis por meio de todos eles.
Após b = a, o compartilhamento por referências explica por que acrescentar um elemento por meio de b também pode alterar o que é visto por meio de a.
Compilação antecipada (AOT)
A compilação antecipada traduz código antes da execução do programa, transferindo o trabalho de tradução para uma etapa de construção separada.
A compilação antecipada pode produzir um executável nativo que funciona sem que seu compilador esteja instalado.
Compilação em tempo de execução (JIT)
A compilação em tempo de execução gera código de máquina nativo durante a execução de um programa, muitas vezes para partes usadas com frequência.
A compilação em tempo de execução pode melhorar um laço de longa duração depois que o ambiente de execução observa sua execução repetida.
Compilador
Um compilador traduz código-fonte em uma representação-alvo, que pode ser instruções nativas de máquina ou bytecode intermediário.
Um compilador pode rejeitar uma atribuição incompatível antes de produzir o programa que um usuário executará.
Completude de Turing
A completude de Turing é a capacidade de expressar qualquer algoritmo computável quando tempo e memória idealizados e ilimitados estão disponíveis.
A completude de Turing não informa a uma equipe se uma linguagem tem o suporte a navegadores ou as bibliotecas de que seu projeto precisa.
Composição
A composição constrói um objeto a partir de outros objetos que ele mantém como componentes ou colaboradores, expressando uma relação de posse.
A composição dá a um carro um objeto motor cujos métodos ele pode chamar sem se tornar um tipo de motor.
Contagem de referências
A contagem de referências acompanha as referências a um objeto e normalmente recupera seu armazenamento quando a contagem chega a zero.
A contagem de referências do CPython precisa do auxílio da coleta de ciclos para objetos inalcançáveis que mantêm referências uns aos outros.

D

Despacho dinâmico
O despacho dinâmico seleciona uma implementação de método durante a execução de acordo com o objeto que recebe a chamada.
O despacho dinâmico direciona shape.area() para a implementação de retângulo quando o objeto que recebe a chamada é um retângulo.
Dobramento de constantes
O dobramento de constantes avalia expressões formadas por constantes durante a compilação, preservando o comportamento exigido do programa.
O dobramento de constantes permite que um compilador substitua 60 * 60 * 24 por 86.400 antes de o programa iniciar.

E

Ecossistema
Um ecossistema é o conjunto de bibliotecas, ferramentas, documentação e apoio da comunidade disponíveis em torno de uma tecnologia.
Um ecossistema forte de análise de dados pode tornar Python uma opção prática para uma equipe, mesmo quando a velocidade bruta de laços não é sua principal vantagem.
Efeito colateral
Um efeito colateral é uma ação observável além de retornar um resultado, incluindo saída ou alterações em dados compartilhados do programa.
Escrever um arquivo de relatório é um efeito colateral mesmo que a função deixe inalterada toda variável numérica global.
Encapsulamento
O encapsulamento agrupa dados com operações controladas que mantêm suas regras e limitam alterações inseguras por outros códigos.
O encapsulamento permite que uma conta recuse um saque excessivo antes que seu saldo se torne negativo.
Erro de lógica
Um erro de lógica faz um programa calcular um resultado não pretendido, mesmo que suas operações executadas possam ser concluídas com sucesso.
Usar a + b / 2 para calcular a média de dois valores é um erro de lógica porque a divisão acontece antes da adição.
Erro em tempo de execução
Um erro em tempo de execução ocorre quando uma operação executada não pode prosseguir segundo as regras da linguagem ou as condições disponíveis.
Dividir por zero em Python produz um erro em tempo de execução quando a execução alcança esse cálculo.
Escopo
Escopo é a parte de um programa onde um nome específico está disponível, independentemente do tempo de vida do objeto ao qual ele se refere.
O escopo de um nome local não se estende a quem chamou a função apenas porque ela retorna o objeto nomeado.
Estouro
O estouro ocorre quando um resultado aritmético está fora da faixa suportada por uma representação numérica de largura fixa.
O estouro de inteiro em Java causa retorno cíclico após o valor máximo, enquanto o estouro de inteiro com sinal em C tem comportamento indefinido.
Estouro de buffer
Um estouro de buffer é uma escrita além dos limites de um bloco de armazenamento, que pode danificar dados vizinhos ou o estado do programa.
Escrever no índice 10 de um array C de dez elementos cria um estouro de buffer em vez de acrescentar um décimo primeiro elemento.
Expressão
Uma expressão é um trecho de código avaliado para produzir um valor, que pode então ser usado pela instrução que o contém.
A expressão width * height fornece o valor retornado por uma função de área de retângulo.
Expressão lambda
Uma expressão lambda é uma expressão de função sem um nome de função declarado, útil para fornecer uma pequena operação como valor.
A expressão lambda Python lambda n: n * n fornece uma operação de elevação ao quadrado a uma sequência de transformações.
Expressão regular
Uma expressão regular descreve um padrão de texto para um mecanismo de correspondência, usando regras para caracteres, repetição e agrupamento.
A expressão regular [0-9]{3}-[0-9]{4} corresponde a um grupo de três algarismos seguido de um hífen e quatro algarismos.

F

Fechamento
Um fechamento é uma função junto do acesso a variáveis de seu ambiente de criação, que pode durar mais que a chamada que a contém.
Um fechamento multiplicador preserva seu fator depois que a função que o criou retorna.
Função de ordem superior
Uma função de ordem superior recebe uma função como argumento ou retorna uma função, permitindo combinar o próprio comportamento.
A função de ordem superior filter recebe um predicado que decide quais valores de entrada manter.
Função de primeira classe
Uma função de primeira classe pode ser tratada como um valor, incluindo ser atribuída a um nome, passada como argumento e retornada por uma chamada.
Uma função de primeira classe pode ser fornecida a map como a operação a aplicar a cada elemento de uma coleção.
Função de retorno de chamada
Uma função de retorno de chamada é uma função fornecida a outro código para que esse código possa invocá-la quando uma operação ou evento exigir.
Passar showMenu como função de retorno de chamada permite que um sistema de botões chame essa função quando o usuário clica.
Função pura
Uma função pura retorna o mesmo resultado para os mesmos argumentos e não realiza efeitos colaterais além de retornar esse resultado.
Uma função pura para um multiplicador fixo pode ser testada sem preparar estado global ou capturar saída.

G

Gargalo
Um gargalo é a parte de um sistema que limita o desempenho geral, tornando menos eficazes as melhorias em outros pontos.
Se a espera pela rede é o gargalo, uma aritmética local mais rápida pode alterar muito pouco o tempo total da requisição.

H

Herança
A herança define uma classe especializada a partir de uma classe existente, reutilizando ou estendendo comportamentos por meio de uma relação de tipo.
A herança é adequada a uma conta de poupança que ainda deve satisfazer o comportamento esperado de uma conta bancária.

I

Implementação
Uma implementação de linguagem é o software que fornece uma forma de traduzir e executar programas escritos nessa linguagem.
CPython e PyPy são implementações diferentes, então código Python pode usar estratégias de execução diferentes.
Imutabilidade
Imutabilidade significa que um valor existente não pode ser alterado no próprio local; uma atualização deve produzir ou selecionar um valor diferente.
A imutabilidade mantém uma coleção original disponível quando uma transformação constrói uma nova versão.
Inferência de tipos
A inferência de tipos deduz tipos a partir do código ao redor, permitindo verificações estáticas mesmo quando anotações de tipo são omitidas.
A inferência de tipos permite que um compilador determine um tipo inteiro a partir de um valor inicial em vez de uma indicação de tipo escrita.
Instrução
Uma instrução é um comando completo de programa, como uma atribuição, um bloco condicional ou um laço.
A instrução total = price * 3 atribui o resultado de uma multiplicação a uma variável.
Interface
Uma interface especifica operações que os tipos que a implementam devem fornecer, permitindo que quem chama confie em um contrato comum.
Uma interface de formas pode exigir um método area() e deixar que cada forma calcule sua área de maneira diferente.
Interpretador
Um interpretador executa um programa por meio de um sistema de software em execução, que pode executar o código-fonte ou uma representação interna.
Um interpretador pode executar um script curto sem antes fornecer ao usuário um executável nativo separado.

L

Laço de leitura, avaliação e impressão (REPL)
Um REPL é um laço interativo de leitura, avaliação e impressão que aceita uma entrada, avalia-a, mostra um resultado e espera outra.
Uma pessoa usa um REPL para testar uma pequena expressão e ver seu resultado antes de inserir outra expressão.
Liberação dupla
Uma liberação dupla é a tentativa de liberar o mesmo bloco de memória alocado duas vezes, violando o ciclo de vida esperado pelo alocador.
Chamar free novamente sobre uma alocação já liberada pode criar uma liberação dupla e comportamento imprevisível.
Ligador
Um ligador conecta arquivos objeto traduzidos e código de bibliotecas necessário, resolvendo referências entre as partes de um programa.
Um ligador conecta a chamada traduzida de um arquivo-fonte à rotina de impressão fornecida por uma biblioteca.
Linguagem de alto nível
Uma linguagem de alto nível expressa programas por meio de conceitos como variáveis e funções enquanto oculta muitos detalhes do hardware.
Uma linguagem de alto nível permite que uma pessoa desenvolvedora solicite uma soma sem escolher quais registradores do processador usar.
Linguagem de montagem
A linguagem de montagem representa instruções de baixo nível usando mnemônicos legíveis e nomes para registradores ou posições de armazenamento.
Uma pessoa analista lê linguagem de montagem para acompanhar operações em um programa cujo código-fonte de alto nível não está disponível.
Linguagem específica de domínio
Uma linguagem específica de domínio é projetada para uma área de problemas específica, como consultas a bancos de dados, padrões ou estilização de páginas.
Uma linguagem específica de domínio como SQL pode expressar uma solicitação de dados mais diretamente do que um laço de busca escrito à mão.
Linguagem multiparadigma
Uma linguagem multiparadigma oferece suporte a vários estilos de organização da computação, permitindo abordagens diferentes em um mesmo programa.
Python é uma linguagem multiparadigma na qual um método de classe pode usar um laço imperativo e uma transformação funcional.

M

Máquina virtual
Uma máquina virtual é um software que fornece um modelo de execução para instruções, como o bytecode usado pelo ambiente de execução de uma linguagem.
Uma máquina virtual Java compatível permite que o mesmo arquivo de classe seja executado em computadores com processadores diferentes.
Método
Um método é uma operação associada a uma classe ou objeto, que muitas vezes atua sobre o estado da instância que recebe a chamada.
Um método de saque verifica o valor solicitado antes de alterar o saldo da conta que recebe a chamada.
Montador
Um montador é um tradutor que codifica instruções de linguagem de montagem como código de máquina para um conjunto de instruções específico.
Um montador transforma add eax, ebx nos bytes correspondentes da instrução x86-64.
Mutação
Mutação é a alteração de um objeto existente no próprio local, de modo que qualquer referência ao mesmo objeto observe o conteúdo atualizado.
Acrescentar um elemento a uma lista compartilhada é mutação; atribuir uma nova lista a um nome altera apenas essa associação.

N

Número de ponto flutuante
Um número de ponto flutuante representa um valor com precisão binária limitada, tornando aproximada a representação da maioria das frações decimais.
Um número de ponto flutuante pode introduzir uma pequena diferença entre 0.1 + 0.2 e o valor armazenado de 0.3.

O

Objeto
Um objeto é uma entidade de programa que combina dados com operações disponíveis, muitas vezes como uma instância específica de uma classe.
Dois objetos de conta podem usar o mesmo método de saque e manter seus próprios saldos distintos.
Ocultação de informação
A ocultação de informação mantém detalhes de implementação por trás de uma interface, para que outros códigos dependam das operações prometidas.
A ocultação de informação permite que uma conta altere sua representação de armazenamento e preserve o comportamento de depósito.
Otimizador de consultas
Um otimizador de consultas escolhe um plano para executar uma consulta ao banco de dados, preservando o resultado solicitado pela consulta.
Um otimizador de consultas pode usar um índice disponível após uma alteração nos dados, sem reescrever a solicitação SQL.

P

Paradigma de programação
Um paradigma de programação é um estilo de organização da computação, com formas específicas de expressar dados, operações e controle.
Um paradigma de programação pode enfatizar atualizações de estado, enquanto outro enfatiza a composição de funções puras.
Parâmetro
Um parâmetro é um nome na definição de uma função que recebe um valor fornecido por um argumento quando a função é chamada.
O parâmetro width recebe 3 quando quem chama avalia area(3, 4).
Pilha de chamadas
A pilha de chamadas acompanha chamadas de funções ativas em ordem de aninhamento para que a execução possa retornar a quem fez a chamada correta.
Uma chamada a uma função area acrescenta uma entrada à pilha de chamadas e a remove quando seu resultado retorna a quem chamou a função.
Plataforma
Uma plataforma é o ambiente de hardware e software no qual um programa deve ser executado, incluindo ambientes de execução e serviços de sistema disponíveis.
Uma plataforma de navegador pode executar JavaScript diretamente por meio de seu mecanismo e módulos compilados por meio do WebAssembly.
Polimorfismo
O polimorfismo permite que uma operação compartilhada funcione com tipos de objetos diferentes, enquanto cada objeto fornece seu comportamento adequado.
O polimorfismo permite que um relatório chame area() em círculos e retângulos sem um ramo separado para cada um.
Ponteiro pendente
Um ponteiro pendente refere-se a armazenamento que já foi liberado e não é mais um alvo válido de acesso.
Um ponteiro pendente ainda pode conter um endereço antigo mesmo depois que esse endereço foi reutilizado para outros dados.
Programação declarativa
A programação declarativa especifica um resultado ou uma relação exigida, deixando os detalhes de execução a cargo de um mecanismo da linguagem.
A programação declarativa permite que uma consulta SQL solicite linhas que atendem a uma condição sem especificar um laço linha por linha.
Programação estruturada
A programação estruturada organiza o controle por meio de sequências, seleções, laços e chamadas, em vez de saltos arbitrários.
A programação estruturada permite que quem lê acompanhe blocos aninhados para ver qual instrução será executada em seguida.
Programação funcional
A programação funcional organiza cálculos por meio da composição de funções e da produção de novos valores, limitando a mutação compartilhada.
A programação funcional pode expressar o cálculo de um relatório como seleção, transformação e combinação de valores.
Programação imperativa
A programação imperativa expressa uma computação como comandos ordenados que atualizam o estado do programa por meio de atribuições e fluxo de controle.
A programação imperativa torna explícito o novo valor de um acumulador em cada passada de um laço.
Programação lógica
A programação lógica declara fatos e regras e depois procura valores que satisfaçam essas relações para responder a uma pergunta.
A programação lógica pode derivar uma relação de avô ou avó conectando duas relações conhecidas entre pais e filhos.
Programação orientada a eventos
A programação orientada a eventos organiza o trabalho em torno de tratadores invocados quando eventos ocorrem, como cliques ou mensagens recebidas.
A programação orientada a eventos espera um clique em um botão antes de executar a atualização de interface registrada.
Programação procedural
A programação procedural agrupa comandos imperativos em funções ou procedimentos nomeados, cada um responsável por uma tarefa coerente.
A programação procedural separa a leitura de dados, o cálculo de um resultado e a exibição de um relatório em tarefas nomeadas.
Propriedade de valores
A propriedade de valores atribui responsabilidade pelo tempo de vida de um valor, permitindo que regras do compilador verifiquem o acesso e organizem a liberação de memória.
A propriedade de valores em Rust rejeita, em código seguro, o acesso por meio de uma referência que duraria mais que o valor para o qual aponta.

R

Recursão
A recursão resolve um problema por meio de chamadas à mesma função, geralmente reduzindo-o em direção a um caso que não precisa de outra chamada.
A recursão de fatorial multiplica por inteiros sucessivamente menores até alcançar o resultado definido para zero.
Referência
Uma referência é um valor que identifica um objeto e permite acessá-lo sem duplicar os dados armazenados pelo objeto.
Copiar uma referência a uma lista dá a outro nome acesso à mesma lista e às suas alterações posteriores.
Registro de ativação
Um registro de ativação armazena informações associadas a uma chamada de função ativa, como associações locais e o ponto de retorno.
Cada chamada recursiva não resolvida precisa de seu próprio registro de ativação, a menos que uma implementação consiga eliminá-lo por otimização.

S

Segurança de memória
A segurança de memória impede classes de acesso inválido à memória, como acessar armazenamento liberado ou escrever fora dos limites válidos.
A segurança de memória pode bloquear um acesso perigoso enquanto o programa ainda contém um erro não relacionado em seu cálculo.
Semântica
Semântica é o significado atribuído às estruturas de um programa, incluindo como os operadores se comportam e como a execução altera valores.
A semântica da divisão inteira explica por que uma divisão de mesma aparência pode dar resultados diferentes entre linguagens.
Sintaxe
Sintaxe é o conjunto de regras que define quais disposições de unidades léxicas formam expressões, instruções e programas válidos.
A sintaxe de Python exige dois-pontos após uma condição if e indentação para o bloco controlado.
Sobrescrita de métodos
A sobrescrita de métodos dá a uma subclasse sua própria implementação de um método herdado, mantendo a operação disponível para quem a chama.
Sobrescrever um método de saque pode acrescentar uma tarifa e preservar a regra contra saldo negativo.
SQL
SQL é uma linguagem para trabalhar com dados relacionais, com consultas que declaram campos solicitados, condições para linhas e ordenação.
SQL pode solicitar os nomes de estudantes acima de um limite de nota e ordená-los alfabeticamente.

T

Tabela de rastreamento
Uma tabela de rastreamento registra valores relevantes de variáveis após os passos executados para que o caminho e as mudanças de estado possam ser acompanhados.
Uma tabela de rastreamento de um acumulador registra o novo total após cada elemento passar pelo laço.
Tipagem dinâmica
A tipagem dinâmica verifica os tipos dos valores durante a execução e permite que um nome se refira a valores de tipos diferentes ao longo do tempo.
A tipagem dinâmica de Python permite que um nome passe de um inteiro para uma cadeia de caracteres, mas operações aritméticas incompatíveis ainda falham.
Tipagem estática
A tipagem estática verifica a compatibilidade de tipos antes da execução, permitindo que muitas combinações inválidas sejam rejeitadas durante o desenvolvimento.
A tipagem estática permite que um compilador Java rejeite texto usado para inicializar uma variável inteira antes da execução do programa.
Tipagem por comportamento
A tipagem por comportamento aceita um objeto com base nas operações que ele fornece, sem necessariamente exigir uma superclasse declarada comum.
A tipagem por comportamento em Python permite que um objeto sem relação com os demais participe de um cálculo de área se fornecer um método area() compatível.
Tipo de dado
Um tipo de dado especifica um conjunto de valores possíveis e as operações permitidas sobre eles, orientando sua representação e seu uso.
Um tipo de dado de cadeia de caracteres permite operações de texto mesmo quando os caracteres do valor são algarismos.
Tratador de eventos
Um tratador de eventos é uma função ou operação registrada para responder quando um evento específico é entregue pelo ambiente de execução.
Um tratador de evento de clique pode atualizar um menu enquanto outro tratador processa uma mensagem recebida pela rede.

U

Unidade léxica
Uma unidade léxica é uma parte significativa do texto-fonte, como uma palavra-chave, um identificador, um literal, um operador ou um sinal de pontuação.
O ponto e vírgula em uma atribuição C é uma unidade léxica, embora não nomeie uma variável nem um valor numérico.
Uso após liberação
Uso após liberação é o acesso ao armazenamento por meio de um ponteiro depois que sua alocação foi liberada, causando um acesso inválido à memória.
O uso após liberação pode sobrescrever um novo objeto que ocupe um bloco liberado anteriormente.

V

Vazamento de memória
Um vazamento de memória retém armazenamento alocado que não é mais necessário, reduzindo a memória disponível para trabalho útil.
Uma cache que cresce continuamente sem remover entradas pode causar um vazamento de memória mesmo em uma aplicação com coleta de lixo.

Sobre esta aula

Por StudeliRelatar um erro

Crie seus próprios Materiais de Estudo

Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.

Começar