Fundamentos de C++
Tipos, expressões, entrada e saída, condições, ciclos e âmbito de variáveis.
Estrutura de um programa
Este programa completo escreve uma mensagem fixa no ecrã:
#include <iostream>
int main() {
std::cout << "Olá\n";
}
Ao correr o programa, vês Olá numa linha.
A primeira linha traz a ferramenta de escrita no ecrã. O bloco int main() marca onde o computador começa. A linha do meio escreve o texto.
Cada instrução simples termina com ;. Um bloco de função ou de ciclo não
precisa de ; depois da chaveta final. Uma definição de classe e um
do ... while terminam com ;. \n dentro do texto muda de linha.
O compilador traduz este ficheiro para um executável. Chegar ao fim de main equivale a return 0;, que indica sucesso ao sistema.
Declarar variáveis com tipos fixos
Uma variável dá nome a um objeto cujo tipo determina os valores representáveis e as operações disponíveis.
int guarda inteiros, double guarda valores de vírgula flutuante, char guarda uma unidade de carácter e bool guarda true ou false. Um objeto é uma região de armazenamento com um tipo e uma variável dá-lhe um nome, por isso inicializa as variáveis antes da primeira leitura.
// Excerto de declarações.
int quantidade = 0;
double preco = 2.75;
char letra = 'A';
bool encontrado = false;
const int limite = 20;
auto total = 3.5; // O compilador deduz double; o tipo continua fixo.
const impede alterar o objeto através desse nome. auto pede ao compilador que deduza o tipo a partir do inicializador, não adia a decisão para a execução. Uma inicialização com chavetas, como int n{3};, impede certas conversões que perdem informação. int n{3.7}; é rejeitado; int n = 3.7; converte para 3.
A largura de int e de long depende do sistema, por isso não assumes tamanhos fixos. Usa sizeof(T) para medir bytes e <limits> para consultar limites. Nos exemplos desta cadeira, char conta armazenamento, não letras visuais de Unicode.
Operadores e divisão
+, -, *, / e % fazem operações aritméticas, com % a calcular o resto de uma divisão inteira. == compara; = atribui. Os operadores relacionais, como < e >=, produzem um bool.
#include <iostream>
int main() {
int a = 7;
int b = 2;
double truncado = a / b;
double quociente = static_cast<double>(a) / b;
std::cout << truncado << " " << quociente << "\n";
std::cout << a % b << " " << std::boolalpha << (a > b) << "\n";
}Executa o código para ver a saída e os gráficos.
Saída:
3 3.5
1 true
A divisão inteira trunca em direção a zero. Por exemplo, -7 / 2 dá -3 e -7 % 2 dá -1, mantendo a == (a / b) * b + a % b quando a operação é representável.
Dividir um inteiro por zero tem comportamento indefinido. O overflow de um inteiro com sinal também tem comportamento indefinido. Não contes com que volte ao menor valor.
Os valores double são aproximações. Para resultados calculados, escolhe o teste adequado ao contrato e ao erro numérico, por exemplo std::fabs(a - b) < 1e-9 com <cmath>.
&& representa “e”, || representa “ou” e ! nega uma condição. Os dois primeiros usam curto-circuito, ou seja, em n != 0 && soma / n > 10 a divisão só é avaliada se n != 0. Usa parênteses quando ajudam a ver a ordem.
Não escrevas 0 <= nota <= 20: C++ compara primeiro 0 <= nota, produz 0 ou 1, e compara esse resultado com 20. Escreve 0 <= nota && nota <= 20.
++i incrementa antes de produzir o valor; i++ produz o valor anterior e incrementa. Num ciclo que ignora esse valor, ambos avançam uma posição. Evita alterar a mesma variável várias vezes na mesma expressão.
Entrada e saída com iostream
#include <iostream> disponibiliza os fluxos padrão. std::cout << valor escreve; std::cin >> valor tenta extrair um valor do tipo da variável. std:: indica o namespace da biblioteca padrão. Na condição de um if ou while, o fluxo vale falso se uma leitura anterior ativou failbit ou badbit. Encontrar o fim e ativar apenas eofbit não torna uma leitura bem-sucedida falsa. A página Ficheiros e validação desenvolve estes estados com registos completos.
Não uses uma variável como se a leitura tivesse resultado sem confirmar o estado do fluxo. Em if (!(std::cin >> n)), o programa deteta entrada ausente ou inválida. Esta validação é diferente de verificar se o valor está no intervalo permitido.
Escolha com switch
Quando a escolha depende de um valor inteiro com casos fixos, switch evita uma cadeia de if. Quando não pretendes continuar no caso seguinte, sai com break ou return. Casos agrupados e queda intencional continuam possíveis.
// Excerto: classifica uma opção de menu.
int opcao = 2;
switch (opcao) {
case 1: std::cout << "um\n"; break;
case 2: std::cout << "dois\n"; break;
default: std::cout << "outra\n"; break;
}
Com opcao = 2, o programa escreve dois. Sem o break do caso 1, escolher 1 executaria também o caso 2. O default trata os valores não listados.
Escolha, iteração e âmbito
if executa um ramo conforme uma condição e else corresponde ao if sem else mais próximo.
O for separa inicialização, condição e avanço. O programa seguinte lê uma quantidade de notas e conta as que são pelo menos 10; as notas variam de 0 a 20 e a quantidade não excede 1000.
#include <iostream>
int main() {
int quantidade = 0;
if (!(std::cin >> quantidade) || quantidade < 0 || quantidade > 1000) {
std::cout << "Quantidade invalida\n";
return 0;
}
int aprovadas = 0;
for (int i = 0; i < quantidade; ++i) {
int nota = 0;
if (!(std::cin >> nota) || nota < 0 || nota > 20) {
std::cout << "Nota invalida\n";
return 0;
}
if (nota >= 10) ++aprovadas;
}
std::cout << "Aprovadas: " << aprovadas << "\n";
}Executa o código para ver a saída e os gráficos.
Saída:
Aprovadas: 3
Segue o exemplo com entrada 5 e notas 9 10 15 0 20:
Antes da iteração i | Notas já lidas | aprovadas |
|---|---|---|
| 0 | nenhuma | 0 |
| 1 | 9 | 0 |
| 2 | 9, 10 | 1 |
| 3 | 9, 10, 15 | 2 |
| 4 | 9, 10, 15, 0 | 2 |
| 5 | 9, 10, 15, 0, 20 | 3 |
Antes da iteração de índice i, já lemos i notas válidas e aprovadas conta apenas essas notas. Esta afirmação é um invariante do ciclo. A leitura seguinte e o if mantêm-no, e ++i aproxima o fim. Quando i atinge a quantidade, todas as notas foram consideradas. Para quantidade zero, o corpo não corre.
while testa antes de cada iteração e pode correr zero vezes. do ... while testa depois e corre pelo menos uma vez. Usa while quando o corpo pode não fazer sentido nenhuma vez, como ler notas de uma quantidade que pode ser zero.
Uma variável declarada num bloco só é visível nesse bloco e nos blocos interiores que não a ocultam. O i declarado no for deixa de estar visível depois dele. Uma variável local normal tem duração automática: a sua vida termina ao sair do bloco. Âmbito indica onde o nome é visível e duração de vida indica quando o objeto existe; não são a mesma coisa.
Compilar e correr com g++
Guarda o programa num ficheiro notas.cpp. Num terminal com GCC disponível, na pasta do ficheiro, corre:
g++ -std=c++17 -Wall -Wextra -Wpedantic notas.cpp -o notas
./notas
O primeiro comando compila e liga o programa. -o escolhe o executável, -std=c++17 fixa a linguagem e as opções -W ativam avisos. Se não houver erros, executa o segundo comando e introduz os números. Depois de mudares o código, volta a compilar, porque correr um executável antigo não valida a versão nova.
Toolchain no macOS e no Windows
No macOS, o comando g++ pode identificar o Clang. Confirma com g++ --version; os programas destas páginas usam C++17 portátil. No Windows, usa a toolchain indicada pela cadeira ou um terminal WSL com GCC. Projetos com vários ficheiros precisam de um ambiente local.
Projetos com CMake e CLion
Projetos com vários ficheiros usam CMake para declarar cada executável e o CLion para configurar a toolchain e depurar. A página Organizar e depurar programas mostra o CMakeLists.txt mínimo e os dois comandos de configuração e build para um projeto com main.cpp.
Passagem de argumentos
Uma função pode receber uma cópia ou dar acesso ao objeto original. A escolha faz parte do contrato, não depende de o tipo ser “imutável” noutra linguagem. Vê Funções, arrays e texto para seguir a diferença linha a linha.
Strings
std::string é o tipo habitual para texto e gere o seu armazenamento. std::cin >> texto lê até ao próximo espaço; std::getline lê uma linha. Os exemplos de texto distinguem estas duas operações e as strings de C.
Ler e escrever ficheiros
Ficheiros usam fluxos como a entrada padrão, mas a abertura e os erros precisam de tratamento. A página Ficheiros e validação explica o ciclo correto e por que while (!eof()) é inadequado.
Como complemento aos primeiros programas, tens uma visão geral de C++ de Rodrigo Dias:
Exercícios
Conversão e divisão inteira
Com int soma = 11, n = 4;, prevê os valores de
double a = soma / n;,
double b = static_cast<double>(soma) / n; e
int c = -soma / n;. Uma média deve conservar a parte fracionária.
Qual expressão serve e porquê? Não executes antes de prever.
Pista
Determina primeiro o tipo de cada operando de /.
Solução
a vale 2.0: a divisão inteira produz 2 antes da
conversão para o destino. b vale 2.75, pois um
operando já é double quando a divisão é calculada.
c vale -2, porque a divisão inteira trunca em
direção a zero, em vez de arredondar para -3. Para a média, usa a expressão
de b, verificando antes que n não é zero.
Confere a tua resposta:
- Obtive 2, 2.75 e -2 nos três resultados.
- Expliquei em que expressão a conversão antecede a divisão.
- Corrigi o cálculo da média sem alterar os valores de entrada.
Justificar o limite de um ciclo
O programa abaixo começa com total = 0 e executa
total += i enquanto i < 4.
for (int i = 0; i < 4; ++i) total += i;Indica os valores visitados, o total final e o que mudaria com
i <= 4. Num array de quatro elementos, os índices válidos
são 0 a 3.
Pista
Escreve cada valor de i que satisfaz a condição, por ordem.
Solução
Com i < 4, o corpo usa 0, 1, 2, 3, dando
0 + 1 + 2 + 3 = 6. Com i <= 4, inclui também
4, dando 10. Num array de quatro elementos, o
segundo limite permitiria o índice inválido 4.
Confere a tua resposta:
- Enumerei 0, 1, 2 e 3.
- Obtive total 6 e distingui o total 10 da alternativa.
- Expliquei o risco de usar <= com índices de um array.
Erros frequentes
Não contes o valor que termina o ciclo como se tivesse passado pelo corpo.
Ordem da proteção de uma divisão
soma e n são inteiros não negativos.
Compara as condições abaixo para n = 0. Qual evita a divisão
por zero? Justifica a ordem de avaliação e prevê o resultado dessa condição
para soma = 60 e n = 5.
n != 0 && soma / n > 10
soma / n > 10 && n != 0
n == 0 && soma / n > 10Pista
&&, uma condição esquerda falsa impede avaliar a direita.Solução
A primeira condição é segura para zero: n != 0 é falso e a
divisão não é avaliada. Na segunda, a divisão vem antes da proteção. Na
terceira, n == 0 é verdadeiro e obriga a avaliar a divisão.
Estas duas versões têm comportamento indefinido para zero, não uma resposta
booleana que possas prever. Com 60 e 5, a primeira avalia
12 > 10 e dá verdadeiro.
Confere a tua resposta:
- Identifiquei a primeira condição como a única segura para n igual a zero.
- Localizei a divisão indevida nas outras duas expressões.
- Obtive verdadeiro para a primeira com soma 60 e n 5.
