SlideShare a Scribd company logo
1 of 13
Download to read offline
Bài viết về function pointer được trích dịch từ tài liệu này:
http://www.newty.de/fpt/index.html
1. 1 Giới thiệu
1. 1.1 Function Pointer là gì?
2. 1.2 Thay thế câu lệnh Switch như thế nào?
2. 2 Syntax của C và C++ function pointer
1. 2.1 Define một function pointer
2. 2.2 Calling Convention
3. 2.3 Gán một địa chỉ vào function pointer
4. 2.4 So sánh các function pointer
5. 2.5 Gọi một hàm sử dụng function pointer
6. 2.6 Truyền function pointer như là một tham số
7. 2.7 Trả về một function pointer
8. 2.8 Sử dụng một mảng các function pointer
3. 3 Implement Callback Functions in C and C++
1. 3.1 Khái niệm về callback function
2. 3.2 Implement a callback function in C
3. 3.3 Implement a Callback to a static C++ Member Function
4. 3.4 Implement a Callback to a non-static C++ Member Function
1 Giới thiệu
Function Pointer cung cấp một kỹ thuật lập trình cực kỳ thú vị, hiệu quả và “đầy màu
sắc”. Chúng ta có thể sử dụng nó để thay thế câu lệnh switch/if, xây dựng quá trình
late-binding hoặc implement hàm callback. Tiếc thay, có thể vì sự phức tạp của nó
mà nó được đề cập rất ít trong hầu hết sách và tài liệu. Nếu có thì nó chỉ được trình
bày một cách rất tóm tắt và sơ sài. Thực ra thì nó ít gây ra lỗi hơn so với pointer bình
thường bởi vì chúng ta không bao giờ phải allocate hoặc de-allocate bộ nhớ cả. Tất
cả việc chúng ta cần làm là hiểu nó làm gì và học cú pháp của nó. Nhưng hãy luôn
tâm niệm rằng: hãy tự hỏi bạn có thực sự cần đến function pointer hay không? Rất
tuyệt để thể hiện cách thức late-binding, thế nhưng sử dụng cấu trúc hiện tại của
C++ làm cho đoạn mã trở nên dễ đọc và rõ ràng hơn. Một khía cạnh khác của late-
binding là runtime: nếu bạn gọi một virtual function, chương trình sẽ xác định hàm
nào được gọi. Nó làm điều đó bằng cách sử dụng V-Table mà chứa tất cả những hàm
có thể gọi. Điều đó có vẻ hơi lãng phí mỗi lần gọi, và có thể bạn sẽ tiết kiệm một
chút nếu sử dụng function pointer thay vì virtual function. Cũng có thể không …
1.1 Function Pointer là gì?
Function pointer là một pointer mà nó chỉ đến địa chỉ của một hàm. Bạn phải luôn
giữ trong đầu rằng một chương trình chạy sẽ chiếm một không gian bộ nhớ xác định
trong bộ nhớ chính. Cả đoạn chương trình thực thi đã được dịch từ mã mà bạn viết
và các biến sử dụng đều được đưa vào trong không gian bộ nhớ này. Vì vậy một
function trong chương trình của bạn không có gì khác hơn là một địa chỉ trong bộ
nhớ.
1.2 Thay thế câu lệnh Switch như thế nào?
Khi chúng ta muốn gọi một hàm DoIt() ở một label xác định trong chương trình,
chúng ta phải để lời gọi tới hàm DoIt() tại label đó. Sau đó biên dịch và mỗi khi
chương trình chạy tới label đó thì hàm DoIt() sẽ được gọi. Mọi thứ đều ok, nhưng sẽ
làm gì nếu giả sử chúng ta không biết tại thời điểm build-time (thời gian dịch) hàm
nào sẽ được gọi? Nghĩa là chỉ đến lúc chạy ta mới biết ở label đó thì nên chạy DoIt()
hay một hàm nào khác. Đó chính là lúc chúng ta muốn sử dụng đến callback-function
hoặc là sử dụng kỹ thuật lấy ra từ một “pool” chứa các possible function. Tuy nhiên
thì chúng ta có thể giải quyết vấn đề này bằng cách sử dụng lệnh switch, và lựa chọn
lời gọi đến hàm thích hợp ở những nhánh khác nhau tùy theo giá trị biểu thức của
switch. Nhưng vẫn có một cách khác là sử dụng function pointer. Trong ví dụ sau đây
chúng ta thực hiện nhiệm vụ của bốn toán tử toán học cơ bản (+, -, *, /). Cách đầu
tiên sử dụng switch và cách thứ hai sử dụng function pointer.
//-----------------------------------------------------------------------------
-
// 1.2 Introductory Example or How to Replace a Switch-Statement
// Task: Perform one of the four basic arithmetic operations specified by the
// characters '+', '-', '*' or '/'.
// The four arithmetic operations ... one of these functions is selected
// at runtime with a swicth or a function pointer
float Plus (float a, float b) {
return a+b;
}
float Minus (float a, float b) {
return a-b;
}
float Multiply(float a, float b) {
return a*b;
}
float Divide (float a, float b) {
return a/b;
}
// Solution with a switch-statement - <opCode> specifies which operation to
execute
void Switch(float a, float b, char opCode) {
float result;
// execute operation
switch(opCode) {
case '+' :
result = Plus (a, b);
break;
case '-' :
result = Minus (a, b);
break;
case '*' :
result = Multiply (a, b);
break;
case '/' :
result = Divide (a, b);
break;
}
cout << "Switch: 2+5=" << result << endl; // display result
}
// Solution with a function pointer - <pt2Func> is a function pointer and
points to
// a function which takes two floats and returns a float. The function pointer
// "specifies" which operation shall be executed.
void Switch_With_Function_Pointer(float a, float b, float (*pt2Func)(float,
float)) {
float result = pt2Func(a, b); // call using function pointer
cout << "Switch replaced by function pointer: 2-5="; // display result
cout << result << endl;
}
// Execute example code
void Replace_A_Switch() {
cout << endl << "Executing function 'Replace_A_Switch'" << endl;
Switch(2, 5, /* '+' specifies function 'Plus' to be executed */ '+');
Switch_With_Function_Pointer(2, 5, /* pointer to function 'Minus' */
&Minus);
}
Chú ý: Một function pointer luôn trỏ đến một function đặc biệt nên tất cả những
function mà chúng ta muốn sử dụng với cùng một function pointer thì phải có cùng
tham số và giá trị trả về. Nói một cách khác là cùng prototype.
2SyntaxcủaC và C++ function pointer
Dựa vào cú pháp thì có hai loại function pointer khác nhau: một là những function
pointer trỏ đến C function hoặc static C++ member function, một là những function
pointer tới non-static C++ member function. Sự khác biệt cơ bản là tất cả pointer
đến non-static member function cần một tham số ẩn: con trỏ this tới instance của
class. Vậy chỉ cần nhớ rằng có hai loại function pointer không tương thích với nhau.
2.1 Define một function pointer
Vì function pointer không khác gì hơn một biến nên nó phải được define giống như
thông thương. Ví dụ dưới đây chúng ta khai báo các function pointer tên là
pt2Function, pt2Member và pt2ConstMember. Chúng trở đến function và lấy một
biến float và hai biến char và trả về một số int. Ở ví dụ C++ chúng ta giả sử rằng
function mà function pointer trỏ đến là non-static member function của TMyClass.
// 2.1 define a function pointer and initialize to NULL
int (*pt2Function)(float, char, char) = NULL; // C
int (TMyClass::*pt2Member)(float, char, char) = NULL; // C++
int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL; // C++
2.2 Calling Convention
Thông thường chúng ta không phải nghĩ về calling convention của một function.
Trình biên dịch giả định rằng cdecl là convention mặc định nếu chúng ta không sử
dụng một convention khác. Nếu bạn muốn tìm hiểu kỹ hơn hãy tìm đọc. Calling
convention nói cho trình biên dịch biết cách truyền tham số và cách tạo ra một
function. Ví dụ về những calling convention khác là stdcall, pascal, fastcall. Nếu
function và function pointer khác calling convention thì chúng cũng không tương
thích với nhau và không thể thực hiện phép gán function pointer vào địa chỉ của
function kia. Đối với trình biên dịch của Borland và Microsoft thì cần khai báo calling
convention ở giữa kiểu trả về và tên hàm hay tên function pointer. Đối với GNU GCC
thì sử dụng từ khóa __attribute__: viết khai báo hàm theo sau bởi từ khóa
__attribute__ và sau đó là trạng thái của calling convention ở trong1
.
// 2.2 define the calling convention
void __cdecl DoIt(float a, char b, char c); // Borland and Microsoft
void DoIt(float a, char b, char c) __attribute__((cdecl)); // GNU GCC
2.3 Gán một địa chỉ vào function pointer
Rất dễ dàng để gán một địa chỉ của một function vào function pointer. Đơn giản chỉ
cần lấy tên của function hoặc member function thích hợp. Mặc dù hầu hết các
compiler support việc đó nhưng tốt hơn hết là chúng ta sử dụng toán tử địa chỉ & và
đặt trước các function name để viết những đoạn mã portable. Chúng ta cũng phải sử
dụng tên đầy đủ của member function bao gồm tên lớp và toán tử scope (::). Chúng
ta cũng phải đảm bảo rằng chúng ta được quyền truy nhập vào function ở bên trong
scope đó.
// 2.3 assign an address to the function pointer
// Note: Although you may ommit the address operator on most compilers
// you should always use the correct way in order to write portable code.
// C
int DoIt (float a, char b, char c) {
printf("DoItn");
return a+b+c;
}
int DoMore(float a, char b, char c)const {
printf("DoMoren");
return a-b+c;
}
pt2Function = DoIt; // short form
pt2Function = &DoMore; // correct assignment using address operator
// C++
class TMyClass {
public:
int DoIt(float a, char b, char c) {
cout << "TMyClass::DoIt"<< endl;
return a+b+c;
};
int DoMore(float a, char b, char c) const {
cout << "TMyClass::DoMore" << endl;
return a-b+c;
};
/* more of TMyClass */
};
pt2ConstMember = &TMyClass::DoMore; // correct assignment using address
operator
pt2Member = &TMyClass::DoIt; // note: <pt2Member> may also legally point to
&DoMore
2.4 So sánh các function pointer
Chúng ta có thể sử dụng các toán tử so sánh (==, !=) như bình thường. Trong ví dụ
dưới đây nó được kiểm tra xem pt2Function và pt2Member có thực sự chứa địa chỉ
của hàm DoIt() và TMyClass::DoMore()
// 2.4 comparing function pointers
// C
if(pt2Function >0) { // check if initialized
if(pt2Function == &DoIt)
printf("Pointer points to DoItn");
} else
printf("Pointer not initialized!!n");
// C++
if(pt2ConstMember == &TMyClass::DoMore)
cout << "Pointer points to TMyClass::DoMore" << endl;
2.5 Gọi một hàm sử dụng function pointer
Trong C, chúng ta gọi một hàm sử dụng function pointer bằng cách explicitly
dereferencing nó bằng toán tử *. Một lựa chọn khác là sử dụng function pointer thay
vì function name. Trong C++ hai toán tử .* và ->* được sử dụng cùng với instance
của một class để gọi một (non-static) member function. Nếu lời gọi diễn ra bên trong
một member function khác, chúng ta có thể sử dụng con trỏ this.
// 2.5 calling a function using a function pointer
int result1 = pt2Function (12, 'a', 'b'); // C short way
int result2 = (*pt2Function) (12, 'a', 'b'); // C
TMyClass instance1;
int result3 = (instance1.*pt2Member)(12, 'a', 'b'); // C++
int result4 = (*this.*pt2Member)(12, 'a', 'b'); // C++ if this-pointer can be
used
TMyClass* instance2 = new TMyClass;
int result4 = (instance2->*pt2Member)(12, 'a', 'b'); // C++, instance2 is a
pointer
delete instance2;
2.6 Truyền function pointer như là một tham số
Chúng ta có thể truyền function pointer như một tham số của một function được gọi
khác. Điều đó rất cần thiết nếu chúng ta muốn truyền một con trỏ tới một callback
function. Đoạn mã dưới đây chỉ cách truyền một pointer tới một function mà trả về
một số nguyên và lấy một số float và 2 char làm tham số:
//-----------------------------------------------------------------------------
-------
// 2.6 How to Pass a Function Pointer
// <pt2Func> is a pointer to a function which returns an int and takes a float
and two char
void PassPtr(int (*pt2Func)(float, char, char)) {
int result = (*pt2Func)(12, 'a', 'b'); // call using function pointer
cout << result << endl;
}
// execute example code - 'DoIt' is a suitable function like defined above in
2.1-4
void Pass_A_Function_Pointer() {
cout << endl << "Executing 'Pass_A_Function_Pointer'" << endl;
PassPtr(&DoIt);
}
2.7 Trả về một function pointer
Hơi mẹo một chút nhưng một function pointer có thể là giá trị trả về của một
function. Trong ví dụ sau đây có hai giải pháp cho việc trả về một function pointer.
Nếu muốn trả về một pointer vào một member function, chúng ta phải thay đổi
definitions và declarations của tất cả các function pointer.
//-----------------------------------------------------------------------------
-------
// 2.7 How to Return a Function Pointer
// 'Plus' and 'Minus' are defined above. They return a float and take two float
// Direct solution: Function takes a char and returns a pointer to a
// function which is taking two floats and returns a float. <opCode>
// specifies which function to return
float (*GetPtr1(const char opCode))(float, float) {
if(opCode == '+')
return &Plus;
else
return &Minus;
} // default if invalid operator was passed
// Solution using a typedef: Define a pointer to a function which is taking
// two floats and returns a float
typedef float(*pt2Func)(float, float);
// Function takes a char and returns a function pointer which is defined
// with the typedef above. <opCode> specifies which function to return
pt2Func GetPtr2(const char opCode) {
if(opCode == '+')
return &Plus;
else
return &Minus; // default if invalid operator was passed
}
// Execute example code
void Return_A_Function_Pointer() {
cout << endl << "Executing 'Return_A_Function_Pointer'" << endl;
// define a function pointer and initialize it to NULL
float (*pt2Function)(float, float) = NULL;
pt2Function=GetPtr1('+'); // get function pointer from function 'GetPtr1'
cout << (*pt2Function)(2, 4) << endl; // call function using the pointer
pt2Function=GetPtr2('-'); // get function pointer from function 'GetPtr2'
cout << (*pt2Function)(2, 4) << endl; // call function using the pointer
}
2.8 Sử dụng một mảng các function pointer
Sử dụng một mảng các function pointer khá thú vị. Nó cho phép khả năng lựa chọn
một function sử dụng chỉ số (index). Cú pháp thì khá phức tạp và thường xuyên dẫn
đến sự nhầm lẫn. Đoạn mã dưới đây bạn sẽ tìm thấy hai cách sử dụng một mảng các
function pointer trong C và C++. Cách thứ nhất sử dụng typedef và cách thứ hai sử
dụng trức tiếp cách khai bao mảng. Nó tùy thuộc vào bạn thích cách nào hơn.
//-----------------------------------------------------------------------------
-------
// 2.8 How to Use Arrays of Function Pointers
// C --------------------------------------------------------------------------
-------
// type-definition: 'pt2Function' now can be used as type
typedef int (*pt2Function)(float, char, char);
// illustrate how to work with an array of function pointers
void Array_Of_Function_Pointers() {
printf("nExecuting 'Array_Of_Function_Pointers'n");
// define arrays and ini each element to NULL, <funcArr1> and <funcArr2>
are arrays
// with 10 pointers to functions which return an int and take a float and
two char
// first way using the typedef
pt2Function funcArr1[10] = {
NULL
};
// 2nd way directly defining the array
int (*funcArr2[10])(float, char, char) = {
NULL
};
// assign the function's address - 'DoIt' and 'DoMore' are suitable
functions
// like defined above in 2.1-4
funcArr1[0] = funcArr2[1] = &DoIt;
funcArr1[1] = funcArr2[0] = &DoMore;
/* more assignments */
// calling a function using an index to address the function pointer
printf("%dn", funcArr1[1](12, 'a', 'b')); // short form
printf("%dn", (*funcArr1[0])(12, 'a', 'b')); // "correct" way of calling
printf("%dn", (*funcArr2[1])(56, 'a', 'b'));
printf("%dn", (*funcArr2[0])(34, 'a', 'b'));
}
// C++-------------------------------------------------------------------------
------
// type-definition: 'pt2Member' now can be used as type
typedef int (TMyClass::*pt2Member)(float, char, char);
// illustrate how to work with an array of member function pointers
void Array_Of_Member_Function_Pointers() {
cout << endl << "Executing 'Array_Of_Member_Function_Pointers'" << endl;
// define arrays and ini each element to NULL, <funcArr1> and <funcArr2>
are
// arrays with 10 pointers to member functions which return an int and take
// a float and two char
// first way using the typedef
pt2Member funcArr1[10] = {
NULL
};
// 2nd way of directly defining the array
int (TMyClass::*funcArr2[10])(float, char, char) = {
NULL
};
// assign the function's address - 'DoIt' and 'DoMore' are suitable member
// functions of class TMyClass like defined above in 2.1-4
funcArr1[0] = funcArr2[1] = &TMyClass::DoIt;
funcArr1[1] = funcArr2[0] = &TMyClass::DoMore;
/* more assignments */
// calling a function using an index to address the member function pointer
// note: an instance of TMyClass is needed to call the member functions
TMyClass instance;
cout << (instance.*funcArr1[1])(12, 'a', 'b') << endl;
cout << (instance.*funcArr1[0])(12, 'a', 'b') << endl;
cout << (instance.*funcArr2[1])(34, 'a', 'b') << endl;
cout << (instance.*funcArr2[0])(89, 'a', 'b') << endl;
}
3Implement Callback Functions in C and C++
3.1 Khái niệm về callback function
Function pointer cung cấp khái niệm về callback function. Trong bài viết này sẽ giới
thiệu về callback function thông qua một hàm về giải thuật sắp xếp nổi tiếng là
qsort. Hàm này sẽ sắp xếp các phần tử theo một tiêu chuẩn sắp xếp mà user định
nghĩa. Kích thước của một item và số item cần sắp xếp sẽ được truyền vào hàm này.
Câu hỏi đặt ra là: làm thế nào để hàm qsort có thể sắp xếp các phần tử mà không có
một thông tin gì về kiểu của phần tử sắp xếp? Câu trả lời rất đơn giản: hàm qsort sẽ
nhận một function pointer tới một hàm so sánh như một tham số. Hàm so sánh sẽ
nhận hai tham số kiểu con trỏ void tới hai phần tử và sẽ đánh giá thứ tự của hai
phần tử đó và trả về một số int. Do đó mỗi lần giải thuật sắp xếp cần đánh giá ví trí
của hai phần tử thì đơn giản chỉ là gọi tới hàm so sánh thông qua function pointer.
3.2 Implement a callback function in C
Hãy tham khảo khai báo của hàm qsort dưới đây:
void qsort(void* field, size_t nElements, size_t sizeOfAnElement,
int(_USERENTRY *cmpFunc)(const void *, const void*));
field trỏ tới phần tử đầu tiên của mảng cần sắp xếp, nElements là số phần tử cần sắp
xếp, sizeOfAnElement là kích thước của một phần tử tính theo byte, và cmpFunc là
function pointer tới hàm so sánh. Hàm so sánh này lấy hai phần tử const void* và trả
về một số nguyên.
void qsort( ... , int(_USERENTRY *cmpFunc)(const void*, const void*))
{
/* sort algorithm - note: item1 and item2 are void-pointers */
int bigger=cmpFunc(item1, item2); // make callback
/* use the result */
}
Ví dụ mẫu sử dụng qsort
//-----------------------------------------------------------------------------
------------
// 3.3 How to make a callback in C by the means of the sort function qsort
#include <stdlib.h> // due to: qsort
#include <time.h> // randomize
#include <stdio.h> // printf
// comparison-function for the sort-algorithm
// two items are taken by void-pointer, converted and compared
int CmpFunc(const void* _a, const void* _b) {
// you've got to explicitly cast to the correct type
const float* a = (const float*) _a;
const float* b = (const float*) _b;
if(*a > *b)
return 1; // first item is bigger than the second one -> return 1
else
if(*a == *b)
return 0; // equality -> return 0
else
return -1; // second item is bigger than the first one -> return -1
}
// example for the use of qsort()
void QSortExample() {
float field[100];
::randomize(); // initialize random-number-generator
for(int c=0;c<100;c++) // randomize all elements of the field
field[c]=random(99);
// sort using qsort()
qsort((void*) field, /*number of items*/ 100, /*size of an item*/
sizeof(field[0]),
/*comparison-function*/ CmpFunc);
// display first ten elements of the sorted field
printf("The first ten elements of the sorted field are ...n");
for(int c=0;c<10;c++)
printf("element #%d contains %.0fn", c+1, field[c]);
printf("n");
}
3.3 Implement a Callback to a static C++ Member Function
Implement callback tới static C++ member function cũng giống như implement
callback tới hàm C. Static member function không cần một object để thực thi vì vậy
nó phải có cùng một prototype như là hàm C với cùng một calling convention, tham
số và giá trị trả về.
3.4 Implement a Callback to a non-static C++ Member Function
Function pointer tới non-static member thì khác với là một con trỏ hàm C vì nó cần
phải truyền một con trỏ this của object. Nếu bạn chỉ muốn gọi hàm callback tới một
member function của một class cụ thể thì chỉ cần chuyển đoạn mã từ một function
pointer bình thường tới con trỏ tới member function. Nhưng sẽ làm thế nào nếu
muốn gọi hàm callback tới một non-static member của một class chưa rõ nào đó (có
thể ở các class khác nhau). Nó khó hơn một chút. Chúng ta cần phải viết một static
member function như là một wrapper. Static member function thì được coi như là
một function bình thường. Sau đó thì chúng ta sẽ ép kiểu con trỏ tới object mà chúng
ta muốn nó thực hiện member function, thành void* và truyền nó tới wrapper như là
một tham số phụ thêm hoặc là thông qua một biến toàn cục. Tất nhiên chúng ta
cũng truyền các tham số gọi tới member function. Wrapper sẽ ép kiểu con trỏ void
thành con trỏ tới object của class tương ứng của nó và gọi member function (kỹ
thuật này giống với kỹ thuật chúng ta xây dựng class Thread). Dưới đây là hai ví dụ:
Ví dụ A: Con trỏ tới object được truyền như là một tham số phụ thêm: Hàm DoItA()
thực hiện với object của class TClassA để gọi hàm callback. Vì vậy con trỏ tới object
của class TClassA và con trỏ tới static wrapper function
TClassA::Wrapper_To_Call_Display được truyền tới DoItA(). Hàm wrapper này là
callback function. Chúng ta có thể viết những class khác giống như TClassA và sử
dụng chung với DoItA miễn là những class này cũng cung cấp các function cần thiết.
Chú ý là giải pháp này có thể hữu dụng nếu chúng ta tự thiết kế giao diện của hàm
callback. Nó tốt hơn rất nhiều so với giải pháp thứ hai sử dụng biến toàn cục.
//-----------------------------------------------------------------------------
------------
// 3.5 Example A: Callback to member function using an additional argument
// Task: The function 'DoItA' makes something which implies a callback to
// the member function 'Display'. Therefore the wrapper function
// 'Wrapper_To_Call_Display is used.
#include <iostream.h> // due to: cout
class TClassA {
public:
void Display(const char* text) {
cout << text << endl;
};
static void Wrapper_To_Call_Display(void* pt2Object, char* text);
/* more of TClassA */
};
// static wrapper function to be able to callback the member function Display()
void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string) {
// explicitly cast to a pointer to TClassA
TClassA* mySelf = (TClassA*) pt2Object;
// call member
mySelf->Display(string);
}
// function does something which implies a callback
// note: of course this function can also be a member function
void DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text)) {
/* do something */
pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)"); //
make callback
}
// execute example code
void Callback_Using_Argument() {
// 1. instantiate object of TClassA
TClassA objA;
// 2. call 'DoItA' for <objA>
DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display);
}
Ví dụ B: Con trỏ tới object được lưu trữ trong một biến toàn cục. Hàm DoItB sẽ thực
hiện với đối tượng của class B để gọi hàm callback. Một con trỏ tới static wrapper
function TClassB::Wrapper_To_Call_Display được truyền vào DoItB. Hàm wrapper
này là callback function. Wrapper sử dụng biến toàn cục void* pt2Object và phép ép
kiểu thành một đối tượng của TClassB. Phải luôn chú ý khởi tạo biến toàn cục để chỉ
đến đối tượng của class chính xác. Chúng ta có thể viết những class khác như
TClassB và sử dụng chúng với DoItB miễn là những class đó cung cấp các function
cần thiết. Cách này không phải là một giải pháp tốt bởi vì sử dụng biến toàn cục rất
nguy hiểm và có thể gây ra những lỗi nghiêm trọng.
//-----------------------------------------------------------------------------
-
// 3.5 Example B: Callback to member function using a global variable
// Task: The function 'DoItB' makes something which implies a callback to
// the member function 'Display'. Therefore the wrapper function
// 'Wrapper_To_Call_Display is used.
#include <iostream.h> // due to: cout
void* pt2Object; // global variable which points to an arbitrary object
class TClassB {
public:
void Display(const char* text) {
cout << text << endl;
};
static void Wrapper_To_Call_Display(char* text);
/* more of TClassB */
};
// static wrapper function to be able to callback the member function Display()
void TClassB::Wrapper_To_Call_Display(char* string) {
// explicitly cast global variable <pt2Object> to a pointer to TClassB
// warning: <pt2Object> MUST point to an appropriate object!
TClassB* mySelf = (TClassB*) pt2Object;
// call member
mySelf->Display(string);
}
// function does something which implies a callback
// note: of course this function can also be a member function
void DoItB(void (*pt2Function)(char* text)) {
/* do something */
pt2Function("hi, i'm calling back using a global ;-)"); // make callback
}
// execute example code
void Callback_Using_Global() {
// 1. instantiate object of TClassB
TClassB objB;
// 2. assign global variable which is used in the static wrapper function
// important: never forget to do this!!
pt2Object = (void*) &objB;
// 3. call 'DoItB' for <objB>
DoItB(TClassB::Wrapper_To_Call_Display);
}
1. [↩]
5 Comments »
 kiennguyen said:
