C++
Ciclo de vida de uma aplicação
Primeiramente ocorre a criação do código-fonte. Depois disso ocorre a fase de
pré-processamento, rola umas operações nessa etapa que eu não vou entrar em detalhes
agora. Depois vem a fase de compilação, que vai gerar o código binário. A quarta fase é a
fase de LinkEdição, onde um Linker gera o executável do seu programa. A quinta etapa do
ciclo de vida é a do loader, ele é responsável por levar o seu programa para a memória RAM.
A sexta fase é a execução do programa na CPU.
Pré-processador - é um programa que recebe um código fonte e antes que a compilação
ocorra, o pré-processador realiza conversões, inserções e substituições no código-fonte.
#define - O comando #define é utilizado para fazer substituições no código-fonte. O
pré-processador vai percorrer o arquivo fonte .cpp substituindo cada ocorrência de ‘texto a
ser substituído’ pelo ‘texto substituto’.
#define TextoASerSubstituido TextoSubstituto
typedef - typedef é um apelido para tipos de dados, por exemplo: typedef int Integer
Código Objeto - é o resultado da compilaçã o do código, mas ainda não é o executável.
Traduz o código-fonte em código de máquina, mas ainda incompleto.
Linker - é um programa responsável por inserir as bibliotecas no código objeto e gerar um
executável funcionando, é nessa etapa que os headers são referenciados e inseridos
namespace - termos atribuídos a partes do código que ajudam na redução de potencial
conflito de nomes entre arquivos de cabeçalho que possam conter funções com nomes
iguais, por exemplo:
Tipos de dados
int - números inteiros
float/double - números de ponto flutuante, diferem no tamanho de armazenamento na RAM
> range numérico do float - 3.4x10^-38 a 3.4x10^38 - 7 dígitos
> range numérico double - 1.6x10^-308 a 1.7x10^308 - 15 dígitos
>range long double - 3.4x10^-4932 a 1.1x10^4932
char - caracteres
bool - true/false
void - indica ao compilador que não há um tipo de dado
Modificadores de tipos:
unsigned - apenas números positivos. Ex: unsigned int Inteiro
signed - números com sinais (padrão) - signed int Inteiro
long - aumenta a capacidade de armazenamento do tipo. Ex: long int Inteiro
short - diminui a capacidade de armazenamento do tipo. Ex: short int Inteiro
Protótipos de funções
Existe a possibilidade de declarar a função antes de criar seu corpo/definição. Assim, o
compilador sabe que essa função existe e não apontará um erro no código, visto que a
leitura do código é sempre de cima para baixo. Por ex:
Existem várias vantagens em declarar protótipos de funções, uma delas sendo a
possibilidade de realizar sobrecarga na assinatura das mesmas. É possível (e
recomendado) que se separe os protótipos em arquivos .h das definições em arquivos .cpp.
Por exemplo:
Somar.h
[Link]
Agora bastaria incluir um #include “Somar.h” no arquivo principal e usar a função somar.
Protótipos de classes
É possível fazer a mesma coisa para as classes, como no exemplo abaixo:
[Link]
[Link]
Static variable
É possível criar uma variável local estática, ou seja, uma variável que não desaparece
quando a chamada de uma função termina. Para isso, é necessário colocar o modificador
static numa variável para indicar ao compilador que ela deve permanecer na memória. Ex:
No bloco de código acima, a variável que conta quantas vezes a função foi chamada nunca
é perdida da memória, diferente do caso de uma variável não estática, que seria desalocada
da memória com o término da função.
Pointers
Variáveis ponteiros são variáveis que armazenam um endereço de memória. Ex:
PtrNumero recebe o endereço de memória de Numero, que é uma variável do tipo inteiro,
através do referenciamento desse endereço. O sinal de asterisco ( * ) é usado para indicar
que uma variável é um ponteiro, e o sinal E comercial ( & ) é usado para pegar o endereço de
memória de uma variável. Pegar o dado que está armazenado dentro de um endereço de
memória é uma operação chamada “desreferenciação”. É possível manipular o valor do
ponteiro utilizando * na frente do nome da variável, como mostrado abaixo.
References
Variáveis referência são variáveis que servem como um ‘apelido’ para uma variável já
existente. Por ex:
Se você verificar no console, ambas as variáveis acima possuem o mesmo valor
armazenado na memória e apontam para o mesmo endereço.
Passagem de argumentos por valor ou por referência
Levando em conta a existência dessas duas features na linguagem, existem inúmeras
possibilidades de manipulação de dados. As mais aplicadas são durante o uso das funções.
Vale lembrar que as funções possuem um escopo próprio (funções, classes e blocos de
código demarcados por chaves { } ). Isso significa que uma variável no argumento de uma
função ou dentro do seu corpo não será a variável que você está querendo manipular, e sim
a cópia do valor que ela possui.
Considere o código abaixo:
Ao chamar a função AlterarPorValor, passando nela a variável Numero, era de se esperar
que Numero passasse a ser 11. Mas o que ocorre não é isso, porque o número No
parâmetro da função será na verdade uma CÓPIA do valor que ela recebe. Para resolver isso
e manipular variáveis diretamente, podemos fazer a passagem do número por referência,
usando ponteiros ou referências. Usando referência com o sinal de &, o código então ficaria
assim:
Já usando ponteiros, a definição da função e o jeito de manipular a variável é um pouco
diferente. Ficaria assim:
Apesar de mudar um pouco o jeito que se escreve o código, o resultado será exatamente o
mesmo.
Arrays
Array é array, não tem segredo, meu cria. Estrutura de dados mais básica que tem,
unidimensional, etc. Tem uns jeitos de declarar, os mais normais:
Dá pra não declarar o tamanho e inicializar usando chaves, dá pra declarar o tamanho e
inicializar usando chaves, e dá pra só declarar o tamanho sem inicializar.
Arrays são considerados tipos primitivos, tecnicamente falando. São estruturas de dados
muito primárias, puras, não possuem métodos. Então o jeito mais “cru” de percorrer um
array em C++ (sem colocar o tamanho hard-coded) é calculando o tamanho do array. Isso é
feito dividindo o tamanho do array pelo tamanho do tipo dele. Por ex: o tipo float ocupa 4
bytes de tamanho na memória. Um array do tipo float com 5 células alocadas vai ter 4x5
bytes de tamanho, ou seja, 20 bytes. A gente sabe disso, mas o compilador não. Então é
possível pegar o tamanho do array utilizando a função nativa ‘sizeof’, e também pegar o
tamanho do tipo float. Ficaria assim:
Mas obviamente isso acaba sendo muito restritivo. Lembrar que array depois que declarado
não pode mudar de tamanho, e não possui métodos. Existe uma solução na biblioteca std
chamada std::vector<T>, que vem para suprir a necessidade de arrays dinâmicos. Mas
depois falo sobre.
Abaixo é um exercício que eu resolvi de encontrar o menor e o maior número dentro de um
array.
Criando uma função para printar um array:
Obs: em C++, um array é basicamente um ponteiro para o endereço do primeiro índice. Ou
seja, o nome de um vetor em C/C++ é a mesma coisa que &array[0].
For each em C++
Vector
Vector é uma estrutura de dados dinâmica e que possui métodos úteis para manipulação de
dados. É a versão maromba do array puro.
Depois eu falo mais sobre estruturas de dados padrões da STL.
Aritmética de Ponteiros
To com preguiça de codar isso. Mas basicamente, um ponteiro de tipo int, por exemplo, terá
como tamanho de cada célula de memória 4 bytes. Então se você tem o endereço ptr, e faz
ptr + 1, você vai pular 4 bytes na memória, e não adicionar 1.
E pra fechar essa história, é possível ter um ponteiro que aponta para OUTRO ponteiro.
Coisa de louco, mas é.
POO em C++
Vou explodir tua mente. O que é uma classe, programaticamente falando? Classe é
basicamente um tipo de dado mais complexo. Ela irá funcionar como um molde de como o
objeto será alocado na memória RAM. Por exemplo, tu sabe que um int ocupa 4 bytes. Que
o tipo boolean ocupa 1 byte. E por aí vai. A classe faz o que? Digamos que você tenha a
classe Produto com os campos quantidade_estoque, que é um inteiro, e
esta_em_promocao, que é um booleano. Produto nesse caso age como se fosse um tipo
que reserva 4 bytes para alocar quantidade_estoque, e 1 byte para alocar
esta_em_promocao, no momento que ela for instanciada, claro. Pica demais, né. Mas
enfim, não sei como funciona com objetos que precisam de dinamicidade, como por
exemplo um std::vector como tipo de um campo chamado lista_de_fornecedores.
Provavelmente é necessário criar outro objeto com novos valores (aliás uma coisa
interessante aqui, toda variável em Java que não seja de tipo primitivo, é uma variável
ponteiro).
Ok, caro leitor, que provavelmente sou eu mesmo no futuro, o que eu (que provavelmente
sou você) quero que você lembre acerca de classes:
1 - Notação ponto vs notação flecha
Existem duas maneiras de instanciar um objeto que implicam em algumas coisas bem
relevantes a serem consideradas. Abaixo estão as duas maneiras:
A diferença entre a primeira e a segunda, é que a primeira é um objeto que fica armazenado
na região da stack na memória, e é automaticamente desalocado quando o escopo que
está inserido termina. Enquanto que na segunda maneira, nós temos uma alocação
diretamente na heap, e esse tipo de instanciamento não é automaticamente desalocado
quando o bloco de execução termina. Esquecer de desalocar objetos da memória gera o
famigerado vazamento de memória.
Classe abstrata
Uma classe abstrata é, conceitualmente, uma classe que não pode ser instanciada e
geralmente é implementada como uma classe que tem uma ou mais funções virtuais puras
(abstratas). Uma função virtual pura é aquela que deve ser substituída por qualquer classe
derivada concreta (isto é, não abstrata). Isso é indicado na declaração com a sintaxe "= 0"
na declaração da função-membro. Em resumo podemos dizer que uma classe é abstrata se
ela possuir pelo menos uma única função virtual pura. Exemplo de uma classe abstrata:
Namespace
Vou partir pra um conteúdo mais avançado agora. Começar por namespace. Namespace
nada mais é do que um “contexto” com nome, que serve pra você poder criar classes e
funções que possuem nomes e assinaturas iguais sem haver conflito. Olha só:
[Link]
[Link]
É só isso mesmo.
Template
Outro nome para generics. Permite ao compilador saber que você não sabe exatamente o
tipo que um método ou classe vai receber até a hora de instanciar/criar os objetos ou
funções.
Spin off: acabei de descobrir como criar strings literais em C++. String nada mais é do que
um array de char, então…
Ok, continuando com template. Uma classe com um template aqui de exemplo.
constexpr
Tratamento de exceptions usando try/catch
Estruturas de dados
Aqui eu vou adentrar um pouco na STL, que é a biblioteca padrão do C/C++. Ela contém um
monte de funcionalidade pronta, como o std::vector que eu citei lá atrás. Mas não só isso,
várias estruturas de dados estão prontinhas para uso aqui. Vou passar em cima das mais
importantes.
Pair: estrutura de dados que funciona em pares
Vector: array dinâmico
List
Map
Métodos mais comuns
begin() retorna um iterador apontando para o início do vetor.
end() retorna um iterador apontando para uma posição após o último elemento do vetor.
rbegin() retorna um iterador reverso apontando para o último elemento do vetor.
rend() retorna um iterador reverso apontando para uma posição antes do primeiro elemento
do vetor.
clear() remove todos os elementos do vetor, deixando-o com tamanho 0.
insert() é usado para inserir elementos em uma posição específica no vetor.
swap() é usado para trocar eficientemente o conteúdo de dois vetores.
STL
A STL - Standard Template Library - tem coisa para caralho. Vou citar alguns headers e
para que servem.
Bibliotecas de utilitários
<cstdlib> → Utilitários de uso geral: controle de programa , alocação de memória dinâmica ,
números aleatórios ,
classificação e pesquisa
<csignal> → Funções e macro constantes para gerenciamento de sinal
<csetjmp> → Macro (e função) que salva (e salta) para um contexto de execução
<cstdarg> → Tratamento de listas de argumentos de comprimento variável
<typeinfo> → Utilitários de informação de tipo de tempo de execução
<typeindex>(C++ 11) → std::type_index
<type_traits>(C++ 11) → Informações de tipo de tempo de compilação
<bitset> → modelo de classe std::bitset
<functional> → Objetos de função, invocações de função, operações Bind e wrappers de
referência
<utility> → Vários componentes utilitários
<ctime> → Utilitários de data / hora estilo C
<crono>(C++ 11) → Utilitários de tempo C++
<cstddef> → Macros e typedefs padrão
<initializer_list>(C++ 11) → modelo de classe std::initializer_list
<tuple>(C++ 11) → std::modelo de classe de tupla
<any>(C++ 17) → std::qualquer classe
<optional>(C++ 17) → std::template de classe opcional
<variant>(C++ 17) → modelo de classe std::variant
<compare>(C++ 20) → Suporte ao operador de comparação de três vias
<version>(C++ 20) → Fornece informações de biblioteca dependentes de implementação
<source_location>(C++ 20) → Suprimentos significa obter a localização do código-fonte
Bibliotecas de strings
<cctype> → Funções para determinar a categoria de caracteres estreitos
<cwctype> → Funções para determinar a categoria de caracteres largos
<cstring> → Várias funções de manipulação de cadeia de caracteres estreitos
<cwchar> → Várias funções de manipulação de string ampla e multibyte
<cuchar>(C++ 11) → Funções de conversão de caracteres Unicode estilo C
<string> → modelo de classe std::basic_string
<string_view>(C++ 17) → modelo de classe std::basic_string_view
<charconv>(C++ 17) → std::to_chars e std::from_chars
<format>(C++ 20) → Biblioteca de formatação incluindo std::format
Bibliotecas de contêineres
<array>(C++ 11) → std::array container
<vector> → std::vector container
<deque> → std::deque container
<list> → std::list container
<forward_list>(C++ 11) → std::forward_list container
<set> → recipientes associativos std::set e std::multiset
<map> → recipientes associativos std::map e std::multimap
<unordered_set>(C++ 11) → std::unordered_set e std::unordered_multiset containers
associativos não ordenados
<unordered_map>(C++ 11) → std::unordered_map e std::unordered_multimap contêineres
associativos não ordenados
<stack> → std::stack container adapter
<queue> → adaptadores de contêiner std::queue e std::priority_queue
<span>(C++ 20) → std::span view
Bibliotecas numéricas
<cmath> → Funções matemáticas comuns
<complex> → Tipo de número complexo
<valarray> → Classe para representar e manipular matrizes de valores
<random>(C++ 11) → Geradores e distribuições de números aleatórios
<numeric> → Operações numéricas em valores em intervalos
<ratio>(C++ 11) → Aritmética racional em tempo de compilação
<cfenv>(C++ 11) → Funções de acesso ao ambiente de ponto flutuante
<bit>(C++ 20) → Funções de manipulação de bits
<numbers>(C++ 20) → Constantes matemáticas
Funções inline
Implementar um programa como um conjunto de funções é bom do ponto de vista da
engenharia de software, mas as chamadas de função envolvem overhead(Minimizar a
sobrecarga) de tempo de execução.
O C++ fornece funções inline para ajudar a reduzir o overhead de chamada de função —
especialmente para funções pequenas. Colocar o qualificador inline antes do tipo de retorno
de uma função na definição de função ‘aconselha’ o compilador a gerar uma cópia do
código da função no seu lugar (quando apropriado) para evitar uma chamada de função.
Em troca, múltiplas cópias do código de função são inseridas no programa (tornando
frequentemente o programa maior) em vez de haver uma única cópia da função para a qual
o controle é passado toda vez que a função é chamada.
O compilador pode ignorar o qualificador inline e em geral faz isso para todas as funções,
exceto as menores. Qualquer alteração em uma função inline poderia exigir que todos os
clientes da função fossem recompilados. Isso pode ser significativo em algumas situações
de desenvolvimento e manutenção de programas
O qualificador inline deve ser utilizado somente com funções pequenas, frequentemente
utilizadas.
Lambda Expressions
No C++ 11 e posteriores, uma expressão lambda – geralmente chamada de lambda — é
uma maneira conveniente de definir um objeto de função anônima (não possui nome)
diretamente no local onde ele é invocado ou passado como um argumento para uma
função. Lambdas resolvem um problema de legibilidade, expressividade e praticidade no
código, ou seja, fazer mais escrevendo menos.