พอยน์เตอร์ของฟังก์ชันคืออะไร?


96

ฉันมีปัญหาในการดูยูทิลิตี้ของตัวชี้ฟังก์ชัน ฉันเดาว่ามันอาจมีประโยชน์ในบางกรณี (มีอยู่จริง) แต่ฉันไม่สามารถนึกถึงกรณีที่ใช้ตัวชี้ฟังก์ชันได้ดีกว่าหรือหลีกเลี่ยงไม่ได้

คุณช่วยยกตัวอย่างการใช้ฟังก์ชันพอยน์เตอร์ (ใน C หรือ C ++) ให้ดีได้ไหม


1
คุณสามารถพบการอภิปรายเกี่ยวกับตัวชี้ฟังก์ชันได้ในคำถาม SO ที่เกี่ยวข้องนี้
itsmatt

20
@itsmatt: ไม่จริง "ทีวีทำงานอย่างไร" ค่อนข้างเป็นคำถามที่แตกต่างจาก "ฉันจะทำอย่างไรกับทีวี"
sbi

6
ใน C ++ คุณอาจใช้ functor ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ) แทน
kennytm

11
ในช่วงเวลาที่มืดมนเมื่อ C ++ ถูก "คอมไพล์" เป็น C คุณจะเห็นวิธีการนำวิธีเสมือนจริงมาใช้ - ใช่ด้วยตัวชี้ฟังก์ชัน
sbk

1
สำคัญมากเมื่อคุณต้องการใช้ C ++ กับ C ++ หรือ C # ที่มีการจัดการเช่นผู้รับมอบสิทธิ์และการติดต่อกลับ
Maher

คำตอบ:


109

ตัวอย่างส่วนใหญ่กลายเป็นการเรียกกลับ : คุณเรียกใช้ฟังก์ชันf()ผ่านที่อยู่ของฟังก์ชันอื่นg()และf()เรียกg()ใช้งานบางอย่าง หากคุณส่งf()ที่อยู่ของh()แทนแล้วf()จะโทรกลับh()แทน

โดยทั่วไปนี้เป็นวิธีการที่จะparametrizeฟังก์ชั่น: บางส่วนของพฤติกรรมของมันไม่ได้ยากเขียนเป็นf()แต่ลงไปในฟังก์ชันการเรียกกลับ ผู้โทรสามารถสร้างf()พฤติกรรมที่แตกต่างกันได้โดยส่งผ่านฟังก์ชันการโทรกลับที่แตกต่างกัน คลาสสิกqsort()มาจากไลบรารีมาตรฐาน C ที่ใช้เกณฑ์การเรียงลำดับเป็นตัวชี้ไปยังฟังก์ชันเปรียบเทียบ

ใน C ++ มักจะทำโดยใช้วัตถุฟังก์ชัน (เรียกอีกอย่างว่า functors) สิ่งเหล่านี้คืออ็อบเจ็กต์ที่โอเวอร์โหลดตัวดำเนินการเรียกใช้ฟังก์ชันดังนั้นคุณจึงสามารถเรียกใช้งานได้ราวกับว่าเป็นฟังก์ชัน ตัวอย่าง:

class functor {
  public:
     void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};

functor f;
f(42);

แนวคิดที่อยู่เบื้องหลังสิ่งนี้คือไม่เหมือนกับตัวชี้ฟังก์ชันวัตถุฟังก์ชันไม่เพียง แต่มีอัลกอริทึมเท่านั้น แต่ยังรวมถึงข้อมูลด้วย:

class functor {
  public:
     functor(const std::string& prompt) : prompt_(prompt) {}
     void operator()(int i) {std::cout << prompt_ << i << '\n';}
  private:
     std::string prompt_;
};

functor f("the answer is: ");
f(42);

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


1
+1 ดูคำตอบนี้สำหรับอีกตัวอย่าง: stackoverflow.com/questions/1727824/…
sharptooth

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

2
@krynr: ฟังก์ชันเสมือนเป็นฟังก์ชันพอยน์เตอร์สำหรับตัวดำเนินการคอมไพเลอร์เท่านั้นและหากคุณต้องถามถึงสิ่งที่ดีสำหรับคุณอาจจะ (หวังว่า!) ไม่จำเป็นต้องใช้กลไกฟังก์ชันเสมือนของคอมไพเลอร์
sbi