Sao lại có fpt ở đây nhỉ
- 26 November 2007 at 5:40 pm
 tuanpv5@yahoo.com said:
Thông hiểu được cái này thì sẽ hiểu ngay cơ chế làm việc của thư viện liên kết động (.dll trên Windows và
.so trên Linux) nhỉ. Bài viết quá hay ! Một tràng pháo tay cho bạn Hoàng nào .
- 26 April 2008 at 3:43 pm
 thachdaigia47 said:
thank you
- 7 January 2009 at 3:06 pm
 van viet said:
thanks tat ca!
dien dan nay tuyet wa
- 20 October 2009 at 11:17 pm
 LongVNIT » Function Pointer said:
[...] Thông thường chúng ta không phải nghĩ về calling convention của một function. Trình biên dịch giả định
rằng cdecl là convention mặc định nếu chúng ta không sử dụng một convention khác. Nếu bạn muốn tìm
hiểu kỹ hơn hãy tìm đọc. Calling convention nói cho trình biên dịch biết cách truyền tham số và cách tạo ra
một function. Ví dụ về những calling convention khác là stdcall, pascal, fastcall. Nếu function và function
pointer khác calling convention thì chúng cũng không tương thích với nhau và không thể thực hiện phép
gán function pointer vào địa chỉ của function kia. Đối với trình biên dịch của Borland và Microsoft thì cần
khai báo calling convention ở giữa kiểu trả về và tên hàm hay tên function pointer. Đối với GNU GCC thì sử
dụng từ khóa __attribute__: viết khai báo hàm theo sau bởi từ khóa __attribute__ và sau đó là trạng thái
của calling convention ở trong1. [...]

