คุณคัดลอกเนื้อหาของอาร์เรย์ไปยัง std :: vector ใน C ++ โดยไม่ต้องวนซ้ำได้อย่างไร


123

ฉันมีอาร์เรย์ของค่าที่ส่งผ่านไปยังฟังก์ชันของฉันจากส่วนอื่นของโปรแกรมที่ฉันต้องการเก็บไว้เพื่อประมวลผลในภายหลัง เนื่องจากฉันไม่รู้ว่าฟังก์ชันของฉันจะถูกเรียกกี่ครั้งก่อนถึงเวลาประมวลผลข้อมูลฉันจึงต้องการโครงสร้างการจัดเก็บแบบไดนามิกดังนั้นฉันจึงเลือกไฟล์std::vector. ฉันไม่ต้องการให้มีการทำวงมาตรฐานค่าทั้งหมดเป็นรายบุคคลก็จะดีถ้าฉันสามารถคัดลอกทั้งหมดโดยใช้สิ่งที่คล้ายกับpush_backmemcpy

คำตอบ:


117

หากคุณสามารถสร้างเวกเตอร์ได้หลังจากที่คุณได้รับอาร์เรย์และขนาดอาร์เรย์แล้วคุณสามารถพูดได้ว่า:

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

... สมมติว่าaเป็นอาร์เรย์ของคุณและnเป็นจำนวนองค์ประกอบที่มี มิฉะนั้นstd::copy()w / resize()จะทำเคล็ดลับ

ฉันจะอยู่ห่าง ๆmemcpy()จนกว่าคุณจะมั่นใจได้ว่าค่านั้นเป็นประเภทข้อมูลธรรมดา (POD)

นอกจากนี้ที่น่าสังเกตว่าไม่มีสิ่งเหล่านี้หลีกเลี่ยงการวนซ้ำจริงๆ - เป็นเพียงคำถามว่าคุณต้องเห็นในรหัสของคุณหรือไม่ ประสิทธิภาพรันไทม์ O (n) หลีกเลี่ยงไม่ได้สำหรับการคัดลอกค่า

สุดท้ายโปรดทราบว่าอาร์เรย์สไตล์ C เป็นคอนเทนเนอร์ที่ถูกต้องอย่างสมบูรณ์แบบสำหรับอัลกอริทึม STL ส่วนใหญ่ - ตัวชี้ดิบเทียบเท่ากับbegin()และ ( ptr + n) เทียบเท่ากับend().


4
สาเหตุที่การวนซ้ำและการเรียก push_back ไม่ดีเป็นเพราะคุณอาจบังคับให้เวกเตอร์ปรับขนาดหลาย ๆ ครั้งหากอาร์เรย์ยาวพอ
bradtgmurray

@bradtgmurray: ฉันคิดว่าการใช้งานตัวสร้างเวกเตอร์ "ตัววนซ้ำสองตัว" อย่างสมเหตุสมผลที่ฉันแนะนำข้างต้นจะเรียก std :: distance () ก่อนบนตัวทำซ้ำสองตัวเพื่อให้ได้จำนวนองค์ประกอบที่ต้องการจากนั้นจึงจัดสรรเพียงครั้งเดียว
Drew Hall

4
@bradtgmurray: แม้แต่ push_back () ก็ไม่เลวร้ายนักเนื่องจากการเติบโตแบบทวีคูณของเวกเตอร์ (หรือที่เรียกว่า "เวลาคงที่ที่ตัดจำหน่าย") ฉันคิดว่ารันไทม์จะอยู่ในลำดับที่ 2x แย่ลงในกรณีที่แย่ที่สุด
Drew Hall

2
และถ้าเวกเตอร์มีอยู่แล้ว vec.clear (); vec.insert (vec.begin (), a, a + n); ก็ใช้ได้เช่นกัน จากนั้นคุณไม่จำเป็นต้องใช้ตัวชี้เป็นเพียงตัวทำซ้ำและการกำหนดเวกเตอร์จะเป็นแบบล้มเหลวทั่วไป (และวิธี C ++ / STL)
MP24

6
อีกทางเลือกหนึ่งเมื่อไม่สามารถสร้างได้จะถูกกำหนด : vec.assign(a, a+n)ซึ่งจะกะทัดรัดกว่าการคัดลอกและปรับขนาด
mMontu

210

มีคำตอบมากมายที่นี่และทุกคนจะได้งานทำ

อย่างไรก็ตามมีคำแนะนำที่ทำให้เข้าใจผิด!

นี่คือตัวเลือก:

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 โดยใช้ vector :: insert เป็นวิธีที่ดีที่สุดสำหรับสถานการณ์ของ bsruth

นี่คือรายละเอียดบางส่วนที่เต็มไปด้วยเลือด:

วิธีที่ 1น่าจะเข้าใจง่ายที่สุด เพียงแค่คัดลอกแต่ละองค์ประกอบจากอาร์เรย์แล้วดันเข้าไปที่ด้านหลังของเวกเตอร์ อนิจจามันช้า เนื่องจากมีการวนซ้ำ (โดยนัยกับฟังก์ชันการคัดลอก) แต่ละองค์ประกอบจึงต้องได้รับการปฏิบัติทีละรายการ ไม่มีการปรับปรุงประสิทธิภาพใด ๆ จากการที่เรารู้ว่าอาร์เรย์และเวกเตอร์เป็นบล็อกที่อยู่ติดกัน

