Как распечатать содержимое вектора?


282

Я хочу распечатать содержимое вектора в C ++, вот что у меня есть:

#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;
    }
}

Как мне распечатать содержимое вектора на экране?


1
почему это "не работает"?
По умолчанию

Ответы:


394

Чтобы чисто ответить на ваш вопрос, вы можете использовать итератор:

std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

Если вы хотите изменить содержимое вектора в цикле for, используйте iteratorвместоconst_iterator .

Но об этом можно сказать гораздо больше. Если вы просто хотите получить ответ, который можете использовать, тогда вы можете остановиться здесь; в противном случае читайте дальше.

auto (C ++ 11) / typedef

Это не другое решение, а дополнение к вышеуказанному iteratorрешению. Если вы используете стандарт C ++ 11 (или более поздний), то вы можете использовать autoключевое слово, чтобы улучшить читаемость:

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

Но тип iбудет неконстантным (т. Е. Компилятор будет использовать std::vector<char>::iteratorкак типi ).

В этом случае вы также можете просто использовать typedef(не ограничиваясь C ++ 11 и очень полезно использовать в любом случае):

typedef std::vector<char> Path;
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

счетчик

Конечно, вы можете использовать целочисленный тип для записи вашей позиции в forцикле:

for(int i=0; i<path.size(); ++i)
  std::cout << path[i] << ' ';

Если вы собираетесь это сделать, лучше использовать типы элементов контейнера, если они доступны и уместны. std::vectorимеет тип члена, вызываемый size_typeдля этого задания: это тип, возвращаемый sizeметодом.

// Path typedef'd to std::vector<char>
for( Path::size_type i=0; i<path.size(); ++i)
  std::cout << path[i] << ' ';

Почему бы просто не использовать это поверх iteratorрешения? Для простых случаев вы тоже можете, но дело в том, что iteratorкласс - это объект, предназначенный для выполнения этой работы для более сложных объектов, где это решение не будет идеальным.

основанный на диапазоне для цикла (C ++ 11)

Смотрите решение Джеффри . В C ++ 11 (и позже) вы можете использовать новый forцикл на основе диапазона , который выглядит следующим образом:

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

Так pathкак является вектором элементов (явно std::vector<char>), объект iимеет тип элемента вектора (то есть, явно, он имеет тип char). Объект iимеет значение, которое является копией фактического элемента в pathобъекте. Таким образом, все изменения iв цикле не сохраняются pathсами по себе. Кроме того, если вы хотите обеспечить тот факт, что вы не хотите иметь возможность изменять скопированное значение iв цикле, вы можете сделать так, чтобы тип iбыл const charследующим:

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

Если вы хотите изменить элементы в path, вы можете использовать ссылку:

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

и даже если вы не хотите изменять path, если копирование объектов стоит дорого, вы должны использовать ссылку const вместо копирования по значению:

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

станд :: копия

Смотрите ответ Иисуса Навина . Вы можете использовать алгоритм STL std::copyдля копирования векторного содержимого в выходной поток. Это элегантное решение, если вам удобно (и, кроме того, это очень полезно, не только в этом случае печати содержимого вектора).

станд :: for_each

Смотрите решение Макса . Использование этого std::for_eachизбыточного сценария является излишним, но это очень полезное решение, если вы хотите сделать больше, чем просто печатать на экран: использование std::for_eachпозволяет вам делать любые (разумные) операции с векторным содержимым.

перегрузка ostream :: operator <<

См . Ответ Криса , это скорее дополнение к другим ответам, так как вам все еще нужно будет реализовать одно из решений выше при перегрузке. В своем примере он использовал счетчик в forцикле. Например, вот как вы можете быстро использовать решение Джошуа :

