LaboratórioFundamentos

Exercícios de Fundamentos

Dez práticas em C++ para transformar os conceitos das aulas em programas: tipos primitivos, coleções, registros, TAD, memória, ponteiros, alocação dinâmica e análise de complexidade.

10exercícios progressivos
C++17soluções completas
4etapas de aprendizagem
Seu progresso0 de 10 concluídos
00

Antes de programar

Use a solução somente depois de tentar

1
Entenda

Identifique entradas, processamento e saídas.

2
Planeje

Escolha tipos, registros e operações.

3
Implemente

Complete, compile e corrija seu código.

4
Teste

Experimente casos comuns e extremos.

01

Prática guiada

Do valor isolado à estrutura conectada

01InicialTipos primitivos

Ficha de um aluno

Leia nome, idade e média. Mostre os dados formatados e informe se o aluno é maior de idade e se foi aprovado, considerando média mínima 6,0.

Práticastringintdoubleboolif
Entrada
Ana Silva
19
8.7
Saída esperada
Aluno: Ana Silva
Idade: 19
Média: 8.7
Maior de idade: sim
Situação: aprovado
Ver dica

Use getline para o nome e fixed << setprecision(1) para uma casa decimal.

Ver resolução comentada
#include <iomanip>
#include <iostream>
#include <string>

int main() {
    std::string nome;
    int idade;
    double media;

    std::getline(std::cin, nome);
    std::cin >> idade >> media;

    bool maiorDeIdade = idade >= 18;
    bool aprovado = media >= 6.0;

    std::cout << std::fixed << std::setprecision(1);
    std::cout << "Aluno: " << nome << '\n';
    std::cout << "Idade: " << idade << '\n';
    std::cout << "Média: " << media << '\n';
    std::cout << "Maior de idade: "
              << (maiorDeIdade ? "sim" : "não") << '\n';
    std::cout << "Situação: "
              << (aprovado ? "aprovado" : "reprovado") << '\n';
}

Ideia: cada variável tem tipo e significado. Os booleanos armazenam resultados lógicos usados nas decisões.

02InicialArray

Cinco notas, uma coleção

Substitua cinco variáveis numeradas por um std::array. Leia as notas, calcule a média e encontre a maior usando laços.

Evitenota1, nota2, nota3...
Organizearray<double, 5> notas
Ver dica

Inicialize a maior com notas[0] e percorra a coleção com um for.

Ver resolução comentada
#include <array>
#include <iomanip>
#include <iostream>

int main() {
    std::array<double, 5> notas{};
    for (double& nota : notas) {
        std::cin >> nota; // A referência altera a posição original.
    }

    double soma = 0.0;
    double maior = notas[0];
    for (double nota : notas) {
        soma += nota;
        if (nota > maior) maior = nota;
    }

    std::cout << std::fixed << std::setprecision(1);
    std::cout << "Média: " << soma / notas.size() << '\n';
    std::cout << "Maior nota: " << maior << '\n';
}

Complexidade: generalizando para n notas, o percurso custa O(n).

03IntermediárioRegistro e vector

Turma organizada com struct

Modele um aluno com matrícula, nome e média. Leia três alunos em um vector e mostre o registro com a maior média.

vector<Aluno>
101Ana8.7
205Bia9.2
310Caio7.4
Ver dica

Crie um Aluno por repetição, use push_back e guarde um ponteiro para o melhor registro.

Ver resolução comentada
#include <iostream>
#include <string>
#include <vector>

struct Aluno {
    int matricula;
    std::string nome;
    double media;
};

int main() {
    std::vector<Aluno> turma;
    for (int i = 0; i < 3; ++i) {
        Aluno aluno;
        std::cin >> aluno.matricula;
        std::cin >> std::ws;
        std::getline(std::cin, aluno.nome);
        std::cin >> aluno.media;
        turma.push_back(aluno);
    }

    const Aluno* melhor = &turma[0];
    for (const Aluno& aluno : turma) {
        if (aluno.media > melhor->media) melhor = &aluno;
    }

    std::cout << "Maior média: " << melhor->nome
              << " (" << melhor->media << ")\n";
}

