No artigo O Mapa da Jornada: de C a C++ em 52 Passos desenhamos o mapa e compilamos um "Olá, C++!" quase sem explicar por quê. Prometi que a partir daqui todo código roda de verdade e que começaríamos pelo que muda de imediato ao sair do C. Cumpro a promessa aqui atacando três coisas que você vê logo na primeira linha de qualquer programa C++: o #include <iostream> no lugar do <stdio.h>, o std::cout no lugar do printf, e esse prefixo std:: que aparece grudado em quase tudo. Por trás desses três detalhes está a filosofia inteira da linguagem — e entendê-la agora vai poupar você de muita confusão adiante.
Por que o C++ existe (e no que ele discorda do C)
O C foi projetado para ser uma camada finíssima sobre o hardware: o que você escreve é quase exatamente o que a máquina executa. Isso o torna previsível e rápido, mas joga toda a responsabilidade nas suas costas — é você quem libera memória, verifica tipos e evita erros. O C++ nasceu da tese de Bjarne Stroustrup de que dava para manter essa performance de C e, ao mesmo tempo, deixar o compilador trabalhar mais a seu favor: pegar erros antes de o programa rodar, deixar os recursos se auto-gerenciarem, e permitir abstrações que não custam nada em tempo de execução. Essa última ideia tem nome — zero-overhead abstraction — e é o lema da casa: você não paga pelo que não usa, e o que usa é tão eficiente quanto se tivesse escrito à mão em C. Guarde essa promessa; ela explica por que tantos recursos "de alto nível" que veremos (ponteiros inteligentes, iteradores, lambdas) não deixam o programa mais lento.
Streams: o fim da string de formato
Em C, você imprime assim:
#include <stdio.h>
int main(void) {
int idade = 30;
printf("Idade: %d\n", idade); // você promete que 'idade' é um int
return 0;
}
O %d é uma promessa que você faz ao compilador: "aqui vai um int". Se você mentir — passar um double, ou trocar a ordem dos argumentos — o compilador raramente reclama, e o programa imprime lixo ou quebra. Em C++, a saída é feita por streams, e a promessa some:
#include <iostream> // std::cout, std::cin, std::endl vivem aqui
int main() {
int idade = 30;
// O operador << descobre SOZINHO que 'idade' é int e imprime como int.
// Sem string de formato, sem chance de descasar tipo e argumento.
std::cout << "Idade: " << idade << '\n';
return 0;
}
O std::cout é o fluxo de saída padrão (equivalente ao stdout), e << é o operador de inserção: ele "empurra" o valor para dentro do fluxo. A mágica é que << está sobrecarregado para cada tipo — existe uma versão para int, outra para double, outra para std::string — e o compilador escolhe a certa em tempo de compilação, olhando o tipo real do dado. Você nunca mais descasa %d de um double. A contrapartida honesta: a formatação numérica fina (casas decimais, alinhamento, preenchimento) é mais verbosa com streams do que com printf, e voltaremos a ela quando fizer diferença.
A entrada é simétrica. Onde em C você usava scanf("%d", &idade) — com aquele & obrigatório e fácil de esquecer —, em C++ você usa std::cin com o operador >>:
#include <iostream>
int main() {
int idade;
std::cout << "Digite sua idade: ";
// >> lê da entrada e escreve direto na variável.
// Repare: NÃO precisa do & que o scanf exigia.
std::cin >> idade;
std::cout << "Ano que vem você terá " << idade + 1 << " anos.\n";
return 0;
}
Some o &. Some a string de formato. O std::cin >> idade sabe que idade é um int e lê um inteiro. Se o usuário digitar texto em vez de número, o stream entra num estado de erro em vez de corromper a memória — tratar esse estado é assunto de uma aula futura, mas note desde já que o default aqui é falhar com segurança, não com um comportamento indefinido.
Namespaces e o mistério do std::
Falta explicar esse std:: colado em cout, cin e endl. Em C, todos os nomes vivem num único saco global: se a biblioteca padrão tem uma função count e você também cria uma count, há conflito, e o remédio clássico era prefixar tudo à mão (minhalib_count, outralib_count). O C++ resolve isso com namespaces: gavetas nomeadas onde os nomes moram. Toda a biblioteca padrão vive na gaveta chamada std, e std::cout significa literalmente "o cout que está dentro de std". O :: é o operador de resolução de escopo — ele diz "procure este nome dentro daquela gaveta".
Você pode pedir para abrir a gaveta e não repetir o prefixo:
#include <iostream>
using namespace std; // traz TODOS os nomes de std para o escopo atual
int main() {
cout << "Sem prefixo agora\n"; // funciona, mas...
return 0;
}
Isso compila e muita gente faz — mas eu vou te pedir para não fazer, e a honestidade sobre o trade-off é o ponto. O using namespace std; despeja centenas de nomes no seu escopo, e um deles pode colidir com algo seu (a STL tem count, distance, swap, data... nomes tão comuns que você provavelmente já usou). Em programas pequenos raramente dói; em código real e em cabeçalhos, é fonte de bugs sutis. O meio-termo idiomático é abrir só o que você usa:
#include <iostream>
int main() {
using std::cout; // traz apenas 'cout' para este escopo
cout << "Só o cout foi importado\n";
// std::cin continua exigindo o prefixo — o que é bom: fica explícito
return 0;
}
Ao longo da série eu vou manter o std:: explícito na maioria dos exemplos. Não é preciosismo: escrever std::sort deixa claro para quem lê que aquele sort é o da biblioteca padrão, e não uma função sua. Clareza vale mais que economia de cinco caracteres.
Juntando tudo
Um programa completo que lê dois números e imprime a soma reúne as três ideias — streams de entrada e saída, e o namespace std — num exemplo que já é C++ idiomático, não C disfarçado:
#include <iostream>
int main() {
std::cout << "Programa da soma. Digite dois inteiros separados por espaço: ";
int a, b;
std::cin >> a >> b; // o >> pode ser encadeado: lê a, depois b
// A saída também encadeia: texto, número, texto, número...
std::cout << a << " + " << b << " = " << (a + b) << '\n';
return 0;
}
Compile e rode com a linha que fixamos no artigo O Mapa da Jornada: de C a C++ em 52 Passos:
g++ -std=c++20 -Wall -Wextra soma.cpp -o soma
./soma
# Digite: 7 5
# Saída: 7 + 5 = 12
Nenhum &, nenhum %d, nenhum prefixo artesanal em nomes. É pouco código, mas cada linha marca uma diferença concreta com o C que você já conhecia.
A troca do printf pelo cout não é questão de gosto. A string de formato é um contrato que o compilador do C não conferia, e cada %d desalinhado com o argumento virava comportamento indefinido — daquele tipo que funciona na sua máquina e corrompe a pilha na do cliente. O stream move essa verificação para a compilação. E o std:: que incomoda no começo é o preço de nomes que não colidem, em vez do prefixo manual que o C obrigava. Os dois são a mesma ideia: o compilador trabalhando mais para você errar menos.
Fontes e leituras recomendadas
- Bjarne Stroustrup, A Tour of C++ (3ª ed.), Capítulo 1 ("The Basics"): apresenta streams e namespaces exatamente no espírito desta aula, direto do criador da linguagem.
- cppreference.com/w/cpp/io/cout e /w/cpp/io/cin: a documentação precisa dos dois streams que usamos hoje, com todas as sobrecargas do operador.
- cppreference.com/w/cpp/language/namespace: referência completa sobre namespaces, o operador
::e as formas dousing. - ISO C++ Core Guidelines, regra SF.7 ("Don't write
using namespaceat global scope in a header file"): a justificativa oficial para o conselho que dei sobre evitar ousing namespace std;. - Herb Sutter & Andrei Alexandrescu, C++ Coding Standards: item sobre uso de namespaces e por que o prefixo explícito melhora a legibilidade em bases de código reais.
Exercícios
Exercício 1
Escreva um programa que peça o nome do usuário e o cumprimente, imprimindo "Olá, <nome>!". Use std::string para guardar o nome (basta incluir <string>) e std::cin >> nome para ler uma palavra.
Ver resposta
✓ Resposta: A leitura com >> pega apenas uma palavra (para no primeiro espaço), o que basta aqui:
#include <iostream>
#include <string> // necessário para std::string
int main() {
std::string nome;
std::cout << "Qual é o seu nome? ";
std::cin >> nome; // lê uma palavra
std::cout << "Olá, " << nome << "!\n";
return 0;
}
Se você digitar "Maria Silva", só "Maria" será lido — porque >> para no espaço. Para ler a linha inteira existe std::getline, que veremos com calma na aula de strings.
Exercício 2
Adapte o programa da soma para também imprimir a diferença, o produto e a divisão inteira de a por b, tudo em linhas separadas, num único encadeamento por linha.
Ver resposta
✓ Resposta: Basta encadear cada operação em sua linha. Atenção à divisão por zero:
#include <iostream>
int main() {
std::cout << "Digite dois inteiros: ";
int a, b;
std::cin >> a >> b;
std::cout << "Soma: " << (a + b) << '\n';
std::cout << "Diferença: " << (a - b) << '\n';
std::cout << "Produto: " << (a * b) << '\n';
if (b != 0)
std::cout << "Divisão: " << (a / b) << '\n';
else
std::cout << "Divisão: indefinida (b = 0)\n";
return 0;
}
Note que dividir inteiro por zero é comportamento indefinido em C++, exatamente como em C — por isso a checagem. O reflexo de C continua valendo aqui.
Exercício 3
Corrija o código a seguir, que não compila, e explique o erro:
#include <iostream>
int main() {
cout << "Testando" << endl;
return 0;
}
Ver resposta
✓ Resposta: O código não compila porque cout e endl vivem no namespace std e não há prefixo nem using. Duas correções válidas:
// Opção A: prefixar (recomendada)
#include <iostream>
int main() {
std::cout << "Testando" << std::endl;
return 0;
}
// Opção B: importar os nomes usados
#include <iostream>
int main() {
using std::cout;
using std::endl;
cout << "Testando" << endl;
return 0;
}
O erro do compilador será algo como 'cout' was not declared in this scope — a pista clássica de que faltou o std::.
Exercício 4
O programa abaixo tem um comportamento perigoso quando o usuário digita uma letra em vez de um número. Compile-o, digite abc quando ele pedir o número, e descreva o que acontece. (Ainda não vamos consertar — só observar.)
#include <iostream>
int main() {
int n;
std::cout << "Digite um número: ";
std::cin >> n;
std::cout << "Você digitou: " << n << '\n';
return 0;
}
Ver resposta
✓ Resposta: Ao digitar abc, o std::cin >> n falha: como n é int e a entrada não é um número, o stream entra em estado de falha e não altera n. Como n foi declarado sem inicializar, ele contém lixo — e o programa imprime esse valor indeterminado (frequentemente 0 em muitos compiladores, mas isso não é garantido). O ponto pedagógico é duplo: primeiro, o C++ falhou com segurança (não corrompeu a memória, apenas não leu); segundo, uma variável não inicializada em C++ é tão perigosa quanto em C. Um bom hábito, que adotaremos, é sempre inicializar: int n = 0;. Como detectar e tratar o estado de falha do stream fica para depois.
Exercício 5
Reescreva o exercício 1 usando using std::cout; e using std::cin; no início do main, sem prefixar as chamadas, mas mantendo std::string com prefixo. Isso compila? Por quê?
Ver resposta
✓ Resposta: Sim, compila. O using std::cout; e using std::cin; trazem esses dois nomes específicos para o escopo do main, então você pode chamá-los sem prefixo; e std::string, que você não importou, continua exigindo o std:::
#include <iostream>
#include <string>
int main() {
using std::cout;
using std::cin;
std::string nome; // ainda com prefixo, pois não foi importado
cout << "Qual é o seu nome? ";
cin >> nome;
cout << "Olá, " << nome << "!\n";
return 0;
}
Isso ilustra o meio-termo idiomático: importar por nome o que você usa muito, mantendo o resto explícito. É mais seguro que using namespace std; e mais enxuto que prefixar tudo.