Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 29.124 phút đọc

Cú pháp con trỏ hàm

Sau bài này bạn sẽ làm được

  • Khai báo và gán con trỏ hàm đúng cú pháp
  • Phân biệt hàm trả về con trỏ với con trỏ tới hàm
  • Dùng typedef để mọi khai báo về sau đọc được
  • Đọc khai báo phức tạp theo quy tắc xoáy trôn ốc

Bài 13.5 đã giới thiệu con trỏ hàm như một loại con trỏ. Chương này coi nó là một công cụ thiết kế: cách tách phần thay đổi khỏi phần cố định, cách thay chuỗi switch bằng một bảng, và cách C mô phỏng đa hình. Bài đầu tiên lo phần cú pháp cho xong.

#Hàm cũng có địa chỉ

dia-chi.c
#include <stdio.h>

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

int main(void) {
    int (*op)(int, int) = cong;

    printf("op(2,3)       = %d\n", op(2, 3));
    printf("(*op)(2,3)    = %d\n", (*op)(2, 3));
    printf("(&cong)(2,3)  = %d\n", (&cong)(2, 3));
    printf("(****op)(2,3) = %d\n", (****op)(2, 3));

    printf("dia chi cong  = %p\n", (void *)(size_t)cong);
    printf("dia chi op    = %p\n", (void *)(size_t)op);
    printf("op == cong ?  %s\n", op == cong ? "co" : "khong");

    return 0;
}
terminal
gcc -std=c11 -Wall -Wextra -o dia-chi dia-chi.c && ./dia-chi
op(2,3)       = 5
(*op)(2,3)    = 5
(&cong)(2,3)  = 5
(****op)(2,3) = 5
dia chi cong  = 0000000000401550
dia chi op    = 0000000000401550
op == cong ?  co

#Khai báo và gán

kieu_tra_ve (*ten)(danh_sach_tham_so);

int    (*op)(int, int);              /* nhan hai int, tra ve int */
void   (*xu_ly)(void);               /* khong tham so, khong tra ve */
double (*tinh)(const double *, size_t);
int    (*ss)(const void *, const void *);   /* dang cua qsort */
Cách viếtHợp lệGhi chú
op = cong;CóCách thường dùng
op = &cong;CóTương đương, dấu và là tùy chọn
op(2, 3)CóCách gọi thường dùng
(*op)(2, 3)CóTương đương, kiểu cũ
op == congCóSo sánh địa chỉ
op = NULL;CóCon trỏ hàm rỗng, phải kiểm trước khi gọi
op + 1KhôngKhông có số học con trỏ trên con trỏ hàm
sizeof(*op)KhôngKhông lấy kích thước một hàm được

#Hai khai báo dễ nhầm

Một cặp dấu ngoặc đổi hẳn ý nghĩa. Không có cách nào đoán ra, chỉ có cách nhớ.
int *f(int);          /* f la HAM, nhan int, tra ve con tro toi int */
int (*f)(int);        /* f la CON TRO, tro toi ham nhan int tra ve int */
Khai báoĐọc là
int *f(int);Hàm nhận int, trả về con trỏ int
int (*f)(int);Con trỏ tới hàm nhận int trả về int
int *a[10];Mảng 10 con trỏ int
int (*a)[10];Con trỏ tới mảng 10 int
int (*f[10])(int);Mảng 10 con trỏ tới hàm
int (*(*f)(int))(int);Con trỏ tới hàm trả về con trỏ tới hàm

#typedef làm mọi thứ đọc được

typedef int (*PhepToan)(int, int);
/*      ^^^ ^  ^^^^^^^^  ^^^^^^^^^^
        |   |     |          tham so
        |   |     ten kieu MOI
        |   kieu tra ve
        tu khoa typedef

   Chu y: ten kieu nam o GIUA, khong phai o cuoi nhu typedef thuong. */

typedef void   (*XuLySuKien)(int ma, void *ngu_canh);
typedef int    (*SoSanh)(const void *, const void *);
typedef size_t (*BamChuoi)(const char *);

#Đọc khai báo phức tạp

Quy tắc xoáy trôn ốc
void (*(*f)(int))(double);

/* Buoc 1: tim TEN. Do la f.
   Buoc 2: nhin sang PHAI truoc, roi sang TRAI, roi mo rong dan.

   f                          f la
   (*f)                       con tro toi
   (*f)(int)                  ham nhan int, tra ve
   *(*f)(int)                 con tro toi
   (*(*f)(int))(double)       ham nhan double, tra ve
   void ...                   void

   Doc thanh cau: f la con tro toi ham nhan int, tra ve con tro
   toi ham nhan double va tra ve void. */
terminal
# Công cụ cdecl dịch hộ, có bản web tại cdecl.org
cdecl explain 'void (*(*f)(int))(double)'
declare f as pointer to function (int) returning pointer to function (double) returning void
cdecl declare f as array 10 of pointer to function returning int
int (*f[10])(void)

Ba khai báo bạn sẽ gặp trong thư viện chuẩn

/* qsort: tham so thu tu la con tro ham */
void qsort(void *co_so, size_t n, size_t co,
           int (*ss)(const void *, const void *));

/* signal: nhan con tro ham VA tra ve con tro ham */
void (*signal(int sig, void (*xu_ly)(int)))(int);

/* Viet lai cho de doc: */
typedef void (*XuLyTinHieu)(int);
XuLyTinHieu signal(int sig, XuLyTinHieu xu_ly);

/* atexit: dang ky ham chay khi chuong trinh ket thuc */
int atexit(void (*ham)(void));

Tự làm thử

  1. Viết chương trình gọi cùng một hàm bằng cả bốn cách ở đầu bài và xác nhận chúng cho cùng kết quả.
  2. Khai báo int *f(int); và int (*g)(int); trong cùng một tệp, gán g bằng một hàm rồi thử gán f.
  3. Viết typedef cho một hàm nhận const char * và trả về size_t, rồi dùng nó làm tham số, biến, và trường struct.
  4. Đọc khai báo char (*(*x[3])())[5]; bằng quy tắc xoáy trôn ốc, rồi kiểm lại bằng cdecl.
  5. Viết lại khai báo của signal bằng hai typedef và xác nhận nó biên dịch được.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Tên hàm tự suy biến thành con trỏ hàm, nên cong và &cong giống nhau, và op(2,3) với (*op)(2,3) cũng vậy.
  • Con trỏ hàm không bảo đảm chuyển đổi được với void * theo chuẩn C, dù POSIX bắt buộc điều đó.
  • int *f(int) là hàm trả về con trỏ, còn int (*f)(int) là con trỏ tới hàm. Dấu ngoặc là toàn bộ khác biệt.
  • typedef đặt tên kiểu ở giữa chứ không ở cuối, và nó làm mọi chỗ dùng về sau đọc được.
  • Khai báo phức tạp đọc bằng quy tắc xoáy trôn ốc, nhưng cách đúng là đừng viết chúng.