เหตุใดจึงไม่จำเป็นต้องประกาศวิธีการของ structs ใน C ++?

ยกตัวอย่างโค้ดต่อไปนี้:

#include <iostream>
#include <string>

int main()
{
    print("Hello!");
}

void print(std::string s) {
    std::cout << s << std::endl;
}

เมื่อพยายามสร้างสิ่งนี้ ฉันได้รับสิ่งต่อไปนี้:

program.cpp: In function ‘int main()’:
program.cpp:6:16: error: ‘print’ was not declared in this scope

ซึ่งสมเหตุสมผล

เหตุใดฉันจึงสามารถดำเนินการแนวคิดที่คล้ายกันในโครงสร้างได้ แต่ไม่ได้รับการตะโกนใส่

struct Snake {
    ...

    Snake() {
        ...
        addBlock(Block(...));
    }

    void addBlock(Block block) {
        ...
    }

    void update() {
        ...
    }

} snake1;

ฉันไม่เพียงแต่ไม่ได้รับคำเตือนเท่านั้น แต่โปรแกรมยังคอมไพล์อีกด้วย! ไร้ข้อผิดพลาด! นี่เป็นเพียงลักษณะของโครงสร้างหรือไม่? เกิดอะไรขึ้นที่นี่? เห็นได้ชัดว่า addBlock(Block) ถูกเรียกก่อนที่จะมีการประกาศวิธีการ


person Community    schedule 30.07.2014    source แหล่งที่มา
comment
จริงๆ แล้วมันเป็นคำถามที่ดีและมีคนสงสัยว่าทำไมถึงเป็นเช่นนี้ โดยเฉพาะอย่างยิ่งคำตอบบางส่วนที่มาในรูปแบบของสิ่งนี้เป็นเหตุผลทางประวัติศาสตร์ เนื่องจากการคอมไพล์สองเฟสมีราคาแพงในปี 1976 แต่ C++ นั้นค่อนข้างจะงี่เง่าที่มีชิ้นส่วนเก่าและชิ้นส่วนใหม่บางส่วน ดังนั้นคุณจึงได้รับความแตกต่างนี้   -  person v.oddou    schedule 30.07.2014
comment
คำตอบสั้นๆ: เนื้อหาของฟังก์ชันจะไม่ถูกคอมไพล์จนกว่าจะคอมไพล์คำจำกัดความของคลาสแล้ว   -  person M.M    schedule 30.07.2014
comment
ในทางปฏิบัติ สิ่งนี้เป็นไปได้เนื่องจากคอมไพเลอร์ต้องย้อนรอยเพียงคลาสเดียว ไม่ใช่ทั้งไฟล์ อย่างหลังมีราคาแพงเกินสมควรเมื่อใช้เทคโนโลยียุค 90   -  person MSalters    schedule 30.07.2014
comment
เมื่อเร็ว ๆ นี้มีคำถามที่คล้ายกัน: stackoverflow.com/questions/24925831/   -  person Tony Delroy    schedule 30.07.2014
comment
คล้ายกับคลาสเช่นกัน: ideone.com/Xw2dbF   -  person user3791372    schedule 30.07.2014
comment
ใน D&E Stroustrup กล่าวว่าคุณลักษณะนี้ได้รับการแนะนำเพื่อจัดการกับปัญหาการค้นหาชื่อ ตัวอย่างเช่น int i; struct s { void foo() { i = 42; } int i; }; เมื่อ foo ถูกกำหนดไว้ภายนอกคลาส i จะอ้างอิงถึง this->i อย่างชัดเจน และควรจะเหมือนกันเมื่อย้ายคำจำกัดความภายในคลาส   -  person dyp    schedule 30.07.2014


คำตอบ (3)


จริงๆ แล้ว struct ใน C++ คือคำจำกัดความ class โดยมีเนื้อหาเป็น public เว้นแต่จะระบุไว้เป็นอย่างอื่นโดยรวมส่วน protected: หรือ private:

เมื่อคอมไพเลอร์เห็น class หรือ struct คอมไพลเลอร์จะแยกย่อยการประกาศทั้งหมดภายในบล็อก ({}) ก่อนที่จะดำเนินการกับมัน

ในกรณีของเมธอดปกติ คอมไพเลอร์ยังไม่เห็นประเภทที่ประกาศ

