¿Cómo imprimo el contenido de un vector?
¿ Cómo imprimo el contenido de a std::vector
en la pantalla?
Una solución que implemente lo siguiente operator<<
también sería buena:
template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
// ... What can I write here?
}
Esto es lo que tengo hasta ahora, sin una función separada:
#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
#include <sstream>
#include <cstdio>
using namespace std;
int main()
{
ifstream file("maze.txt");
if (file) {
vector<char> vec(istreambuf_iterator<char>(file), (istreambuf_iterator<char>()));
vector<char> path;
int x = 17;
char entrance = vec.at(16);
char firstsquare = vec.at(x);
if (entrance == 'S') {
path.push_back(entrance);
}
for (x = 17; isalpha(firstsquare); x++) {
path.push_back(firstsquare);
}
for (int i = 0; i < path.size(); i++) {
cout << path[i] << " ";
}
cout << endl;
return 0;
}
}
Si tiene un compilador de C++ 11, sugeriría usar un bucle for basado en rango (ver más abajo); o bien utilice un iterador. Pero tienes varias opciones, las cuales explicaré a continuación.
Bucle for basado en rango (C++11)
En C++ 11 (y posteriores) puedes usar el nuevo bucle for basado en rango, que se ve así:
std::vector<char> path;
// ...
for (char i: path)
std::cout << i << ' ';
El tipo char
en la declaración del bucle for debe ser el tipo de los elementos del vector path
y no un tipo de indexación de enteros. En otras palabras, dado que path
es de tipo std::vector<char>
, el tipo que debería aparecer en el bucle for basado en rango es char
. Sin embargo, es probable que a menudo veas el tipo explícito reemplazado por el auto
tipo de marcador de posición:
for (auto i: path)
std::cout << i << ' ';
Independientemente de si utiliza el tipo explícito o la auto
palabra clave, el objeto i
tiene un valor que es una copia del elemento real del path
objeto. Por lo tanto, todos los cambios i
en el bucle no se conservan en path
sí mismo:
std::vector<char> path{'a', 'b', 'c'};
for (auto i: path) {
i = '_'; // 'i' is a copy of the element in 'path', so although
// we can change 'i' here perfectly fine, the elements
// of 'path' have not changed
std::cout << i << ' '; // will print: "_ _ _"
}
for (auto i: path) {
std::cout << i << ' '; // will print: "a b c"
}
Si también desea prohibir la posibilidad de cambiar este valor copiado de i
en el bucle for, puede forzar que el tipo de i
sea const char
así:
for (const auto i: path) {
i = '_'; // this will now produce a compiler error
std::cout << i << ' ';
}
Si desea modificar los elementos para path
que esos cambios persistan fuera path
del bucle for, puede utilizar una referencia como esta:
for (auto& i: path) {
i = '_'; // changes to 'i' will now also change the
// element in 'path' itself to that value
std::cout << i << ' ';
}
e incluso si no quieres modificar path
, si la copia de objetos es costosa deberías usar una referencia constante en lugar de copiar por valor:
for (const auto& i: path)
std::cout << i << ' ';
Iteradores
Antes de C++11, la solución canónica habría sido utilizar un iterador, y eso sigue siendo perfectamente aceptable. Se utilizan de la siguiente manera:
std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
Si desea modificar el contenido del vector en el bucle for, utilice iterator
en lugar de const_iterator
.
Suplemento: typedef / type alias (C++11) / auto (C++11)
Esta no es otra solución, sino un complemento de la iterator
solución anterior. Si está utilizando el estándar C++ 11 (o posterior), puede utilizar la auto
palabra clave para mejorar la legibilidad:
for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
Aquí el tipo de i
será no constante (es decir, el compilador utilizará std::vector<char>::iterator
como tipo de i
). Esto se debe a que llamamos al begin
método, por lo que el compilador dedujo el tipo de i
eso. Si llamamos al cbegin
método ("c" para const), entonces i
será std::vector<char>::const_iterator
:
for (auto i = path.cbegin(); i != path.cend(); ++i) {
*i = '_'; // will produce a compiler error
std::cout << *i << ' ';
}
Si no se siente cómodo con el compilador deduciendo tipos, entonces en C++ 11 puede usar un alias de tipo para evitar tener que escribir el vector todo el tiempo (un buen hábito que debe adquirir):
using Path = std::vector<char>; // C++11 onwards only
Path path; // 'Path' is an alias for std::vector<char>
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
Si no tiene acceso a un compilador de C++ 11 (o no le gusta la sintaxis de alias de tipo por cualquier motivo), puede usar el más tradicional typedef
:
typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
Nota al margen:
En este punto, es posible que se haya encontrado o no con iteradores antes, y puede que haya escuchado o no que los iteradores son lo que "se supone" que debe usar, y puede que se pregunte por qué. La respuesta no es fácil de apreciar, pero, en resumen, la idea es que los iteradores son una abstracción que te protege de los detalles de la operación.
Es conveniente tener un objeto (el iterador) que realice la operación que desea (como el acceso secuencial) en lugar de que usted mismo escriba los detalles (siendo los "detalles" el código que realiza el acceso real a los elementos del vector). Debes notar que en el bucle for solo le pides al iterador que te devuelva un valor ( *i
, donde i
está el iterador); nunca interactúas path
directamente con él mismo. La lógica es la siguiente: creas un iterador y le das el objeto que deseas recorrer ( iterator i = path.begin()
), y luego todo lo que haces es pedirle al iterador que obtenga el siguiente valor para ti ( *i
); nunca tuvo que preocuparse exactamente de cómo lo hizo el iterador; eso es asunto suyo, no suyo.
Está bien, pero ¿cuál es el punto? Bueno, imagina si obtener un valor no fuera sencillo. ¿Y si implica un poco de trabajo? No necesita preocuparse, porque el iterador se ha encargado de eso por usted: clasifica los detalles, todo lo que necesita hacer es pedirle un valor. Además, ¿qué pasa si cambias el contenedor std::vector
por otro? En teoría, su código no cambia incluso si lo hacen los detalles de cómo acceder a los elementos en el nuevo contenedor: recuerde, el iterador ordena todos los detalles detrás de escena, por lo que no necesita cambiar su código en absoluto. -- simplemente le pide al iterador el siguiente valor en el contenedor, igual que antes.
Entonces, si bien esto puede parecer una exageración confusa para recorrer un vector, hay buenas razones detrás del concepto de iteradores, por lo que es mejor que te acostumbres a usarlos.
Indexación
También puedes usar un tipo entero para indexar explícitamente los elementos del vector en el bucle for:
for (int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
Si va a hacer esto, es mejor utilizar los tipos de miembros del contenedor, si están disponibles y son apropiados. std::vector
tiene un tipo de miembro llamado size_type
para este trabajo: es el tipo devuelto por el size
método.
typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
for (Path::size_type i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
¿Por qué no utilizar esto con preferencia a la iterator
solución? Para casos simples, puede hacerlo, pero usar an iterator
trae varias ventajas, que he descrito brevemente anteriormente. Como tal, mi consejo sería evitar este método a menos que tenga buenas razones para ello.
std::copiar (C++11)
Vea la respuesta de Joshua . Puede utilizar el algoritmo STL std::copy
para copiar el contenido del vector en el flujo de salida. No tengo nada que agregar, excepto decir que no uso este método; pero no hay ninguna buena razón para ello aparte del hábito.
std::rangos::copiar (C++20)
Para completar, C++20 introdujo rangos, que pueden actuar en todo el rango de a std::vector
, por lo que no es necesario begin
and end
:
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
std::vector<char> path;
// ...
std::ranges::copy(path, std::ostream_iterator<char>(std::cout, " "));
A menos que tenga un compilador reciente (en GCC aparentemente al menos la versión 10.1 ), es probable que no tenga soporte para rangos incluso si tiene algunas funciones de C++20 disponibles.
Sobrecarga std::ostream::operator<<
Vea también la respuesta de Chris a continuación . Esto es más un complemento a las otras respuestas, ya que aún necesitará implementar una de las soluciones anteriores en la sobrecarga, pero el beneficio es un código mucho más limpio. Así es como puedes usar la std::ranges::copy
solución anterior:
#include <iostream>
#include <vector>
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
using Path = std::vector<char>; // type alias for std::vector<char>
std::ostream& operator<< (std::ostream& out, const Path& v) {
if ( !v.empty() ) {
out << '[';
std::ranges::copy(v, std::ostream_iterator<char>(out, ", "));
out << "\b\b]"; // use two ANSI backspace characters '\b' to overwrite final ", "
}
return out;
}
int main() {
Path path{'/', 'f', 'o', 'o'};
// will output: "path: [/, f, o, o]"
std::cout << "path: " << path << std::endl;
return 0;
}
Ahora puede pasar sus Path
objetos a su flujo de salida como los tipos fundamentales. Usar cualquiera de las otras soluciones anteriores también debería ser igualmente sencillo.
Conclusión
Cualquiera de las soluciones presentadas aquí funcionará. Depende de usted (y del contexto o de sus estándares de codificación) cuál es el "mejor". Probablemente sea mejor dejar cualquier cosa más detallada que esto para otra pregunta en la que los pros y los contras se puedan evaluar adecuadamente, pero como siempre, la preferencia del usuario siempre influirá: ninguna de las soluciones presentadas es objetivamente incorrecta, pero algunas le parecerán mejor a cada codificador. .
Apéndice
Esta es una solución ampliada de una anterior que publiqué. Dado que esa publicación siguió llamando la atención, decidí ampliarla y consultar las otras excelentes soluciones publicadas aquí, al menos aquellas que he usado personalmente en el pasado al menos una vez. Sin embargo, alentaría al lector a que consulte las respuestas a continuación porque probablemente haya buenas sugerencias que he olvidado o que no conozco.
Una forma mucho más sencilla de hacerlo es con el algoritmo de copia estándar :
#include <iostream>
#include <algorithm> // for copy
#include <iterator> // for ostream_iterator
#include <vector>
int main() {
/* Set up vector to hold chars a-z */
std::vector<char> path;
for (int ch = 'a'; ch <= 'z'; ++ch)
path.push_back(ch);
/* Print path vector to console */
std::copy(path.begin(), path.end(), std::ostream_iterator<char>(std::cout, " "));
return 0;
}
El ostream_iterator es lo que se llama un adaptador iterador . Tiene una plantilla sobre el tipo para imprimir en la secuencia (en este caso, char
). cout
(también conocido como salida de consola) es la secuencia en la que queremos escribir, y el carácter de espacio ( " "
) es lo que queremos imprimir entre cada elemento almacenado en el vector.
Este algoritmo estándar es poderoso y también lo son muchos otros. El poder y la flexibilidad que le brinda la biblioteca estándar son los que la hacen tan grandiosa. Imagínese: puede imprimir un vector en la consola con solo una línea de código. No es necesario tratar casos especiales con el carácter separador. No necesita preocuparse por los bucles for. La biblioteca estándar lo hace todo por usted.
En C++23 podrá utilizar std::print
para imprimir la mayoría de los tipos estándar, incluidos std::vector
. Por ejemplo:
import std;
int main() {
auto v = std::vector{1, 2, 3};
std::print("{}", v);
}
huellas dactilares
[1, 2, 3]
a stdout
.
Mientras tanto puedes utilizar la biblioteca {fmt} , std::print
se basa en:
#include <vector>
#include <fmt/ranges.h>
int main() {
auto v = std::vector<int>{1, 2, 3};
fmt::print("{}", v);
}
rayo divino: https://godbolt.org/z/xEdz15
No recomendaría sobrecargar operator<<
para tipos que no controlas, como los contenedores estándar.
Descargo de responsabilidad : soy el autor de {fmt} std::format
y std::print
.