การประกาศล่วงหน้าใน C ++ คืออะไร


215

ที่: http://www.learncpp.com/cpp-tutorial/19-header-files/

ต่อไปนี้จะกล่าวถึง:

add.cpp:

int add(int x, int y)
{
    return x + y;
}

main.cpp:

#include <iostream>

int add(int x, int y); // forward declaration using function prototype

int main()
{
    using namespace std;
    cout << "The sum of 3 and 4 is " << add(3, 4) << endl;
    return 0;
}

เราใช้การประกาศไปข้างหน้าเพื่อให้คอมไพเลอร์รู้ว่า " add" เมื่อรวบรวมmain.cppอะไร ดังที่ได้กล่าวไว้ก่อนหน้าการเขียนการประกาศล่วงหน้าสำหรับทุกฟังก์ชั่นที่คุณต้องการใช้ที่อาศัยอยู่ในไฟล์อื่นสามารถทำให้เบื่อได้อย่างรวดเร็ว

คุณสามารถอธิบาย " การประกาศล่วงหน้า " เพิ่มเติมได้หรือไม่ มีปัญหาอะไรถ้าเราใช้มันในmain()ฟังก์ชั่น?


1
"การประกาศไปข้างหน้า" จริงๆแล้วเป็นเพียงการประกาศ ดู (ตอนท้าย) คำตอบนี้: stackoverflow.com/questions/1410563/…
sbi

คำตอบ:


381

เหตุใดจึงจำเป็นต้องมีการประกาศล่วงหน้าใน C ++

คอมไพเลอร์ต้องการให้แน่ใจว่าคุณยังไม่ได้สะกดคำผิดหรือส่งจำนวนอาร์กิวเมนต์ที่ไม่ถูกต้องไปยังฟังก์ชัน ดังนั้นจึงยืนยันว่าจะเห็นการประกาศของ 'เพิ่ม' (หรือประเภทอื่น ๆ คลาสหรือฟังก์ชั่น) ก่อนที่มันจะถูกนำมาใช้

นี่เป็นเพียงการอนุญาตให้คอมไพเลอร์ทำงานได้ดีขึ้นในการตรวจสอบความถูกต้องของรหัสและอนุญาตให้มันเป็นระเบียบเรียบร้อยหมดสิ้นเพื่อให้สามารถสร้างไฟล์วัตถุที่ดูเรียบร้อย หากคุณไม่จำเป็นต้องส่งต่อสิ่งต่าง ๆ คอมไพเลอร์จะสร้างออบเจ็กต์ไฟล์ที่จะต้องมีข้อมูลเกี่ยวกับการเดาที่เป็นไปได้ทั้งหมดว่าฟังก์ชัน 'เพิ่ม' อาจเป็นอะไร และตัวเชื่อมโยงจะต้องมีตรรกะที่ฉลาดมากเพื่อลองใช้งาน 'เพิ่ม' ที่คุณตั้งใจจะโทรจริงๆเมื่อฟังก์ชั่น 'เพิ่ม' อาจมีชีวิตอยู่ในไฟล์วัตถุที่แตกต่างกัน dll หรือ exe เป็นไปได้ว่าตัวเชื่อมโยงอาจได้รับการเพิ่มผิด สมมติว่าคุณต้องการใช้ int add (int a, float b) แต่บังเอิญลืมที่จะเขียนมัน แต่ linker พบ int ที่มีอยู่แล้วเพิ่ม (int a, int b) และคิดว่าเป็นสิ่งที่ถูกต้องและใช้สิ่งนั้นแทน รหัสของคุณจะรวบรวม แต่จะไม่ทำสิ่งที่คุณคาดหวัง

ดังนั้นเพื่อให้สิ่งต่าง ๆ ชัดเจนและหลีกเลี่ยงการเดา ฯลฯ คอมไพเลอร์ยืนยันว่าคุณประกาศทุกอย่างก่อนที่มันจะถูกนำมาใช้

ความแตกต่างระหว่างการประกาศและคำจำกัดความ

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

วิธีการประกาศล่วงหน้าสามารถลดเวลาสร้างได้อย่างมาก

