Revisando Algoritmos e Técnicas de Programação
Todo sistema computacional — de um aplicativo bancário a um jogo eletrônico — nasce de uma sequência lógica e finita de passos capaz de resolver um problema. Essa é exatamente a definição dada para aquilo que chamamos de algoritmo: um conjunto ordenado e não ambíguo de instruções, executável em tempo finito, que transforma uma entrada em uma saída desejada (Forbellone; Eberspächer, 2005).
Na perspectiva da Ciência da Computação, os algoritmos funcionam como uma tecnologia, pois a escolha do procedimento influencia diretamente o tempo de execução, o consumo de memória e a capacidade de um sistema atender volumes maiores de dados (Cormen et al., 2022). Inclusive, Manzano e Oliveira (2019) reforçam que o algoritmo em si é algo anterior à linguagem de programação, pois ele é a representação do raciocínio lógico, independente da sintaxe que futuramente será usada para implementá-lo.
Para que esse algoritmo possa ser comunicado a alguém (um colega ou um professor) ou a algo (um compilador) precisamos de notações. Ascencio e Campos (2012) descrevem duas formas clássicas: o português estruturado (pseudocódigo), que aproxima a lógica da linguagem natural, e os fluxogramas, que representam graficamente o fluxo de decisões e repetições. Ambas as notações possuem sintaxe (as regras de escrita, a forma) e semântica (o significado, o que cada instrução efetivamente realiza), conforme observa-se na Figura 1.

