Ở bài trước, chúng ta đã tìm hiểu về HÀM CÓ ĐỐI SỐ MẶC ĐIỆNH TRONG C++ (Default arguments) và vai trò quan trọng của đối số mặc định trong việc xác định giá trị mặc định cho các tham số trong ngôn ngữ lập trình c ++. Trong bài học này, chúng ta sẽ tiếp tục khám phá về một khái niệm quan trọng khác trong C++, đó là Con trỏ hàm (Function pointers).
Tìm hiểu về Con trỏ hàm trong C++
Con trỏ hàm là một biến lưu trữ địa chỉ của một hàm. Thông qua biến con trỏ hàm này, chúng ta có thể gọi trực tiếp hàm mà nó trỏ tới. Cú pháp khai báo con trỏ hàm như sau:
(*)();
Ví dụ:
int(*fcnPtr)(int); // con trỏ hàm nhận vào 1 biến kiểu int và trả về kiểu int void(*fcnPtr)(int, int); // con trỏ hàm nhận vào 2 biến kiểu int và trả về kiểu void
Chú ý: Dấu ngoặc () quanh *fcnPtr là bắt buộc.
Gán địa chỉ của hàm cho con trỏ hàm
Giống như các con trỏ khác, con trỏ hàm cũng phải được gán giá trị trước khi sử dụng. Để gán địa chỉ của một hàm cho con trỏ hàm, ta không cần sử dụng toán tử (&) để lấy địa chỉ của hàm. C++ sẽ tự động chuyển đổi một hàm thành con trỏ hàm nếu cần.
int funcA(); int funcB(); void funcC(); double funcD(int a); int main() { int(*fcnPtr)() = funcA; // con trỏ fcnPtr trỏ đến hàm funcA fcnPtr = funcB; // con trỏ fcnPtr có thể trỏ đến một hàm khác có cùng cấu trúc fcnPtr = funcC; // lỗi, kiểu trả về của con trỏ hàm và hàm không trùng nhau fcnPtr = funcD; // lỗi, kiểu trả về của con trỏ hàm và hàm không trùng nhau return 0; }
Chú ý: Cấu trúc (tham số và kiểu trả về) của con trỏ hàm phải khớp với cấu trúc của hàm.
Gọi một hàm bằng con trỏ hàm
Con trỏ hàm có thể được sử dụng để gọi trực tiếp hàm mà nó trỏ tới. Có hai cách để thực hiện lời gọi hàm bằng con trỏ hàm:
void swapNumber(int &a, int &b) { int temp = a; a = b; b = temp; } int main() { void(*ptrSwap) (int &, int &) = swapNumber; int a = 5, b = 10; cout << "Before: " << a << " " << b << endl; (*ptrSwap)(a, b); // gọi hàm tường minh cout << "After: " << a << " " << b << endl; ptrSwap(a, b); // hoặc gọi hàm ngầm định cout << "After: " << a << " " << b << endl; return 0; }
Chú ý: Các tham số mặc định của hàm không sử dụng được thông qua con trỏ hàm. Tham số mặc định được xác định tại thời điểm biên dịch chương trình, còn con trỏ hàm được sử dụng tại thời điểm chạy chương trình.
Truyền hàm vào hàm dưới dạng đối số
Con trỏ hàm cũng là một biến con trỏ, do đó chúng ta có thể sử dụng con trỏ hàm làm tham số cho một hàm khác. Khi tham số của hàm là con trỏ hàm, đối số chính là địa chỉ của hàm.
Ví dụ: viết chương trình thực hiện việc sắp xếp tăng hoặc giảm mảng 1 chiều các số nguyên.
void swapNumber(int &a, int &b) { int temp = a; a = b; b = temp; } void selectionSort(int *arr, int n, bool(*comparisonFcn)(int, int)) { int i, j, find_idx; for (i = 0; i < n - 1; i++) { find_idx = i; for (j = i + 1; j < n; j++) { if (comparisonFcn(arr[find_idx], arr[j])) { find_idx = j; } } swapNumber(arr[find_idx], arr[i]); } } bool asc(int a, int b) { return a > b; } bool desc(int a, int b) { return a < b; } int main() { int arr[] = { 64, 25, 12, 22, 11 }; int n = sizeof(arr) / sizeof(int); // Sắp xếp tăng selectionSort(arr, n, asc); cout << "Asc array: \n"; printArray(arr, n); // Sắp xếp giảm selectionSort(arr, n, desc); cout << "Desc array: \n"; printArray(arr, n); return 0; }
Chương trình trên sử dụng con trỏ hàm là tham số thứ 3 của hàm selectionSort(). Khi có thêm những nhu cầu sắp xếp khác nhau, chúng ta chỉ cần viết thêm hàm có điều kiện sắp xếp, và thay đổi đối số thứ 3 khi gọi hàm, mà không phải viết lại toàn bộ thuật toán bên trong hàm.
Đối số mặc định của tham số hàm kiểu con trỏ hàm
Tương tự như các kiểu dữ liệu cơ bản khác, chúng ta có thể cung cấp một đối số mặc định cho tham số hàm kiểu con trỏ hàm.
Ví dụ:
void selectionSort(int *arr, int n, bool(*comparisonFcn)(int, int) = asc); int main() { int arr[] = { 64, 25, 12, 22, 11 }; int n = sizeof(arr) / sizeof(int); // Sắp xếp tăng selectionSort(arr, n); // Sắp xếp giảm selectionSort(arr, n, desc); return 0; }
std::function trong C++11
C++11 cung cấp một cách thay thế cho việc sử dụng con trỏ hàm bằng cách sử dụng kiểu dữ liệu std::function thuộc thư viện
Ví dụ:
#include #include using namespace std; int funcA(); double funcB(int); void funcC(int &, int &); int main() { function fncPtrA = funcA; function fncPtrB = funcB; function fncPtrC = funcC; return 0; }
Khai báo con trỏ hàm với từ khóa auto trong C++11
Từ phiên bản C++11 trở về sau, từ khóa auto được dùng để tự động nhận dạng kiểu dữ liệu thông qua kiểu dữ liệu của giá trị khởi tạo ra nó. Vì vậy, từ khóa auto cũng có thể nhận dạng ra loại con trỏ hàm.
void swapNumber(int &a, int &b) { int temp = a; a = b; b = temp; } int main() { auto ptrSwap = swapNumber; int a = 5, b = 10; cout << "Before: " << a << " " << b << endl; ptrSwap(a, b); cout << "After: " << a << " " << b << endl; return 0; }
Chú ý: Từ khóa auto xác định kiểu dữ liệu tại thời gian biên dịch, nên nó không được sử dụng cho tham số hàm. Vì vậy việc sử dụng auto có phần bị hạn chế.
Kết luận
Qua bài học này, chúng ta đã tìm hiểu về Con trỏ hàm trong C++ (Function pointers) và vai trò quan trọng của chúng trong việc tham chiếu và gọi trực tiếp các hàm ngôn ngữ lập trình c' class='hover-show-link replace-link-2056'>trong ngôn ngữ lập trình c ++.
Con trỏ hàm thường được sử dụng khi chúng ta có các hàm có cùng kiểu trả về và danh sách tham số, hoặc khi bạn cần truyền một hàm cho hàm khác.
Con trỏ hàm có cú pháp khai báo khó nhớ và dễ gây ra lỗi nếu chưa nắm rõ, bạn có thể đơn giản hóa bằng cách sử dụng kiểu std::function của C++11.
Trong bài tiếp theo, chúng ta sẽ cùng tìm hiểu về ĐỆ QUY TRONG C++ (Recursion), một khái niệm quan trọng khác trong ngôn ngữ lập trình C++.
Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập - Thử thách - Không ngại khó”.