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++

  1. Por que usar C++?
  2. Apelo: atualizações na linguagem
  3. C++ é C com std::cout?
  4. 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:

  1. 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);
  2. 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;
  3. 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;
  4. 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;
  5. 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.
  6. 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);
  7. 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.

Não há por que perder tempo aprendendo C++ antigo: boa parte das _toolchains_ já lidam com C++ moderno, que além de possuir uma biblioteca padrão mais ampla e robusta, é repleto de recursos novos que inutilizam boa parte do trabalho braçal das antigas versões de C++.

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:

_"C em código C++ é C++ ruim."_ C++ possui suas próprias mecânicas para lidar com segurança, memória e algoritmos. Além disso, alguns comandos possuem significado diferente em C e em C++.

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

  1. #define's são seguros?

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:

CompiladorVersãoPadrão
g++8.xc++17
g++7.xc++17
g++6.xc++14
g++5.4c++11
g++<5.4c++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.

O pré-processador é um programa que executa antes do seu código passar para o compilador de verdade. Seus comandos são iniciados com `#` e podem servir para inclusão de código de outros arquivos, instruir o compilador sobre qual linha apontar um erro, gerar código extra para o compilador, dentre outras tarefas.

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>true, o programa executa o que está no <escopo 1> e em seguida vai para <após o if>.

Caso a <condição 1>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 e else são opcionais;
  • É possível elencar quantos else ifs 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:

  1. Comparações entre números podem ser feitas com os operadores descritos no tutorial Outros/Operadores.
  2. Se x não é menor nem igual a y, então x com certeza é maior que y.

<*> 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

  1. Prioridade de Operadores
  2. Operadores Aritméticos
  3. Operadores Relacionais
  4. Operadores Unários
  5. Operadores Lógicos
  6. 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.

OperadorSintaxe
Somax + y
Subtraçãox - y
Multiplicaçãox * y
Divisãox / 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.

OperadorSintaxe
Igualdadex == y
Diferençax != y
Menor quex < y
Maior quex > y
Menor ou igualx <= y
Maior ou igualx >= y

Operadores Unários

Operadores unários são aqueles que operam por apenas um único dado.

OperadorSintaxe
Negação (para números)-x
Incremento++x
Decremento--x

Operadores Lógicos

Operadores lógicos trazem o resultado de uma operação booleana.

OperadorSintaxetrue quando
Complemento lógico*!x ou not xx é false
"E" lógicox && yx e y são true ao mesmo tempo
"Ou" lógicox || yx 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.