คำนำ
Java ไม่เหมือนกับ C ++ ตรงกันข้ามกับ hype เครื่อง Java hype ต้องการให้คุณเชื่อว่าเนื่องจาก Java มี C ++ เหมือนวากยสัมพันธ์จึงมีความคล้ายคลึงกับภาษา ไม่มีอะไรเพิ่มเติมจากความจริง ข้อมูลที่ผิดนี้เป็นส่วนหนึ่งของเหตุผลที่โปรแกรมเมอร์ Java ไปที่ C ++ และใช้ไวยากรณ์เหมือน Java โดยไม่เข้าใจความหมายของรหัส
ต่อไปเราไป
แต่ฉันไม่สามารถเข้าใจได้ว่าทำไมเราควรทำเช่นนี้ ฉันคิดว่ามันเกี่ยวข้องกับประสิทธิภาพและความเร็วตั้งแต่เราเข้าถึงโดยตรงไปยังที่อยู่หน่วยความจำ ฉันถูกไหม?
ไปในทางตรงกันข้ามจริง ฮีปนั้นช้ากว่ากองมากเนื่องจากกองซ้อนนั้นง่ายมากเมื่อเทียบกับกอง ตัวแปรหน่วยเก็บข้อมูลอัตโนมัติ (รู้จักกันในชื่อตัวแปรสแต็ก) มี destructors ที่เรียกว่าเมื่อพวกเขาออกจากขอบเขต ตัวอย่างเช่น:
{
std::string s;
}
// s is destroyed here
ในทางตรงกันข้ามถ้าคุณใช้ตัวชี้การจัดสรรแบบไดนามิก destructor นั้นจะต้องถูกเรียกด้วยตนเอง delete
เรียกสิ่งนี้ว่า destructor
{
std::string* s = new std::string;
}
delete s; // destructor called
สิ่งนี้ไม่เกี่ยวข้องกับnew
ไวยากรณ์ที่แพร่หลายใน C # และ Java พวกเขาจะใช้เพื่อวัตถุประสงค์ที่แตกต่างอย่างสิ้นเชิง
ประโยชน์ของการจัดสรรแบบไดนามิก
1. คุณไม่จำเป็นต้องรู้ขนาดของอาร์เรย์ล่วงหน้า
หนึ่งในปัญหาแรกที่โปรแกรมเมอร์ C ++ จำนวนมากพบคือเมื่อพวกเขายอมรับอินพุตโดยพลการจากผู้ใช้คุณสามารถจัดสรรขนาดคงที่สำหรับตัวแปรสแต็กเท่านั้น คุณไม่สามารถเปลี่ยนขนาดของอาร์เรย์ได้เช่นกัน ตัวอย่างเช่น:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
แน่นอนถ้าคุณใช้การstd::string
แทนstd::string
การปรับขนาดตัวเองภายในเพื่อที่จะไม่เป็นปัญหา แต่วิธีแก้ปัญหานี้คือการจัดสรรแบบไดนามิก คุณสามารถจัดสรรหน่วยความจำแบบไดนามิกโดยอิงจากอินพุตของผู้ใช้ตัวอย่างเช่น:
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
หมายเหตุด้านข้าง : หนึ่งข้อผิดพลาดที่ผู้เริ่มต้นทำคือการใช้อาร์เรย์ความยาวผันแปร นี่คือส่วนขยาย GNU และหนึ่งใน Clang เพราะสะท้อนส่วนขยายของ GCC จำนวนมาก ดังนั้นสิ่งต่อไปนี้
int arr[n]
ไม่ควรเชื่อถือ
เนื่องจากฮีปมีขนาดใหญ่กว่าสแต็กมากจึงสามารถจัดสรร / จัดสรรใหม่หน่วยความจำได้มากตามที่เขา / เธอต้องการในขณะที่สแต็กมีข้อ จำกัด
2. อาร์เรย์ไม่ใช่ตัวชี้
นี่เป็นประโยชน์ที่คุณถามอย่างไร คำตอบจะชัดเจนเมื่อคุณเข้าใจความสับสน / ตำนานหลังอาร์เรย์และพอยน์เตอร์ โดยทั่วไปถือว่าพวกเขาเหมือนกัน แต่พวกเขาไม่ได้ ตำนานนี้มาจากความจริงที่ว่าพอยน์เตอร์สามารถห้อยเหมือนอาเรย์และเพราะการสลายตัวของอาเรย์ถึงพอยน์เตอร์ที่ระดับสูงสุดในการประกาศฟังก์ชั่น อย่างไรก็ตามเมื่ออาร์เรย์สลายตัวไปยังตัวชี้แล้วตัวชี้จะสูญเสียsizeof
ข้อมูล ดังนั้นsizeof(pointer)
จะให้ขนาดของตัวชี้เป็นไบต์ซึ่งโดยปกติจะเป็น 8 ไบต์ในระบบ 64 บิต
คุณไม่สามารถกำหนดให้กับอาร์เรย์ได้ แต่เริ่มต้นได้เท่านั้น ตัวอย่างเช่น:
int arr[5] = {1, 2, 3, 4, 5}; // initialization
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
// be given by the amount of members in the initializer
arr = { 1, 2, 3, 4, 5 }; // ERROR
ในทางกลับกันคุณสามารถทำอะไรก็ได้ที่คุณต้องการด้วยพอยน์เตอร์ น่าเสียดายเนื่องจากความแตกต่างระหว่างพอยน์เตอร์และอาร์เรย์เป็นแบบคลื่นใน Java และ C # ผู้เริ่มต้นไม่เข้าใจความแตกต่าง
3. ความแตกต่าง
Java และ C # มีสิ่งอำนวยความสะดวกที่ช่วยให้คุณสามารถจัดการกับวัตถุได้เช่นใช้as
คำหลัก ดังนั้นหากใครบางคนต้องการที่จะปฏิบัติต่อEntity
วัตถุเป็นPlayer
วัตถุใคร ๆ ก็สามารถทำได้Player player = Entity as Player;
สิ่งนี้มีประโยชน์มากถ้าคุณตั้งใจจะเรียกใช้ฟังก์ชั่นบนคอนเทนเนอร์ที่เป็นเนื้อเดียวกันซึ่งควรใช้กับประเภทที่ระบุเท่านั้น การทำงานสามารถทำได้ในลักษณะคล้ายกันด้านล่าง:
std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
if (!test) // not a triangle
e.GenericFunction();
else
e.TriangleOnlyMagic();
}
ดังนั้นถ้าสามเหลี่ยมมีฟังก์ชั่นหมุนมันจะเป็นข้อผิดพลาดของคอมไพเลอร์ถ้าคุณพยายามเรียกมันบนทุกวัตถุของคลาส ใช้dynamic_cast
คุณสามารถจำลองas
คำหลัก เพื่อความชัดเจนหากการร่ายไม่สำเร็จจะส่งคืนพอยน์เตอร์ที่ไม่ถูกต้อง ดังนั้นจึง!test
เป็นชวเลขที่สำคัญสำหรับการตรวจสอบว่าtest
เป็นโมฆะหรือตัวชี้ที่ไม่ถูกต้องซึ่งหมายถึงการโยนล้มเหลว
ประโยชน์ของตัวแปรอัตโนมัติ
หลังจากได้เห็นการจัดสรรแบบไดนามิกที่ยอดเยี่ยมทุกสิ่งสามารถทำได้คุณอาจสงสัยว่าทำไมไม่มีใครไม่ใช้การจัดสรรแบบไดนามิกตลอดเวลา ฉันบอกเหตุผลหนึ่งไปแล้วว่าฮีปช้า และถ้าคุณไม่ต้องการหน่วยความจำทั้งหมดนั้นคุณไม่ควรใช้มันในทางที่ผิด ดังนั้นนี่คือข้อเสียบางอย่างในลำดับที่ไม่มี
มันเป็นข้อผิดพลาดได้ง่าย การจัดสรรหน่วยความจำด้วยตนเองเป็นสิ่งที่อันตรายและคุณมีแนวโน้มที่จะรั่วไหล หากคุณไม่ชำนาญในการใช้ดีบักเกอร์หรือvalgrind
(เครื่องมือรั่วหน่วยความจำ) คุณอาจดึงผมออกจากหัว โชคดีที่ RAII สำนวนและตัวชี้อัจฉริยะช่วยบรรเทาปัญหานี้ได้เล็กน้อย แต่คุณต้องคุ้นเคยกับการฝึกฝนเช่น The Rule Of Three และ Rule Of Five มันเป็นข้อมูลจำนวนมากที่ต้องทำและผู้เริ่มต้นที่ไม่รู้หรือไม่สนใจจะตกหลุมพรางนี้
มันไม่จำเป็น แตกต่างจาก Java และ C # ที่มีการใช้new
คำสำคัญทุกที่ใน C ++ คุณควรใช้เฉพาะเมื่อคุณต้องการ วลีทั่วไปจะไปทุกอย่างดูเหมือนว่าเป็นตอกถ้าคุณมีค้อน ในขณะที่ผู้เริ่มต้นที่เริ่มต้นด้วย C ++ จะกลัวพอยน์เตอร์และเรียนรู้การใช้ตัวแปรสแต็กตามนิสัย, Java และ C # โปรแกรมเมอร์เริ่มโดยใช้พอยน์เตอร์โดยไม่เข้าใจ! นั่นคือก้าวเท้าที่ผิดไป คุณต้องละทิ้งทุกสิ่งที่คุณรู้เพราะไวยากรณ์เป็นสิ่งหนึ่งการเรียนรู้ภาษาก็เป็นอีกสิ่งหนึ่ง
1. (N) RVO - Aka, (ตั้งชื่อ) การเพิ่มประสิทธิภาพมูลค่าส่งคืน
หนึ่งในการเพิ่มประสิทธิภาพของคอมไพเลอร์จำนวนมากทำให้เป็นสิ่งที่เรียกว่าตัดออกและค่าตอบแทนการเพิ่มประสิทธิภาพ สิ่งเหล่านี้สามารถลบล้าง copys ที่ไม่จำเป็นซึ่งมีประโยชน์สำหรับวัตถุที่มีขนาดใหญ่มากเช่นเวกเตอร์ที่มีองค์ประกอบหลายอย่าง โดยทั่วไปวิธีปฏิบัติทั่วไปคือการใช้พอยน์เตอร์เพื่อโอนความเป็นเจ้าของแทนที่จะคัดลอกวัตถุขนาดใหญ่เพื่อย้ายไปมา นี้ได้นำไปสู่การเริ่มแรกของความหมายย้ายและตัวชี้สมาร์ท
หากคุณกำลังใช้พอยน์เตอร์อยู่ (N) RVO จะไม่เกิดขึ้น มันมีประโยชน์มากกว่าและมีข้อผิดพลาดน้อยกว่าที่จะใช้ประโยชน์จาก (N) RVO มากกว่ากลับหรือผ่านพอยน์เตอร์หากคุณกังวลเกี่ยวกับการปรับให้เหมาะสม การรั่วไหลของข้อผิดพลาดสามารถเกิดขึ้นได้หากผู้เรียกฟังก์ชั่นมีหน้าที่รับผิดชอบต่อdelete
วัตถุที่จัดสรรแบบไดนามิกและเช่นนั้น มันอาจเป็นเรื่องยากที่จะติดตามความเป็นเจ้าของของวัตถุหากตัวชี้ถูกส่งไปรอบ ๆ เหมือนมันฝรั่งร้อน เพียงใช้ตัวแปรสแต็คเพราะมันง่ายและดีกว่า