@sbi: คุณพูดถูกแน่นอน อย่างไรก็ตามฉันคิดว่ามันช่วยให้เข้าใจสิ่งที่เกิดขึ้นภายในสิ่งที่เป็นนามธรรม นอกจากนี้การติดตั้ง vtable ของคุณเองใน C และเขียนโค้ดเชิงวัตถุจะทำให้ได้รับประสบการณ์การเรียนรู้ที่ดีมาก
Florian

จุดบราวนี่เพื่อให้คำตอบของชีวิตจักรวาลและทุกสิ่งพร้อมกับสิ่งที่ OP ขอ
ธรรมดา

41

โดยทั่วไปฉันมักใช้ (อย่างมืออาชีพ) ในตารางการกระโดด (ดูคำถาม StackOverflow นี้ด้วย )

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

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

คุณสามารถสร้างพอยน์เตอร์ฟังก์ชันอาร์เรย์ 2d และเพียงแค่โทร handleEvent[state][event]


24

ตัวอย่าง:

  1. การจัดเรียง / การค้นหาแบบกำหนดเอง
  2. รูปแบบที่แตกต่างกัน (เช่นกลยุทธ์ผู้สังเกตการณ์)
  3. โทรกลับ

1
Jump table เป็นหนึ่งในการใช้งานที่สำคัญ
Ashish

หากมีตัวอย่างที่ใช้งานได้สิ่งนี้จะทำให้ฉันได้รับคะแนนโหวตเพิ่มขึ้น
Donal Fellows

1
กลยุทธ์และผู้สังเกตการณ์น่าจะนำไปใช้โดยใช้ฟังก์ชันเสมือนได้ดีกว่าหากมี C ++ มิฉะนั้น +1
Billy ONeal

ฉันคิดว่าการใช้ตัวชี้ฟังก์ชันอย่างชาญฉลาดสามารถทำให้ผู้สังเกตการณ์มีขนาดกะทัดรัดและน้ำหนักเบามากขึ้น
Andrey

@BillyONeal เฉพาะในกรณีที่คุณยึดติดกับคำจำกัดความของ GoF อย่างเคร่งครัดโดยมี Javaisms รั่วไหล ฉันจะอธิบายstd::sortของcompพารามิเตอร์ที่เป็นกลยุทธ์
Caleth

10

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


7

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


1
ฉันทำสิ่งนี้ตลอดเวลาเพื่อรองรับ Windows XP และยังคงใช้สารพัด Windows 7 +1.
Billy ONeal

7

ฉันจะสวนทางกับกระแสตรงนี้

ใน C พอยน์เตอร์ฟังก์ชันเป็นวิธีเดียวในการปรับแต่งเนื่องจากไม่มี OO

ใน C ++ คุณสามารถใช้ฟังก์ชันพอยน์เตอร์หรือ functors (อ็อบเจ็กต์ฟังก์ชัน) เพื่อให้ได้ผลลัพธ์เดียวกัน

functors มีข้อดีหลายประการเหนือตัวชี้ฟังก์ชั่นดิบเนื่องจากลักษณะของวัตถุโดยเฉพาะ:

  • พวกเขาอาจนำเสนอไฟล์ operator()
  • สามารถมีสถานะ / การอ้างอิงถึงตัวแปรที่มีอยู่
  • สามารถสร้างได้ตรงจุด ( lambdaและbind)

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

typedef float(*pt2Func)(float, float);
  // defines a symbol pt2Func, pointer to a (float, float) -> float function

typedef int (TMyClass::*pt2Member)(float, char, char);
  // defines a symbol pt2Member, pointer to a (float, char, char) -> int function
  // belonging to the class TMyClass

ครั้งเดียวที่ฉันเคยเห็นฟังก์ชันพอยน์เตอร์ที่ใช้ซึ่ง functors ไม่สามารถอยู่ใน Boost.Spirit พวกเขาใช้ไวยากรณ์ในทางที่ผิดอย่างเต็มที่เพื่อส่งผ่านพารามิเตอร์จำนวนหนึ่งโดยพลการเป็นพารามิเตอร์เทมเพลตเดียว

 typedef SpecialClass<float(float,float)> class_type;

