Con trỏ hàm (Function Pointer)
Không chỉ biến, ngay cả hàm cũng có một địa chỉ trong bộ nhớ (nơi chứa đoạn mã máy của hàm đó). Con trỏ hàm cho phép lưu địa chỉ này và gọi hàm một cách gián tiếp thông qua con trỏ.
Khai báo và sử dụng con trỏ hàm
Phần tiêu đề “Khai báo và sử dụng con trỏ hàm”#include <iostream>
int add(int a, int b) { return a + b;}
int subtract(int a, int b) { return a - b;}
int main() { int (*operation)(int, int); // Khai báo con trỏ hàm: nhận (int, int), trả về int
operation = add; // Gán địa chỉ của hàm add cho con trỏ std::cout << operation(5, 3) << std::endl; // 8
operation = subtract; // Đổi sang trỏ đến hàm subtract std::cout << operation(5, 3) << std::endl; // 2
return 0;}Cú pháp khai báo int (*operation)(int, int); đọc là: “operation là con trỏ đến một hàm nhận hai int và trả về int”. Dấu ngoặc quanh *operation là bắt buộc để phân biệt với việc khai báo một hàm trả về con trỏ.
Truyền hàm làm tham số cho hàm khác
Phần tiêu đề “Truyền hàm làm tham số cho hàm khác”Đây là ứng dụng phổ biến và hữu ích nhất của con trỏ hàm - cho phép “tham số hóa hành vi” của một hàm:
#include <iostream>
int add(int a, int b) { return a + b; }int multiply(int a, int b) { return a * b; }
int calculate(int a, int b, int (*operation)(int, int)) { return operation(a, b); // Gọi hàm được truyền vào}
int main() { std::cout << calculate(5, 3, add) << std::endl; // 8 std::cout << calculate(5, 3, multiply) << std::endl; // 15
return 0;}calculate không cần biết trước phép tính cụ thể là gì - nó chỉ cần một hàm phù hợp với “khuôn mẫu” int(int, int) được truyền vào lúc gọi.
typedef/using để đặt tên cho kiểu con trỏ hàm
Phần tiêu đề “typedef/using để đặt tên cho kiểu con trỏ hàm”Cú pháp con trỏ hàm khá rối mắt, nên thường được đặt tên gọn hơn bằng using (hoặc typedef kiểu cũ):
#include <iostream>
using Operation = int(*)(int, int); // Đặt tên "Operation" cho kiểu con trỏ hàm này
int add(int a, int b) { return a + b; }
int calculate(int a, int b, Operation op) { return op(a, b);}
int main() { std::cout << calculate(5, 3, add) << std::endl; // 8 return 0;}So sánh với lambda và std::function
Phần tiêu đề “So sánh với lambda và std::function”Trong C++ hiện đại, lambda và std::function (đã học ở bài “Lambda function”) thường được ưu tiên hơn con trỏ hàm thuần túy, vì chúng linh hoạt hơn - có thể “ghi nhớ” (capture) biến từ ngữ cảnh xung quanh, điều mà con trỏ hàm không làm được:
#include <functional>#include <iostream>
int calculate_v2(int a, int b, std::function<int(int, int)> operation) { return operation(a, b);}
int main() { int bonus = 10;
// Lambda có thể "capture" biến bonus - con trỏ hàm thuần túy không làm được điều này auto add_with_bonus = [bonus](int a, int b) { return a + b + bonus; };
std::cout << calculate_v2(5, 3, add_with_bonus) << std::endl; // 18
return 0;}Tổng kết
Phần tiêu đề “Tổng kết”- Con trỏ hàm lưu địa chỉ của một hàm, cho phép gọi hàm đó một cách gián tiếp
- Cú pháp:
KieuTraVe (*ten_con_tro)(KieuThamSo1, KieuThamSo2); - Ứng dụng phổ biến nhất: truyền hành vi (behavior) như một tham số cho hàm khác
- Trong C++ hiện đại, lambda và
std::functionthường linh hoạt và dễ đọc hơn con trỏ hàm thuần túy