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ết | Hợp lệ | Ghi chú |
|---|---|---|
| op = cong; | Có | Cách thường dùng |
| op = ≅ | 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 == cong | Có | So sánh địa chỉ |
| op = NULL; | Có | Con trỏ hàm rỗng, phải kiểm trước khi gọi |
| op + 1 | Không | Không có số học con trỏ trên con trỏ hàm |
| sizeof(*op) | Không | Không lấy kích thước một hàm được |
#Hai khai báo dễ nhầm
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ử
- 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ả.
- Khai báo
int *f(int);vàint (*g)(int);trong cùng một tệp, gángbằng một hàm rồi thử gánf. - Viết
typedefcho một hàm nhậnconst char *và trả vềsize_t, rồi dùng nó làm tham số, biến, và trường struct. - Đọ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. - Viết lại khai báo của
signalbằng haitypedefvà 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
congvà&conggiố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ònint (*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.