Fonte: Autoria própria com o auxílio de ferramentas de IA.
Um algoritmo pode estar sintaticamente correto e, ainda assim, semanticamente errado — por exemplo, calcular uma média usando a fórmula de uma soma simples. Farrer et al. (1999) alertam que essa distinção é o que separa um programador que apenas “decora comandos” de um profissional que efetivamente resolve problemas.
Neste artigo, temos o intuito de revisar o conteúdo comumente visto na disciplina de Algoritmos e Técnicas de Programação dos semestres iniciais dos cursos de Computação. Para isso, vamos demonstrar trechos de código em linguagem C++, adicionando um conceito por vez, exatamente como esses conceitos são apresentados na literatura clássica de algoritmos e técnicas de programação.
O contexto que adotaremos nos exemplos será um backlog de filmes. Começaremos com variáveis simples para evoluir até as ideias que permitam um sistema modular capaz de cadastrar, listar, pesquisar e avaliar filmes. Deixaremos o código completo ao final do artigo.
Conceitos básicos
Variáveis e constantes
Uma variável representa uma região nomeada da memória utilizada para armazenar um valor que pode mudar durante a execução. Toda variável possui um tipo, um identificador e um valor, sendo que o tipo determina quais valores podem ser armazenados e quais operações são válidas. Em C++, uma variável deve ser declarada antes do uso (Stroustrup, 2024).
// FORMATAÇÃO: Tipo Identificador = Valor;
string tituloFilme = "Interestelar";
int anoLancamento = 2014;
double notaFilme = 8.7;
bool assistido = true;No trecho acima, tituloFilme, anoLancamento, notaFilme e assistido são identificadores. Os valores iniciais são atribuídos no momento da declaração.
Uma constante representa um valor que não deve ser alterado após sua inicialização. Em C++, a palavra reservada const comunica essa intenção ao compilador.
// FORMATAÇÃO: const Tipo Identificador = Valor;
const int ANO_MINIMO_CINEMA = 1895;
const int NOTA_MAXIMA = 10;Constantes reduzem a repetição excessiva de valores espalhados pelo código e tornam as regras do domínio mais explícitas. É sempre bom lembrar que o uso de nomes descritivos também favorece legibilidade e manutenção.
Tipos primitivos de dados
Os tipos primitivos representam categorias básicas de valores. A lista de tipos primitivos pode variar conforme as linguagens de programação, mas alguns tipos aparecem em praticamente todas. Para a linguagem C++, os tipos mais comuns estão descritos na Tabela 1.
| Tipo | Finalidade | Exemplo de domínios | Exemplo de dados |
|---|---|---|---|
int | números inteiros | ano do filme | 2026 |
float | número real com precisão simples | nota do filme | 7.5f |
double | número real com maior precisão | nota do filme | 8.75 |
char | um caractere | Classificação etária | 'A' |
bool | valor lógico | filme assistido? | true ou false |
A escolha do tipo deve refletir o domínio do problema, ou seja, a escolha dos tipos depende do contexto que estamos tentando representar. No exemplo acima, o ano de lançamento de um filme é naturalmente inteiro, por isso ele pode ser atribuído como inteiro. Uma nota pode exigir casas decimais, logo ela é declarada como decimal (float ou double). O estado de um filme no backlog pode ser de “assistido” ou “não assistido”, possuindo apenas duas possibilidades. Portanto, ele pode ser representado por um tipo bool.
Observação: É possível alterar o tamanho ou a capacidade de armazenamento dos números inteiros e caracteres usando modificadores como short, long, long long, signed (com sinal) e unsigned (apenas positivos).
Comando de atribuição
A atribuição é o processo de colocar um valor em uma variável. Em C++, utiliza-se o operador igual ( = ).
notaFilme = 9.0;
anoLancamento = anoLancamento + 1;O símbolo ( = ) representa atribuição, não igualdade matemática. Inclusive, a leitura correta para o código na linha 1 é: “notaFilme recebe 9.0″. Desta forma, fica bem explícito que o símbolo de igual ( = ) não serve para comparar, mas para atribuir algo. Para comparação de igualdade, utiliza-se dois símbolos de igualdade juntos ( == ).
Na linha 2, a expressão à direita é calculada primeiro (anoLancamento + 1); depois, o resultado é armazenado na variável à esquerda. Para este exemplo, lemos: “anoLancamento recebe o valor de anoLancamento mais 1″.
Expressões aritméticas, relacionais e lógicas
As expressões aritméticas servem para combinar valores numéricos. Em outras palavras, elas servem para realizarmos operações matemáticas, tais como a média.
double media = (nota1 + nota2 + nota3) / 3.0;
int resto = quantidadeFilmes % 2;Os operadores mais comuns são soma ( + ), subtração ( - ), multiplicação ( * ), divisão ( / ) e módulo ( % ), que serve para calcular o resto de uma divisão inteira entre dois números.
As expressões relacionais comparam valores e produzem true ou false como resultado de suas comparações:
notaFilme >= 7.0
anoLancamento == 2014
tituloFilme != "Sem título"Os operadores relacionais são igual a ( == ), diferente de ( != ), menor que ( < ), maior que ( > ), menor ou igual a ( <= ) e maior ou igual a ( >= ).
Por fim, temos também as expressões lógicas, que combinam condições para encontrar situações verdadeiras ( true ) ou falsas ( false ):
assistido && notaFilme >= 8.0
anoLancamento >= 2000 || notaFilme >= 9.0
(!assistido)O operador AND/E ( && ) exige que ambas as condições sejam verdadeiras para produzir o resultado verdadeiro, enquanto o operador OR/OU ( || ) exige pelo menos uma verdadeira para que o resultado seja verdadeiro. A Tabela 2 apresenta as possibilidades de utilização dos operadores.
| A | B | A AND B | A OR B |
|---|---|---|---|
| Falso | Falso | Falso | Falso |
| Falso | Verdadeiro | Falso | Verdadeiro |
| Verdadeiro | Falso | Falso | Verdadeiro |
| Verdadeiro | Verdadeiro | Verdadeiro | Verdadeiro |
O último exemplo apresentado no trecho de código anterior é do operador NOT/NÃO ( ! ). Ele inverte o valor lógico original da variável, conforme descrito na Tabela 3.
| A | NOT A |
|---|---|
| Falso | Verdadeiro |
| Verdadeiro | Falso |
Importante ressaltar que a precedência dos operadores pode ser explicitada com parênteses, prática recomendada quando a expressão contém diferentes categorias de operadores.
Entrada e saída de dados
Conforme Deitel e Deitel (2013), a entrada e a saída padrão em C++ são feitas pelos objetos cin e cout, definidos na biblioteca <iostream>. O objeto cout envia dados à saída padrão por meio do operador ( >> ) e cin lê os dados da entrada padrão usando o operador ( << ).
Vejamos o exemplo de código a seguir:
#include <iostream>
#include <string>
using namespace std;
int main() {
// Variáveis: armazenam dados que podem mudar
string titulo;
int ano;
float nota;
// Constante: valor fixo, não muda durante a execução
const string NOME_SISTEMA = "Backlog de Filmes";
cout << "== " << NOME_SISTEMA << " ==" << endl;
cout << "Digite o titulo do filme: ";
getline(cin, titulo); // getline le a linha inteira, inclusive espacos
cout << "Digite o ano de lancamento: ";
cin >> ano;
cout << "Digite a nota (0 a 10): ";
cin >> nota;
// Expressao relacional: gera um valor booleano
bool notaAlta = (nota >= 7.0);
cout << "Filme cadastrado: " << titulo << " (" << ano << ")" << endl;
cout << "Nota alta? " << (notaAlta ? "Sim" : "Nao") << endl;
return 0;
}Na primeira linha, inserimos o código #include <iostream> para importar a biblioteca que oferece os fluxos para entrada e saída dos dados (cin e cout). Em seguida, habilitamos o tipo string, que não é um tipo primitivo, com a biblioteca #include <string>. Isso foi necessário porque o código captura palavras vindas nas respostas do usuário e não temos à disposição de forma primitiva o tipo string. Portanto, importamos uma biblioteca que nos permite essa operação.
A diretiva using namespace std; evita que seja preciso escrever std::cout a cada chamada. Além disso, repare que utilizamos getline(cin, titulo) em vez de cin >> titulo. Essa escolha se deu porque títulos de filmes podem conter espaços, e o getline lê a linha completa, incluindo espaços.
Na linha 29 executamos um operador ternário ( notaAlta ? "Sim" : "Nao" ) é uma forma compacta de decisão, que veremos a seguir de modo estruturado.
Estruturas de decisão
As estruturas de decisão (ou seleção) permitem que o algoritmo escolha um caminho de execução conforme uma condição (Farrer et al., 1999). Em outras palavras, elas alteram o fluxo sequencial do programa para executar blocos diferentes conforme os dados processados. A Figura 2 nos apresenta uma visão do conceito.