คุณสามารถรับฟังก์ชั่นการประกาศลงในไฟล์. cpp หรือ. h ปัจจุบันของคุณโดย # รวมถึงส่วนหัวที่มีการประกาศฟังก์ชัน อย่างไรก็ตามการทำเช่นนี้อาจทำให้การคอมไพล์ของคุณช้าลงโดยเฉพาะอย่างยิ่งหากคุณรวมส่วนหัวเป็น. h แทนที่จะเป็น. cpp ของโปรแกรมของคุณเนื่องจากทุกอย่างที่ # รวมถึง. h ที่คุณเขียนจะลงท้ายด้วย # รวมถึงส่วนหัวทั้งหมด คุณได้เขียน #includes ไว้ด้วย ทันใดนั้นคอมไพเลอร์ก็มีหน้า #included และหน้าของรหัสที่จำเป็นต้องรวบรวมแม้เมื่อคุณต้องการใช้เพียงหนึ่งหรือสองฟังก์ชั่น เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถใช้การประกาศไปข้างหน้าและพิมพ์การประกาศของฟังก์ชันเองที่ด้านบนของไฟล์ หากคุณใช้ฟังก์ชั่นเพียงไม่กี่ฟังก์ชั่นนี้จะทำให้คอมไพล์ของคุณเร็วขึ้นเทียบกับ #including ส่วนหัวเสมอ สำหรับโครงการขนาดใหญ่จริงๆ

ทำลายการอ้างอิงแบบวงกลมโดยที่ทั้งสองคำจำกัดความใช้กัน

นอกจากนี้การส่งต่อการประกาศสามารถช่วยคุณแบ่งวงจร นี่คือที่ที่ทั้งสองฟังก์ชั่นพยายามใช้ซึ่งกันและกัน เมื่อสิ่งนี้เกิดขึ้น (และเป็นสิ่งที่ถูกต้องสมบูรณ์แบบที่จะต้องทำ) คุณอาจ # รวมไฟล์ส่วนหัวหนึ่งไฟล์ แต่ไฟล์ส่วนหัวนั้นพยายามที่จะรวม # ไฟล์ส่วนหัวที่คุณกำลังเขียนอยู่ .... ซึ่ง #includes ส่วนหัวอื่น ๆ ซึ่งรวมถึงสิ่งที่คุณกำลังเขียน คุณติดอยู่ในสถานการณ์ไก่และไข่โดยที่ไฟล์ส่วนหัวแต่ละไฟล์พยายามที่จะรวมอีกครั้ง ในการแก้ปัญหานี้คุณสามารถส่งต่อชิ้นส่วนที่คุณต้องการในไฟล์ใดไฟล์หนึ่งและปล่อย #include ออกจากไฟล์นั้น

เช่น:

ไฟล์ Car.h

#include "Wheel.h"  // Include Wheel's definition so it can be used in Car.
#include <vector>

class Car
{
    std::vector<Wheel> wheels;
};

ไฟล์ Wheel.h

อืม ... ต้องมีการประกาศ Car ที่นี่เนื่องจาก Wheel มีตัวชี้ไปที่รถ แต่ Car.h ไม่สามารถรวมที่นี่ได้เพราะจะทำให้เกิดข้อผิดพลาดในการคอมไพเลอร์ หากมี Car.h รวมอยู่นั่นก็จะพยายามรวม Wheel.h ซึ่งจะรวม Car.h ซึ่งจะรวม Wheel.h และสิ่งนี้จะดำเนินต่อไปตลอดกาลดังนั้นคอมไพเลอร์จะทำให้เกิดข้อผิดพลาดแทน วิธีแก้ปัญหาคือการส่งต่อประกาศรถยนต์แทน:

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

หากคลาสล้อมีวิธีที่จำเป็นต้องเรียกใช้วิธีการของรถยนต์วิธีเหล่านั้นสามารถกำหนดได้ใน Wheel.cpp และ Wheel.cpp สามารถรวม Car.h ได้โดยไม่ทำให้เกิดรอบ


4
การประกาศล่วงหน้ายังมีความจำเป็นเมื่อฟังก์ชั่นเป็นมิตรกับคลาสสองคลาสหรือมากกว่านั้น
Barun