Organização: a struct reúne os campos da entidade; o vector reúne vários registros.

04IntermediárioTAD e classe

Projetando o TAD Conta

Implemente uma conta cujo saldo não possa ser alterado diretamente. Depósitos devem ser positivos e saques não podem ultrapassar o saldo.

Estado privadodouble saldo
Contrato públicodepositar · sacar · consultar
Ver dica

Mantenha o saldo em private. As operações públicas devem preservar saldo >= 0.

Ver resolução comentada
#include <iostream>

class Conta {
private:
    double saldo = 0.0;

public:
    bool depositar(double valor) {
        if (valor <= 0.0) return false;
        saldo += valor;
        return true;
    }

    bool sacar(double valor) {
        if (valor <= 0.0 || valor > saldo) return false;
        saldo -= valor;
        return true;
    }

    double consultarSaldo() const { return saldo; }
};

int main() {
    Conta conta;
    conta.depositar(150.0);
    if (!conta.sacar(200.0)) std::cout << "Saque recusado\n";
    conta.sacar(40.0);
    std::cout << "Saldo: " << conta.consultarSaldo() << '\n';
}

TAD: o cliente conhece as operações, enquanto a representação e o invariante ficam protegidos.

05IntermediárioTAD Lista

Lista de tarefas por operações

Crie um TAD apoiado por vector<string> com operações para adicionar, remover por posição, contar e listar tarefas.

EstudarCompilarTestar
Ver dica

O índice é válido quando indice < tarefas.size(). Use erase para remover.

Ver resolução comentada
#include <iostream>
#include <string>
#include <vector>

class ListaDeTarefas {
private:
    std::vector<std::string> tarefas;

public:
    void adicionar(const std::string& tarefa) {
        tarefas.push_back(tarefa);
    }

    bool remover(std::size_t indice) {
        if (indice >= tarefas.size()) return false;
        tarefas.erase(tarefas.begin() + indice);
        return true;
    }

    std::size_t quantidade() const { return tarefas.size(); }

    void listar() const {
        for (std::size_t i = 0; i < tarefas.size(); ++i)
            std::cout << i << ": " << tarefas[i] << '\n';
    }
};

int main() {
    ListaDeTarefas lista;
    lista.adicionar("Estudar TAD");
    lista.adicionar("Compilar");
    lista.adicionar("Testar");
    lista.remover(1);
    lista.listar();
}

Custos: adicionar no fim é O(1) amortizado; remover no meio é O(n).

06IntermediárioReferências e ponteiros

Alterando o valor original

Implemente dobrar usando referência e zerar usando ponteiro. A segunda função deve aceitar nullptr com segurança.

endereço 0xA0valor = 21
int&int*
Ver dica

A referência é usada como a variável. O ponteiro deve ser verificado e desreferenciado com *.

Ver resolução comentada
#include <iostream>

void dobrar(int& numero) {
    numero *= 2;
}

bool zerar(int* numero) {
    if (numero == nullptr) return false;
    *numero = 0;
    return true;
}

int main() {
    int valor = 21;
    dobrar(valor);
    std::cout << valor << '\n'; // 42

    zerar(&valor);
    std::cout << valor << '\n'; // 0
    zerar(nullptr);
}

Diferença: o ponteiro armazena um endereço e pode ser nulo; a referência funciona como outro nome para um objeto válido.

07AvançadoMemória dinâmica

Lista encadeada sem vazamento

Implemente inserção no início e percurso de uma lista simplesmente encadeada. Use unique_ptr para a desalocação automática.

início302010null
Ver dica

O novo nó recebe o antigo início com std::move(inicio); depois, inicio recebe o novo nó.

Ver resolução comentada
#include <iostream>
#include <memory>

struct No {
    int valor;
    std::unique_ptr<No> proximo;
};

