Pular para o conteúdo

Fundamentos de Programação

Ciência da Computação Universitária · Tópico 1

Ver Slides Treinar
1.1

O que este curso é e como usá-lo

Este é o primeiro curso de Ciência da Computação Universitária: fundamentos de programação , ministrado com Python 3. Pressupõe nenhuma experiência prévia em programação. Todo programa neste material é completo e executável — digite-o, execute-o, altere uma linha e previamente antecipe as alterações antes de executar novamente. Esse ciclo de prever-então-executar é toda a habilidade.

O curso é uma escada:

  • As unidades 1.1 a 1.3 estabelecem as bases: valores e tipos, seleção e iteração.
  • A unidade 1.4 organiza o trabalho em funções.
  • As unidades 1.5 e 1.6 adicionam coleções padrão e arquivos com tratamento de erros.
  • A unidade 1.7 é a disciplina superior: decompor problemas, testar e depurar.

Como ler cada unidade: uma breve narrativa explica a importância; uma imagem ou regra prática vem antes da sintaxe; depois as regras precisas; então exemplos resolvidos que ficam mais difíceis, cada um com um rastreamento verificável executando o código; depois os erros reais que as pessoas cometem; e por fim um resumo de três linhas. Cada exemplo aqui pode ser verificado de outra forma — executando-o. Ler código sem executá-lo é como ler partitura musical sem ouví-la.

1.1

Valores, tipos e entrada/saída

Um aplicativo de compras mostra "4.20" — isso é texto ou número? Se for texto, somar dois determinados fornecem "4.203.50"; se for um número, fornece 8.7. O que acontece depende do tipo, e todo o bloco 1.1 aprende a identificar tipos antes de você executar qualquer coisa.

A value has a type, and the type decides what you can do with it: int (whole inteiros), float (decimais), str (texto), bool (True/False). O type(...) ela nunca edita o valor antigo. A imagem mental, importante manter durante todo o curso:

age = 21              # int
price = 4.20          # float
name = "Ada"          # str
open_ = True          # bool
print(type(age))      # <class 'int'>

Variáveis são nomes vinculados a valores; a atribuição age = 22 re vincula o nome — nunca edita o valor antigo. A imagem mental, valiosa durante toda a graduação:

Estado do programa como caixas: cada etiqueta de nome aponta para sua própria caixa de valor; reassociar move a etiqueta, ela nunca edita a caixa. A expressão 6 * 3.50 lê duas caixas e produz uma nova caixa float 21.0
Nomes são etiquetas em caixas; expressões leem caixas e criam novas caixas

Ler uma expressão é "abrir as caixas, calcular, fechar uma nova caixa": count * 3.50 com count = 6 abre a caixa int 6 e a caixa float 3.50, e a mesclagem do Python promove a regra para o int — o resultado é a caixa float 21.0, nunca a caixa int 21.

Conversão é explícita: int("21") é 21, mas "4" + 2 é um TypeError — o Python recusa adivinhar se você quis dizer "42" ou 6. Saída com print(...); entrada com input(...), que sempre retorna uma string, então números precisam de conversão:

born = int(input("Birth year? "))
print("About", 2026 - born, "years old")

Exemplo resolvido 1 (o caixa do café). count = int(input("Coffees? ")), depois print("Total:", count * 3.50). Em 6 cafés isso imprime Total: 21.0 — a caixa .0 marca um float, porque int * float promove para float. Observar qual tipo aparece na saída de uma expressão é todo o hábito da unidade 1.1. Rastreie pelo estado: input retorna a string "6"; int(...) abre uma nova caixa contendo a caixa int 6; a multiplicação cria uma nova caixa float 21.0; print a exibe.

Exemplo resolvido 2 (o acidente clássico, reparado). Uma impressora de faixas junta uma contagem a uma mensagem:

count = int(input("Tickets? "))
print("Sold " + count + " today")

Ao executar, o Python levanta TypeError: can only concatenate str (not "int") to str — a regra recusa adivinhar. O reparo nomeia a conversão explicitamente: print("Sold " + str(count) + " today"), ou a forma mais limpa print("Vendido", count, "hoje"), which inserts the spaces for you. The lesson to carry: + significa adição para números, junção para strings, e nunca uma mesclagem silenciosa.

