Làm thế nào để bạn sao chép nội dung của một mảng vào một std :: vector trong C ++ mà không cần lặp?


121

Tôi có một mảng các giá trị được truyền cho hàm của tôi từ một phần khác của chương trình mà tôi cần lưu trữ để xử lý sau. Vì tôi không biết chức năng của mình sẽ được gọi bao nhiêu lần trước khi đến lúc xử lý dữ liệu, tôi cần một cấu trúc lưu trữ động, vì vậy tôi đã chọn một std::vector. Tôi không muốn phải thực hiện vòng lặp tiêu chuẩn cho push_backtất cả các giá trị riêng lẻ, thật tuyệt nếu tôi có thể sao chép tất cả bằng cách sử dụng một cái gì đó tương tự memcpy.

Câu trả lời:


116

Nếu bạn có thể xây dựng vectơ sau khi bạn nhận được kích thước mảng và kích thước mảng, bạn có thể chỉ cần nói:

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

... giả sử alà mảng của bạn và nlà số phần tử mà nó chứa. Nếu không, std::copy()w / resize()sẽ thực hiện các mẹo.

Tôi sẽ tránh xa memcpy()trừ khi bạn có thể chắc chắn rằng các giá trị là loại dữ liệu cũ (POD).

Ngoài ra, đáng lưu ý rằng không ai trong số này thực sự tránh được vòng lặp for - đó chỉ là câu hỏi liệu bạn có phải xem nó trong mã của mình hay không. Hiệu suất thời gian chạy O (n) là không thể tránh khỏi để sao chép các giá trị.

Cuối cùng, lưu ý rằng mảng kiểu C là các thùng chứa hoàn toàn hợp lệ cho hầu hết các thuật toán STL - con trỏ thô tương đương begin()và ( ptr + n) tương đương với end().


4
Lý do tại sao lặp và gọi Push_back là xấu là vì bạn có thể buộc vectơ thay đổi kích thước nhiều lần nếu mảng đủ dài.
bradtgm bồ

@bradtgm bồ: Tôi nghĩ rằng bất kỳ triển khai hợp lý nào của hàm tạo vectơ "hai vòng lặp" mà tôi đề xuất ở trên sẽ gọi std :: distance () trước trên hai trình vòng lặp để lấy số phần tử cần thiết, sau đó chỉ phân bổ một lần.
Hội trường Drew

4
@bradtgm bồ: Ngay cả Push_back () sẽ không quá tệ vì sự tăng trưởng theo cấp số nhân của vectơ (còn gọi là "thời gian không đổi được khấu hao"). Tôi nghĩ thời gian chạy sẽ chỉ ở mức tồi tệ gấp 2 lần trong trường hợp xấu nhất.
Hội trường Drew

2
Và nếu vectơ đã ở đó, vec.clear (); vec.insert (vec.begin (), a, a + n); cũng sẽ làm việc Sau đó, bạn thậm chí sẽ không yêu cầu phải là một con trỏ, chỉ là một trình vòng lặp và việc gán vectơ sẽ là chung chung (và cách C ++ / STL).
MP24

6
Một cách khác khi không thể xây dựng sẽ được chỉ định : vec.assign(a, a+n), sẽ nhỏ gọn hơn so với sao chép và thay đổi kích thước.
mMontu

209

Đã có nhiều câu trả lời ở đây và chỉ về tất cả chúng sẽ hoàn thành công việc.

Tuy nhiên có một số lời khuyên sai lệch!

Dưới đây là các tùy chọn:

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());
}

Để cắt ngắn một câu chuyện dài Phương pháp 4, sử dụng vector :: insert, là cách tốt nhất cho kịch bản của bsruth.

Dưới đây là một số chi tiết chính:

Phương pháp 1 có lẽ là dễ hiểu nhất. Chỉ cần sao chép từng phần tử từ mảng và đẩy nó vào mặt sau của vectơ. Than ôi, nó chậm. Bởi vì có một vòng lặp (ngụ ý với chức năng sao chép), mỗi phần tử phải được xử lý riêng lẻ; không có cải tiến hiệu suất nào có thể được thực hiện dựa trên thực tế là chúng ta biết mảng và vectơ là các khối liền kề nhau.