Fonte: Autoria própria com o auxílio de ferramentas de IA.
A estrutura mais comum é o if-else, que avalia uma expressão lógica. Quando o resultado é verdadeiro, o bloco associado é executado. Caso contrário, o else fornece uma alternativa de execução para o código. O if-else é mais adequado para faixas e expressões complexas de lógica.
Outra opção viável para tomar decisões é o switch. Ele seleciona um bloco com base no valor de uma expressão integral, enumerada ou caractere. É útil em menus e situações nas quais uma variável deve ser comparada com valores discretos (Schildt, 1997).
No switch, cada case representa um valor possível esperado, enquanto o bloco default trata valores não previstos. Entre as opções de case, é importante colocarmos o break para encerrar o switch quando a opção desejada for obtida.
No código a seguir apresentamos dois contextos onde decisões são necessárias, aplicando tanto o if-else quando o switch.
#include <iostream>
#include <string>
using namespace std;
int main() {
string titulo;
float nota;
int opcaoMenu;
cout << "Titulo: ";
getline(cin, titulo);
cout << "Nota (0 a 10): ";
cin >> nota;
// Estrutura if-else em cascata: classifica o filme pela nota
if (nota >= 9.0) {
cout << titulo << " -> Obra-prima!" << endl;
} else if (nota >= 7.0) {
cout << titulo << " -> Muito bom." << endl;
} else if (nota >= 5.0) {
cout << titulo << " -> Ok, pode assistir." << endl;
} else {
cout << titulo << " -> Talvez pular esse." << endl;
}
// Estrutura switch: menu de opcoes do sistema
cout << "\nMenu: 1-Cadastrar 2-Listar 3-Sair" << endl;
cout << "Escolha: ";
cin >> opcaoMenu;
switch (opcaoMenu) {
case 1:
cout << "Abrindo cadastro..." << endl;
break; // break evita que a execucao "caia" no proximo case
case 2:
cout << "Listando filmes..." << endl;
break;
case 3:
cout << "Encerrando." << endl;
break;
default:
cout << "Opcao invalida." << endl;
}
return 0;
}Na situação que envolve a variável nota usamos o if-else pois ele funciona bem para condições contínuas (faixas de nota). Como as condições são avaliadas de cima para baixo, assim que uma condição verdadeira é encontrada, os blocos seguintes da cadeia são ignorados. Isso poupa tempo de execução e recursos de processamento.
Por outro lado, no contexto do menu numérico o switch é ideal pois trabalha com valores discretos. Assim, o fluxo de decisão fica mais “limpo” para a leitura e entendimento da lógica. É importante destacar que o uso do break é essencial para o bom funcionamento do switch. Sem ele, o C++ continua executando os casos seguintes (case), gerando um comportamento chamado fall-through (Schildt, 1997).
Operador ternário
O operador ternário é uma forma compacta de escrever uma decisão simples em C++. Ele recebe esse nome porque trabalha com três partes: uma condição, um valor para o caso verdadeiro e um valor para o caso falso. Sua estrutura geral é:
condicao ? valor_se_verdadeiro : valor_se_falso
Por exemplo, a expressão
idade >= 18 ? "Maior de idade" : "Menor de idade"verifica se a idade é maior ou igual a 18. Se a condição for verdadeira, retorna o primeiro texto (“Maior de idade”); caso contrário, retorna o segundo (“Menor de idade”).
Na prática, o operador ternário pode substituir um if-else simples quando a decisão serve para escolher um valor. Por exemplo,
string situacao = nota >= 60 ? "Aprovado" : "Reprovado"; armazena em situacao um texto diferente conforme a nota do estudante. A mesma lógica poderia ser escrita com if-else, mas o ternário deixa o código mais curto quando a regra é direta e fácil de ler.
Apesar de útil, o operador ternário deve ser usado com moderação. Quando a condição envolve muitas comparações, várias decisões encadeadas ou blocos de instruções maiores, o if-else costuma ser mais claro. Assim, uma boa regra prática é usar o ternário para escolhas simples de valor e reservar o if-else para decisões mais complexas, nas quais a legibilidade do código é mais importante que a concisão.
Estruturas de repetição
As estruturas de repetição (laços ou loops) executam um bloco de instruções múltiplas vezes enquanto uma condição for verdadeira, evitando a repetição manual de código (Manzano; Oliveira, 2019).
Conforme observamos na Figura 3, as estruturas de repetição permitem a execução de um único bloco de código várias vezes, sem que seja necessário reescrevê-lo excessivamente.