More Related Content

What's hot

Tai lieu lap trinh c cho 8051
Tai lieu lap trinh c cho 8051Tai lieu lap trinh c cho 8051
Tai lieu lap trinh c cho 8051engineertrongbk
 
Thực hành lập trình led đơn codientu.info--
Thực hành lập trình led đơn   codientu.info--Thực hành lập trình led đơn   codientu.info--
Thực hành lập trình led đơn codientu.info--trungnb22
 
3 Function
3 Function3 Function
3 FunctionCuong
 
Một số vấn đề thường gặp trong lập trình - Đăng Bình Phương (ĐH KHTN)
Một số vấn đề thường gặp trong lập trình - Đăng Bình Phương (ĐH KHTN)Một số vấn đề thường gặp trong lập trình - Đăng Bình Phương (ĐH KHTN)
Một số vấn đề thường gặp trong lập trình - Đăng Bình Phương (ĐH KHTN)Thanh Minh Hoang
 
Giáo trình c++ full tiếng việt
Giáo trình c++ full tiếng việtGiáo trình c++ full tiếng việt
Giáo trình c++ full tiếng việtMôi Trường Việt
 
Sổ tay thư viện hàm ngôn ngữ C
Sổ tay thư viện hàm ngôn ngữ CSổ tay thư viện hàm ngôn ngữ C
Sổ tay thư viện hàm ngôn ngữ Cvncoding
 
