Sobre este tutorial
Para quem?
Os tutoriais foram feitos para quem já possui alguma experiência com alguma linguagem de programação, não necessariamente muita. Podemos dizer que é para "iniciantes, mas não do zero".
Comandos no terminal
Vários comandos serão necessários de se executar no terminal (em especial,
compilar e executar os programas que você fizer). Quando você ver blocos de
código com várias linhas se iniciando em $
, o que vem depois de cada $
é
um comando para se executar no terminal, por exemplo:
$ echo "Hey"
Hey
No caso acima, echo "Hey"
é o comando para ser executado, e Hey
(logo
abaixo) é apenas o que aparece ao executá-lo.
Tópicos com *
Tópicos com <*>
no início do nome possuem algum exercício.
"Encontrei um erro no tutorial"
Sinta-se livre e abra uma issue no repositório oficial. :)
Sobre C++
- Por que usar C++?
- Apelo: atualizações na linguagem
- C++ é C com std::cout?
- C++ é baixo-nível, assim como C?
Por que usar C++?
C++ é uma Linguagem de Propósito Geral (GPL -- General Purpose Language). Ou seja, é projetada para que o programador decida onde será utilizada. Por conta de algumas premissas descritas abaixo, há razões para compiladores de C++ geralmente prezarem por gerar código otimizado. Em resumo, C++ pode ser utilizado em lugares em que façam parte das preocupações do programador:
- Performance ótima, previsível e garantida, e isso é oferecido pela linguagem graças aos seus compiladores principais: g++, clang e CL (este último para o Microsoft Visual Studio Community/Ultimate);
- Abstração de operações, tipos, manipulação de memória, etc., sendo possível que operações e manipulações complexas possuam uma interfaces simples de se utilizar ou mesmo automatizar o gerenciamento de memória dinâmica sem a necessidade de um Garbage Collector;
- Compatibilidade de arquitetura: como o g++ é análogo a um irmão do gcc (compilador de C), que possui uma longa data de existência (e portanto mais pessoas se dedicaram a portar o compilador para suas plataformas), é possível compilar código C++ para arquiteturas diversas, principalmente microcontroladores que costumam ser preocupantemente difíceis de se ter um compilador de outra linguagem para eles. É possível, por exemplo, desenvolver para processadores diversos processadores ARM, mesmo antigos como ARM7TDMI (processador do console GameBoyAdvance) ou novos como os Cortex (como o dos celulares Android modernos e vários microcontroladores), ao mesmo tempo que é possível desenvolver para processadores voltados a Desktops e Notebooks, como os Intel/AMD x86 e x86_64;
- Recursos de programação moderna (a partir de C++11), mesmo para programação de software de base (como Sistemas Operacionais, Device Drivers, etc.): funções lambda, suporte a Threads, dentre outros;
- Necessidade de reimplementação de recursos básicos: é possível implementar qualquer recurso da biblioteca padrão da linguagem sem a necessidade de uma segunda linguagem, mantendo código eficiente, reutilizável e seguro. Assim, é possível ter uma implementação de algumas abstrações de uma forma para um sistema limitado em processamento ou memória, e de outra para um sistema em que restrições de processamento e memória não sejam um problema.
- Zero-cost Abstractions (Abstração de custo zero): é possível escrever camadas de abstração em C++ (seja para conectar duas interfaces não-compatíveis, ou oferecer uma API adequada a partir de outra, ou construir uma API completamente nova e independente) sem que isso imponha custo adicional. O compilador deve ser capaz de otimizar e eliminar tais camadas sempre que possível (por exemplo, uma chamada para função que retorne um valor fixo é substituída pelo próprio valor retornado), dentre outras otimizações relacionadas a abstração (seja de tipos, operações ou arquitetura);
- Semântica de "move": a linguagem oferece a possibilidade de evitar cópias desnecessárias (ou que não devem existir) em contextos diversos.
Apelo: atualizações na linguagem
Um pequeno apelo a ser feito é sempre procurar informações atualizadas a respeito da linguagem. O motivo é que, em 2011, um novo padrão da linguagem foi aceito e incorporado a ela, chamado de C++11. Esse novo padrão define novos recursos que melhoraram a qualidade da linguagem de deixando-a desde mais segura e robusta a uma linguagem mais moderna, simplificando bastante a forma de se programar nela. Logo, a forma de se programar em C++ pré-11 pode ser dita como obsoleta. Inclusive, o advento de C++14 e C++17 (lançados em 2014 e 2017, respectivamente), já fazem de C++11 um padrão obsoleto em algumas partes.
Como exemplo, o código abaixo exemplifica a criação e iteração por elementos de um Map em C++ antigo (pré-C++11):
std::map<std::string, int> data = std::map<std::string, int>();
data.insert(std::pair<std::string, int>{"x", 4});
data.insert(std::pair<std::string, int>{"y", 4});
data.insert(std::pair<std::string, int>{"z", 12});
for (std::map<std::string, int>::iterator it = data.begin();
it != data.end();
++it) {
std::cout << it->first << ": " << it->second << "\n";
}
O mesmo código, em C++11:
auto data = std::map<std::string, int>{
{"x", 4},
{"y", 4},
{"z", 12},
};
for (auto elm: data) {
std::cout << elm.first << ": " << elm.second << "\n";
}
E por fim, em C++17:
auto data = std::map<std::string, int>{
{"x", 4},
{"y", 4},
{"z", 12},
};
for (auto [key, value]: data) {
std::cout << key << ": " << value << "\n";
}
Comparando com um código equivalente em Python:
data = {
'x': 4,
'y': 4,
'z': 12,
}
for key, value in data:
print(f'{key}: {value}')
Isso deve deixar claro por que buscar trabalhar com C++ moderno.
C++ é C com std::cout?
Não, C++ não é C com std::cout
. Como C++ surgiu baseada em C (inclusive, suas
primeiras implementações geravam código C) e sempre prometeu uma certa
retrocompatibilidade com C, é comum ver construções em ambas as linguagens que
sejam parecidas, ou mesmo código em C que compile em C++. Por consequência, se
criam crenças de que entender de C implica em entender de C++ (e vice-versa),
além de ser comum programadores escreverem código C no meio de código C++. Mas,
serve de regra:
Por exemplo:
- Em C é comum e necessário trabalhar com ponteiros explicitamente. Em C++ isso é, para praticamente todo caso, um erro (por conta de diversos problemas de segurança relacionados a ponteiros, undefined-behaviours não muito óbvios ao programador, e por C++ oferecer recursos melhores para gerenciá-los).
- O mesmo se aplica ao uso da diretiva
#define
: para se escrever código genérico em C,#define
é essencial. Porém C++ possui seu próprio mecanismo de código genérico: Templates, que reduzem a possibilidade de erros (veja a Recomendação de Leitura #1) e aproveitam melhor o sistema de tipos da linguagem.
C++ é baixo-nível, assim como C?
Não, pelos mesmos motivos pelo qual C também não é. Mas, é claro, várias construções da linguagem são capazes de dar essa ilusão ao programador, por exemplo: gerenciamento manual de memória, suporte nativo da linguagem para referência explícita de locais na memória, tipos primitivos dependentes da arquitetura, o fato de que compiladores de C e C++ costumam gerar código nativo, dentre outros.
Documentação Recomendada
É recomendado procurar a documentação no site cpp-reference, que costuma estar atualizado, conter boas explicações, indicações claras das versões de C++, exemplos de uso, e conformidade com o que se espera de código moderno da linguagem.
Outra documentação frequentemente encontrada é o C-plus-plus, porém ela costuma dar exemplos pouco idiomáticos e muitas vezes misturando código C em exemplos de C++.
Recomendações de Leitura
Hello World - C++
Para ver melhor como a linguagem se comporta, vamos direto a um código de exemplo:
#include <iostream>
int main() {
std::cout << "Hello, world!\n";
}
Como é de se imaginar, é o clássico código que mostra o texto "Hello, world!" no console. Esse código será nosso exemplo para explicação nos subtópicos que se seguem.
Salve o código em um arquivo de texto como hello.cpp
.
Compilando com g++
Para compilar um arquivo (supondo que o código do "Hello, World" esteja salvo
como "hello.cpp") com o g++
, basta executar em um terminal:
g++ hello.cpp
Isso irá gerar um arquivo com nome padrão ("a.out"). Esse arquivo pode ser executado diretamente:
$ ./a.out
Hello, world!
Caso queira definir o nome do executável final, existe a flag -o <nome>
:
$ g++ hello.cpp -o hello
$ ./hello
Hello, world!
Especificando a versão de C++
Dependendo da versão do compilador que estiver utilizando, a versão padrão de C++ utilizada será diferente:
Compilador | Versão | Padrão |
---|---|---|
g++ | 8.x | c++17 |
g++ | 7.x | c++17 |
g++ | 6.x | c++14 |
g++ | 5.4 | c++11 |
g++ | <5.4 | c++03 |
É possível explicitar a versão do padrão de C++ utilizando a flag -std
:
$ g++ hello.cpp -o hello -std=c++11
Isso fará compilar com C++11. -std=c++14
para C++14, e por aí vai. Vale
lembrar que antes de um padrão de C++ ficar pronto ele é disponibilizado com
uma flag específica. Por exemplo, antes de C++11 ficar pronto, a flag era
-std=c++0x
. Antes de C++14 ficar pronto, a flag era -std=c++1y
, e antes de
C++17 ficar pronto, a flag era -std=c++1z
. Por fim, para utilizar C++20 (que
ainda não está pronto), a
flag é -std=c++2a
.
Explicação do código de exemplo
A primeira linha é demarcada por um #include
:
#include <iostream>
Comandos iniciados com #
são diretivas do pré-processador.
No caso da #include
, ela indica que o conteúdo de um arquivo deve ser
incluído naquele ponto. Para buscar esse arquivo, foi utilizado <>
para dar
preferência às bibliotecas do sistema. Caso a preferência fosse por arquivos na
pasta atual, seria utilizado ""
, mas isso será visto melhor mais tarde.
Sendo assim, em resumo, está sendo incluído o conteúdo do arquivo "iostream", presente na biblioteca padrão de C++.
Mais à frente é criada uma função chamada main
com tipo de retorno int
:
int main() {
Essa função é responsável por ser o ponto de início do programa. Ou seja,
quando a.out
foi executado, ela foi o ponto de entrada do programa. As chaves
({}
) delimitam o que chamamos de Escopo, e o Escopo de uma função são os
comandos que ela executa quando chamada.
O que main
executa ao ser chamada é apenas chamar o operador <<
de cout
:
std::cout << "Hello, world!\n";
O <<
é o chamado "operador de left-shift", que é utilizado para deslocar os
bits de números inteiros (ou seja, um número cujos bits fossem 0110
,
deslocado 2 bits à direita ficaria 0001
). Porém, std::cout
é uma instância
de um tipo definido na biblioteca padrão, e esse tipo define seu próprio
comportamento para quando <<
é utilizado sobre ele. No caso, o comportamento
definido é redirecionar o texto para a saída do console (ou seja, mostrar na
tela). Dessa maneira, std::cout << "Texto"
irá mostrar "Texto". Por fim, o
\n
apenas indica para pular a linha após "Hello, world!".
Ausência de return no main
Em C++, a única função que não exige retorno (ao menos de maneira segura, então
se alguma outra função compilar mesmo sem colocar o retorno: cuidado) é a
main
.
Em outras palavras, a main
é a única função na qual se pode retirar a linha
iniciada com return
do código abaixo:
int main() {
return 0; // opcional
}
Quando não é colocada essa linha, o compilador já entende o retorno como 0.
O retorno de main
é utilizado por quem executou o programa para saber se ele
chegou ao fim com sucesso (retorno 0) ou ocorreu alguma falha (retorno
negativo). As falhas podem número de envolver:
- Argumentos* insuficientes (o programa exigia 4 argumentos, mas foram passados 3, 5 ou mesmo nenhum);
- Programa interrompido pelo usuário;
- O programa (ou algum outro que ele inicia) não existe;
- Algum dos argumentos enviados é inválido (ainda que a quantidade esteja correta).
Dentre outros.
*: o conceito de "argumentos" será visto no próximo tópico.
Argumentos do programa
Os argumentos de um programa são informações adicionais mandadas a ele, por exemplo: seu programa abre uma imagem, mas qual imagem deve abrir? Ou seu programa converte um arquivo em um formato de áudio para outro formato: qual arquivo será convertido e qual o nome do arquivo gerado?
Os argumentos do programa podem ser acessados mudando a definição de main
para:
int main(int argc, char* argv[]) {
std::cout << "Num. of arguments: " << argc << '\n';
std::cout << "First arg: " << argv[0] << '\n';
}
Em que argc
conterá quantos argumentos foram passados e argv
conterá
quais os argumentos passados. O n-ésimo argumento de um programa pode ser
acessado com argv[n-1]
, ou seja: o 1º argumento é guardado em argv[0]
, o 2º
em argv[1]
, e por aí vai. Vale lembrar que o primeiro argumento (argv[0]
) é
sempre o nome do programa.
Exercício
Faça o programa acima compilar (dica: está faltando importar a definição de
std::cout
) e o execute com:
$ ./<programa> Works!
Qual a saída da execução? O que acontece se você trocar Works!
para It works!
?
Comentários
Comentários são textos no seu código que serão completamente ignorados pelo compilador. Servem para documentar o que seu código faz, ou como utilizá-lo, ou mesmo dar pequenos avisos a programadores que forem ler seu código eventualmente (incluindo você mesmo).
Em C++, há duas formas de comentar:
- Comentar apenas uma linha;
- Comentar um bloco de código.
Comentar uma linha é feito apenas escrevendo //
a partir do ponto que se quer
deixar comentado. Por exemplo:
std::cout << "Este trecho é considerado pelo compilador\n"; // Este não
// E nem este, mesmo tendo: std::cout << "código C++\n";
Comentar um bloco de código é feito escrevendo /*
onde se pretende começar o
comentário e */
onde se pretende terminá-lo:
#include <iostream>
int main() {
std::cout << "Este trecho é considerado pelo compilador\n";
/*
Agora, a partir daqui não é mais:
std::cout << "Isso nem irá aparecer no programa.\n";
std : : cuot << "pode até" + . "errar a sintaxe e os nomes.
*/
/* Funciona para parte de uma linha também */
}
Estruturas básicas
Várias linguagens de programação imperativas possuem estruturas para controlar qual rumo tomará seu programa. Os tópicos a seguir irão demonstrar como funcionam cada uma das estruturas básicas de controle disponíveis em C++.
If
No caso da estrutura if
, o controle do fluxo de execução do programa é dado
por uma condição: se algo for verdadeiro, então o programa toma uma ação, mas
caso não seja, outra ação é tomada no lugar.
Sintaxe
A sintaxe de um if
é:
if (/* <condição 1> */) {
/* <escopo 1> */
} else if (/* <condição 2> */) {
/* <escopo 2> */
} else {
/* <escopo 3> */
}
/* <após o if> */
Quando seu programa executar, caso a <condição 1>
dê true
, o programa
executa o que está no <escopo 1>
e em seguida vai para <após o if>
.
Caso a <condição 1>
dê false
, é verificada a <condição 2>
. Se ela der
true
, o programa executa o que está no <escopo 2>
e em seguida vai para
<após o if>
.
Por fim, caso nenhuma das condições seja true
, o <escopo 3>
(o do else
) é
executado (e, assim como os outros, depois vai para <após o if>
.
Observações:
- O
else if
eelse
são opcionais; - É possível elencar quantos
else if
s você quiser. Eles serão checados um por um, em ordem, até que algum deles dêtrue
.
Exercício
O programa abaixo deveria, dado o primeiro argumento (desconsiderando o nome do programa), informar se o número apostado é maior, menor ou igual a um número secreto. Porém, o programa está incompleto.
Seu exercício é completar o programa no trecho que se pede.
#include <iostream>
// disponibiliza o `std::stoi`
#include <string>
int main(int argc, char* argv[]) {
// verifica se foi passado o argumento com o número
if (argc < 2) {
std::cout << "Faltou passar o número apostado!\n";
// Caso não tenha passado, o main se encerra com um código de erro
return -1;
}
const auto SECRET_NUMBER = 12;
// converte o 1º argumento de string para int, armazenando o resultado em
// `guess`
auto guess = std::stoi(argv[1]);
// checa se o número apostado está correto, ou se é menor ou igual ao
// secreto
/* apague esta linha e escreva seu código */
}
Dicas:
- Comparações entre números podem ser feitas com os operadores descritos no tutorial Outros/Operadores.
- Se
x
não é menor nem igual ay
, entãox
com certeza é maior quey
.
<*> While
<*> For
<*> Switch-Case
Funções
Retorno de funções
Parâmetros de funções
Parâmetros const
Argumento por referência
Funções genéricas
Classes
Atributos
Métodos
Modificadores de acesso
Structs
Organizando projetos em C++
Separando em arquivos
Linkando bibliotecas
CMake
Outros
Operadores para tipos primitivos
Índice
- Prioridade de Operadores
- Operadores Aritméticos
- Operadores Relacionais
- Operadores Unários
- Operadores Lógicos
- Outros Operadores
Prioridade de Operadores
A prioridade dos operadores define em que ordem eles são executados primeiro. Uma tabela completa se encontra no cppreference.
Operadores Aritméticos
Operadores aritméticos servem para fazer manipulações algébricas. Efetivamente, somar, dividir, etc.
Operador | Sintaxe |
---|---|
Soma | x + y |
Subtração | x - y |
Multiplicação | x * y |
Divisão | x / y |
Resto* | x % y |
* É comum encontrar documentações descrevendo o operador %
como "Módulo". O
problema é que %
faz resto da divisão, e não módulo. A diferença entre
eles é visível nos números negativos:
x: | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 |
x resto 3: | -2 | -1 | 0 | -2 | -1 | 0 | 1 | 2 | 0 | 1 | 2 |
x módulo 3: | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |
Perceba que, diferente do resto da divisão, o "módulo" sempre repete os valores [0..N), em que, para a tabela de cima, N seria 3.
Operadores Relacionais
Operadores relacionais servem para verificar se uma relação entre dois elementos é verdadeira.
Operador | Sintaxe |
---|---|
Igualdade | x == y |
Diferença | x != y |
Menor que | x < y |
Maior que | x > y |
Menor ou igual | x <= y |
Maior ou igual | x >= y |
Operadores Unários
Operadores unários são aqueles que operam por apenas um único dado.
Operador | Sintaxe |
---|---|
Negação (para números) | -x |
Incremento | ++x |
Decremento | --x |
Operadores Lógicos
Operadores lógicos trazem o resultado de uma operação booleana.
Operador | Sintaxe | true quando |
---|---|---|
Complemento lógico* | !x ou not x | x é false |
"E" lógico | x && y | x e y são true ao mesmo tempo |
"Ou" lógico | x || y | x ou y são true |
*: O complemento lógico serve para retornar o inverso de um booleano. Ou seja,
se thing
era verdadeiro, !thing
é falso. Perceba que esse operador é também
um operador unário.
Outros Operadores
Neste tutorial estão listados apenas os operadores mais usuais. Uma lista completa de operadores pode ser vista no cppreference.