แต่เนื่องจากเทมเพลตและแลมบดาแบบต่างๆอยู่ใกล้ ๆ ฉันจึงไม่แน่ใจว่าเราจะใช้ตัวชี้ฟังก์ชันในโค้ด C ++ ที่แท้จริงมานานแล้ว


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

3
@krynr: ฉันไม่เห็นด้วยอย่างสุภาพ สิ่งที่สำคัญคือสิ่งที่คุณเห็นและพิมพ์นั่นคือไวยากรณ์ที่คุณใช้ มันไม่ควรสำคัญกับคุณวิธีการทำงานทั้งหมดเบื้องหลัง: นี่คือสิ่งที่เป็นนามธรรมเป็นเรื่องเกี่ยวกับ
Matthieu M.

5

ใน C การใช้แบบคลาสสิกคือฟังก์ชัน qsortโดยที่พารามิเตอร์ที่สี่คือตัวชี้ไปยังฟังก์ชันที่จะใช้เพื่อดำเนินการสั่งซื้อภายในการจัดเรียง ในภาษา C ++ เรามักจะใช้ functors (อ็อบเจกต์ที่ดูเหมือนฟังก์ชัน) สำหรับสิ่งนี้


2
@Kenny ™: ฉันกำลังชี้ให้เห็นเพียงอินสแตนซ์อื่น ๆ ของสิ่งนี้ในไลบรารีมาตรฐาน C ตัวอย่างที่คุณอ้างถึงเป็นส่วนหนึ่งของไลบรารีของบุคคลที่สาม
Billy ONeal

5

เมื่อเร็ว ๆ นี้ฉันใช้ตัวชี้ฟังก์ชันเพื่อสร้างเลเยอร์นามธรรม

ฉันมีโปรแกรมที่เขียนด้วยภาษา C ล้วนที่ทำงานบนระบบฝังตัว รองรับฮาร์ดแวร์หลายรูปแบบ ขึ้นอยู่กับฮาร์ดแวร์ที่ฉันใช้งานอยู่จำเป็นต้องเรียกฟังก์ชันบางเวอร์ชันที่แตกต่างกัน

ในเวลาเริ่มต้นโปรแกรมจะระบุฮาร์ดแวร์ที่ทำงานอยู่และเติมตัวชี้ฟังก์ชัน รูทีนระดับสูงทั้งหมดในโปรแกรมเรียกใช้ฟังก์ชันที่อ้างอิงโดยพอยน์เตอร์ ฉันสามารถเพิ่มการรองรับฮาร์ดแวร์รุ่นใหม่ ๆ ได้โดยไม่ต้องสัมผัสกับกิจวัตรระดับสูงกว่า

ฉันเคยใช้คำสั่ง switch / case เพื่อเลือกเวอร์ชันของฟังก์ชันที่เหมาะสม แต่สิ่งนี้ใช้ไม่ได้จริงเมื่อโปรแกรมขยายตัวเพื่อรองรับตัวแปรฮาร์ดแวร์มากขึ้นเรื่อย ๆ ฉันต้องเพิ่มคำชี้แจงกรณีทั่วทุกแห่ง

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


3

เช่นเดียวกับที่Rich ได้กล่าวไว้ข้างต้นเป็นเรื่องปกติมากที่ฟังก์ชันพอยน์เตอร์ใน Windows จะอ้างอิงที่อยู่บางส่วนที่เก็บฟังก์ชัน

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

อ้างอิง:


2

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


2

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

สมมติว่าคุณกำลังเขียน Bomberman 5 วินาทีหลังจากบุคคลนั้นวางระเบิดมันควรจะระเบิด (เรียกใช้explode()ฟังก์ชัน)

ตอนนี้มี 2 วิธีในการทำ วิธีหนึ่งคือการ "ตรวจสอบ" ระเบิดทั้งหมดบนหน้าจอเพื่อดูว่าพร้อมที่จะระเบิดในลูปหลักหรือไม่

