C++ es un lenguaje de propósito general creado por Bjarne Stroustrup en los años 80. Es un lenguaje multiparadigma de tipado estático, que compila a código máquina, por lo que sus ejecutables no son portables pero su código si puede serlo. Estos son algunos enlaces de interés:
Para este documento vamos a utilizar la versión C++20 del estándar, cuya implementación ya está suficientemente extendida entre los principales compiladores del mercado.
El código que se compila va en ficheros .cpp
, también conocidos como unidades de compilación, por ejemplo:
// Fichero: hola.cpp
#include<iostream>
int main () {
std::cout << "Hola mundo\n";
}
Para poner comentarios de una sola línea usamos //
, si se quiere usar varias líneas se hace con /*
*/
. La directiva #include
indica al preprocesador del compilador que incluya el contenido de otro fichero en esa posición. En el caso del ejemplo incluye la cabecera iostream
, que contiene definiciones para trabajar con flujos de entrada y salida. Las cabeceras son ficheros, que normalmente tienen la extensión .hpp
o .h
, que contienen definiciones cuyo código está en otro fichero .cpp
o en una biblioteca ya compilada (.dll
, .lib
, .so
). Después tenemos la función main
, que es la entrada al programa y en ella mandamos a la terminal una cadena de texto usando el objeto std::cout
.
La función
main
es la única que permite omitir devolver un valor conreturn
. Si se omite esta operación, por defecto el compilador hará que el programa devuelva el valor0
.
Existen otra variante de la función main
:
// Fichero: holarg.cpp
#include<iostream>
int main (int argc, char ** argv) {
std::cout << "Argumentos: " << argc << "\n";
for (int i = 0; i < argc; i++) {
std::cout << i << ": " << argv[i] << "\n";
}
return 0;
}
En este caso la función main
devuelve un valor entero, donde 0
indica que no ha habido ningún error, y recibe una serie de argumentos, donde argc
es el número de argumentos recibido y argv
los argumentos como cadenas de texto. Las cadenas de texto estándar de C, son segmentos de memoria que terminan con el valor 0
(no confundirlo con el carácter cero). En el ejemplo mostramos el número de argumentos y con la sentencia for
vamos mostrando cada argumento por pantalla.
Existen diferentes compiladores, como el de MSVC o el GCC, que tienen una cantidad considerable de opciones que no vamos a documentar aquí. Lo más fácil es utilizar un IDE, que se encargue de gestionar la compilación.
Para poder usar aquello que vamos a definir, necesitamos de nombres identificadores. Un identificador se compone de una sucesión de letras (mayúsculas y minúsculas), dígitos (0
-9
) y/o el guion bajo (_
), que no debe comenzar por dígito. No se podrán usar las palabras reservadas del lenguaje como identificadores.
Es muy importante tener en cuenta en C++, que no podremos usar ningún elemento que no haya sido definido o declarado previamente a su uso. Esto implica ciertas incomodidades que no tienen muchos lenguajes modernos, por ello C y C++ suelen tener código cuyo propósito es declarar la existencia de una definición que se encuentra en otra unidad de compilación o más adelante dentro de la misma unidad.
Además, el lenguaje nos permite crear espacios de nombres, donde agrupar conjuntos de definiciones. Por ejemplo, std
es el espacio de nombres donde se encuentran las definiciones de la biblioteca estándar. Para acceder al contenido de uno se usa el operador ::
, por ejemplo, std::cout
es el nombre cualificado para acceder a la definición cout
dentro de std
. La forma ::miembro
hace referencia a miembros que están en el espacio de nombres global a todos.
La sintaxis básica para definir un espacio de nombres es la siguiente:
La palabra clave inline
incluye las definiciones dentro del espacio de nombres que contenga al espacio definido. Si no se incluye un nombre identificador, el ámbito de sus miembros se circunscribe a la unidad de compilación.
Alternativamente, podemos definir espacios de nombre de la siguiente manera para no tener que anidarlos manualmente:
Existe la posibilidad de utilizar los miembros de un espacio de nombres, en una unidad de compilación, sin tener que utilizar su nombre cualificado. Para ello se utiliza la sintaxis:
Si sólo queremos incorporar un miembro determinado usaremos la siguiente sintaxis:
Si queremos crear un alias para un espacio de nombres tenemos la siguiente sintaxis:
La sintaxis para definir números enteros es la siguiente:
Desde C++14 se puede utilizar la comilla simple (
'
) para separar grupos de dígitos, como una especie de separador de millares arbitrario. A la hora de compilar el carácter es ignorado, por lo que es meramente estético.
Los prefijos validos son:
Prefijos | Tipo | Dígitos |
---|---|---|
- | Decimal | 0 -9 |
0b , 0B |
Binario | 0 -1 |
0x , 0X |
Hexadecimal | 0 -9 , A -F , a -f |
0 |
Octal | 0 -7 |
Los sufijos validos son:
Prefijos | Tipo | Descripción |
---|---|---|
- | int |
Entero con signo. |
u , U |
usigned |
Entero sin signo. |
l , L |
long |
Entero largo con signo. |
ul , lu , uL , Lu ,Ul , lU , UL , LU |
usigned long |
Entero largo sin signo. |
ll , LL |
long long |
Entero muy largo con signo. |
ull , llu , uLL , LLu ,Ull , llU , ULL , LLU |
usigned long long |
Entero muy largo sin signo. |
Estos son los tipos que representan números enteros, con sus tamaño en bits (donde W es Windows, U es Unix/Linux y x es ambos):
Tipo | Variantes | W16 | x32 | W64 | U64 |
---|---|---|---|---|---|
signed char |
- | 8 | 8 | 8 | 8 |
unsigned char |
- | 8 | 8 | 8 | 8 |
short |
short int , signed short , signed short int |
16 | 16 | 16 | 16 |
unsigned short |
unsigned short int |
16 | 16 | 16 | 16 |
int |
signed , signed int |
16 | 32 | 32 | 32 |
unsigned |
unsigned int |
16 | 32 | 32 | 32 |
long |
long int , signed long , signed long int |
32 | 32 | 32 | 64 |
unsigned long |
unsigned long int |
32 | 32 | 32 | 64 |
long long |
long long int , signed long long , signed long long int |
64 | 64 | 64 | 64 |
unsigned long long |
unsigned long long int |
64 | 64 | 64 | 64 |
Los rangos de valores para cada tamaño son:
Tamaño | Rango |
---|---|
8-bits con signo | -128 .. 127 |
8-bits sin signo | 0 .. 255 |
16-bits con signo | -32768 .. 32767 |
16-bits sin signo | 0 .. 65535 |
32-bits con signo | -2147483648 .. 2147483647 |
32-bits sin signo | 0 .. 4294967295 |
64-bits con signo | -9223372036854775808 .. 9223372036854775807 |
64-bits sin signo | 0 .. 18446744073709551615 |
Se puede convertir sin pérdida cuando se asigna un entero de un tipo a uno más grande. Al revés pueden haber pérdidas de información, así como al intentar asignar un valor sin signo a uno con signo y viceversa.
En la cabecera cstdint
se incorporan una serie de alias para los tipos enteros, así como algunas macros que definen los límites numéricos de cada tamaño. Con int8_t
, int16_t
, int32_t
e int64_t
, podemos definir variables enteras con signo de un tamaño fijo, sin preocuparnos de la plataforma. Con uint8_t
, uint16_t
, uint32_t
y uint64_t
, podemos hacer lo mismo pero para enteros sin signo. También con la cabecera limits
tenemos herramientas para comprobar propiedades sobre cualquier tipo tipo numérico.
La sintaxis para definir números reales es la siguiente:
Desde C++17 se puede usar el prefijo
0x
o0X
para usar dígitos hexadecimales al definir un número real. Sin embargo, para indicar el exponente se utilizap
oP
, en lugar dee
yE
. Por ejemplo,0x1EFp2
.
Los sufijos validos son:
Prefijos | Tipo | Tamaño | Descripción |
---|---|---|---|
- | double |
64 | Real doble. |
f , f |
float |
32 | Real simple. |
l , L |
long double |
128 | Real doble largo. |
El tipo
long double
ocupa 128 bits en arquitecturas de 64 bits, siguiendo el estándar IEEE 754, pero tradicionalmente en las de 32 bits este tipo ocupaba 80 bits, siguiendo el formato de precisión extendida.
La sintaxis para definir caracteres es la siguiente:
Las secuencias de escape disponibles son:
Secuencia | Significado | Secuencia | Significado |
---|---|---|---|
\' |
Comilla simple. | \" |
Comilla doble. |
\? |
Interrogación. | \\ |
Barra invertida. |
\a |
Alarma. | \b |
Retroceso. |
\f |
Alimentación. | \n |
Nueva línea. |
\r |
Retroceso de línea. | \t |
Tabulación horizontal. |
\v |
Tabulación vertical. | \0 |
Carácter nulo. |
\### |
Carácter octal. | \x## \x#### |
Carácter hexadecimal. |
\u#### |
Carácter Unicode (UTF-16). | \U######## |
Carácter Unicode (UTF-32). |
Los prefijos validos son:
Prefijos | Tipo | Descripción |
---|---|---|
- | char |
Carácter normal. |
u8 |
char8_t |
Carácter UTF-8. |
u |
char16_t |
Carácter UTF-16. |
U |
char32_t |
Carácter UTF-32. |
L |
wchar_t |
Carácter Unicode (UTF-16 en Windows y UTF-32 en Unix/Linux). |
La sintaxis para definir cadenas de texto es la siguiente:
Los prefijos validos son:
Prefijos | Elementos | Descripción |
---|---|---|
- | char |
Carácter normal. |
u8 |
char8_t |
Carácter UTF-8. |
u |
char16_t |
Carácter UTF-16. |
U |
char32_t |
Carácter UTF-32. |
L |
wchar_t |
Carácter Unicode (UTF-16 en Windows y UTF-32 en Unix/Linux). |
Las cadenas de texto como tal no existe como tipos básicos del lenguaje, es necesario usar arrays (
char []
) y punteros (char *
) para almacenar una cadena de texto. Dicho lo cual, la biblioteca estándar tiene el tipostd::string
para almacenar cadenas y trabajar con ellas de forma cómoda.
Lo valores booleanos están representados por el tipo bool
. Estos se utilizan para dar valor a condiciones que pueden ser ciertas o falsas. Su sintaxis es la siguiente:
Por compatibilidad con el lenguaje C, el valor
true
equivale al entero1
yfalse
al0
. Por ello hay que entender la siguiente regla: una condición será falsa si su valor es cero y cierta con cualquier otro valor.
Un puntero es un tipo de valor que nos indica una dirección de memoria donde está el valor con el que vamos a trabajar. En el caso de no apuntar valor alguno, utilizaremos el literal nullptr
.
Internamente equivale al valor cero y sustituye a la histórica macro
NULL
, para ayudar al compilador con las conversiones. El tipostd::nullptr_t
representa los punteros nulos en el estándar y tiene el mismo tamaño que el tipovoid *
.
Una variable es un valor con nombre que puede variar durante la ejecución del programa. La sintaxis básica para definir variables es la siguiente:
Con la palabra clave auto
se indica al compilador que infiera el tipo para la variable en base a la expresión que inicializa su valor:
Cada nombre representa una dirección en la memoria, donde se almacena el valor resultante de evaluar la expresión que se le asigne a la variable, ya sea en la inicialización o con el operador asignación (=
). El tipo le indica al compilador el tamaño que ocupan los valores con los que vamos a trabajar, para acceder a su estructura interna de forma correcta. Por ejemplo:
// Fichero: variables.cpp
#include<iostream>
int main () {
int a = 1, b = 2;
auto c = 3.14;
std::cout << a << ", " << b << ", " << c << "\n"; // 1, 2, 3.14
b = 4;
std::cout << a << ", " << b << ", " << c << "\n"; // 1, 4, 3.14
}
Los arrays son bloques de memoria que contienen un número fijo de elementos, a los que se acceden de forma indexada. La sintaxis básica para definir arrays es la siguiente:
Entre corchetes indicamos el tamaño de cada dimensión del array, pudiendo inicializar el contenido con una serie de valores. Se puede omitir el tamaño de la primera dimensión si se inicializa la variable. Por ejemplo:
// Fichero: arrays.cpp
#include<iostream>
int main () {
int a[2];
a[0] = 1;
a[1] = 2;
std::cout << a[0] << ", " << a[1] << "\n"; // 1, 2
char b[] = "Hola"; // char b[5]
std::cout << b << "\n"; // Hola
int c[][2] = { {1, 2}, {3, 4} }; // int c[2][2]
std::cout << c[0][0] << ", " << c[0][1] << "\n"; // 1, 2
std::cout << c[1][0] << ", " << c[1][1] << "\n"; // 3, 4
char d[][20] = { "inicio", "final" }; // char d[2][20]
std::cout << d[0] << "\n"; // inicio
std::cout << d[1] << "\n"; // final
}
Hay que tener en cuenta que la primera posición en un array es el índice cero, la segunda el índice uno y así sucesivamente. Los arrays conforman un bloque contiguo en la memoria del programa, donde se almacenan los valores, calculando su posición en la memoria mediante los índices utilizados. Al inicializar un array no es requisito inicializar todos los valores del segmento de memoria, pero sí es requisito que no excedan en número de elementos.
Al usar el *
, a continuación de un tipo, modificamos su significado para convertir dicha variable en un puntero. Un puntero es un tipo de variable que apunta a otro lugar de la memoria y por lo tanto lo que almacena es una dirección de memoria. Con el operador &
podemos obtener la dirección de memoria de cualquier variable, mientras que con el operador *
accedemos al contenido apuntado. Por ejemplo:
// Fichero: punteros.cpp
#include<iostream>
int main () {
int a = 24;
std::cout << a << "\n"; // 24
int * b = &a;
*b = 42;
std::cout << a << "\n"; // 42
}
Con un puntero se puede acceder a un array de valores, de hecho un tipo común de las cadenas de texto es char *
. Para recorrer los elementos de un array apuntado por un vector se puede utilizar el operador de indexación []
, o se puede usar la aritmética de punteros, que no es muy recomendable. Se puede definir punteros de punteros, por si necesitamos hacer una tabla de elementos. Por ejemplo:
// Fichero: parrays.cpp
#include<iostream>
int main () {
char c[] = "cadena";
char * d = c;
std::cout << d[2] << "\n"; // d
std::cout << *(d+2) << "\n"; // d
}
La utilidad fundamental de los punteros es que con el operador new
podemos reservar memoria del sistema, para almacenar un nuevo valor, y con delete
borrar dicha memoria reservada. Por ejemplo:
// Fichero: memoria.cpp
#include<iostream>
int main () {
int * a = new int { 8 };
std::cout << *a << "\n"; // 8
delete a;
int * b = new int[5] { 5, 4, 3, 2, 1 };
std::cout << b[2] << "\n"; // 3
delete[] b;
int ** c = new int*[2] {
new int[3] { 1, 2, 3 },
new int[4] { 4, 5, 6, 7 }
};
std::cout << c[0][2] << "\n"; // 3
std::cout << c[1][3] << "\n"; // 7
delete[] c[0];
delete[] c[1];
delete[] c;
}
Con new
se reserva un elemento, mientras que con new[]
se reservan un número arbitrario de elementos. Por ello hay que utilizar delete[]
cuando se haya usado new[]
, para garantizar la correcta eliminación del bloque de memoria reservado. Otro aspecto relevante del ejemplo, es que con {}
podemos opcionalmente indicar los valores con los que inicializar el bloque reservado de memoria.
Los punteros son una característica de bajo nivel, que requiere ser metódico y cuidadoso, para evitar que los programas tengan fugas de memoria y terminen fallando. Por ello no son muy populares entre el programador común, haciendo que muchos lenguajes dispongan de abstracciones como los punteros inteligentes y la gestión de memoria automática con recolectores de basura. Sin embargo, la librería estándar de C++ incorpora punteros inteligentes como
std::shared_ptr
ystd::unique_ptr
, que veremos más adelante.
Debido al poco cariño que la gente le tiene a los punteros, se introdujo en C++ el modificador &
para convertir una variable en una referencia a otra variable. Esencialmente es un puntero con ciertas limitaciones y ventajas. Por ejemplo:
// Fichero: referencias.cpp
#include<iostream>
int main () {
int a { 8 };
int b[] { 5, 4, 3, 2, 1 };
int c[][3] { { 1, 2, 3 }, { 4, 5, 6 } };
auto & ra = a; // int & ra = a;
auto & rb = b; // int (& rb)[5] = b;
auto & rc = c; // int (& rc)[2][3] = c;
std::cout << ra << "\n"; // 8
std::cout << rb[2] << "\n"; // 3
std::cout << rc[0][0] << "\n"; // 1
std::cout << rc[1][1] << "\n"; // 5
}
La principal ventaja es que su uso es más sencillo, porque no requiere del operador *
para acceder al contenido al que hace referencia. Como desventajas, la primera es que no se puede declarar una referencia sin inicializar su valor. Otra desventaja es que no se le puede asignar directamente el resultado de reservar memoria con new
, porque no están las referencias pensadas para estas tareas. Si usamos el operador &
con una variable referencia, nos dará la dirección de memoria de la variable que referencia.
Una constante es un valor que no va a cambiar durante la ejecución del programa. La sintaxis básica para definir constantes es la siguiente:
Para el compilador una constante es una variable capada, que no puede ser reasignada. Se puede utilizar auto
para inferir el tipo en tiempo de compilación. Por ejemplo:
// Fichero: constantes.cpp
#include<iostream>
int main () {
const auto a = 123; // const int a
std::cout << a << "\n"; // 123
const auto b = "hola"; // const char * const b
std::cout << b << "\n"; // hola
// Ilegal: b[0] = 'H';
// Ilegal: b = "Hola";
const char * c = "hola";
std::cout << c << "\n"; // hola
// Ilegal: c[0] = 'H';
c = "Hola";
std::cout << c << "\n"; // Hola
char cadena[] = "hola";
char * const d = cadena;
std::cout << d << "\n"; // hola
d[0] = 'H';
// Ilegal: d = "Hola";
std::cout << d << "\n"; // Hola
}
Con tipos normales o referencias, una constante no puede cambiar el contenido del valor asignado. Pero con punteros, dependiendo de la posición donde se use const
, si se puede llegar a cambiar el contenido de un valor asignado. Por ejemplo, const char *
significa que no se puede modificar el contenido apuntado por la variable, pero sí se puede cambiar a dónde apunta la variable. Mientras que char * const
lo que impide es cambiar el valor al que apunta la variable. Es un matiz importante a tener en cuenta, aunque no es habitual tener que lidiar con el segundo caso, pero sí con el primero.
Esta es la precedencia de mayor a menor de los operadores del lenguaje:
Nivel | Operadores | Descripción | Asociatividad |
---|---|---|---|
1 | x::y |
Resolución de ámbito | Izquierda |
2 | x++ , x-- T(x) , T{x} x(y) x[y] x.y , x->y |
Incremento/decremento postfijo Casting funcional Llamada a función Acceso indexado Acceso a miembro |
Izquierda |
3 | ++x , --x +x , -x !x , ~x (T)x *x &x sizeof(x) co_await x new , new[] delete , delete[] |
Incremento/decremento prefijo Signo positivo/negativo Negación lógica/binaria Casting clásico Contenido apuntado Dirección de Tamaño de Espera de corrutina Reservar memoria Eliminar memoria |
Derecha |
4 | x.*y , x->*y |
Puntero a miembro | Izquierda |
5 | x * y , x / y , x % y |
Multiplicación, división y resto | Izquierda |
6 | x + y , x – y |
Suma y resta | Izquierda |
7 | x << y , x >> y |
Desplazamiento a la izquierda/derecha | Izquierda |
8 | x <=> y |
Comparación por diferencia | Izquierda |
9 | x < y , x <= y , x > y , x >= y |
Menor, menor o igual, mayor y mayor o igual | Izquierda |
10 | x == y , x != y |
Igual y distinto | Izquierda |
11 | x & y |
Conjunción binaria | Izquierda |
12 | x ^ y |
Disyunción exclusiva binaria | Izquierda |
13 | x | y |
Disyunción binaria | Izquierda |
14 | x && y |
Conjunción booleana | Izquierda |
15 | x || y |
Disyunción booleana | Izquierda |
16 | x ? y : z throw x co_yield x x = y x += y , x -= y , x *= y , x /= y , x %= y , x &= y , x |= y , x ^= y , x <<= y , x >>= y |
Condicional Lanzar excepción Lanzar valor Asignación Asignación compuesta |
Derecha |
17 | x, y |
Coma | Izquierda |
Donde x
, y
y z
son expresiones, y T
es un tipo.
El primer bloque de operadores booleanos son los de comparación:
Operación | Descripción |
---|---|
X == Y |
X es igual a Y . |
X != Y |
X no es igual a Y . |
X <= Y |
X es menor o igual que Y . |
X < Y |
X es menor que Y . |
X >= Y |
X es mayor o igual que Y . |
X > Y |
X es mayor que Y . |
X <=> Y |
Resultado de X - Y . |
El primer bloque de operadores booleanos son los lógicos:
Operación | Descripción |
---|---|
!X |
Negación de X . |
X && Y |
Conjunción de X con Y . |
X || Y |
Disyunción de X con Y . |
Estos operadores nos permiten componer condiciones más complejas. Para entenderlos mejor aquí tenemos sus tablas de la verdad:
X |
Y |
!X |
!Y |
X && Y |
X || Y |
---|---|---|---|---|---|
true |
true |
false |
false |
true |
true |
false |
true |
true |
false |
false |
true |
true |
false |
false |
true |
false |
true |
false |
false |
true |
true |
false |
false |
Los operadores de conjunción y disyunción funcionan por cortocircuito, por lo que si el operando izquierdo es suficiente para determinar el resultado, no se evaluará el operando derecho. Es decir, si X
es false
en una conjunción, el resultado será false
, pero si es true
en una disyunción, el resultado será true
.
Por último, existe el operador condicional ternario C ? T : F
, donde se evalúa la condición C
y si esta es cierta, se devuelve el valor de la expresión T
, si es falsa se devuelve el valor de F
.
El primer bloque de operadores numéricos es el de los aritméticos:
Operación | Descripción |
---|---|
+X |
Signo positivo. |
-X |
Signo negativo. |
X + Y |
Suma: X más Y . |
X - Y |
Resta: X menos Y . |
X * Y |
Multiplicación: X por Y . |
X / Y |
División: X entre Y . |
X % Y |
Resto de dividir X entre Y . |
++X , X++ |
Incremento prefijo y postfijo. |
--X , X-- |
Decremento prefijo y postfijo. |
La diferencia entre el incremento, o decremento, prefijo y postfijo radica en que la versión postfija devuelve el valor de X
previo al cambio. Con la versión prefija se realiza el cambio y entonces se devuelve el valor de X
:
// Fichero: incremento.cpp
#include<iostream>
int main () {
int x = 1;
std::cout << ++x << "\n"; // 2
std::cout << x++ << "\n"; // 2
std::cout << x << "\n"; // 3
}
El segundo bloque son los operadores a nivel de bits:
Operación | Descripción |
---|---|
~X |
Negación de los bits de X . |
X & Y |
Conjunción de los bits de X con Y . |
X | Y |
Disyunción de los bits de X con Y . |
X ^ Y |
Disyunción exclusiva de los bits de X con Y . |
X << Y |
Desplazamiento a la izquierda Y bits de X . |
X >> Y |
Desplazamiento a la derecha Y bits de X . |
De modo similar a los operadores lógicos, están son sus tablas de la verdad:
X |
Y |
~X |
~Y |
X & Y |
X | Y |
X ^ Y |
---|---|---|---|---|---|---|
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
0 |
Estos son los operadores de asignación disponibles:
Operación | Descripción |
---|---|
X = Y |
Asigna Y a X . |
X += Y |
Equivale a X = X + Y . |
X -= Y |
Equivale a X = X - Y . |
X *= Y |
Equivale a X = X * Y . |
X /= Y |
Equivale a X = X / Y . |
X %= Y |
Equivale a X = X % Y . |
X &= Y |
Equivale a X = X & Y . |
X |= Y |
Equivale a X = X | Y . |
X ^= Y |
Equivale a X = X ^ Y . |
X <<= Y |
Equivale a X = X << Y . |
X >>= Y |
Equivale a X = X >> Y . |
Estos son los operadores para acceder a miembros:
Operación | Descripción |
---|---|
X::Y |
Acceso al miembro Y del espacio de nombres X . |
X.Y |
Acceso al miembro Y de X . |
X[Y] |
Acceso al elemento Y en X . |
El primer bloque son operadores básicos para manipular punteros:
Operación | Descripción |
---|---|
X->Y |
Acceso al miembro Y del puntero X . |
&X |
Dirección de X en la memoria. |
*X |
Contenido apuntado por X en la memoria. |
X.*Y |
Acceso al contenido apuntado por el miembro Y de X . |
X->*Y |
Acceso al contenido apuntado por el miembro Y del puntero X . |
El segundo bloque son los operadores para manipular la memoria:
Operación | Descripción |
---|---|
new T |
Reserva memoria para almacenar un valor del tipo T . |
new T(Args) |
Reserva memoria para almacenar un valor del tipo T , pasándole una serie de argumentos para construirlo. |
new T[] |
Reserva memoria para almacenar un array del tipo T . |
delete X |
Libera el espacio reservado para el valor apuntado por X . |
delete[] X |
Libera el espacio reservado para el array apuntado por X . |
El siguiente bloque son operadores para convertir tipos:
Operación | Descripción |
---|---|
(T)X |
Casting para convertir el resultado de X al tipo T . |
T(X) T{X} |
Casting funcional para convertir el resultado de X al tipo T . |
Estos operadores normalmente se utilizan con punteros y referencias, pero también se pueden usar con tipos básicos para forzar conversiones que requieran ser explícitas. Para hacer punteros genéricos en C, se solía utilizar el tipo void *
, para después hacer un casting al tipo oportuno. Con C++ lo recomendable es usar jerarquías de tipos, para aprovechar el polimorfismo y usar algunos de los casting funcionales que ofrece el lenguaje:
Casting | Descripción |
---|---|
static_cast<T>(X) |
Convierte el resultado de X al tipo T sin ninguna comprobación en tiempo de ejecución. Sin embargo, el compilador realiza algunas comprobaciones para evitar algunos errores de ejecución. |
dynamic_cast<T>(X) |
Convierte el resultado de X al tipo T con una comprobación en tiempo de ejecución, si esta falla se devuelve nullptr cuando el tipo es un puntero, o se lanza la excepción std::bad_cast en el resto de casos. Sólo se puede usar con tipos dentro de una misma jerarquía. Para convertir una clase base a una hija, la base ha de tener algún método virtual. |
reinterpret_cast<T>(X) |
Convierte el resultado de X al tipo T sin ninguna comprobación en tiempo de ejecución. Es esencialmente un casting al estilo C para punteros. |
const_cast<T>(X) |
Convierte el resultado de X al tipo T para eliminar el modificador const o añadirlo. |
Estos son operadores que no entran en las demás categorías antes expuestas:
Operación | Descripción |
---|---|
X, Y |
Ejecuta la expresión X y después la expresión Y . |
F(Args) |
Invocación de la función F con una serie de argumentos. |
sizeof(X) |
Da el tamaño que ocupa en memoria la expresión X . |
throw x |
Lanza el valor de X como una excepción. |
co_await X |
Espera asíncrona para el resultado de la corrutina X . |
co_yield X |
Lanza el valor X en una corrutina generadora. |
La primera sentencia de control es el if
-else
, cuya sintaxis es la siguiente:
Un bloque puede ser una sentencia terminada con punto y coma (expresión;
) o un bloque de sentencias delimitado por llaves ({ expresiones }
). Si la condición se evalúa a true
, se ejecutará el primer bloque, si no se ejecutará el segundo si está definido. Por ejemplo:
// Fichero: ifelse.cpp
#include<iostream>
int main () {
int a, b;
std::cout << "A = ";
std::cin >> a;
std::cout << "B = ";
std::cin >> b;
if (a == b) {
std::cout << "A y B son iguales.\n";
} else if (a < b) {
std::cout << "A es menor que B.\n";
} else {
std::cout << "A es mayor que B.\n";
}
}
En el ejemplo vemos el objeto
std::cin
, que nos permite introducir por consola un valor y almacenarlo en una variable. Téngase en cuenta que este objeto no reserva automáticamente memoria, por lo que para introducir una cadena de texto se requiere tener reservado previamente el espacio necesario o utilizar tipos comostd::string
.
Desde C++17 se puede introducir antes de la condición, separado con un punto y coma, una expresión de inicialización. Por ejemplo:
// Fichero: ifinit.cpp
#include <string>
#include <iostream>
int main () {
std::string nombre;
std::cout << "Nombre: ";
if (std::cin >> nombre; !nombre.empty()) {
std::cout << "Hola, " << nombre << ".\n";
}
}
La sentencia de control switch
permite evaluar una expresión y ejecutar un bloque dependiendo del valor obtenido. Su sintaxis es:
Dependiendo del valor de la expresión, se va buscando en orden una cláusula case
donde encaje su valor. La primera que encaje será el punto de entrada de la ejecución, ejecutando sus expresiones hasta encontrar la sentencia break
al final de dicho bloque, pues de lo contrario saltaría a ejecutar el cuerpo de la siguiente cláusula. Si no se encaja con ninguna cláusula, se ejecutará la cláusula default
de estar definida. Por ejemplo:
// Fichero: switch.cpp
#include<iostream>
int main () {
int a;
std::cout << "> ";
std::cin >> a;
switch (a) {
case 5: std::cout << "Cinco.\n";
case 4: std::cout << "Cuatro.\n";
case 3: std::cout << "Tres.\n";
case 2: std::cout << "Dos.\n";
case 1: std::cout << "Uno.\n";
case 0: std::cout << "Cero.\n";
break;
default: std::cout << "Indefinido.\n";
}
}
La sentencia de control while
permite ejecutar un bloque de código mientras se cumpla una condición. Su sintaxis es:
Con la sentencia do
-while
podremos hacer lo mismo que con la anterior, con la diferencia de que siempre se ejecutará al menos una vez el bloque de código. Su sintaxis es:
Si queremos ejecutar el código un número determinado de veces, con la sentencia for
podremos hacerlo con la siguiente sintaxis:
Donde inicio son las expresiones, separadas por comas, que se ejecutan antes de entrar en el bucle. Normalmente se utiliza el inicio para declarar las variables contadoras del bucle. Con condición, en cada iteración del bucle, se controla al inicio de cada vuelta si se ha de ejecutar o no. Y con incremento tenemos las expresiones que se ejecutan al final de cada iteración, que habitualmente se usan para incrementar los contadores.
Existe una variante del bucle for
que nos permite recorrer los elementos de un contenedor de datos, tales como los arrays por ejemplo. Su sintaxis es:
De forma similar a lo que vimos con el if
, desde C++20 se puede introducir, separado con un punto y coma, una expresión de inicialización en los bucles for
para contenedores.
Con este ejemplo podremos ver en acción los diferentes tipos de bucles:
// Fichero: bucles.cpp
#include <string>
#include <iostream>
int main () {
const int SIZE = 4;
int nums[SIZE] = { 1, 2, 3, 4 };
std::string respuesta;
do {
int i = 0;
std::cout << "While: ";
while (i < SIZE) {
std::cout << nums[i] << " ";
++i;
}
std::cout << "\n";
std::cout << "For: ";
for (int j = 0; j < SIZE; j++) {
std::cout << nums[j] << " ";
}
std::cout << "\n";
std::cout << "For each: ";
for (auto n : nums) {
std::cout << n << " ";
}
std::cout << "\n";
std::cout << "¿Salir? (S/N) ";
std::cin >> respuesta;
} while (respuesta != "s" && respuesta != "S");
}
La primera sentencia de salto es return
, que se utiliza para salir de una función, devolviendo un valor de forma opcional. Su sintaxis es:
Después están break
y continue
. La primera se utiliza en bucles, o sentencias switch
, para salir fuera de dicha sentencia que la contiene. La segunda se utiliza en bucles y, en lugar de salir de este, lo que provoca es un salto a la siguiente iteración del bucle. Su sintaxis es:
Para entenderlo mejor, veamos el siguiente ejemplo:
// Fichero: saltos.cpp
#include<iostream>
int main () {
int xs[] = {
1, 2, 3, 4, 5, 6, 7, 0, 8, 9
};
for (auto x : xs) {
if (x == 0) break;
if (x % 2 == 0) continue;
std::cout << x << " ";
}
std::cout << "\n";
// 1 3 5 7
}
Por último está el demonio de la programación estructurada, la sentencia maldita, la portadora de grandes tormentos de ofuscación, la que no debería ser nombrada. La sentencia goto
se utiliza para hacer un salto incondicional a otra parte del código, que se identifica con una etiqueta. La sintaxis de esta herejía es:
El uso de goto
no se recomienda en absoluto, ya que puede derivar en muy malas prácticas de programación. Su uso debe estar muy justificado y es preferible utilizar otras opciones antes.
Las excepciones es un mecanismo del lenguaje para gestionar los errores en nuestros programas. Cuando algo falle de forma inesperada, podemos lanzar una excepción con la siguiente sintaxis:
Donde excepción debe ser una expresión que devuelva un valor. La biblioteca estándar dispone de la clase base std::exception
para representar excepciones, pero el lenguaje permite lanzar todo tipo de valores.
Una vez lanzada la excepción, por nosotros o por el sistema, tendremos que capturarla con la sentencia try
-catch
, cuya sintaxis es:
Hay que tener en cuenta que try
debe ir acompañado al menos de un catch
. En caso de lanzarse una excepción, se comprobará en el orden que están definidas las cláusulas catch
, para ejecutar el bloque de expresiones que primero encaje con el error recibido. Esto significa que el orden importa, por lo que si la primera es un catch (...)
, todas las que vengan después quedarán tapadas por esta primera, ya que es el modelo más genérico para capturar excepciones. Dentro de un catch
se puede usar throw;
, que relanza la última excepción capturada.
En este ejemplo podemos ver un manejo básico de las excepciones:
// Fichero: excepciones.cpp
#include<iostream>
int main () {
try {
throw 42;
std::cout << "Todo fue bien\n";
} catch (...) {
std::cout << "Error inesperado\n";
}
}
Una función es un bloque de código con nombre, que podemos parametrizar, para obtener diferentes resultados. Para definir una se utiliza la siguiente sintaxis:
Una función tiene un tipo de retorno, pero si no devuelve nada usará void
como tipo del resultado. También se permite usar auto
como tipo de retorno, para inferir el tipo en tiempo de compilación. Luego se puede tener entre cero y N parámetros, cuya sintaxis es:
Se puede, por herencia de C, usar void
para indicar que no tiene parámetros, pero lo normal es no poner nada entre los paréntesis. También podemos usar auto
como tipo de un parámetro, para inferir su tipo con el compilador. El lenguaje permite definir valores por defecto para los parámetros, pero sólo si el parámetro tiene un nombre y los parámetros a continuación también tienen valores por defecto.
C++ es un lenguaje que permite polimorfismo, por lo que podemos tener dos funciones definidas con el mismo nombre, siempre que varíen los parámetros de su definición. Por lo que podemos tener
void foo ()
,void foo (int)
ychar foo (bool)
al mismo tiempo sin problemas. A la hora de invocar la función, dependiendo del tipo del argumento se elegirá una función u otra.
La firma de una función corresponde a toda la definición previa al cuerpo de la misma, definido con { expresiones }
. Si necesitamos utilizar la función en un lugar del proyecto donde no está definida, podremos declararla usando la firma de la función, sin el cuerpo, terminando la sentencia con un punto y coma. Por ejemplo:
// Declaración:
int suma (int, int);
// Definición:
int suma (int a, int b) {
return a + b;
}
En cuanto a los modificadores disponibles, para funciones que no son miembros de un tipo, son los que indican si la función está libre de excepciones o no:
Modificador | Descripción |
---|---|
noexcept noexcept(true) |
La función no lanza excepciones. |
noexcept(false) |
La función lanza excepciones. |
throw() throw(tipos) |
La función lanza excepciones. (Nota: deprecado en C++17 y eliminado en C++20.) |
Disponemos del operador
noexcept(X)
para saber si la expresiónX
no lanza excepciones, devolviendo como resultado un booleano.
Con el sufijo -> tipo
indicamos el tipo de retorno de la función y requiere el uso de auto
. Se utiliza esta herramienta para dar información al compilador de lo que tiene que inferir. Lo habitual es usar este mecanismo en conjunción con plantillas y el operador decltype(X)
, que infiere el tipo para una entidad o expresión.
Existe el prefijo inline
, que permite indicar al compilador que debe insertar el código de la función en los puntos del programa donde es invocada. El compilador no siempre podrá insertar el código, por lo que es una recomendación que el programador da para la optimización del programa.
La elipsis ...
sirve para crear funciones con un número variable de argumentos en su llamada. Se añade al final de la lista de parámetros, pudiendo separarlo opcionalmente con una coma por compatibilidad con C. Su uso no impide usar valores por defecto para los parámetros anteriores. Para poder usar este tipo de parámetros, es necesario utilizar la cabecera cstdarg
, que dispone de los siguientes elementos:
Elemento | Descripción |
---|---|
std::va_list |
Estructura para almacenar los argumentos variables. |
va_start(L, N) |
Carga en la lista L un número N de argumentos. |
va_copy(LD, LS) |
Copia en la lista LD el contenido de LS . |
va_arg(L, T) |
Devuelve el siguiente argumento en la lista L , indicando su tipo con T . |
va_end(L) |
Elimina la lista L de argumentos. |
Por ejemplo:
// Fichero: varargs.cpp
#include <cstdarg>
#include <iostream>
int sum (int n, ...) {
int r = 0;
std::va_list args;
va_start(args, n);
for (int i = 0; i < n; i++) {
r += va_arg(args, int);
}
va_end(args);
return r;
}
int main () {
std::cout << sum(4, 1, 2, 3, 4) << "\n"; // 10
}
Las expresiones lambda es una forma de definir una función sin nombre. Son bien conocidas en el mundo de la programación funcional. Se utilizan mucho para procesar datos en contenedores de la biblioteca estándar, de los que veremos ejemplos más adelante.
La sintaxis básica para definir una lambda es la siguiente:
En cuanto a los parámetros se usa las mismas reglas que las funciones normales para su definición. Pero antes, tenemos las capturas, que es una lista de variables sobre las que vamos a hacer el cierre (closure), para poder usarlas en el cuerpo de la lambda. Hay que tener en cuenta que las lambdas se traducen en objetos de la clase std::function
, por lo que se pueden devolver como resultados de una función y por ello hay que gestionar cómo se cierran las variables. Para ello esta lista de cero o más variables, separadas por comas, podrán declararse con la siguiente sintaxis:
Con &
se capturan por referencia todas las variables utilizadas en el cuerpo de la lambda que estén en el ámbito de la misma, mientras que con =
se capturan por copia. Con this
se capturan por referencia los miembros del objeto del que es miembro la función donde se ha declarado la expresión lambda, pero con *this
se capturan por copia. Luego podemos indicar el nombre de las variables que queremos capturar, ya sea por copia o por referencia con &
. La elipsis ...
se utiliza para el pack de expansión de variables en plantillas, que explicaremos más adelante.
Hay que tener en cuenta, que con el cierre de las variables lo que se está haciendo es crear una nueva variable con el mismo nombre, que será una propiedad del objeto función que se está construyendo. Por eso podemos modificar la inicialización de dichas propiedades con las siguientes formas:
Forma | Descripción |
---|---|
= expresión |
Se inicializa con el valor de la expresión, que también puede ser una lista de expresiones entre llaves. |
(expresiones) |
Lista de valores entre paréntesis. |
{expresiones} |
Lista de valores entre llaves. |
{miembros} |
Lista de valores asignados a los miembros del tipo, con la forma .miembro = expresión . Los miembros que se declaren en la lista, tienen que estar en el orden en el que han sido definidos en el tipo o no compilará el código. |
En cuanto a los modificadores disponibles, tenemos un grupo opcional que especifica el comportamiento general de la lambda:
Modificador | Descripción |
---|---|
mutable |
Permite al cuerpo de la función a modificar las variables capturadas por copia e invocar sus métodos no constantes, ya que por defecto la copia se asume como const . |
constexpr |
Define la lambda como una función constexpr . |
consteval |
Define la lambda como una función consteval . No se pueden utilizar consteval y constexpr al mismo tiempo. |
Después viene, de forma también opcional, el modificador de comportamiento de excepciones:
Modificador | Descripción |
---|---|
noexcept noexcept(true) |
La función no lanza excepciones. |
noexcept(false) |
La función lanza excepciones. |
throw() throw(tipos) |
La función lanza excepciones. (Nota: deprecado en C++17 y eliminado en C++20.) |
A continuación está -> tipo
, para indicar el tipo de retorno de la función lambda, por si queremos hacerlo explícito en lugar de dejar al compilador inferirlo en base a las expresiones return
del cuerpo de la misma.
Las clases son la forma de definir tipos por parte del usuario, para expresar abstracciones y estructuras de datos más complejas de las ofrecidas por el lenguaje. Su sintaxis básica es la siguiente:
El orden de las secciones puede variar e incluso podemos tener varias secciones con el mismo tipo de visibilidad si fuera necesario. En caso de definir miembros fuera de una sección, se asume private
como visibilidad por defecto. Dependiendo de la visibilidad, los miembros de la clase serán accesibles o no desde fuera:
Visibilidad | public |
protected |
private |
---|---|---|---|
Dentro de la clase actual | Sí | Sí | Sí |
Dentro de las clases hijas | Sí | Sí | No |
Fuera de las anteriores | Sí | No | No |
El lenguaje C++ permite herencia múltiple entre tipos. Cuando una clase hereda de otra, puede incorporar definiciones a las que puede acceder y sobrescribir si fuera necesario. Con el modificador final
se impide que la clase pueda ser heredadas por otras clases. Para heredar de otras clases tenemos la lista de padres, que tiene la siguiente sintaxis:
Si no se indica la visibilidad del padre heredado, se asume por defecto que será private
, modificando así la visibilidad de los miembros heredados para restringirlos todavía más. En cuanto al modificador virtual
, marca la clase heredada como una clase base virtual, para evitar problemas con la herencia múltiple, cuando una clase superior es heredada múltiples veces por una descendiente. virtual
puede usarse también después de los modificadores de visibilidad. Por ejemplo:
// Fichero: multiple.cpp
#include<iostream>
class A {
public:
void hola () {
std::cout << "Hola\n";
}
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
int main () {
D objeto;
objeto.hola();
}
Los atributos de una clase son las variables definidas para almacenar la información. Para definir uno se usa la siguiente sintaxis:
Los modificadores aceptados para los atributos son:
Modificador | Descripción |
---|---|
mutable |
Permite que una variable no estática sea modificable al usarse desde una función constante. |
volatile |
Avisa la compilador que la variable se usa de forma volátil en código concurrente, para optimizar su uso. |
const |
La variable es una constante y no se puede modificar su valor. |
El modificador
mutable
no se puede usar con un tipo constante (const T
), pero sí con un puntero a un tipo constante (const T *
oconst T &
).
Para inicializar una variable, tenemos las siguientes formas:
Forma | Descripción |
---|---|
= expresión |
Se inicializa con el valor de la expresión, que también puede ser una lista de expresiones entre llaves. |
(expresiones) |
Lista de valores entre paréntesis. |
{expresiones} |
Lista de valores entre llaves. |
{miembros} |
Lista de valores asignados a los miembros del tipo, con la forma .miembro = expresión . Los miembros que se declaren en la lista, tienen que estar en el orden en el que han sido definidos en el tipo o no compilará el código. |
Los métodos de una clase son las funciones definidas para ejecutar las operaciones que implementa. Para definir uno se usa la siguiente sintaxis:
Si no implementamos el cuerpo de la función dentro de la clase, hay que hacerlo fuera con la siguiente sintaxis:
Hay que tener en cuenta que la firma, declarada dentro de la clase, tiene que ser exactamente la misma definida fuera. Igual que pasaba con las funciones que no pertenecen a una clase, tenemos el modificador noexcept
para indicar si el método lanza excepciones o no. Luego -> tipo
indica cual es el tipo a devolver al usar auto
como tipo de retorno en la firma de la función. En cuanto al resto de modificadores:
Modificador | Descripción |
---|---|
constexpr |
Pide al compilador que el miembro sea constante o ejecutable por el compilador. |
consteval |
Define y fuerza que la función sea ejecutable por el compilador. |
volatile |
La función puede ser usada por objetos volátiles y los parámetros de la función también serán tratados como variables volátiles. |
const |
La función es sólo de lectura, no puede modificar el contenido del objeto, salvo que el atributo esté definido como mutable. |
& |
La función es invocada cuando el objeto que la invoca es un valor normal (LVALUE). |
&& |
La función es invocada cuando el objeto que la invoca es un valor que ha sido movido (RVALUE). |
inline |
Se recomienda al compilador que inserte el código del cuerpo de la función en aquellos sitios donde es invocada. Cuando el cuerpo se define dentro de la clase, se asume este modificador como implícito. |
Veamos un ejemplo con un par de métodos:
// Fichero: metodos.cpp
#include<iostream>
class Foo {
public:
const int a = 1;
mutable int b = 2;
int c = 3;
int foo (int) const;
void bar () {
std::cout << a << ", " << b << ", "
<< ++c << "; " << foo(c) << "\n";
}
};
int Foo::foo(int a) const {
a *= this->a + (b++);
return a + a;
}
int main () {
Foo obj;
obj.bar(); // 1, 2, 4; 24
obj.bar(); // 1, 3, 5; 40
}
La expresión this
, usada en el ejemplo, es un puntero a la propia instancia que está llamando al método. Su uso puede ser útil para resolver conflictos, cuando un parámetro de una función tiene el mismo nombre que un miembro de la clase.
Cuando heredamos de una clase podemos querer que se sobrescriban algunos métodos, para cambiar el comportamiento de una determinada operación, aprovechando así los mecanismos de herencia y polimorfismo conjuntamente. Para ello tenemos que usar el modificador virtual
, al inicio de la firma de la función, en aquellas funciones que podrían ser rescritas en las clases hijas.
Definir qué métodos son virtuales es necesario para que el compilador las añada a la tabla de funciones virtuales (vtable) de la clase, que luego será apuntada por un puntero oculto (vptr) en las clases base, para saber cuáles son los métodos que tiene que llamar para la instancia actual.
Con el modificador = 0
, que va al final de la firma de la función, podemos indicar que es un método virtual abstracto. Esto quiere decir que el método no tiene implementación para esa clase, por lo que las clases hijas tendrán que encargarse para poder ser instanciadas.
Para rescribir un método virtual, a partir de C++11, se utilizan los modificadores override
y final
, que son situados al final de la firma de la función. Aunque el lenguaje permite combinaciones extrañas, lo aconsejable es usar cada uno suelto. Usaremos override
cuando simplemente queremos sobrescribir un método virtual en una clase hija. Pero usaremos final
si queremos sobrescribirlo e impedir que las clases hijas puedan sobrescribirlo.
Antes de C++11, para sobrescribir una función virtual se usaba el modificador
virtual
. Todavía se puede seguir usando el método en C++20, pero se recomienda usaroverride
yfinal
en su lugar.
Para comprender mejor lo anterior veamos un ejemplo:
// Fichero: herencia.cpp
#include <iostream>
class A {
public:
virtual void foo () = 0;
virtual void bar () {
std::cout << "A::bar()\n";
}
};
class B : public A {
public:
void foo () final {
std::cout << "B::foo()\n";
}
void bar () override = 0;
};
class C : public B {
public:
void bar () override {
std::cout << "C::bar()\n";
}
};
class D : public A {
public:
void foo () override {
std::cout << "D::foo()\n";
}
};
int main () {
A * c = new C();
c->foo(); // B::foo()
c->bar(); // C::bar()
A * d = new D();
d->foo(); // D::foo()
d->bar(); // A::bar()
delete c;
delete d;
}
Las clase A
tiene dos métodos virtuales, uno de ellos abstracto, por lo que es una clase abstracta y no puede ser instanciada. Después la clase B
hereda de A
y sobrescribe el método abstracto foo
, pero sobrescribe bar
para que sea abstracto, convirtiendo la clase también en abstracta. Entonces la clase C
hereda de B
, sobrescribiendo bar
para poder instanciarla. Por último tenemos la clase D
que hereda de A
y sobrescribe foo
para poder instanciarla.
Existe una serie de métodos especiales en los tipos, que definen cómo se construye una instancia, cómo se destruye o cómo se copia. La sintaxis de los constructores es:
Si no implementamos el cuerpo dentro de la clase, hay que hacerlo fuera con la siguiente sintaxis:
Un constructor puede tener una lista de parámetros, como las funciones normales. El constructor con cero parámetros se denomina constructor por defecto. También tenemos el caso especial del constructor copia (clase(const clase & obj)
), así como el constructor de movimiento (clase(clase && obj)
). El primero hace una copia de obj
para crear la nueva instancia, mientras que el segundo se queda con todos los recursos (punteros y referencias) para construir la nueva instancia.
Para forzar la invocación del constructor de movimiento, se puede utilizar la función
std::move
.
Además de los modificadores previamente vistos, con explicit
se añade como requisito que el tipo de los argumentos ha de corresponder con el tipo de los parámetros. Esto evita que se haga una conversión implícita para hacer encajar el valor en el tipo del parámetro. Luego tenemos =default
, que sólo se puede aplicar al constructor por defecto, el de copia y el de movimiento, que indica de forma explícita al compilador que genere la versión por defecto automáticamente. Mientras que =delete
, que se puede aplicar a todo tipo de constructor, indica al compilador que no se va a generar ningún código para dicho constructor.
Los inicializadores son una lista con la forma nombre(expresión)
o nombre{expresión}
, donde el nombre puede ser una variable interna o un tipo padre de la clase, que inicializa lo valores de la instancia que se está construyendo. Dependiendo de la expresión asignada, se usará un constructor u otro para inicializar cada elemento, por ello se usaba la notación con paréntesis, pero con C++11 se incorporó el uso de llaves para hacer lo mismo y unificar diferentes tipos de inicialización de objetos.
A continuación, la sintaxis de los destructores es:
Si no implementamos el cuerpo dentro de la clase, hay que hacerlo fuera con la siguiente sintaxis:
Por último, la sintaxis del operador de copia/movimiento es:
Si no implementamos el cuerpo dentro de la clase, hay que hacerlo fuera con la siguiente sintaxis:
Donde el parámetro puede ser clase obj
, const clase & obj
o clase && obj
, siendo este último para hacer el operador de movimiento.
Para hacer un operador de copia se suele usar como tipo
const clase &
, pero en ocasiones hay desarrolladores que utilizan el patrón copia-e-intercambio y por ello usan como tipoclase
. Este patrón hace un intercambio entre el objeto de origen y el de destino.
Por último veamos un ejemplo de lo anterior:
// Fichero: especial.cpp
#include <iostream>
class Foo {
public:
Foo() {
std::cout << "Foo()\n";
}
explicit Foo(int v) {
std::cout << "Foo(int) -> " << v << "\n";
}
Foo(const Foo & obj) {
std::cout << "Foo(const Foo &)\n";
}
Foo(Foo && obj) {
std::cout << "Foo(Foo &&)\n";
}
virtual ~Foo() {
std::cout << "~Foo()\n";
}
Foo & operator =(const Foo & obj) {
std::cout << "operator =(const Foo &)\n";
return *this;
}
Foo & operator =(Foo && obj) {
std::cout << "operator =(Foo &&)\n";
return *this;
}
};
int main () {
Foo a, b(42), c(a), d(std::move(c));
b = d;
a = std::move(b);
}
Los miembros estáticos no forman parte de los objetos instanciados de una clase, sino a la clase misma. Para acceder a ellos se usa la forma clase::miembro
, en lugar de acceder con los operadores .
o ->
.
Para declarar una variable estática se utiliza la siguiente sintaxis:
Para inicializar la variable estática, se debe hacer fuera de la clase con la siguiente sintaxis:
Para declarar o definir una función estática se utiliza la siguiente sintaxis:
Si no implementamos el cuerpo de la función estática dentro de la clase, hay que hacerlo fuera con la siguiente sintaxis:
Salvo por el modificador static
, el resto de modificadores son iguales que en los métodos de una clase. Veamos un ejemplo para hacernos una idea de todo lo anterior:
// Fichero: estatico.cpp
#include<iostream>
class Foo {
public:
static int a;
static int foo (int);
};
int Foo::a = 0;
int Foo::foo(int b) {
a += b;
return a;
}
int main () {
Foo::a = 10;
std::cout << Foo::a << "\n"; // 10
std::cout << Foo::foo(1) << "\n"; // 11
}
Las estructuras es una forma alternativa para definir tipos por parte del usuario. Su sintaxis básica es la siguiente:
Su existencia en el lenguaje se debe a una herencia del lenguaje C, pero en C++ actualmente funcionan exactamente como un tipo definido con class
, con la diferencia de que por defecto la visibilidad es pública, si no se indica ningún bloque de visibilidad.
Las uniones son un tipo de dato especial, que permite definir una abstracción que contenga diferentes tipos de información en el mismo espacio de memoria a lo largo del tiempo. Su sintaxis básica es la siguiente:
En cuanto a definiciones se permiten variables, funciones y tipos. Puede tener constructores y destructores, pero no puede tener métodos virtuales. También están limitados los tipos que se pueden usar para las variables, estos no pueden tener métodos especiales.
La utilidad de las uniones viene de los tiempos de C, cuando era necesario tener diferentes estructuras de datos almacenables desde un solo tipo, algo así como una jerarquía de clases solapadas en una sola clase. En memoria se reserva el espacio para el tipo mayor de toda la unión y se almacena la información dentro de dicho espacio, para interpretarla en base a la variable que se haga referencia a la hora de recuperar la información. Por ejemplo:
// Fichero: union.cpp
#include<iostream>
union Foo {
char a;
short b;
int c;
};
int main () {
Foo v;
v.c = 0x12345678;
std::cout << std::hex << v.c << "\n"; // 12345678
std::cout << std::hex << v.b << "\n"; // 5678
std::cout << std::hex << (int)v.a << "\n"; // 78
}
Las enumeraciones son tipos de datos que asocian valores con nombres identificadores, creando constantes de forma ordenada y agrupada. Su sintaxis básica es la siguiente:
Los valores deben ser expresiones constantes, incluyendo expresiones constexpr
. Se puede indicar un tipo básico numérico, para definir cuál es el tamaño de los valores en memoria. Luego las palabras claves class
y struct
sirven para crear un tipo enumerado estricto, de ese modo una variable de ese tipo sólo puede recibir valores definidos en él, teniendo que usar la sintaxis tipo::nombre
. Sin no se usa este mecanismo, basta con usar el nombre sin cualificar.
En ocasiones puede ser necesario declarar la existencia de un tipo enumerado sin incluir su definición, usando la siguiente sintaxis:
En caso de querer usar en un ámbito general, de tipo o de función, una enumeración estricta sin los nombres cualificados, se puede utilizar la siguiente sintaxis:
Esto incluye en el ámbito los nombres, para poder usarlos sin indicar el tipo. Por ejemplo:
// Fichero: enums.cpp
#include<iostream>
enum Bar { A = 10, B, C };
enum class Foo : int { A, B, C };
int main () {
Bar b = A;
std::cout << b << "\n";
{
std::cout << A << "\n";
std::cout << B << "\n";
std::cout << C << "\n";
}
Foo f = Foo::A;
std::cout << (int) f << "\n";
{
using enum Foo;
std::cout << (int) A << "\n";
std::cout << (int) B << "\n";
std::cout << (int) C << "\n";
}
}
Existen dos formas de crear alias de tipos. Por un lado la heredada de C con typedef
, que sigue la siguiente sintaxis:
Y por el otro la incorporada en C++11 con using
, que sigue la siguiente sintaxis:
La palabra clave friend
permite indicar al compilador que una definición externa, a un tipo, puede acceder al contenido privado del mismo. Podemos declarar como amigos de un tipo a funciones y tipos.
// Fichero: amigos.cpp
#include<iostream>
class Bar {
private:
int dato = 0;
public:
void mostrar() {
std::cout << dato << "\n";
}
friend class Foo;
};
class Foo {
public:
void cambiar (Bar & obj, int valor) {
obj.dato = valor;
}
};
int main () {
Foo a;
Bar b;
b.mostrar(); // 0
a.cambiar(b, 1);
b.mostrar(); // 1
}
Usando la palabra clave operator
seguido de un operador, o un tipo, se puede usar funciones para sobrecargar operadores del lenguaje. Las formas aceptadas son:
Forma | Firma Método | Firma Función | Operadores |
---|---|---|---|
@a |
R A::operator @() |
R operator @(A a) |
+ , - , ~ |
@a |
R & A::operator @() |
R & operator @(A & a) |
++ , -- |
a@ |
R A::operator @(int) |
R operator @(A & a, int) |
++ , -- |
a@b |
R A::operator @(B b) |
R operator @(A a, B b) |
+ , - , * , / , % , & , | , ^ , << , >> , , |
a@b |
bool A::operator @(const & B b) |
bool operator @(const & A a, const & B b) |
== , != , < , > , <= , >= |
a@b |
auto A::operator @(const & B b) |
auto operator @(const & A a, const & B b) |
<=> |
@a |
bool A::operator @() |
bool operator @(A a) |
! |
a@b |
bool A::operator @(B b) |
bool operator @(A a, B b) |
&& , || |
a@b |
R & A::operator @(B b) |
- | = |
a@b |
R & A::operator @(B b) |
R & operator @(A & a, B b) |
+= , -= , *= , /= , %= , &= , |= , ^= , >>= , <<= |
a(b, ..., z) |
R A::operator ()(B b, ..., Z z) |
- | () |
a[b] |
R & A::operator [](B b) |
- | [] |
a->b |
R * A::operator ->() |
- | -> |
a->*b |
R & A::operator ->*(B b) |
R & operator ->*(A a, B b) |
->* |
*a |
R & A::operator *() |
R & operator *(A a) |
* |
&a |
R * A::operator &() |
R * operator &(A a) |
& |
(T)a |
T A::operator T() |
- | (T) , static_cast<T>() |
new T |
void * A::operator new(size_t n) |
void * operator new(size_t n) |
new |
new T[n] |
void * A::operator new[](size_t m) |
void * operator new[](size_t m) |
new[] |
delete a |
void A::operator delete(void * a) |
void operator delete(void * a) |
delete |
delete[] a |
void A::operator delete[](void * a) |
void operator delete[](void * a) |
delete[] |
co_await a |
auto A::operator co_await() |
auto operator co_await(A a) |
co_await |
Dependiendo de si es un método de una clase o una función suelta, se podrán utilizar unos modificadores u otros. En cuanto a los tipos de los parámetros y del resultado, hay cierta flexibilidad para usar referencias y punteros, incluso se puede alterar el comportamiento esperado para un operador, como ocurre con <<
a la hora de canalizar flujos de salida de datos, en lugar de desplazar bits. Por ejemplo:
// Fichero: sobrecarga.cpp
#include<iostream>
struct Foo {
int X, Y;
};
std::ostream & operator <<(std::ostream & out, const Foo & obj) {
out << "(" << obj.X << "," << obj.Y << ")";
return out;
}
inline Foo operator +(const Foo & left, const Foo & right) {
return Foo{
.X = left.X + right.X,
.Y = left.Y + right.Y
};
}
int main () {
Foo v{2, 3}, w{4, 5};
std::cout << v << " + " << w << " = "
<< (v + w) << "\n";
}
Con C++11 se incorporó la posibilidad de definir literales de usuario, permitiendo así reinterpretar los literales del lenguaje en base a un sufijo designado. Para ello hay que usar la sintaxis:
En base a un tipo de retorno escogido, usaremos como sufijo una secuencia de letras que comience por guion bajo (_
), ya que las secuencias que comienzan sin guion bajo se reservan para la biblioteca estándar. En cuanto a los parámetros, están permitidas las siguientes firmas:
(const char *)
(unsigned long long int)
(long double)
(char)
(wchar_t)
(char8_t)
(char16_t)
(char32_t)
(const char *, std::size_t)
(const wchar_t *, std::size_t)
(const char8_t *, std::size_t)
(const char16_t *, std::size_t)
(const char32_t *, std::size_t)
Por ejemplo:
// Fichero: literales.cpp
#include <string>
#include <iostream>
struct Foo {
int X, Y;
};
std::ostream & operator <<(std::ostream & out, const Foo & obj) {
out << "(" << obj.X << "," << obj.Y << ")";
return out;
}
Foo operator ""_f(const char * v, size_t) {
std::string texto{v};
auto coma = texto.find(',');
return Foo{
.X = std::stoi(texto.substr(0, coma)),
.Y = std::stoi(texto.substr(coma + 1))
};
}
int main () {
std::cout << "2,3"_f << "\n";
}
La palabra clave inline
, permite indicar al compilador que debe intentar insertar el cuerpo de la misma en aquellos puntos donde es invocada. Es una recomendación para el compilador, que se usa como optimización en cuanto a la ejecución, pero como contrapartida aumenta el tamaño del programa compilado.
Esta palabra se usa como un modificador que va al inicio de la definición de una función, es decir, antes del tipo de retorno de la función. También se puede utilizar para funciones especiales como el constructor, el destructor o la sobrecarga de operadores. Por ejemplo:
// Fichero: inline.cpp
#include<iostream>
inline void foo (int a, int b) {
std::cout << a << " + " << b << " = " << a + b << "\n";
}
int main () {
foo(2 , 3);
}
Las funciones cuyo cuerpo es definido dentro de la declaración de un
class
,struct
ounion
, son tratadas como funcionesinline
por defecto, así como las funciones condelete
. También se asume implícitamente comoinline
aquellas funciones conconstexpr
.
La palabra clave constexpr
es un modificador para recomendar al compilador que ejecute la expresión siguiente en tiempo de compilación. Hay una serie de restricciones en el estándar para que efectivamente se pueda ejecutar la expresión con el compilador, pero en términos generales podemos usar literales o variables inicializadas con literales.
Se puede definir funciones como constexpr
, pero tiene todavía más limitaciones que los otros tipos de expresiones. Por ejemplo, no puede ser una función virtual, ni una corrutina, entre muchas otras limitaciones. En caso de necesitar forzar al compilador a ejecutarlo, usaremos la palabra clave consteval
, que hará fallar la compilación si no puede ejecutar. Hay que tener en cuenta que consteval
puede llegar a ser más estricto que constexpr
.
También, podemos ejecutar sentencias condicionales con if constexpr (condición)
, haciendo que se compile sólo uno de los dos bloques de la sentencia, dependiendo de si es cierta o falsa la condición. Con C++23 se incorpora consteval
a las sentencias condicionales.
Por último, tenemos la función especial static_assert
, que puede recibir hasta dos argumentos, el primero una expresión booleana y el segundo una cadena de texto literal. El compilador ejecuta la expresión booleana, para asegurarse de que se cumple la condición indicada; en caso contrario falla la compilación y nos muestra el mensaje de la cadena.
El preprocesado es la etapa inicial de la compilación, que realiza sustituciones de texto.
La directiva más básica es definir macros, que consiste en un identificador que se va a sustituir por un texto determinado. Para ello se usa la siguiente sintaxis:
El texto es opcional, porque podemos simplemente definir una macro para hacer comprobaciones condicionales, que se verán en la siguiente sección. Si queremos eliminar una macro usaremos la directiva:
Aunque no es recomendable, se pueden definir macros con parámetros con la directiva:
Los parámetros es una lista separada por comas de nombres. La elipsis ...
se utiliza para crear macros que tengan un número arbitrario de argumentos en sus usos. Para este último caso se utiliza las macros:
__VA_OPT__(texto)
: Que añade el texto indicado si la lista de argumentos arbitrario no está vacía.__VA_ARGS__
: Añade los argumentos arbitrarios separados por comas.Finalmente, tenemos un par de operadores para los parámetros de las macros. Usando el operador #
con un parámetro, este se convertirá en un literal de cadena de texto. Con el operador ##
tomará el parámetro y lo concatenará al texto al que esté a su izquierda.
Estas son las directivas condicionales del preprocesador:
Directiva | Descripción |
---|---|
#ifdef nombre |
Si está definida la macro nombre se procesa el bloque siguiente. |
#ifndef nombre |
Si no está definida la macro nombre se procesa el bloque siguiente. |
#if expresión |
Si es cierta la expresión se procesa el bloque siguiente. |
#elif expresión |
Si no son ciertas las condiciones anteriores, pero sí lo es la expresión se procesa el bloque siguiente. |
#else |
Si no son ciertas las condiciones anteriores se procesa el bloque siguiente. |
#endif |
Cierra el bloque de directivas condicionales. |
Para incluir ficheros dentro de otro, tenemos las siguientes directivas:
Directiva | Descripción |
---|---|
#include <fichero> |
Incluye un fichero que buscará en las rutas de librerías indicadas al compilar. |
#include "fichero" |
Incluye un fichero que buscará en el directorio actual. |
El fichero puede ser una ruta, cuando se use la forma con comillas.
Para organizar un proyecto, se divide en unidades de compilación el código, que son ficheros .cpp
. El método clásico, si queremos acceder a las definiciones de dichas unidades, consiste en definir una cabecera .h
o .hpp
, que contenga definiciones y firmas de funciones. Por ejemplo:
// Fichero: tools.hpp
#ifndef __TOOLS_H__
#define __TOOLS_H__
int inc (int);
#endif
// Fichero: tools.cpp
#include "tools.hpp"
int inc (int x) {
return x + 1;
}
// Fichero: main.cpp
#include<iostream>
#include<tools.hpp>
int main () {
std::cout << inc(1) << "\n";
}
Pero con C++20 se incluye la posibilidad de definir módulos, para facilitar la definición de unidades de compilación y su acceso. Para ello tenemos las expresiones export
e import
, la primera para exportar definiciones y la segunda para importar módulos o cabeceras.
Para crear un módulo primero hay que definirlo y a continuación definir qué se va a exportar con el módulo. La sintaxis para definirlo es la siguiente:
El identificador es una serie de nombres separados por :
, para construir un nombre cualificado para el módulo. La sintaxis para exportar definiciones en el módulo es la siguiente:
En caso de necesitar usar directivas del preprocesador, antes de declarar el módulo, hay que utilizar el módulo global con module;
, para utilizar las directivas y después ya declarar el módulo. Otro aspecto de la definición de módulos es module : private;
, que inicia la sección privada que no va a formar parte de la construcción del módulo por parte del compilador. Hay que entender que la parte pública del módulo sería una suerte de cabecera de la unidad de compilación, por lo que modificar cualquier cosa en ella, exportada o no, provoca que se recompilen todas las unidades de compilación que utilicen el módulo. Esto no ocurre con la parte privada del módulo.
Para importar módulos utilizamos la siguiente sintaxis:
El identificador puede ser un nombre cualificado de módulo, así como una cabecera usando <fichero>
o "fichero"
. Si se utiliza la palabra clave export
, al importar el módulo actual se importará también el módulo importado con export import
.
Para entenderlo mejor, veamos el anterior ejemplo como un módulo:
// Fichero: tools.cpp
export module tools;
export int inc (int);
module : private;
int inc (int x) {
return x + 1;
}
// Fichero: main.cpp
import <iostream>;
import tools;
int main () {
std::cout << inc(1) << "\n";
}
Las plantillas es la herramienta del lenguaje para poder hacer código genérico independiente de los tipos. La sintaxis básica para definir una plantilla es:
Como definiciones podemos tener tipos, funciones y variables. Los requisitos tienen que ver con los conceptos que veremos en una subsección más adelante. Los parámetros tenemos tres categorías: variables, variables de tipo y plantillas de variables de tipo.
La sintaxis para definir variables como parámetros de una plantilla es:
La primera forma es un tipo normal, con un nombre y un posible valor por defecto. La segunda forma es una variable que representa una lista de parámetros variable. La tercera forma usa auto
y se puede combinar con *
y &
, como modificadores, para deducir en tiempo de compilación el tipo de la variable de la plantilla.
La sintaxis para definir variables de tipo como parámetros de una plantilla es:
Después de indicar si es una variable de tipo general, con typename
o class
, que son sinónimos, o si es una restricción definida con conceptos, podremos darle un nombre y un valor por defecto al parámetro. Pero también podemos convertir ese parámetro en una lista de parámetros variable.
La sintaxis para definir plantillas de variables de tipo como parámetros de una plantilla es:
Es similar a las variables de tipo, con el añadido de estar parametrizado como una plantilla. Esto tiene su utilidad si queremos que uno de los parámetros sea una plantilla con una forma concreta, en lugar de cualquier tipo posible que representaría typename
.
Veamos un simple ejemplo para entenderlo mejor:
// Fichero: plantillas.cpp
#include<iostream>
template<typename T1, typename T2>
struct Tupla {
T1 primero;
T2 segundo;
};
template<typename T1, typename T2>
void mostrar (const Tupla<T1, T2> & valor) {
std::cout << "{ " << valor.primero
<< ", " << valor.segundo
<< " }\n";
}
int main () {
auto v1 = Tupla{1, true};
auto v2 = Tupla{3.14, "PI"};
mostrar(v1); // { 1, 1 }
mostrar(v2); // { 3.14, PI }
}
En el ejemplo hemos usado el compilador para que deduzca los tipos que se están usando a la hora de instanciar las plantillas Tupla
y mostrar
, pero podríamos haber usado la forma explícita también:
auto v1 = Tupla<int, bool>{1, true};
auto v2 = Tupla<double, const char *>{3.14, "PI"};
mostrar<int, bool>(v1);
mostrar<double, const char *>(v2);
Claramente ganamos legibilidad cuando el compilador deduce por nosotros los tipos de las instancias de las plantillas que usemos. Pero no siempre será posible, por lo que hay que tener en cuenta cómo se pueden instanciar de forma explícita.
También podemos forzar que se genere una instancia concreta de una plantilla, sobre todo cuando queremos crear una librería externa. En el caso de tipos se usa la siguiente sintaxis:
Sin extern
se define la instanciación y se genera el código, con extern
se declara la instanciación pero se asume que su código ha sido generado en otra unidad de compilación. En el caso de las funciones la sintaxis es:
Igual que pasa con los tipos podemos definir o declarar una instanciación. El resto es casi igual a declarar la firma de una función.
Podemos especializar una plantilla, es decir, crear un subtipo de la misma, indicando una parte o la totalidad de los tipos que conforman los parámetros de una. En estas plantillas especializadas podemos sobrecargar la definición previa de la plantilla, redefiniendo todo su contenido si fuera necesario. Este mecanismo permite la metaprogramación, al ejecutarse expresiones en tiempo de compilación.
Para especializar parcial o totalmente una plantilla, hay que pasar los argumentos entre <
y >
a la derecha del nombre de la plantilla, por ejemplo:
// Fichero: especializar.cpp
#include<iostream>
template<typename T>
void mostrar_valor (T valor) {
std::cout << valor;
}
template<>
void mostrar_valor<bool> (bool valor) {
std::cout << (valor ? "true" : "false");
}
template<>
void mostrar_valor<const char *> (const char * valor) {
std::cout << '"' << valor << '"';
}
template<typename T1, typename T2>
struct Tupla {
T1 primero;
T2 segundo;
void mostrar () {
std::cout << "{ ";
mostrar_valor(primero);
std::cout << ", ";
mostrar_valor(segundo);
std::cout << " }\n";
}
};
int main () {
auto v1 = Tupla{1, true};
auto v2 = Tupla{3.14, "PI"};
v1.mostrar(); // { 1, true }
v2.mostrar(); // { 3.14, "PI" }
}
En este ejemplo tenemos una función para mostrar valores, que hemos sobrecargado para booleanos y cadenas, especializando la plantilla mostrar_valor
. Otro ejemplo sería calcular con metaprogramación la secuencia de Fibonacci:
// Fichero: metafibonacci.cpp
#include <cstdint>
#include <iostream>
constexpr uint8_t MAX_VALUE = 46;
template<uint8_t n>
class MetaFibonacci {
private:
template<uint8_t i, uint64_t v0, uint64_t v1>
class fibonacci {
public:
enum {
Result = fibonacci<i - 1, v1, v0 + v1>::Result
};
};
template<uint64_t v0, uint64_t v1>
class fibonacci<0, v0, v1> {
public:
enum {
Result = v0
};
};
public:
enum {
Result = fibonacci<n, 0, 1>::Result
};
};
template<unsigned char n>
inline void print_numbers() {
print_numbers<n - 1>();
std::cout << ", ";
std::cout << MetaFibonacci<n>::Result;
}
template<>
inline void print_numbers<0>() {
std::cout << MetaFibonacci<0>::Result;
}
int main () {
std::cout << "Fibonacci: ";
print_numbers<MAX_VALUE>();
std::cout << "\n";
}
Aquí tenemos la plantilla fibonacci
, dentro de MetaFibonacci
, que tiene tres parámetros. A continuación tiene una especialización parcial, donde solo tiene dos parámetros y el primer argumento es el valor cero.
Podemos tener plantillas con un número de parámetros variable utilizando la notación ...
, que hemos visto en la sintaxis de los parámetros de las plantillas. La notación tipo... nombre
define una lista variable de variables, mientras que nombre...
expande esa lista de diferentes formas. Lo mismo se aplica a variables de tipo.
Si queremos saber cuál es el número de elementos de una lista variable, tenemos el operador sizeof...(nombre)
, donde nombre es una variable de datos o de tipos. Por ejemplo:
// Fichero: expansion.cpp
#include <iostream>
// Versión estándar de mostrar:
void mostrar () {}
template<typename T, typename... TS>
void mostrar (T value, TS... args) {
std::cout << value;
mostrar(args...);
}
// Versión alternativa de mostrar:
template<typename T, typename... TS>
void mostrarsi (T value, TS... args) {
std::cout << value;
if constexpr (sizeof...(TS) > 0) {
mostrar(args...);
}
}
int main () {
mostrar(1, ", ", 2, ", ", 3, ", ", 4, "\n");
mostrarsi(1.2, ", ", 3.4, "\n");
}
Podemos hacer una reducción aplicando alguno de los siguientes operadores binarios: +
, -
, *
, /
, %
, ^
, &
, |
, =
, <
, >
, <<
, >>
, +=
, -=
, *=
, /=
, %=
, ^=
, &=
, |=
, <<=
, >>=
, ==
, !=
, <=
, >=
, &&
, ||
, ,
, .*
, ->*
. Para ello es necesario utilizar alguna de las siguientes cuatro formas:
(nombre @ ...)
(... @ nombre)
(nombre @ ... @ inicial)
(inicial @ ... @ nombre)
Donde @
es un operador binario de la lista anterior, nombre
es el nombre de la variable de datos o de tipos, e inicial
es una expresión que representa el valor inicial para la reducción. Por ejemplo:
// Fichero: reducir.cpp
#include<iostream>
template<typename... TS>
auto sumar (TS... args) {
return (0 + ... + args);
}
int main () {
std::cout << sumar(1, 2, 3, 4) << "\n";
}
La función
std::forward
permite mover el contenido de una variable. Por ejemplo, si tenemos un parámetro definido comoT && ... args
y queremos que se mueva su contenido un constructor de forma eficiente, usaremos la formatipo(std::forward<T>(args)...)
. Se puede usar con otras funciones, para obligar a que se mueva el contenido de la variable usada como argumento, al parámetro de dicha función que mueve valores.
Se puede crear un alias de una plantilla con la siguiente sintaxis:
Basta con indicar la declaración del tipo cuyo alias queremos crear, en lugar de la definición completa. Es decir, basta con la firma o cabecera del tipo.
Se pueden tener plantillas de lambdas con la siguiente sintaxis:
El prácticamente la misma sintaxis y elementos que las lambdas normales, pero aquí podemos poner variables de tipo entre <
y >
, así como indicar los requisitos que ha de cumplir la función mediante conceptos.
Los conceptos son una forma de poder definir requisitos para las plantillas, de modo que definimos un concepto como una restricción que se han de cumplir, para que se pueda instanciar correctamente la plantilla. La sintaxis para definir un concepto es la siguiente:
En este caso la restricción puede ser una composición de los siguientes elementos: otros conceptos (no puede ser una relación recursiva), conjunciones con &&
, disyunciones con ||
, negaciones con !
y expresiones requires
. En el fondo estamos manejado predicados lógicos para formar una fórmula que de como resultado true
.
La expresión requires
, dentro de la definición de una restricción, sigue la siguiente sintaxis:
Los parámetros tienen la forma tipo nombre
, para usar dichos nombres dentro de los requisitos, para las comprobaciones realizadas en tiempo de compilación. Mientras que los requisitos los hay de cuatro categorías:
Categoría | Descripción |
---|---|
Simple | Expresiones del lenguaje que no empiezan por requires , que el compilador comprueba si es válida sin evaluarla. Por ejemplo: template<typename T> concept C = requires (T a, T b) { a + b; } |
Tipos | Expresiones que comienzan por typename , para comprobar que existe el tipo indicado. Por ejemplo: template<typename T> concept C = requires { typename T::Inner; } |
Compuestas | Que tienen la forma {expresión} noexcept -> tipo , donde noexcept es opcional, expresión es una expresión simple, y tipo es el tipo en el que ha de encajar la expresión (permite usar decltype((expresión)) ). |
Anidadas | Son expresiones que empiezan con requires . |
Además de lo anterior, el uso de los conceptos definidos puede darse en dos casos. El primero es usar un concepto como una restricción para una variable de tipo definida en los parámetros, y el segundo es usarlo en los requisitos de la plantilla. Para usar la cláusula de requisitos se usa la siguiente sintaxis:
Donde la restricción es una composición de conceptos, que ejercen como predicados, conjunciones con &&
, disyunciones con ||
y negaciones con !
. Por ejemplo:
// Fichero: conceptos.cpp
#include <vector>
#include <iostream>
template<typename P, typename T>
concept Predicado = requires (P p, T x) {
{p(x)} -> std::same_as<bool>;
};
template<typename T, typename P>
void mostrar (const T & datos, P predicado)
requires Predicado<P, typename T::value_type> {
for (auto item : datos) {
if (predicado(item)) {
std::cout << item << " ";
}
}
std::cout << "\n";
}
int main () {
std::vector datos = {1, 2, 3, 4, 5, 6, 7, 8};
mostrar(datos, [](int x) { return x % 2 == 0; });
}
Las corrutinas son funciones que permiten la ejecución de código asíncrono, sin detener por completo la ejecución de la función que las invoca. Para trabajar con corrutinas es necesario incluir la librería coroutine
y utilizar los siguientes operadores:
Operador | Descripción |
---|---|
co_await |
Suspende la ejecución de la rutina hasta que vuelve a ser activada. |
co_yield |
Suspende la ejecución de la rutina para devolver un valor. |
co_return |
Completa la ejecución de la rutina devolviendo un valor. |
Para que el compilador permita el uso de corrutinas, es necesario crear un tipo auxiliar que defina en su interior otro tipo llamado promise_type
, que tendrá una serie de operaciones definidas que dictarán el comportamiento de la corrutina definida:
Método | Descripción |
---|---|
get_return_object |
Este método es invocado cuando se tiene que instanciar una corrutina. |
initial_suspend |
Este método es invocado cuando se va pausar la corrutina con co_await . |
final_suspend |
Este método es invocado cuando se va pausar por última vez la corrutina en su finalización. |
unhandled_exception |
Este método es invocado cuando se produce una excepción en la corrutina. |
return_value |
Este método es invocado cuando se sale de la corrutina con co_return devolviendo un valor. |
return_void |
Este método es invocado cuando se sale de la corrutina con co_return sin devolver un valor. |
await_transform |
Este método es invocado cuando se obtiene el valor que está esperando co_await en la corrutina y se quiere transformar el objeto recibido de tipo awaitable. |
yield_value |
Este método es invocado cuando se devuelve un valor en la corrutina con co_yield . |
En el caso de initial_suspend
, final_suspend
, await_transform
y yield_value
, el tipo de retorno será una instancia de un tipo awaitable, como por ejemplo, std::suspend_always
o std::suspend_never
. El primero indica que la ejecución en efecto se ha de poner en pausa, mientras que el segundo indica lo contrario. Ambos casos no generan un valor de retorno con el operador, sino que se ha de gestionar aparte dicha obtención del valor generado. Por ejemplo:
// Fichero: corrutina.cpp
#include <iostream>
#include <coroutine>
template <typename T>
struct Tarea {
// Usamos el manejador base de la librería estándar:
struct promise_type;
using Manejador = std::coroutine_handle<promise_type>;
// Definimos el tipo promise_type que necesita el compilador:
struct promise_type {
// El último valor generado en la corrutina:
T valor;
// Método para instanciar una corrutina:
Tarea get_return_object () {
return Tarea(Manejador::from_promise(*this));
}
// Método para cuando se va pausar la corrutina con co_await:
std::suspend_always initial_suspend () {
return {};
}
// Método para cuando se va pausar por última vez la corrutina:
std::suspend_always final_suspend () noexcept {
return {};
}
// Método para cuando se produce una excepción en la corrutina:
void unhandled_exception () {}
// Método para cuando se sale de la corrutina con co_return:
template <std::convertible_to<T> From>
void return_value (From && from) {
valor = std::forward<From>(from);
}
};
// Definimos el tipo awaitable para poder usar co_await:
struct Esperador {
Manejador & manejador;
// Método para indicar que se tiene que pausar la ejecución
// al estar devolviendo false como resultado:
bool await_ready() noexcept {
return false;
}
// Método que recibe una corrutina externa y que después,
// de realizar las operaciones que sean necesarias, volverá
// a activar la ejecución de dicha corrutina:
void await_suspend(std::coroutine_handle<> externo) noexcept {
externo.resume();
}
// Método que se invoca cuando se va a reactivar la ejecución
// de la corrutina que se ha puesto en espera con co_await:
T await_resume() noexcept {
manejador.resume();
return manejador.promise().valor;
}
};
// Sobrecarga el operador co_await para supender
Esperador operator co_await () noexcept {
// Como el operador co_await está siendo usado con una tarea
// vamos a configurar el "esperador" con una referencia al
// manejador del tipo promesa de la corrutina:
return Esperador{manejador_};
}
// Devuelve el resultado generado por la tarea:
T resultado () {
manejador_();
return std::move(manejador_.promise().valor);
}
// Construye una tarea con un manejador recibido:
Tarea (Manejador h) : manejador_(h) {}
// Destruye la tarea eliminando los recursos del manejador:
~Tarea () {
manejador_.destroy();
}
private:
Manejador manejador_;
};
Tarea<std::uint64_t> factorial (unsigned x) {
if (x > 1) {
auto y = co_await factorial(x - 1);
co_return x * y;
} else {
co_return 1;
}
}
int main () {
auto tarea = factorial(10);
std::cout << tarea.resultado() << '\n';
}
En este ejemplo calculamos el factorial con una corrutina. Por un lado está la clase Tarea
, que contiene a promise_type
y un tipo awaitable llamado Esperador
, que es lo que necesita co_await
para trabajar. Lo ideal es hacer una clase genérica para gestionar tareas de todo tipo, ya que la complejidad que subyace a las corrutinas es considerable.
Otro tipo de corrutinas son las que generan valores con co_yield
, para crear una secuencia de valores que se van calculando de forma perezosa. Por ejemplo:
// Fichero: generador.cpp
#include <iostream>
#include <coroutine>
template <typename T>
struct Generador {
// Usamos el manejador base de la librería estándar:
struct promise_type;
using Manejador = std::coroutine_handle<promise_type>;
// Definimos el tipo promise_type que necesita el compilador:
struct promise_type {
// El último valor generado en la corrutina:
T valor;
// La última excepción lanzada en la corrutina:
std::exception_ptr error;
// Método para instanciar una corrutina:
Generador get_return_object () {
return Generador(Manejador::from_promise(*this));
}
// Método para cuando se va pausar la corrutina con co_await:
std::suspend_always initial_suspend () {
return {};
}
// Método para cuando se va pausar por última vez la corrutina:
std::suspend_always final_suspend () noexcept {
return {};
}
// Método para cuando se produce una excepción en la corrutina:
void unhandled_exception () {
error = std::current_exception();
}
// Método para cuando se sale de la corrutina con co_return:
void return_void () {}
// Método para cuando se devuelve un valor en la corrutina con co_yield:
template <std::convertible_to<T> From>
std::suspend_always yield_value (From && from) {
valor = std::forward<From>(from);
return {};
}
};
// Genera el siguiente elemento de la secuencia:
bool siguiente () {
// Genera el siguiente elemento de la corrutina:
manejador_();
// Comprueba si se ha generado alguna excepción:
if (manejador_.promise().error) {
std::rethrow_exception(manejador_.promise().error);
}
// Comprueba si se ha terminado de ejecutar la corrutina:
return !manejador_.done();
}
// Devuelve el último elemento generado en la secuencia:
T actual () {
return std::move(manejador_.promise().valor);
}
// Construye un generador con un manejador recibido:
Generador (Manejador h) : manejador_(h) {}
// Destruye el generador eliminando los recursos del manejador:
~Generador () {
manejador_.destroy();
}
private:
Manejador manejador_;
};
Generador<std::uint64_t> fibonacci (unsigned n) {
if (n > 94) {
throw std::runtime_error("Secuencia demasiado grande.");
} if (n == 0) {
co_return;
} else {
std::uint64_t a = 0, b = 1, aux;
for (unsigned i = 0; i < n; ++i) {
co_yield a;
aux = a + b;
a = b;
b = aux;
}
}
}
int main () {
try {
auto gen = fibonacci(20);
while (gen.siguiente()) {
std::cout << gen.actual() << ' ';
}
std::cout << '\n';
} catch (const std::exception & ex) {
std::cerr << "Error: " << ex.what() << '\n';
} catch (...) {
std::cerr << "Error desconocido...\n";
}
}
Para trabajar con cadenas tenemos, en el módulo string
, la clase genérica std::basic_string
. Este tipo trabaja con cadenas mutables y tenemos las siguientes especializaciones:
Tipo | Definición | Codificación |
---|---|---|
std::string |
std::basic_string<char> |
ASCI |
std::wstring |
std::basic_string<wchar_t> |
Unicode |
std::u8string |
std::basic_string<char8_t> |
UTF-8 |
std::u16string |
std::basic_string<char16_t> |
UTF-16 |
std::u32string |
std::basic_string<char32_t> |
UTF-32 |
En el módulo string_view
, tenemos la clase genérica std::basic_string_view
, que nos permite tener una vista inmutable a una cadena o un fragmento de la misma. Para ello tenemos las siguientes especializaciones:
Tipo | Definición | Codificación |
---|---|---|
std::string_view |
std::basic_string_view<char> |
ASCI |
std::wstring_view |
std::basic_string_view<wchar_t> |
Unicode |
std::u8string_view |
std::basic_string_view<char8_t> |
UTF-8 |
std::u16string_view |
std::basic_string_view<char16_t> |
UTF-16 |
std::u32string_view |
std::basic_string_view<char32_t> |
UTF-32 |
Los miembros principales que disponemos para trabajar con cadenas son:
Categoría | Tipos | Miembro | Descripción |
---|---|---|---|
General | basic_string basic_string_view |
operator= |
Operador de asignación. |
General | basic_string |
assign |
Asigna caracteres a una cadena. |
Iterador | basic_string basic_string_view |
begin cbegin |
Iterador que apunta al inicio del recorrido. |
Iterador | basic_string basic_string_view |
end cend |
Iterador que apunta al final del recorrido. |
Iterador | basic_string basic_string_view |
rbegin crbegin |
Iterador que apunta al inicio del recorrido inverso. |
Iterador | basic_string basic_string_view |
rend crend |
Iterador que apunta al final del recorrido inverso. |
Acceso | basic_string basic_string_view |
at |
Accede a un carácter cualquiera, con comprobación de límites. |
Acceso | basic_string basic_string_view |
operator[] |
Accede a un carácter cualquiera. |
Acceso | basic_string basic_string_view |
front |
Accede al primer carácter. |
Acceso | basic_string basic_string_view |
back |
Accede al último carácter. |
Acceso | basic_string basic_string_view |
data |
Accede al puntero al primer carácter. |
Acceso | basic_string |
c_str |
Devuelve una versión inmutable de la cadena en estilo C. |
Acceso | basic_string |
operator basic_string_view |
Devuelve una vista inmutable de la cadena. |
Capacidad | basic_string basic_string_view |
empty |
Comprueba si la cadena está vacía. |
Capacidad | basic_string basic_string_view |
size length |
Devuelve el número de caracteres. |
Capacidad | basic_string basic_string_view |
max_size |
Devuelve el tamaño máximo para cualquier cadena. |
Capacidad | basic_string |
capacity |
Devuelve el número máximo de caracteres actualmente reservado. |
Capacidad | basic_string |
reserve |
Reserva espacio en la memoria. |
Capacidad | basic_string |
shrink_to_fit |
Reduce el espacio reservado al tamaño actual de la cadena. |
Operaciones | basic_string |
append |
Añade caracteres al final. |
Operaciones | basic_string |
operator+= |
Añade caracteres al final. |
Operaciones | basic_string |
clear |
Borra el contenido de la cadena. |
Operaciones | basic_string |
insert |
Inserta caracteres. |
Operaciones | basic_string |
erase |
Borra caracteres. |
Operaciones | basic_string |
push_back |
Añade un carácter al final. |
Operaciones | basic_string |
pop_back |
Elimina un carácter al final. |
Operaciones | basic_string basic_string_view |
compare |
Compara dos cadenas. |
Operaciones | basic_string basic_string_view |
starts_with |
Comprueba si la cadena empieza con un valor dado. |
Operaciones | basic_string basic_string_view |
ends_with |
Comprueba si la cadena termina con un valor dado. |
Operaciones | basic_string basic_string_view |
substr |
Devuelve un fragmento de la cadena. |
Operaciones | basic_string basic_string_view |
copy |
Copia caracteres. |
Operaciones | basic_string |
replace |
Sustituye una sección de la cadena. |
Operaciones | basic_string |
resize |
Modifica el espacio reservado en la memoria para la cadena. |
Operaciones | basic_string |
swap |
Intercambia el contenido de dos cadenas. |
Búsqueda | basic_string basic_string_view |
find |
Busca la primera ocurrencia de un valor dado. |
Búsqueda | basic_string basic_string_view |
rfind |
Busca la última ocurrencia de un valor dado. |
Búsqueda | basic_string basic_string_view |
find_first_of |
Busca la primera ocurrencia de un carácter dado. |
Búsqueda | basic_string basic_string_view |
find_first_not_of |
Busca la primera ocurrencia que no sea el carácter dado. |
Búsqueda | basic_string basic_string_view |
find_last_of |
Busca la última ocurrencia de un carácter dado. |
Búsqueda | basic_string basic_string_view |
find_last_not_of |
Busca la última ocurrencia que no sea el carácter dado. |
Constantes | basic_string basic_string_view |
npos |
Valor especial para definir que no se ha encontrado un valor, entre otros usos. |
Además tenemos las siguientes funciones dentro del espacio de nombres std
:
Categoría | Tipos | Función | Descripción |
---|---|---|---|
General | basic_string |
operator+ |
Concatena dos cadenas. |
General | basic_string basic_string_view |
operator== operator!= operator< operator> operator<= operator>= operator<=> |
Compara dos cadenas. |
General | basic_string |
erase erase_if |
Elimina caracteres dentro de la cadena. |
General | basic_string |
swap |
Intercambia el contenido de dos cadenas. |
E/S | basic_string |
operator>> |
Entrada de cadenas por consola. |
E/S | basic_string basic_string_view |
operator<< |
Salida de cadenas por consola. |
E/S | basic_string |
getline |
Lee una línea de la consola para almacenarla en una cadena. |
Conversiones | basic_string |
stoi stol stoll |
Convierte una cadena a un entero con signo. |
Conversiones | basic_string |
stoul stoull |
Convierte una cadena a un entero sin signo. |
Conversiones | basic_string |
stof stod stold |
Convierte una cadena a un número de coma flotante. |
Conversiones | basic_string |
to_string to_wstring |
Convierte números a una cadena. |
Literales | basic_string |
operator""s |
Convierte un literal a cadena. |
Literales | basic_string_view |
operator""sv |
Convierte un literal a una vista. |
Útiles | basic_string basic_string_view |
hash<T> |
Devuelve el código hash para un valor. |
Además de lo anterior tenemos una serie de funciones heredadas de la biblioteca estándar C, para comprobar y transformar, tanto caracteres como cadenas de estilo C.
Para manejar expresiones regulares, en el módulo regex
, tenemos la clase genérica std::basic_regex
, que tiene como especializaciones a std::regex
, para cadenas ASCII, y a std::wregex
, para cadenas Unicode.
Estos son los contenedores principales de la biblioteca estándar:
Tipo | Módulo | Descripción |
---|---|---|
std::array<T,N> |
array |
Array de tamaño fijo. |
std::vector<T> |
vector |
Array de tamaño dinámico. |
std::deque<T> |
deque |
Cola doblemente encabezada. |
std::list<T> |
list |
Lista doblemente enlazada. |
std::forward_list<T> |
forward_list |
Lista simplemente enlazada. |
std::map<K,T> |
map |
Diccionario clave-valor, sin repetición de claves. |
std::multimap<K,T> |
map |
Diccionario clave-valor, con repetición de claves. |
std::unordered_map<K,T> |
unordered_map |
Diccionario clave-valor, sin repetición de claves y organizadas mediante valores hash. |
std::unordered_multimap<K,T> |
unordered_map |
Diccionario clave-valor, con repetición de claves y organizadas mediante valores hash. |
std::set<T> |
set |
Conjunto de valores sin repetición. |
std::multiset<T> |
set |
Conjunto de valores con repetición. |
std::unordered_set<T> |
unordered_set |
Conjunto de valores sin repetición y organizados mediante valores hash. |
std::unordered_multiset<T> |
unordered_set |
Conjunto de valores con repetición y organizados mediante valores hash. |
std::stack<T> |
stack |
Pila de elementos (LIFO). |
std::queue<T> |
queue |
Cola de elementos (FIFO). |
std::priority_queue<T> |
queue |
Cola de elementos con prioridades. |
std::span<T> |
span |
Vista mutable de un contenedor. |
Donde T
es el tipo de los elementos del contenedor, N
es un entero con el tamaño del contenedor y K
es el tipo de las claves del contenedor. Entonces, los miembros principales que disponemos para trabajar con contenedores son:
Categoría | Tipos | Miembro | Descripción |
---|---|---|---|
General | array , vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , stack , queue , priority_queue , span |
operator= |
Operador de asignación. |
General | vector , deque , list , forward_list |
assign |
Asigna elementos a un contenedor. |
Iterador | array , vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , span |
begin cbegin |
Iterador que apunta al inicio del recorrido. |
Iterador | array , vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , span |
end cend |
Iterador que apunta al final del recorrido. |
Iterador | array , vector , deque , list , map , multimap , set , multiset , span |
rbegin crbegin |
Iterador que apunta al inicio del recorrido inverso. |
Iterador | array , vector , deque , list , map , multimap , set , multiset , span |
rend crend |
Iterador que apunta al final del recorrido inverso. |
Iterador | forward_list |
before_begin cbefore_begin |
Iterador que apunta al elemento anterior al inicio del recorrido. |
Acceso | array , vector , deque , map , unordered_map |
at |
Accede a un elemento cualquiera, con comprobación de límites. |
Acceso | array , vector , deque , map , unordered_map , span |
operator[] |
Accede a un elemento cualquiera. |
Acceso | array , vector , deque , list , forward_list , queue , span |
front |
Accede al primer elemento. |
Acceso | array , vector , deque , list , queue , span |
back |
Accede al último elemento. |
Acceso | stack , priority_queue |
top |
Accede al elemento en la cima. |
Acceso | array , vector , span |
data |
Accede al puntero al primer elemento. |
Capacidad | array , vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , stack , queue , priority_queue , span |
empty |
Comprueba si el contenedor está vacío. |
Capacidad | array , vector , deque , list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , stack , queue , priority_queue , span |
size |
Devuelve el número de elementos. |
Capacidad | array , vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
max_size |
Devuelve el tamaño máximo para cualquier contenedor. |
Capacidad | vector |
capacity |
Devuelve el número máximo de elementos actualmente reservado. |
Capacidad | vector , unordered_map , unordered_multimap , unordered_set , unordered_multiset |
reserve |
Reserva espacio en la memoria. |
Capacidad | vector , deque |
shrink_to_fit |
Reduce el espacio reservado al tamaño actual del contenedor. |
Capacidad | span |
size_bytes |
Devuelve el tamaño de la secuencia en bytes. |
Operaciones | vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
clear |
Borra el contenido del contenedor. |
Operaciones | vector , deque , list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
insert |
Inserta elementos. |
Operaciones | vector , deque , list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
erase |
Borra elementos. |
Operaciones | stack , queue , priority_queue |
push |
Añade un elemento al contenedor. |
Operaciones | stack , queue , priority_queue |
pop |
Elimina un elemento del contenedor. |
Operaciones | vector , deque , list |
push_back |
Añade un elemento al final. |
Operaciones | vector , deque , list |
pop_back |
Elimina un elemento al final. |
Operaciones | deque , list , forward_list |
push_front |
Añade un elemento al inicio. |
Operaciones | deque , list , forward_list |
pop_front |
Elimina un elemento al inicio. |
Operaciones | vector , deque , list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , stack , queue , priority_queue |
emplace |
Añade elementos en una posición dada. |
Operaciones | vector , deque , list |
emplace_back |
Añade elementos al final del contenedor. |
Operaciones | deque , list , forward_list |
emplace_front |
Añade elementos al inicio del contenedor. |
Operaciones | forward_list |
insert_after |
Inserta elementos después de una posición dada. |
Operaciones | forward_list |
erase_after |
Borra elementos después de una posición dada. |
Operaciones | forward_list |
emplace_after |
Añade elementos después de una posición dada. |
Operaciones | map , unordered_map |
insert_or_assign |
Inserta un elemento o actualiza el valor si la clave ya existe. |
Operaciones | map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
emplace_hint |
Añade elementos en una posición dada. |
Operaciones | map , unordered_map |
try_emplace |
Añade elementos en una posición dada si la clave no existe. |
Operaciones | array |
fill |
Rellena el contenedor con un valor dado. |
Operaciones | vector , deque , list , forward_list |
resize |
Modifica el espacio reservado en la memoria para el contenedor. |
Operaciones | array , vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , stack , queue , priority_queue |
swap |
Intercambia el contenido de dos contenedores. |
Operaciones | list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
merge |
Fusiona dos contenedores ordenados previamente. |
Operaciones | list |
splice |
Mueve elementos desde otro contenedor. |
Operaciones | forward_list |
splice_after |
Mueve elementos desde otro contenedor. |
Operaciones | list , forward_list |
remove remove_if |
Elimina elementos dentro del contenedor. |
Operaciones | list , forward_list |
reverse |
Invierte el orden de los elementos del contendor. |
Operaciones | list , forward_list |
unique |
Elimina duplicados consecutivos del contenedor. |
Operaciones | list , forward_list |
sort |
Ordena los elementos del contenedor. |
Operaciones | map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
extract |
Extrae elementos de otro contenedor. |
Búsqueda | map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
count |
Cuenta el número de apariciones de un elemento dentro del contenedor. |
Búsqueda | map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
find |
Busca un elemento dentro del contenedor. |
Búsqueda | map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
contains |
Comprueba si existe un elemento dentro del contenedor. |
Búsqueda | map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
equal_range |
Devuelve un rango de elementos que encaje con una clave. |
Búsqueda | map , multimap , set , multiset |
lower_bound |
Devuelve un iterador al primer elemento mayor o igual que una clave. |
Búsqueda | map , multimap , set , multiset |
upper_bound |
Devuelve un iterador al primer elemento mayor que una clave. |
Selección | span |
first |
Obtiene una selección de los primeros N elementos. |
Selección | span |
last |
Obtiene una selección de los últimos N elementos. |
Selección | span |
subspan |
Obtiene una selección de N elementos. |
El tipo
span
no dispone de los métodoscbegin
,cend
,crbegin
ycrend
, para crear iteradores constantes.
Además tenemos las siguientes funciones dentro del espacio de nombres std
:
Categoría | Tipos | Función | Descripción |
---|---|---|---|
General | array , vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , stack , queue |
operator== operator!= |
Compara dos contenedores. |
General | array , vector , deque , list , forward_list , map , multimap , set , multiset , stack , queue |
operator< operator> operator<= operator>= operator<=> |
Compara dos contenedores. |
General | vector , deque , list , forward_list , multiset |
erase |
Elimina elementos dentro del contenedor. |
General | vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset |
erase_if |
Elimina elementos dentro del contenedor. |
General | array , vector , deque , list , forward_list , map , multimap , unordered_map , unordered_multimap , set , multiset , unordered_set , unordered_multiset , stack , queue , priority_queue |
swap |
Intercambia el contenido de dos contenedores. |
General | array |
get<N> |
Accede a un elemento de un contenedor de tamaño fijo. |
Útiles | array |
tuple_element<T> |
Devuelve el tipo de los elementos del contenedor. |
Útiles | array |
tuple_size<T> |
Devuelve el tamaño del contenedor. |
Estos son algunos de los tipos y funciones de la biblioteca estándar, en el módulo iterator
, para manejar iteradores:
Tipo | Elemento | Descripción |
---|---|---|
Función | std::ranges::iter_move |
Mueve el contenido de un iterador a otro. |
Función | std::ranges::iter_swap |
Intercambia el contenido entre dos iteradores. |
Clase | std::reverse_iterator<T> |
Recorre una secuencia en orden inverso. |
Función | std::make_reverse_iterator<T> |
Crea un reverse_iterator infiriendo el tipo con el argumento. |
Clase | std::move_iterator<T> |
Recorre una secuencia moviendo su contenido. |
Función | std::make_move_iterator<T> |
Crea un move_iterator infiriendo el tipo con el argumento. |
Clase | std::back_insert_iterator<T> |
Iterador para insertar al final de un contenedor. |
Función | std::back_inserter<T> |
Crea un back_insert_iterator infiriendo el tipo con el argumento. |
Clase | std::front_insert_iterator<T> |
Iterador para insertar al inicio de un contenedor. |
Función | std::front_inserter<T> |
Crea un front_insert_iterator infiriendo el tipo con el argumento. |
Clase | std::insert_iterator<T> |
Iterador para insertar en un contenedor. |
Función | std::inserter<T> |
Crea un insert_iterator infiriendo el tipo con el argumento. |
Función | std::advance<T,U> |
Avanza un iterador N posiciones. |
Función | std::distance<T> |
Devuelve la distancia entre el inicio y el final del recorrido. |
Función | std::next<T> |
Avanza un iterador una posición. |
Función | std::prev<T> |
Retrocede un iterador una posición. |
Función | std::begin<T> std::cbegin<T> |
Iterador que apunta al inicio del recorrido. |
Función | std::end<T> std::cend<T> |
Iterador que apunta al final del recorrido. |
Función | std::rbegin<T> std::crbegin<T> |
Iterador que apunta al inicio del recorrido inverso. |
Función | std::rend<T> std::crend<T> |
Iterador que apunta al final del recorrido inverso. |
Función | std::size<T> std::ssize<T> |
Devuelve el número de elementos de la secuencia. |
Función | std::empty<T> |
Comprueba si la secuencia está vacía. |
Función | std::data<T> |
Accede al puntero al primer elemento de la secuencia. |
Con C++20 se incorporó el concepto de los rangos, como un mecanismo más genérico para definir secuencias iterables. Para ello tenemos los espacios de nombres std::ranges
y std::views
. Estos son algunos de los tipos y funciones de la biblioteca estándar, en el módulo iterator
y ranges
, para manejar rangos:
Categoría | Elemento | Descripción |
---|---|---|
Operaciones | std::ranges::advance |
Avanza un iterador N posiciones. |
Operaciones | std::ranges::distance |
Devuelve la distancia entre el inicio y el final del recorrido. |
Operaciones | std::ranges::next |
Avanza un iterador una posición. |
Operaciones | std::ranges::prev |
Retrocede un iterador una posición. |
Acceso | std::ranges::begin std::ranges::cbegin |
Iterador que apunta al inicio del recorrido. |
Acceso | std::ranges::end std::ranges::cend |
Iterador que apunta al final del recorrido. |
Acceso | std::ranges::rbegin std::ranges::crbegin |
Iterador que apunta al inicio del recorrido inverso. |
Acceso | std::ranges::rend std::ranges::crend |
Iterador que apunta al final del recorrido inverso. |
Acceso | std::ranges::size std::ranges::ssize |
Devuelve el número de elementos de la secuencia. |
Acceso | std::ranges::empty |
Comprueba si la secuencia está vacía. |
Acceso | std::ranges::data std::ranges::cdata |
Accede al puntero al primer elemento de la secuencia. |
Vistas | std::ranges::view_interface |
Clase que representa una vista a una secuencia. |
Vistas | std::ranges::subrange |
Clase que representa una vista a una secuencia, mediante un rango compuesto por un par iterador-limite. |
Factoría | std::ranges::empty_view std::views::empty |
Una vista sin elementos. |
Factoría | std::ranges::single_view std::views::single |
Una vista con un solo elemento. |
Factoría | std::ranges::iota_view std::views::iota |
Una vista compuesta por un valor inicial que se va incrementando con cada paso. |
Factoría | std::ranges::basic_istream_view std::views::istream |
Una vista que está asociada a un flujo de entrada al que aplica el operador >> a cada paso. |
Adaptador | std::views::all_t std::views::all |
Una vista que incluye todos los elementos de un rango. |
Adaptador | std::ranges::ref_view |
Una vista de los elementos de otro rango. |
Adaptador | std::ranges::owning_view |
Una vista con la propiedad única de los elementos de un rango. |
Adaptador | std::ranges::filter_view std::views::filter |
Una vista con los elementos de una secuencia que satisfacen un predicado. |
Adaptador | std::ranges::transform_view std::views::transform |
Una vista que aplica una función de transformación a los elementos de una secuencia. |
Adaptador | std::ranges::take_view std::views::take |
Una vista que toma los N primeros elementos de otra secuencia. |
Adaptador | std::ranges::take_while_view std::views::take_while |
Una vista que toma los N primeros elementos de otra secuencia mientras se cumpla un predicado. |
Adaptador | std::ranges::drop_view std::views::drop |
Una vista que descarta los N primeros elementos de otra secuencia. |
Adaptador | std::ranges::drop_while_view std::views::drop_while |
Una vista que descarta los N primeros elementos de otra secuencia mientras se cumpla un predicado. |
Adaptador | std::ranges::join_view std::views::join |
Una vista que aplana una vista de secuencias. |
Adaptador | std::ranges::split_view std::views::split |
Una vista obtenida de dividir otra vista usando un delimitador. |
Adaptador | std::ranges::lazy_split_view std::views::lazy_split |
Una vista obtenida de dividir otra vista usando un delimitador. |
Adaptador | std::views::counted |
Crea un rango con un iterador y un tamaño. |
Adaptador | std::ranges::common_view std::views::common |
Convierte una vista en un objeto de tipo common_range . |
Adaptador | std::ranges::reverse_view std::views::reverse |
Una vista que recorre otra secuencia en sentido inverso. |
Adaptador | std::ranges::elements_view std::views::elements |
Una vista que selecciona un campo determinado de las tuplas de una secuencia. |
Adaptador | std::ranges::keys_view std::views::keys |
Una vista que selecciona el primer campo de una tupla clave-valor. |
Adaptador | std::ranges::values_view std::views::values |
Una vista que selecciona el segundo campo de una tupla clave-valor. |
Los adaptadores se pueden combinar utilizando el operador
|
. Por ejemplo, siv
es una secuencia de números,v | std::views::take(3) | std::views::reverse
devolverá una vista que recorrerá al revés los primeros tres elementos de la secuencia.
La biblioteca estándar dispone de una serie de algoritmos genéricos, en el módulo algorithm
, para trabajar con contenedores, iteradores y rangos. El primera tabla son funciones que no modifican la secuencia:
Función | Función (Rangos) | Descripción |
---|---|---|
std::all_of |
std::ranges::all_of |
Comprueba si se cumple un predicado para todos los elementos de una secuencia. |
std::any_of |
std::ranges::any_of |
Comprueba si se cumple un predicado para algún elemento de una secuencia. |
std::none_of |
std::ranges::none_of |
Comprueba si se cumple un predicado para ningún elemento de una secuencia. |
std::for_each |
std::ranges::for_each |
Aplica una función a cada elemento de una secuencia. |
std::for_each_n |
std::ranges::for_each_n |
Aplica una función a los N primeros elementos de una secuencia. |
std::count std::count_if |
std::ranges::count std::ranges::count_if |
Obtiene el número de elementos de una secuencia que cumple un criterio específico. |
std::mismatch |
std::ranges::mismatch |
Encuentra la primera posición donde dos secuencias difieren. |
std::find std::find_if std::find_if_not |
std::ranges::find std::ranges::find_if std::ranges::find_if_not |
Busca el primer elemento de una secuencia que cumpla un criterio específico. |
std::find_end |
std::ranges::find_end |
Busca en una secuencia de elementos la última posición de una determinada secuencia dada. |
std::find_first_of |
std::ranges::find_first_of |
Busca un elemento cualquier dentro de un conjunto dado. |
std::adjacent_find |
std::ranges::adjacent_find |
Busca los dos primeros elementos adyacentes que son iguales o que cumplen un predicado dado. |
std::search |
std::ranges::search |
Busca una secuencia de elementos. |
std::search_n |
std::ranges::search_n |
Busca un número consecutivo de copias de un elemento en una secuencia. |
La segunda tabla son funciones que modifican la secuencia:
Función | Función (Rangos) | Descripción |
---|---|---|
std::copy std::copy_if |
std::ranges::copy std::ranges::copy_if |
Copia una secuencia de elementos a una nueva localización. |
std::copy_n |
std::ranges::copy_n |
Copia un número de elementos a una nueva localización. |
std::copy_backward |
std::ranges::copy_backward |
Copia una secuencia de elementos en sentido inverso a una nueva localización. |
std::move |
std::ranges::move |
Mueve una secuencia de elementos a una nueva localización. |
std::move_backward |
std::ranges::move_backward |
Mueve una secuencia de elementos en sentido inverso a una nueva localización. |
std::fill |
std::ranges::fill |
Copia y asigna un valor a los elementos de una secuencia. |
std::fill_n |
std::ranges::fill_n |
Copia y asigna un valor a un número de elementos en una secuencia. |
std::transform |
std::ranges::transform |
Aplica una función a una secuencia de elementos y almacena el resultado en una nueva localización. |
std::generate |
std::ranges::generate |
Asigna el resultado de llamar sucesivas veces a una función a los elementos de una secuencia. |
std::generate_n |
std::ranges::generate_n |
Asigna el resultado de llamar sucesivas veces a una función a un número de elementos en una secuencia. |
std::remove std::remove_if |
std::ranges::remove std::ranges::remove_if |
Elimina elementos de una secuencia que cumplan un criterio específico. |
std::remove_copy std::remove_copy_if |
std::ranges::remove_copy std::ranges::remove_copy_if |
Copia los elementos de una secuencia, omitiendo aquellos que cumplan un criterio específico. |
std::replace std::replace_if |
std::ranges::replace std::ranges::replace_if |
Sustituye con un valor dado aquellos elementos de una secuencia que cumplan un criterio específico. |
std::replace_copy std::replace_copy_if |
std::ranges::replace_copy std::ranges::replace_copy_if |
Copia los elementos de una secuencia, sustituyendo con un valor dado aquellos que cumplan un criterio específico. |
std::swap |
- | Intercambia los valores de dos objetos. |
std::swap_ranges |
std::ranges::swap_ranges |
Intercambia los valores de dos secuencias. |
std::iter_swap |
- | Intercambia los valores apuntados por dos iteradores. |
std::reverse |
std::ranges::reverse |
Invierte el orden de una secuencia de elementos. |
std::reverse_copy |
std::ranges::reverse_copy |
Crea una copia con el orden invertido de una secuencia de elementos. |
std::rotate |
std::ranges::rotate |
Rota el orden de una secuencia de elementos. |
std::rotate_copy |
std::ranges::rotate_copy |
Crea una copia con el orden de los elementos de una secuencia rotados. |
std::shift_left std::shift_right |
- | Rota el valor de los elementos de una secuencia. |
std::shuffle |
std::ranges::shuffle |
Reordena aleatoriamente los elementos de una secuencia. |
std::sample |
std::ranges::sample |
Selecciona N elementos aleatoriamente de una secuencia. |
std::unique |
std::ranges::unique |
Elimina los duplicados consecutivos en una secuencia de elementos. |
std::unique_copy |
std::ranges::unique_copy |
Crea una copia de una secuencia de elementos donde se elimina los duplicados consecutivos. |
La tercera tabla son funciones que particionan una secuencia:
Función | Función (Rangos) | Descripción |
---|---|---|
std::is_partitioned |
std::ranges::is_partitioned |
Comprueba si una secuencia está particionada en base a un predicado dado. |
std::partition |
std::ranges::partition |
Divide una secuencia en dos grupos de elementos. |
std::partition_copy |
std::ranges::partition_copy |
Copia la división de una secuencia en dos grupos de elementos. |
std::stable_partition |
std::ranges::stable_partition |
Divide una secuencia en dos grupos de elementos, manteniendo el orden relativo de los mismos entre sí. |
std::partition_point |
std::ranges::partition_point |
Localiza el punto de partición en una secuencia particionada. |
La cuarta tabla son funciones que ordenan una secuencia:
Función | Función (Rangos) | Descripción |
---|---|---|
std::is_sorted |
std::ranges::is_sorted |
Comprueba si una secuencia está ordenada en sentido ascendente. |
std::is_sorted_until |
std::ranges::is_sorted_until |
Busca la mayor subsecuencia ordenada en una secuencia de elementos. |
std::sort |
std::ranges::sort |
Ordena en sentido ascendente una secuencia de elementos. |
std::partial_sort |
std::ranges::partial_sort |
Ordena los N primeros elementos de una secuencia. |
std::partial_sort_copy |
std::ranges::partial_sort_copy |
Copia la ordenación de los N primeros elementos de una secuencia. |
std::stable_sort |
std::ranges::stable_sort |
Ordena una secuencia de elementos, manteniendo el orden previo entre valores iguales. |
std::nth_element |
std::ranges::nth_element |
Ordena de forma parcial una secuencia, asegurando que está particionada por un elemento dado. |
La quinta tabla son funciones que trabajan con una secuencia ordenada:
Categoría | Función | Función (Rangos) | Descripción |
---|---|---|---|
Búsqueda | std::lower_bound |
std::ranges::lower_bound |
Devuelve un iterador al primer elemento no menor que un valor dado. |
Búsqueda | std::upper_bound |
std::ranges::upper_bound |
Devuelve un iterador al primer elemento mayor que un valor dado. |
Búsqueda | std::binary_search |
std::ranges::binary_search |
Comprueba si un elemento existe en una secuencia parcialmente ordenada. |
Búsqueda | std::equal_range |
std::ranges::equal_range |
Devuelve una secuencia de elementos que encaja con una clave específica. |
Mezcla | std::merge |
std::ranges::merge |
Fusiona dos secuencias de elementos ordenados. |
Mezcla | std::inplace_merge |
std::ranges::inplace_merge |
Fusiona dos secuencias de elementos ordenados dentro de una misma secuencia. |
Conjunto | std::includes |
std::ranges::includes |
Comprueba si una secuencia es subconjunto de otra. |
Conjunto | std::set_difference |
std::ranges::set_difference |
Calcula la diferencia entre dos conjuntos. |
Conjunto | std::set_intersection |
std::ranges::set_intersection |
Calcula la intersección entre dos conjuntos. |
Conjunto | std::set_symmetric_difference |
std::ranges::set_symmetric_difference |
Calcula la diferencia simétrica entre dos conjuntos. |
Conjunto | std::set_union |
std::ranges::set_union |
Calcula la unión entre dos conjuntos. |
La sexta tabla son funciones que trabajan con un montículo:
Función | Función (Rangos) | Descripción |
---|---|---|
std::is_heap |
std::ranges::is_heap |
Comprueba si una secuencia es un montículo de máximos. |
std::is_heap_until |
std::ranges::is_heap_until |
Busca la mayor subsecuencia ordenada como un montículo en una secuencia de elementos. |
std::make_heap |
std::ranges::make_heap |
Crea un montículo de máximos con una secuencia de elementos. |
std::push_heap |
std::ranges::push_heap |
Añade un elemento a un montículo de máximos. |
std::pop_heap |
std::ranges::pop_heap |
Saca un elemento de un montículo de máximos. |
std::sort_heap |
std::ranges::sort_heap |
Ordena un montículo de máximos como una secuencia de elementos en sentido ascendente. |
La séptima tabla son funciones para encontrar el mínimo o el máximo en una secuencia:
Función | Función (Rangos) | Descripción |
---|---|---|
std::max |
std::ranges::max |
Obtiene mayor elemento entre los valores dados. |
std::max_element |
std::ranges::max_element |
Obtiene el elemento máximo de una secuencia. |
std::min |
std::ranges::min |
Obtiene menor elemento entre los valores dados. |
std::min_element |
std::ranges::min_element |
Obtiene el elemento mínimo de una secuencia. |
std::minmax |
std::ranges::minmax |
Obtiene el mayor y el menor elemento entre los valores dados. |
std::minmax_element |
std::ranges::minmax_element |
Obtiene el elemento máximo y mínimo de una secuencia. |
std::clamp |
std::ranges::clamp |
Restringe un valor entre un par de valores límites dados. |
La octava tabla son funciones que comparan secuencias:
Función | Función (Rangos) | Descripción |
---|---|---|
std::equal |
std::ranges::equal |
Comprueba si una secuencia es igual a otra. |
std::lexicographical_compare |
std::ranges::lexicographical_compare |
Comprueba si una secuencia es menor lexicográficamente que otra. |
std::lexicographical_compare_three_way |
- | Comprueba una secuencia con otra con el operador de comparación <=> . |
La novena tabla son funciones que permutan una secuencia:
Función | Función (Rangos) | Descripción |
---|---|---|
std::is_permutation |
std::ranges::is_permutation |
Comprueba si una secuencia es una permutación de otra. |
std::next_permutation |
std::ranges::next_permutation |
Obtiene la siguiente permutación lexicográfica de una secuencia de elementos. |
std::prev_permutation |
std::ranges::prev_permutation |
Obtiene la anterior permutación lexicográfica de una secuencia de elementos. |
La décima tabla son funciones numéricas sobre una secuencia, del módulo numeric
:
Función | Descripción |
---|---|
std::iota |
Rellena una secuencia con incrementos sucesivos de un valor inicial. |
std::accumulate |
Suma o reduce una secuencia de elementos. |
std::inner_product |
Calcula el producto interior de dos secuencias de elementos. |
std::adjacent_difference |
Calcula la diferencia entre elementos adyacentes en una secuencia. |
std::partial_sum |
Calcula la suma parcial de una secuencia de elementos. |
std::reduce |
Reduce una secuencia de elementos sin un orden concreto. |
std::exclusive_scan |
Similar a partial_sum , excluyendo el último elemento de la secuencia. |
std::inclusive_scan |
Similar a partial_sum , incluyendo el último elemento de la secuencia. |
std::transform_reduce |
Aplica una función a una secuencia y después la reduce. |
std::transform_exclusive_scan |
Aplica una función a una secuencia y después la operación exclusive_scan . |
std::transform_inclusive_scan |
Aplica una función a una secuencia y después la operación inclusive_scan . |
Por defecto los algoritmos son secuenciales en su ejecución, pero se puede como primer parámetro indicar una política de ejecución en algunos de ellos. Las opciones son:
seq
: Ejecución secuencial.par
: Ejecución paralela si está disponible.par_unseq
: Ejecución paralela y/o vectorizada si está disponible.unseq
: Ejecución vectorizada si está disponible.
Estos son los tipos principales de la biblioteca estándar para manejar la entrada y salida:
Tipo | Módulo | char |
wchar_t |
Descripción |
---|---|---|---|---|
std::ios_base |
ios |
Gestiona los flags para formatear la entrada y salida, así como las excepciones. | ||
std::basic_ios<T> |
ios |
std::ios |
std::wios |
Gestiona flujos de datos arbitrarios. |
std::basic_ostream<T> |
ostream |
std::ostream |
std::wostream |
Encapsula un dispositivo abstracto para operaciones de salida. |
std::basic_istream<T> |
istream |
std::istream |
std::wistream |
Encapsula un dispositivo abstracto para operaciones de entrada. |
std::basic_iostream<T> |
istream |
std::iostream |
std::wiostream |
Encapsula un dispositivo abstracto para operaciones de entrada y salida. |
std::basic_ostringstream<T> |
sstream |
std::ostringstream |
std::wostringstream |
Encapsula un dispositivo de cadenas para operaciones de salida. |
std::basic_istringstream<T> |
sstream |
std::istringstream |
std::wistringstream |
Encapsula un dispositivo de cadenas para operaciones de entrada. |
std::basic_stringstream<T> |
sstream |
std::stringstream |
std::wstringstream |
Encapsula un dispositivo de cadenas para operaciones de entrada y salida. |
std::basic_ofstream<T> |
fstream |
std::ofstream |
std::wofstream |
Encapsula un fichero para operaciones de salida. |
std::basic_ifstream<T> |
fstream |
std::ifstream |
std::wifstream |
Encapsula un fichero para operaciones de entrada. |
std::basic_fstream<T> |
fstream |
std::fstream |
std::wfstream |
Encapsula un fichero para operaciones de entrada y salida. |
Los miembros principales que disponemos para trabajar con ios_base
son:
Categoría | Miembro | Descripción |
---|---|---|
General | operator= |
Operador de asignación. |
Formato | flags |
Modifica los flags de formato. |
Formato | setf |
Activa flags de formato. |
Formato | unsetf |
Desactiva flags de formato. |
Formato | precision |
Configura la precisión de la parte decimal en números reales. |
Formato | width |
Configura el ancho de columna. |
Localización | imbue |
Cambia la configuración para la localización. |
Localización | getloc |
Obtiene la configuración para la localización. |
También como parte de ios_base
tenemos los siguientes tipos y valores definidos, a los que se puede acceder usando std::ios
o std::wios
:
Tipo | Descripción | Valores |
---|---|---|
openmode |
Modo de apertura del flujo. | app : Se sitúa al final del flujo después de cada escritura.binary : Abre el flujo en modo binario.in : Abre el flujo para lectura.out : Abre el flujo para escritura.trunc : Borra el contenido del flujo al abrirlo.ate : Se sitúa al final del flujo después de abrirlo. |
fmtflags |
Flags de formato. | dec : Usa la notación decimal para números enteros.oct : Usa la notación octal para números enteros.hex : Usa la notación hexadecimal para números enteros.basefield : dec | oct | hex .left : Ajuste a la izquierda.right : Ajuste a la derecha.internal : Ajuste interno.adjustfield : left | right | internal .scientific : Muestra tipos de coma flotante con notación científica.fixed : Muestra tipos de coma flotante con notación fija.floatfield : scientific | fixed .boolalpha : Muestra los valores booleanos de forma alfabética.showbase : Muestra un prefijo para indicar la base de un número entero.showpoint : Muestra siempre el separador decimal con números reales.showpos : Muestra el signo + para números positivos.skipws : Descarta los espacios en blanco iniciales con operaciones de entrada.unitbuf : Procesa el buffer de salida con cada operación de salida.uppercase : Salida en mayúsculas. |
iostate |
Estado del flujo. | goodbit : Sin errores.badbit : Error de flujo irrecuperable.failbit : Operación de E/S fallida.eofbit : La entrada ha alcanzado el final de fichero. |
seekdir |
Posición de partida para desplazarse. | beg : Inicio del flujo.end : Final del flujo.cur : Posición actual del flujo. |
Los miembros principales que disponemos para trabajar con basic_ios
son:
Categoría | Miembro | Descripción |
---|---|---|
General | operator= |
Operador de asignación. |
Estado | good |
Comprueba que no ha ocurrido ningún error de E/S. |
Estado | eof |
Comprueba si se ha llegado al final de fichero. |
Estado | fail |
Comprueba que si ha ocurrido algún error de E/S. |
Estado | bad |
Comprueba que si ha ocurrido algún error irrecuperable de E/S. |
Estado | operator! |
Comprueba que si ha ocurrido algún error de E/S. |
Estado | operator bool |
Comprueba que no ha ocurrido ningún error de E/S. |
Estado | rdstate |
Devuelve los flags de estado del flujo. |
Estado | setstate |
Modifica los flags de estado del flujo. |
Estado | clear |
Borra los flags de estado del flujo. |
Formato | copyfmt |
Copia la información de formato. |
Formato | fill |
Configura el carácter de relleno. |
Varios | exceptions |
Gestiona la máscara de excepciones. |
Varios | imbue |
Cambia la configuración para la localización. |
Varios | rdbuf |
Gestiona el buffer asociado al flujo. |
Varios | tie |
Gestiona el flujo enlazado. |
Varios | narrow |
Convierte un carácter a su equivalente estándar. |
Varios | widen |
Convierte un carácter a su equivalente local. |
Los miembros principales que disponemos para trabajar con basic_ostream
son:
Categoría | Miembro | Descripción |
---|---|---|
General | operator= |
Operador de asignación. |
Salida | operator<< |
Envía información con formato a la salida. |
Salida | put |
Envía un carácter a la salida. |
Salida | write |
Envía una cadena de texto a la salida. |
Posición | tellp |
Devuelve la posición actual en el flujo. |
Posición | seekp |
Modifica la posición actual en el flujo. |
Varios | flush |
Sincroniza el flujo con el dispositivo subyacente. |
Varios | swap |
Intercambio de flujos entre objetos. |
Los miembros principales que disponemos para trabajar con basic_istream
son:
Categoría | Miembro | Descripción |
---|---|---|
General | operator= |
Operador de asignación. |
Salida | operator>> |
Obtiene información con formato de la entrada. |
Salida | get |
Obtiene un carácter de la entrada. |
Salida | peek |
Obtiene un carácter de la entrada sin eliminarlo del buffer. |
Salida | unget |
Recupera de vuelta el último carácter extraído del buffer de entrada, para volver a añadirlo al inicio del mismo. |
Salida | putback |
Añade un carácter al inicio del buffer de entrada. |
Salida | getline |
Obtiene una línea de texto de la entrada. |
Salida | ignore |
Descarta caracteres de la entrada hasta encontrar el valor dado como argumento. |
Salida | read |
Obtiene un bloque de tamaño fijo de texto de la entrada. |
Salida | readsome |
Obtiene un bloque de texto con lo que hay en el buffer de entrada. |
Salida | gcount |
Obtiene el número de caracteres obtenido por la última operación de lectura sin formato. |
Posición | tellp |
Devuelve la posición actual en el flujo. |
Posición | seekp |
Modifica la posición actual en el flujo. |
Varios | sync |
Sincroniza el flujo con el dispositivo subyacente. |
Varios | swap |
Intercambio de flujos entre objetos. |
Los miembros principales que disponemos para trabajar con basic_fstream
son:
Categoría | Miembro | Descripción |
---|---|---|
General | operator= |
Operador de asignación. |
General | swap |
Intercambio de flujos entre objetos. |
Operaciones | is_open |
Comprueba si el flujo tiene un fichero abierto. |
Operaciones | open |
Abre un fichero y lo asocia al flujo. |
Operaciones | close |
Cierra el fichero asociado al flujo. |
Los miembros principales que disponemos para trabajar con basic_stringstream
son:
Categoría | Miembro | Descripción |
---|---|---|
General | operator= |
Operador de asignación. |
General | swap |
Intercambio de flujos entre objetos. |
Operaciones | str |
Modifica u obtiene la cadena actual del flujo. |
Operaciones | view |
Obtiene una vista a la cadena actual del flujo. |
Para trabajar con la consola tenemos, en el módulo iostream
, los objetos std::cout
para la salida estándar, std::cin
para la entrada estándar, std::cerr
para la salida de errores estándar. Estos objetos trabajan con ASCII, usando el tipo char
, pero para Unicode tenemos std::wcout
, std::wcin
y std::wcerr
, usando el tipo wchar_t
.
Para enviar un mensaje a la pantalla se utiliza el operador <<
con std::cout
o std::cerr
, ya que son de tipo std::ostream
. Mientras que para obtener un valor se usa >>
con std::cin
, ya que es de tipo std:istream
.
Para una correcta localización, a la hora de mostrar texto por la consola, podemos usar la función setlocale
de C, pero en C++ existe el tipo std::locale
, en el módulo locale
. Por ejemplo:
#include<iostream>
int main () {
std::locale::global(std::locale("es_ES"));
std::cout << "¡Hola España!\n";
std::locale::global(std::locale("C"));
std::cout << "¡Adios España!\n";
}
Con la función estática global
, podemos pasarle una configuración determinada para la localización, que para ello construimos un objeto std::locale
indicando con una cadena el lenguaje o configuración a usar. Por temas históricos, la configuración C
es la utilizada por defecto en C, pero si queremos usar texto en español podemos usar valores como es_ES
, es
o spanish
, entre otras.
Por defecto, la biblioteca estándar de C++, tiene activada la compatibilidad con la E/S de C. Si no se necesita las funciones del estándar de C para la E/S, se puede desactivar la compatibilidad con la función
sync_with_stdio
con la sentenciastd::ios_base::sync_with_stdio(false);
.
Además tenemos las siguientes funciones dentro del espacio de nombres std
, que se pueden usar con los operadores de entrada >>
y salida <<
, para dar formato a dichas operaciones:
Módulo | Función | Descripción |
---|---|---|
ios |
boolalpha noboolalpha |
Activa o desactiva la representación alfabética de los booleanos. |
ios |
showbase noshowbase |
Activa o desactiva el uso del prefijo que indica la base numérica. |
ios |
showpoint noshowpoint |
Activa o desactiva si se debe mostrar siempre el punto decimal en números reales. |
ios |
showpos noshowpos |
Activa o desactiva si se debe mostrar siempre el signo positivo en números. |
ios |
skipws noskipws |
Activa o desactiva si se deben descartar lo espacios en blanco iniciales con la entrada. |
ios |
uppercase nouppercase |
Activa o desactiva si el texto ha de estar sólo en mayúsculas. |
ios |
unitbuf nounitbuf |
Activa o desactiva si se debe procesar el buffer de salida con cada operación de salida. |
ios |
internal left right |
Configura la alineación del texto en la columna actual. |
ios |
dec hex oct |
Configura la base numérica a usar para los enteros. |
ios |
fixed scientific hexfloat defaultfloat |
Configura el formato usado para los números reales. |
istream |
ws |
Descarta los espacios en blanco iniciales de la entrada. |
ostream |
ends |
Inserta el carácter nulo ('\0' ) en un flujo de salida. |
ostream |
flush |
Limpia el buffer de salida procesando su contenido. |
ostream |
endl |
Inserta un salto de línea ('\n' ) y limpia el buffer de salida procesando su contenido. |
ostream |
emit_on_flush noemit_on_flush |
Controla cuándo se emite un basic_syncbuf al limpiar el buffer. |
ostream |
flush_emit |
Limpia el buffer y emite su contenido si está usando un basic_syncbuf . |
iomanip |
resetiosflags |
Limpia los ios_base flags indicados. |
iomanip |
setiosflags |
Activa los ios_base flags indicados. |
iomanip |
setbase |
Modifica los ios_base flags indicados. |
iomanip |
setfill |
Modifica el carácter de relleno. |
iomanip |
setprecision |
Modifica la precisión de la parte decimal de los números reales. |
iomanip |
setw |
Modifica el ancho de columna para las operaciones de E/S. |
iomanip |
get_money |
Obtiene un valor de tipo monetario. |
iomanip |
put_money |
Envía un valor de tipo monetario. |
iomanip |
get_time |
Obtiene un valor de tipo fecha o tiempo. |
iomanip |
put_time |
Envía un valor de tipo fecha o tiempo. |
iomanip |
quoted |
Inserta y extrae cadenas con comillas con espacios embebidos. |
Estos son los tipos principales de la biblioteca estándar, dentro del módulo filesystem
, para gestionar el sistema de ficheros:
Tipo | Descripción |
---|---|
std::path |
Clase para representar rutas en el sistema de ficheros. |
std::filesystem_error |
Clase para representar errores en el sistema de ficheros. |
std::directory_entry |
Clase para representar un elemento en un directorio. |
std::directory_iterator |
Clase para iterar sobre el contenido de un directorio. |
std::recursive_directory_iterator |
Clase para iterar sobre el contenido de un directorio y sus subdirectorios. |
std::file_status |
Clase para representar el tipo y los permisos de un fichero. |
std::space_info |
Clase para obtener información sobre el espacio disponible en un sistema de ficheros. |
std::file_type |
Enumeración para . |
std::perms |
Enumeración para . |
std::perm_options |
Enumeración para . |
std::copy_options |
Enumeración para . |
std::directory_options |
Enumeración para . |
std::file_time_type |
Tipo para representar marcas de tiempo para ficheros. |
Además, tenemos las siguientes funciones dentro del espacio de nombres std
, para realizar operaciones sobre el sistema de ficheros:
Función | Descripción |
---|---|
absolute |
Crea una ruta absoluta. |
canonical weakly_canonical |
Crea una ruta canónica. |
relative proximate |
Crea una ruta relativa. |
copy |
Copia ficheros o directorios. |
copy_file |
Copia el contenido de ficheros. |
copy_symlink |
Copia un enlaces simbólicos. |
create_directory create_directories |
Crea directorios nuevos. |
create_hard_link |
Crea enlaces duros. |
create_symlink create_directory_symlink |
Crea enlaces simbólicos. |
current_path |
Obtiene o modifica el directorio de trabajo actual. |
exists |
Comprueba si una ruta existe o no. |
equivalent |
Comprueba si dos rutas hacen referencia al mismo elemento. |
file_size |
Obtiene el tamaño de un fichero. |
hard_link_count |
Obtiene el número de enlaces duros asociados a un fichero. |
last_write_time |
Obtiene o modifica la marca de tiempo de la última modificación. |
permissions |
Modifica los permisos de acceso a un fichero. |
read_symlink |
Obtiene el destino de un enlace simbólico. |
remove remove_all |
El primero elimina un fichero o directorio vacío. El segundo además elimina el contenido de un directorio recursivamente. |
rename |
Mueve o renombra un fichero o directorio. |
resize_file |
Cambia el tamaño de un fichero, ya sea truncando el contenido o rellenando con ceros. |
space |
Obtiene el espacio libre disponible en el sistema de ficheros. |
status symlink_status |
Determina los atributos de un fichero. |
temp_directory_path |
Obtiene un directorio para ficheros temporales. |
A las anteriores, también se le suman las siguientes funciones para comprobar propiedades de los ficheros:
Función | Descripción |
---|---|
is_block_file |
Comprueba si la ruta es un dispositivo de bloque. |
is_character_file |
Comprueba si la ruta es un dispositivo de caracteres. |
is_directory |
Comprueba si la ruta es un directorio. |
is_empty |
Comprueba si la ruta es un fichero o directorio vacío. |
is_fifo |
Comprueba si la ruta es una tubería con nombre. |
is_other |
Comprueba si la ruta es otro tipo de fichero. |
is_regular_file |
Comprueba si la ruta es un fichero normal. |
is_socket |
Comprueba si la ruta es un socket IPC con nombre. |
is_symlink |
Comprueba si la ruta es un enlace simbólico. |
status_known |
Comprueba si el estado para un fichero es conocido. |
Existen muchos tipos y funciones para la gestión dinámica de la memoria en la biblioteca estándar, pero nos vamos a centrar en los punteros inteligentes. Hay dos tipos genéricos para crear punteros inteligentes y uno auxiliar:
Tipo | Descripción |
---|---|
std::unique_ptr<T> |
Puntero con semánticas de propiedad única. |
std::shared_ptr<T> |
Puntero con semánticas de propiedad compartida. |
std::weak_ptr<T> |
Referencia débil a un objeto gestionado por shared_ptr . |
Un unique_ptr
no comparte la propiedad del puntero, por lo que no se puede copiar su contenido, mientras que un shared_ptr
sí se puede compartir, porque lleva la cuenta del número de referencias que hay al objeto apuntado. Es el caso de shared_ptr
el que se asemejaría más a los punteros inteligentes de C# o Python, pero aquí no hay recolector de basura, por lo que cuando se llega a cero en el contador de referencias, se elimina de la memoria el objeto apuntado.
Para facilitar la construcción de instancias, usando punteros inteligentes, se utilizan las funciones std::make_unique<T>
y std::make_shared<T>
, que por debajo implementan la reserva de memoria con el operador new
, invocando un constructor del tipo T
en base a los argumentos pasados.
También son muy importantes, para la gestión de memoria, las funciones std::move
y std::forward
. La primera cambia la semántica del argumento de copiar a mover su valor. La segunda, dependiendo del tipo, elige entre dar al argumento la semántica de copiar o mover, para tener referencias a objetos que no queremos que su contenido se mueva.
Dentro de la biblioteca estándar tenemos los siguientes tipos como utilidades varias:
Módulo | Tipo | Descripción |
---|---|---|
functional |
std::hash<T> |
Calcula un valor hash para un objeto de un tipo T . |
bitset |
std::bitset<N> |
Representa una secuencia de tamaño fijo de N bits. |
utility |
std::pair<T1,T2> |
Representa una tupla de dos componentes. |
tuple |
std::tuple<...> |
Representa una tupla de N componentes. |
variant |
std::variant<...> |
Representa una unión de tipos, para almacenar un objeto de alguno de dichos tipos. Se utiliza como sustituto de los tipos union . |
optional |
std::optional<T> |
Representa un valor opcional, que puede contener o no la instancia actual. Es un concepto prestado de la programación funcional y las mónadas. |
any |
std::any |
Representa una unión de cualquier tipo, para almacenar cualquier tipo de objeto. |
typeinfo |
std::type_info |
Contiene información sobre la implementación de un tipo. El operador typeid(T) devuelve este tipo de objetos si está activado el RTTI al compilar. |
Para trabajar con el tiempo, en el módulo chrono
, tenemos los siguientes tipos:
Tipo | Descripción |
---|---|
std::system_clock |
Reloj en tiempo real del sistema (wall clock). |
std::steady_clock |
Reloj monótono que nunca va a ser ajustado. |
std::high_resolution_clock |
Reloj con la mayor precisión disponible. |
std::is_clock std::is_clock_v |
Comprueba si un tipo es un reloj. |
std::utc_clock |
Reloj de tipo UTC (Coordinated Universal Time). |
std::tai_clock |
Reloj de tipo TAI (International Atomic Time). |
std::gps_clock |
Reloj para GPS. |
std::file_clock |
Reloj usado para marcas de tiempo de ficheros. |
std::local_t |
Pseudo-reloj que representa la hora local. |
std::time_point |
Representa una marca en el tiempo. |
std::clock_cast |
Convierte marcas de tiempo de un reloj a otro. |
En el módulo functional
, tenemos herramientas para trabajar con funciones. El tipo principal es std::function
, que representa una función del programa como valor, permitiendo que las funciones sean ciudadanos de primera clase como en los lenguajes de programación funcional. También hay tipos que encapsulan operadores matemáticos como funciones, para poder usarlos en algoritmos de la biblioteca estándar en lugar de crear una lambda para ello.
En el módulo format
, tenemos herramientas para dar formato a cadenas. La función principal es std::format
, pero no usa el formato de cadenas de C, en su lugar usa la sintaxis:
El formato depende de las especializaciones que tengamos del tipo genérico std::formatter
. Este mecanismo para dar formato a cadenas, es una alternativa a tipos como stringstream
.
Por último, en el módulo type_traits
, podemos encontrar un buen repertorio de tipos y funciones para trabajar con metaprogramación. Mediante plantillas podemos ejecutar operaciones varias en tiempo de compilación, usando el paradigma de la programación funcional con una sintaxis no del todo amable.
Dentro de la biblioteca estándar tenemos diferentes herramientas matemáticas. La primera de ellas son las funciones comunes, que están en su mayoría en el módulo cmath
:
Categoría | Miembro | Descripción |
---|---|---|
Básicas | abs fabs |
Valor absoluto. |
Básicas | fmod |
Resto de una división. |
Básicas | remainder |
Resto con signo de una división. |
Básicas | remquo |
Resto con signo de una división y los tres últimos bits del resultado de la división. |
Básicas | fma |
fma(x,y,z) equivale a x * y + z . |
Básicas | fmax |
El mayor de dos números. |
Básicas | fmin |
El menor de dos números. |
Básicas | fdim |
Diferencia positiva de dos números: fmax(0,x-y) . |
Básicas | nan |
Devuelve el valor NaN. |
Interpolación | lerp |
Calcula la función de interpolación lineal. |
Exponencial | exp |
Calcula el valor de ex. |
Exponencial | exp2 |
Calcula el valor de 2x. |
Exponencial | expm1 |
Calcula el valor de ex-1. |
Exponencial | log |
Calcula el valor de ln x. |
Exponencial | log10 |
Calcula el valor de log10 x. |
Exponencial | log2 |
Calcula el valor de log2 x. |
Exponencial | log1p |
Calcula el valor de ln(1+x). |
Potencias | pow |
Calcula el valor de xy. |
Potencias | sqrt |
Calcula la raíz cuadrada de x. |
Potencias | cbrt |
Calcula la raíz cúbica de x. |
Potencias | hypot |
Calcula la hipotenusa. |
Trigonometría | sin |
Calcula el seno en radianes. |
Trigonometría | cos |
Calcula el coseno en radianes. |
Trigonometría | tan |
Calcula la tangente en radianes. |
Trigonometría | asin |
Calcula el arcoseno en radianes. |
Trigonometría | acos |
Calcula el arcocoseno en radianes. |
Trigonometría | atan |
Calcula la arcotangente en radianes. |
Trigonometría | atan2 |
Calcula la arcotangente de y/x en radianes. |
Hiperbólicas | sinh |
Calcula el seno hiperbólico en radianes. |
Hiperbólicas | cosh |
Calcula el coseno hiperbólico en radianes. |
Hiperbólicas | tanh |
Calcula la tangente hiperbólica en radianes. |
Hiperbólicas | asinh |
Calcula el arcoseno hiperbólico en radianes. |
Hiperbólicas | acosh |
Calcula el arcocoseno hiperbólico en radianes. |
Hiperbólicas | atanh |
Calcula la arcotangente hiperbólica en radianes. |
Precisión | erf |
Función de error. |
Precisión | erfc |
Función complementaria de error. |
Precisión | tgamma |
Función gamma. |
Precisión | lgamma |
Logaritmo natural de la función gamma. |
Redondeo | ceil |
El siguiente entero mayor o igual. |
Redondeo | floor |
El siguiente entero menor o igual. |
Redondeo | trunc |
El siguiente entero menor o igual en magnitud. |
Redondeo | round lround llround |
El entero más cercano. |
Redondeo | nearbyint |
El entero más cercano usando el modo actual de redondeo. |
Redondeo | rint lrint llrint |
El entero más cercano usando el modo actual de redondeo con excepciones si el resultado difiere. |
Manipulación | frexp |
Descompone un número entre su significado y su potencia de 2 . |
Manipulación | ldexp |
Multiplica un número por 2 elevado a la potencia. |
Manipulación | modf |
Descompone un número en su parte entera y la decimal. |
Manipulación | scalbn scalbln |
Multiplica un número por FLT_RADIX elevado a la potencia. |
Manipulación | ilogb |
Extrae el exponente de un número. |
Manipulación | logb |
Extrae el exponente de un número. |
Manipulación | nextafter nexttoward |
Siguiente valor de coma flotante representable. |
Manipulación | copysign |
Copia el signo de un valor de coma flotante. |
Clasificación | fpclassify |
Obtiene la categoría de un valor de coma flotante. |
Clasificación | isfinite |
Comprueba si el valor es finito. |
Clasificación | isinf |
Comprueba si el valor es infinito. |
Clasificación | isnan |
Comprueba si el valor no es valido. |
Clasificación | isnormal |
Comprueba si el valor es normal. |
Clasificación | signbit |
Comprueba si el valor es negativo. |
Clasificación | isgreater |
Comprueba si el primer valor es mayor que el segundo. |
Clasificación | isgreaterequal |
Comprueba si el primer valor es mayor o igual que el segundo. |
Clasificación | isless |
Comprueba si el primer valor es menor que el segundo. |
Clasificación | islessequal |
Comprueba si el primer valor es menor o igual que el segundo. |
Clasificación | islessgreater |
Comprueba si el primer valor es menor o mayor que el segundo. |
Clasificación | isunordered |
Comprueba si dos valores no son ordenables. |
Las anteriores funciones funcionan con el tipo double
, pero tienen versiones terminadas en f
para float
y en l
para long double
. Además, dentro de cmath
hay constantes como INFINITY
o NAN
, para representar el infinito o un número inválido con los números reales, que son macros heredadas del lenguaje C.
En el módulo numeric
tenemos las siguientes funciones adicionales:
Función | Descripción |
---|---|
std::gcd |
Calcula el máximo común divisor de dos números. |
std::lcm |
Calcula el mínimo común múltiplo de dos números. |
std::midpoint |
Calcula el punto medio entre dos números. |
std::lerp |
Calcula la función de interpolación lineal. |
std::iota |
Rellena una secuencia con incrementos sucesivos de un valor inicial. |
std::accumulate |
Suma o reduce una secuencia de elementos. |
std::inner_product |
Calcula el producto interior de dos secuencias de elementos. |
std::adjacent_difference |
Calcula la diferencia entre elementos adyacentes en una secuencia. |
std::partial_sum |
Calcula la suma parcial de una secuencia de elementos. |
std::reduce |
Reduce una secuencia de elementos sin un orden concreto. |
std::exclusive_scan |
Similar a partial_sum , excluyendo el último elemento de la secuencia. |
std::inclusive_scan |
Similar a partial_sum , incluyendo el último elemento de la secuencia. |
std::transform_reduce |
Aplica una función a una secuencia y después la reduce. |
std::transform_exclusive_scan |
Aplica una función a una secuencia y después la operación exclusive_scan . |
std::transform_inclusive_scan |
Aplica una función a una secuencia y después la operación inclusive_scan . |
Por último, tenemos los siguientes módulos matemáticos:
Módulo | Descripción |
---|---|
bit |
Recoge una serie de funciones para trabajar a nivel de bits con valores y secuencias. |
complex |
Tiene la clase std::complex<T> que representa los números complejos. |
limits |
Tiene la clase std::numeric_limits<T> que da información sobre los tipos numéricos del lenguaje. |
numbers |
Recoge un conjunto de constantes, como el número e o pi . |
random |
Recoge una serie de tipos para generar números aleatorios. Por un lado, tenemos distribuciones como std::uniform_int_distribution<T> , para producir números enteros aleatorios de forma uniforme, y std::uniform_real_distribution<T> , para hacer lo mismo con números reales, entre otras. Por otro lado, las distribuciones requieren de generadores como std::default_random_engine , entre otros. |
valarray |
Tiene la clase std::valarray<T> que representa un array especializado en números para optimizar operaciones matemáticas. |
La biblioteca estándar incorpora soporte para la concurrencia. Estos son los principales tipos disponibles:
Módulo | Tipo | Descripción |
---|---|---|
thread |
std::thread |
Gestiona un hilo separado de ejecución. |
thread |
std::jthread |
Gestiona un hilo separado de ejecución, con soporte para que se vuelva a unir al hilo principal por sí solo y para que pueda ser cancelado. |
atomic |
std::atomic<T> |
Gestiona que las operaciones que se hagan sobre un objeto sean atómicas. |
atomic |
std::atomic_ref<T> |
Gestiona que las operaciones que se hagan sobre una referencia a un objeto sean atómicas. |
atomic |
std::atomic_flag |
Gestiona que las operaciones que se hagan sobre un booleano sean atómicas. Además, estas operaciones están libres de bloqueos. |
mutex |
std::mutex |
Representa una primitiva para sincronizar el acceso ordenado, a datos compartidos por múltiples hilos, mediante el bloqueo de la ejecución. |
mutex |
std::timed_mutex |
Similar a mutex , pero los bloqueos tienen un tiempo de espera máximo. |
mutex |
std::recursive_mutex |
Similar a mutex , pero el se pueden acumular bloqueos de forma recursiva por el mismo hilo. |
mutex |
std::recursive_timed_mutex |
Similar a mutex , pero el se pueden acumular bloqueos de forma recursiva por el mismo hilo con un tiempo de espera máximo. |
shared_mutex |
std::shared_mutex |
Similar a mutex , pero puede permitir acceso compartido a varios hilos. |
shared_mutex |
std::shared_timed_mutex |
Similar a shared_mutex , pero los bloqueos tienen un tiempo de espera máximo. |
mutex |
std::lock_guard<M> |
Gestiona el bloqueo de un mutex y liberándolo cuando se sale del ámbito donde el candado ha sido declarado. |
mutex |
std::scoped_lock<...> |
Similar a lock_guard , pero permite gestionar varios mutex a la vez. |
mutex |
std::unique_lock<M> |
Gestiona cómo se mueve la propiedad de un mutex. |
shared_mutex |
std::shared_lock<M> |
Gestiona cómo se mueve la propiedad de un shared mutex. |
mutex |
std::lock<...> |
Función para bloquear un grupo de mutex, si alguno estuviera bloqueado se bloquea la ejecución del hilo actual. |
mutex |
std::try_lock<...> |
Función para bloquear un grupo de mutex, si alguno estuviera bloqueado devuelve un índice para indicar cuál ha sido, si no devuelve el valor -1 . |
future |
std::future<T> |
Espera por un valor que es asignado de forma asíncrona. |
future |
std::shared_future<T> |
Espera por un valor, posiblemente referenciado por otros objetos de tipo future , que es asignado de forma asíncrona. |
future |
std::promise<T> |
Almacena un valor para recuperarlo de forma asíncrona. |
future |
std::packaged_task<...> |
Encapsula una función para almacenar su valor de retorno para recuperarlo de forma asíncrona. |
future |
std::async<...> |
Ejecuta una función de forma asíncrona, potencialmente en un nuevo hilo, y devuelve un objeto de tipo future con el que se obtendrá el resultado. |