วิธีการเริ่มต้นหน่วยความจำด้วยตัวดำเนินการใหม่ใน C ++


176

ฉันเพิ่งเริ่มเข้าสู่ C ++ และฉันต้องการรับนิสัยที่ดี หากฉันเพิ่งจัดสรรอาเรย์ประเภทที่intมีnewโอเปอเรเตอร์ฉันจะกำหนดค่าเริ่มต้นให้เป็น 0 ได้อย่างไรโดยไม่ต้องวนลูปเองทั้งหมด? ฉันควรจะเพียงแค่ใช้memset? มีวิธี“ C ++” ที่จะทำหรือไม่


19
หากคุณต้องการเก็บนิสัยที่ดีของ C ++ ให้หลีกเลี่ยงการใช้อาร์เรย์โดยตรงและใช้เวกเตอร์แทน เวกเตอร์จะเริ่มต้นรายการทั้งหมดโดยไม่คำนึงถึงประเภทและจากนั้นคุณไม่จำเป็นต้องจำให้เรียกผู้ดำเนินการลบ []
brianegge

@brianegge: ถ้าฉันต้องการส่งอาเรย์ไปยังฟังก์ชัน C ภายนอกฉันจะให้เวกเตอร์นั้นได้ไหม
Dreamlax

12
&vector[0]คุณสามารถส่งผ่าน
jamesdlin

แน่นอนว่าเมื่อคุณส่งอาร์เรย์ไปยังฟังก์ชัน C โดยทั่วไปคุณจะต้องระบุตัวชี้ไปยังองค์ประกอบแรก & vector [0] ดังที่ @jamesdlin กล่าวและขนาดของอาร์เรย์ที่ระบุไว้ในกรณีนี้โดย vector.size ()
Trebor Rude

ที่เกี่ยวข้อง (ถามประเภทที่ไม่ใช่อาร์เรย์): stackoverflow.com/questions/7546620/…
Aconcagua

คำตอบ:


392

มันเป็นคุณสมบัติที่รู้จักกันน้อยที่น่าประหลาดใจของ C ++ (ดังที่เห็นได้จากความจริงที่ว่าไม่มีใครให้คำตอบนี้) แต่จริงๆแล้วมันมีไวยากรณ์พิเศษสำหรับการเริ่มต้นค่าอาร์เรย์:

new int[10]();

โปรดทราบว่าคุณต้องใช้วงเล็บที่ว่างเปล่าตัวอย่างเช่นคุณไม่สามารถใช้(0)หรืออย่างอื่นได้ (ซึ่งเป็นสาเหตุที่เป็นประโยชน์สำหรับการเริ่มต้นค่าเท่านั้น)

สิ่งนี้ได้รับอนุญาตอย่างชัดเจนโดย ISO C ++ 03 5.3.4 [expr.new] / 15 ซึ่งกล่าวว่า:

นิพจน์ใหม่ที่สร้างวัตถุชนิดTเริ่มต้นวัตถุดังต่อไปนี้:

...

  • หาก new-initializer เป็นของฟอร์ม()รายการนั้นจะถูกกำหนดค่าเริ่มต้น (8.5)

และไม่ จำกัด ประเภทที่อนุญาตให้ใช้ในขณะที่(expression-list)แบบฟอร์มนั้นถูก จำกัด อย่างชัดเจนโดยกฎเพิ่มเติมในส่วนเดียวกันซึ่งไม่อนุญาตประเภทอาเรย์


1
ในขณะที่ฉันยอมรับว่าสิ่งนี้เป็นที่รู้จักน้อยฉันไม่สามารถ (ทั้งหมด) เห็นด้วยว่ามันน่าประหลาดใจมาก - มันถูกเพิ่มเข้ามาใน C ++ 03 ซึ่งคนส่วนใหญ่ดูเหมือนจะไม่สนใจเลย (เพราะนี่เป็นหนึ่งในสิ่งใหม่ไม่กี่อย่าง มันเพิ่ม)
Jerry Coffin

2
@Jerry: ฉันต้องยอมรับว่าฉันยังไม่รู้ (อาจเป็นเพราะเมื่อฉันได้อ่านมาตรฐานมันเป็น C ++ 03 แล้ว) ที่กล่าวมานั้นเป็นเรื่องที่น่าแปลกใจที่การใช้งานทั้งหมดที่ฉันรู้จักสนับสนุน (ฉันเดาว่ามันเป็นเพราะเรื่องเล็กน้อยที่จะนำไปใช้)
Pavel Minaev