class Lista {
private:
    std::unique_ptr<No> inicio;

public:
    void inserirNoInicio(int valor) {
        auto novo = std::make_unique<No>();
        novo->valor = valor;
        novo->proximo = std::move(inicio);
        inicio = std::move(novo);
    }

    void imprimir() const {
        const No* atual = inicio.get();
        while (atual != nullptr) {
            std::cout << atual->valor << ' ';
            atual = atual->proximo.get();
        }
        std::cout << '\n';
    }
};

int main() {
    Lista lista;
    lista.inserirNoInicio(10);
    lista.inserirNoInicio(20);
    lista.inserirNoInicio(30);
    lista.imprimir();
} // A cadeia é desalocada automaticamente.

RAII: cada nó possui exclusivamente o próximo. Destruir o início libera toda a cadeia.

08IntermediárioEstática × dinâmica

Quantidade conhecida em execução

Leia n temperaturas e mostre a menor, a maior e quantas ficaram acima da média. Escolha uma coleção redimensionável.

Tamanho fixoarray<double, 7>
Tamanho em execuçãovector<double>(n)
Ver dica

Leia e some no primeiro percurso; calcule a média; conte os valores acima dela no segundo.

Ver resolução comentada
#include <iostream>
#include <vector>

int main() {
    std::size_t n;
    std::cin >> n;
    if (n == 0) return 0;

    std::vector<double> temperaturas(n);
    double soma = 0.0;
    for (double& valor : temperaturas) {
        std::cin >> valor;
        soma += valor;
    }

    double menor = temperaturas[0];
    double maior = temperaturas[0];
    double media = soma / n;
    std::size_t acima = 0;

    for (double valor : temperaturas) {
        if (valor < menor) menor = valor;
        if (valor > maior) maior = valor;
        if (valor > media) ++acima;
    }

    std::cout << "Menor: " << menor << '\n';
    std::cout << "Maior: " << maior << '\n';
    std::cout << "Acima da média: " << acima << '\n';
}

Análise: tempo O(n) e espaço O(n). O vector administra a memória dinâmica.

09IntermediárioBig O

Reconhecendo padrões de crescimento

Associe cada trecho à complexidade de tempo correspondente.

A
cout << valores[0];
B
for (int i = 0; i < n; ++i)
    visitar(i);
C
for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j)
        comparar(i, j);
D
while (n > 1)
    n /= 2;

Ver explicação
A · O(1)Acesso único.B · O(n)O laço repete n vezes.C · O(n²)n repetições para cada uma das n externas.D · O(log n)O problema é dividido por dois.
10AvançadoBusca binária

Busca que reduz o problema pela metade

Implemente busca binária iterativa em um vetor ordenado. Devolva o índice do alvo ou -1.

381219meio273451
Ver dica

Mantenha inicio e fim. Compare o alvo com o meio e descarte metade do intervalo.

Ver resolução comentada
#include <iostream>
#include <vector>

int buscaBinaria(const std::vector<int>& valores, int alvo) {
    int inicio = 0;
    int fim = static_cast<int>(valores.size()) - 1;

    while (inicio <= fim) {
        int meio = inicio + (fim - inicio) / 2;
        if (valores[meio] == alvo) return meio;

        if (valores[meio] < alvo)
            inicio = meio + 1;
        else
            fim = meio - 1;
    }
    return -1;
}

int main() {
    std::vector<int> valores{3, 8, 12, 19, 27, 34, 51};
    int alvo;
    std::cin >> alvo;
    std::cout << "Índice: " << buscaBinaria(valores, alvo) << '\n';
}

Complexidade: tempo O(log n) e espaço auxiliar O(1).

02

Depois dos 10 exercícios

Desafio integrador: mapa de amizades

Combine registros, classe, coleção dinâmica, referências e análise de custo em um pequeno grafo social. Este desafio não entra na contagem.

AnaBiaCaioDavi

Implemente estas operações

  1. adicionarPessoa(nome)
  2. adicionarAmizade(a, b)
  3. listarAmigos(pessoa)
  4. saoAmigos(a, b)

Use vector<Pessoa> para os registros e vector<vector<int>> como lista de adjacência.