template <typename T>
std::ostream& operator<< (std::ostream& out, const std::vector<T>& v) {
  if ( !v.empty() ) {
    out << '[';
    std::copy (v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
    out << "\b\b]";
  }
  return out;
}

Использование любого из других решений должно быть простым.

вывод

Любое из представленных здесь решений будет работать. Это зависит от вас и от того, какой код является «лучшим». Что-нибудь более подробное, чем это, вероятно, лучше оставить для другого вопроса, где плюсы / минусы можно правильно оценить; но, как всегда, предпочтение пользователя всегда будет играть роль: ни одно из представленных решений не является правильным, но некоторые будут выглядеть лучше для каждого отдельного кодера.

добавление

Это расширенное решение, о котором я писал ранее. Поскольку этот пост продолжал привлекать внимание, я решил расширить его и сослаться на другие отличные решения, которые были опубликованы здесь. Мой оригинальный пост был замечание , что отметил , что , если вы были намерены на модифицируя вектор внутри forцикла , то есть два способа , предоставляемые std::vectorк элементам доступа: std::vector::operator[]который не делает проверки границ, и std::vector::atкоторый действительно выполняет проверку границ. Другими словами, atбудет выброшено, если вы попытаетесь получить доступ к элементу вне вектора и operator[]не сможете . Первоначально я добавил этот комментарий только для того, чтобы упомянуть кое-что, о чем было бы полезно знать, если кто-то уже этого не сделал. И я не вижу разницы сейчас. Отсюда и это дополнение.


Если вы выполняете цикл 0сквозного прохождения, vector::size()а вектор внутри цикла не изменяется, нет необходимости использовать at()дополнительные расходы на проверку границ. Тем не менее, я бы пошел с итератором, как вы предлагаете.
Эд С.

1
@Ed: да, нет смысла использовать, atесли ничто в цикле не изменяет вектор, но я подумал, что упомяну об этом просто на случай, если вектор будет изменен в цикле (не рекомендуется), и потому что он никогда не получит упомянуть, и это может быть полезно, по крайней мере, знать об этом.
Zorawar

Цикл for для диапазона может быть переписан для использования ссылок, которые могут быть важны в случае больших подобъектов, следующим образом:for (auto const &i: path) std::cout << i << ' ';
underscore_d

@underscore_d: спасибо. Я очистил этот раздел, и я надеюсь, что он стал более полным и немного более понятным.
Zorawar

«оператор перегрузки <<» не является хорошим решением; по крайней мере один операнд перегруженного оператора должен быть классом, определенным вашей программой, из-за зависимого от аргумента поиска
MM

218

Гораздо проще сделать это с помощью стандартного алгоритма копирования :

#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;
}

Ostream_iterator - это то, что называется адаптером итератора . Он шаблонизируется по типу для печати в поток (в данном случае, char). cout(иначе вывод консоли) - это поток, в который мы хотим записать, а символ пробела ( " ") - это то, что мы хотим напечатать между каждым элементом, хранящимся в векторе.

Этот стандартный алгоритм является мощным, как и многие другие. Мощь и гибкость, которые дает стандартная библиотека, делают ее такой замечательной. Только представьте: вы можете вывести вектор на консоль всего одной строкой кода. Вам не нужно иметь дело с особыми случаями с символом разделителя. Вам не нужно беспокоиться о петлях. Стандартная библиотека делает все это за вас.


3
Что делать, если мой вектор был типа vector<pair<int, struct node>>. Как использовать вышеуказанный метод для печати этого вектора?
MTK

6
Строка разделителя пишется после каждого элемента, а не между, т. Е. Также после последнего. Это может потребовать работы с особыми случаями, если вы хотите, чтобы это было только между ними, т. Е. Как разделитель.
Quigi

2
@mtk вы можете объявить operator<<функцию для вашей конкретной пары <>.
ShoeLace

Добавлен ответ, демонстрирующий аналогичный подход, но с учетом приведенного выше комментария @Quigi, касающегося дополнительного конечного разделителя.
17

@ShoeLace Нет другого пути?
thegreatcoder

69

В C ++ 11 теперь вы можете использовать цикл for на основе диапазона :

for (auto const& c : path)
    std::cout << c << ' ';

Это прекрасно работает, только если размер вектора не изменяется в теле диапазона для цикла.
Брайан