Fonte: Autoria própria com o auxílio de ferramentas de IA.
Segundo Ascencio e Campos (2012), dentre as opções disponíveis de repetição, temos:
- o
while, que testa a condição antes de executar; - o
do-while, que executa pelo menos uma vez, testando a condição depois; e - o
for, que é indicado quando o número de repetições é conhecido.
No código abaixo, apresentamos os três laços de repetição, sendo duas dentro do menu (do-while e for) e uma contagem de encerramento com while.
#include <iostream>
using namespace std;
int main() {
int opcao;
const int MAX_FILMES = 5;
string titulos[MAX_FILMES]; // veremos vetores na proxima secao
int totalCadastrados = 0;
// do-while: garante que o menu apareca ao menos uma vez
do {
cout << "\n1-Cadastrar 2-Listar 0-Sair\nOpcao: ";
cin >> opcao;
cin.ignore(); // limpa o \n deixado pelo cin no buffer
if (opcao == 1) {
if (totalCadastrados < MAX_FILMES) {
cout << "Titulo: ";
getline(cin, titulos[totalCadastrados]);
totalCadastrados++;
} else {
cout << "Backlog cheio!" << endl;
}
} else if (opcao == 2) {
// for: percorre um numero conhecido de posicoes
for (int i = 0; i < totalCadastrados; i++) {
cout << i + 1 << ". " << titulos[i] << endl;
}
}
} while (opcao != 0); // repete enquanto o usuario nao escolher sair
// while: exemplo de contagem regressiva de encerramento
int contagem = 3;
while (contagem > 0) {
cout << "Fechando em " << contagem << "..." << endl;
contagem--;
}
return 0;
}A escolha do do-while no menu se deu porque ele precisa aparecer ao menos uma vez, mesmo antes de qualquer entrada do usuário. Logo, essa era a melhor opção.
Para percorrer o vetor titulos, o for é ideal já que sabemos exatamente seu tamanho (totalCadastrados). Quanto à contagem de encerramento, bem, escolhemos o while por ser simples de escrever.
Observação: usamos cin.ignore() na linha 14 para descartar o caractere de nova linha (\n) deixado no buffer pelo 'cin >>' da linha anterior, evitando que a próxima leitura com getline seja "pulada" — um erro clássico e recorrente para iniciantes.
Strings em C++
Strings são basicamente sequências de caracteres. Em C++ existem duas representações para elas: o vetor de caracteres (em inglês, char array) bem ao estilo da linguagem C, que é um vetor de char terminado pelo símbolo '\0'; e a classe std::string, da biblioteca <string>, muito mais segura e prática (Deitel; Deitel, 2013).
A Figura 4 apresenta como ocorre o armazenamento e o acesso do vetor de caracteres num programa de computador.

Fonte: Autoria própria com o auxílio de ferramentas de IA.
No exemplo abaixo declaramos uma string a partir da classe std::string.
string titulo = "O Senhor dos Anéis";Quando trabalhamos com strings, várias operações ficam à disposição para o(a) desenvolvedor(a) usar. A classe string oferece métodos extras para manipular strings como .length() e .substr(). Essas operações (e outras) facilitam bastante o dia-a-dia na tarefa de desenvolvimento.
cout << titulo.length(); // quantidade de caracteres
cout << titulo[0]; // primeiro caractere
cout << titulo.substr(0, 8); // parte da stringOutra possibilidade com a classe std::string é a sobrecarga do operador ( + ) para concatenação. No código a seguir estamos juntando o texto da variável titulo com o ano convertido em texto pela função to_string, que converte valores numéricos em texto. Entre o título e o ano, existe um sinal de parênteses abrindo e após o ano há o sinal de fechamento dos parênteses.
string descricao = titulo + " (" + to_string(ano) + ")";Também é possível pesquisar uma substring por meio do método find, conforme o exemplo. Nele estamos buscando a palavra “Senhor” dentro do título completo do filme. Existe uma comparação com if para que o código verifique se o trecho foi encontrado ou não.
// string::npos representa uma posição inexistente.
if (titulo.find("Senhor") != string::npos) {
cout << "Trecho encontrado.\n";
}Como uma string é uma sequência de caracteres, podemos realizar uma busca dentro da string. Em outras palavras, podemos percorrer uma string em busca de caracteres específicos usando alguma estrutura de repetição (for, while ou do-while).
Abaixo podemos observar um for baseado em intervalo que percorre diretamente cada caractere da string titulo e imprime os caracteres um a um.
for (char caractere : titulo) {
cout << caractere << '\n';
}Como algumas strings podem conter espaços, o ideal é utilizar o método getline para capturar os valores de variáveis do tipo string. O método cin >> é o jeito mais comum de capturar valores em C++ e continua sendo uma ótima opção para outros tipos de dados (int, float etc.). Apesar de também funcionar com strings, ainda é mais apropriado utilizar o getline para fazer uma leitura completa da string sem o risco de algo ser ignorado por causa de espaços imprevistos.
A sintaxe para usar o getline é:
cout << "Título: ";
getline(cin, titulo);Importante lembrar de limpar o fluxo antes de chamar o getline depois de uma leitura numérica com cin >> para garantir que não será lida nenhuma quebra de linha ( \n ) inesperada.
cin.ignore();Estruturas de dados homogêneas: vetores e matrizes
Uma estrutura homogênea é aquela que armazena vários elementos do mesmo tipo. Em C++, costuma-se chamar uma estrutura homogênea de arranjo (em inglês, array).
Um arranjo tradicional possui tamanho fixo definido na compilação e posições contíguas na memória. Além disso, confome Deitel e Deitel (2017), a primeira posição possui índice zero ( 0 ). Portanto, um array com n elementos termina no índice n - 1.
A Figura 5 traz de forma visual a representação da ideia de array. Observe que o acesso aos elementos se dá através de índices numéricos.