2
ใช่มันเป็นเรื่องเล็กน้อยที่จะใช้ เท่าที่เป็นใหม่ทั้งหมด "การเริ่มต้นค่า" เป็นของใหม่ใน C ++ 03
เจอร์รี่โลงศพ

34
ใน C ++ 11 คุณสามารถใช้ initializtion เหมือนกันเช่นกัน: new int[10] {}. นอกจากนี้คุณยังสามารถระบุค่าเพื่อเริ่มต้นด้วย:new int[10] {1,2,3}
bames53

โปรดอย่าสับสนเริ่มต้นเริ่มต้นด้วยค่าเริ่มต้น: พวกเขาทั้งสองมีการกำหนดไว้อย่างชัดเจนในมาตรฐานและมีการเริ่มต้นที่แตกต่างกัน
Deduplicator

25

สมมติว่าคุณต้องการอาร์เรย์จริงๆและไม่ใช่ std :: vector, "C ++ way" จะเป็นเช่นนี้

#include <algorithm> 

int* array = new int[n]; // Assuming "n" is a pre-existing variable

std::fill_n(array, n, 0); 

แต่โปรดทราบว่าภายใต้ประทุนนี้จริง ๆ แล้วยังเป็นเพียงแค่วนรอบที่กำหนดแต่ละองค์ประกอบให้กับ 0 (ไม่มีทางที่จะทำได้อีกเลยยกเว้นสถาปัตยกรรมพิเศษด้วยการสนับสนุนระดับฮาร์ดแวร์)


ฉันไม่สนใจว่าลูปจะถูกนำไปใช้ภายใต้ฟังก์ชั่นหรือไม่ฉันแค่อยากจะรู้ว่าฉันต้องใช้ลูปนั้นหรือไม่ ขอบคุณสำหรับทิป.
Dreamlax

4
คุณอาจจะแปลกใจ ฉันเคยเป็น. ใน STL ของฉัน (ทั้ง GCC และ Dinkumware) std :: copy จะกลายเป็น memcpy หากตรวจพบว่ามันถูกเรียกด้วยบิวด์อิน ฉันจะไม่แปลกใจถ้า std :: fill_n ใช้ memset
Brian Neal

2
Nope ใช้ 'การกำหนดค่าเริ่มต้น' เพื่อตั้งค่าสมาชิกทั้งหมดเป็น 0
Martin York

24

มีหลายวิธีในการจัดสรรอาเรย์ประเภทที่อยู่ภายในและวิธีการเหล่านี้ทั้งหมดนั้นถูกต้องแม้ว่าวิธีใดที่จะเลือกขึ้นอยู่กับ ...

การเริ่มต้นด้วยตนเองขององค์ประกอบทั้งหมดในลูป

int* p = new int[10];
for (int i = 0; i < 10; i++)
{
    p[i] = 0;
}

ใช้std::memsetฟังก์ชั่นจาก<cstring>

int* p = new int[10];
std::memset(p, 0, sizeof(int) * 10);

ใช้std::fill_nอัลกอริทึมจาก<algorithm>

int* p = new int[10];
std::fill_n(p, 10, 0);

ใช้std::vectorภาชนะ

std::vector<int> v(10); // elements zero'ed

หากมีC ++ 0xให้ใช้คุณลักษณะรายการเริ่มต้น

int a[] = { 1, 2, 3 }; // 3-element static size array
vector<int> v = { 1, 2, 3 }; // 3-element array but vector is resizeable in runtime

1
ควรเป็น vector <int> หากคุณเพิ่ม p = new int [10] () คุณมีรายการทั้งหมด
karsten

@mloskot ในกรณีแรกที่คุณได้เริ่มต้นอาร์เรย์โดยใช้ "ใหม่" วิธีการอ้างอิงโดยจะเกิดขึ้น? ถ้าฉันใช้รูปแบบint array[SIZE] ={1,2,3,4,5,6,7};สัญกรณ์ฉันสามารถใช้void rotateArray(int (& input)[SIZE], unsigned int k);จะเป็นการประกาศฟังก์ชั่นของฉันสิ่งที่จะเป็นเมื่อใช้การประชุมครั้งแรก? ข้อเสนอแนะใด ๆ
Anu

