¿Cómo imprimo el contenido de un vector?

Resuelto forthewinwin asked hace 12 años • 33 respuestas

¿ Cómo imprimo el contenido de a std::vectoren 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;
    }
}
forthewinwin avatar May 25 '12 14:05 forthewinwin
Aceptado

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 charen la declaración del bucle for debe ser el tipo de los elementos del vector pathy no un tipo de indexación de enteros. En otras palabras, dado que pathes 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 autotipo de marcador de posición:

for (auto i: path)
    std::cout << i << ' ';

Independientemente de si utiliza el tipo explícito o la autopalabra clave, el objeto itiene un valor que es una copia del elemento real del pathobjeto. Por lo tanto, todos los cambios ien el bucle no se conservan en pathsí 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 ien el bucle for, puede forzar que el tipo de isea const charasí:

for (const auto i: path) {
    i = '_'; // this will now produce a compiler error
    std::cout << i << ' ';
}

Si desea modificar los elementos para pathque esos cambios persistan fuera pathdel 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 iteratoren lugar de const_iterator.

Suplemento: typedef / type alias (C++11) / auto (C++11)

Esta no es otra solución, sino un complemento de la iteratorsolución anterior. Si está utilizando el estándar C++ 11 (o posterior), puede utilizar la autopalabra clave para mejorar la legibilidad:

for (auto i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

Aquí el tipo de iserá no constante (es decir, el compilador utilizará std::vector<char>::iteratorcomo tipo de i). Esto se debe a que llamamos al beginmétodo, por lo que el compilador dedujo el tipo de ieso. Si llamamos al cbeginmétodo ("c" para const), entonces iserá 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 iestá el iterador); nunca interactúas pathdirectamente 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::vectorpor 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::vectortiene un tipo de miembro llamado size_typepara este trabajo: es el tipo devuelto por el sizemé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 iteratorsolución? Para casos simples, puede hacerlo, pero usar an iteratortrae 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::copypara 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 beginand 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::copysolució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 Pathobjetos 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.

Zorawar avatar May 25 '2012 17:05 Zorawar

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.

Joshua Kravitz avatar Jul 04 '2012 21:07 Joshua Kravitz

En C++23 podrá utilizar std::printpara 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::printse 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::formaty std::print.

vitaut avatar Mar 20 '2019 22:03 vitaut