Lap trinh huong doi tuong voi c++ smith.n studio
Lap trinh huong doi tuong voi c++   smith.n studioLap trinh huong doi tuong voi c++   smith.n studio
Lap trinh huong doi tuong voi c++ smith.n studiona
 
Phần 6: Hàm
Phần 6: HàmPhần 6: Hàm
Phần 6: HàmHuy Rùa
 
Các cấu trúc lệnh trong C
Các cấu trúc lệnh trong CCác cấu trúc lệnh trong C
Các cấu trúc lệnh trong Cpnanhvn
 
LAP TRINH C - SESSION 2
LAP TRINH C - SESSION 2LAP TRINH C - SESSION 2
LAP TRINH C - SESSION 2pnanhvn
 
Ngon ngu c theo chuan ansi
Ngon ngu c theo chuan ansiNgon ngu c theo chuan ansi
Ngon ngu c theo chuan ansiHuynh MVT
 

What's hot (16)

Chuong 01
Chuong 01Chuong 01
Chuong 01
 
Tai lieu lap trinh c cho 8051
Tai lieu lap trinh c cho 8051Tai lieu lap trinh c cho 8051
Tai lieu lap trinh c cho 8051
 
Thực hành lập trình led đơn codientu.info--
Thực hành lập trình led đơn   codientu.info--Thực hành lập trình led đơn   codientu.info--
Thực hành lập trình led đơn codientu.info--
 
3 Function
3 Function3 Function
3 Function
 
Một số vấn đề thường gặp trong lập trình - Đăng Bình Phương (ĐH KHTN)
Một số vấn đề thường gặp trong lập trình - Đăng Bình Phương (ĐH KHTN)Một số vấn đề thường gặp trong lập trình - Đăng Bình Phương (ĐH KHTN)
Một số vấn đề thường gặp trong lập trình - Đăng Bình Phương (ĐH KHTN)
 
Giáo trình c++ full tiếng việt
Giáo trình c++ full tiếng việtGiáo trình c++ full tiếng việt
Giáo trình c++ full tiếng việt
 
Chuong 04 ham
Chuong 04 hamChuong 04 ham
Chuong 04 ham
 
Sổ tay thư viện hàm ngôn ngữ C
Sổ tay thư viện hàm ngôn ngữ CSổ tay thư viện hàm ngôn ngữ C
Sổ tay thư viện hàm ngôn ngữ C
 
Lap trinh huong doi tuong voi c++ smith.n studio
Lap trinh huong doi tuong voi c++   smith.n studioLap trinh huong doi tuong voi c++   smith.n studio
Lap trinh huong doi tuong voi c++ smith.n studio
 
Phần 6: Hàm
Phần 6: HàmPhần 6: Hàm
Phần 6: Hàm
 
C++
C++C++
C++
 
Các cấu trúc lệnh trong C
Các cấu trúc lệnh trong CCác cấu trúc lệnh trong C
Các cấu trúc lệnh trong C
 
LAP TRINH C - SESSION 2
LAP TRINH C - SESSION 2LAP TRINH C - SESSION 2
LAP TRINH C - SESSION 2
 
Lesson04
Lesson04Lesson04
Lesson04
 
Ngon ngu c theo chuan ansi
Ngon ngu c theo chuan ansiNgon ngu c theo chuan ansi
Ngon ngu c theo chuan ansi
 
Lesson04
Lesson04Lesson04
Lesson04
 

Viewers also liked

Viewers also liked (14)

TEMPO Magazine January 2013 Issue
TEMPO Magazine January 2013 IssueTEMPO Magazine January 2013 Issue
TEMPO Magazine January 2013 Issue
 
Thu vien chuan c++
Thu vien chuan c++Thu vien chuan c++
Thu vien chuan c++
 
Ngon ngu lap trinh c&c++
Ngon ngu lap trinh c&c++Ngon ngu lap trinh c&c++
Ngon ngu lap trinh c&c++
 
TEMPO Magazine February 2013 Issue
TEMPO Magazine February 2013 IssueTEMPO Magazine February 2013 Issue
TEMPO Magazine February 2013 Issue
 
C10 generic algorithms
C10 generic algorithmsC10 generic algorithms
C10 generic algorithms
 