Erros comuns. Esquecer que input() sempre retorna uma string, portanto, input() * 2 repete texto em vez de duplicar um número. Escrever 4.20 + 6 e esperar uma resposta do tipo int — qualquer valor flutuante na expressão gera um float. Confiando 0.1 + 0.2 == 0.3 (é False; arredondamento binário — a unidade 1.2 mostra o ajuste).

Resumo: quatro tipos básicos determinam toda operação; nomes são rótulos, atribuição rebinda; expressões misturam tipos por promoção com floats; conversões são explícitas e input() requer uma.

Vocabulário Treinar
English Português
programming fundamentals/ˈprəʊɡræmɪŋ ˌfʌndəˈmentlz/ fundamentos de programação
type/taɪp/ tipo
value/ˈvæljuː/ valor
variables/ˈveərɪəblz/ variáveis
string/strɪŋ/ string
selection/sɪˈlekʃn/ seleção
iteration/ˌɪtəˈreɪʃn/ iteração
infinite loop/ˈɪnfɪnət luːp/ loop infinito
trace table/treɪs ˈteɪbl/ tabela de rastreamento
function/ˈfʌŋkʃn/ função
return value/rɪˈtɜːn ˈvæljuː/ valor de retorno
parameter/pəˈræmɪtə/ parâmetro
set/set/ conjunto
1.2

Seleção: escolher entre caminhos

Um catraca de parque temático deve cobrar crianças, adultos e idosos diferentemente — um programa, vários caminhos. Seleção é como um programa escolhe: if, elif, else executa um bloco apenas quando uma condição é satisfeita.

Fluxograma das regras de preço do cinema com a verificação de idade antes da verificação de estudante; um estudante de 70 anos toma o primeiro ramo verdadeiro e paga 5
Idade 70, estudante: o primeiro ramo verdadeiro define o preço

A condição é qualquer expressão booleana; comparações (==, !=, <, >=) e lógica (and, or, not) a constroem. Duas regras tornam cada decisão legível:

  1. Ramos são testados de cima para baixo; o primeiro ramo verdadeiro vence e os demais são ignorados.
  2. Recuo não é decoração — ele É o bloco. Um espaço extra altera quais linhas pertencem ao ramo.
temp = 37.5
if temp >= 38:
    print("Fever")
elif temp >= 36 and temp < 38:
    print("Normal")
else:
    print("Low")

Exemplo resolvido 1 (preço do cinema). Para 12 ou 65+ paga-se 5, estudantes pagam 7, todos os outros 10:

age = int(input("Age? "))
student = input("Student? y/n ") == "y"
if age < 12 or age >= 65:
    price = 5
elif student:
    price = 7
else:
    price = 10
print("Pay", price)

A ordem importa: o primeiro ramo verdadeiro é escolhido, razão pela qual a verificação de idade precede a verificação do estudante — um estudante de 70 anos deve pagar 5, e essa ordem o estabelece. Execute com 70 e y para ver o caminho mais à esquerda do fluxograma ser selecionado.

Exemplo resolvido 2 (faixas de pontuação, escalonado). A mesma estrutura, agora com uma cadeia na qual cada elif requer apenas uma comparação, pois os testes anteriores já falharam: falhou:

score = int(input("Score? "))
if score >= 80:
    band = "A"
elif score >= 70:
    band = "B"
elif score >= 60:
    band = "C"
else:
    band = "F"
print("Band:", band)

notar: o 79 não precisa também de Band: B — chegar a essa note: o elif score >= 70 também não precisa do and score < 80 — chegar àquela linha já prova que a pontuação está abaixo de 80. Confiar no fluxo contínuo assim é o que torna as cadeias curtas; retestar tudo é a versão mais longa e errada do iniciante. version.

