ฉันมีประสบการณ์เมื่อเร็ว ๆ นี้กับตัวชี้ฟังก์ชั่นใน C.
ดังนั้นด้วยประเพณีการตอบคำถามของคุณเองฉันจึงตัดสินใจสรุปพื้นฐานเบื้องต้นเล็กน้อยสำหรับผู้ที่ต้องการดำน้ำแบบด่วนในเรื่อง
ฉันมีประสบการณ์เมื่อเร็ว ๆ นี้กับตัวชี้ฟังก์ชั่นใน C.
ดังนั้นด้วยประเพณีการตอบคำถามของคุณเองฉันจึงตัดสินใจสรุปพื้นฐานเบื้องต้นเล็กน้อยสำหรับผู้ที่ต้องการดำน้ำแบบด่วนในเรื่อง
คำตอบ:
เริ่มจากฟังก์ชั่นพื้นฐานที่เราจะชี้ไปที่ :
int addInt(int n, int m) {
return n+m;
}
สิ่งแรกที่ให้มีกำหนดตัวชี้ไปยังฟังก์ชั่นที่ได้รับ 2 ที่int
และส่งกลับint
:
int (*functionPtr)(int,int);
ตอนนี้เราสามารถชี้ไปที่ฟังก์ชั่นของเราได้อย่างปลอดภัย:
functionPtr = &addInt;
ตอนนี้เรามีตัวชี้ไปยังฟังก์ชั่นมาใช้งานกัน:
int sum = (*functionPtr)(2, 3); // sum == 5
การส่งตัวชี้ไปยังฟังก์ชันอื่นโดยพื้นฐานแล้วจะเหมือนกัน:
int add2to3(int (*functionPtr)(int, int)) {
return (*functionPtr)(2, 3);
}
เราสามารถใช้พอยน์เตอร์ของฟังก์ชั่นในการคืนค่าได้เช่นกัน (พยายามติดตาม, มันยุ่งเหยิง):
// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
printf("Got parameter %d", n);
int (*functionPtr)(int,int) = &addInt;
return functionPtr;
}
แต่มันก็ดีกว่าการใช้typedef
:
typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef
myFuncDef functionFactory(int n) {
printf("Got parameter %d", n);
myFuncDef functionPtr = &addInt;
return functionPtr;
}
pshufb
มันช้าดังนั้นการดำเนินการก่อนหน้านี้ยังเร็วกว่า x264 / x265 ใช้สิ่งนี้อย่างกว้างขวางและเป็นโอเพ่นซอร์ส
ตัวชี้ฟังก์ชั่นใน C สามารถใช้ในการเขียนโปรแกรมเชิงวัตถุใน C
ตัวอย่างเช่นบรรทัดต่อไปนี้เขียนใน C:
String s1 = newString();
s1->set(s1, "hello");
ใช่->
และการขาดการnew
ประกอบการเป็นผู้ให้ตายแล้วออกไป แต่แน่ใจว่าดูเหมือนจะบ่งบอกว่าเรากำลังตั้งค่าข้อความของบางระดับจะเป็นString
"hello"
โดยใช้ตัวชี้ฟังก์ชั่นก็เป็นไปได้ที่จะเลียนแบบวิธีการใน C
สิ่งนี้สำเร็จได้อย่างไร
String
ระดับเป็นจริงstruct
กับพวงของคำแนะนำการทำงานซึ่งทำหน้าที่เป็นวิธีการวิธีการจำลองแบบ ต่อไปนี้เป็นการประกาศบางส่วนของString
คลาส:
typedef struct String_Struct* String;
struct String_Struct
{
char* (*get)(const void* self);
void (*set)(const void* self, char* value);
int (*length)(const void* self);
};
char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);
String newString();
ดังที่เห็นได้ว่าเมธอดของString
คลาสเป็นตัวชี้ฟังก์ชันไปยังฟังก์ชันที่ประกาศ ในการจัดทำอินสแตนซ์ของString
ที่newString
ฟังก์ชั่นที่เรียกกันในการตั้งค่าตัวชี้ฟังก์ชั่นการทำงานของตน:
String newString()
{
String self = (String)malloc(sizeof(struct String_Struct));
self->get = &getString;
self->set = &setString;
self->length = &lengthString;
self->set(self, "");
return self;
}
ตัวอย่างเช่นgetString
ฟังก์ชันที่เรียกใช้โดยการเรียกใช้get
เมธอดจะถูกกำหนดดังนี้:
char* getString(const void* self_obj)
{
return ((String)self_obj)->internal->value;
}
สิ่งหนึ่งที่สามารถสังเกตได้คือไม่มีแนวคิดของอินสแตนซ์ของวัตถุและมีวิธีการที่เป็นส่วนหนึ่งของวัตถุดังนั้น "วัตถุตัวเอง" จะต้องผ่านในแต่ละการร้องขอ (และinternal
นี่เป็นเพียงการซ่อนstruct
ซึ่งถูกตัดออกจากรายการรหัสก่อนหน้า - เป็นวิธีการซ่อนข้อมูล แต่ไม่เกี่ยวข้องกับตัวชี้ฟังก์ชัน)
ดังนั้นแทนที่จะความสามารถในการทำหนึ่งต้องผ่านในวัตถุในการดำเนินการเกี่ยวกับs1->set("hello");
s1->set(s1, "hello")
กับที่คำอธิบายเล็ก ๆ น้อย ๆ ไม่ต้องผ่านในการอ้างอิงถึงตัวเองออกจากวิธีการที่เราจะย้ายไปยังส่วนถัดไปซึ่งเป็นมรดกใน C
สมมติว่าเราต้องการให้ subclass ของString
, ImmutableString
พูด เพื่อที่จะทำให้สตริงไม่เปลี่ยนรูปset
วิธีจะไม่สามารถเข้าถึงได้ในขณะที่ยังคงเข้าถึงget
และlength
และบังคับให้ "ตัวสร้าง" ที่จะยอมรับchar*
:
typedef struct ImmutableString_Struct* ImmutableString;
struct ImmutableString_Struct
{
String base;
char* (*get)(const void* self);
int (*length)(const void* self);
};
ImmutableString newImmutableString(const char* value);
โดยทั่วไปสำหรับคลาสย่อยทั้งหมดเมธอดที่มีอยู่จะเป็นตัวชี้ฟังก์ชันอีกครั้ง เวลานี้การประกาศสำหรับวิธีการที่ไม่อยู่จึงไม่สามารถเรียกว่าในset
ImmutableString
สำหรับการดำเนินการของImmutableString
รหัสที่เกี่ยวข้องเท่านั้นคือฟังก์ชั่น "ตัวสร้าง" ที่newImmutableString
:
ImmutableString newImmutableString(const char* value)
{
ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));
self->base = newString();
self->get = self->base->get;
self->length = self->base->length;
self->base->set(self->base, (char*)value);
return self;
}
ในการสร้างอินสแตนซ์ของImmutableString
ฟังก์ชันพอยน์เตอร์ไปยังget
และlength
เมธอดอ้างถึงจริง ๆString.get
และString.length
เมธอดโดยการผ่านbase
ตัวแปรซึ่งเป็นString
วัตถุที่จัดเก็บไว้ภายใน
การใช้ตัวชี้ฟังก์ชันสามารถรับการสืบทอดของเมธอดจากซูเปอร์คลาส
เรายังสามารถดำเนินการต่อไปความแตกต่างใน C
ตัวอย่างเช่นถ้าเราต้องการเปลี่ยนพฤติกรรมของlength
วิธีการที่จะกลับมา0
ตลอดเวลาในImmutableString
ชั้นเรียนด้วยเหตุผลบางอย่างสิ่งที่จะต้องทำคือ:
length
วิธีการเอาชนะlength
วิธีการเอาชนะการเพิ่มlength
วิธีการแทนที่ในImmutableString
อาจทำได้โดยการเพิ่มlengthOverrideMethod
:
int lengthOverrideMethod(const void* self)
{
return 0;
}
จากนั้นตัวชี้ฟังก์ชั่นสำหรับlength
วิธีการในตัวสร้างจะถูกเชื่อมโยงกับlengthOverrideMethod
:
ImmutableString newImmutableString(const char* value)
{
ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));
self->base = newString();
self->get = self->base->get;
self->length = &lengthOverrideMethod;
self->base->set(self->base, (char*)value);
return self;
}
ตอนนี้แทนที่จะมีพฤติกรรมที่เหมือนกันสำหรับlength
วิธีการในImmutableString
ชั้นเรียนเป็นString
ชั้นเรียนตอนนี้length
วิธีการจะอ้างถึงพฤติกรรมที่กำหนดไว้ในlengthOverrideMethod
ฟังก์ชั่น
ฉันต้องเพิ่มข้อจำกัดความรับผิดชอบที่ฉันยังคงเรียนรู้วิธีการเขียนด้วยสไตล์การเขียนโปรแกรมเชิงวัตถุใน C ดังนั้นอาจมีประเด็นที่ฉันไม่ได้อธิบายได้ดีหรืออาจปิดเครื่องหมายในแง่ของการใช้ OOP ที่ดีที่สุด ใน C. แต่จุดประสงค์ของฉันคือพยายามแสดงให้เห็นถึงการใช้งานตัวชี้ฟังก์ชั่นหลายอย่าง
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการเขียนโปรแกรมเชิงวัตถุใน C โปรดอ้างอิงคำถามต่อไปนี้:
ClassName_methodName
ตั้งชื่อฟังก์ชั่นบางอย่าง จากนั้นคุณจะได้รับค่าใช้จ่ายรันไทม์และพื้นที่เก็บข้อมูลเท่ากับที่คุณทำใน C ++ และ Pascal
คำแนะนำเกี่ยวกับการถูกไล่ออก: วิธีใช้งานพอยน์เตอร์ใน GCC บนเครื่อง x86 โดยการคอมไพล์โค้ดของคุณด้วยมือ:
ตัวอักษรสตริงเหล่านี้เป็นไบต์ของรหัสเครื่อง x86 แบบ 32 บิต 0xC3
เป็นx86ret
การเรียนการสอน
โดยปกติแล้วคุณจะไม่เขียนด้วยมือคุณจะเขียนด้วยภาษาแอสเซมบลีแล้วใช้แอสเซมเบลอร์nasm
เพื่อรวมเข้าไปในไบนารีแบบเรียบซึ่งคุณ hexdump เป็นตัวอักษรสตริง C
ส่งคืนค่าปัจจุบันของการลงทะเบียน EAX
int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
เขียนฟังก์ชั่นการสลับ
int a = 10, b = 20;
((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
เขียนตัวนับ for-loop ที่ 1,000 เรียกใช้ฟังก์ชันในแต่ละครั้ง
((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
คุณสามารถเขียนฟังก์ชันเรียกซ้ำที่นับได้ถึง 100
const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
i = ((int(*)())(lol))(lol);
โปรดทราบว่าคอมไพเลอร์วางตัวอักษรสตริงใน.rodata
ส่วน (หรือ.rdata
บน Windows) ซึ่งมีการเชื่อมโยงเป็นส่วนหนึ่งของส่วนข้อความ (พร้อมกับรหัสสำหรับฟังก์ชั่น)
ส่วนของข้อความได้รับการอนุญาตให้อ่าน + Exec ดังนั้นการส่งสตริงตัวอักษรไปยังฟังก์ชันพอยน์เตอร์จึงทำงานได้โดยไม่จำเป็นต้องมีmprotect()
หรือการVirtualProtect()
เรียกของระบบเหมือนที่คุณต้องการสำหรับหน่วยความจำที่จัดสรรแบบไดนามิก (หรือgcc -z execstack
เชื่อมโยงโปรแกรมด้วยสแต็ค + ส่วนข้อมูล + ความสามารถในการประมวลผลฮีปเพื่อแฮ็คอย่างรวดเร็ว)
ในการแยกส่วนเหล่านี้คุณสามารถคอมไพล์สิ่งนี้เพื่อวางเลเบลบนไบต์และใช้ disassembler
// at global scope
const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";
การคอมไพล์ด้วยgcc -c -m32 foo.c
และแยกส่วนobjdump -D -rwC -Mintel
เราสามารถรับชุดประกอบและพบว่ารหัสนี้ละเมิด ABI โดยการปิดกั้น EBX (การลงทะเบียนที่สงวนไว้สำหรับการโทร) และโดยทั่วไปจะไม่มีประสิทธิภาพ
00000000 <swap>:
0: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4] # load int *a arg from the stack
4: 8b 5c 24 08 mov ebx,DWORD PTR [esp+0x8] # ebx = b
8: 8b 00 mov eax,DWORD PTR [eax] # dereference: eax = *a
a: 8b 1b mov ebx,DWORD PTR [ebx]
c: 31 c3 xor ebx,eax # pointless xor-swap
e: 31 d8 xor eax,ebx # instead of just storing with opposite registers
10: 31 c3 xor ebx,eax
12: 8b 4c 24 04 mov ecx,DWORD PTR [esp+0x4] # reload a from the stack
16: 89 01 mov DWORD PTR [ecx],eax # store to *a
18: 8b 4c 24 08 mov ecx,DWORD PTR [esp+0x8]
1c: 89 19 mov DWORD PTR [ecx],ebx
1e: c3 ret
not shown: the later bytes are ASCII text documentation
they're not executed by the CPU because the ret instruction sends execution back to the caller
รหัสเครื่องนี้ (อาจ) ทำงานในรหัส 32 บิตบน Windows, Linux, OS X และอื่น ๆ : อนุสัญญาการโทรเริ่มต้นบน OS ทั้งหมดเหล่านี้ส่งผ่าน args บนสแต็กแทนการลงทะเบียนที่มีประสิทธิภาพมากขึ้น แต่ EBX นั้นถูกสงวนไว้สำหรับการโทรในการประชุมปกติทั้งหมดดังนั้นการใช้มันเป็น register register โดยไม่บันทึก / เรียกคืนมันสามารถทำให้ caller crash ได้ง่าย
หนึ่งในสิ่งที่ฉันชอบใช้สำหรับพอยน์เตอร์ของฟังก์ชั่นคือตัววนซ้ำราคาถูกและใช้ง่าย -
#include <stdio.h>
#define MAX_COLORS 256
typedef struct {
char* name;
int red;
int green;
int blue;
} Color;
Color Colors[MAX_COLORS];
void eachColor (void (*fp)(Color *c)) {
int i;
for (i=0; i<MAX_COLORS; i++)
(*fp)(&Colors[i]);
}
void printColor(Color* c) {
if (c->name)
printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}
int main() {
Colors[0].name="red";
Colors[0].red=255;
Colors[1].name="blue";
Colors[1].blue=255;
Colors[2].name="black";
eachColor(printColor);
}
int (*cb)(void *arg, ...)
ของฉันมีลักษณะเช่นนี้ ค่าส่งคืนของตัววนซ้ำยังช่วยให้ฉันหยุดก่อน (ถ้าไม่ใช่ศูนย์)
พอยน์เตอร์ของฟังก์ชั่นกลายเป็นเรื่องง่ายที่จะประกาศเมื่อคุณมีผู้ประกาศพื้นฐาน:
ID
: ID เป็น*D
: D ตัวชี้ไปยังD(<parameters>)
: D ฟังก์ชั่นการถ่าย<
พารามิเตอร์>
กลับในขณะที่ D เป็นตัวประกาศอื่นที่สร้างขึ้นโดยใช้กฎเดียวกัน ในท้ายที่สุดมันจะลงท้ายด้วยID
(ดูตัวอย่างด้านล่าง) ซึ่งเป็นชื่อของนิติบุคคลที่ประกาศ ลองสร้างฟังก์ชั่นโดยใช้ตัวชี้ไปยังฟังก์ชั่นที่ไม่ทำอะไรเลยและคืนค่า int และคืนค่าพอยน์เตอร์ให้กับฟังก์ชันที่ใช้ char และ return int ด้วยประเภท defs มันเป็นเช่นนี้
typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);
อย่างที่คุณเห็นมันเป็นเรื่องง่ายที่จะสร้างมันขึ้นมาโดยใช้ typedefs หากไม่มี typedefs ก็ไม่ยากอย่างใดอย่างหนึ่งกับกฎของนักประกาศด้านบนที่ใช้อย่างสม่ำเสมอ อย่างที่คุณเห็นฉันพลาดส่วนที่ตัวชี้ชี้ไปและสิ่งที่ฟังก์ชันส่งคืน นั่นคือสิ่งที่ปรากฏที่ด้านซ้ายสุดของการประกาศและไม่เป็นที่สนใจ: มันจะถูกเพิ่มเข้าไปในท้ายที่สุดถ้ามีผู้ประกาศขึ้นมาแล้ว มาทำกันเถอะ สร้างขึ้นอย่างต่อเนื่องใช้คำแรก - แสดงโครงสร้างการใช้[
และ]
:
function taking
[pointer to [function taking [void] returning [int]]]
returning
[pointer to [function taking [char] returning [int]]]
อย่างที่คุณเห็นหนึ่งสามารถอธิบายประเภทได้อย่างสมบูรณ์โดยผนวกท้ายประกาศหนึ่งหลังจากแต่ละอื่น ๆ การก่อสร้างสามารถทำได้สองวิธี หนึ่งคือจากล่างขึ้นบนเริ่มต้นด้วยสิ่งที่ถูกต้อง (ใบไม้) และทำงานผ่านจนถึงตัวระบุ วิธีอื่น ๆ คือจากบนลงล่างเริ่มจากตัวบ่งชี้ทำงานไปจนถึงใบไม้ ฉันจะแสดงทั้งสองวิธี
การก่อสร้างเริ่มต้นด้วยสิ่งที่อยู่ด้านขวา: สิ่งที่ส่งคืนซึ่งเป็นฟังก์ชั่นการทำความสะอาด เพื่อให้ผู้ประกาศชัดเจนฉันจะให้เลขพวกเขา:
D1(char);
แทรกพารามิเตอร์ char โดยตรงเนื่องจากไม่สำคัญ เพิ่มตัวชี้ไปยัง declarator โดยเปลี่ยนจากD1
*D2
โปรดทราบว่าเราต้องล้อมวงเล็บ*D2
ไว้ ที่สามารถเป็นที่รู้จักโดยมองถึงความสำคัญของและผู้ประกอบการฟังก์ชั่นการโทร*-operator
โดยไม่ต้องวงเล็บของเราคอมไพเลอร์จะอ่านว่ามันเป็น()
*(D2(char p))
แต่นั่นคงไม่ใช่การแทนที่ D1 ธรรมดา*D2
อีกต่อไปแน่นอน วงเล็บอนุญาตให้ใช้กับผู้ประกาศ ดังนั้นคุณไม่ได้ทำอะไรผิดถ้าคุณเพิ่มพวกเขามากเกินไปจริง ๆ
(*D2)(char);
ชนิดส่งคืนเสร็จสมบูรณ์! ทีนี้มาแทนที่D2
ฟังก์ชั่นการประกาศฟังก์ชั่นการ<parameters>
คืนซึ่งเป็นD3(<parameters>)
ที่เราอยู่ในขณะนี้
(*D3(<parameters>))(char)
โปรดทราบว่าไม่จำเป็นต้องใช้วงเล็บเนื่องจากเราต้องการ D3
เป็น function-declarator และไม่ใช่ตัวประกาศสำหรับตัวชี้ในเวลานี้ ยิ่งใหญ่สิ่งเดียวที่เหลืออยู่คือพารามิเตอร์ของมัน พารามิเตอร์จะทำเหมือนกับที่เราเคยทำชนิดการส่งคืนเพียงกับแทนที่ด้วยchar
void
ดังนั้นฉันจะคัดลอกมัน:
(*D3( (*ID1)(void)))(char)
ฉันถูกแทนที่D2
ด้วยID1
เนื่องจากเราเสร็จสิ้นด้วยพารามิเตอร์นั้น (มันเป็นตัวชี้ไปยังฟังก์ชั่น - ไม่จำเป็นต้องมีตัวประกาศอื่น) ID1
จะเป็นชื่อของพารามิเตอร์ ตอนนี้ฉันบอกข้างต้นที่ท้ายหนึ่งเพิ่มประเภทที่ประกาศทั้งหมดเหล่านั้นแก้ไข - คนที่ปรากฏที่ด้านซ้ายสุดของทุกการประกาศ สำหรับฟังก์ชั่นนั้นจะกลายเป็นประเภทส่งคืน สำหรับพอยน์เตอร์ที่ชี้ไปที่ประเภท ฯลฯ ... มันน่าสนใจเมื่อเขียนลงไปมันจะปรากฏขึ้นในลำดับที่ตรงกันข้ามที่อยู่ด้านขวาสุด :) อย่างไรก็ตามการแทนที่มันให้การประกาศที่สมบูรณ์ int
แน่นอนทั้งสองครั้ง
int (*ID0(int (*ID1)(void)))(char)
ฉันได้เรียกตัวระบุของฟังก์ชันID0
ในตัวอย่างนั้น
สิ่งนี้เริ่มต้นที่ตัวบ่งชี้ทางด้านซ้ายสุดในคำอธิบายของประเภทซึ่งมีผู้ประกาศว่าเมื่อเราเดินผ่านทางขวา เริ่มต้นด้วยฟังก์ชั่นรับ<
พารามิเตอร์>
กลับมา
ID0(<parameters>)
สิ่งต่อไปในรายละเอียด (หลังจาก "กลับ") เป็นตัวชี้ไปยัง มารวมกัน:
*ID0(<parameters>)
แล้วสิ่งต่อไปคือการถ่าย functon <
พารามิเตอร์>
กลับ พารามิเตอร์เป็นตัวอักษรธรรมดาดังนั้นเราจึงใส่มันทันทีอีกครั้งเพราะมันเป็นเรื่องเล็กน้อย
(*ID0(<parameters>))(char)
สังเกตเครื่องหมายวงเล็บที่เราเพิ่มเข้าไปเนื่องจากเราต้องการให้*
ผูกก่อนแล้วตาม(char)
ด้วย มิฉะนั้นก็จะอ่านฟังก์ชั่นการถ่าย<
พารามิเตอร์>
ฟังก์ชั่นที่กลับมา ... ไม่อนุญาตให้ใช้ฟังก์ชันที่ส่งคืนฟังก์ชัน
ตอนนี้เราก็ต้องใส่พารามิเตอร์<
>
ฉันจะแสดงเวอร์ชั่นสั้น ๆ ของการหลุดพ้นเนื่องจากฉันคิดว่าตอนนี้คุณมีความคิดแล้วว่าจะทำอย่างไร
pointer to: *ID1
... function taking void returning: (*ID1)(void)
เพิ่งint
ประกาศต่อหน้าผู้ประกาศเหมือนที่เราทำจากล่างขึ้นบนและเราเสร็จแล้ว
int (*ID0(int (*ID1)(void)))(char)
จากล่างขึ้นบนหรือบนลงล่างดีขึ้นหรือไม่ ฉันคุ้นเคยกับการ bottom-up แต่บางคนอาจรู้สึกสะดวกสบายเมื่อใช้จากบนลงล่าง ฉันคิดว่ามันเป็นเรื่องของรสนิยม อนึ่งหากคุณใช้ตัวดำเนินการทั้งหมดในการประกาศนั้นคุณจะได้รับ int:
int v = (*ID0(some_function_pointer))(some_char);
นั่นเป็นคุณสมบัติที่ดีของการประกาศใน C: การประกาศยืนยันว่าหากตัวดำเนินการเหล่านั้นถูกนำมาใช้ในการแสดงออกโดยใช้ตัวระบุมันจะให้ผลเป็นประเภททางด้านซ้ายมือ มันก็เป็นเช่นนั้นสำหรับอาร์เรย์ด้วย
หวังว่าคุณจะชอบการกวดวิชาเล็ก ๆ นี้! ตอนนี้เราสามารถเชื่อมโยงกับสิ่งนี้ได้เมื่อผู้คนสงสัยเกี่ยวกับไวยากรณ์ของฟังก์ชั่นการประกาศที่แปลก ฉันพยายามใส่ตัวซีให้น้อยที่สุดเท่าที่จะทำได้ รู้สึกอิสระที่จะแก้ไข / แก้ไขสิ่งต่าง ๆ ในนั้น
เป็นประโยชน์อย่างมากเมื่อคุณต้องการฟังก์ชั่นที่แตกต่างกันในเวลาที่ต่างกันหรือขั้นตอนการพัฒนาที่แตกต่างกัน ตัวอย่างเช่นฉันกำลังพัฒนาแอปพลิเคชันบนโฮสต์คอมพิวเตอร์ที่มีคอนโซล แต่ซอฟต์แวร์รุ่นสุดท้ายจะวางบน Avnet ZedBoard (ซึ่งมีพอร์ตสำหรับการแสดงผลและคอนโซล แต่พวกเขาไม่ต้องการ / ต้องการสำหรับ รุ่นสุดท้าย) ดังนั้นในระหว่างการพัฒนาฉันจะใช้printf
เพื่อดูสถานะและข้อความแสดงข้อผิดพลาด แต่เมื่อฉันทำเสร็จแล้วฉันไม่ต้องการพิมพ์อะไรเลย นี่คือสิ่งที่ฉันทำ:
// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION
// Define which version we want to use
#define DEBUG_VERSION // The current version
// #define RELEASE_VERSION // To be uncommented when finished debugging
#ifndef __VERSION_H_ /* prevent circular inclusions */
#define __VERSION_H_ /* by using protection macros */
void board_init();
void noprintf(const char *c, ...); // mimic the printf prototype
#endif
// Mimics the printf function prototype. This is what I'll actually
// use to print stuff to the screen
void (* zprintf)(const char*, ...);
// If debug version, use printf
#ifdef DEBUG_VERSION
#include <stdio.h>
#endif
// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
#define INVALID_VERSION
#endif
#endif
// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
#define INVALID_VERSION
#endif
#endif
#ifdef INVALID_VERSION
// Won't allow compilation without a valid version define
#error "Invalid version definition"
#endif
ในversion.c
ฉันจะกำหนด 2 ฟังก์ชั่นต้นแบบที่มีอยู่ในversion.h
#include "version.h"
/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return None
*
*****************************************************************************/
void board_init()
{
// Assign the print function to the correct function pointer
#ifdef DEBUG_VERSION
zprintf = &printf;
#else
// Defined below this function
zprintf = &noprintf;
#endif
}
/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
return;
}
แจ้งให้ทราบว่าตัวชี้ฟังก์ชันจะเป็นต้นแบบในการversion.h
เป็น
void (* zprintf)(const char *, ...);
เมื่อมีการอ้างอิงในแอปพลิเคชันมันจะเริ่มดำเนินการทุกที่ที่มีการชี้ซึ่งยังไม่ได้กำหนด
ในversion.c
แจ้งให้ทราบในboard_init()
ฟังก์ชั่นที่zprintf
ได้รับมอบหมายฟังก์ชั่นที่ไม่ซ้ำกัน (ซึ่งตรงกับฟังก์ชั่นลายเซ็น) ขึ้นอยู่กับรุ่นที่กำหนดไว้version.h
zprintf = &printf;
zprintf เรียกใช้ printf เพื่อการดีบัก
หรือ
zprintf = &noprint;
zprintf เพิ่งกลับมาและจะไม่เรียกใช้รหัสที่ไม่จำเป็น
ใช้รหัสจะมีลักษณะเช่นนี้:
#include "version.h"
#include <stdlib.h>
int main()
{
// Must run board_init(), which assigns the function
// pointer to an actual function
board_init();
void *ptr = malloc(100); // Allocate 100 bytes of memory
// malloc returns NULL if unable to allocate the memory.
if (ptr == NULL)
{
zprintf("Unable to allocate memory\n");
return 1;
}
// Other things to do...
return 0;
}
โค้ดด้านบนจะใช้printf
ถ้าอยู่ในโหมดดีบั๊กหรือไม่ทำอะไรเลยถ้าอยู่ในโหมดรีลีส ซึ่งง่ายกว่าการทำโครงการทั้งหมดและการใส่ความคิดเห็นหรือลบรหัส สิ่งที่ฉันต้องทำคือเปลี่ยนเวอร์ชั่นในversion.h
และโค้ดจะทำส่วนที่เหลือ!
ตัวชี้ฟังก์ชั่นมักจะถูกกำหนดโดยtypedef
และใช้เป็นค่าพารามิเตอร์และผลตอบแทน
คำตอบข้างต้นมีคำอธิบายแล้วฉันได้ยกตัวอย่าง:
#include <stdio.h>
#define NUM_A 1
#define NUM_B 2
// define a function pointer type
typedef int (*two_num_operation)(int, int);
// an actual standalone function
static int sum(int a, int b) {
return a + b;
}
// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
return (*funp)(a, b);
}
// use function pointer as return value,
static two_num_operation get_sum_fun() {
return ∑
}
// test - use function pointer as variable,
void test_pointer_as_variable() {
// create a pointer to function,
two_num_operation sum_p = ∑
// call function via pointer
printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}
// test - use function pointer as param,
void test_pointer_as_param() {
printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}
// test - use function pointer as return value,
void test_pointer_as_return_value() {
printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}
int main() {
test_pointer_as_variable();
test_pointer_as_param();
test_pointer_as_return_value();
return 0;
}
หนึ่งในการใช้งานที่ยิ่งใหญ่สำหรับตัวชี้ฟังก์ชั่นใน C คือการเรียกฟังก์ชั่นที่เลือกในเวลาทำงาน ตัวอย่างเช่นไลบรารีรันไทม์ C มีสองรูทีนqsort
และbsearch
ซึ่งนำตัวชี้ไปยังฟังก์ชันที่ถูกเรียกเพื่อเปรียบเทียบสองรายการที่เรียงลำดับ สิ่งนี้ช่วยให้คุณสามารถเรียงลำดับหรือค้นหาตามลำดับสิ่งใดก็ได้ตามเกณฑ์ที่คุณต้องการใช้
ตัวอย่างพื้นฐานมากหากมีฟังก์ชันหนึ่งที่เรียกว่าprint(int x, int y)
ซึ่งจำเป็นต้องเรียกใช้ฟังก์ชัน (อย่างใดอย่างหนึ่งadd()
หรือsub()
ซึ่งเป็นชนิดเดียวกัน) จากนั้นสิ่งที่เราจะทำเราจะเพิ่มอาร์กิวเมนต์ตัวชี้ฟังก์ชันหนึ่งprint()
ฟังก์ชันตามที่แสดงด้านล่าง :
#include <stdio.h>
int add()
{
return (100+10);
}
int sub()
{
return (100-10);
}
void print(int x, int y, int (*func)())
{
printf("value is: %d\n", (x+y+(*func)()));
}
int main()
{
int x=100, y=200;
print(x,y,add);
print(x,y,sub);
return 0;
}
ผลลัพธ์คือ:
ค่าคือ: 410
ค่าคือ: 390
การเริ่มต้นจากฟังก์ชั่นเริ่มต้นมีที่อยู่หน่วยความจำบางส่วนจากจุดที่พวกเขาเริ่มดำเนินการ ในภาษาแอสเซมบลีพวกเขาจะถูกเรียกว่าเป็น (เรียกว่า "ที่อยู่หน่วยความจำของฟังก์ชัน") ตอนนี้กลับมาที่ C ถ้าฟังก์ชั่นมีที่อยู่หน่วยความจำก็สามารถจัดการได้โดยพอยน์เตอร์ใน
1. ก่อนอื่นคุณต้องประกาศตัวชี้ไปยังฟังก์ชัน 2. ผ่านที่อยู่ของฟังก์ชันที่ต้องการ
**** หมายเหตุ -> ฟังก์ชั่นควรเป็นประเภทเดียวกัน ****
โปรแกรมอย่างง่ายนี้จะแสดงทุกสิ่ง
#include<stdio.h>
void (*print)() ;//Declare a Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
//The Functions should Be of Same Type
int main()
{
print=sayhello;//Addressof sayhello is assigned to print
print();//print Does A call To The Function
return 0;
}
void sayhello()
{
printf("\n Hello World");
}
หลังจากนั้นให้ดูว่าเครื่องเข้าใจพวกเขาได้อย่างไรคำสั่งเครื่องของโปรแกรมด้านบนในสถาปัตยกรรม 32 บิต
พื้นที่เครื่องหมายสีแดงแสดงวิธีการแลกเปลี่ยนที่อยู่และการจัดเก็บใน eax จากนั้นเป็นคำสั่งเรียกพวกเขาบน eax eax มีที่อยู่ที่ต้องการของฟังก์ชั่น
ตัวชี้ฟังก์ชั่นเป็นตัวแปรที่มีที่อยู่ของฟังก์ชั่น เนื่องจากมันเป็นตัวแปรพอยน์เตอร์ที่มีคุณสมบัติที่ถูก จำกัด คุณสามารถใช้มันได้เหมือนกับที่คุณทำกับตัวแปรพอยน์เตอร์อื่น ๆ ในโครงสร้างข้อมูล
ข้อยกเว้นเดียวที่ฉันนึกได้คือการใช้ตัวชี้ฟังก์ชั่นเป็นการชี้ไปยังสิ่งอื่นนอกเหนือจากค่าเดียว การทำเลขคณิตของตัวชี้โดยการเพิ่มหรือลดตัวชี้ฟังก์ชั่นหรือการเพิ่ม / ลบออฟเซ็ตให้กับตัวชี้ฟังก์ชั่นนั้นไม่ได้มีประโยชน์ใด ๆ เลยในขณะที่ตัวชี้ฟังก์ชั่นชี้ไปที่สิ่งเดียว
ขนาดของตัวแปรตัวชี้ฟังก์ชั่นจำนวนไบต์ที่ถูกครอบครองโดยตัวแปรอาจแตกต่างกันไปขึ้นอยู่กับสถาปัตยกรรมพื้นฐานเช่น x32 หรือ x64 หรืออะไรก็ตาม
การประกาศสำหรับตัวแปรตัวชี้ฟังก์ชั่นจำเป็นต้องระบุข้อมูลประเภทเดียวกันกับการประกาศฟังก์ชั่นเพื่อให้คอมไพเลอร์ C ทำการตรวจสอบประเภทที่ปกติ หากคุณไม่ได้ระบุรายการพารามิเตอร์ในการประกาศ / คำนิยามของตัวชี้ฟังก์ชั่นคอมไพเลอร์ C จะไม่สามารถตรวจสอบการใช้พารามิเตอร์ มีหลายกรณีที่การขาดการตรวจสอบนี้มีประโยชน์อย่างไรก็ตามโปรดจำไว้ว่าได้ลบตาข่ายนิรภัยออกแล้ว
ตัวอย่างบางส่วน:
int func (int a, char *pStr); // declares a function
int (*pFunc)(int a, char *pStr); // declares or defines a function pointer
int (*pFunc2) (); // declares or defines a function pointer, no parameter list specified.
int (*pFunc3) (void); // declares or defines a function pointer, no arguments.
คำประกาศสองข้อแรกนั้นค่อนข้างคล้ายกันในเรื่องนี้:
func
เป็นฟังก์ชั่นที่รับint
a char *
และa และคืนค่าint
pFunc
เป็นตัวชี้ฟังก์ชั่นที่ได้รับการกำหนดที่อยู่ของฟังก์ชั่นที่ใช้int
และchar *
และส่งกลับint
ดังนั้นจากข้างต้นเราจะมีสายแหล่งที่อยู่ของฟังก์ชั่นที่func()
ได้รับมอบหมายให้ตัวแปรชี้ฟังก์ชั่นในขณะที่pFunc
pFunc = func;
ขอให้สังเกตไวยากรณ์ที่ใช้กับการประกาศ / การกำหนดตัวชี้ฟังก์ชันซึ่งใช้วงเล็บในการเอาชนะกฎที่มีความสำคัญเหนือธรรมชาติ
int *pfunc(int a, char *pStr); // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr); // declares a function pointer that returns an int
ตัวอย่างการใช้งานที่แตกต่างกันหลายอย่าง
ตัวอย่างการใช้งานตัวชี้ฟังก์ชั่น:
int (*pFunc) (int a, char *pStr); // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr); // declare a pointer to a function pointer variable
struct { // declare a struct that contains a function pointer
int x22;
int (*pFunc)(int a, char *pStr);
} thing = {0, func}; // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr)); // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr)); // declare a function pointer that points to a function that has a function pointer as an argument
คุณสามารถใช้รายการพารามิเตอร์ความยาวผันแปรได้ในนิยามของตัวชี้ฟังก์ชัน
int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);
หรือคุณไม่สามารถระบุรายการพารามิเตอร์ได้เลย สิ่งนี้มีประโยชน์ แต่จะช่วยลดโอกาสที่คอมไพเลอร์ C ทำการตรวจสอบในรายการอาร์กิวเมนต์ที่ให้ไว้
int sum (); // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int sum2(void); // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);
สไตล์หล่อ C
คุณสามารถใช้สไตล์ C พร้อมกับพอยน์เตอร์ฟังก์ชั่น อย่างไรก็ตามโปรดทราบว่าคอมไพเลอร์ C อาจจะหละหลวมเกี่ยวกับการตรวจสอบหรือให้คำเตือนมากกว่าข้อผิดพลาด
int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum; // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum; // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum; // compiler error of bad cast generated, parenthesis are required.
เปรียบเทียบตัวชี้ฟังก์ชั่นกับความเท่าเทียมกัน
คุณสามารถตรวจสอบว่าตัวชี้ฟังก์ชันเท่ากับที่อยู่ฟังก์ชันเฉพาะโดยใช้if
คำสั่งแม้ว่าฉันไม่แน่ใจว่ามีประโยชน์อย่างไร ตัวดำเนินการเปรียบเทียบอื่น ๆ ดูเหมือนจะมีประโยชน์น้อยกว่า
static int func1(int a, int b) {
return a + b;
}
static int func2(int a, int b, char *c) {
return c[0] + a + b;
}
static int func3(int a, int b, char *x) {
return a + b;
}
static char *func4(int a, int b, char *c, int (*p)())
{
if (p == func1) {
p(a, b);
}
else if (p == func2) {
p(a, b, c); // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
} else if (p == func3) {
p(a, b, c);
}
return c;
}
Array of Function Pointers
และถ้าคุณต้องการให้อาร์เรย์ของฟังก์ชันพอยน์เตอร์แต่ละองค์ประกอบที่รายการอาร์กิวเมนต์มีความแตกต่างกันคุณสามารถกำหนดตัวชี้ฟังก์ชันด้วยรายการอาร์กิวเมนต์ที่ไม่ได้ระบุ (ไม่ใช่void
ซึ่งหมายความว่าไม่มีข้อโต้แย้ง แต่ไม่ได้ระบุ) บางอย่างเช่นต่อไปนี้ อาจเห็นคำเตือนจากคอมไพเลอร์ C นอกจากนี้ยังใช้งานได้กับพารามิเตอร์ตัวชี้ฟังก์ชันกับฟังก์ชัน:
int(*p[])() = { // an array of function pointers
func1, func2, func3
};
int(**pp)(); // a pointer to a function pointer
p[0](a, b);
p[1](a, b, 0);
p[2](a, b); // oops, left off the last argument but it compiles anyway.
func4(a, b, 0, func1);
func4(a, b, 0, func2); // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);
// iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
func4(a, b, 0, p[i]);
}
// iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
(*pp)(a, b, 0); // pointer to a function pointer so must dereference it.
func4(a, b, 0, *pp); // pointer to a function pointer so must dereference it.
}
สไตล์ C namespace
ใช้ Global struct
กับตัวชี้ฟังก์ชั่น
คุณสามารถใช้static
คำสำคัญเพื่อระบุฟังก์ชั่นที่มีชื่อเป็นขอบเขตไฟล์และจากนั้นกำหนดให้ตัวแปรทั่วโลกเป็นวิธีการให้สิ่งที่คล้ายกับการnamespace
ทำงานของ C ++
ในไฟล์ส่วนหัวกำหนดโครงสร้างที่จะเป็น namespace ของเราพร้อมกับตัวแปรทั่วโลกที่ใช้มัน
typedef struct {
int (*func1) (int a, int b); // pointer to function that returns an int
char *(*func2) (int a, int b, char *c); // pointer to function that returns a pointer
} FuncThings;
extern const FuncThings FuncThingsGlobal;
จากนั้นในไฟล์ต้นฉบับ C:
#include "header.h"
// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
return a + b;
}
static char *func2 (int a, int b, char *c)
{
c[0] = a % 100; c[1] = b % 50;
return c;
}
const FuncThings FuncThingsGlobal = {func1, func2};
สิ่งนี้จะถูกใช้โดยการระบุชื่อที่สมบูรณ์ของตัวแปรโกลบอลและชื่อสมาชิกเพื่อเข้าถึงฟังก์ชัน โมดิconst
ฟายเออร์นี้ใช้กับทั่วโลกเพื่อให้ไม่สามารถเปลี่ยนแปลงได้โดยไม่ได้ตั้งใจ
int abcd = FuncThingsGlobal.func1 (a, b);
ขอบเขตการใช้งานตัวชี้ฟังก์ชั่น
คอมโพเนนต์ไลบรารี DLL สามารถทำบางสิ่งที่คล้ายกับnamespace
วิธีการลักษณะ C ซึ่งมีการร้องขออินเทอร์เฟซไลบรารีเฉพาะจากวิธีการจากโรงงานในอินเทอร์เฟซไลบรารีซึ่งสนับสนุนการสร้างstruct
ตัวชี้ฟังก์ชันที่ประกอบด้วย .. อินเตอร์เฟสไลบรารีนี้โหลดรุ่น DLL ที่ร้องขอ struct ที่มีตัวชี้ฟังก์ชันที่จำเป็นจากนั้นส่งคืน struct ให้กับผู้เรียกที่ร้องขอเพื่อใช้งาน
typedef struct {
HMODULE hModule;
int (*Func1)();
int (*Func2)();
int(*Func3)(int a, int b);
} LibraryFuncStruct;
int LoadLibraryFunc LPCTSTR dllFileName, LibraryFuncStruct *pStruct)
{
int retStatus = 0; // default is an error detected
pStruct->hModule = LoadLibrary (dllFileName);
if (pStruct->hModule) {
pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
retStatus = 1;
}
return retStatus;
}
void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
if (pStruct->hModule) FreeLibrary (pStruct->hModule);
pStruct->hModule = 0;
}
และสิ่งนี้สามารถใช้เป็นใน:
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
// ....
myLib.Func1();
// ....
FreeLibraryFunc (&myLib);
วิธีการเดียวกันนี้สามารถใช้เพื่อกำหนดเลเยอร์ฮาร์ดแวร์ที่เป็นนามธรรมสำหรับโค้ดที่ใช้โมเดลเฉพาะของฮาร์ดแวร์พื้นฐาน ตัวชี้ฟังก์ชั่นจะเต็มไปด้วยฟังก์ชั่นเฉพาะของฮาร์ดแวร์โดยโรงงานเพื่อให้ฟังก์ชั่นเฉพาะของฮาร์ดแวร์ที่ใช้ฟังก์ชั่นที่ระบุในรูปแบบฮาร์ดแวร์นามธรรม สิ่งนี้สามารถใช้เพื่อจัดเตรียมเลเยอร์ฮาร์ดแวร์นามธรรมที่ใช้โดยซอฟต์แวร์ซึ่งเรียกใช้ฟังก์ชันจากโรงงานเพื่อรับฟังก์ชั่นอินเทอร์เฟซฮาร์ดแวร์เฉพาะจากนั้นใช้พอยน์เตอร์ฟังก์ชันที่จัดเตรียมไว้เพื่อดำเนินการสำหรับฮาร์ดแวร์พื้นฐานโดยไม่จำเป็นต้องทราบรายละเอียดการนำไปปฏิบัติ .
ตัวชี้ฟังก์ชั่นเพื่อสร้างผู้ได้รับมอบหมายตัวจัดการและการโทรกลับ
คุณสามารถใช้ตัวชี้ฟังก์ชั่นเป็นวิธีการมอบหมายงานหรือฟังก์ชั่นบางอย่าง ตัวอย่างแบบคลาสสิกใน C คือตัวชี้ฟังก์ชั่นการมอบหมายตัวแทนการเปรียบเทียบที่ใช้กับฟังก์ชันไลบรารีมาตรฐาน C qsort()
และbsearch()
เพื่อให้ลำดับการเรียงสำหรับการเรียงลำดับรายการหรือดำเนินการค้นหาแบบไบนารีในรายการที่เรียงลำดับ ตัวแทนฟังก์ชั่นการเปรียบเทียบระบุอัลกอริทึมการเปรียบเทียบที่ใช้ในการเรียงลำดับหรือการค้นหาแบบไบนารี
การใช้อื่นคล้ายกับการนำอัลกอริทึมไปใช้กับคอนเทนเนอร์ไลบรารีแม่แบบ C ++ มาตรฐาน
void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
unsigned char *pList = pArray;
unsigned char *pListEnd = pList + nItems * sizeItem;
for ( ; pList < pListEnd; pList += sizeItem) {
p (pList);
}
return pArray;
}
int pIncrement(int *pI) {
(*pI)++;
return 1;
}
void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
unsigned char *pList = pArray;
unsigned char *pListEnd = pList + nItems * sizeItem;
for (; pList < pListEnd; pList += sizeItem) {
p(pList, pResult);
}
return pArray;
}
int pSummation(int *pI, int *pSum) {
(*pSum) += *pI;
return 1;
}
// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;
ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);
อีกตัวอย่างหนึ่งคือด้วยซอร์สโค้ด GUI ซึ่งมีการลงทะเบียนตัวจัดการสำหรับเหตุการณ์เฉพาะโดยให้ตัวชี้ฟังก์ชั่นซึ่งเรียกจริงเมื่อเหตุการณ์เกิดขึ้น เฟรมเวิร์ก Microsoft MFC พร้อมข้อความแผนที่ใช้สิ่งที่คล้ายกันเพื่อจัดการกับข้อความ Windows ที่ส่งไปยังหน้าต่างหรือเธรด
ฟังก์ชั่นแบบอะซิงโครนัสที่ต้องการการติดต่อกลับคล้ายกับตัวจัดการเหตุการณ์ ผู้ใช้ฟังก์ชั่นอะซิงโครนัสเรียกใช้ฟังก์ชั่นอะซิงโครนัสเพื่อเริ่มต้นการดำเนินการบางอย่างและให้ตัวชี้ฟังก์ชันซึ่งฟังก์ชันอะซิงโครนัสจะเรียกใช้เมื่อการดำเนินการเสร็จสิ้น ในกรณีนี้เหตุการณ์คือฟังก์ชันอะซิงโครนัสที่ทำให้งานเสร็จสมบูรณ์