Phương pháp 2 là một cải tiến hiệu suất được đề xuất cho Phương pháp 1; chỉ cần dự trữ trước kích thước của mảng trước khi thêm nó. Đối với mảng lớn, điều này có thể giúp đỡ. Tuy nhiên, lời khuyên tốt nhất ở đây là không bao giờ sử dụng dự trữ trừ khi hồ sơ cho thấy bạn có thể có được một cải tiến (hoặc bạn cần đảm bảo các trình lặp của mình sẽ không bị vô hiệu). Bjarne đồng ý . Tình cờ, tôi thấy rằng phương pháp này thực hiện chậm nhất trong hầu hết thời gian mặc dù tôi đang cố gắng giải thích toàn diện tại sao nó thường chậm hơn đáng kể so với phương pháp 1 ...

Phương pháp 3 là giải pháp trường học cũ - ném một số C vào vấn đề! Hoạt động tốt và nhanh chóng cho các loại POD. Trong trường hợp này, thay đổi kích thước được yêu cầu phải được gọi vì memcpy hoạt động bên ngoài giới hạn của vectơ và không có cách nào để nói với vectơ rằng kích thước của nó đã thay đổi. Ngoài việc là một giải pháp xấu xí (sao chép byte!) Hãy nhớ rằng điều này chỉ có thể được sử dụng cho các loại POD . Tôi sẽ không bao giờ sử dụng giải pháp này.

Phương pháp 4 là cách tốt nhất để đi. Nó có nghĩa là rõ ràng, nó (thường) nhanh nhất và nó hoạt động cho bất kỳ đối tượng. Không có nhược điểm nào khi sử dụng phương pháp này cho ứng dụng này.

Phương thức 5 là một tinh chỉnh trên Phương pháp 4 - sao chép mảng vào một vectơ và sau đó nối thêm nó. Tùy chọn tốt - nói chung là nhanh và rõ ràng.

Cuối cùng, bạn biết rằng bạn có thể sử dụng vectơ thay cho mảng, phải không? Ngay cả khi một hàm mong đợi các mảng kiểu c, bạn có thể sử dụng các vectơ:

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

Hy vọng rằng sẽ giúp được ai đó ngoài kia!


6
Bạn không thể tham khảo một cách an toàn và có thể tham khảo "& dataArray [dataArraySize]" - đó là thông báo cho một con trỏ / iterator quá khứ. Thay vào đó, bạn có thể nói dataArray + dataArraySize để lấy con trỏ mà không cần phải hủy đăng ký trước.
Hội trường Drew

2
@Drew: có, bạn có thể, ít nhất là trong C. Nó được xác định là &exprkhông đánh giá expr, nó chỉ tính toán địa chỉ của nó. Và một con trỏ một quá khứ yếu tố cuối cùng là hoàn toàn hợp lệ, quá.
Roland Illig

2
Bạn đã thử làm phương pháp 4 với 2 chưa? tức là đặt chỗ trước khi chèn. Có vẻ như nếu kích thước dữ liệu lớn, nhiều lần chèn sẽ cần nhiều phân bổ lại. Bởi vì chúng tôi biết kích thước của một tiên nghiệm, chúng tôi có thể thực hiện việc tái phân bổ, trước khi chèn.
Jorge Leitao

2
@MattyT điểm của phương pháp 5 là gì? Tại sao tạo một bản sao trung gian của dữ liệu?
Ruslan

2
Cá nhân tôi muốn tự động kiếm lợi từ mảng phân rã thành con trỏ: dataVec.insert(dataVec.end(), dataArray, dataArray + dataArraySize);- xuất hiện rõ ràng hơn nhiều đối với tôi. Không thể đạt được bất cứ điều gì từ phương thức 5, chỉ trông khá kém hiệu quả - trừ khi trình biên dịch có thể tối ưu hóa lại vectơ.
Aconcagua

37

Nếu tất cả những gì bạn đang làm là thay thế dữ liệu hiện có, thì bạn có thể làm điều này

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

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

1
Đơn giản để hiểu và chắc chắn là giải pháp nhanh nhất (nó chỉ là một memcpy đằng sau hậu trường).
Don Scott

Là Deta.assign nhanh hơn data.insert?
Jim


10