person NirMH    schedule 30.07.2014
comment
@juanchopanza: มันใช้ได้กับประเภทภายในโดยสิ้นเชิง เมื่อคอมไพเลอร์เห็นคลาสหรือโครงสร้าง คอมไพเลอร์จะย่อยการประกาศทั้งหมดก่อน ซึ่งหมายความว่า การประกาศ จะต้องย่อยได้ หากการประกาศใช้ประเภทภายใน จะต้องประกาศประเภทภายใน (อาจกำหนดขึ้นอยู่กับการประกาศ) - person Mooing Duck; 30.07.2014
comment
@juanchopanza: ฉันคิดว่าเรากำลังมีข้อผิดพลาดในการสื่อสาร ฉันกำลังบอกว่าขั้นตอนแรกจะแยกแยะการประกาศทั้งหมดโดยนัยว่าการประกาศจะต้องเป็นไปตามกฎปกติสำหรับประเภท/ลายเซ็นที่ถูกประกาศ ตามลำดับ สิ่งนี้จะเกิดขึ้นซ้ำอีกครั้งในประเภทภายใน หลังจากนั้น คำจำกัดความจะถูกแยกวิเคราะห์ ฉันไม่เห็นว่านี่เป็นข้อยกเว้นสำหรับประเภทภายใน ฉันเห็นว่านี่เป็นประเภทภายในที่เป็นไปตามกฎเดียวกันกับประเภทภายนอกทุกประการ - person Mooing Duck; 30.07.2014
comment
@MooingDuck คุณพูดถูก ฉันไม่ได้อ่านความคิดเห็นแรกของคุณอย่างละเอียดเพียงพอ ฉันเดาว่าคุณหมายถึงสิ่งนี้: ideone.com/K40DDE ซึ่งฟังดูสมเหตุสมผลดี - person juanchopanza; 30.07.2014

มาตรฐาน C++ 3.4.1:

.4:

ชื่อที่ใช้ในขอบเขตส่วนกลาง นอกเหนือจากฟังก์ชัน คลาส หรือเนมสเปซที่ผู้ใช้ประกาศ จะต้องได้รับการประกาศก่อนที่จะใช้ในขอบเขตส่วนกลาง

นี่คือสาเหตุที่ไม่สามารถใช้ตัวแปรและฟังก์ชันโกลบอลได้ก่อนการประกาศก่อนหน้านี้

.5:

ชื่อที่ใช้ในเนมสเปซที่ผู้ใช้ประกาศไว้นอกคำจำกัดความของฟังก์ชันหรือคลาสใดๆ จะต้องประกาศก่อนใช้ในเนมสเปซนั้น หรือก่อนที่จะใช้ในเนมสเปซที่ล้อมรอบเนมสเปซของมัน

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

.7:

ชื่อที่ใช้ในคำจำกัดความของคลาส X ภายนอกเนื้อหาฟังก์ชันสมาชิกหรือคำจำกัดความของคลาสที่ซ้อนกัน 29 จะต้องประกาศด้วยวิธีใดวิธีหนึ่งดังต่อไปนี้: — ก่อนที่จะใช้ในคลาส X หรือเป็นสมาชิกของคลาสฐานของ X (10.2) หรือ — ถ้า X เป็นคลาสที่ซ้อนกันของคลาส Y (9.7) ก่อนคำจำกัดความของ X ใน Y หรือจะต้องเป็นสมาชิกของคลาสฐานของ Y (การค้นหานี้ใช้กับคลาสปิดของ Y โดยเริ่มจาก คลาสที่ปิดล้อมในสุด),30 หรือ — ถ้า X เป็นคลาสโลคัล (9.8) หรือเป็นคลาสที่ซ้อนกันของคลาสโลคัล ก่อนคำจำกัดความของคลาส X ในบล็อกที่ล้อมรอบคำจำกัดความของคลาส X หรือ — ถ้า X เป็น สมาชิกของเนมสเปซ N หรือเป็นคลาสที่ซ้อนกันของคลาสที่เป็นสมาชิกของ N หรือเป็นคลาสโลคัลหรือคลาสที่ซ้อนกันภายในคลาสโลคัลของฟังก์ชันที่เป็นสมาชิกของ N ก่อนคำจำกัดความของคลาส X ใน เนมสเปซ N หรือในเนมสเปซที่ปิดล้อมของ N ตัวใดตัวหนึ่ง