foreach bomb in game 
   if bomb.boomtime()
       bomb.explode()

อีกวิธีหนึ่งคือแนบการโทรกลับไปยังระบบนาฬิกาของคุณ เมื่อระเบิดปลูกคุณเพิ่มการเรียกกลับจะทำให้มันเรียก bomb.explode () เมื่อเวลาที่เหมาะสม

// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;

// IN the main loop:
foreach callback in callbacks
    if callback.timeToRun
         callback.function()

ที่นี่callback.function()สามารถเป็นฟังก์ชันใดก็ได้เนื่องจากเป็นตัวชี้ฟังก์ชัน


คำถามถูกแท็กด้วย [C] และ [C ++] ไม่ใช่แท็กภาษาอื่น ดังนั้นการให้ข้อมูลโค้ดในภาษาอื่นจึงค่อนข้างไม่ตรงประเด็น
cmaster - คืนสถานะโมนิกา

2

การใช้ตัวชี้ฟังก์ชัน

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

#include<iostream>
#include<map>
using namespace std;
//typedef  map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;

int Add(int x, int y)
{
    return x+y;
}
int Sub(int x, int y)
{
        return x-y;
}
int Multi(int x, int y)
{
        return x*y;
}
void initializeFunc()
{
        objFunMap["Add"]=Add;
        objFunMap["Sub"]=Sub;
        objFunMap["Multi"]=Multi;
}
int main()
{
    initializeFunc();

    while(1)
    {
        string func;
        cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
        int no, a, b;
        cin>>no;

        if(no==1)
            func = "Add";
        else if(no==2)
            func = "Sub";
        else if(no==3)
            func = "Multi";
        else 
            break;

        cout<<"\nEnter 2 no :";
                cin>>a>>b;

        //function is called using function pointer based on user input
        //If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
        int ret = objFunMap[func](a, b);      
        cout<<ret<<endl;
    }
    return 0;
}

ด้วยวิธีนี้เราได้ใช้ตัวชี้ฟังก์ชันในรหัส บริษัท จริงของเรา คุณสามารถเขียนจำนวนฟังก์ชัน 'n' และเรียกใช้โดยใช้วิธีนี้

เอาท์พุท:

    ป้อนตัวเลือกของคุณ (1. เพิ่ม 2. ย่อย 3. หลาย): 1
    ใส่ 2 no: 2 4
    6
    ป้อนตัวเลือกของคุณ (1. เพิ่ม 2. ย่อย 3. หลาย): 2
    ใส่ 2 no: 10 3
    7
    ป้อนตัวเลือกของคุณ (1. เพิ่ม 2. ย่อย 3. หลาย): 3
    ใส่ 2 no: 3 6
    18

2

มุมมองที่แตกต่างนอกเหนือจากคำตอบที่ดีอื่น ๆ ที่นี่:

ใน C คุณใช้เฉพาะฟังก์ชันพอยน์เตอร์เท่านั้นไม่ใช่ฟังก์ชัน (โดยตรง)

ฉันหมายถึงคุณเขียนฟังก์ชัน แต่คุณไม่สามารถจัดการกับฟังก์ชันได้ ไม่มีการแสดงเวลาทำงานของฟังก์ชันที่คุณสามารถใช้ได้ เรียกว่า "ฟังก์ชัน" ไม่ได้ด้วยซ้ำ เมื่อคุณเขียน:

my_function(my_arg);

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

(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);

และอื่น ๆ (ขอบคุณ @LuuVinhPhuc)

คุณใช้ตัวชี้ฟังก์ชันเป็นค่าอยู่แล้ว เห็นได้ชัดว่าคุณต้องการมีตัวแปรสำหรับค่าเหล่านั้น - และนี่คือที่ที่การใช้ metion อื่น ๆ ทั้งหมดเข้ามา: Polymorphism / การปรับแต่ง (เช่นใน qsort) การเรียกกลับตารางกระโดดเป็นต้น

ใน C ++ นั้นซับซ้อนกว่าเล็กน้อยเนื่องจากเรามี lambdas และวัตถุที่มีoperator()และแม้แต่std::functionคลาส แต่หลักการส่วนใหญ่ก็ยังคงเหมือนเดิม