8
@BrianP. Ага. Печать элементов контейнера не изменяет диапазон контейнера.
Чистка

Что здесь предпочтительнее - c как копия значения или как постоянная ссылка, чтобы избежать копирования элемента?
kleinfreund

@kleinfreund Это зависит от содержания вектора. Например, для вектора chars существует вероятность того, что передача по константной ссылке на самом деле более затратна, чем по значению. Но здесь мы говорим о супер микро оптимизации.
обувь

43

Я думаю, что лучший способ сделать это - просто перегрузить operator<<, добавив эту функцию в вашу программу:

#include <vector>
using std::vector;
#include <iostream>
using std::ostream;

template<typename T>
ostream& operator<< (ostream& out, const vector<T>& v) {
    out << "{";
    size_t last = v.size() - 1;
    for(size_t i = 0; i < v.size(); ++i) {
        out << v[i];
        if (i != last) 
            out << ", ";
    }
    out << "}";
    return out;
}

Затем вы можете использовать <<оператор для любого возможного вектора, предполагая, что его элементы также ostream& operator<<определили:

vector<string>  s = {"first", "second", "third"};
vector<bool>    b = {true, false, true, false, false};
vector<int>     i = {1, 2, 3, 4};
cout << s << endl;
cout << b << endl;
cout << i << endl;

Выходы:

{first, second, third}
{1, 0, 1, 0, 0}
{1, 2, 3, 4}

3
Хранение v.size () - 1 как int - это возможная потеря точности. Я исправил это в принятой рецензируемой редакции ( stackoverflow.com/revisions/23397700/5 ), но после этого он был отредактирован снова, восстанавливая возможную потерю точности. Я думаю, на практике это не имеет большого значения, поскольку векторы обычно не так велики.
JDiMatteo

Отсутствие сохранения в качестве переменной снижает читабельность кода, что является частью вашего редактирования, с которым я не согласен. Я изменил тип lastна size_t.
Крис Редфорд

size_t last = v.size() - 1;выглядит излишним, вы можете использовать if (i) out << ", ";условие перед out << v[i]; ссылкой
Владимир Гамалян

3
Этот оператор не найден в ADL, поскольку он не находится в пространстве имен ни одного из своих аргументов. Так что оно будет скрыто любым другим пространством имен operator<<. Пример
ММ

Если вы собираетесь это сделать, зачем тестировать if (i != last) каждый раз в цикле? Вместо этого, если контейнер не пустой, то (а) отправьте первый элемент, а затем (б) отправьте циклически остальные элементы, сначала напечатав разделитель (в качестве префикса). Не требуется проверка внутреннего контура (кроме самого условия контура). Требуется только одно внеплановое испытание.
WhozCraig

22

Как насчет for_each+ лямбда-выражения :

#include <vector>
#include <algorithm>
...
std::vector<char> vec;
...
std::for_each(
              vec.cbegin(),
              vec.cend(),
              [] (const char c) {std::cout << c << " ";} 
              );
...

Конечно, диапазон на основе является наиболее элегантным решением для этой конкретной задачи, но этот дает также много других возможностей.

объяснение

for_eachАлгоритм принимает входной диапазон и вызываемый объект , вызывая объект на каждом элементе диапазона. Входной диапазон определяется двумя итераторы . Вызываемый объект может быть функцией, указатель на функцию, объект класса , который перегружает () operatorили , как в данном случае, лямбда - выражения . Параметр для этого выражения соответствует типу элементов вектора.

Прелесть этой реализации заключается в мощности, которую вы получаете от лямбда-выражений - вы можете использовать этот подход для гораздо большего, чем просто печать вектора.


11

Просто скопируйте контейнер в консоль.

std::vector<int> v{1,2,3,4};
std::copy(v.begin(),v.end(),std::ostream_iterator<int>(std::cout, " " ));

Должен вывести:

1 2 3 4

8

Проблема, вероятно , в предыдущем цикле: (x = 17; isalpha(firstsquare); x++). Этот цикл не будет выполняться вообще (если firstsquareон не альфа) или будет работать вечно (если он альфа). Причина в том, что firstsquareне меняется, так как xувеличивается.