std::vector.Fonte: Autoria própria com o auxílio de ferramentas de IA.
Por exemplo, quando declaramos:
int v[4];estamos criando tecnicamente um array unidimensional com quatro posições inteiras, cujos índices são 0, 1, 2 e 3. No caso de int v[4], as posições válidas são v[0], v[1], v[2] e v[3] e, portanto, tentar acessar v[4] é um erro lógico. Como essa posição está fora dos limites do array e não existe na estrutura, pode-se considerar também como um erro semântico no código.
O programador precisa controlar esses limites manualmente, já que o acesso com colchetes não verifica automaticamente se o índice é válido. Outra característica importante é que o tamanho de um array nativo normalmente é fixo após sua criação. Por isso, se o programa precisa armazenar mais elementos do que o previsto, é necessário criar outra estrutura, controlar memória dinamicamente ou usar alternativas modernas, como std::vector (que aparece na Figura 5).
Vetores
Na prática didática de Algoritmos, a estrutura int v[4] que criamos acima costuma ser chamada de vetor, porque representa uma sequência linear de elementos acessados por índice. Assim, em sala de aula, é aceitável dizer que int v[4] é um vetor de inteiros.
Porém, cabe destacar que em uma explicação mais rigorosa de C++, o termo mais preciso é array nativo unidimensional, herdado da linguagem C. A diferença terminológica importa porque C++ também possui std::vector, que é um contêiner dinâmico da biblioteca padrão (introduzido no C++ 11), diferente do array nativo de tamanho fixo. Exemplos de utilização e operações da classe std::vector estão na Figura 5.
Na prática, o termo vetor acabou adquirindo dois significados entre os programadores de língua portuguesa. Para este artigo, os arrays nativos unidimensionais e vetores serão tratados como sinônimos, pois vamos declará-los com a “roupagem” herdada da linguagem C, descrita no código abaixo.
const int CAPACIDADE = 5;
string titulos[CAPACIDADE];
double notas[CAPACIDADE];No código acima, temos dois arrays paralelos para armazenar os dados de um mesmo filme. Um deles é do tipo string e o outro do tipo double. Ambos possuem o mesmo tamanho, definido pela constante CAPACIDADE (igual a 5). Em casos assim, é importante que os valores sejam atribuídos de forma a compartilharem o mesmo índice nos arrays paralelos, para que a representação do objeto do mundo real seja fidedigna.
// Ambos os dados, que se referem ao mesmo filme, estão na posição zero dos dois vetores.
titulos[0] = "Interestelar";
notas[0] = 8.7;Para percorrer os elementos de um vetor, podemos utilizar alguma estrutura de repetição, como o laço for abaixo:
for (int indice = 0; indice < quantidade; indice++) {
cout << titulos[indice] << " - " << notas[indice] << '\n';
}Quando vamos percorrer um vetor, é importante que o programa impeça o acesso fora do intervalo entre 0 e CAPACIDADE - 1 (que é igual a 4). Acessar uma posição inválida produz comportamento indefinido no código, por isso, a verificação de continuidade do laço for não é menor e igual ( indice <= quantidade ), é apenas menor ( indice < quantidade ).
Matrizes
De forma simplória, uma matriz é um array com duas ou mais dimensões. Ela possui dois atributos que indicam a quantidade de linhas e de colunas que ela utiliza para armazenar os valores. Por essa construção em linhas e colunas, muitas pessoas associam matrizes à ideia de planilhas, igual à exposta na Figura 6.

Fonte: Autoria própria com o auxílio de ferramentas de IA.
Nos moldes do backlog que estamos descrevendo aqui, cada linha pode representar um filme e cada coluna um critério, como roteiro e direção. No exemplo abaixo, a matriz avaliacoes tem 3 linhas (índices de 0 a 2) e 2 colunas (índices de 0 a 1). Cada linha é um filme e cada coluna é um critério de avaliação do filme naquela linha específica.
const int QUANTIDADE_FILMES = 3;
const int QUANTIDADE_CRITERIOS = 2;
double avaliacoes[QUANTIDADE_FILMES][QUANTIDADE_CRITERIOS] = {
{9.0, 8.5},
{7.5, 8.0},
{8.8, 9.2}
};Com o objetivo de acessarmos os dados, podemos usar laços de repetição, sendo um laço externo para percorrer as linhas e um loop interno para percorrer as colunas.
for (int filme = 0; filme < QUANTIDADE_FILMES; filme++) {
double soma = 0.0;
for (int criterio = 0; criterio < QUANTIDADE_CRITERIOS; criterio++) {
soma += avaliacoes[filme][criterio];
}
double media = soma / QUANTIDADE_CRITERIOS;
cout << "Média do filme " << filme << ": " << media << '\n';
}Em casos mais simples, os arrays paralelos funcionam, mas tornam o código vulnerável a inconsistências. Por exemplo, as variáveis título, ano, nota e estado do nosso contexto precisam permanecer sincronizados pelo mesmo índice e, durante a codificação, algo pode ser modificado sem que alguém perceba. Para resolver essa questão, uma estrutura heterogênea é uma boa opção.
Estruturas de dados heterogêneas: registros com struct
Um registro (ou struct) agrupa campos de tipos diferentes em uma única unidade lógica. Em C++, a struct declara um novo tipo composto que agrega vários atributos, igual o exemplo na Figura 7. Conforme Stroustrup (2024), essa organização aproxima a representação computacional do objeto do domínio.

