#include <fmt/core.h>
@@ -592,6 +592,24 @@ Impressão de Saída Padrão
Impressão de Saída Padrão
+Tomando vantagem do padrão C++20 com o header
+<format>
é possível implementar uma versão
+simplificada do print
(sem depender de bibliotecas externas
+como fmt::print
). Uma possível solução utilizando macros de
+C é (tome cuidado com possíveis efeitos indesejados de macros):
+#include <format>
+
+// Solution using __VA_ARGS__ and VA_OPT (## from c++20)
+#define print(fmt, ...) \
+ printf("%s", std::format(fmt, ##__VA_ARGS__).c_str())
+
+int main() {
+ print("olá mundo!\n");
+ return 0;
+}
+
+
+Impressão de Saída Padrão
Para imprimir na saída padrão utilizaremos o comando
fmt::print
. Este comando é dividido em duas partes, sendo
que na primeira colocamos a mensagem formatada e, a seguir, colocamos as
@@ -602,37 +620,37 @@
Impressão de Saída Padrão
Resposta: através do padrão de substituição
{}
.
-
int32_t x1 = 7;
- print("x1 é {}", x1); // x1 é 7
- float x6 = x1 / 2.0;
- print("metade de {} é {}", x1, x6); // metade de 7 é 3.5
- char b = 'L';
- print("isto é uma {}etra", b); // isto é uma Letra
- print("Olá mundo! \n"); // Olá mundo! (quebra de linha)
+
int32_t x1 = 7;
+ print("x1 é {}", x1); // x1 é 7
+ float x6 = x1 / 2.0;
+ print("metade de {} é {}", x1, x6); // metade de 7 é 3.5
+ char b = 'L';
+ print("isto é uma {}etra", b); // isto é uma Letra
+ print("Olá mundo! \n"); // Olá mundo! (quebra de linha)
Condicionais e Laços de Repetição
Condicionais podem ser feitos através dos comandos if ou if else.
-int x = 12;
-if (x > 10)
- print("x maior de 10\n");
-else
- print("x menor ou igual a 10\n");
+int x = 12;
+if (x > 10)
+ print("x maior de 10\n");
+else
+ print("x menor ou igual a 10\n");
Laços de repetição podem ser feitos através de comandos while ou for.
Um comando for é dividido em três partes: inicialização, condição de
continuação e incremento.
-
for (auto i=0; i < 10 ; i++) {
- print("i : {}\n" , i);
-}
+
for (auto i=0; i < 10 ; i++) {
+ print("i : {}\n" , i);
+}
-
auto j=0;
-while (j < 10) {
- print("j : {}\n", j);
- j++;
-}
+
auto j=0;
+while (j < 10) {
+ print("j : {}\n", j);
+ j++;
+}
@@ -647,31 +665,31 @@ Controle de fluxo com break
e
z
):
-
int z = 0;
-int i = 0;
-for (; i < 10; i++) {
- if (i > 5) continue;
- print("z={} i={}\n",
- z, i);
- z++;
-}
-//
-// z==6 i==10
-print("final z={} i={}\n",
- z, i);
-
int z = 0;
int i = 0;
-while (i < 10) {
- print("z={} i={}\n",
- z, i);
- z++;
- if (i > 5) break;
- i++;
-}
-// z==7 i==6
+for (; i < 10; i++) {
+ if (i > 5) continue;
+ print("z={} i={}\n",
+ z, i);
+ z++;
+}
+//
+// z==6 i==10
print("final z={} i={}\n",
z, i);
+
+
int z = 0;
+int i = 0;
+while (i < 10) {
+ print("z={} i={}\n",
+ z, i);
+ z++;
+ if (i > 5) break;
+ i++;
+}
+// z==7 i==6
+print("final z={} i={}\n",
+ z, i);
@@ -685,17 +703,17 @@ Saltos incondicionais com goto
else
, break
, etc.
Contabilize quantos prints são executados (variável
z
):
- int z = 0;
- for (auto i = 0; i < 10; i++) {
- if (i < 5) continue; int j = i;
- while (j < 10) {
- if (i > 6) goto fim;
- print("z={} i={} j={}\n", z, i, j); z++; j++;
- }
- }
-fim:
- // z==9: i=5 j=5..9 [5 passos]; i=6 j=6..9 [4 passos]
- print("final z={}\n", z);
+ int z = 0;
+ for (auto i = 0; i < 10; i++) {
+ if (i < 5) continue; int j = i;
+ while (j < 10) {
+ if (i > 6) goto fim;
+ print("z={} i={} j={}\n", z, i, j); z++; j++;
+ }
+ }
+fim:
+ // z==9: i=5 j=5..9 [5 passos]; i=6 j=6..9 [4 passos]
+ print("final z={}\n", z);
Tipos Compostos
@@ -706,9 +724,9 @@ Tipos Compostos
Os tipos compostos podem ser vetores (arrays) ou agregados (structs,
…).
-
int32_t v[8]; // cria um vetor com 8 inteiros
-v[0] = 3; // atribui o valor 3 à primeira posição
-v[7] = 5; // atribui o valor 5 à última posição
+
int32_t v[8]; // cria um vetor com 8 inteiros
+v[0] = 3; // atribui o valor 3 à primeira posição
+v[7] = 5; // atribui o valor 5 à última posição
v: | 3 | | | | | | | 5 |
0 1 2 3 4 5 6 7
@@ -721,29 +739,29 @@ Tipos Agregados I
forma completamente diferente na linguagem C++):
-
// Em C (tipo agregado P)
-struct P
-{
- int32_t x;
- char y;
-};
-
-// declara variável tipo P
-struct P p1;
-// designated initializers
-struct P p2 = {.x=10, .y='Y'};
-
-
// Em C++ (tipo agregado P)
-class P
+// Em C (tipo agregado P)
+struct P
{
-public:
- int32_t x;
- char y;
-};
+ int32_t x;
+ char y;
+};
+
// declara variável tipo P
-P p1;
+struct P p1;
// designated initializers
-auto p2 = P{.x=10, .y='Y'};
+struct P p2 = {.x=10, .y='Y'};
+
+
// Em C++ (tipo agregado P)
+class P
+{
+public:
+ int32_t x;
+ char y;
+};
+// declara variável tipo P
+P p1;
+// designated initializers
+auto p2 = P{.x=10, .y='Y'};
@@ -754,11 +772,11 @@ Tipos Agregados II
Assim como na inicialização designada, podemos utilizar o operador
ponto (.) para acessar campos do agregado.
Exemplo:
-auto p1 = P{.y = 'A'};
-
-p1.x = 20; // atribui 20 à variável x de p1
-p1.x = p1.x + 1; // incrementa a variável x de p1
-print("{} {}\n", p1.x, p1.y); // imprime '21 A'
+auto p1 = P{.y = 'A'};
+
+p1.x = 20; // atribui 20 à variável x de p1
+p1.x = p1.x + 1; // incrementa a variável x de p1
+print("{} {}\n", p1.x, p1.y); // imprime '21 A'
p1: | 21 | 'A' |
p1.x p1.y
@@ -769,9 +787,9 @@ Espaço de Memória
float) ocupa 4 bytes, enquanto um char ocupa apenas 1 byte.
No caso de vetores, o espaço ocupado na memória é multiplicado pelo
número de elementos. Vamos calcular o espaço das variáveis:
-int32_t v[256]; // = 1024 bytes = 1 kibibyte = 1 KiB
-char x[1000]; // = 1000 bytes = 1 kilobyte = 1 kB
-float y[5]; // = 20 bytes
+int32_t v[256]; // = 1024 bytes = 1 kibibyte = 1 KiB
+char x[1000]; // = 1000 bytes = 1 kilobyte = 1 kB
+float y[5]; // = 20 bytes
Já nos agregados, assumimos o espaço ocupado como a soma de suas
variáveis internas (embora na prática o tamanho possa ser ligeiramente
superior, devido a alinhamentos de memória).
@@ -782,16 +800,16 @@ Tipos Genéricos
permitem que algum outro tipo seja passado como parâmetro.
Consideremos o agregado P que carrega um int e um char… como
transformá-lo em um agregado genérico em relação à variável x?
-template<typename T>
-class G
-{
-public:
- T x; // qual o tipo da variável x?
- char y;
-};
-// declara o agregado genérico G
-G<float> g1 = {.x = 3.14, .y = 'Y'};
-G<int> g2 = {.x = 3, .y = 'Y'};
+template<typename T>
+class G
+{
+public:
+ T x; // qual o tipo da variável x?
+ char y;
+};
+// declara o agregado genérico G
+G<float> g1 = {.x = 3.14, .y = 'Y'};
+G<int> g2 = {.x = 3, .y = 'Y'};
Valores constantes e casts
@@ -799,19 +817,19 @@ Valores constantes e casts
const
. Uma mudança de tipos pode ser feita com type
cast. Em C++, utilize static_cast<tipo>
ao invés
do padrão C de cast.
-unsigned int x = 10;
-int y1 = (int) x; // em C
-int y2 = int(x); // em C
-int y3 = static_cast<int>(x); // em C++
-const unsigned int z1 = x; // OK
-// unsigned int z2 = z1; // ERRO
+unsigned int x = 10;
+int y1 = (int) x; // em C
+int y2 = int(x); // em C
+int y3 = static_cast<int>(x); // em C++
+const unsigned int z1 = x; // OK
+// unsigned int z2 = z1; // ERRO
O const
pode ser removido em algumas circustâncias
através de um const_cast
. Em C++, existe também o
constexpr
, que diferentemente do const
, nunca
pode ser removido, pois é de tempo de compilação. Em C, algo similar é
possível com macros, mas permite reescrita, sendo inseguro.
-#define k1 10 // C (inseguro e permite redefinição)
-constexpr int k2 = 10; // C++ (seguro, impossível redefinir)
+#define k1 10 // C (inseguro e permite redefinição)
+constexpr int k2 = 10; // C++ (seguro, impossível redefinir)
Resumo até agora
@@ -834,29 +852,29 @@ Rotinas I
procedimentos, que podem por sua vez receber parâmetros.
Tomemos por exemplo a função quadrado que retorna o valor passado
elevado ao quadrado.
-// função que retorna um 'int', com parâmetro 'p'
-int quadrado (int p) {
- return p*p;
-}
-// variável do tipo 'int', com valor 25
-int x = quadrado(5);
+// função que retorna um 'int', com parâmetro 'p'
+int quadrado (int p) {
+ return p*p;
+}
+// variável do tipo 'int', com valor 25
+int x = quadrado(5);
Rotinas II
Quando nenhum valor é retornado (em um procedimento), utilizamos a
palavra-chave void
. Procedimentos são úteis mesmo quando
nenhum valor é retornado. Exemplo: (de a até b):
-void imprime (int a , int b) {
- for (auto i=a ; i<b ; i++)
- print("{}\n", i) ;
-}
+void imprime (int a , int b) {
+ for (auto i=a ; i<b ; i++)
+ print("{}\n", i) ;
+}
Também é possível retornar múltiplos elementos (par ou tupla),
através de um structured binding (requer
#include<tuple>
):
-auto duplo(int p) {
- return std::make_tuple(p+3, p+6);
-}
-auto [x1,x2] = duplo(10); // x1=13 x2=16
+auto duplo(int p) {
+ return std::make_tuple(p+3, p+6);
+}
+auto [x1,x2] = duplo(10); // x1=13 x2=16
Ponteiros I
@@ -880,17 +898,17 @@ Ponteiros II
Em ponteiros para agregados, o operador de acesso (.) é substituído
por uma seta (->). O operador &
toma o endereço da
variável:
-struct P {
- int32_t x;
- char y;
-};
-
-void imprimir(struct P* p1, struct P p2) {
- print("{} {}\n", p1->x, p2.x);
-}
-// ...
-struct P p0 = {.x = 20, .y = 'Y'}; // cria variável 'p0'
-imprimir(&p0, p0); // resulta em '20 20'
+struct P {
+ int32_t x;
+ char y;
+};
+
+void imprimir(struct P* p1, struct P p2) {
+ print("{} {}\n", p1->x, p2.x);
+}
+// ...
+struct P p0 = {.x = 20, .y = 'Y'}; // cria variável 'p0'
+imprimir(&p0, p0); // resulta em '20 20'
Alocação Dinâmica de Memória
@@ -899,26 +917,26 @@ Alocação Dinâmica de Memória
memória:
-
// Aloca (C) o agregado P
-struct P* vp =
- malloc(1*sizeof(struct P));
-// inicializa campos de P
-vp->x = 10;
-vp->y = 'Y';
-// imprime x (valor 10)
-print("{}\n", vp->x);
-// descarta a memória
-free(vp);
+
// Aloca (C) o agregado P
+struct P* vp =
+ malloc(1*sizeof(struct P));
+// inicializa campos de P
+vp->x = 10;
+vp->y = 'Y';
+// imprime x (valor 10)
+print("{}\n", vp->x);
+// descarta a memória
+free(vp);
-
// Aloca (C++) o agregado P
-auto* vp = new P{
- .x = 10,
- .y = 'Y'
- };
-// imprime x (valor 10)
-print("{}\n", vp->x);
-// descarta a memória
-delete vp;
+
// Aloca (C++) o agregado P
+auto* vp = new P{
+ .x = 10,
+ .y = 'Y'
+ };
+// imprime x (valor 10)
+print("{}\n", vp->x);
+// descarta a memória
+delete vp;
@@ -928,26 +946,26 @@ Rotinas III
localização desta função na memória do computador. Por exemplo:
-
// tipo: int(*)(int)
-int quadrado(int p) {
- return p*p;
-}
-
-
// tipo: float(*)(int)
-auto fquad(int p) -> float{
+// tipo: int(*)(int)
+int quadrado(int p) {
return p*p;
}
+
+
// tipo: float(*)(int)
+auto fquad(int p) -> float{
+ return p*p;
+}
Este fato pode ser útil para receber funções como parâmetro, bem como
armazenar funções anônimas (lambdas):
-
// armazena lambda no ponteiro de função 'quad'
-int(*quad)(int) = [](int p) -> int {
- return p*p;
- };
-print("{}\n", quad(3)); // 9
-// ou, utilizando 'auto' para deduzir o tipo
-auto func = [](int p) { return p*p; };
+
// armazena lambda no ponteiro de função 'quad'
+int(*quad)(int) = [](int p) -> int {
+ return p*p;
+ };
+print("{}\n", quad(3)); // 9
+// ou, utilizando 'auto' para deduzir o tipo
+auto func = [](int p) { return p*p; };
Rotinas IV
@@ -957,27 +975,27 @@ Rotinas IV
agregado, chamado this:
-
// Em C (tipo agregado Z)
-struct Z {
- int x;
-};
-
-// imprime campo x
-void imprimex(struct Z* this)
-{
- print("{}\n", this->x);
-}
+
// Em C (tipo agregado Z)
+struct Z {
+ int x;
+};
+
+// imprime campo x
+void imprimex(struct Z* this)
+{
+ print("{}\n", this->x);
+}
-
// Em C++ (tipo agregado Z)
-class Z
-{
-public:
- int x;
- // imprime campo x
- void imprimex() {
- print("{}\n", this->x);
- }
-};
+
// Em C++ (tipo agregado Z)
+class Z
+{
+public:
+ int x;
+ // imprime campo x
+ void imprimex() {
+ print("{}\n", this->x);
+ }
+};
@@ -994,24 +1012,24 @@
Ponteiros III
std::make_unique
ou
std::make_shared
.
-
// Aloca (C++) o agregado P
-auto* vp = new P{
- .x = 10,
- .y = 'Y'
- };
-// imprime x (valor 10)
-print("{}\n", vp->x);
-// descarta a memória
-delete vp;
+
// Aloca (C++) o agregado P
+auto* vp = new P{
+ .x = 10,
+ .y = 'Y'
+ };
+// imprime x (valor 10)
+print("{}\n", vp->x);
+// descarta a memória
+delete vp;
-
// Aloca (C++) o agregado P
-auto vp =
- std::make_unique<P>(
- P{.x = 10, .y = 'Y'});
-// imprime x (valor 10)
-print("{}\n", vp->x);
-// descarta a memória
-// delete vp;
+
// Aloca (C++) o agregado P
+auto vp =
+ std::make_unique<P>(
+ P{.x = 10, .y = 'Y'});
+// imprime x (valor 10)
+print("{}\n", vp->x);
+// descarta a memória
+// delete vp;
@@ -1028,25 +1046,25 @@
Ponteiros IV
NULL
e
std::nullptr
.
-
// Aloca (C++) o agregado P
-auto* vp = new P{
- .x = 10,
- .y = 'Y'
- };
-if(vp) print("sucesso!\n");
-if(!vp) print("falha!\n");
-if(vp==NULL) print("falha!\n");
-if(vp==0) print("falha!\n");
+
// Aloca (C++) o agregado P
+auto* vp = new P{
+ .x = 10,
+ .y = 'Y'
+ };
+if(vp) print("sucesso!\n");
+if(!vp) print("falha!\n");
+if(vp==NULL) print("falha!\n");
+if(vp==0) print("falha!\n");
-
// Aloca (C++) o agregado P
-auto vp =
- std::make_unique<P>(
- P{.x = 10, .y = 'Y'});
-if(vp) print("sucesso!\n");
-if(!vp) print("falha!\n");
-
-// reseta manualmente
-vp = std::nullptr;
+
// Aloca (C++) o agregado P
+auto vp =
+ std::make_unique<P>(
+ P{.x = 10, .y = 'Y'});
+if(vp) print("sucesso!\n");
+if(!vp) print("falha!\n");
+
+// reseta manualmente
+vp = std::nullptr;
@@ -1058,18 +1076,18 @@
Conceitos I
Por exemplo, podemos criar um conceito
TemImprimeX
, que exige que o agregado possua um método
imprimex()
:
-
template <typename Agregado>
-concept TemImprimeX = requires(Agregado a) {
- { a.imprimex() };
-};
+
template <typename Agregado>
+concept TemImprimeX = requires(Agregado a) {
+ { a.imprimex() };
+};
Conceitos II
Assim, podemos utilizar um conceito mais específico ao invés de um
tipo automático:
-auto a1 = Z{.x = 1}; // tipo automático
-TemImprimeX auto a2 = Z{.x = 2}; // tipo conceitual
-Z a3 = Z{.x = 3}; // tipo explícito
+auto a1 = Z{.x = 1}; // tipo automático
+TemImprimeX auto a2 = Z{.x = 2}; // tipo conceitual
+Z a3 = Z{.x = 3}; // tipo explícito
Importante: a noção de conceitos é
fundamental para a compreensão de tipos abstratos, central no
curso de estruturas de dados.
@@ -1088,41 +1106,41 @@ Passagem de Parâmetros por Referência I
não permitem cópias, sendo obrigatoriamente passados por referência.
Para transformar uma variável viva para uma variável em
movimento, basta usar o comando std::move
.
-auto p1 = std::make_unique<P>(P{.x = 10, .y = 'Y'});
-print("{}\n", p1->x); // imprime x (valor 10)
-auto p2 = std::move(p1);
-if(!p1) print("p1 não existe mais!\n");
-print("{}\n", p2->x); // imprime x (valor 10)
+auto p1 = std::make_unique<P>(P{.x = 10, .y = 'Y'});
+print("{}\n", p1->x); // imprime x (valor 10)
+auto p2 = std::move(p1);
+if(!p1) print("p1 não existe mais!\n");
+print("{}\n", p2->x); // imprime x (valor 10)
Passagem de Parâmetros por Referência II
-
// C++
-void imprimex(P* vp) {
- // imprime x (valor 10)
- print("{}\n", vp->x);
-}
-// ...
-auto p = P{
- .x = 10,
- .y = 'Y'
- };
-// cópia de ponteiro
-imprimex(&p);
-
-
// C++
-void imprimex(P& vp) {
+// C++
+void imprimex(P* vp) {
// imprime x (valor 10)
- print("{}\n", vp.x);
+ print("{}\n", vp->x);
}
// ...
auto p = P{
.x = 10,
.y = 'Y'
};
-// referência (lvalue)
-imprimex(p);
+// cópia de ponteiro
+imprimex(&p);
+
+
// C++
+void imprimex(P& vp) {
+ // imprime x (valor 10)
+ print("{}\n", vp.x);
+}
+// ...
+auto p = P{
+ .x = 10,
+ .y = 'Y'
+ };
+// referência (lvalue)
+imprimex(p);
@@ -1132,31 +1150,31 @@
Passagem de Parâmetros por Referência III
referências de lado direito (
rvalue). Observe:
-
void teste1(int x) {
- x = 10;
-}
-void teste2(int* x) {
- *x = 10;
-}
-void teste3(int& x) {
- x = 10;
-}
-void teste4(int&& x) {
- x = 10;
-}
+
void teste1(int x) {
+ x = 10;
+}
+void teste2(int* x) {
+ *x = 10;
+}
+void teste3(int& x) {
+ x = 10;
+}
+void teste4(int&& x) {
+ x = 10;
+}
-
int a = 20;
-teste1(a); // a == 20
-teste2(&a); // a <- 10
-teste3(a); // a <- 10
-// teste4(a); // ERRO
-teste4(std::move(a)); // OK
-// supostamente a <- 10
-
-teste1(20); // OK
-// teste2(20); // ERRO
-// teste3(20); // ERRO
-teste4(20); // OK
+
int a = 20;
+teste1(a); // a == 20
+teste2(&a); // a <- 10
+teste3(a); // a <- 10
+// teste4(a); // ERRO
+teste4(std::move(a)); // OK
+// supostamente a <- 10
+
+teste1(20); // OK
+// teste2(20); // ERRO
+// teste3(20); // ERRO
+teste4(20); // OK
Observação: existe também a sintaxe
@@ -1194,16 +1212,16 @@
Tipo std::string
char*
,
char[]
ou
const char*
em
C. Para utilizar, basta fazer
#include <string>
.
Exemplo:
-
std::string s1 = "abcd";
-std::string s2 = "ef";
-print("tamanho1={} tamanho2={}\n", s1.length(), s2.length());
-// tamanho1=4 tamanho2=2
-s1 = s1 + s2;
-print("s1={} s2={}\n", s1, s2);
-// s1=abcdef s2=ef
-const char* cs = s1.c_str();
-print("s1={} cs={}\n", s1, cs);
-// s1=abcdef cs=abcdef
+
std::string s1 = "abcd";
+std::string s2 = "ef";
+print("tamanho1={} tamanho2={}\n", s1.length(), s2.length());
+// tamanho1=4 tamanho2=2
+s1 = s1 + s2;
+print("s1={} s2={}\n", s1, s2);
+// s1=abcdef s2=ef
+const char* cs = s1.c_str();
+print("s1={} cs={}\n", s1, cs);
+// s1=abcdef cs=abcdef
Tipo std::array
@@ -1211,19 +1229,19 @@ Tipo std::array
std::array<tipo, tamanho>
permite representar vetores
de tamanho fixo. Para utilizar, basta fazer
#include <array>
. Exemplo:
-int v1[10];
-int v2[] = {1, 2, 3, 4};
-std::array<int, 10> a1{};
-std::array<int, 4> a2 = {1, 2, 3, 4};
-print("v[0]={} v[3]={} tam={}\n", v2[0], v2[3],
- sizeof(v2) / sizeof(v2[0]));
-// v[0]=1 v[3]=4 tam=4
-print("a[0]={} a[3]={} tam={}\n", a2[0], a2[3], a2.size());
-// a[0]=1 a[3]=4 tam=4
-print("{} {} {}\n", std::is_aggregate<int*>::value,
- std::is_aggregate<int[]>::value,
- std::is_aggregate<std::array<int, 4>>::value);
-// false true true
+int v1[10];
+int v2[] = {1, 2, 3, 4};
+std::array<int, 10> a1{};
+std::array<int, 4> a2 = {1, 2, 3, 4};
+print("v[0]={} v[3]={} tam={}\n", v2[0], v2[3],
+ sizeof(v2) / sizeof(v2[0]));
+// v[0]=1 v[3]=4 tam=4
+print("a[0]={} a[3]={} tam={}\n", a2[0], a2[3], a2.size());
+// a[0]=1 a[3]=4 tam=4
+print("{} {} {}\n", std::is_aggregate<int*>::value,
+ std::is_aggregate<int[]>::value,
+ std::is_aggregate<std::array<int, 4>>::value);
+// false true true
Tipo std::vector
@@ -1231,19 +1249,19 @@ Tipo std::vector
representar vetores com tamanho variável (através do método
push_back
). Para utilizar, basta fazer
#include <vector>
. Exemplo:
-int v1[10];
-int v2[] = {1, 2, 3, 4};
-std::vector<int> k1{};
-std::vector<int> k2 = {1, 2, 3, 4};
-k2.push_back(999);
-//
-print("v[0]={} v[3]={} tam={}\n", v2[0], v2[3],
- sizeof(v2) / sizeof(v2[0]));
-// v[0]=1 v[3]=4 tam=4
-print("k[0]={} k[4]={} tam={}\n", k2[0], k2[4], k2.size());
-// k[0]=1 k[4]=999 tam=5
-print("{}\n", std::is_aggregate<std::vector<int>>::value);
-// false
+int v1[10];
+int v2[] = {1, 2, 3, 4};
+std::vector<int> k1{};
+std::vector<int> k2 = {1, 2, 3, 4};
+k2.push_back(999);
+//
+print("v[0]={} v[3]={} tam={}\n", v2[0], v2[3],
+ sizeof(v2) / sizeof(v2[0]));
+// v[0]=1 v[3]=4 tam=4
+print("k[0]={} k[4]={} tam={}\n", k2[0], k2[4], k2.size());
+// k[0]=1 k[4]=999 tam=5
+print("{}\n", std::is_aggregate<std::vector<int>>::value);
+// false
Tipo std::optional
@@ -1251,19 +1269,19 @@ Tipo std::optional
opcional, com alocação em stack, não em heap como
smart pointers. Para utilizar, basta fazer
#include <optional>
. Exemplo:
-std::optional<int> busca(char c, const std::vector<char>& v) {
- // busca char 'c' num vetor v e retorna posição
- for(int i=0; i<static_cast<int>(v.size()); i++)
- if(v[i] == c)
- return i; // encontrou
- // não encontrou
- return std::nullopt;
-}
-// ...
-std::vector<char> v = {'a', 'b', 'c'};
-auto op = busca('x', v);
-if(op) print("posicao={}", *op);
-else print("não encontrou");
+std::optional<int> busca(char c, const std::vector<char>& v) {
+ // busca char 'c' num vetor v e retorna posição
+ for(int i=0; i<static_cast<int>(v.size()); i++)
+ if(v[i] == c)
+ return i; // encontrou
+ // não encontrou
+ return std::nullopt;
+}
+// ...
+std::vector<char> v = {'a', 'b', 'c'};
+auto op = busca('x', v);
+if(op) print("posicao={}", *op);
+else print("não encontrou");
Tipo std::unique_ptr
@@ -1272,18 +1290,18 @@ Tipo std::unique_ptr
função útil é o get
, que retorna um ponteiro nativo C para
o dado. A função reset
apaga o ponteiro manualmente. Para
utilizar, basta fazer #include <memory>
. Exemplo:
-auto* p1 = new int{10};
-auto* p2 = p1;
-print("*p1={} *p2={}\n", *p1, *p2);
-// *p1=10 *p2=10
-delete p1;
-
-auto u1 = std::make_unique<int>(10);
-auto u2 = std::move(u1);
-auto* p3 = u2.get();
-print("*u2={} *p3={}\n", *u2, *p3);
-// *u2=10 *p3=10
-u2.reset(); // apaga ponteiro u2 manualmente
+auto* p1 = new int{10};
+auto* p2 = p1;
+print("*p1={} *p2={}\n", *p1, *p2);
+// *p1=10 *p2=10
+delete p1;
+
+auto u1 = std::make_unique<int>(10);
+auto u2 = std::move(u1);
+auto* p3 = u2.get();
+print("*u2={} *p3={}\n", *u2, *p3);
+// *u2=10 *p3=10
+u2.reset(); // apaga ponteiro u2 manualmente
Tipo std::shared_ptr
@@ -1297,14 +1315,14 @@ Tipo std::shared_ptr
std::weak_ptr
ou cycles::relation_ptr
(a
seguir). Para utilizar, basta fazer
#include <memory>
. Exemplo:
-auto s1 = std::make_shared<int>(10);
-auto s2 = s1;
-std::weak_ptr<int> w1 = s1;
-auto s3 = w1.lock();
-print("*s1={} *s2={} *s3={}\n", *s1, *s2, *s3);
-// *s1=10 *s2=10 *s3=10
-s1.reset(); // apaga ponteiro s1 manualmente
-print("*s2={} *s3={} ainda existem!\n", *s2, *s3);
+auto s1 = std::make_shared<int>(10);
+auto s2 = s1;
+std::weak_ptr<int> w1 = s1;
+auto s3 = w1.lock();
+print("*s1={} *s2={} *s3={}\n", *s1, *s2, *s3);
+// *s1=10 *s2=10 *s3=10
+s1.reset(); // apaga ponteiro s1 manualmente
+print("*s2={} *s3={} ainda existem!\n", *s2, *s3);
Tipo std::function
@@ -1315,17 +1333,17 @@ Tipo std::function
enquanto as demais só podem ser encapsuladas como
std::function
. Basta fazer
#include <functional>
. Exemplo:
-// captureless lambda
-int(*fquad1)(int) = [](int p) -> int { return p*p; };
-std::function<int(int)> fquad2 = [](int p) { return p*p; };
-// capturando variável x (por cópia)
-int x = 10;
-int y = 20;
-// closure x1 (retorna x + 1)
-std::function<int()> x1 = [x]() { return x+1; };
-// capturando todas variáveis locais com =, y por referência
-std::function<int()> fxy = [=, &y]() { y++; return x+y; };
-int z = fxy(); // z==31 y==21
+// captureless lambda
+int(*fquad1)(int) = [](int p) -> int { return p*p; };
+std::function<int(int)> fquad2 = [](int p) { return p*p; };
+// capturando variável x (por cópia)
+int x = 10;
+int y = 20;
+// closure x1 (retorna x + 1)
+std::function<int()> x1 = [x]() { return x+1; };
+// capturando todas variáveis locais com =, y por referência
+std::function<int()> fxy = [=, &y]() { y++; return x+y; };
+int z = fxy(); // z==31 y==21
@@ -1342,16 +1360,16 @@ Proposta para um std::scan
busca criar uma função scn::scan
que substitua a
scanf
(pelo mesmo raciocínio empregado na abolição do
printf
). Exemplo:
-#include <scn/scn.h>
-// lembre-se de incluir o pacote eliaskosunen/scnlib no CMake
-using scn::scan;
-// ...
-int x = 0;
-int y = 0;
-auto resto = scan("10 20", "{}", x);
-scan(resto, "{}", y);
-print("x={} y={}", x, y);
-// x=10 y=20
+#include <scn/scn.h>
+// lembre-se de incluir o pacote eliaskosunen/scnlib no CMake
+using scn::scan;
+// ...
+int x = 0;
+int y = 0;
+auto resto = scan("10 20", "{}", x);
+scan(resto, "{}", y);
+print("x={} y={}", x, y);
+// x=10 y=20
Ponteiro cycles::relation_ptr
@@ -1362,15 +1380,15 @@ Ponteiro cycles::relation_ptr
em um C++ futuro. Exemplo:
Para utilizar, basta fazer
#include <cycles/relation_ptr>
. Exemplo:
-using cycles::relation_pool;
-using cycles::relation_ptr;
-// veja instruções em: https://github.com/igormcoelho/cycles
-relation_pool<> grupo;
-auto r1 = grupo.make<int>(10);
-auto r2 = std::move(r1);
-print("*r2={}\n",*r2);
-// *r2=10
-r2.reset(); // apaga ponteiro r2 manualmente
+using cycles::relation_pool;
+using cycles::relation_ptr;
+// veja instruções em: https://github.com/igormcoelho/cycles
+relation_pool<> grupo;
+auto r1 = grupo.make<int>(10);
+auto r2 = std::move(r1);
+print("*r2={}\n",*r2);
+// *r2=10
+r2.reset(); // apaga ponteiro r2 manualmente
@@ -1457,15 +1475,15 @@ Modularização Básica
Um programa começa pelo seu “ponto de entrada” (ou
entrypoint), tipicamente uma função
int main()
:
-#include<iostream> // inclui arquivo externo
-int main() {
- return 0; // 0 significa: nenhum erro
-}
-A declaração de funções pode ser feita antes da definição:
-int quadrado(int p); // declara a função 'quadrado'
-int quadrado(int p) {
- return p*p; // implementa a função 'quadrado'
+#include<iostream> // inclui arquivo externo
+int main() {
+ return 0; // 0 significa: nenhum erro
}
+A declaração de funções pode ser feita antes da definição:
+int quadrado(int p); // declara a função 'quadrado'
+int quadrado(int p) {
+ return p*p; // implementa a função 'quadrado'
+}
Declarações vem em arquivos .h
, enquanto as respectivas
implementações em arquivo .cpp
(ou juntas como
.hpp
).
@@ -1517,49 +1535,49 @@ Tipos na biblioteca padrão C++
vantajoso usar o existente na STL, chamado std::pair
(o
prefixo std::
é chamado namespace e evita colisões
de nomes):
-#include<iostream> // funções de entrada/saída
-#include<tuple> // agregados de par e tupla
-int main() {
- std::pair<int, char> p {5, 'C'}; // direct init.
- printf("%d %c\n", p.first, p.second); // 5 C
- // ...
-}
+#include<iostream> // funções de entrada/saída
+#include<tuple> // agregados de par e tupla
+int main() {
+ std::pair<int, char> p {5, 'C'}; // direct init.
+ printf("%d %c\n", p.first, p.second); // 5 C
+ // ...
+}
Relembrando (agregado Z)
-// Em C++ (tipo agregado Z)
-class Z
-{
-public:
- int x;
- // imprime campo x
- void imprimex() {
- printf("%d\n", this->x);
- }
-};
+// Em C++ (tipo agregado Z)
+class Z
+{
+public:
+ int x;
+ // imprime campo x
+ void imprimex() {
+ printf("%d\n", this->x);
+ }
+};
Relembrando (conceito TemImprimeX)
-template<typename Agregado>
-concept bool
-TemImprimeX = requires(Agregado a) {
- {
- a.imprimex()
- }
-};
+template<typename Agregado>
+concept bool
+TemImprimeX = requires(Agregado a) {
+ {
+ a.imprimex()
+ }
+};
Baixando o Catch2 e executando
diff --git a/slides/1-revisao-tipos/makefile b/slides/1-revisao-tipos/makefile
index 12ec239..a7de399 100644
--- a/slides/1-revisao-tipos/makefile
+++ b/slides/1-revisao-tipos/makefile
@@ -9,9 +9,10 @@ all: deps
#
#
# -F pandoc-crossref
- /usr/bin/pandoc --toc -s --embed-resources --standalone -V theme:Warsaw -t beamer \
+ /usr/bin/pandoc --toc -s --embed-resources --standalone --filter pandoc-latex-fontsize -V theme:Warsaw -t beamer \
-H header.tex \
$(MDFILE) -o $(OUTPDF) --slide-level 2
+# $(OUTPDF)
#--filter pandoc-source-exec