วิธีที่ 2เป็นการปรับปรุงประสิทธิภาพที่แนะนำสำหรับวิธีที่ 1 เพียงจองขนาดของอาร์เรย์ไว้ล่วงหน้าก่อนเพิ่ม สำหรับอาร์เรย์ขนาดใหญ่สิ่งนี้อาจช่วยได้ อย่างไรก็ตามคำแนะนำที่ดีที่สุดคือห้ามใช้เงินสำรองเว้นแต่การทำโปรไฟล์จะแนะนำว่าคุณอาจได้รับการปรับปรุง (หรือคุณต้องตรวจสอบให้แน่ใจว่าตัวทำซ้ำของคุณจะไม่ถูกยกเลิก) Bjarne ตกลง บังเอิญฉันพบว่าวิธีนี้ทำงานช้าที่สุดเกือบตลอดเวลาแม้ว่าฉันจะพยายามอธิบายอย่างละเอียดว่าทำไมมันถึงช้ากว่าวิธีที่ 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 เพื่อรับตัวชี้โดยไม่ต้องยกเลิกการอ้างอิงก่อน
Drew Hall

2
@Drew: ใช่คุณทำได้อย่างน้อยใน C มันถูกกำหนดไว้&exprว่าไม่ได้ประเมินexprแต่จะคำนวณที่อยู่ของมันเท่านั้น และตัวชี้หนึ่งที่ผ่านมาสุดท้ายคือองค์ประกอบที่ถูกต้องสมบูรณ์มากเกินไป
Roland Illig

2
คุณลองทำวิธีที่ 4 กับ 2 แล้วหรือยัง? คือการสำรองพื้นที่ก่อนใส่ ดูเหมือนว่าหากขนาดข้อมูลใหญ่การแทรกหลายรายการจะต้องมีการจัดสรรซ้ำหลายครั้ง เนื่องจากเราทราบขนาดเบื้องต้นเราจึงสามารถทำการจัดสรรใหม่ก่อนที่จะแทรก
Jorge Leitao

2
@MattyT ประเด็นวิธีที่ 5 คืออะไร? ทำไมต้องทำสำเนาข้อมูลระดับกลาง
Ruslan

2
โดยส่วนตัวแล้วฉันอยากจะได้กำไรจากอาร์เรย์ที่สลายไปเป็นพอยน์เตอร์โดยอัตโนมัติ: dataVec.insert(dataVec.end(), dataArray, dataArray + dataArraySize);- ปรากฏชัดเจนกว่าสำหรับฉันมาก ไม่สามารถรับอะไรจากวิธีที่ 5 ได้เพียง แต่ดูไม่มีประสิทธิภาพ - เว้นแต่คอมไพเลอร์จะสามารถปรับเวกเตอร์ให้เหมาะสมได้อีกครั้ง
Aconcagua

37

หากสิ่งที่คุณทำคือการแทนที่ข้อมูลที่มีอยู่คุณสามารถทำได้

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

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

1
เข้าใจง่ายและเป็นวิธีแก้ปัญหาที่เร็วที่สุด (เป็นเพียงเบื้องหลังของ memcpy เท่านั้น)
Don Scott

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
Wjy ไม่ใช้ตัวสร้างเวกเตอร์เพื่อคัดลอกข้อมูล?
Martin York

3

ฉันพูดหลีกเลี่ยง memcpy ไม่มีเหตุผลที่จะยุ่งกับการทำงานของตัวชี้เว้นแต่คุณจะต้องทำจริงๆ นอกจากนี้จะใช้ได้เฉพาะกับประเภท 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);
}

ฉันชอบวิธีนี้เพราะการใช้งานเวกเตอร์ควรจะสามารถปรับให้เหมาะสมสำหรับวิธีที่ดีที่สุดในการแทรกค่าตามประเภทตัววนซ้ำและประเภทของตัวมันเอง คุณค่อนข้างตอบกลับเกี่ยวกับการใช้งาน stl

หากคุณต้องการรับประกันความเร็วที่เร็วที่สุดและคุณรู้ว่าประเภทของคุณเป็นประเภท POD ฉันขอแนะนำวิธีการปรับขนาดในคำตอบของ 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

นอกเหนือจากวิธีการที่นำเสนอข้างต้นแล้วคุณต้องแน่ใจว่าคุณใช้ std :: Vector.reserve (), std :: Vector.resize () หรือสร้างเวกเตอร์ให้มีขนาดเพื่อให้แน่ใจว่าเวกเตอร์ของคุณมีองค์ประกอบเพียงพอใน เพื่อเก็บข้อมูลของคุณ ถ้าไม่คุณจะทำให้หน่วยความจำเสียหาย ซึ่งเป็นจริงสำหรับ std :: copy () หรือ memcpy ()

นี่คือเหตุผลที่ต้องใช้ vector.push_back () คุณไม่สามารถเขียนผ่านจุดสิ้นสุดของเวกเตอร์ได้


หากคุณใช้ back_inserter คุณไม่จำเป็นต้องจองขนาดของเวกเตอร์ที่คุณกำลังคัดลอกไว้ล่วงหน้า back_inserter ทำ push_back ()
John Dibling

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 (source, source + item_count); และไว้วางใจคอมไพเลอร์ของคุณในการสร้าง memcpy!
Chris Jefferson

เชื่อถือคอมไพลเลอร์ของคุณในการสร้าง __memcpy_int_aligned; ซึ่งน่าจะเร็วกว่านี้
MSalters
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.