Presentation1
Presentation1Presentation1
Presentation1
 
C7 class relationship
C7 class relationshipC7 class relationship
C7 class relationship
 
Catalogo cepilleria
Catalogo cepilleriaCatalogo cepilleria
Catalogo cepilleria
 
C4 data structures
C4 data structuresC4 data structures
C4 data structures
 
C5 classes and objects
C5 classes and objectsC5 classes and objects
C5 classes and objects
 
TEMPO Magazine April 2013 Issue
TEMPO Magazine April 2013 IssueTEMPO Magazine April 2013 Issue
TEMPO Magazine April 2013 Issue
 
Nang cao c++
Nang cao c++Nang cao c++
Nang cao c++
 
Bai tap oop c++
Bai tap oop c++Bai tap oop c++
Bai tap oop c++
 
TEMPO Magazine March 2013 Issue
TEMPO Magazine March 2013 IssueTEMPO Magazine March 2013 Issue
TEMPO Magazine March 2013 Issue
 

Similar to Con tro ham c++

Lap trinh c_tu_co_ban_den_nang_cao
Lap trinh c_tu_co_ban_den_nang_caoLap trinh c_tu_co_ban_den_nang_cao
Lap trinh c_tu_co_ban_den_nang_caoHuy Nguyễn
 
Hàm và nạp chồng hàm
Hàm và nạp chồng hàmHàm và nạp chồng hàm
Hàm và nạp chồng hàmLAnhHuy4
 
BÀI 6: Thủ tục (SUB) và hàm (FUNCTION) - Giáo trình FPT
BÀI 6: Thủ tục (SUB) và hàm (FUNCTION) - Giáo trình FPTBÀI 6: Thủ tục (SUB) và hàm (FUNCTION) - Giáo trình FPT
BÀI 6: Thủ tục (SUB) và hàm (FUNCTION) - Giáo trình FPTMasterCode.vn
 
Hàm và Chuỗi
Hàm và ChuỗiHàm và Chuỗi
Hàm và Chuỗipnanhvn
 
3 Function
3 Function3 Function
3 FunctionCuong
 
C3 functions and_library
C3 functions and_libraryC3 functions and_library
C3 functions and_libraryHồ Lợi
 
Nmlt c15 ham_nangcao_phan1_in
Nmlt c15 ham_nangcao_phan1_inNmlt c15 ham_nangcao_phan1_in
Nmlt c15 ham_nangcao_phan1_inHuy Nguyễn
 
Lec3. Ham.pdf
Lec3. Ham.pdfLec3. Ham.pdf
Lec3. Ham.pdfKinHongnh
 
Chapter02 data types_control_structures
Chapter02 data types_control_structuresChapter02 data types_control_structures
Chapter02 data types_control_structurestrinhtv060392
 

Similar to Con tro ham c++ (20)

Lap trinh c_tu_co_ban_den_nang_cao
Lap trinh c_tu_co_ban_den_nang_caoLap trinh c_tu_co_ban_den_nang_cao
Lap trinh c_tu_co_ban_den_nang_cao
 
Chuong 01 mo dau
Chuong 01 mo dauChuong 01 mo dau
Chuong 01 mo dau
 
Bai 18
Bai 18Bai 18
Bai 18
 
Hàm và nạp chồng hàm
Hàm và nạp chồng hàmHàm và nạp chồng hàm
Hàm và nạp chồng hàm
 
BÀI 6: Thủ tục (SUB) và hàm (FUNCTION) - Giáo trình FPT
BÀI 6: Thủ tục (SUB) và hàm (FUNCTION) - Giáo trình FPTBÀI 6: Thủ tục (SUB) và hàm (FUNCTION) - Giáo trình FPT
BÀI 6: Thủ tục (SUB) và hàm (FUNCTION) - Giáo trình FPT
 
Hàm và Chuỗi
Hàm và ChuỗiHàm và Chuỗi
Hàm và Chuỗi
 
3 Function
3 Function3 Function
3 Function
 
693 toiuuma c
693 toiuuma c693 toiuuma c
693 toiuuma c
 
693 toiuuma c
693 toiuuma c693 toiuuma c
693 toiuuma c
 
Bai 17
Bai 17Bai 17
Bai 17
 
C3 functions and_library
C3 functions and_libraryC3 functions and_library
C3 functions and_library
 
C3 functions and_library
C3 functions and_libraryC3 functions and_library
C3 functions and_library
 
Chuong 2
Chuong 2Chuong 2
Chuong 2
 
Nmlt c15 ham_nangcao_phan1_in
Nmlt c15 ham_nangcao_phan1_inNmlt c15 ham_nangcao_phan1_in
Nmlt c15 ham_nangcao_phan1_in
 
Nmlt c06 ham
Nmlt c06 hamNmlt c06 ham
Nmlt c06 ham
 
Nmlt c06 ham_in
Nmlt c06 ham_inNmlt c06 ham_in
Nmlt c06 ham_in
 
Lec3. Ham.pdf
Lec3. Ham.pdfLec3. Ham.pdf
Lec3. Ham.pdf
 
Dethi c++ -lt
Dethi c++ -ltDethi c++ -lt
Dethi c++ -lt
 
Ctdl lab01
Ctdl lab01Ctdl lab01
Ctdl lab01
 
Chapter02 data types_control_structures
Chapter02 data types_control_structuresChapter02 data types_control_structures
Chapter02 data types_control_structures
 

More from Tiến Quang Phan

More from Tiến Quang Phan (9)

Windows comunication foundation
Windows comunication foundationWindows comunication foundation
Windows comunication foundation
 
VBA for AutoCAD
VBA for AutoCADVBA for AutoCAD
VBA for AutoCAD
 
Mainboard
MainboardMainboard
Mainboard
 
Keyboard shortcuts for windows 8 pdf
Keyboard shortcuts for windows 8 pdfKeyboard shortcuts for windows 8 pdf
Keyboard shortcuts for windows 8 pdf
 
C9 templates
C9 templatesC9 templates
C9 templates
 
C8 object-oriented thinking
C8 object-oriented thinkingC8 object-oriented thinking
C8 object-oriented thinking
 
C2 basics of_c_and_cpp
C2 basics of_c_and_cppC2 basics of_c_and_cpp
C2 basics of_c_and_cpp
 
C1 introduction
C1 introductionC1 introduction
C1 introduction
 
Giao trinh c can ban
Giao trinh c can banGiao trinh c can ban
Giao trinh c can ban
 

