Một con trỏ hàm là một biến chứa địa chỉ của hàm. Vì nó là một biến con trỏ mặc dù với một số thuộc tính bị hạn chế, bạn có thể sử dụng nó giống như bất kỳ biến con trỏ nào khác trong cấu trúc dữ liệu.
Ngoại lệ duy nhất tôi có thể nghĩ đến là coi con trỏ hàm là trỏ đến một thứ khác không phải là một giá trị. Thực hiện số học con trỏ bằng cách tăng hoặc giảm con trỏ hàm hoặc thêm / bớt một phần bù cho con trỏ hàm không thực sự là tiện ích vì con trỏ hàm chỉ trỏ đến một điều duy nhất, điểm vào của hàm.
Kích thước của biến con trỏ hàm, số byte bị chiếm bởi biến, có thể thay đổi tùy thuộc vào kiến trúc cơ bản, ví dụ x32 hoặc x64 hoặc bất cứ điều gì.
Khai báo cho một biến con trỏ hàm cần chỉ định cùng loại thông tin như khai báo hàm để trình biên dịch C thực hiện các loại kiểm tra mà nó thường làm. Nếu bạn không chỉ định danh sách tham số trong khai báo / định nghĩa của con trỏ hàm, trình biên dịch C sẽ không thể kiểm tra việc sử dụng tham số. Có những trường hợp khi thiếu kiểm tra này có thể hữu ích tuy nhiên chỉ cần nhớ rằng một mạng lưới an toàn đã được gỡ bỏ.
Vài ví dụ:
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.
Hai khai báo đầu tiên có phần giống nhau ở chỗ:
func
là một hàm lấy một int
và một char *
và trả về mộtint
pFunc
là một con trỏ hàm được gán địa chỉ của hàm lấy một int
và a char *
và trả về mộtint
Vì vậy, từ trên chúng ta có thể có một dòng nguồn trong đó địa chỉ của hàm func()
được gán cho biến con trỏ hàm pFunc
như trong pFunc = func;
.
Lưu ý cú pháp được sử dụng với khai báo / định nghĩa con trỏ hàm trong đó dấu ngoặc đơn được sử dụng để vượt qua các quy tắc ưu tiên toán tử tự nhiên.
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
Một số ví dụ sử dụng khác nhau
Một số ví dụ về việc sử dụng con trỏ hàm:
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
Bạn có thể sử dụng danh sách tham số độ dài thay đổi trong định nghĩa của một con trỏ hàm.
int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);
Hoặc bạn không thể chỉ định một danh sách tham số. Điều này có thể hữu ích nhưng nó loại bỏ cơ hội cho trình biên dịch C thực hiện kiểm tra trên danh sách đối số được cung cấp.
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);
Phong cách C
Bạn có thể sử dụng phôi kiểu C với các con trỏ hàm. Tuy nhiên, hãy lưu ý rằng trình biên dịch C có thể lỏng lẻo về kiểm tra hoặc cung cấp các cảnh báo thay vì lỗi.
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.
So sánh con trỏ hàm với đẳng thức
Bạn có thể kiểm tra xem một con trỏ hàm có bằng một địa chỉ hàm cụ thể bằng cách sử dụng một if
câu lệnh mặc dù tôi không chắc nó sẽ hữu ích như thế nào. Các toán tử so sánh khác dường như thậm chí còn ít tiện ích hơn.
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;
}
Một mảng các con trỏ hàm
Và nếu bạn muốn có một mảng các con trỏ hàm, mỗi phần tử trong danh sách đối số có sự khác biệt thì bạn có thể định nghĩa một con trỏ hàm với danh sách đối số không xác định (không void
có nghĩa là không có đối số mà chỉ là không xác định) giống như sau có thể thấy cảnh báo từ trình biên dịch C. Điều này cũng hoạt động cho một tham số con trỏ hàm đến một chức năng:
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.
}
Kiểu C namespace
Sử dụng Toàn cầu struct
với Chức năng Con trỏ
Bạn có thể sử dụng static
từ khóa để chỉ định một hàm có tên là phạm vi tệp và sau đó gán nó cho biến toàn cục như một cách cung cấp một cái gì đó tương tự như namespace
chức năng của C ++.
Trong tệp tiêu đề, xác định một cấu trúc sẽ là không gian tên của chúng ta cùng với một biến toàn cục sử dụng nó.
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;
Sau đó, trong tệp nguồn 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};
Điều này sau đó sẽ được sử dụng bằng cách chỉ định tên đầy đủ của biến cấu trúc toàn cầu và tên thành viên để truy cập hàm. Công cụ const
sửa đổi được sử dụng trên toàn cầu để không thể thay đổi ngẫu nhiên.
int abcd = FuncThingsGlobal.func1 (a, b);
Các lĩnh vực ứng dụng của con trỏ hàm
Một thành phần thư viện DLL có thể làm điều gì đó tương tự như cách namespace
tiếp cận kiểu C trong đó giao diện thư viện cụ thể được yêu cầu từ phương thức xuất xưởng trong giao diện thư viện hỗ trợ tạo struct
con trỏ hàm chứa .. Giao diện thư viện này tải phiên bản DLL được yêu cầu, tạo một cấu trúc với các con trỏ hàm cần thiết, và sau đó trả về cấu trúc cho người gọi yêu cầu sử dụng.
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;
}
và điều này có thể được sử dụng như trong:
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
// ....
myLib.Func1();
// ....
FreeLibraryFunc (&myLib);
Cách tiếp cận tương tự có thể được sử dụng để xác định một lớp phần cứng trừu tượng cho mã sử dụng một mô hình cụ thể của phần cứng cơ bản. Các con trỏ hàm được điền bởi các chức năng cụ thể của phần cứng bởi một nhà máy để cung cấp chức năng cụ thể cho phần cứng thực hiện các chức năng được chỉ định trong mô hình phần cứng trừu tượng. Điều này có thể được sử dụng để cung cấp một lớp phần cứng trừu tượng được sử dụng bởi phần mềm gọi hàm chức năng của nhà máy để có được giao diện chức năng phần cứng cụ thể sau đó sử dụng các con trỏ hàm được cung cấp để thực hiện các hành động cho phần cứng cơ bản mà không cần biết chi tiết triển khai về mục tiêu cụ thể .
Chức năng Con trỏ để tạo Đại biểu, Trình xử lý và Gọi lại
Bạn có thể sử dụng các con trỏ hàm như một cách để ủy thác một số tác vụ hoặc chức năng. Ví dụ kinh điển trong C là con trỏ hàm ủy nhiệm so sánh được sử dụng với các hàm thư viện C chuẩn qsort()
và bsearch()
để cung cấp thứ tự đối chiếu để sắp xếp danh sách các mục hoặc thực hiện tìm kiếm nhị phân trên danh sách các mục được sắp xếp. Đại biểu hàm so sánh chỉ định thuật toán đối chiếu được sử dụng trong sắp xếp hoặc tìm kiếm nhị phân.
Một cách sử dụng khác tương tự như áp dụng thuật toán cho bộ chứa Thư viện Mẫu Tiêu chuẩn 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);
Một ví dụ khác là với mã nguồn GUI trong đó trình xử lý cho một sự kiện cụ thể được đăng ký bằng cách cung cấp một con trỏ hàm thực sự được gọi khi sự kiện xảy ra. Khung Microsoft MFC với bản đồ thông báo của nó sử dụng một cái gì đó tương tự để xử lý các thông báo Windows được gửi đến một cửa sổ hoặc luồng.
Các hàm không đồng bộ yêu cầu gọi lại tương tự như trình xử lý sự kiện. Người dùng hàm không đồng bộ gọi hàm không đồng bộ để bắt đầu một số hành động và cung cấp một con trỏ hàm mà hàm không đồng bộ sẽ gọi sau khi hành động hoàn tất. Trong trường hợp này, sự kiện là hàm không đồng bộ hoàn thành nhiệm vụ của nó.