7

В C ++ 11 цикл for на основе диапазона может быть хорошим решением:

vector<char> items = {'a','b','c'};
for (char n : items)
    cout << n << ' ';

Вывод:

a b c 

6

Использование, std::copyно без дополнительного конечного разделителя

Альтернативный / модифицированный подход, использующий std::copy(как первоначально использовалось в ответе @JoshuaKravtiz ), но без включения дополнительного конечного разделителя после последнего элемента:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

template <typename T>
void print_contents(const std::vector<T>& v, const char * const separator = " ")
{
    if(!v.empty())
    {
        std::copy(v.begin(),
                  --v.end(),
                  std::ostream_iterator<T>(std::cout, separator));
        std::cout << v.back() << "\n";
    }
}

// example usage
int main() {
    std::vector<int> v{1, 2, 3, 4};
    print_contents(v);      // '1 2 3 4'
    print_contents(v, ":"); // '1:2:3:4'
    v = {};
    print_contents(v);      // ... no std::cout
    v = {1};
    print_contents(v);      // '1'
    return 0;
}

Пример использования, примененный к контейнеру пользовательского типа POD:

// includes and 'print_contents(...)' as above ...

class Foo
{
    int i;
    friend std::ostream& operator<<(std::ostream& out, const Foo& obj);
public:
    Foo(const int i) : i(i) {}
};

std::ostream& operator<<(std::ostream& out, const Foo& obj)
{
    return out << "foo_" << obj.i; 
}

int main() {
    std::vector<Foo> v{1, 2, 3, 4};
    print_contents(v);      // 'foo_1 foo_2 foo_3 foo_4'
    print_contents(v, ":"); // 'foo_1:foo_2:foo_3:foo_4'
    v = {};
    print_contents(v);      // ... no std::cout
    v = {1};
    print_contents(v);      // 'foo_1'
    return 0;
}

5

оператор перегрузки <<:

template<typename OutStream, typename T>
OutStream& operator<< (OutStream& out, const vector<T>& v)
{
    for (auto const& tmp : v)
        out << tmp << " ";
    out << endl;
    return out;
}

Использование:

vector <int> test {1,2,3};
wcout << test; // or any output stream

3

Я вижу две проблемы. Как указывалось выше, for (x = 17; isalpha(firstsquare); x++)существует либо бесконечный цикл, либо он вообще не выполняется, а также в том if (entrance == 'S')случае, если входной символ отличается от 'S', то ничто не добавляется к вектору пути, делая его пустым и, таким образом, ничего не печатая на экране. Вы можете проверить последнюю проверку path.empty()или печать path.size().

В любом случае, не лучше ли использовать строку вместо вектора? Вы также можете обращаться к содержимому строки, например, к массиву, искать символы, извлекать подстроки и легко печатать строку (без цикла).

Выполнение всего этого со строками могло бы быть способом, которым это написано менее запутанным способом и легче обнаружить проблему.


3

Этот ответ основан на ответе от Zorawar, но я не мог оставить комментарий там.

Вы можете сделать auto (C ++ 11) / typedef версию const, используя вместо этого cbegin и cend

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

1

В С ++ 11``

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