2
น่าสนใจมากยิ่งขึ้นคุณสามารถเรียกฟังก์ชั่นเป็น(&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... เพราะฟังก์ชั่นที่จะสลายตัวชี้ฟังก์ชั่น
phuclv

1

สำหรับภาษา OO เพื่อทำการเรียกหลายรูปแบบเบื้องหลัง (ซึ่งใช้ได้กับ C จนถึงจุดหนึ่งที่ฉันเดา)

นอกจากนี้ยังมีประโยชน์มากในการฉีดพฤติกรรมที่แตกต่างไปยังฟังก์ชันอื่น (foo) ที่รันไทม์ นั่นทำให้ฟังก์ชัน foo ฟังก์ชันลำดับที่สูงขึ้น นอกจากความยืดหยุ่นแล้วยังทำให้โค้ด foo อ่านได้ง่ายขึ้นเนื่องจากให้คุณดึงตรรกะพิเศษของ "if-else" ออกมา

มันเปิดใช้งานสิ่งที่มีประโยชน์อื่น ๆ อีกมากมายใน Python เช่นเครื่องกำเนิดไฟฟ้าการปิด ฯลฯ


0

ฉันใช้ตัวชี้ฟังก์ชันอย่างกว้างขวางสำหรับการจำลองไมโครโปรเซสเซอร์ที่มี opcodes 1 ไบต์ อาร์เรย์ของฟังก์ชันพอยน์เตอร์ 256 ฟังก์ชันเป็นวิธีที่เป็นธรรมชาติในการนำไปใช้


0

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


0

ฉันจะพยายามให้รายการที่ค่อนข้างครอบคลุมที่นี่:

  • การโทรกลับ : ปรับแต่งฟังก์ชันบางอย่าง (ไลบรารี) ด้วยรหัสที่ผู้ใช้ให้มา ตัวอย่างที่สำคัญคือqsort()แต่ยังมีประโยชน์ในการจัดการกับเหตุการณ์ (เช่นปุ่มเรียกการโทรกลับเมื่อมีการคลิก) หรือจำเป็นในการเริ่มเธรด ( pthread_create())

  • Polymorphism : vtable ในคลาส C ++ ไม่ใช่แค่ตารางของตัวชี้ฟังก์ชัน และโปรแกรม C อาจเลือกที่จะจัดเตรียม vtable สำหรับวัตถุบางอย่าง:

    struct Base;
    struct Base_vtable {
        void (*destruct)(struct Base* me);
    };
    struct Base {
        struct Base_vtable* vtable;
    };
    
    struct Derived;
    struct Derived_vtable {
        struct Base_vtable;
        void (*frobnicate)(struct Derived* me);
    };
    struct Derived {
        struct Base;
        int bar, baz;
    }
    

    จากDerivedนั้นผู้สร้างจะตั้งค่าvtableตัวแปรสมาชิกเป็นโกลบอลอ็อบเจ็กต์ด้วยการนำคลาสที่ได้รับมาใช้destructและfrobnicateและโค้ดที่ต้องการทำลายการstruct Base*เรียกแบบง่ายๆbase->vtable->destruct(base)ซึ่งจะเรียกเวอร์ชันที่ถูกต้องของตัวทำลายโดยไม่ขึ้นอยู่กับคลาสที่ได้รับมาซึ่งbaseชี้ไปที่ .

    หากไม่มีพอยน์เตอร์ฟังก์ชันความหลากหลายจะต้องถูกเข้ารหัสด้วยโครงสร้างของสวิตช์เช่น

    switch(me->type) {
        case TYPE_BASE: base_implementation(); break;
        case TYPE_DERIVED1: derived1_implementation(); break;
        case TYPE_DERIVED2: derived2_implementation(); break;
        case TYPE_DERIVED3: derived3_implementation(); break;
    }
    

    สิ่งนี้ค่อนข้างเทอะทะค่อนข้างเร็ว

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

การใช้ตัวชี้ฟังก์ชันทั้งหมดที่ฉันเคยเห็นนั้นตกอยู่ในหนึ่งในสามคลาสกว้าง ๆ นี้

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