Як роздрукувати вміст вектора?


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буде не const (тобто компілятор буде використовувати 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 << ' ';

std :: копія

Дивіться відповідь Джошуа . Ви можете використовувати алгоритм STL, std::copyщоб скопіювати векторний вміст у вихідний потік. Це елегантне рішення, якщо вам це зручно (і до того ж це дуже корисно, не тільки в цьому випадку друку вмісту вектора).

std :: for_each

Дивіться рішення Макса . Використання std::for_eachзайвого сценарію для цього простого сценарію, але це дуже корисне рішення, якщо ви хочете зробити більше, ніж просто друк на екрані: використання std::for_eachдозволяє робити будь-які (розумні) операції над векторним вмістом.

перевантаження ostream :: оператор <<

Дивіться відповідь Кріса , це більше доповнення до інших відповідей, оскільки вам все одно потрібно буде реалізувати одне з вищезазначених рішень при перевантаженні. У своєму прикладі він використав лічильник у 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якщо нічого в циклі не змінює вектор, але я подумав, що я згадаю його про всяк випадок, коли вектор буде змінений у циклі (як це не рекомендується) і тому що він ніколи не отримує згадати, і це може бути корисно принаймні знати про це.
Зоравар

Діапазон для циклу може бути переписаний для використання посилань, що може бути важливим у випадку великих суб'єктів, як:for (auto const &i: path) std::cout << i << ' ';
підкреслюється_d

@underscore_d: спасибі Я прибрав цей розділ і, сподіваюся, зараз він і більш повний, і трохи зрозуміліший.
Зоравар

"оператор перевантаження <<" не є хорошим рішенням; принаймні один операнд перевантаженого оператора повинен бути класом, визначеним вашою програмою, через пошук аргументів
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(aka консольний вихід) - це потік, в який ми хочемо записати, а пробільний символ ( " ") - це те, що ми хочемо надрукувати між кожним елементом, що зберігається у векторі.

Цей стандартний алгоритм є потужним, як і багато інших. Потужність та гнучкість, яку надає стандартна бібліотека, - це те, що робить її такою великою. Уявіть собі: ви можете надрукувати вектор на консоль лише одним рядком коду. Вам не доведеться мати справу з особливими справами з символом роздільника. Вам не потрібно турбуватися про for-петлі. Стандартна бібліотека робить все це за вас.


3
що робити, якщо мій вектор був тип vector<pair<int, struct node>>. Як я можу використовувати вищезазначений метод для друку цього вектора?
mtk

6
Рядок роздільника пишеться після кожного елемента, а не між, тобто, також після останнього. Це може зажадати вирішення особливих випадків, якщо ви хочете лише між ними, тобто як роздільник.
Квігі

2
@mtk ви можете оголосити operator<<функцію для вашої конкретної пари <>.
ShoeLace

Додано відповідь, що демонструє аналогічний підхід, але з урахуванням коментаря accout @Quigi: s вище щодо додаткового розділювача.
dfri

@ShoeLace Немає іншого способу?
thegreatcoder

69

У C ++ 11 тепер ви можете використовувати цикл на основі діапазону :

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

Це чудово працює, лише якщо розмір вектора не змінюється в тілі діапазону для циклу.
Брайан

8
@BrianP. Так. Друк елементів контейнера не змінює діапазон контейнера.
взуття

Що тут краще - c як копія значення або як посилання const, щоб уникнути копіювання елемента?
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/reitions/23397700/5 ), але після цього він був відредагований знову, відновлюючи можливу втрату точності. Я думаю, це на практиці не має великого значення, оскільки вектори зазвичай не такі великі.
JDiMatteo

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

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

3
ADL цього оператора не знайдено, оскільки він не знаходиться в просторі імен жодного з його аргументів. Тож воно буде приховано будь-яким іншим простором імен operator<<. Приклад
ММ

Якщо ви збираєтеся це робити, навіщо тестувати if (i != last) кожен раз у циклі? Натомість, якщо контейнер не порожній, тоді (a) надішліть перший елемент, а потім (b) петлю - надішліть решта елементів, надрукуючи спочатку роздільник (як префікс). Не потрібно тестувати внутрішню петлю (крім самої умови циклу). Потрібен лише один тест поза циклом.
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 гарне рішення може бути на основі діапазону для циклу:

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, але я не зміг залишити коментар там.

Ви можете зробити автоматичну (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] << ' ';

Ця відповідь не дає додаткової інформації порівняно з уже наявними відповідями.
Яшас

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оператор повинен сприймати вектор як non-const. Це може зробити програму небезпечною або ввести неправомірний код. Крім того, оскільки додається нульовий символ, 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.