ฉันมีปัญหาในการดูยูทิลิตี้ของตัวชี้ฟังก์ชัน ฉันเดาว่ามันอาจมีประโยชน์ในบางกรณี (มีอยู่จริง) แต่ฉันไม่สามารถนึกถึงกรณีที่ใช้ตัวชี้ฟังก์ชันได้ดีกว่าหรือหลีกเลี่ยงไม่ได้
คุณช่วยยกตัวอย่างการใช้ฟังก์ชันพอยน์เตอร์ (ใน C หรือ C ++) ให้ดีได้ไหม
ฉันมีปัญหาในการดูยูทิลิตี้ของตัวชี้ฟังก์ชัน ฉันเดาว่ามันอาจมีประโยชน์ในบางกรณี (มีอยู่จริง) แต่ฉันไม่สามารถนึกถึงกรณีที่ใช้ตัวชี้ฟังก์ชันได้ดีกว่าหรือหลีกเลี่ยงไม่ได้
คุณช่วยยกตัวอย่างการใช้ฟังก์ชันพอยน์เตอร์ (ใน C หรือ C ++) ให้ดีได้ไหม
คำตอบ:
ตัวอย่างส่วนใหญ่กลายเป็นการเรียกกลับ : คุณเรียกใช้ฟังก์ชัน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
โดยทั่วไปฉันมักใช้ (อย่างมืออาชีพ) ในตารางการกระโดด (ดูคำถาม StackOverflow นี้ด้วย )
ตาราง Jump มักใช้ (แต่ไม่เฉพาะ) ในเครื่องที่มีสถานะ จำกัดเพื่อให้เป็นข้อมูลขับเคลื่อน แทนที่จะเป็นสวิตช์ / เคสที่ซ้อนกัน
switch (state)
case A:
switch (event):
case e1: ....
case e2: ....
case B:
switch (event):
case e3: ....
case e1: ....
คุณสามารถสร้างพอยน์เตอร์ฟังก์ชันอาร์เรย์ 2d และเพียงแค่โทร handleEvent[state][event]
ตัวอย่าง:
std::sort
ของcomp
พารามิเตอร์ที่เป็นกลยุทธ์
ตัวอย่าง "คลาสสิก" สำหรับประโยชน์ของฟังก์ชันพอยน์เตอร์คือqsort()
ฟังก์ชันไลบรารี C ซึ่งใช้การเรียงลำดับด่วน เพื่อให้เป็นสากลสำหรับโครงสร้างข้อมูลใด ๆ และทั้งหมดที่ผู้ใช้อาจเกิดขึ้นต้องใช้ตัวชี้โมฆะสองสามตัวในการจัดเรียงข้อมูลและตัวชี้ไปยังฟังก์ชันที่รู้วิธีเปรียบเทียบสององค์ประกอบของโครงสร้างข้อมูลเหล่านี้ สิ่งนี้ช่วยให้เราสามารถสร้างฟังก์ชันที่เราเลือกได้สำหรับงานและในความเป็นจริงยังช่วยให้สามารถเลือกฟังก์ชันการเปรียบเทียบในขณะทำงานได้เช่นการเรียงลำดับจากน้อยไปมากหรือมากไปน้อย
เห็นด้วยกับทั้งหมดข้างต้นบวก .... เมื่อคุณโหลด dll แบบไดนามิกที่รันไทม์คุณจะต้องใช้ตัวชี้ฟังก์ชันเพื่อเรียกใช้ฟังก์ชัน
ฉันจะสวนทางกับกระแสตรงนี้
ใน 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 ++ เพราะเราใช้พอยน์เตอร์อัจฉริยะ ยังไงก็ตามฉันกำลังคิดอยู่
ใน C การใช้แบบคลาสสิกคือฟังก์ชัน qsortโดยที่พารามิเตอร์ที่สี่คือตัวชี้ไปยังฟังก์ชันที่จะใช้เพื่อดำเนินการสั่งซื้อภายในการจัดเรียง ในภาษา C ++ เรามักจะใช้ functors (อ็อบเจกต์ที่ดูเหมือนฟังก์ชัน) สำหรับสิ่งนี้
เมื่อเร็ว ๆ นี้ฉันใช้ตัวชี้ฟังก์ชันเพื่อสร้างเลเยอร์นามธรรม
ฉันมีโปรแกรมที่เขียนด้วยภาษา C ล้วนที่ทำงานบนระบบฝังตัว รองรับฮาร์ดแวร์หลายรูปแบบ ขึ้นอยู่กับฮาร์ดแวร์ที่ฉันใช้งานอยู่จำเป็นต้องเรียกฟังก์ชันบางเวอร์ชันที่แตกต่างกัน
ในเวลาเริ่มต้นโปรแกรมจะระบุฮาร์ดแวร์ที่ทำงานอยู่และเติมตัวชี้ฟังก์ชัน รูทีนระดับสูงทั้งหมดในโปรแกรมเรียกใช้ฟังก์ชันที่อ้างอิงโดยพอยน์เตอร์ ฉันสามารถเพิ่มการรองรับฮาร์ดแวร์รุ่นใหม่ ๆ ได้โดยไม่ต้องสัมผัสกับกิจวัตรระดับสูงกว่า
ฉันเคยใช้คำสั่ง switch / case เพื่อเลือกเวอร์ชันของฟังก์ชันที่เหมาะสม แต่สิ่งนี้ใช้ไม่ได้จริงเมื่อโปรแกรมขยายตัวเพื่อรองรับตัวแปรฮาร์ดแวร์มากขึ้นเรื่อย ๆ ฉันต้องเพิ่มคำชี้แจงกรณีทั่วทุกแห่ง
ฉันยังลองเลเยอร์ฟังก์ชันระดับกลางเพื่อดูว่าจะใช้ฟังก์ชันใด แต่ก็ไม่ได้ช่วยอะไรมาก ฉันยังคงต้องอัปเดตคำสั่งกรณีในหลาย ๆ ที่เมื่อใดก็ตามที่เราเพิ่มตัวแปรใหม่ ด้วยฟังก์ชันพอยน์เตอร์ฉันต้องเปลี่ยนฟังก์ชันการเริ่มต้นเท่านั้น
เช่นเดียวกับที่Rich ได้กล่าวไว้ข้างต้นเป็นเรื่องปกติมากที่ฟังก์ชันพอยน์เตอร์ใน Windows จะอ้างอิงที่อยู่บางส่วนที่เก็บฟังก์ชัน
เมื่อคุณเขียนโปรแกรมC language
บนแพลตฟอร์ม Windows คุณจะโหลดไฟล์ DLL บางไฟล์ในหน่วยความจำหลัก (โดยใช้LoadLibrary
) และในการใช้ฟังก์ชันที่เก็บไว้ใน DLL คุณต้องสร้างตัวชี้ฟังก์ชันและชี้ไปที่ที่อยู่เหล่านี้ (โดยใช้GetProcAddress
)
อ้างอิง:
พอยน์เตอร์ของฟังก์ชันสามารถใช้ใน C เพื่อสร้างอินเทอร์เฟซที่จะเขียนโปรแกรม ขึ้นอยู่กับฟังก์ชันการทำงานเฉพาะที่จำเป็นในรันไทม์สามารถกำหนดการนำไปใช้งานอื่นให้กับตัวชี้ฟังก์ชันได้
การใช้งานหลักของฉันของพวกเขาได้รับการเรียกกลับ: เมื่อคุณจำเป็นต้องบันทึกข้อมูลเกี่ยวกับฟังก์ชั่นไปเรียกในภายหลัง
สมมติว่าคุณกำลังเขียน 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()
สามารถเป็นฟังก์ชันใดก็ได้เนื่องจากเป็นตัวชี้ฟังก์ชัน
การใช้ตัวชี้ฟังก์ชัน
เพื่อเรียกใช้ฟังก์ชันแบบไดนามิกตามข้อมูลที่ผู้ใช้ป้อน โดยการสร้างแผนผังของสตริงและตัวชี้ฟังก์ชันในกรณีนี้
#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
มุมมองที่แตกต่างนอกเหนือจากคำตอบที่ดีอื่น ๆ ที่นี่:
ฉันหมายถึงคุณเขียนฟังก์ชัน แต่คุณไม่สามารถจัดการกับฟังก์ชันได้ ไม่มีการแสดงเวลาทำงานของฟังก์ชันที่คุณสามารถใช้ได้ เรียกว่า "ฟังก์ชัน" ไม่ได้ด้วยซ้ำ เมื่อคุณเขียน:
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
คลาส แต่หลักการส่วนใหญ่ก็ยังคงเหมือนเดิม
(&my_function)(my_arg)
, (*my_function)(my_arg)
, (**my_function)(my_arg)
, (&**my_function)(my_arg)
, (***my_function)(my_arg)
... เพราะฟังก์ชั่นที่จะสลายตัวชี้ฟังก์ชั่น
สำหรับภาษา OO เพื่อทำการเรียกหลายรูปแบบเบื้องหลัง (ซึ่งใช้ได้กับ C จนถึงจุดหนึ่งที่ฉันเดา)
นอกจากนี้ยังมีประโยชน์มากในการฉีดพฤติกรรมที่แตกต่างไปยังฟังก์ชันอื่น (foo) ที่รันไทม์ นั่นทำให้ฟังก์ชัน foo ฟังก์ชันลำดับที่สูงขึ้น นอกจากความยืดหยุ่นแล้วยังทำให้โค้ด foo อ่านได้ง่ายขึ้นเนื่องจากให้คุณดึงตรรกะพิเศษของ "if-else" ออกมา
มันเปิดใช้งานสิ่งที่มีประโยชน์อื่น ๆ อีกมากมายใน Python เช่นเครื่องกำเนิดไฟฟ้าการปิด ฯลฯ
ฉันใช้ตัวชี้ฟังก์ชันอย่างกว้างขวางสำหรับการจำลองไมโครโปรเซสเซอร์ที่มี opcodes 1 ไบต์ อาร์เรย์ของฟังก์ชันพอยน์เตอร์ 256 ฟังก์ชันเป็นวิธีที่เป็นธรรมชาติในการนำไปใช้
การใช้ตัวชี้ฟังก์ชันอย่างหนึ่งอาจเป็นจุดที่เราอาจไม่ต้องการแก้ไขโค้ดเมื่อฟังก์ชันถูกเรียกใช้ (หมายความว่าการเรียกอาจเป็นไปตามเงื่อนไขและภายใต้เงื่อนไขที่แตกต่างกันเราจำเป็นต้องดำเนินการประมวลผลประเภทต่างๆ) ที่นี่ตัวชี้ฟังก์ชันมีประโยชน์มากเนื่องจากเราไม่จำเป็นต้องแก้ไขโค้ดในตำแหน่งที่ฟังก์ชันถูกเรียกใช้ เราเรียกฟังก์ชันโดยใช้ตัวชี้ฟังก์ชันพร้อมอาร์กิวเมนต์ที่เหมาะสม ตัวชี้ฟังก์ชันสามารถทำให้ชี้ไปยังฟังก์ชันต่างๆได้ตามเงื่อนไข (สามารถทำได้ที่ไหนสักแห่งในช่วงเริ่มต้น) ยิ่งไปกว่านั้นแบบจำลองข้างต้นยังมีประโยชน์มากหากเราไม่สามารถแก้ไขโค้ดที่ถูกเรียก (สมมติว่าเป็นไลบรารี API ที่เราไม่สามารถแก้ไขได้) API ใช้ตัวชี้ฟังก์ชันสำหรับการเรียกใช้ฟังก์ชันที่กำหนดโดยผู้ใช้ที่เหมาะสม
ฉันจะพยายามให้รายการที่ค่อนข้างครอบคลุมที่นี่:
การโทรกลับ : ปรับแต่งฟังก์ชันบางอย่าง (ไลบรารี) ด้วยรหัสที่ผู้ใช้ให้มา ตัวอย่างที่สำคัญคือ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;
}
สิ่งนี้ค่อนข้างเทอะทะค่อนข้างเร็ว
โค้ดที่โหลดแบบไดนามิก : เมื่อโปรแกรมโหลดโมดูลลงในหน่วยความจำและพยายามเรียกเข้าไปในโค้ดของมันจะต้องผ่านตัวชี้ฟังก์ชัน
การใช้ตัวชี้ฟังก์ชันทั้งหมดที่ฉันเคยเห็นนั้นตกอยู่ในหนึ่งในสามคลาสกว้าง ๆ นี้