for(int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';

Этот ответ не дает никакой дополнительной информации по сравнению с уже существующими ответами.
Yashas

0
#include<bits/stdc++.h>
using namespace std;

int main()
{
    vector <pair<string,int> > v;
    int n;
    cin>>n;
int i;
    for( i=0;i<n;i++)
    {
        int  end;
        string str;
        cin>>str;
        cin>>end;
        v.push_back(make_pair(str,end));
    }



for (int j=0;j<n;j++)
{
    cout<<v[j].first<< " "<<v[j].second<<endl;
}``
}

2
Здравствуй! Добро пожаловать в stackoverflow. Было бы здорово, если бы вы включили в фрагмент кода какое-то объяснение того, что вы делаете, и как оно отвечает на вопрос.
Слабгорб


0

Это сработало для меня:

    for (auto& i : name)
    {
    int r = 0;
    for (int j = 0; j < name[r].size();j++) 
    {
    std::cout << i[j];
    }
    r++;
    std::cout << std::endl;
    }

0

Для тех, кто интересуется: я написал обобщенное решение, которое использует лучшее из обоих миров, более обобщенно для любого типа диапазона и помещает кавычки вокруг неарифметических типов (желательно для строковых типов). Кроме того, этот подход не должен иметь никаких проблем с ADL, а также избегать «неожиданностей» (поскольку он добавляется явно в каждом конкретном случае):

template <typename T>
inline constexpr bool is_string_type_v = std::is_convertible_v<const T&, std::string_view>;

template<class T>
struct range_out {
  range_out(T& range) : r_(range) {
  }
  T& r_;
  static_assert(!::is_string_type_v<T>, "strings and string-like types should use operator << directly");
};

template <typename T>
std::ostream& operator<< (std::ostream& out, range_out<T>& range) {
  constexpr bool is_string_like = is_string_type_v<T::value_type>;
  constexpr std::string_view sep{ is_string_like ? "', '" : ", " };

  if (!range.r_.empty()) {
    out << (is_string_like ? "['" : "[");
    out << *range.r_.begin();
    for (auto it = range.r_.begin() + 1; it != range.r_.end(); ++it) {
      out << sep << *it;
    }
    out << (is_string_like ? "']" : "]");
  }
  else {
    out << "[]";
  }

  return out;
}

Теперь его довольно легко использовать в любом диапазоне:

std::cout << range_out{ my_vector };

Струнный чек оставляет место для улучшения. У меня также есть static_assertпроверка в моем решении, чтобы избежать std::basic_string<>, но я оставил это здесь для простоты.


-1

Вы можете написать свою собственную функцию:

void printVec(vector<char> vec){
    for(int i = 0; i < vec.size(); i++){
        cout << vec[i] << " ";
    }
    cout << endl;
}

-1

Для людей, которые хотят однострочники без петель:

Я не могу поверить, что никто этого не понимает, но, возможно, это из-за более С-подобного подхода. В любом случае, это абсолютно безопасно сделать без цикла, в одну строку, предполагая, что std::vector<char>завершается нулем:

std::vector<char> test { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\0' };
std::cout << test.data() << std::endl;

Но я бы добавил это в ostreamоператор, как предложила @Zorawar, просто для безопасности:

template <typename T>std::ostream& operator<< (std::ostream& out, std::vector<T>& v)
{
    v.push_back('\0'); // safety-check!
    out << v.data();
    return out;
}

std::cout << test << std::endl; // will print 'Hello, world!'

Мы можем добиться аналогичного поведения, используя printfвместо этого:

fprintf(stdout, "%s\n", &test[0]); // will also print 'Hello, world!'

НОТА:

Перегруженный ostreamоператор должен принять вектор как неконстантный. Это может сделать программу небезопасной или ввести некорректный код. Кроме того, поскольку добавляется нулевой символ, std::vectorможет произойти перераспределение значения . Так что использование for-циклов с итераторами, скорее всего, будет быстрее.


1
1. fprintf(stdout, "%s\n", &test[0]);ничем не отличается std::cout << test.data(), оба требуют нулевого вектора. 2. Оператор «но я бы обернул это в оператор ostream», << который изменяет правильный операнд, очень плохая идея.
HolyBlackCat

Я fprintf(stdout, "%s\n", &test[0]);долгое время использовал в коде, не доставляя мне никаких проблем. Интересный! И я согласен, что не очень приятно модифицировать вектор в ostreamоператоре, но мне не нравится как циклическое выполнение вручную, так и использование итераторов. Почему-то я чувствую, что для простых операций, таких как печать, std::vector<char>стандартная библиотека должна скрывать эти вещи. Но C ++ постоянно развивается, это может произойти в ближайшее время.
ワ イ き ん ぐ
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.