เมื่อเขียนโครงการใน C ++ การจัดการการแบ่งส่วนไฟล์เป็นสิ่งจำเป็นมาก วันนี้บรรณาธิการของ Foxin Technology Channel จะนำคำอธิบายโดยละเอียดเกี่ยวกับไฟล์ส่วนหัวและไฟล์ต้นฉบับใน C ++ ฉันหวังว่ามันจะเป็นประโยชน์สำหรับคุณในการเรียนรู้ความรู้นี้!
คำอธิบายโดยละเอียดของไฟล์ส่วนหัวและไฟล์ต้นฉบับใน C ++
1. โหมดการรวบรวม C ++
โดยทั่วไปในโปรแกรม C ++ มีไฟล์เพียงสองประเภทเท่านั้น - ไฟล์. CPP และไฟล์. h ในหมู่พวกเขาไฟล์. cpp เรียกว่าไฟล์ต้นฉบับ C ++ และซอร์สโค้ดของ C ++ ถูกวางไว้ในนั้น ในขณะที่ไฟล์. h ถูกเรียกว่าไฟล์ส่วนหัว C ++ และซอร์สโค้ดของ C ++ จะถูกวางไว้ในนั้น
ภาษา C ++ รองรับ "การรวบรวมแยก" กล่าวอีกนัยหนึ่งเนื้อหาทั้งหมดของโปรแกรมสามารถแบ่งออกเป็นส่วนต่าง ๆ และวางไว้ในไฟล์. CPP ที่แตกต่างกัน สิ่งต่าง ๆ ในไฟล์. cpp นั้นค่อนข้างอิสระ เมื่อรวบรวม (คอมไพล์) คุณไม่จำเป็นต้องสื่อสารกับไฟล์อื่น ๆ คุณจะต้องเชื่อมโยงกับไฟล์เป้าหมายอื่น ๆ หลังจากรวบรวมไว้ในไฟล์เป้าหมาย ตัวอย่างเช่นฟังก์ชั่นส่วนกลาง "เป็นโมฆะ () {}" ถูกกำหนดไว้ในไฟล์ A.CPP และฟังก์ชั่นนี้จะต้องมีการเรียกในไฟล์ b.cpp ถึงกระนั้นไฟล์ A.CPP และ B.CPP ก็ไม่จำเป็นต้องรู้การดำรงอยู่ของกันและกัน แต่สามารถรวบรวมได้แยกต่างหาก หลังจากรวบรวมไว้ในไฟล์เป้าหมายลิงก์และโปรแกรมทั้งหมดสามารถเรียกใช้ได้
สิ่งนี้ประสบความสำเร็จได้อย่างไร? จากมุมมองของโปรแกรมการเขียนมันง่ายมาก ในไฟล์ B.CPP ก่อนที่จะเรียกฟังก์ชั่น "void a ()" ประกาศฟังก์ชั่น "void a ();" อันดับแรก. นี่เป็นเพราะคอมไพเลอร์จะสร้างตารางสัญลักษณ์เมื่อรวบรวม b.cpp สัญลักษณ์เช่น "void a ()" ที่ไม่สามารถมองเห็นได้จะถูกเก็บไว้ในตารางนี้ เมื่อเชื่อมโยงอีกครั้งคอมไพเลอร์จะค้นหาคำจำกัดความของสัญลักษณ์นี้ในไฟล์วัตถุอื่น เมื่อพบแล้วโปรแกรมสามารถสร้างได้อย่างราบรื่น
โปรดทราบว่ามีสองแนวคิดที่กล่าวถึงที่นี่หนึ่งคือ "คำจำกัดความ" และอีกแนวคิดหนึ่งคือ "การประกาศ" พูดง่ายๆคือ "คำจำกัดความ" หมายถึงการอธิบายสัญลักษณ์ในวิธีที่สมบูรณ์และสมบูรณ์: ไม่ว่าจะเป็นตัวแปรหรือฟังก์ชั่นประเภทใดที่มันกลับมาพารามิเตอร์ที่ต้องการ ฯลฯ "การประกาศ" เพียงแค่ประกาศการมีอยู่ของสัญลักษณ์นี้นั่นคือบอกคอมไพเลอร์ว่าสัญลักษณ์นี้ถูกกำหนดไว้ในไฟล์อื่น ฉันจะใช้มันก่อน เมื่อคุณเชื่อมโยงไปที่อื่นเพื่อค้นหาว่ามันคืออะไร เมื่อกำหนดคุณต้องกำหนดสัญลักษณ์ (ตัวแปรหรือฟังก์ชั่น) อย่างสมบูรณ์ตามไวยากรณ์ C ++ และเมื่อประกาศคุณจะต้องเขียนต้นแบบของสัญลักษณ์นี้เท่านั้น ควรสังเกตว่าสัญลักษณ์สามารถประกาศได้หลายครั้งตลอดทั้งโปรแกรม แต่ต้องกำหนดเพียงครั้งเดียว แค่คิดว่าถ้ามีคำจำกัดความที่แตกต่างกันสองประการของสัญลักษณ์คอมไพเลอร์ควรฟังใคร
กลไกนี้นำประโยชน์มากมายให้กับโปรแกรมเมอร์ C ++ และยังนำไปสู่วิธีการเขียนโปรแกรม พิจารณาว่าหากมีฟังก์ชั่นที่ใช้กันทั่วไป "void f () {}" ที่จะถูกเรียกในไฟล์. cpp จำนวนมากในโปรแกรมทั้งหมดเราต้องกำหนดฟังก์ชั่นนี้ในไฟล์เดียวและประกาศฟังก์ชั่นนี้ในไฟล์อื่น ๆ ฟังก์ชั่นนั้นง่ายต่อการจัดการและมันหมายถึงเพียงหนึ่งประโยคที่จะประกาศ อย่างไรก็ตามถ้ามีฟังก์ชั่นมากเกินไปเช่นฟังก์ชั่นทางคณิตศาสตร์จำนวนมากมีหลายร้อยรายการ? โปรแกรมเมอร์ทุกคนสามารถเขียนและเขียนฟังก์ชั่นทั้งหมดในรูปแบบของพวกเขาได้หรือไม่?
2. ไฟล์ส่วนหัวคืออะไร
เห็นได้ชัดว่าคำตอบนั้นเป็นไปไม่ได้ แต่มีวิธีที่ง่ายมากในการช่วยให้โปรแกรมเมอร์บันทึกปัญหาของการจดจำต้นแบบฟังก์ชั่นจำนวนมาก: เราสามารถเขียนคำแถลงทั้งหมดของฟังก์ชั่นหลายร้อยฟังก์ชั่นก่อนและวางไว้ในไฟล์ เมื่อโปรแกรมเมอร์ต้องการพวกเขาให้คัดลอกสิ่งเหล่านี้ทั้งหมดลงในซอร์สโค้ดของเขา
วิธีนี้เป็นไปได้อย่างแน่นอน แต่ก็ยังลำบากเกินไปและดูงุ่มง่าม ดังนั้นไฟล์ส่วนหัวสามารถมีบทบาทได้ ไฟล์ส่วนหัวที่เรียกว่าจริง ๆ แล้วมีเนื้อหาเดียวกับเนื้อหาในไฟล์. cpp ซึ่งทั้งคู่เป็นซอร์สโค้ด C ++ แต่ไฟล์ส่วนหัวไม่จำเป็นต้องรวบรวม เราใส่การประกาศฟังก์ชั่นทั้งหมดลงในไฟล์ส่วนหัว เมื่อไฟล์แหล่งข้อมูล. CPP ต้องการพวกเขาพวกเขาสามารถรวมอยู่ในไฟล์. cpp นี้ผ่านคำสั่งแมโคร "#include" เพื่อให้เนื้อหาของพวกเขาถูกรวมเข้ากับไฟล์. cpp เมื่อรวบรวมไฟล์. cpp ฟังก์ชั่นของไฟล์. h เหล่านี้จะถูกเล่น
มายกตัวอย่างกันเถอะ สมมติว่าฟังก์ชั่นทางคณิตศาสตร์ทั้งหมดมีเพียงสอง: F1 และ F2 จากนั้นเราใส่คำจำกัดความของพวกเขาใน Math.cpp:
/ * math.cpp */double f1 () {// ทำอะไรที่นี่ ... return;} double f2 (double a) {// ทำอะไรที่นี่ ... กลับ a * a;}/ * สิ้นสุด math.cpp *//และวางประกาศฟังก์ชั่น "เหล่านั้น" ในไฟล์ส่วนหัว Math.h:
/ * math.h */double f1 (); double f2 (double);/ * end of math.h */
ในไฟล์อื่น main.cpp ฉันต้องการเรียกฟังก์ชั่นทั้งสองนี้ดังนั้นฉันแค่ต้องรวมไฟล์ส่วนหัว:
/ * main.cpp */#รวม "math.h" main () {int number1 = f1 (); int number2 = f2 (number1);}/ * สิ้นสุดของ main.cpp */นี่เป็นโปรแกรมที่สมบูรณ์ ควรสังเกตว่าไฟล์. h ไม่จำเป็นต้องเขียนหลังจากคำสั่งของคอมไพเลอร์ แต่จะต้องพบในสถานที่ที่คอมไพเลอร์สามารถค้นหาได้ (ตัวอย่างเช่นในไดเรกทอรีเดียวกับ main.cpp) main.cpp และ math.cpp สามารถรวบรวมเพื่อสร้าง main.o และ math.o ตามลำดับจากนั้นเชื่อมโยงไฟล์วัตถุทั้งสองนี้และโปรแกรมสามารถเรียกใช้ได้
3. #รวม
#include เป็นคำสั่งแมโครจากภาษา C ที่ใช้งานได้ก่อนคอมไพล์คอมไพเลอร์นั่นคือเมื่อคอมไพล์ล่วงหน้า จุดประสงค์ของ #include คือการรวมเนื้อหาของไฟล์ที่เขียนไว้อย่างสมบูรณ์และสมบูรณ์หลังจากลงในไฟล์ปัจจุบัน เป็นมูลค่าการกล่าวขวัญว่าไม่มีฟังก์ชั่นอื่นหรือฟังก์ชั่นย่อย ฟังก์ชั่นของมันคือการแทนที่ทุกสถานที่ที่ปรากฏขึ้นด้วยเนื้อหาของไฟล์ที่เขียนไว้ข้างหลัง การเปลี่ยนข้อความอย่างง่ายไม่มีอะไรอื่น ดังนั้นประโยคแรกในไฟล์ Main.cpp (#include "Math.h") จะถูกแทนที่ด้วยเนื้อหาของไฟล์ Math.h ก่อนการรวบรวม นั่นคือเมื่อกระบวนการรวบรวมกำลังจะเริ่มเนื้อหาของ Main.cpp ได้เปลี่ยนไป:
/ * ~ main.cpp */double f1 (); double f2 (double); main () {int number1 = f1 (); int number2 = f2 (number1);}/ * สิ้นสุด ~ main.cpp */ไม่มากหรือน้อยก็ถูกต้อง ในทำนองเดียวกันถ้าเราใช้ฟังก์ชั่น F1 และ F2 นอกเหนือจาก Main.cpp เราต้องเขียนประโยค #include "Math.h" ก่อนที่จะใช้ฟังก์ชั่นทั้งสองนี้
4. สิ่งที่ควรเขียนในไฟล์ส่วนหัว
ผ่านการสนทนาข้างต้นเราสามารถเข้าใจได้ว่าฟังก์ชั่นของไฟล์ส่วนหัวจะรวมอยู่ใน. CPP อื่น ๆ พวกเขาไม่ได้มีส่วนร่วมในการรวบรวม แต่ในความเป็นจริงเนื้อหาของพวกเขาจะถูกรวบรวมในไฟล์. CPP หลายไฟล์ ผ่านกฎของ "คำจำกัดความสามารถเป็นเพียงครั้งเดียว" เราสามารถสรุปได้อย่างง่ายดายว่าควรวางตัวแปรและฟังก์ชั่นเฉพาะในไฟล์ส่วนหัวและคำจำกัดความของพวกเขาไม่ควรวางไว้ เนื่องจากเนื้อหาของไฟล์ส่วนหัวจะถูกนำมาใช้ในไฟล์. cpp ที่แตกต่างกันหลายไฟล์และพวกเขาทั้งหมดจะถูกรวบรวม แน่นอนว่ามันโอเคที่จะประกาศ หากคุณใส่คำจำกัดความมันจะเทียบเท่ากับคำจำกัดความของสัญลักษณ์ (ตัวแปรหรือฟังก์ชัน) ที่ปรากฏในหลายไฟล์ แม้ว่าคำจำกัดความเหล่านี้จะเหมือนกัน แต่ก็ไม่ถูกกฎหมายสำหรับคอมไพเลอร์ที่จะทำเช่นนั้น
ดังนั้นสิ่งหนึ่งที่คุณควรจำไว้คือในไฟล์ส่วนหัว. h มีเพียงตัวแปรหรือฟังก์ชั่นการประกาศและไม่วางคำจำกัดความ นั่นคือประโยคเช่น: extern int a; และเป็นโมฆะ f (); สามารถเขียนได้ในไฟล์ส่วนหัวเท่านั้น นี่คือข้อความ ถ้าคุณเขียนประโยคเช่น int a; หรือเป็นโมฆะ f () {} จากนั้นเมื่อไฟล์ส่วนหัวรวมอยู่ในไฟล์. cpp สองไฟล์ขึ้นไปคอมไพเลอร์จะรายงานข้อผิดพลาดทันที (เกี่ยวกับภายนอกมีการหารือกันก่อนหน้านี้และความแตกต่างระหว่างคำจำกัดความและการประกาศจะไม่ถูกกล่าวถึงที่นี่) อย่างไรก็ตามมีข้อยกเว้นสามข้อสำหรับกฎนี้
1. คำจำกัดความของวัตถุ const สามารถเขียนได้ในไฟล์ส่วนหัว เนื่องจากวัตถุ const ทั่วโลกไม่ได้ประกาศโดยภายนอกโดยค่าเริ่มต้นจึงมีความถูกต้องในไฟล์ปัจจุบันเท่านั้น เขียนวัตถุดังกล่าวลงในไฟล์ส่วนหัวแม้ว่าจะรวมอยู่ในไฟล์. CPP อื่น ๆ อีกหลายไฟล์วัตถุนั้นใช้ได้เฉพาะในไฟล์ที่มีอยู่และมองไม่เห็นกับไฟล์อื่น ๆ ดังนั้นมันจะไม่นำไปสู่คำจำกัดความหลายคำ ในเวลาเดียวกันเนื่องจากวัตถุในไฟล์. cpp เหล่านี้รวมอยู่ในไฟล์ส่วนหัวสิ่งนี้ทำให้มั่นใจได้ว่าค่าของวัตถุ const ในไฟล์. cpp เหล่านี้เหมือนกันซึ่งสามารถกล่าวได้ว่าฆ่านกสองตัวด้วยหินก้อนเดียว ในทำนองเดียวกันคำจำกัดความของวัตถุคงที่สามารถใส่ลงในไฟล์ส่วนหัวได้
2. คำจำกัดความของฟังก์ชั่นอินไลน์สามารถเขียนได้ในไฟล์ส่วนหัว เนื่องจากฟังก์ชั่นอินไลน์ต้องการคอมไพเลอร์ไปที่อินไลน์ตามคำจำกัดความของมันที่พบมันแทนที่จะเป็นแค่ฟังก์ชั่นทั่วไปที่สามารถประกาศได้ก่อนแล้วจึงเชื่อมโยง (ฟังก์ชั่นอินไลน์จะไม่เชื่อมโยง) คอมไพเลอร์จำเป็นต้องดูคำจำกัดความที่สมบูรณ์ของฟังก์ชั่นอินไลน์ระหว่างการรวบรวม หากฟังก์ชั่นอินไลน์สามารถกำหนดได้เพียงครั้งเดียวเช่นฟังก์ชั่นปกตินี่จะเป็นเรื่องยากที่จะทำ เนื่องจากไม่เป็นไรในไฟล์ฉันสามารถเขียนคำจำกัดความของฟังก์ชั่นอินไลน์ที่จุดเริ่มต้นเพื่อที่ฉันจะได้เห็นคำจำกัดความเมื่อฉันใช้มันในภายหลัง แต่ถ้าฉันใช้ฟังก์ชั่นนี้ในไฟล์อื่น ๆ แทบจะไม่มีทางออกที่ดีสำหรับสิ่งนี้ดังนั้น C ++ จึงกำหนดว่าฟังก์ชันอินไลน์สามารถกำหนดได้หลายครั้งในโปรแกรม ตราบใดที่ฟังก์ชั่นอินไลน์ปรากฏขึ้นเพียงครั้งเดียวในไฟล์. CPP และในไฟล์. CPP ทั้งหมดคำจำกัดความของฟังก์ชันอินไลน์นี้จะเหมือนกันและสามารถรวบรวมได้ จากนั้นเห็นได้ชัดว่ามันควรที่จะใส่คำจำกัดความของฟังก์ชั่นอินไลน์ลงในไฟล์ส่วนหัว
3. คำจำกัดความของคลาสสามารถเขียนได้ในไฟล์ส่วนหัว เนื่องจากเมื่อสร้างวัตถุคลาสในโปรแกรมคอมไพเลอร์สามารถรู้ได้ว่าวัตถุของคลาสนี้ควรวางอย่างไรเมื่อคำจำกัดความของคลาสนี้มองเห็นได้อย่างสมบูรณ์ดังนั้นข้อกำหนดสำหรับคำจำกัดความของคลาสนั้นเหมือนกับฟังก์ชั่นอินไลน์ ดังนั้นจึงเป็นวิธีปฏิบัติที่ดีที่จะใส่คำจำกัดความของคลาสลงในไฟล์ส่วนหัวและรวมไฟล์ส่วนหัวในไฟล์. cpp ที่ใช้ในคลาสนี้ ที่นี่มันคุ้มค่าที่จะกล่าวถึงว่าคำจำกัดความของคลาสมีสมาชิกข้อมูลและสมาชิกฟังก์ชั่น สมาชิกข้อมูลจะไม่ถูกกำหนดจนกว่าวัตถุเฉพาะจะถูกสร้างขึ้น (พื้นที่จัดสรร) แต่สมาชิกฟังก์ชั่นจำเป็นต้องกำหนดตั้งแต่เริ่มต้นซึ่งเป็นสิ่งที่เรามักเรียกว่าการใช้งานของคลาส โดยทั่วไปวิธีการของเราคือวางคำจำกัดความของคลาสในไฟล์ส่วนหัวและวางรหัสการใช้งานของสมาชิกฟังก์ชั่นในไฟล์. cpp นี่ก็โอเคและเป็นวิธีที่ดี อย่างไรก็ตามมีอีกวิธีหนึ่ง นั่นคือการเขียนรหัสการใช้งานโดยตรงของสมาชิกฟังก์ชั่นลงในนิยามคลาส ในคลาส C ++ หากสมาชิกฟังก์ชั่นถูกกำหนดไว้ในตัวนิยามคลาสคอมไพเลอร์จะถือว่าฟังก์ชั่นเป็นแบบอินไลน์ ดังนั้นจึงเป็นเรื่องถูกกฎหมายที่จะเขียนคำจำกัดความของสมาชิกฟังก์ชั่นลงในร่างกายนิยามคลาสและรวบรวมไว้ในไฟล์ส่วนหัว โปรดทราบว่ามันผิดกฎหมายที่จะเขียนคำจำกัดความของสมาชิกฟังก์ชั่นในไฟล์ส่วนหัวของคำจำกัดความของคลาสและไม่ได้เป็นนิยามของคลาสเนื่องจากสมาชิกฟังก์ชั่นไม่ได้อินไลน์ในเวลานี้ เมื่อไฟล์ส่วนหัวรวมอยู่ในไฟล์. CPP สองไฟล์ขึ้นไปสมาชิกฟังก์ชั่นนี้จะถูกกำหนดใหม่
5. มาตรการป้องกันในไฟล์ส่วนหัว
พิจารณาว่าหากไฟล์ส่วนหัวมีเฉพาะงบประกาศจะใช้ได้ถ้ามันรวมอยู่ในไฟล์. cpp เดียวกันหลายครั้ง - เนื่องจากการเกิดขึ้นของคำสั่งประกาศไม่ จำกัด อย่างไรก็ตามข้อยกเว้นสามข้อในไฟล์ส่วนหัวที่กล่าวถึงข้างต้นนั้นเป็นการใช้ไฟล์ส่วนหัว จากนั้นเมื่อข้อยกเว้นสามข้อใด ๆ ข้างต้นปรากฏขึ้นในไฟล์ส่วนหัวและรวมอยู่หลายครั้งด้วย. CPP ปัญหาจะใหญ่ เพราะแม้ว่าองค์ประกอบไวยากรณ์ในข้อยกเว้นทั้งสามนี้ "สามารถกำหนดได้ในหลาย ๆ ไฟล์" แต่สามารถปรากฏขึ้นเพียงครั้งเดียวในไฟล์ต้นฉบับเดียวเท่านั้น " ลองนึกภาพว่าถ้า AH มีคำจำกัดความของ Class A และ BH มีคำจำกัดความของคลาส B เนื่องจากคำจำกัดความของคลาส B ขึ้นอยู่กับคลาส A AH ก็ถูก #รวมอยู่ใน BH ขณะนี้มีไฟล์ต้นฉบับที่ใช้ทั้งคลาส A และคลาส B ดังนั้นโปรแกรมเมอร์จึงมีทั้ง AH และ BH ในไฟล์ต้นฉบับนี้ ในเวลานี้ปัญหาเกิดขึ้น: คำจำกัดความของคลาส A ปรากฏขึ้นสองครั้งในไฟล์ต้นฉบับนี้! ดังนั้นโปรแกรมทั้งหมดจึงไม่สามารถรวบรวมได้ คุณอาจคิดว่ามันเป็นความผิดพลาดของโปรแกรมเมอร์ - เขาควรรู้ว่า BH มี AH - แต่อันที่จริงเขาไม่ควร
การใช้ "#Define" ด้วยการรวบรวมแบบมีเงื่อนไขสามารถแก้ปัญหานี้ได้ดี ในไฟล์ส่วนหัวชื่อจะถูกกำหนดผ่าน #Define และ #IFNDEF ... #Endif ถูกรวบรวมอย่างมีเงื่อนไขเพื่อให้คอมไพเลอร์สามารถตัดสินใจได้ว่าจะรวบรวมเนื้อหาที่ตามมาในส่วนหัวตามว่าชื่อจะถูกกำหนดหรือไม่ แม้ว่าวิธีนี้จะง่าย แต่คุณต้องจำไว้ว่าต้องเขียนเมื่อเขียนไฟล์ส่วนหัว
ขอบคุณสำหรับการอ่านบทความนี้ ฉันหวังว่าคำอธิบายโดยละเอียดของไฟล์ส่วนหัวและไฟล์ต้นทางใน C ++ ที่แนะนำในบทความนี้สามารถช่วยคุณได้ ขอบคุณสำหรับการสนับสนุนจากเครือข่ายช่องเทคโนโลยีใหม่!