เหตุใดคำจำกัดความของฟังก์ชันพอยน์เตอร์จึงทำงานกับเครื่องหมาย '&' หรือเครื่องหมายดอกจัน '*' จำนวนเท่าใดก็ได้


216

ทำไมงานต่อไปนี้

void foo() {
    cout << "Foo to you too!\n";
};

int main() {
    void (*p1_foo)() = foo;
    void (*p2_foo)() = *foo;
    void (*p3_foo)() = &foo;
    void (*p4_foo)() = *&foo;
    void (*p5_foo)() = &*foo;
    void (*p6_foo)() = **foo;
    void (*p7_foo)() = **********************foo;

    (*p1_foo)();
    (*p2_foo)();
    (*p3_foo)();
    (*p4_foo)();
    (*p5_foo)();
    (*p6_foo)();
    (*p7_foo)();
}

คำตอบ:


224

มีเพียงไม่กี่ชิ้นเท่านั้นที่ทำให้ชุดค่าผสมทั้งหมดเหล่านี้ทำงานเหมือนกัน

เหตุผลพื้นฐานว่าทำไมงานเหล่านี้ทั้งหมดคือฟังก์ชัน (เช่นfoo) สามารถแปลงเป็นตัวชี้ไปยังฟังก์ชันโดยปริยาย นี่คือเหตุผลที่void (*p1_foo)() = foo;ผลงาน: จะถูกแปลงโดยปริยายเข้าไปในตัวชี้ไปที่ตัวเองและตัวชี้ที่กำหนดให้foop1_foo

unary &เมื่อนำไปใช้กับฟังก์ชั่นจะให้ตัวชี้ไปยังฟังก์ชันเช่นเดียวกับที่มันให้ที่อยู่ของวัตถุเมื่อนำไปใช้กับวัตถุ สำหรับพอยน์เตอร์ไปยังฟังก์ชั่นทั่วไปมันจะซ้ำซ้อนเสมอเนื่องจากการแปลงฟังก์ชันไปเป็นฟังก์ชั่นตัวชี้โดยนัย ในกรณีใด ๆ นี่คือเหตุผลที่void (*p3_foo)() = &foo;ทำงาน

unary *เมื่อนำไปใช้กับตัวชี้ฟังก์ชั่นให้ผลตอบแทนฟังก์ชั่นชี้ไปที่เช่นเดียวกับมันให้ผลผลิตวัตถุชี้ไปที่เมื่อมันถูกนำไปใช้กับตัวชี้ธรรมดาไปยังวัตถุ

กฎเหล่านี้สามารถรวมกันได้ ลองพิจารณาตัวอย่างที่สองถึงล่าสุดของคุณ**foo:

  • อันดับแรกfooจะถูกแปลงเป็นตัวชี้ไปยังตัวเองโดยปริยายและตัวแรก*จะถูกนำไปใช้กับตัวชี้ฟังก์ชั่นนั้นโดยยอมให้ฟังก์ชั่นfooอีกครั้ง
  • แล้วผลที่ตามมาเป็นอีกครั้งโดยปริยายแปลงเป็นตัวชี้ไปที่ตัวเองและครั้งที่สองถูกนำไปใช้อีกครั้งยอมฟังก์ชั่น*foo
  • จากนั้นจะถูกแปลงเป็นตัวชี้ฟังก์ชันอีกครั้งโดยปริยายและกำหนดให้กับตัวแปร

คุณสามารถเพิ่มได้มาก*เท่าที่คุณต้องการผลลัพธ์จะเหมือนเดิมเสมอ ยิ่ง*s, merrier

เราสามารถพิจารณาตัวอย่างที่ห้าของคุณ&*foo:

  • ก่อนfooจะถูกแปลงเป็นตัวชี้ไปยังตัวเองโดยปริยาย *จะนำมาใช้กับ unary และให้ผลfooอีกครั้ง
  • จากนั้น&จะนำไปใช้fooโดยยอมให้ตัวชี้ไปยังfooซึ่งถูกกำหนดให้กับตัวแปร

&เท่านั้นที่สามารถนำไปใช้กับการทำงานแม้ว่าจะไม่ฟังก์ชั่นที่ได้รับการแปลงเป็นตัวชี้ฟังก์ชั่น (เว้นแต่ของหลักสูตรตัวชี้ฟังก์ชั่นเป็นตัวแปรซึ่งในกรณีนี้ผลที่ได้คือตัวชี้ไป-a-pointer- to-a-function ตัวอย่างเช่นคุณสามารถเพิ่มในรายการของคุณvoid (**pp_foo)() = &p7_foo;)

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

โปรดทราบว่าคุณไม่จำเป็นต้องใช้เอกภาพ*เพื่อทำการโทรผ่านตัวชี้ฟังก์ชั่น ทั้งสอง(*p1_foo)();และ(p1_foo)();มีผลลัพธ์เหมือนกันอีกครั้งเนื่องจากการแปลงฟังก์ชันเป็นตัวชี้ฟังก์ชัน


2
@Jimmy: พวกมันไม่ได้อ้างถึงพอยน์เตอร์ของฟังก์ชั่นมันเป็นแค่พอยน์เตอร์ของฟังก์ชั่น &fooใช้ที่อยู่ของfooซึ่งส่งผลให้ตัวชี้ฟังก์ชั่นชี้ไปที่fooอย่างที่คาดหวัง
Dennis Zickefoose

2
คุณไม่สามารถโซ่&ผู้ประกอบการสำหรับวัตถุอย่างใดอย่างหนึ่งได้รับint p;, &pผลตอบแทนถัวเฉลี่ยตัวชี้ไปยังpและคือการแสดงออก rvalue; &ผู้ประกอบการต้องมีการแสดงออก lvalue
James McNellis

12
ฉันไม่เห็นด้วย. ยิ่ง*'s ที่ความสุขน้อยลง
เซทคาร์เนกี้

28
โปรดอย่าแก้ไขไวยากรณ์ของตัวอย่างของฉัน ฉันเลือกตัวอย่างเป็นพิเศษเพื่อแสดงให้เห็นถึงคุณสมบัติของภาษา
James McNellis

7
ขณะที่ทราบด้านมาตรฐาน C อย่างชัดเจนระบุว่าการรวมกันของ&*ยกเลิกการออกแต่ละอื่น ๆ (6.5.3.2): "The unary & operator yields the address of its operand."/ - "If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue."/
Lundin

9

ฉันคิดว่ามันเป็นประโยชน์เช่นกันที่จะจำไว้ว่า C เป็นเพียงนามธรรมสำหรับเครื่องจักรพื้นฐานและนี่คือหนึ่งในสถานที่ที่สิ่งที่เป็นนามธรรมนั้นรั่ว

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


0

&และ *เป็นการดำเนินงาน idempotent บนสัญลักษณ์ที่ประกาศเป็นฟังก์ชันใน C ซึ่งหมายถึงfunc == *func == &func == *&funcและดังนั้น*func == **func

ก็หมายความว่าชนิดint ()เป็นเช่นเดียวกับint (*)()เป็นพารามิเตอร์ฟังก์ชั่นและ func กำหนดไว้สามารถส่งเป็น*func, หรือfunc เป็นเช่นเดียวกับ ลิงค์ Godbolt&func(&func)()func()

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

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

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