Typedef cho Con Trỏ Hàm

Chúng ta có thể sử dụng typedef để đơn giản hóa việc sử dụng con trỏ hàm (function pointer). Hãy tưởng tượng chúng ta có một số hàm, tất cả đều có cùng một chữ ký (signature), sử dụng đối số (argument) của chúng để in (print) ra một thứ gì đó theo những cách khác nhau:

#include <stdio.h>

void print_to_n(int n)
{
    for (int i = 1; i <= n; ++i)
        printf("%d\n", i);
}

void print_n(int n)
{
    printf("%d\n, n);
}

Ở đây chúng ta có thể sử dụng typedef để tạo một kiểu con trỏ hàm được đặt tên là printer:

typedef void (*printer_t)(int);

Dòng này tạo ra một kiểu (type), có tên là print_t cho một con trỏ đến một hàm nhận một đối số int và không trả về gì, khớp với chữ ký của các hàm mà chúng ta có ở trên. Để sử dụng nó, chúng ta tạo một biến thuộc kiểu vừa tạo và gán cho nó một con trỏ cho một trong những hàm được đề cập:

printer_t p = &print_to_n;
void (*p)(int) = &print_to_n; // This would be required without the type

Sau đó, gọi hàm được trỏ đến bằng biến con trỏ hàm:

p(5);           // Prints 1 2 3 4 5 on separate lines
(*p)(5);        // So does this

Do đó typedef cho phép một cú pháp đơn giản hơn khi xử lý các con trỏ hàm. Điều này dễ thấy hơn khi con trỏ hàm được sử dụng trong các tình huống phức tạp hơn, chẳng hạn như đối số cho các hàm.

Nếu bạn đang sử dụng một hàm nhận một con trỏ hàm làm tham số (parameter), mà không có kiểu con trỏ hàm được define trước thì định nghĩa hàm sẽ là:

void foo (void (*printer)(int), int y){
    //code
    printer(y);
    //code
}

Tuy nhiên với typedef sẽ thành:

void foo (printer_t printer, int y){
    //code
    printer(y);
    //code
}

Tương tự, các hàm có thể trả về con trỏ hàm và một lần nữa, việc sử dụng typedef có thể làm cho cú pháp đơn giản hơn khi làm như vậy.

Một ví dụ cổ điển là hàm signal trong <signal.h>. Khai báo của nó (theo tiêu chuẩn C) là:

void (*signal(int sig, void (*func)(int)))(int);

Đó là một hàm nhận hai đối số — một int và một con trỏ đến một hàm nhận int làm đối số và không trả về gì — và trả về một con trỏ đến hàm giống như đối số thứ hai của nó.

Nếu chúng ta define một kiểu SigCatcher làm bí danh cho con trỏ tới kiểu hàm:

typedef void (*SigCatcher)(int);

Chúng ta có thể khai báo signal() sử dụng:

SigCatcher signal(int sig, SigCatcher func);

Nhìn chung, cách này dễ hiểu hơn (mặc dù tiêu chuẩn C không yêu cầu define một kiểu để thực hiện công việc). Hàm signal nhận hai đối số, một int và một SigCatcher, và trả về một SigCatcher — trong đó SigCatcher là một con trỏ đến một hàm nhận đối số int và không trả về gì.

Mặc dù việc sử dụng typedef đặt tên cho các kiểu con trỏ đến hàm giúp dễ đọc hơn. Nhưng nó cũng có thể gây bối rối cho người khác khi bảo trì code của bạn sau này, vì vậy hãy sử dụng cẩn thận và cung cấp tài liệu đầy đủ. Xem thêm Con tr hàm.

Icons made by Freepik from www.flaticon.com