Con tro ham c++

  • 1. Bài viết về function pointer được trích dịch từ tài liệu này: http://www.newty.de/fpt/index.html 1. 1 Giới thiệu 1. 1.1 Function Pointer là gì? 2. 1.2 Thay thế câu lệnh Switch như thế nào? 2. 2 Syntax của C và C++ function pointer 1. 2.1 Define một function pointer 2. 2.2 Calling Convention 3. 2.3 Gán một địa chỉ vào function pointer 4. 2.4 So sánh các function pointer 5. 2.5 Gọi một hàm sử dụng function pointer 6. 2.6 Truyền function pointer như là một tham số 7. 2.7 Trả về một function pointer 8. 2.8 Sử dụng một mảng các function pointer 3. 3 Implement Callback Functions in C and C++ 1. 3.1 Khái niệm về callback function 2. 3.2 Implement a callback function in C 3. 3.3 Implement a Callback to a static C++ Member Function 4. 3.4 Implement a Callback to a non-static C++ Member Function 1 Giới thiệu Function Pointer cung cấp một kỹ thuật lập trình cực kỳ thú vị, hiệu quả và “đầy màu sắc”. Chúng ta có thể sử dụng nó để thay thế câu lệnh switch/if, xây dựng quá trình late-binding hoặc implement hàm callback. Tiếc thay, có thể vì sự phức tạp của nó mà nó được đề cập rất ít trong hầu hết sách và tài liệu. Nếu có thì nó chỉ được trình bày một cách rất tóm tắt và sơ sài. Thực ra thì nó ít gây ra lỗi hơn so với pointer bình thường bởi vì chúng ta không bao giờ phải allocate hoặc de-allocate bộ nhớ cả. Tất cả việc chúng ta cần làm là hiểu nó làm gì và học cú pháp của nó. Nhưng hãy luôn tâm niệm rằng: hãy tự hỏi bạn có thực sự cần đến function pointer hay không? Rất tuyệt để thể hiện cách thức late-binding, thế nhưng sử dụng cấu trúc hiện tại của C++ làm cho đoạn mã trở nên dễ đọc và rõ ràng hơn. Một khía cạnh khác của late- binding là runtime: nếu bạn gọi một virtual function, chương trình sẽ xác định hàm
  • 2. nào được gọi. Nó làm điều đó bằng cách sử dụng V-Table mà chứa tất cả những hàm có thể gọi. Điều đó có vẻ hơi lãng phí mỗi lần gọi, và có thể bạn sẽ tiết kiệm một chút nếu sử dụng function pointer thay vì virtual function. Cũng có thể không … 1.1 Function Pointer là gì? Function pointer là một pointer mà nó chỉ đến địa chỉ của một hàm. Bạn phải luôn giữ trong đầu rằng một chương trình chạy sẽ chiếm một không gian bộ nhớ xác định trong bộ nhớ chính. Cả đoạn chương trình thực thi đã được dịch từ mã mà bạn viết và các biến sử dụng đều được đưa vào trong không gian bộ nhớ này. Vì vậy một function trong chương trình của bạn không có gì khác hơn là một địa chỉ trong bộ nhớ. 1.2 Thay thế câu lệnh Switch như thế nào? Khi chúng ta muốn gọi một hàm DoIt() ở một label xác định trong chương trình, chúng ta phải để lời gọi tới hàm DoIt() tại label đó. Sau đó biên dịch và mỗi khi chương trình chạy tới label đó thì hàm DoIt() sẽ được gọi. Mọi thứ đều ok, nhưng sẽ làm gì nếu giả sử chúng ta không biết tại thời điểm build-time (thời gian dịch) hàm nào sẽ được gọi? Nghĩa là chỉ đến lúc chạy ta mới biết ở label đó thì nên chạy DoIt() hay một hàm nào khác. Đó chính là lúc chúng ta muốn sử dụng đến callback-function hoặc là sử dụng kỹ thuật lấy ra từ một “pool” chứa các possible function. Tuy nhiên thì chúng ta có thể giải quyết vấn đề này bằng cách sử dụng lệnh switch, và lựa chọn lời gọi đến hàm thích hợp ở những nhánh khác nhau tùy theo giá trị biểu thức của switch. Nhưng vẫn có một cách khác là sử dụng function pointer. Trong ví dụ sau đây chúng ta thực hiện nhiệm vụ của bốn toán tử toán học cơ bản (+, -, *, /). Cách đầu tiên sử dụng switch và cách thứ hai sử dụng function pointer. //----------------------------------------------------------------------------- - // 1.2 Introductory Example or How to Replace a Switch-Statement // Task: Perform one of the four basic arithmetic operations specified by the // characters '+', '-', '*' or '/'. // The four arithmetic operations ... one of these functions is selected // at runtime with a swicth or a function pointer float Plus (float a, float b) { return a+b; } float Minus (float a, float b) { return a-b; } float Multiply(float a, float b) { return a*b; } float Divide (float a, float b) {
  • 3. return a/b; } // Solution with a switch-statement - <opCode> specifies which operation to execute void Switch(float a, float b, char opCode) { float result; // execute operation switch(opCode) { case '+' : result = Plus (a, b); break; case '-' : result = Minus (a, b); break; case '*' : result = Multiply (a, b); break; case '/' : result = Divide (a, b); break; } cout << "Switch: 2+5=" << result << endl; // display result } // Solution with a function pointer - <pt2Func> is a function pointer and points to // a function which takes two floats and returns a float. The function pointer // "specifies" which operation shall be executed. void Switch_With_Function_Pointer(float a, float b, float (*pt2Func)(float, float)) { float result = pt2Func(a, b); // call using function pointer cout << "Switch replaced by function pointer: 2-5="; // display result cout << result << endl; } // Execute example code void Replace_A_Switch() { cout << endl << "Executing function 'Replace_A_Switch'" << endl; Switch(2, 5, /* '+' specifies function 'Plus' to be executed */ '+'); Switch_With_Function_Pointer(2, 5, /* pointer to function 'Minus' */ &Minus); } Chú ý: Một function pointer luôn trỏ đến một function đặc biệt nên tất cả những function mà chúng ta muốn sử dụng với cùng một function pointer thì phải có cùng tham số và giá trị trả về. Nói một cách khác là cùng prototype. 2SyntaxcủaC và C++ function pointer Dựa vào cú pháp thì có hai loại function pointer khác nhau: một là những function pointer trỏ đến C function hoặc static C++ member function, một là những function pointer tới non-static C++ member function. Sự khác biệt cơ bản là tất cả pointer đến non-static member function cần một tham số ẩn: con trỏ this tới instance của class. Vậy chỉ cần nhớ rằng có hai loại function pointer không tương thích với nhau. 2.1 Define một function pointer Vì function pointer không khác gì hơn một biến nên nó phải được define giống như thông thương. Ví dụ dưới đây chúng ta khai báo các function pointer tên là
  • 4. pt2Function, pt2Member và pt2ConstMember. Chúng trở đến function và lấy một biến float và hai biến char và trả về một số int. Ở ví dụ C++ chúng ta giả sử rằng function mà function pointer trỏ đến là non-static member function của TMyClass. // 2.1 define a function pointer and initialize to NULL int (*pt2Function)(float, char, char) = NULL; // C int (TMyClass::*pt2Member)(float, char, char) = NULL; // C++ int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL; // C++ 2.2 Calling Convention Thông thường chúng ta không phải nghĩ về calling convention của một function. Trình biên dịch giả định rằng cdecl là convention mặc định nếu chúng ta không sử dụng một convention khác. Nếu bạn muốn tìm hiểu kỹ hơn hãy tìm đọc. Calling convention nói cho trình biên dịch biết cách truyền tham số và cách tạo ra một function. Ví dụ về những calling convention khác là stdcall, pascal, fastcall. Nếu function và function pointer khác calling convention thì chúng cũng không tương thích với nhau và không thể thực hiện phép gán function pointer vào địa chỉ của function kia. Đối với trình biên dịch của Borland và Microsoft thì cần khai báo calling convention ở giữa kiểu trả về và tên hàm hay tên function pointer. Đối với GNU GCC thì sử dụng từ khóa __attribute__: viết khai báo hàm theo sau bởi từ khóa __attribute__ và sau đó là trạng thái của calling convention ở trong1 . // 2.2 define the calling convention void __cdecl DoIt(float a, char b, char c); // Borland and Microsoft void DoIt(float a, char b, char c) __attribute__((cdecl)); // GNU GCC 2.3 Gán một địa chỉ vào function pointer Rất dễ dàng để gán một địa chỉ của một function vào function pointer. Đơn giản chỉ cần lấy tên của function hoặc member function thích hợp. Mặc dù hầu hết các compiler support việc đó nhưng tốt hơn hết là chúng ta sử dụng toán tử địa chỉ & và đặt trước các function name để viết những đoạn mã portable. Chúng ta cũng phải sử dụng tên đầy đủ của member function bao gồm tên lớp và toán tử scope (::). Chúng ta cũng phải đảm bảo rằng chúng ta được quyền truy nhập vào function ở bên trong scope đó. // 2.3 assign an address to the function pointer // Note: Although you may ommit the address operator on most compilers // you should always use the correct way in order to write portable code. // C int DoIt (float a, char b, char c) { printf("DoItn"); return a+b+c; } int DoMore(float a, char b, char c)const { printf("DoMoren");
  • 5. return a-b+c; } pt2Function = DoIt; // short form pt2Function = &DoMore; // correct assignment using address operator // C++ class TMyClass { public: int DoIt(float a, char b, char c) { cout << "TMyClass::DoIt"<< endl; return a+b+c; }; int DoMore(float a, char b, char c) const { cout << "TMyClass::DoMore" << endl; return a-b+c; }; /* more of TMyClass */ }; pt2ConstMember = &TMyClass::DoMore; // correct assignment using address operator pt2Member = &TMyClass::DoIt; // note: <pt2Member> may also legally point to &DoMore 2.4 So sánh các function pointer Chúng ta có thể sử dụng các toán tử so sánh (==, !=) như bình thường. Trong ví dụ dưới đây nó được kiểm tra xem pt2Function và pt2Member có thực sự chứa địa chỉ của hàm DoIt() và TMyClass::DoMore() // 2.4 comparing function pointers // C if(pt2Function >0) { // check if initialized if(pt2Function == &DoIt) printf("Pointer points to DoItn"); } else printf("Pointer not initialized!!n"); // C++ if(pt2ConstMember == &TMyClass::DoMore) cout << "Pointer points to TMyClass::DoMore" << endl; 2.5 Gọi một hàm sử dụng function pointer Trong C, chúng ta gọi một hàm sử dụng function pointer bằng cách explicitly dereferencing nó bằng toán tử *. Một lựa chọn khác là sử dụng function pointer thay vì function name. Trong C++ hai toán tử .* và ->* được sử dụng cùng với instance của một class để gọi một (non-static) member function. Nếu lời gọi diễn ra bên trong một member function khác, chúng ta có thể sử dụng con trỏ this. // 2.5 calling a function using a function pointer int result1 = pt2Function (12, 'a', 'b'); // C short way int result2 = (*pt2Function) (12, 'a', 'b'); // C TMyClass instance1; int result3 = (instance1.*pt2Member)(12, 'a', 'b'); // C++ int result4 = (*this.*pt2Member)(12, 'a', 'b'); // C++ if this-pointer can be used TMyClass* instance2 = new TMyClass; int result4 = (instance2->*pt2Member)(12, 'a', 'b'); // C++, instance2 is a pointer delete instance2; 2.6 Truyền function pointer như là một tham số
  • 6. Chúng ta có thể truyền function pointer như một tham số của một function được gọi khác. Điều đó rất cần thiết nếu chúng ta muốn truyền một con trỏ tới một callback function. Đoạn mã dưới đây chỉ cách truyền một pointer tới một function mà trả về một số nguyên và lấy một số float và 2 char làm tham số: //----------------------------------------------------------------------------- ------- // 2.6 How to Pass a Function Pointer // <pt2Func> is a pointer to a function which returns an int and takes a float and two char void PassPtr(int (*pt2Func)(float, char, char)) { int result = (*pt2Func)(12, 'a', 'b'); // call using function pointer cout << result << endl; } // execute example code - 'DoIt' is a suitable function like defined above in 2.1-4 void Pass_A_Function_Pointer() { cout << endl << "Executing 'Pass_A_Function_Pointer'" << endl; PassPtr(&DoIt); } 2.7 Trả về một function pointer Hơi mẹo một chút nhưng một function pointer có thể là giá trị trả về của một function. Trong ví dụ sau đây có hai giải pháp cho việc trả về một function pointer. Nếu muốn trả về một pointer vào một member function, chúng ta phải thay đổi definitions và declarations của tất cả các function pointer. //----------------------------------------------------------------------------- ------- // 2.7 How to Return a Function Pointer // 'Plus' and 'Minus' are defined above. They return a float and take two float // Direct solution: Function takes a char and returns a pointer to a // function which is taking two floats and returns a float. <opCode> // specifies which function to return float (*GetPtr1(const char opCode))(float, float) { if(opCode == '+') return &Plus; else return &Minus; } // default if invalid operator was passed // Solution using a typedef: Define a pointer to a function which is taking // two floats and returns a float typedef float(*pt2Func)(float, float); // Function takes a char and returns a function pointer which is defined // with the typedef above. <opCode> specifies which function to return pt2Func GetPtr2(const char opCode) { if(opCode == '+') return &Plus; else return &Minus; // default if invalid operator was passed } // Execute example code void Return_A_Function_Pointer() { cout << endl << "Executing 'Return_A_Function_Pointer'" << endl; // define a function pointer and initialize it to NULL float (*pt2Function)(float, float) = NULL; pt2Function=GetPtr1('+'); // get function pointer from function 'GetPtr1'
  • 7. cout << (*pt2Function)(2, 4) << endl; // call function using the pointer pt2Function=GetPtr2('-'); // get function pointer from function 'GetPtr2' cout << (*pt2Function)(2, 4) << endl; // call function using the pointer } 2.8 Sử dụng một mảng các function pointer Sử dụng một mảng các function pointer khá thú vị. Nó cho phép khả năng lựa chọn một function sử dụng chỉ số (index). Cú pháp thì khá phức tạp và thường xuyên dẫn đến sự nhầm lẫn. Đoạn mã dưới đây bạn sẽ tìm thấy hai cách sử dụng một mảng các function pointer trong C và C++. Cách thứ nhất sử dụng typedef và cách thứ hai sử dụng trức tiếp cách khai bao mảng. Nó tùy thuộc vào bạn thích cách nào hơn. //----------------------------------------------------------------------------- ------- // 2.8 How to Use Arrays of Function Pointers // C -------------------------------------------------------------------------- ------- // type-definition: 'pt2Function' now can be used as type typedef int (*pt2Function)(float, char, char); // illustrate how to work with an array of function pointers void Array_Of_Function_Pointers() { printf("nExecuting 'Array_Of_Function_Pointers'n"); // define arrays and ini each element to NULL, <funcArr1> and <funcArr2> are arrays // with 10 pointers to functions which return an int and take a float and two char // first way using the typedef pt2Function funcArr1[10] = { NULL }; // 2nd way directly defining the array int (*funcArr2[10])(float, char, char) = { NULL }; // assign the function's address - 'DoIt' and 'DoMore' are suitable functions // like defined above in 2.1-4 funcArr1[0] = funcArr2[1] = &DoIt; funcArr1[1] = funcArr2[0] = &DoMore; /* more assignments */ // calling a function using an index to address the function pointer printf("%dn", funcArr1[1](12, 'a', 'b')); // short form printf("%dn", (*funcArr1[0])(12, 'a', 'b')); // "correct" way of calling printf("%dn", (*funcArr2[1])(56, 'a', 'b')); printf("%dn", (*funcArr2[0])(34, 'a', 'b')); } // C++------------------------------------------------------------------------- ------ // type-definition: 'pt2Member' now can be used as type typedef int (TMyClass::*pt2Member)(float, char, char); // illustrate how to work with an array of member function pointers void Array_Of_Member_Function_Pointers() { cout << endl << "Executing 'Array_Of_Member_Function_Pointers'" << endl; // define arrays and ini each element to NULL, <funcArr1> and <funcArr2> are // arrays with 10 pointers to member functions which return an int and take // a float and two char // first way using the typedef pt2Member funcArr1[10] = { NULL
  • 8. }; // 2nd way of directly defining the array int (TMyClass::*funcArr2[10])(float, char, char) = { NULL }; // assign the function's address - 'DoIt' and 'DoMore' are suitable member // functions of class TMyClass like defined above in 2.1-4 funcArr1[0] = funcArr2[1] = &TMyClass::DoIt; funcArr1[1] = funcArr2[0] = &TMyClass::DoMore; /* more assignments */ // calling a function using an index to address the member function pointer // note: an instance of TMyClass is needed to call the member functions TMyClass instance; cout << (instance.*funcArr1[1])(12, 'a', 'b') << endl; cout << (instance.*funcArr1[0])(12, 'a', 'b') << endl; cout << (instance.*funcArr2[1])(34, 'a', 'b') << endl; cout << (instance.*funcArr2[0])(89, 'a', 'b') << endl; } 3Implement Callback Functions in C and C++ 3.1 Khái niệm về callback function Function pointer cung cấp khái niệm về callback function. Trong bài viết này sẽ giới thiệu về callback function thông qua một hàm về giải thuật sắp xếp nổi tiếng là qsort. Hàm này sẽ sắp xếp các phần tử theo một tiêu chuẩn sắp xếp mà user định nghĩa. Kích thước của một item và số item cần sắp xếp sẽ được truyền vào hàm này. Câu hỏi đặt ra là: làm thế nào để hàm qsort có thể sắp xếp các phần tử mà không có một thông tin gì về kiểu của phần tử sắp xếp? Câu trả lời rất đơn giản: hàm qsort sẽ nhận một function pointer tới một hàm so sánh như một tham số. Hàm so sánh sẽ nhận hai tham số kiểu con trỏ void tới hai phần tử và sẽ đánh giá thứ tự của hai phần tử đó và trả về một số int. Do đó mỗi lần giải thuật sắp xếp cần đánh giá ví trí của hai phần tử thì đơn giản chỉ là gọi tới hàm so sánh thông qua function pointer. 3.2 Implement a callback function in C Hãy tham khảo khai báo của hàm qsort dưới đây: void qsort(void* field, size_t nElements, size_t sizeOfAnElement, int(_USERENTRY *cmpFunc)(const void *, const void*)); field trỏ tới phần tử đầu tiên của mảng cần sắp xếp, nElements là số phần tử cần sắp xếp, sizeOfAnElement là kích thước của một phần tử tính theo byte, và cmpFunc là function pointer tới hàm so sánh. Hàm so sánh này lấy hai phần tử const void* và trả về một số nguyên. void qsort( ... , int(_USERENTRY *cmpFunc)(const void*, const void*)) { /* sort algorithm - note: item1 and item2 are void-pointers */ int bigger=cmpFunc(item1, item2); // make callback /* use the result */ } Ví dụ mẫu sử dụng qsort
  • 9. //----------------------------------------------------------------------------- ------------ // 3.3 How to make a callback in C by the means of the sort function qsort #include <stdlib.h> // due to: qsort #include <time.h> // randomize #include <stdio.h> // printf // comparison-function for the sort-algorithm // two items are taken by void-pointer, converted and compared int CmpFunc(const void* _a, const void* _b) { // you've got to explicitly cast to the correct type const float* a = (const float*) _a; const float* b = (const float*) _b; if(*a > *b) return 1; // first item is bigger than the second one -> return 1 else if(*a == *b) return 0; // equality -> return 0 else return -1; // second item is bigger than the first one -> return -1 } // example for the use of qsort() void QSortExample() { float field[100]; ::randomize(); // initialize random-number-generator for(int c=0;c<100;c++) // randomize all elements of the field field[c]=random(99); // sort using qsort() qsort((void*) field, /*number of items*/ 100, /*size of an item*/ sizeof(field[0]), /*comparison-function*/ CmpFunc); // display first ten elements of the sorted field printf("The first ten elements of the sorted field are ...n"); for(int c=0;c<10;c++) printf("element #%d contains %.0fn", c+1, field[c]); printf("n"); } 3.3 Implement a Callback to a static C++ Member Function Implement callback tới static C++ member function cũng giống như implement callback tới hàm C. Static member function không cần một object để thực thi vì vậy nó phải có cùng một prototype như là hàm C với cùng một calling convention, tham số và giá trị trả về. 3.4 Implement a Callback to a non-static C++ Member Function Function pointer tới non-static member thì khác với là một con trỏ hàm C vì nó cần phải truyền một con trỏ this của object. Nếu bạn chỉ muốn gọi hàm callback tới một member function của một class cụ thể thì chỉ cần chuyển đoạn mã từ một function pointer bình thường tới con trỏ tới member function. Nhưng sẽ làm thế nào nếu muốn gọi hàm callback tới một non-static member của một class chưa rõ nào đó (có thể ở các class khác nhau). Nó khó hơn một chút. Chúng ta cần phải viết một static member function như là một wrapper. Static member function thì được coi như là một function bình thường. Sau đó thì chúng ta sẽ ép kiểu con trỏ tới object mà chúng
  • 10. ta muốn nó thực hiện member function, thành void* và truyền nó tới wrapper như là một tham số phụ thêm hoặc là thông qua một biến toàn cục. Tất nhiên chúng ta cũng truyền các tham số gọi tới member function. Wrapper sẽ ép kiểu con trỏ void thành con trỏ tới object của class tương ứng của nó và gọi member function (kỹ thuật này giống với kỹ thuật chúng ta xây dựng class Thread). Dưới đây là hai ví dụ: Ví dụ A: Con trỏ tới object được truyền như là một tham số phụ thêm: Hàm DoItA() thực hiện với object của class TClassA để gọi hàm callback. Vì vậy con trỏ tới object của class TClassA và con trỏ tới static wrapper function TClassA::Wrapper_To_Call_Display được truyền tới DoItA(). Hàm wrapper này là callback function. Chúng ta có thể viết những class khác giống như TClassA và sử dụng chung với DoItA miễn là những class này cũng cung cấp các function cần thiết. Chú ý là giải pháp này có thể hữu dụng nếu chúng ta tự thiết kế giao diện của hàm callback. Nó tốt hơn rất nhiều so với giải pháp thứ hai sử dụng biến toàn cục. //----------------------------------------------------------------------------- ------------ // 3.5 Example A: Callback to member function using an additional argument // Task: The function 'DoItA' makes something which implies a callback to // the member function 'Display'. Therefore the wrapper function // 'Wrapper_To_Call_Display is used. #include <iostream.h> // due to: cout class TClassA { public: void Display(const char* text) { cout << text << endl; }; static void Wrapper_To_Call_Display(void* pt2Object, char* text); /* more of TClassA */ }; // static wrapper function to be able to callback the member function Display() void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string) { // explicitly cast to a pointer to TClassA TClassA* mySelf = (TClassA*) pt2Object; // call member mySelf->Display(string); } // function does something which implies a callback // note: of course this function can also be a member function void DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text)) { /* do something */ pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)"); // make callback } // execute example code void Callback_Using_Argument() { // 1. instantiate object of TClassA TClassA objA; // 2. call 'DoItA' for <objA> DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display); }
  • 11. Ví dụ B: Con trỏ tới object được lưu trữ trong một biến toàn cục. Hàm DoItB sẽ thực hiện với đối tượng của class B để gọi hàm callback. Một con trỏ tới static wrapper function TClassB::Wrapper_To_Call_Display được truyền vào DoItB. Hàm wrapper này là callback function. Wrapper sử dụng biến toàn cục void* pt2Object và phép ép kiểu thành một đối tượng của TClassB. Phải luôn chú ý khởi tạo biến toàn cục để chỉ đến đối tượng của class chính xác. Chúng ta có thể viết những class khác như TClassB và sử dụng chúng với DoItB miễn là những class đó cung cấp các function cần thiết. Cách này không phải là một giải pháp tốt bởi vì sử dụng biến toàn cục rất nguy hiểm và có thể gây ra những lỗi nghiêm trọng. //----------------------------------------------------------------------------- - // 3.5 Example B: Callback to member function using a global variable // Task: The function 'DoItB' makes something which implies a callback to // the member function 'Display'. Therefore the wrapper function // 'Wrapper_To_Call_Display is used. #include <iostream.h> // due to: cout void* pt2Object; // global variable which points to an arbitrary object class TClassB { public: void Display(const char* text) { cout << text << endl; }; static void Wrapper_To_Call_Display(char* text); /* more of TClassB */ }; // static wrapper function to be able to callback the member function Display() void TClassB::Wrapper_To_Call_Display(char* string) { // explicitly cast global variable <pt2Object> to a pointer to TClassB // warning: <pt2Object> MUST point to an appropriate object! TClassB* mySelf = (TClassB*) pt2Object; // call member mySelf->Display(string); } // function does something which implies a callback // note: of course this function can also be a member function void DoItB(void (*pt2Function)(char* text)) { /* do something */ pt2Function("hi, i'm calling back using a global ;-)"); // make callback } // execute example code void Callback_Using_Global() { // 1. instantiate object of TClassB TClassB objB; // 2. assign global variable which is used in the static wrapper function // important: never forget to do this!! pt2Object = (void*) &objB; // 3. call 'DoItB' for <objB> DoItB(TClassB::Wrapper_To_Call_Display); } 1. [↩] 5 Comments »
  • 12.  kiennguyen said: Sao lại có fpt ở đây nhỉ - 26 November 2007 at 5:40 pm  tuanpv5@yahoo.com said: Thông hiểu được cái này thì sẽ hiểu ngay cơ chế làm việc của thư viện liên kết động (.dll trên Windows và .so trên Linux) nhỉ. Bài viết quá hay ! Một tràng pháo tay cho bạn Hoàng nào . - 26 April 2008 at 3:43 pm  thachdaigia47 said: thank you - 7 January 2009 at 3:06 pm  van viet said: thanks tat ca! dien dan nay tuyet wa - 20 October 2009 at 11:17 pm  LongVNIT » Function Pointer said: [...] Thông thường chúng ta không phải nghĩ về calling convention của một function. Trình biên dịch giả định rằng cdecl là convention mặc định nếu chúng ta không sử dụng một convention khác. Nếu bạn muốn tìm hiểu kỹ hơn hãy tìm đọc. Calling convention nói cho trình biên dịch biết cách truyền tham số và cách tạo ra một function. Ví dụ về những calling convention khác là stdcall, pascal, fastcall. Nếu function và function pointer khác calling convention thì chúng cũng không tương thích với nhau và không thể thực hiện phép gán function pointer vào địa chỉ của function kia. Đối với trình biên dịch của Borland và Microsoft thì cần khai báo calling convention ở giữa kiểu trả về và tên hàm hay tên function pointer. Đối với GNU GCC thì sử
  • 13. dụng từ khóa __attribute__: viết khai báo hàm theo sau bởi từ khóa __attribute__ và sau đó là trạng thái của calling convention ở trong1. [...]