Bỏ qua để đến nội dung

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ỏ.

#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ỏ.

Đâ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;
}

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;
}
  • 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::function thường linh hoạt và dễ đọc hơn con trỏ hàm thuần túy