1
เฮ้สกอตต์ในจุดของคุณในการสร้างครั้ง: คุณจะบอกว่ามันเป็นวิธีปฏิบัติที่ดีที่สุด / ทั่วไปที่จะส่งต่อการประกาศและรวมส่วนหัวตามความจำเป็นในไฟล์. cpp? จากการอ่านคำตอบของคุณมันน่าจะเป็นเช่นนั้น แต่ฉันสงสัยว่ามีคำเตือนไหม?
Zepee

5
@Zepee มันคือความสมดุล สำหรับการสร้างอย่างรวดเร็วฉันจะบอกว่ามันเป็นวิธีปฏิบัติที่ดีและฉันขอแนะนำให้ลอง อย่างไรก็ตามอาจต้องใช้ความพยายามและบรรทัดพิเศษของรหัสที่อาจต้องได้รับการดูแลและปรับปรุงหากชื่อประเภท ฯลฯ ยังคงมีการเปลี่ยนแปลง (แม้ว่าเครื่องมือจะเริ่มดีขึ้นเมื่อเปลี่ยนชื่อสิ่งของโดยอัตโนมัติ) ดังนั้นจึงมีการแลกเปลี่ยน ฉันเคยเห็นรหัสฐานที่ไม่มีใครมารบกวน หากคุณพบว่าตัวเองทำซ้ำกำหนดไปข้างหน้าเดียวกันคุณสามารถใส่ไว้ในไฟล์ส่วนหัวที่แยกต่างหากและรวมถึงสิ่งที่ชอบ: stackoverflow.com/questions/4300696/what-is-the-iosfwd-header
Scott Langham

จำเป็นต้องมีการสำแดงข้อมูลไปข้างหน้าเมื่อไฟล์ส่วนหัวอ้างถึงกันและกันเช่น ie stackoverflow.com/questions/396084/…
Nicholas Hamilton

1
ฉันสามารถเห็นสิ่งนี้ทำให้ devs อื่น ๆ ในทีมของฉันเป็นพลเมืองที่ไม่ดีจริงๆของ codebase หากคุณไม่ต้องการความคิดเห็นด้วยการประกาศล่วงหน้าเช่น// From Car.hนั้นคุณสามารถสร้างสถานการณ์ที่ลำบากบางอย่างที่พยายามค้นหาคำจำกัดความที่อยู่ข้างถนน
Dagrooms

25

คอมไพเลอร์มองหาสัญลักษณ์แต่ละตัวที่ใช้ในหน่วยการแปลปัจจุบันก่อนหน้านี้มีการประกาศหรือไม่อยู่ในหน่วยปัจจุบัน มันเป็นเพียงเรื่องของสไตล์ที่ให้ลายเซ็นวิธีการทั้งหมดที่จุดเริ่มต้นของไฟล์ต้นฉบับในขณะที่คำจำกัดความที่มีให้ในภายหลัง การใช้งานที่สำคัญของมันคือเมื่อคุณใช้ตัวชี้ไปยังคลาสเป็นตัวแปรสมาชิกของคลาสอื่น

//foo.h
class bar;    // This is useful
class foo
{
    bar* obj; // Pointer or even a reference.
};

// foo.cpp
#include "bar.h"
#include "foo.h"

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


12

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

int add( int x, int y )

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

ดังนั้น ' ปฏิญญาไปข้างหน้า ' จึงเป็นเพียงสิ่งที่พูดถึงบนกระป๋อง มันประกาศอะไรบางอย่างล่วงหน้าก่อนการใช้งาน

โดยทั่วไปแล้วคุณจะต้องรวมการประกาศไปข้างหน้าในไฟล์ส่วนหัวแล้วรวมไฟล์ส่วนหัวนั้นด้วยวิธีเดียวกันกับที่iostreamรวมอยู่ด้วย


12

คำว่า " การประกาศไปข้างหน้า " ใน C ++ นั้นส่วนใหญ่จะใช้สำหรับการประกาศคลาสเท่านั้น ดู (ท้าย) คำตอบนี้เพราะเหตุใด "การประกาศไปข้างหน้า" ของชั้นเรียนเป็นเพียงการประกาศคลาสอย่างง่าย ๆ ที่มีชื่อแฟนซี

