Як ви скопіюєте вміст масиву в std :: vector в C ++, не маючи циклу?


122

У мене є масив значень, який передається моїй функції з іншої частини програми, яку мені потрібно зберігати для подальшої обробки. Оскільки я не знаю, скільки разів буде викликана моя функція, перш ніж настане час обробляти дані, мені потрібна динамічна структура зберігання, тому я вибрав a std::vector. Мені не хочеться робити стандартний цикл для push_backвсіх значень окремо, було б добре, якби я міг просто скопіювати все це за допомогою чогось подібного memcpy.

Відповіді:


117

Якщо ви можете побудувати вектор після отримання масиву та розміру масиву, ви можете просто сказати:

std::vector<ValueType> vec(a, a + n);

... припускаючи, що aце ваш масив і nкількість елементів, які він містить. Інакше std::copy()ж / resize()зробить трюк.

Я б тримався осторонь, memcpy()якщо ви не можете бути впевнені, що значення є типовими даними (POD).

Крім того, варто відзначити, що жодне з цих дійсно не уникає циклу for - це лише питання, чи потрібно вам це бачити у своєму коді чи ні. O (n) продуктивність виконання неминуча для копіювання значень.

Нарешті, зауважте, що масиви у стилі C є ідеально допустимими контейнерами для більшості алгоритмів STL - необроблений покажчик еквівалентний begin(), а ( ptr + n) еквівалентний end().


4
Причина, чому циклічне циклічне виклик та виклик push_back погано, полягає в тому, що ви можете змусити вектор змінювати розмір декількох разів, якщо масив достатньо довгий.
bradtgmurray

@bradtgmurray: Я думаю, що будь-яка розумна реалізація конструктора векторів "два ітератори", яку я запропонував вище, спочатку закликає std :: distance () на двох ітераторах, щоб отримати необхідну кількість елементів, а потім виділити лише один раз.
Дрю Холл

4
@bradtgmurray: Навіть push_back () не був би надто поганим через експоненціальне зростання векторів (він же "амортизований постійний час"). Я думаю, що час виконання був би лише на 2 рази гіршим у гіршому випадку.
Дрю Холл

2
А якщо вектор вже є, vec.clear (); vec.insert (vec.begin (), a, a + n); працювали б також. Тоді ви навіть не вимагатимете, щоб бути вказівником, просто ітератором, і призначення вектора було б невдалим загальним (і способом C ++ / STL).
MP24

6
Іншою альтернативою, коли неможливо побудувати, було б призначити :, vec.assign(a, a+n)що було б більш компактно, ніж копіювати та змінювати розмір.
mMontu

209

Тут було багато відповідей, і майже всі вони зроблять роботу.

Однак є кілька оманливих порад!

Ось варіанти:

vector<int> dataVec;

int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

