[C] Function Pointer (Con trỏ hàm) - P1

Ngô Văn Tuân

Gà con
Staff member
Một cách ngắn gọn: Con trỏ hàm là biến lưu địa chỉ của hàm.

Nếu bạn tự hỏi học con trỏ hàm để làm gì thì mình sẽ cho bạn một lý do để học con trỏ hàm: một ứng dụng phổ biến của con trỏ hàm là để pass sự kiện từ lớp dưới lên các lớp trên. Lấy ví dụ khi bạn click chuột, sự kiện này được gửi từ chuột lên hệ điều hành thông qua giao thức USB, hệ điều hành sẽ gửi sự kiện này vào application thông qua con trỏ hàm. Để hệ điều hành biết hàm cần chạy ở đâu, application sẽ viết một hàm bình thường, sau đó đưa con trỏ trỏ tới hàm đó cho hệ điều hành và kêu với hệ điều hành: hãy nhảy đến vị trí đó và chạy hàm nếu có sự kiện click chuột xảy ra. Đó là nguyên lý để hiểu tại sao bạn có những hàm void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) trong STM32, private void button1_Click(object sender, System.EventArgs e) trong lập trình C#, setOnClickListener( new OnClickListener() { ... }; trong Java hay onclick="myFunction()" trong JavaScript. Với các ngôn ngữ bậc cao, khái niệm con trỏ hàm được thay thế bằng những khái niệm khác để dễ hiểu hơn nhưng bản chất bên trong vẫn là sử dụng con trỏ hàm.
Chú ý: nếu đọc không hiểu thì bỏ qua.

1. Khai báo biến con trỏ hàm:
<kiểu dữ liệu trả về của hàm> ( *<tên con trỏ hàm>) (<kiểu tham số truyền vào số 0>, <kiểu tham số truyền vào số 1>, ...);
Ví dụ:
C:
void (*fun_ptr)(int);                       // declare function pointer : fun_ptr
float (*fun1_ptr)(int,float);               // declare function pointer : fun1_ptr
uint32_t (*fun2_ptr)(int, float, uint32_t); // declare function pointer : fun2_ptr
Nhận xét:
  • Biến fun_ptr có kiểu dữ liệu void (*)(int)
  • Biến fun1_ptr có kiểu dữ liệu float (*)(int,float)
  • Biến fun2_ptr có kiểu dữ liệu uint32_t (*)(int, float, uint32_t)
2. Sử dụng con trỏ hàm:
Để sử dụng con trỏ cần có các toán tử sau:
* : toán tử đi đến vị trí lưu trong con trỏ và chạy hàm​
& : toán tử lấy địa chỉ hàm​
Ví dụ:
C:
#include <stdio.h>
#include <stdint.h>

void fun0(int a)
{
    printf("fun0: a=%d\n", a);
}

float fun1(int a, float b)
{
    printf("fun1: a=%d b=%f\n", a, b);
    return b;
}

uint32_t fun2(int a, float b, uint32_t c)
{
    printf("fun2: a=%d b=%f c=0x%08X\n", a, b, c);
    return c;
}

void fun3(int a)
{
    printf("fun3: a=%d\n", a);
}

int main()
{

    void (*fun_ptr)(int);           // declare function pointer : fun_ptr
    fun_ptr = &fun0;                // assign value of fun_ptr with address of fun0
    (*fun_ptr)(10);                 // go to pointed address and run code with parameter

    float (*fun1_ptr)(int, float) = &fun1;       // declare function pointer: fun1_ptr and assign value of fun1_ptr with address of fun1
    float b = (*fun1_ptr)(10, 20.5);            // go to pointed address, run code with parameter and get return value
    printf("fun1_ptr return value b=%f \n", b);

    uint32_t(*fun2_ptr)(int, float, uint32_t);              // declare function pointer: fun2_ptr
    fun2_ptr = &fun2;                                       // assign value of fun2_ptr with address of fun2
    uint32_t c = (*fun2_ptr)(10, 20.5, (uint32_t)&fun2);    // go to pointed address, run code with parameter and get return value
    printf("fun2_ptr return value c=%d \n", c);

    fun_ptr = &fun3;                // change value of fun_ptr from address of fun0 to address of fun3
    (*fun_ptr)(100);                // go to pointed address, run code with parameter
    return 0;
}
fun0: a=10
fun1: a=10 b=20.500000
fun1_ptr return value b=20.500000
fun2: a=10 b=20.500000 c=0x000A13C0
fun2_ptr return value c=660416
fun3: a=100
Nhận xét:
  • Ta thấy được sự tương quan giữa: khai báo biến với khai báo hàm, lấy địa chỉ hàm với lấy địa chỉ biến, lấy giá trị biến với chạy hàm để lấy giá trị return.
  • Có thể coi con trỏ biến là một con trỏ hàm đặc biệt mà việc gọi con trỏ biến (bằng toán tử *) là việc gọi một hàm với 0 tham số truyền vào, không thực hiện lệnh nào mà chỉ trả về giá trị mà con trỏ trỏ tới.
3. Typedef
Ta rất nên dùng typdef để định nghĩa một function pointer, cái này sẽ giúp code clean hơn.
Ví dụ:
C:
#include <stdio.h>
#include <stdint.h>

typedef int (*func_t)(int, int);

int add(int a, int b)
{
    return a + b;
}

int minus(int a, int b) {
    return a - b;
}

int main()
{
    func_t func;
    func = &add;
    int sum = (*func)(100, 200);
    printf("sum = %d \n", sum);

    func = &minus;
    int sub = (*func)(100, 200);
    printf("sub = %d \n", sub);

    return 0;
}
sum = 300
sub = -100
Giải thích:
  • Trong ví dụ trên, lệnh typedef int (*func_t)(int, int); đã đặt tên cho kiểu dữ int (*)(int,int)func_t.
  • Do đó khi ta khai báo func_t func; thì thực chất là đang khai báo biến func có kiểu dữ liệu int (*)(int,int)
  • Hai hàm số int add(int a, int b)int minus(int a, int b) đều có dạng int (*)(int,int) nên ta có thể gán func = &add;func = &minus;
Bài trước: [C] Variable Pointer (Con trỏ biến) - P2
Bài tiếp: [C] Function Pointer (Con trỏ hàm) - P2
 
Last edited:
Top