Fonte: Autoria própria com o auxílio de ferramentas de IA.
A forma para se declarar uma struct em C++ está representada no código a seguir. No exemplo temos um estrutura chamada Filme que possui os seguintes campos: codigo, titulo, ano, nota e assistido. Observe que cada campo possui os seu tipo.
struct Filme {
int codigo;
string titulo;
int ano;
double nota;
bool assistido;
};A partir deste registro, podemos declarar variáveis do tipo Filme contenham todos os dados relacionados:
Filme backlog;
backlog.codigo = 1;
backlog.titulo = "Interestelar";
backlog.ano = 2014;
backlog.nota = 8.7;
backlog.assistido = true;Para acessar os campos da struct, fazemos uso do operador ponto ( . ), conforme exposto no exemplo. Há também o operador seta ( ) descrito na Figura 7, mas ele descreve um conceito que não vamos trabalhar neste artigo (ponteiros). Portanto, deixaremos para discuti-lo em outra oportunidade.
A partir do tipo composto Filme, é possível criar vetores de registros através do método de arrays em C++. Dessa forma, temos uma organização melhor dos dados, garantindo que elas sempre estarão dentro da mesma posição e unificadas por ela.
const int CAPACIDADE = 100;
Filme backlog[CAPACIDADE];Dessa forma, cada posição do vetor contém um registro completo dos dados que desejamos manipular sobre os filmes.
backlog[0].titulo = "Interestelar";
backlog[0].ano = 2014;As principais vantagens das structs é a redução do risco de desencontro nas posições dos dados entre vetores paralelos e a maior facilidade na passagem de um filme inteiro para funções.
Funções e procedimentos
A função é um recurso de programação que divide um programa em unidades menores e nomeadas. A decomposição modular reduz repetição, limita responsabilidades e permite testar partes da solução separadamente. De acordo com Martin (2009), uma boa função deve realizar uma tarefa coerente e possuir interface clara.
Observando a Figura 8, vemos que uma função (ou procedimento) pode receber parâmetros de entrada, que são valores iniciais para que a execução possa ser realizada. Esses parâmetros funcionam dentro do escopo da função (escopo local) e são recebidos dentro dos parênteses que aparecem após o nome da função/procedimento.

