เจาะลึกถึง C++ กับ Java
เผยแพร่แล้ว: 2022-07-22บทความนับไม่ถ้วนเปรียบเทียบคุณลักษณะทางเทคนิคของ C++ และ Java แต่ความแตกต่างใดที่สำคัญที่สุดที่ต้องพิจารณา ตัวอย่างเช่น เมื่อการเปรียบเทียบแสดง Java ไม่สนับสนุนการสืบทอดหลายรายการและ C++ หมายความว่าอย่างไร และเป็นสิ่งที่ดีหรือไม่? บางคนโต้แย้งว่านี่เป็นข้อดีของ Java ในขณะที่บางคนบอกว่าเป็นปัญหา
มาสำรวจสถานการณ์ที่นักพัฒนาซอฟต์แวร์ควรเลือก C++, Java หรือภาษาอื่นทั้งหมด และที่สำคัญกว่านั้น เหตุใด การตัดสินใจจึงสำคัญ
การตรวจสอบพื้นฐาน: การสร้างภาษาและระบบนิเวศ
C ++ เปิดตัวในปี 1985 เป็นส่วนหน้าของคอมไพเลอร์ C คล้ายกับที่ TypeScript คอมไพล์ไปยัง JavaScript คอมไพเลอร์ C++ สมัยใหม่มักจะคอมไพล์เป็นรหัสเครื่องดั้งเดิม แม้ว่าคอมไพเลอร์ของ C++ อ้างว่าจะลดความสามารถในการพกพาลง และจำเป็นต้องสร้างใหม่สำหรับสถาปัตยกรรมเป้าหมายใหม่ แต่โค้ด C++ จะทำงานบนเกือบทุกแพลตฟอร์มของโปรเซสเซอร์
เปิดตัวครั้งแรกในปี 1995 Java ไม่ได้สร้างโดยตรงไปยังโค้ดเนทีฟ แต่ Java จะสร้าง bytecode ซึ่งเป็นการแสดงไบนารีระดับกลางที่ทำงานบน Java Virtual Machine (JVM) กล่าวอีกนัยหนึ่ง เอาต์พุตของคอมไพเลอร์ Java ต้องการไฟล์เรียกทำงานเฉพาะของแพลตฟอร์มเพื่อรัน
ทั้ง C ++ และ Java อยู่ในตระกูลของภาษา C-like เนื่องจากโดยทั่วไปแล้วจะคล้ายกับ C ในไวยากรณ์ ความแตกต่างที่สำคัญที่สุดคือระบบนิเวศของพวกมัน: แม้ว่า C++ สามารถเรียกใช้ไลบรารีที่อิงตาม C หรือ C++ หรือ API ของระบบปฏิบัติการได้อย่างราบรื่น Java เหมาะที่สุดสำหรับไลบรารีที่ใช้ Java คุณสามารถเข้าถึงไลบรารี C ใน Java ได้โดยใช้ Java Native Interface (JNI) API แต่มีแนวโน้มที่จะเกิดข้อผิดพลาดและต้องใช้โค้ด C หรือ C++ บางส่วน C++ ยังโต้ตอบกับฮาร์ดแวร์ได้ง่ายกว่า Java เนื่องจาก C++ เป็นภาษาระดับล่าง
การแลกเปลี่ยนโดยละเอียด: ข้อมูลทั่วไป หน่วยความจำ และอื่นๆ
เราสามารถเปรียบเทียบ C++ กับ Java จากหลายมุมมอง ในบางกรณี การตัดสินใจระหว่าง C++ และ Java นั้นชัดเจน โดยทั่วไปแล้วแอปพลิเคชัน Android ดั้งเดิมควรใช้ Java เว้นแต่ว่าแอปนั้นเป็นเกม ผู้พัฒนาเกมส่วนใหญ่ควรเลือกใช้ C++ หรือภาษาอื่นเพื่อให้แอนิเมชั่นเรียลไทม์ราบรื่นที่สุด การจัดการหน่วยความจำของ Java มักทำให้เกิดความล่าช้าระหว่างการเล่นเกม
แอปพลิเคชันข้ามแพลตฟอร์มที่ไม่ใช่เกมอยู่นอกเหนือขอบเขตของการสนทนานี้ ในกรณีนี้ ทั้ง C++ และ Java ก็ไม่เหมาะ เพราะมันละเอียดเกินไปสำหรับการพัฒนา GUI อย่างมีประสิทธิภาพ สำหรับแอปที่มีประสิทธิภาพสูง วิธีที่ดีที่สุดคือสร้างโมดูล C++ เพื่อใช้งานหนัก และใช้ภาษาสำหรับนักพัฒนาซอฟต์แวร์มากขึ้นสำหรับ GUI
แอปพลิเคชันข้ามแพลตฟอร์มที่ไม่ใช่เกมอยู่นอกเหนือขอบเขตของการสนทนานี้ ในกรณีนี้ ทั้ง C++ และ Java ก็ไม่เหมาะ เพราะมันละเอียดเกินไปสำหรับการพัฒนา GUI อย่างมีประสิทธิภาพ
ทวีต
สำหรับบางโครงการ ตัวเลือกอาจไม่ชัดเจน ลองเปรียบเทียบเพิ่มเติม:
ลักษณะเฉพาะ | C++ | Java |
---|---|---|
เป็นมิตรกับผู้เริ่มต้น | ไม่ | ใช่ |
ประสิทธิภาพรันไทม์ | ดีที่สุด | ดี |
เวลาในการตอบสนอง | คาดการณ์ได้ | คาดการณ์ไม่ได้ |
ตัวชี้อัจฉริยะสำหรับการนับการอ้างอิง | ใช่ | ไม่ |
การรวบรวมขยะเครื่องหมายและกวาดทั่วโลก | ไม่ | ที่จำเป็น |
การจัดสรรหน่วยความจำกอง | ใช่ | ไม่ |
การคอมไพล์เป็นไฟล์ปฏิบัติการดั้งเดิม | ใช่ | ไม่ |
การคอมไพล์เป็น Java bytecode | ไม่ | ใช่ |
โต้ตอบโดยตรงกับ API ระบบปฏิบัติการระดับต่ำ | ใช่ | ต้องใช้รหัส C |
โต้ตอบโดยตรงกับไลบรารี C | ใช่ | ต้องใช้รหัส C |
โต้ตอบโดยตรงกับไลบรารี Java | ผ่าน JNI | ใช่ |
การสร้างมาตรฐานและการจัดการแพ็คเกจ | ไม่ | Maven |
นอกเหนือจากคุณลักษณะที่เปรียบเทียบในตารางแล้ว เรายังจะเน้นคุณลักษณะการเขียนโปรแกรมเชิงวัตถุ (OOP) เช่น การสืบทอดหลายรายการ ข้อมูลทั่วไป/เทมเพลต และการสะท้อนกลับ โปรดทราบว่าทั้งสองภาษารองรับ OOP: Java กำหนดไว้ ในขณะที่ C++ รองรับ OOP ควบคู่ไปกับฟังก์ชันส่วนกลางและข้อมูลคงที่
มรดกหลายอย่าง
ใน OOP การสืบทอดคือเมื่อคลาสลูกสืบทอดแอตทริบิวต์และเมธอดจากคลาสพาเรนต์ ตัวอย่างมาตรฐานหนึ่งคือคลาส Rectangle
ที่สืบทอดจากคลาส Shape
ทั่วไป:
// Note that we are in a C++ file class Shape { // Position int x, y; public: // The child class must override this pure virtual function virtual void draw() = 0; }; class Rectangle: public Shape { // Width and height int w, h; public: void draw(); };
การสืบทอดหลายรายการคือเมื่อคลาสย่อยสืบทอดมาจากผู้ปกครองหลายคน ต่อไปนี้คือตัวอย่าง การใช้คลาส Rectangle
และ Shape
และคลาส Clickable
เพิ่มเติม:
// Not recommended class Shape {...}; class Rectangle: public Shape {...}; class Clickable { int xClick, yClick; public: virtual void click() = 0; }; class ClickableRectangle: public Rectangle, public Clickable { void click(); };
ในกรณีนี้ เรามีฐานสองประเภท: Shape
(ประเภทฐานของ Rectangle
) และ Clickable
ClickableRectangle
สืบทอดจากทั้งสองเพื่อสร้างวัตถุสองประเภท
C ++ รองรับการสืบทอดหลายรายการ จาวาไม่ได้ การสืบทอดหลายรายการมีประโยชน์ในบางกรณี เช่น:
- การสร้างภาษาเฉพาะโดเมนขั้นสูง (DSL)
- ดำเนินการคำนวณที่ซับซ้อนในเวลารวบรวม
- การปรับปรุงความปลอดภัยของประเภทโปรเจ็กต์ในลักษณะที่ไม่สามารถทำได้ใน Java
อย่างไรก็ตาม โดยทั่วไปเราไม่แนะนำให้ใช้การสืบทอดหลายรายการ มันสามารถทำให้โค้ดซับซ้อนและส่งผลกระทบต่อประสิทธิภาพการทำงาน เว้นแต่จะรวมกับโปรแกรมเมตาดาต้าของเทมเพลต ซึ่งทำได้ดีที่สุดโดยเฉพาะโปรแกรมเมอร์ C++ ที่มีประสบการณ์มากที่สุดเท่านั้น
ข้อมูลทั่วไปและเทมเพลต
คลาสเวอร์ชันทั่วไปที่ทำงานกับประเภทข้อมูลใดๆ ได้นั้นมีประโยชน์สำหรับการนำโค้ดมาใช้ซ้ำ ทั้งสองภาษาให้การสนับสนุนนี้—Java ผ่าน generics, C++ ผ่านเทมเพลต—แต่ความยืดหยุ่นของเทมเพลต C++ สามารถทำให้การเขียนโปรแกรมขั้นสูงปลอดภัยและแข็งแกร่งยิ่งขึ้น คอมไพเลอร์ C++ สร้างคลาสหรือฟังก์ชันที่กำหนดเองใหม่ทุกครั้งที่คุณใช้ประเภทที่แตกต่างกันกับเทมเพลต นอกจากนี้ เทมเพลต C++ ยังสามารถเรียกใช้ฟังก์ชันที่กำหนดเองตามประเภทของพารามิเตอร์ของฟังก์ชันระดับบนสุดได้ ทำให้ข้อมูลบางประเภทมีโค้ดเฉพาะได้ สิ่งนี้เรียกว่าความเชี่ยวชาญด้านเทมเพลต Java ไม่มีคุณสมบัติเทียบเท่า
ในทางตรงกันข้าม เมื่อใช้ generics คอมไพเลอร์ Java จะสร้างอ็อบเจ็กต์ทั่วไปที่ไม่มีประเภทผ่านกระบวนการที่เรียกว่าการลบประเภท Java ดำเนินการตรวจสอบประเภทในระหว่างการคอมไพล์ แต่โปรแกรมเมอร์ไม่สามารถแก้ไขการทำงานของคลาสหรือเมธอดทั่วไปตามพารามิเตอร์ของประเภทได้ เพื่อให้เข้าใจได้ดีขึ้น มาดูตัวอย่างสั้นๆ ของ std::string format(std::string fmt, T1 item1, T2 item2)
ที่ใช้เทมเพลต template<class T1, class T2>
จาก C++ ห้องสมุดที่ฉันสร้าง:
std::string firstParameter = "A string"; int secondParameter = 123; // Format printed output as an eight-character-wide string and a hexadecimal value format("%8s %x", firstParameter, secondParameter); // Format printed output as two eight-character-wide strings format("%8s %8s", firstParameter, secondParameter);
C ++ จะสร้างฟังก์ชันการจัด format
เป็น std::string format(std::string fmt, std::string item1, int item2)
ในขณะที่ Java จะสร้างโดยไม่มี string
เฉพาะและประเภทวัตถุ int
สำหรับ item1
และ item2
ในกรณีนี้ เทมเพลต C++ ของเรารู้ว่าพารามิเตอร์ขาเข้าล่าสุดคือ int
ดังนั้นจึงสามารถทำการแปลง std::to_string
ที่จำเป็นในการเรียก format
ที่สองได้ หากไม่มีเท็มเพลต คำสั่ง C++ printf
ที่พยายามพิมพ์ตัวเลขเป็นสตริง เช่นเดียวกับในการเรียก format
ที่สองจะมีพฤติกรรมที่ไม่ได้กำหนดไว้ และอาจทำให้แอพพลิเคชั่นหยุดทำงานหรือพิมพ์ขยะ ฟังก์ชัน Java จะสามารถจัดการกับตัวเลขเป็นสตริงในการเรียก format
แรกเท่านั้น และจะไม่จัดรูปแบบเป็นจำนวนเต็มฐานสิบหกโดยตรง นี่เป็นตัวอย่างเล็กน้อย แต่แสดงให้เห็นถึงความสามารถของ C++ ในการเลือกเทมเพลตเฉพาะเพื่อจัดการกับออบเจ็กต์คลาสใดก็ได้โดยไม่ต้องแก้ไขคลาสหรือฟังก์ชัน format
เราสามารถสร้างเอาต์พุตได้อย่างถูกต้องใน Java โดยใช้การสะท้อนแทนที่จะใช้ generics แม้ว่าวิธีนี้จะขยายน้อยลงและมีโอกาสเกิดข้อผิดพลาดมากกว่า
การสะท้อน
ใน Java เป็นไปได้ที่จะค้นหารายละเอียดโครงสร้าง (ที่รันไทม์) เช่น สมาชิกที่พร้อมใช้งานในคลาสหรือประเภทคลาส คุณลักษณะนี้เรียกว่าการสะท้อน น่าจะเป็นเพราะมันเหมือนกับการชูกระจกขึ้นกับวัตถุเพื่อดูว่ามีอะไรอยู่ข้างใน (ดูข้อมูลเพิ่มเติมได้ในเอกสารการสะท้อนของ Oracle)
C++ ไม่มีการสะท้อนอย่างสมบูรณ์ แต่ C++ สมัยใหม่มีข้อมูลประเภทรันไทม์ (RTTI) RTTI อนุญาตให้ตรวจหารันไทม์ของอ็อบเจ็กต์บางประเภทได้ แม้ว่าจะเข้าถึงข้อมูลอย่างเช่น สมาชิกของอ็อบเจ็กต์ไม่ได้ก็ตาม
การจัดการหน่วยความจำ
ข้อแตกต่างที่สำคัญอีกประการหนึ่งระหว่าง C++ และ Java คือการจัดการหน่วยความจำ ซึ่งมีสองวิธีหลัก: แบบแมนนวล ซึ่งนักพัฒนาต้องติดตามและปล่อยหน่วยความจำด้วยตนเอง และอัตโนมัติ โดยซอฟต์แวร์จะติดตามว่าวัตถุใดยังคงใช้อยู่เพื่อรีไซเคิลหน่วยความจำที่ไม่ได้ใช้ ใน Java ตัวอย่างคือการรวบรวมขยะ
Java ต้องการหน่วยความจำที่รวบรวมโดยขยะ ทำให้สามารถจัดการหน่วยความจำได้ง่ายกว่าวิธีการแบบแมนนวล และขจัดข้อผิดพลาดในการปลดปล่อยหน่วยความจำที่มักทำให้เกิดช่องโหว่ด้านความปลอดภัย C ++ ไม่มีการจัดการหน่วยความจำอัตโนมัติ แต่รองรับรูปแบบการรวบรวมขยะที่เรียกว่าตัวชี้อัจฉริยะ ตัวชี้อัจฉริยะใช้การนับอ้างอิงและมีความปลอดภัยและมีประสิทธิภาพหากใช้อย่างถูกต้อง C ++ ยังมีตัวทำลายล้างที่ล้างหรือปล่อยทรัพยากรเมื่อมีการทำลายวัตถุ
ในขณะที่ Java เสนอเฉพาะการจัดสรรฮีป C++ รองรับทั้งการจัดสรรฮีป (โดยใช้ฟังก์ชัน C malloc
new
และ delete
หรือเก่ากว่า) และการจัดสรรสแต็ก การจัดสรรสแต็กสามารถทำได้เร็วและปลอดภัยกว่าการจัดสรรฮีป เนื่องจากสแต็กเป็นโครงสร้างข้อมูลเชิงเส้นในขณะที่ฮีปเป็นแบบทรี ดังนั้นหน่วยความจำสแต็กจึงง่ายกว่ามากในการจัดสรรและปล่อย
ข้อดีอีกประการของ C++ ที่เกี่ยวข้องกับการจัดสรรสแต็กคือเทคนิคการเขียนโปรแกรมที่เรียกว่า Resource Acquisition Is Initialization (RAII) ใน RAII ทรัพยากรต่างๆ เช่น ข้อมูลอ้างอิงเชื่อมโยงกับวงจรชีวิตของอ็อบเจ็กต์ที่ควบคุม ทรัพยากรจะถูกทำลายเมื่อสิ้นสุดวงจรชีวิตของวัตถุนั้น RAII คือวิธีการทำงานของพอยน์เตอร์อัจฉริยะ C++ โดยไม่ต้องลดการอ้างอิงด้วยตนเอง—ตัวชี้อัจฉริยะที่อ้างอิงที่ด้านบนสุดของฟังก์ชันจะถูกยกเลิกการอ้างอิงโดยอัตโนมัติเมื่อออกจากฟังก์ชัน หน่วยความจำที่เชื่อมต่อจะถูกปล่อยออกเช่นกันหากนี่เป็นข้อมูลอ้างอิงล่าสุดกับตัวชี้อัจฉริยะ แม้ว่า Java จะมีรูปแบบที่คล้ายคลึงกัน แต่ก็ยากกว่า RAII ของ C++ โดยเฉพาะอย่างยิ่งหากคุณต้องการสร้างทรัพยากรจำนวนมากในบล็อกโค้ดเดียวกัน
ประสิทธิภาพรันไทม์
Java มีประสิทธิภาพรันไทม์ที่มั่นคง แต่ C ++ ยังคงครองตำแหน่งมงกุฎเนื่องจากการจัดการหน่วยความจำด้วยตนเองเร็วกว่าการรวบรวมขยะสำหรับแอปพลิเคชันในโลกแห่งความเป็นจริง แม้ว่า Java จะสามารถทำงานได้ดีกว่า C++ ในบางกรณีเนื่องจากการคอมไพล์ JIT แต่ C++ จะชนะกรณีที่ไม่สำคัญส่วนใหญ่
โดยเฉพาะอย่างยิ่ง ไลบรารีหน่วยความจำมาตรฐานของ Java ทำงานทับตัวรวบรวมขยะด้วยการจัดสรรเมื่อเปรียบเทียบกับการใช้การจัดสรรฮีปที่ลดลงของ C++ อย่างไรก็ตาม Java ยังคงค่อนข้างเร็วและควรเป็นที่ยอมรับได้ เว้นแต่ว่าเวลาในการตอบสนองเป็นข้อกังวลหลัก ตัวอย่างเช่น ในเกมหรือแอปพลิเคชันที่มีข้อจำกัดแบบเรียลไทม์
การจัดการบิลด์และแพ็คเกจ
สิ่งที่ Java ขาดประสิทธิภาพก็ชดเชยความสะดวกในการใช้งาน องค์ประกอบหนึ่งที่ส่งผลต่อประสิทธิภาพของนักพัฒนาคือการสร้างและการจัดการแพ็คเกจ—วิธีที่เราสร้างโครงการและนำการพึ่งพาภายนอกมาสู่แอปพลิเคชัน ใน Java เครื่องมือที่เรียกว่า Maven ทำให้กระบวนการนี้ง่ายขึ้นเป็นขั้นตอนง่ายๆ ไม่กี่ขั้นตอน และรวมเข้ากับ IDE จำนวนมาก เช่น IntelliJ IDEA
อย่างไรก็ตาม ใน C++ ไม่มีที่เก็บแพ็คเกจมาตรฐาน ไม่มีแม้แต่วิธีการมาตรฐานในการสร้างโค้ด C++ ในแอปพลิเคชัน: นักพัฒนาบางคนชอบ Visual Studio ในขณะที่คนอื่นใช้ CMake หรือชุดเครื่องมือที่กำหนดเองอื่น นอกจากนี้ ไลบรารี C++ เชิงพาณิชย์บางตัวยังมีรูปแบบไบนารีที่เพิ่มความซับซ้อนเข้าไปอีก และไม่มีวิธีที่สอดคล้องกันในการรวมไลบรารีเหล่านั้นเข้ากับกระบวนการสร้าง นอกจากนี้ การเปลี่ยนแปลงในการตั้งค่าบิลด์หรือเวอร์ชันคอมไพเลอร์อาจทำให้เกิดความท้าทายในการทำให้ไลบรารีไบนารีทำงาน
เป็นมิตรกับผู้เริ่มต้น
การสร้างและการจัดการแพ็คเกจที่ขัดแย้งกันไม่ได้เป็นเพียงเหตุผลเดียวที่ C++ นั้นเป็นมิตรกับผู้เริ่มต้นน้อยกว่า Java โปรแกรมเมอร์อาจมีปัญหาในการดีบักและใช้งาน C++ อย่างปลอดภัย เว้นแต่จะคุ้นเคยกับ C, ภาษาแอสเซมบลี หรือการทำงานระดับล่างของคอมพิวเตอร์ คิดว่า C++ เปรียบเสมือนเครื่องมือไฟฟ้า: สามารถทำได้หลายอย่างแต่มันอันตรายหากนำไปใช้ในทางที่ผิด
วิธีการจัดการหน่วยความจำดังกล่าวของ Java ยังทำให้สามารถเข้าถึงได้มากกว่า C++ โปรแกรมเมอร์ Java ไม่ต้องกังวลกับการปล่อยหน่วยความจำอ็อบเจ็กต์เนื่องจากภาษาจะดูแลสิ่งนั้นโดยอัตโนมัติ
เวลาตัดสินใจ: C ++ หรือ Java?
ตอนนี้เราได้สำรวจความแตกต่างระหว่าง C++ และ Java ในเชิงลึกแล้ว เรากลับมาที่คำถามเดิม: C++ หรือ Java? แม้จะเข้าใจทั้งสองภาษาอย่างลึกซึ้ง แต่ก็ไม่มีคำตอบใดที่เหมาะสมกับทุกประการ
วิศวกรซอฟต์แวร์ที่ไม่คุ้นเคยกับแนวคิดการเขียนโปรแกรมระดับต่ำอาจเลือก Java ได้ดีกว่าเมื่อจำกัดการตัดสินใจไว้ที่ C++ หรือ Java ยกเว้นบริบทแบบเรียลไทม์ เช่น การเล่นเกม ในทางกลับกัน นักพัฒนาที่ต้องการขยายขอบเขตอันไกลโพ้น อาจเรียนรู้เพิ่มเติมโดยการเลือก C++
อย่างไรก็ตาม ความแตกต่างทางเทคนิคระหว่าง C++ และ Java อาจเป็นปัจจัยเพียงเล็กน้อยในการตัดสินใจ ผลิตภัณฑ์บางประเภทต้องการตัวเลือกเฉพาะ หากคุณยังไม่แน่ใจ คุณสามารถศึกษาแผนผังลำดับงานได้ แต่จำไว้ว่าท้ายที่สุดแล้วอาจนำคุณไปยังภาษาที่สาม