Vì tôi chỉ có thể chỉnh sửa câu trả lời của riêng mình, tôi sẽ đưa ra câu trả lời tổng hợp từ các câu trả lời khác cho câu hỏi của tôi. Cảm ơn tất cả các bạn đã trả lời.

Sử dụng std :: copy , điều này vẫn lặp lại trong nền, nhưng bạn không phải gõ mã.

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

Sử dụng memcpy thường xuyên . Điều này có lẽ được sử dụng tốt nhất cho các kiểu dữ liệu cơ bản (ví dụ int) nhưng không phải cho các mảng hoặc các lớp phức tạp hơn.

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

Tôi sẽ đề nghị phương pháp này.
mmocny

Rất có thể hiệu quả hơn để thay đổi kích thước vectơ của bạn lên phía trước nếu bạn biết kích thước trước thời hạn và không sử dụng back_inserter.
luke

bạn có thể thêm my_data.reserve (kích thước)
David Nehme

Lưu ý rằng trong nội bộ điều này đang làm chính xác những gì bạn dường như muốn tránh. Nó không sao chép bit, nó chỉ lặp và gọi push_back (). Tôi đoán bạn chỉ muốn tránh gõ mã?
mmocny

1
Wjy không sử dụng hàm tạo vector để sao chép dữ liệu?
Martin York

3

tránh memcpy, tôi nói. Không có lý do để gây rối với các hoạt động con trỏ trừ khi bạn thực sự phải làm. Ngoài ra, nó sẽ chỉ hoạt động cho các loại POD (như int) nhưng sẽ thất bại nếu bạn xử lý các loại yêu cầu xây dựng.


8
Có lẽ đây nên là một nhận xét về một trong những câu trả lời khác, vì bạn không thực sự đề xuất một giải pháp.
vây

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
Trong khi đoạn mã này được chào đón và có thể cung cấp một số trợ giúp, nó sẽ được cải thiện rất nhiều nếu nó bao gồm một lời giải thích về cách thứclý do tại sao điều này giải quyết vấn đề. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai, không chỉ là người hỏi bây giờ! Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng.
Toby Speight

4
Đợi đã, cái gì myints?
mavavilj

2

Còn một câu trả lời nữa, vì người này nói "Tôi không biết hàm của mình sẽ được gọi bao nhiêu lần", bạn có thể sử dụng phương thức chèn vectơ như vậy để nối các mảng giá trị vào cuối vectơ:

vector<int> x;

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

Tôi thích cách này vì việc triển khai vectơ nên có thể tối ưu hóa để có cách tốt nhất để chèn các giá trị dựa trên kiểu lặp và chính kiểu đó. Bạn đang phần nào trả lời về việc thực hiện stl.

Nếu bạn cần đảm bảo tốc độ nhanh nhất và bạn biết loại của mình là loại POD thì tôi sẽ đề xuất phương pháp thay đổi kích thước trong câu trả lời của Thomas:

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

Ngoài các phương pháp được trình bày ở trên, bạn cần đảm bảo rằng bạn sử dụng std :: Vector.reserve (), std :: Vector.resize () hoặc xây dựng vectơ theo kích thước, để đảm bảo vectơ của bạn có đủ các phần tử trong nó để giữ dữ liệu của bạn. nếu không, bạn sẽ bị hỏng bộ nhớ. Điều này đúng với cả std :: copy () hoặc memcpy ().

Đây là lý do để sử dụng vector.push_back (), bạn không thể viết qua phần cuối của vectơ.


Nếu bạn đang sử dụng back_inserter, bạn không cần phải đặt trước kích thước của vectơ bạn đang sao chép. back_inserter thực hiện một Push_back ().
John Dibling

0

Giả sử bạn biết vật phẩm trong vector lớn như thế nào:

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


Điều đó không phụ thuộc vào việc thực hiện std :: vector?
ReaperUnreal

Thật kinh khủng! Bạn đang điền vào mảng hai lần, một với '0', sau đó với các giá trị phù hợp. Chỉ cần làm: std :: vector <int> myArray (nguồn, nguồn + item_count); và tin tưởng trình biên dịch của bạn để sản xuất memcpy!
Chris Jefferson

Tin tưởng trình biên dịch của bạn để tạo __memcpy_int_align; điều đó sẽ còn nhanh hơn nữa
MSalters
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.