Comparações de ponto flutuante exigem cuidado: 0.1 + 0.2 == 0.3 é False (binário abs(x - y) < 1e-9 com uma condição que nunca será alcançada porque um ramo anterior já a capturou.

Erros comuns. Escrever = onde se pretende == — o erro clássico, e Python recusa isso em condições (SyntaxError) justamente porque é tão comum. Testar o caso especial vem depois do geral, então o geral sempre prevalece. Usando elif com uma condição que nunca é alcançada porque um ramo anterior já a capturou.

Resumo: condições escolhem caminhos, primeiro-verdadeiro-vence, indentação define o bloco; ordem testes de específico para geral; floats comparam por tolerância, nunca por ==.

1.3

Iteração: fazendo novamente

Pagar faturas 5,000 manualmente é uma carreira; com um laço, é um programa. Iteração repete trabalho: um laço for percorre uma sequência conhecida; um laço while executa enquanto uma condição se mantém. A questão de design é sempre: você sabe quantas vezes vai repetir, ou uma condição decide?

total = 0
for i in range(1, 101):      # 1..100 — range stops BEFORE its end
    total += i
print(total)                  # 5050

Use for quando a contagem de repetições for conhecida; use while quando uma condição decidir. Todo laço while precisa de três elementos visíveis no código: o valor inicial da variável, a condição, e algo dentro que leve à saída — caso contrário, é um laço infinito. Laço

O laço while de fatorial ao lado de sua tabela de rastreamento: n e fatorial após cada rodada, de 5 e 1 até 1 e 120, quando a condição se torna Falsa
Tabela de rastreamento para o laço de fatorial
n = 5
factorial = 1
while n > 1:
    factorial *= n
    n -= 1                    # the step toward exiting
print(factorial)              # 120

A tabela de rastreamento é o hábito de verificação do curso para laços: siga as variáveis rodada por rodada, primeiro no papel. Para o laço de fatorial: (n=5, f=1) → (4, 5) → (3, 20) → (2, 60) → (1, 120). Construa a tabela manualmente, depois adicione um print(n, factorial) dentro do laço e deixe o Python corrigir.

Exemplo resolvido 2 (uma busca com break, escalonado). Encontre o primeiro múltiplo de 7 que seja maior que 100 — a contagem de repetições é desconhecida, a condição decide:

n = 100
while True:
    if n % 7 == 0:
        break                 # found it — leave the loop now
    n += 1
print(n)                      # 105

O rastreamento é curto: 100, 101, …, 104 não passam no teste; 105 passa e break sai. break deixa o laço cedo; continue pula para a próxima rodada. Ambos são ferramentas de legibilidade — um laço que deve percorrer até encontrar algo, depois parar, lê-se melhor com break. A escalada seguinte, dobrando dinheiro: a 5% ao ano, quanto tempo o 1000 leva para chegar ao 2000?

balance = 1000.0
years = 0
while balance < 2000.0:       # the condition itself is the goal
    balance *= 1.05
    years += 1
print(years, round(balance, 2))   # 15 2078.93

Quinze anos, e o loop para no momento em que a condição inverte — a tabela de traçagem mostraria o saldo cruzando a linha no 2078.93, sem pousar exatamente no 2000.

Erros comuns. Editar a lista (unidade 1.5) ou a variável do loop que você está for-percorrendo. range(1, 100) quando deveria ser 100 — o final é excluído. Um while cuja etapa de saída esteja ausente ou comentada: a condição nunca se inverte, e o programa congela.

Resumo: for para contagens de viagem conhecidas, while para condições; todo loop mostra início, condição e passo; trace no papel, depois imprima na nota; break e continue para saídas antecipadas limpas.

Vocabulário Treinar
English Português
list/lɪst/ lista
dictionary/ˈdɪkʃənəri/ dicionário
tuple/ˈtuːpl/ tupla
1.4

Funções: nomeando um trabalho uma vez

O mesmo cálculo de preço aparece em cinco locais; a regra do imposto muda; quatro dos cinco são atualizados. Uma função empacota um trabalho atrás de um nome para que seja escrita uma vez e confiada em todos os lugares: parâmetros entram, um valor de retorno sai. def define; chamar executa.

def total_price(count, unit=3.50):
    """Return the total for count items at unit price."""
    return count * unit

print(total_price(6))            # 21.0
print(total_price(6, unit=4.0))  # 24.0

A definição fornece um parâmetro (count, unit); a chamada fornece um argumento (6). Um valor padrão (unit=3.50) torna um argumento opcional. return devolve um valor e encerra a chamada; imprimir não é retornar — uma função sem return retorna None, e o chamador que tenta aritmética sobre ela recebe um erro de TypeError.

Três quadros de chamada: a impressão chama order_total(250, 4), que pausa enquanto add_fee(1000) executa; 1003 é devolvido ao longo da cadeia
Chamadas aninhadas: o chamador pausa até o valor retornar

Cada chamada possui seu próprio escopo local: os nomes internos são invisíveis externamente e desaparecem quando a chamada termina. Um rastreamento de chamadas aninhadas lê-se: chamador pausa → chamado executa → valor de retorno chega ao chamador.

Exemplo resolvido 1 (o pedido com taxa inclusa).

def add_fee(amount):
    """Return amount plus a fixed 3 pence fee."""
    return amount + 3

def order_total(unit_pence, quantity):
    """Return the order total: subtotal plus the fixed fee."""
    return add_fee(unit_pence * quantity)

print(order_total(250, 4))   # 1003

O contrato no docstring importa: pence inteiros dentro, pence inteiros fora. Rastrear order_total(250, 4): subtotal 1000, add_fee retorna 1003 — e o quadro diagrama acima mostra a pausa e a devolução.

Exemplo resolvido 2 (um algoritmo real dentro de uma pequena função, expandido). is_prime(n) retorna True quando nada de 2 até $\sqrt{n}$ divide n:

def is_prime(n):
    if n < 2:
        return False
    d = 2
    while d * d <= n:        # only test up to the square root
        if n % d == 0:
            return False     # found a divisor — answer known, stop
        d += 1
    return True

print([p for p in range(2, 30) if is_prime(p)])
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

Dois hábitos de função se encontram aqui: um retorno antecipado de return encerra a chamada assim que a resposta é conhecida (sem necessidade de variável flag), e o limite do laço d * d <= n vem da dedução — se $n = a \times b$ com $a \le b$, então $a \le \sqrt{n}$, portanto testar além de $\sqrt{n}$ re-testa divisores já vistos. Execute-o: a lista contém exatamente os primos abaixo de 30.

Erros comuns. Imprimir dentro de uma função em vez de retornar, para que o resultado não possa ser reutilizado. Esquecer o return em um ramo — esse caminho silenciosamente gera None. Acessar locais de uma função de fora; eles somem quando a chamada termina.

Resumo: def uma vez, chamar em qualquer lugar; parâmetros entram, um valor sai via return; retornos precoces encerram a chamada; variáveis locais nascem e morrem com ela; strings de documentação descrevem o contrato.

Vocabulário Treinar
English Português
argument/ˈɑːɡjuːmənt/ argumento
local scope/ˈləʊkl skəʊp/ escopo local
1.5

Coleções: listas, dicionários, tuplas, conjuntos

O caixa de um café precisa dos pedidos do dia em ordem, do menu por nome, de uma localização que não deve se alterar e das bebidas únicas de hoje. Quatro tarefas, quatro recipientes — escolher entre eles é a decisão de design da unidade 1.5.

Uma lista é uma sequência ordenada e mutável: marks = [72, 85, 61], indexada de 0, fatiado com marks[1:3]. Um dicionário mapeia chaves para valores: price = {"flat white": 3.5, "tea": 2.8}, lido e escrito por chave. Uma tupla é uma sequência fixa (53.1, -1.5) — ideal para coordenadas que não devem mudar. Um conjunto armazena itens únicos sem ordem: {"a", "b"}.

marks = [72, 85, 61]
marks.append(90)               # [72, 85, 61, 90]
best = max(marks)              # 90
average = sum(marks) / len(marks)   # 77.0
Nomes a e b apontam para um único objeto de lista, que contém 1 e 2 após b.append(2)
Alienação: dois nomes, uma lista

Atribuição nunca copia: a = [1]; b = a; b.append(2) faz a referenciar [1, 2] — a lista é compartilhada, os nomes não. Essa alienação são as caixas da unidade 1.1 novamente, agora com duas etiquetas em uma única caixa, e também explica a próxima regra: mutar uma lista enquanto percorre seus elementos salta itens — percorra uma cópia (for m in marks[:]) ou construa uma nova lista.

Exemplo resolvido 1 (contagem por categoria).

orders = ["tea", "flat white", "tea", "tea", "mocha"]
tally = {}
for item in orders:
    tally[item] = tally.get(item, 0) + 1
print(tally)   # {'tea': 3, 'flat white': 1, 'mocha': 1}

O .get(key, 0) do dicionário fornece 0 para uma primeira observação — a idioma que torna a contagem em uma única linha. Verifique manualmente: "tea" aparece três vezes em orders, e o total impresso confirma isso.

Exemplo resolvido 2 (o best-seller do dia, ampliado). Baseando-se no total, relate o item mais frequente e sua contagem:

best = max(tally, key=tally.get)
print(best, tally[best])       # tea 3

O argumento key=tally.get instrui max a comparar itens por suas contagens em vez de ordem alfabética — o mesmo truque key= ordena qualquer coleção por qualquer critério. O ampliamento é relevante: programas reais encadeiam essas ferramentas (contar, depois classificar) em vez de reescanear orders duas vezes.

Escolher a coleção é a decisão de design: ordem + duplicatas → lista; consulta por nome → dicionário; agrupamento fixo → tupla; unicidade → conjunto.

Erros comuns. Acessar ⟨tally[item] += 1⟩ em uma chave que ainda não está no dicionário (KeyError) — usar ⟨.get⟩ é a correção. Esperar que um conjunto ou dicionário lembre a ordem. Assumir que ⟨b = a⟩ copia uma lista; ele cria apenas um alias.

Resumo: quatro coleções para quatro tarefas; atribuição compartilha, nunca copia; ⟨.get⟩ torna a contagem segura; ⟨key=⟩ torna o ranking uma única linha.

Vocabulário Treinar
English Português
aliasing/ˈeɪlɪəsɪŋ/ aliasing
1.6

Arquivos e erros

O café fecha, a luz apaga — e o total da manhã sobrevive apenas se o programa o tiver gravado em disco. Um arquivo persiste após o programa. with open(path) as f: opens, closes on exit, and reads text; open(path, "w") grava (e esvazia primeiro — o erro clássico).

with open("log.txt", "w") as f:
    f.write("opened\n")

with open("log.txt") as f:
    for line in f:
        print(line.rstrip())     # rstrip removes the trailing newline

O bloco with é a barreira de segurança: qualquer coisa que aconteça dentro dele, o arquivo é fechado. A leitura fornece linhas com seu caractere de nova linha, então .rstrip() pertence a todo loop.

Erros são valores que Python levanta como exceções: FileNotFoundError, ValueError, KeyError, TypeError. try/except captura um tipo nomeado — nunca um except: genérico que esconde bugs reais.

Exemplo resolvido ⟨1⟩ (arquivo de configuração ausente). Ler um tamanho de texto de um arquivo de configuração que pode estar faltando, ou pode não conter um número inteiro:

try:
    with open("settings.txt") as f:
        size = int(f.read().strip())
except FileNotFoundError:
    size = 12                    # no file yet: a sensible default
except ValueError:
    size = 12                    # the file holds "big", not a number
print("Text size", size)

Execute três vezes — com ⟨16⟩ no arquivo, sem arquivo, e com ⟨big⟩ no arquivo — e ele imprimirá um tamanho utilizável cada vez, por três rotas diferentes. Essa é a estrutura de código robusto: uma solução sensível várias rotas para ele. Trate o erro esperado, onde você pode responder a ele; deixe o restante falhar com ruído alto.

Um leitor de configurações: o bloco try abre, lê e converte o valor; funciona, ou um FileNotFoundError ou ValueError define o tamanho padrão ⟨12⟩; todas as três rotas se encontram e o programa continua

Exemplo resolvido 2 (um loop de entrada robusto, escalonado). Pergunte até que o usuário digite um número inteiro:

def read_count():
    while True:
        try:
            return int(input("How many? "))
        except ValueError:
            print("Whole numbers only.")

print(read_count())

Alimente-o x depois 4: a primeira passagem levanta, é capturada, imprime a mensagem, e o while True pergunta novamente; a segunda passagem de int(...) tem sucesso e return sai do loop e da função em um único movimento. Três unidades combinam aqui — o loop (1.3), o contrato da função (1.4) e a exceção (1.6) — que é o ponto da escalação.

Erros comuns. open(path, "w") no caminho errado: o arquivo é criado ou esvaziado antes que você perceba. Capturar Exception amplamente e continuar com estado. Esquecendo .rstrip(), assim a saída mostra linhas com espaçamento duplo.

Resumo: with open(...) para leitura e escrita; "w" esvazia primeiro; exceções são valores — capture o tipo nomeado que você espera, recorra com sensatez, deixe o restante falhar.

Vocabulário Treinar
English Português
file/faɪl/ arquivo
exceptions exceções
decomposition/ˌdiːkɒmpəˈzɪʃn/ decomposição
testing/ˈtestɪŋ/ testes
debugging/ˈdiːbʌɡɪŋ/ depuração
1.7

Decomposição, teste e depuração

Dois equipes escrevem o mesmo aplicativo; uma entrega em uma semana, outra afoga por um mês. A diferença não é velocidade de digitação — é que o código da equipe rápida é construído a partir de pequenos pedaços que cada um pode provar corretos enquanto a equipe lenta depura um aplicativo inteiro de uma vez. Unit Test 1.7 é essa disciplina.

Decomposição divide um problema em funções de uma tarefa cada — cada pequena suficiente para testar isoladamente. Teste compara a saída real de uma função com a esperada, nas fronteiras: zero, um, vazio, negativo, enorme.

O ciclo guiado por testes: escreva um teste falhando que define o contrato; execute-o e veja-o falhar pelo motivo correto; escreva o corpo mais simples que passe; reexecute até ficar verde; refatore mantendo a estrutura com os testes
O ciclo guiado por testes: vermelho, verde, refatoração
def add_fee(amount):
    return amount + 3

def order_total(unit_pence, quantity):
    return add_fee(unit_pence * quantity)

assert order_total(250, 4) == 1003
assert order_total(250, 0) == 3      # the zero-quantity boundary
assert order_total(0, 4) == 3

Um ⟨assert⟩ que nunca dispara é um teste aprovado. Escreva os testes antes de escrever o (vermelho → verde → refatoração) é esse hábito rodando continuamente. (vermelho → verde → refatoração) é esse hábito rodando continuamente.

desconto de 1% sobre £order_total — o teste é escrito primeiro, na nova fronteira: Verifique o teste central manualmente: subtotal 10, dez por cento de desconto resulta em 10, e

def order_total(unit_pence, quantity):
    subtotal = add_fee(unit_pence * quantity)
    if subtotal >= 1000:             # pence: over £10
        subtotal = int(subtotal * 0.9)
    return subtotal

assert order_total(200, 4) == 803    # under £10 — unchanged
assert order_total(250, 4) == 902    # 1003 pence → 10% off → 902.7 → 902
assert order_total(250, 5) == 1127   # 1253 pence → 1127.7 → 1127

Verifique o teste intermediário manualmente: subtotal 1003, dez por cento de desconto resulta em 902.7, e int(...) arredonda para baixo 902. Note a primeira afirmação: em 200 pence e quatro xícaras, o total permanece 803 — os testes de fronteira provam que o desconto é aplicado e que não é aplicado prematuramente.

Exemplo resolvido 2 (bissecionar um erro com impressões, escalonado). A contagem de 1.5 imprime {'tea': 3}, mas a linha do best-seller imprime a bebida errada. O método: (1) reproduzir com a menor entrada falha; (2) imprimir os valores na fronteira entre a parte confiável e a suspeita — aqui, a contagem antes da max; (3) corrigir uma coisa; (4) executar os testes novamente. Mover a impressão pela metade do código em cada execução reduz pela metade a busca: quatro impressões encontram um erro em um programa de dezesseis linhas, embora bem ele se esconda.

Depuração é encontrar a diferença entre o que você acredita que o código faz e o que ele realmente faz — a impressão na fronteira de confiança/suspeita é a ferramenta.

Erros comuns. Testar apenas o caminho feliz: as fronteiras (zero, um, vazio, negativo) são onde os programas quebram. Escrever uma semana de código antes do primeiro teste, depois depurar uma montanha. Corrigir duas coisas de uma vez, para que nenhum resultado signifique nada.

Resumo: decompor até que cada peça teste isoladamente; afirmar as fronteiras assim que uma função existe; escrever o teste primeiro quando possível; depurar por bisseção com impressões nas fronteiras de confiança — o hábito no qual todo o grau se baseia.

Aulas interativas sobre este tópico

Passe por ele passo a passo, com exercícios de verificação instantânea.

Mais tópicos em Ciência da Computação Universitária

Entrar ou criar conta

IGCSE, A-Level & AP