Fonte: Autoria própria com o auxílio de ferramentas de IA.
Em C++, quando uma rotina é declarada com tipo de retorno (int, float etc.), ela é chamada de função, igual ao código abaixo.
/*
Exemplo de função que possui o tipo double e se chama calcularMedia.
Ela recebe dois parâmetros, chamados nota1 e nota2, também do tipo double.
*/
double calcularMedia(double nota1, double nota2) {
return (nota1 + nota2) / 2.0;
}Quando o retorno é void, a rotina funciona como aquilo que a literatura introdutória de programação costuma chamar de procedimento. A sintaxe é exatamente a mesma das funções, com exceção para o tipo de dado retornado, que é void neste caso. Isso quer dizer que não há retorno ao final do bloco. Além disso, costuma-se nomear como procedimento aqueles blocos de código que não produzem valores durante sua execução, conforme o código a seguir.
/*
Neste exemplo, o procedimento apenas imprime um texto na
tela, sem produzir um valor novo durante sua execução.
*/
void exibirCabecalho() {
cout << "BACKLOG DE FILMES\n";
}Como a linguagem C++ não possui uma palavra reservada separada chamada procedure, a distinção entre função e procedimento é mais conceitual do que técnica. Na Tabela 4 podemos verificar as características entre os conceitos.
| Aspecto | Função em C++ | Procedimento em C++ |
| Tipo de retorno | Qualquer um diferente de void | void |
| Produz algum valor durante sua execução | Sim | Não |
| Exemplo | double calcularMedia() | void listarFilmes() |
Protótipos
Caso a função ou o procedimento sejam definidos e sua lógica estruturada após o bloco main, é necessário declarar seu protótipo antes, para que o compilador saiba qual função/procedimento está sendo referenciada(o) e faça a devida conexão no arquivo intermediário da compilação. O protótipo serve para informar ao compilador o nome, o tipo de retorno e os parâmetros esperados.
Observe o código abaixo. Temos dois blocos além do main, que são o procedimento exibirMenu() e a função lerOpcao(). Ambos estão sendo implementados após o bloco main, porém, seus protótipos estão declarados antes, para garantir a correta referência do compilador.
void exibirMenu();
int lerOpcao();
int main() {
exibirMenu();
int opcao = lerOpcao();
}
void exibirMenu() {
// implementação
}
int lerOpcao() {
// implementação
}Passagem de parâmetros por valor e por referência
Existem duas formas de passar parâmetros para as funções/procedimentos. Uma delas é a passagem por valor, onde a função recebe uma cópia do argumento, ou seja, o valor original é copiado e enviado para a outra variável. Isso implica que alterações no parâmetro local (dentro da função) não afetam a variável original, graças ao escopo local.
No código a seguir temos a variável quantidade recebendo o valor 5. Este valor é copiado e enviado para o parâmetro numero. Após a execução da linha 2, numero passa a valer 6 (obs: o operador ++ acrescenta 1 ao valor original da variável). Entretanto, a variável quantidade mantém seu valor original, que é 5.
// Exemplo de passagem de parâmetro por valor
void incrementarPorValor(int numero) {
numero++; // numero passa a valer 6
}
int quantidade = 5;
incrementarPorValor(quantidade); // quantidade continua valendo 5O outro método é a passagem por referência. Nela, o parâmetro funciona como outro nome para a variável original. Ao invés da cópia do valor inicial, temos apenas um apontamento para o local da memória principal – chamada popularmente de RAM – onde o valor original está armazenado. Quando for este o caso de uso, o símbolo ( & ) aparece na declaração da função/procedimento.
void incrementarPorReferencia(int& numero) {
numero++; // numero passa a valer 6
}
int quantidade = 5;
incrementarPorReferencia(quantidade); // quantidade passa a valer 6, porque o parâmetro alterou o valor original na posição de memória RAMA passagem por valor favorece o isolamento dos dados, pois a função não modifica o valor original. Em contrapartida, copiar estruturas grandes pode aumentar o custo da aplicação. Neste caso, a passagem por referência evita a cópia de dados pesados na memória, melhorando o desempenho do programa. Contudo, ela exige cuidado com efeitos colaterais, pois o(a) desenvolvedor(a) pode acabar modificando valores que não desejava sem perceber.
Em alguns casos, o(a) desenvolvedor(a) pode utilizar a referência constante como instrumento de proteção, que combina eficiência e proteção contra modificações acidentais.
void exibirFilme(const Filmes& filme) {
cout << filme.titulo << '\n';
}No código acima, o parâmetro filme é constante e recebe a referência da posição de memória do valor original para executar sua tarefa de imprimir o título. Apenas pelo fato de declararmos o parâmetro como const Filme&, evitamos copiar o registro todo e ainda impedimos modificações dentro da função.
Conclusão
Antes de qualquer coisa, existe uma ideia. Depois, essa ideia é convertida em um algoritmo que deve organizar a solução de problemas em passos precisos, executáveis e verificáveis. Para um desenvolvedor ou analista de sistemas, esse domínio possui impacto direto na qualidade profissional.
A análise de requisitos depende da capacidade de identificar dados, regras e fluxos, enquanto a implementação exige selecionar estruturas adequadas e controlar estados. Mesmo em ambientes com frameworks e recursos de alto nível, erros fundamentais continuam relacionados a condições incorretas, limites de arrays, validação insuficiente, efeitos colaterais e representação inadequada dos dados.
Aprender algoritmos, portanto, significa desenvolver uma forma disciplinada de raciocinar sobre sistemas. Essa base sustenta conteúdos posteriores de estruturas de dados, orientação a objetos, engenharia de software, bancos de dados e análise de desempenho.
Antes de encerrarmos, vou deixar o código completo de uma primeira versão do backlog com todos os conceitos que vimos neste artigo, desde o básico até as funções/procedimentos. Recomendo que estude-o com atenção e escreva o código para treinar.
#include <cctype>
#include <iomanip>
#include <iostream>
#include <limits>
#include <string>
using namespace std;
const int CAPACIDADE_MAXIMA = 100;
const double NOTA_MINIMA = 0.0;
const double NOTA_MAXIMA = 10.0;
struct Filme {
int codigo;
string titulo;
int ano;
double nota;
bool assistido;
};
void limparEntrada();
void exibirMenu();
int lerInteiro(const string& mensagem);
double lerNota(const string& mensagem);
bool lerBooleano(const string& mensagem);
string converterParaMinusculas(string texto);
string obterSituacao(bool assistido);
void cadastrarFilme(Filme backlog[], int& quantidade);
void listarFilmes(const Filme backlog[], int quantidade);
int buscarIndicePorCodigo(const Filme backlog[], int quantidade, int codigo);
void pesquisarPorTitulo(const Filme backlog[], int quantidade);
void atualizarNota(Filme backlog[], int quantidade);
void marcarComoAssistido(Filme backlog[], int quantidade);
int main() {
Filme backlog[CAPACIDADE_MAXIMA];
int quantidade = 0;
int opcao;
do {
exibirMenu();
opcao = lerInteiro("Opção: ");
switch (opcao) {
case 1:
cadastrarFilme(backlog, quantidade);
break;
case 2:
listarFilmes(backlog, quantidade);
break;
case 3:
pesquisarPorTitulo(backlog, quantidade);
break;
case 4:
atualizarNota(backlog, quantidade);
break;
case 5:
marcarComoAssistido(backlog, quantidade);
break;
case 0:
cout << "Programa encerrado.\n";
break;
default:
cout << "Opção inválida.\n";
}
} while (opcao != 0);
return 0;
}
void limparEntrada() {
cin.clear();
cin.ignore(
numeric_limits<streamsize>::max(),
'\n'
);
}
void exibirMenu() {
cout << "\n===== BACKLOG DE FILMES =====\n";
cout << "1 - Cadastrar filme\n";
cout << "2 - Listar filmes\n";
cout << "3 - Pesquisar por título\n";
cout << "4 - Atualizar nota\n";
cout << "5 - Marcar como assistido\n";
cout << "0 - Sair\n";
}
int lerInteiro(const string& mensagem) {
int valor;
while (true) {
cout << mensagem;
if (cin >> valor) {
limparEntrada();
return valor;
}
cout << "Valor inválido. Digite um número inteiro.\n";
limparEntrada();
}
}
double lerNota(const string& mensagem) {
double nota;
while (true) {
cout << mensagem;
if (cin >> nota &&
nota >= NOTA_MINIMA &&
nota <= NOTA_MAXIMA) {
limparEntrada();
return nota;
}
cout << "Nota inválida. Informe um valor entre "
<< NOTA_MINIMA << " e "
<< NOTA_MAXIMA << ".\n";
limparEntrada();
}
}
bool lerBooleano(const string& mensagem) {
int valor;
while (true) {
valor = lerInteiro(mensagem);
if (valor == 0 || valor == 1) {
return valor == 1;
}
cout << "Digite 1 para sim ou 0 para não.\n";
}
}
string converterParaMinusculas(string texto) {
for (char& caractere : texto) {
caractere = static_cast<char>(
tolower(
static_cast<unsigned char>(caractere)
)
);
}
return texto;
}
string obterSituacao(bool assistido) {
return assistido ? "Assistido" : "Pendente";
}
void cadastrarFilme(Filme backlog[], int& quantidade) {
if (quantidade >= CAPACIDADE_MAXIMA) {
cout << "Capacidade máxima atingida.\n";
return;
}
Filme novoFilme;
novoFilme.codigo =
quantidade == 0
? 1
: backlog[quantidade - 1].codigo + 1;
cout << "Título: ";
getline(cin, novoFilme.titulo);
while (novoFilme.titulo.empty()) {
cout << "O título não pode ficar vazio.\n";
cout << "Título: ";
getline(cin, novoFilme.titulo);
}
novoFilme.ano =
lerInteiro("Ano de lançamento: ");
novoFilme.nota =
lerNota("Nota de 0 a 10: ");
novoFilme.assistido =
lerBooleano("Assistido? (1/0): ");
backlog[quantidade] = novoFilme;
quantidade++;
cout << "Filme cadastrado com o código "
<< novoFilme.codigo << ".\n";
}
void listarFilmes(const Filme backlog[], int quantidade) {
if (quantidade == 0) {
cout << "O backlog está vazio.\n";
return;
}
cout << left
<< setw(8) << "Código"
<< setw(32) << "Título"
<< setw(8) << "Ano"
<< setw(8) << "Nota"
<< "Situação\n";
cout << string(70, '-') << '\n';
for (int indice = 0;
indice < quantidade;
indice++) {
const Filme& filme = backlog[indice];
cout << left
<< setw(8) << filme.codigo
<< setw(32) << filme.titulo.substr(0, 30)
<< setw(8) << filme.ano
<< setw(8) << fixed << setprecision(1)
<< filme.nota
<< obterSituacao(filme.assistido)
<< '\n';
}
}
int buscarIndicePorCodigo(const Filme backlog[], int quantidade, int codigo) {
for (int indice = 0;
indice < quantidade;
indice++) {
if (backlog[indice].codigo == codigo) {
return indice;
}
}
return -1;
}
void pesquisarPorTitulo(const Filme backlog[], int quantidade) {
if (quantidade == 0) {
cout << "O backlog está vazio.\n";
return;
}
string termo;
cout << "Informe parte do título: ";
getline(cin, termo);
string termoNormalizado =
converterParaMinusculas(termo);
bool encontrou = false;
for (int indice = 0;
indice < quantidade;
indice++) {
string tituloNormalizado =
converterParaMinusculas(
backlog[indice].titulo
);
if (tituloNormalizado.find(
termoNormalizado
) != string::npos) {
const Filme& filme = backlog[indice];
cout << filme.codigo << " - "
<< filme.titulo << " ("
<< filme.ano << "), nota "
<< filme.nota << ", "
<< obterSituacao(filme.assistido)
<< '\n';
encontrou = true;
}
}
if (!encontrou) {
cout << "Nenhum filme encontrado.\n";
}
}
void atualizarNota(Filme backlog[], int quantidade) {
int codigo =
lerInteiro("Código do filme: ");
int indice = buscarIndicePorCodigo(
backlog,
quantidade,
codigo
);
if (indice == -1) {
cout << "Filme não encontrado.\n";
return;
}
backlog[indice].nota =
lerNota("Nova nota: ");
cout << "Nota atualizada.\n";
}
void marcarComoAssistido(Filme backlog[], int quantidade) {
int codigo =
lerInteiro("Código do filme: ");
int indice = buscarIndicePorCodigo(
backlog,
quantidade,
codigo
);
if (indice == -1) {
cout << "Filme não encontrado.\n";
return;
}
backlog[indice].assistido = true;
cout << "Filme marcado como assistido.\n";
}Obrigado pela leitura e bons estudos!
Referências
ASCENCIO, Ana Fernanda Gomes; CAMPOS, Edilene Aparecida Veneruchi de. Fundamentos da programação de computadores: algoritmos, Pascal, C/C++ e Java. 3. ed. São Paulo: Pearson Prentice Hall, 2012.
CORMEN, Thomas H.; LEISERSON, Charles E.; RIVEST, Ronald L.; STEIN, Clifford. Introduction to algorithms. 4th ed. Cambridge, MA: MIT Press, 2022.
DEITEL, Paul; DEITEL, Harvey. C++ como programar. 6. ed. São Paulo: Pearson, 2013.
FARRER, Harry et al. Algoritmos estruturados. 3. ed. Rio de Janeiro: LTC, 1999.
FORBELLONE, André Luiz Villar; EBERSPÄCHER, Henri Frederico. Lógica de programação: a construção de algoritmos e estruturas de dados. 3. ed. São Paulo: Pearson Prentice Hall, 2005.
MANZANO, José Augusto Navarro Garcia; OLIVEIRA, Jayr Figueiredo de. Algoritmos: lógica para desenvolvimento de programação de computadores. 27. ed. São Paulo: Érica, 2019.
SCHILDT, Herbert. C++: completo e total. 3. ed. São Paulo: Pearson Makron Books, 1997.
STROUSTRUP, Bjarne. Programming: principles and practice using C++. 3 ed. Boston: Addison-Wesley Professional, 2024.