1
ฉันกลัวตัวอย่างที่มีstd::memsetเป็นสิ่งที่ผิด - คุณผ่าน 10 ดูเหมือนว่าจะคาดหวังว่าจำนวนไบต์ - ดูen.cppreference.com/w/cpp/string/byte/memset (ฉันคิดว่าสิ่งนี้แสดงให้เห็นอย่างชัดเจนว่าทำไมเราควรหลีกเลี่ยงการสร้างระดับต่ำเช่นนี้เมื่อเป็นไปได้)
Suma

@ ซูมาจับที่ยอดเยี่ยม! แก้ไขแล้ว. ดูเหมือนว่าจะเป็นผู้สมัครสำหรับข้อผิดพลาดเก่าทศวรรษ :-) ใช่ฉันเห็นด้วยกับความคิดเห็นของคุณ
mloskot

7

หากหน่วยความจำที่คุณกำลังจัดสรรเป็นคลาสที่มีตัวสร้างที่ทำสิ่งที่มีประโยชน์ตัวดำเนินการใหม่จะเรียกตัวสร้างนั้นและปล่อยให้วัตถุของคุณเริ่มต้นได้

แต่ถ้าคุณจัดสรรPODหรือสิ่งที่ไม่มีคอนสตรัคเตอร์ที่เริ่มต้นสถานะของวัตถุคุณจะไม่สามารถจัดสรรหน่วยความจำและเริ่มต้นหน่วยความจำนั้นด้วยตัวดำเนินการใหม่ในการดำเนินการครั้งเดียว อย่างไรก็ตามคุณมีหลายตัวเลือก:

1) ใช้ตัวแปรสแต็กแทน คุณสามารถจัดสรรและกำหนดค่าเริ่มต้นได้ในขั้นตอนเดียวดังนี้:

int vals[100] = {0};  // first element is a matter of style

2) memset()การใช้งาน โปรดทราบว่าหากวัตถุที่คุณกำลังจัดสรรไม่ใช่PODจดจำมันเป็นความคิดที่ไม่ดี ตัวอย่างหนึ่งที่เฉพาะเจาะจงคือถ้าคุณจำคลาสที่มีฟังก์ชั่นเสมือนจริงคุณจะระเบิด vtable และปล่อยให้วัตถุของคุณอยู่ในสถานะที่ใช้ไม่ได้

3) ระบบปฏิบัติการหลายระบบมีการเรียกที่ทำในสิ่งที่คุณต้องการจัดสรรให้กับฮีปและเริ่มต้นข้อมูลเป็นบางสิ่ง ตัวอย่าง Windows จะเป็นVirtualAlloc()

4) นี่คือตัวเลือกที่ดีที่สุด หลีกเลี่ยงการจัดการหน่วยความจำด้วยตัวเองเลย คุณสามารถใช้คอนเทนเนอร์ STL เพื่อทำอะไรก็ได้ที่คุณจะทำกับหน่วยความจำแบบดิบรวมถึงการจัดสรรและการเริ่มต้นทั้งหมดในคราวเดียว:

std::vector<int> myInts(100, 0);  // creates a vector of 100 ints, all set to zero

6

ใช่นั่นคือ:

std::vector<int> vec(SIZE, 0);

ใช้เวกเตอร์แทนอาร์เรย์ที่จัดสรรแบบไดนามิก ประโยชน์รวมถึงไม่ต้องกังวลกับการลบอาร์เรย์อย่างชัดเจน (จะถูกลบเมื่อเวกเตอร์ออกนอกขอบเขต) และหน่วยความจำจะถูกลบโดยอัตโนมัติแม้ว่าจะมีข้อยกเว้นโยน

แก้ไข: เพื่อหลีกเลี่ยงการต่อไดรฟ์โดย downvotes จากคนที่ไม่รำคาญที่จะอ่านความคิดเห็นด้านล่างนี้ฉันควรจะทำให้มันชัดเจนมากขึ้นว่าคำตอบนี้ไม่ได้พูดแบบเวกเตอร์ที่เป็นเสมอคำตอบที่เหมาะสม แต่แน่ใจว่าเป็นวิธี C ++ มากกว่า "ด้วยตนเอง" ทำให้แน่ใจว่าจะลบอาร์เรย์

ขณะนี้มี C ++ 11, นอกจากนี้ยังมี std :: array ที่จำลองอาร์เรย์ขนาดคงที่ (vs vector ที่สามารถเติบโตได้) นอกจากนี้ยังมี std :: unique_ptr ที่จัดการอาเรย์ที่จัดสรรแบบไดนามิก (ที่สามารถรวมกับการเริ่มต้นตามที่ได้รับคำตอบในคำตอบอื่น ๆ สำหรับคำถามนี้) สิ่งเหล่านี้เป็นวิธี C ++ มากกว่าการจัดการตัวชี้ไปยังอาร์เรย์ด้วยตนเอง IMHO