// Method 1: Copy the array to the vector using back_inserter.
{
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 2: Same as 1 but pre-extend the vector by the size of the array using reserve
{
    dataVec.reserve(dataVec.size() + dataArraySize);
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 3: Memcpy
{
    dataVec.resize(dataVec.size() + dataArraySize);
    memcpy(&dataVec[dataVec.size() - dataArraySize], &dataArray[0], dataArraySize * sizeof(int));
}

// Method 4: vector::insert
{
    dataVec.insert(dataVec.end(), &dataArray[0], &dataArray[dataArraySize]);
}

// Method 5: vector + vector
{
    vector<int> dataVec2(&dataArray[0], &dataArray[dataArraySize]);
    dataVec.insert(dataVec.end(), dataVec2.begin(), dataVec2.end());
}

Скоротити довгу історію короткий метод 4, використовуючи вектор :: insert, найкращий сценарій bsruth.

Ось деякі деталі горіха:

Спосіб 1 , мабуть, найпростіший для розуміння. Просто скопіюйте кожен елемент з масиву та натисніть на задню частину вектора. На жаль, це повільно. Оскільки є цикл (мається на увазі функція копіювання), кожен елемент повинен оброблятися індивідуально; жодне поліпшення продуктивності не може бути здійснене виходячи з того, що ми знаємо, що масив і вектори є суміжними блоками.

Метод 2 - запропоноване поліпшення ефективності методу 1; просто попередньо зарезервуйте розмір масиву перед його додаванням. Для великих масивів це може допомогти. Однак найкраща порада тут ніколи не використовувати резерв, якщо профілювання не підкаже, що ви можете отримати поліпшення (або вам потрібно переконатися, що ваші ітератори не будуть визнані недійсними). Б'ярн погоджується . Між іншим, я виявив, що цей метод проводився найповільніше, хоча я намагаюся всебічно пояснити, чому він регулярно значно повільніше, ніж метод 1 ...

Метод 3 - це рішення старої школи - киньте трохи проблеми C! Відмінно і швидко працює для типів POD. У цьому випадку потрібно змінити розмір, оскільки memcpy працює поза межами вектора, і немає можливості сказати вектору, що його розмір змінився. Крім некрасивого рішення (копіювання байтів!) Пам’ятайте, що це можна використовувати лише для типів POD . Я б ніколи не використовував це рішення.

Метод 4 - найкращий шлях. Це сенс зрозумілий, він (як правило) найшвидший і працює для будь-яких об'єктів. Немає недоліків використання цього методу для цього додатка.

Метод 5 - це налаштування методу 4 - скопіюйте масив у вектор, а потім додайте його. Хороший варіант - як правило, швидко і зрозуміло.

Нарешті, ви знаєте, що можете використовувати вектори замість масивів, правда? Навіть коли функція очікує масивів у стилі c, ви можете використовувати вектори:

vector<char> v(50); // Ensure there's enough space
strcpy(&v[0], "prefer vectors to c arrays");

Сподіваюся, що хтось там допомагає!


6
Ви не можете безпечно та портативно посилатися на "& dataArray [dataArraySize]" - це відновлення вказівника / ітератора минулого кінця. Натомість ви можете сказати dataArray + dataArraySize, щоб отримати вказівник, не спершу знеструмлювати його.
Дрю Холл

2
@Drew: так, ви можете, принаймні в C. Це визначено, що &exprне оцінює expr, він лише обчислює його адресу. І вказівник, який проходить повз останній елемент, теж цілком дійсний.
Роланд Ілліг

2
Ви спробували зробити метод 4 з 2? тобто резервування місця перед вставкою. Здається, що якщо розмір даних великий, для декількох вставок знадобиться кілька перерозподілів. Оскільки ми знаємо розмір апріорі, ми можемо зробити перерозподіл, перш ніж вставляти.
Хорхе Лейтао

2
@MattyT у чому сенс методу 5? Навіщо робити проміжну копію даних?
Руслан

2
Я особисто вважаю за краще отримувати прибуток від масивів, що стикаються до покажчиків автоматично: dataVec.insert(dataVec.end(), dataArray, dataArray + dataArraySize);- видається мені набагато зрозумілішим. З методу 5 неможливо отримати нічого, лише виглядає досить неефективно - якщо компілятор не зможе знову оптимізувати вектор.
Аконкагуа

37

Якщо все, що ви робите, це заміна наявних даних, то ви можете це зробити

std::vector<int> data; // evil global :)

void CopyData(int *newData, size_t count)
{
   data.assign(newData, newData + count);
}

1
Просте для розуміння і, безумовно, найшвидше рішення (це просто записка поза кадром).
Дон Скотт

Чи deta.assign швидше, ніж data.insert?
Джим


10

Оскільки я можу відредагувати лише власну відповідь, я збираюся зробити складену відповідь з інших відповідей на моє запитання. Дякую всім вам, хто відповів.

Використовуючи std :: copy , це все ще повторюється у фоновому режимі, але вам не потрібно вводити код.

int foo(int* data, int size)
{
   static std::vector<int> my_data; //normally a class variable
   std::copy(data, data + size, std::back_inserter(my_data));
   return 0;
}

Використання звичайної memcpy . Це, мабуть, найкраще використовується для базових типів даних (тобто int), але не для більш складних масивів структур або класів.

vector<int> x(size);
memcpy(&x[0], source, size*sizeof(int));

Я збирався рекомендувати такий підхід.
mmocny

Швидше за все, більш ефективно змінити розмір вектора передньо, якщо ви знаєте розмір достроково, а не використовувати back_inserter.
luke

ви можете додати my_data.reserve (розмір)
David Nehme

Зауважте, що всередині цього робиться саме те, чого, здається, ви хочете уникати. Це не копіювання бітів, це просто циклічне повторення та виклик push_back (). Я думаю, ви хотіли лише уникати введення коду?
mmocny

1
Чи не використовуєте векторний конструктор для копіювання даних?
Мартін Йорк

3

уникаю мечпи, я кажу. Немає приводу возитися з операціями з покажчиком, якщо вам справді не доведеться. Крім того, він буде працювати лише для типів POD (наприклад, int), але буде невдалим, якщо ви маєте справу з типами, які потребують побудови.


8
Можливо, це має бути коментарем до однієї з інших відповідей, оскільки ви насправді не пропонуєте рішення.
finnw

3
int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//source

unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

std::vector<int> myvector (dataArraySize );//target

std::copy ( myints, myints+dataArraySize , myvector.begin() );

//myvector now has 1,2,3,...10 :-)

2
У той час як цей фрагмент коду вітається, і може забезпечити деяку допомогу, було б значно покращено , якщо вона була придбана пояснення про те , як і те, чому це вирішує проблему. Пам’ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз запитує! Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
Toby Speight

4
Зачекайте, що myints?
mavavilj

2

Ще одна відповідь, оскільки людина сказала: "Я не знаю, скільки разів буде викликана моя функція", ви можете використати метод вставки векторного типу так, щоб додати масиви значень до кінця вектора:

vector<int> x;

void AddValues(int* values, size_t size)
{
   x.insert(x.end(), values, values+size);
}

Мені це подобається, тому що реалізація вектора повинна бути в змозі оптимізувати найкращий спосіб вставити значення на основі типу ітератора та самого типу. Ви дещо відповідаєте на виконання стл.

Якщо вам потрібно гарантувати найшвидшу швидкість, і ви знаєте, що ваш тип - тип POD, тоді я рекомендую метод зміни розміру у відповіді Томаса:

vector<int> x;

void AddValues(int* values, size_t size)
{
   size_t old_size(x.size());
   x.resize(old_size + size, 0);
   memcpy(&x[old_size], values, size * sizeof(int));
}

1

На додаток до методів, представлених вище, вам потрібно переконатися, що ви використовуєте або std :: Vector.reserve (), std :: Vector.resize (), або конструюєте вектор за розміром, щоб переконатися, що ваш вектор має достатньо елементів у це зберігати ваші дані. якщо ні, ви зіпсуєте пам’ять. Це стосується або std :: copy (), або memcpy ().

Це причина використання vector.push_back (), ви не можете записати минуле кінця вектора.


Якщо ви використовуєте back_inserter, вам не потрібно попередньо резервувати розмір вектора, в який ви копіюєте. back_inserter робить push_back ().
Джон Дайблінг

0

Припустимо, ви знаєте, наскільки великий елемент у векторі:

std::vector<int> myArray;
myArray.resize (item_count, 0);
memcpy (&myArray.front(), source, item_count * sizeof(int));

http://www.cppreference.com/wiki/stl/vector/start


Чи це не залежить від реалізації std :: vector?
ReaperUnreal

Це жахливо! Ви заповнюєте масив двічі, один з "0", а потім з відповідними значеннями. Просто зробіть: std :: vector <int> myArray (джерело, джерело + item_count); і довіряйте вашому компілятору, щоб створити memcpy!
Кріс Джефферсон

Довіряйте своєму компілятору створити __memcpy_int_aligned; це повинно бути ще швидше
MSalters
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.