ฉันคิดว่านี่พูดถึงโค้ดทั้งหมดที่ไม่อยู่ในโค้ดที่เรียกใช้งาน cpu (เช่นโค้ดที่ประกาศ)

และสุดท้ายส่วนที่น่าสนใจ:

3.3.7 ขอบเขตคลาส [basic.scope.class]

1 กฎต่อไปนี้อธิบายขอบเขตของชื่อที่ประกาศในคลาส

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

2) ชื่อ N ที่ใช้ในคลาส S จะอ้างอิงถึงคำประกาศเดียวกันในบริบทและเมื่อมีการประเมินอีกครั้งในขอบเขตของ S ที่เสร็จสมบูรณ์ ไม่จำเป็นต้องมีการวินิจฉัยสำหรับการละเมิดกฎนี้

3) หากการจัดลำดับการประกาศสมาชิกใหม่ในคลาสทำให้ได้โปรแกรมสำรองที่ถูกต้องตาม (1) และ (2) โปรแกรมนั้นมีรูปแบบไม่ถูกต้อง ไม่จำเป็นต้องทำการวินิจฉัย

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

พูดได้อย่างมีประสิทธิภาพในชั้นเรียน การประกาศจะถูกค้นหาในรูปแบบการรวบรวมสองเฟส

person v.oddou    schedule 30.07.2014

"ทำไม ฉันจึงสามารถดำเนินการแนวคิดที่คล้ายกันในโครงสร้าง แต่ไม่ถูกตะโกนใส่ได้"

ในคำจำกัดความ struct หรือ class คุณกำลัง นำเสนออินเทอร์เฟซสาธารณะ ให้กับชั้นเรียน และจะง่ายกว่ามากในการทำความเข้าใจ ค้นหา และบำรุงรักษา/อัปเดต API นั้น หากมีการนำเสนอใน:

  • ลำดับที่คาดเดาได้ด้วย
  • ความยุ่งเหยิงน้อยที่สุด

สำหรับลำดับที่คาดเดาได้ ผู้คนมีสไตล์เป็นของตัวเองและมี "ศิลปะ" อยู่บ้าง แต่ตัวอย่างเช่น ฉันใช้ตัวระบุการเข้าถึงแต่ละตัวมากที่สุดหนึ่งครั้งและเสมอ public ก่อน protected ก่อน private จากนั้นภายในลำดับที่ปกติฉันจะใส่ typedefs, const data, ตัวสร้าง , ตัวทำลาย, ฟังก์ชันกลายพันธุ์/ไม่ใช่ const, ฟังก์ชัน const, statics, friends....

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

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

  • ผู้ที่ชื่นชอบรูปแบบการเขียนโปรแกรมจากล่างขึ้นบนจะไม่ชอบการถูกบังคับให้มีการประกาศแยกกันในชั้นเรียนหรือละทิ้งแนวทางปฏิบัติในการจัดกลุ่มที่ขัดแย้งกันบ่อยครั้งโดยตัวระบุการเข้าถึง

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

  • แนวปฏิบัติที่ดีที่สุดคือสำหรับคลาสที่จะไม่อัดแน่นในอินเทอร์เฟซที่กว้างขวางมาก... โดยทั่วไปคุณต้องการแกนหลักที่ใช้งานได้และจากนั้นก็มีฟังก์ชันอำนวยความสะดวกตามที่เห็นสมควร หลังจากนั้นก็ควรพิจารณาสิ่งที่สามารถเพิ่มเป็นฟังก์ชันที่ไม่ใช่สมาชิกได้ std::string มักอ้างว่ามีฟังก์ชันสมาชิกมากเกินไป แม้ว่าโดยส่วนตัวแล้วฉันคิดว่ามันค่อนข้างสมเหตุสมผล ถึงกระนั้น สิ่งนี้ก็ยังแตกต่างจากไฟล์ส่วนหัวที่ประกาศอินเทอร์เฟซของไลบรารี ซึ่งสามารถคาดหวังได้ว่าฟังก์ชันการทำงานที่ครบถ้วนสมบูรณ์จะถูกอัดแน่นเข้าด้วยกัน ทำให้การแยกการใช้งาน inline ออกมาเป็นที่ต้องการมากขึ้น

person Tony Delroy    schedule 30.07.2014