กล่าวอีกนัยหนึ่ง "ไปข้างหน้า" เพิ่มบัลลาสต์ให้กับคำศัพท์เนื่องจากการประกาศใด ๆ ที่สามารถมองเห็นได้ว่าเป็นการส่งต่อในขณะที่มันประกาศตัวระบุบางอย่างก่อนที่มันจะถูกนำมาใช้

(สำหรับการประกาศเมื่อเทียบกับคำจำกัดความอีกครั้งให้ดูที่ความแตกต่างระหว่างคำจำกัดความและการประกาศคืออะไร )


2

เมื่อคอมไพเลอร์เห็นadd(3, 4)ว่าจำเป็นต้องทราบความหมาย ด้วยการประกาศไปข้างหน้าคุณบอกคอมไพเลอร์ว่าaddเป็นฟังก์ชันที่ใช้สอง ints และส่งกลับค่า int นี่เป็นข้อมูลสำคัญสำหรับคอมไพเลอร์เนื่องจากจำเป็นต้องใส่ 4 และ 5 ในการแสดงที่ถูกต้องลงบนสแต็กและจำเป็นต้องรู้ว่าสิ่งที่ประเภทที่ส่งคืนโดยการเพิ่มคือ

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


1
int add(int x, int y); // forward declaration using function prototype

คุณช่วยอธิบาย "การประกาศล่วงหน้า" ได้มากขึ้นอีกไหม ปัญหาคืออะไรถ้าเราใช้มันในฟังก์ชั่น main ()?

#include"add.h"มันเป็นเช่นเดียวกับ หากคุณรู้ว่าตัวประมวลผลล่วงหน้าขยายไฟล์ที่คุณพูดถึงใน#includeไฟล์. cpp ที่คุณเขียน#includeคำสั่ง นั่นหมายความว่าถ้าคุณเขียน#include"add.h"คุณจะได้รับสิ่งเดียวกันมันเหมือนกับว่าคุณกำลังทำ

ฉันสมมติว่าadd.hมีบรรทัดนี้:

int add(int x, int y); 

1

ข้อมูลเพิ่มเติมอย่างย่อเกี่ยวกับ: โดยปกติคุณจะใส่การอ้างอิงไปข้างหน้าเหล่านั้นไว้ในไฟล์ส่วนหัวที่เป็นของไฟล์. c (pp) ที่มีการใช้งานฟังก์ชัน / ตัวแปร ฯลฯ ในตัวอย่างของคุณมันจะเป็นดังนี้: add.h:

extern int เพิ่ม (int a, int b);

คีย์เวิร์ด extern ระบุว่าฟังก์ชันถูกประกาศจริงในไฟล์ภายนอก (อาจเป็นไลบรารีเป็นต้น) main.c ของคุณจะมีลักษณะเช่นนี้:

#include 
#include "add.h"

int หลัก ()
{
.
.
.


แต่เราไม่เพียง แต่ใส่การประกาศในไฟล์ส่วนหัว? ฉันคิดว่านี่เป็นเหตุผลที่ฟังก์ชันกำหนดไว้ใน "add.cpp" และใช้การประกาศไปข้างหน้าหรือไม่ ขอบคุณ
Simplicity

0

ปัญหาหนึ่งคือคอมไพเลอร์ไม่ทราบว่าฟังก์ชันของคุณส่งค่าชนิดใด ฟังก์ชันจะคืนค่า a intในกรณีนี้ แต่อาจถูกต้องเท่าที่จะเป็นได้ ปัญหาอีกประการหนึ่งคือคอมไพเลอร์ไม่ทราบว่าข้อโต้แย้งประเภทใดที่ฟังก์ชั่นของคุณคาดหวังและไม่สามารถเตือนคุณได้หากคุณส่งค่าผิดประเภท มีกฎ "การส่งเสริม" พิเศษซึ่งใช้เมื่อผ่านไปพูดค่าจุดลอยตัวไปยังฟังก์ชั่นที่ไม่ได้ประกาศ (คอมไพเลอร์ต้องขยายให้พิมพ์เป็นสองเท่า) ซึ่งมักจะไม่ใช่สิ่งที่ฟังก์ชั่นคาดหวังจริง ๆ ในเวลาทำงาน

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