11
สิ่งนี้ไม่ได้ตอบคำถามที่ถาม
John Knoeller

1
ฉันควรใช้std::vectorแทนการจัดสรรอาร์เรย์แบบไดนามิกเสมอหรือไม่ ประโยชน์ของการใช้อาร์เรย์เหนือเวกเตอร์คืออะไรและในทางกลับกัน
Dreamlax

1
@John Knoller: OP ถามเกี่ยวกับวิธีการทำ C ++ ฉันจะบอกว่าเวกเตอร์นั้นเป็นวิธี c ++ แน่นอนว่าคุณถูกต้องว่าอาจมีสถานการณ์ที่ยังคงต้องเรียกใช้อาเรย์แบบธรรมดาและไม่ทราบว่าสถานการณ์ของ OP อาจเป็นแบบใด ฉันคาดเดาไม่ได้เนื่องจากดูเหมือนว่าเป็นไปได้ที่ OP ไม่ทราบเกี่ยวกับเวกเตอร์
villintehaspam

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

2
@villintehaspam: ไม่ไม่ใช่วิธี C ++ ที่จะทำ มันเป็นวิธี Java ที่จะทำมัน การเกาะติดvectorทุกที่โดยไม่คำนึงถึงบริบทเรียกว่า "การเขียนโค้ด Java ใน C ++"
AnT

2

std::fillเป็นวิธีหนึ่ง ใช้ตัววนซ้ำสองตัวและค่าเพื่อเติมภูมิภาคด้วย หรือสำหรับลูป (ฉันคิดว่า) เป็นวิธี C ++ มากกว่า

สำหรับการตั้งค่าอาร์เรย์ของชนิดจำนวนเต็มดั้งเดิมเป็น 0 โดยเฉพาะmemsetนั้นใช้ได้แม้ว่ามันอาจทำให้คิ้วเพิ่มขึ้น พิจารณาด้วยcallocถึงแม้ว่ามันจะไม่สะดวกในการใช้จาก C ++ เนื่องจากการส่ง

สำหรับส่วนของฉันฉันมักจะใช้วง

(ฉันไม่ชอบที่จะคาดเดาความตั้งใจของคนที่สอง แต่มันเป็นความจริงนั่นstd::vectorคือทุกสิ่งเท่าเทียมกันดีกว่าที่จะใช้new[])


1

คุณสามารถใช้ memset:

int myArray[10];
memset( myArray, 0, 10 * sizeof( int ));

ฉันเข้าใจว่าฉันสามารถใช้ได้memsetแต่ฉันไม่แน่ใจว่านี่เป็นวิธี C ++ ในการเข้าถึงปัญหาหรือไม่
Dreamlax

1
มันไม่ได้เป็น 'วิธี C ++' แต่จริงๆแล้วมันไม่ได้เป็นอาเรย์
Pete Kirkham

1
@ gbrandt: ซึ่งจะบอกว่ามันทำงานได้ไม่ดีนักใน C หรือ C ++ มันทำงานได้กับค่าส่วนใหญ่ของประเภทที่เป็นถ่านหรือถ่านที่ไม่ได้ลงนาม มันใช้งานได้กับค่าส่วนใหญ่ประเภทนี้คือ 0 (อย่างน้อยที่สุดในการปรับใช้ส่วนใหญ่) มิฉะนั้นแล้วมันก็ไร้ประโยชน์
Jerry Coffin

1
10 * sizeof( *myArray )10 * sizeof( int )เป็นเอกสารที่มากขึ้นและการเปลี่ยนแปลงหลักฐานกว่า
Kevin Reid

1
ไม่ว่าในกรณีใด OP มีอาเรย์และชุดข้อมูลดิบเป็นวิธีที่เร็วและง่ายที่สุดในการเป็นศูนย์อาเรย์นั้น
Gregor Brandt

-1

std::vectorโดยปกติสำหรับรายการแบบไดนามิกของรายการคุณใช้

โดยทั่วไปฉันใช้ memset หรือลูปสำหรับการจัดสรรแบบไดนามิกหน่วยความจำดิบขึ้นอยู่กับว่าฉันคาดว่าตัวแปรของพื้นที่จะเป็นรหัสในอนาคต

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