Hướng dẫn chi tiết về con trỏ hàm trong C++

6.7 Con trỏ hàm (chương này khá phức tạp) Con trỏ hàm trỏ đến hàm thay vì đối tượng. Giống như các con trỏ khác, con trỏ hàm phải trỏ đến kiểu dữ liệu cụ thể. Kiểu hàm được xác định bởi kiểu trả về và danh sách tham số, không phụ thuộc vào tên hàm. Ví dụ:

// So sánh độ dài của hai chuỗi string
bool lengthCompare(const string &, const string &);

Kiểu hàm ở đây là bool(const string&, const string&). Để khai báo con trỏ trỏ đến hàm này, thay tên hàm bằng con trỏ:

// pf trỏ đến hàm nhận 2 tham chiếu const string và trả về bool
bool(*pf)(const string&, const string&); // chưa khởi tạo

Bắt đầu từ tên khai báo, *pf cho thấy đây là con trỏ; phần bên phải là danh sách tham số xác định đây là hàm; bên trái cho biết kiểu trả về là bool. Do đó, pf là con trỏ hàm với kiểu tham số và kiểu trả về như trên. *Cặp ngoặc đơn hai bên pf là bắt buộc. Nếu bỏ qua sẽ tạo ra hàm trả về con trỏ bool:

// Khai báo hàm pf trả về con trỏ bool
bool *pf(const string &, const string &);

Sử dụng con trỏ hàm Khi sử dụng tên hàm như giá trị, nó tự động chuyển thành con trỏ hàm. Ví dụ:

pf = lengthCompare; // pf trỏ đến hàm lengthCompare
pf = &lengthCompare; // Gán địa chỉ tương đương

Có thể gọi hàm thông qua con trỏ mà không cần giải tham chiếu:

bool b1 = pf("hello","goodbye"); // Gọi hàm lengthCompare
bool b2 = (*pf)("hello","goodbye"); // Gọi tương đương
bool b3 = lengthCompare("hello","goodbye"); // Gọi trực tiếp

Không có quy tắc ép kiểu giữa các con trỏ hàm khác nhau. Có thể gán NULL hoặc biểu thức hằng số 0 để biểu thị con trỏ không trỏ đến hàm nào:

string::size_type sumLength(const string &,const string &);
bool cstringCompare(const char*,const char*);
pf = 0; // Đúng: con trỏ không trỏ đến hàm
pf = sumLength; // Sai: kiểu trả về không khớp
pf = cstringCompare; // Sai: kiểu tham số không khớp
pf = lengthCompare; // Đúng: kiểu hàm hoàn toàn khớp

Con trỏ hàm với hàm trùng tải Khi sử dụng hàm trùng tải, ngữ cảnh phải xác định rõ hàm cần chọn:

void ff(int*);
void ff(unsigned int);
void(*pfl)(unsigned int) = ff; // pfl trỏ đến ff(unsigned)

Compiler chọn hàm dựa trên kiểu con trỏ, phải khớp chính xác:

void(*pf2)(int) = ff; // Sai: không có hàm nào phù hợp
double(*pf3)(int*) = ff; // Sai: kiểu trả về không khớp

Tham số là con trỏ hàm Tương tự mảng, tham số không thể là kiểu hàm nhưng có thể là con trỏ hàm. Dạng khai báo:

// Tham số thứ ba là kiểu hàm, tự động chuyển thành con trỏ
void useBigger(const string & s1,const string &s2,
bool pf(const string &,const string &));
// Dạng tương đương
void useBigger(const string &s1,const string &s2,bool(*pf)(const string &,const string &));

Có thể truyền trực tiếp tên hàm:

useBigger(s1,s2,lengthCompare); // Tự động chuyển thành con trỏ

Sử dụng typedef và decltype để đơn giản hóa:

// Alias kiểu hàm
typedef bool Func(const string&,const string&);
typedef decltype(lengthCompare) Func2;
// Alias con trỏ hàm
typedef bool(*FuncP)(const string&,const string&);
typedef decltype(lengthCompare)*FuncP2;

Khởi tạo lại useBigger:

void useBigger(const string&,const string&,Func);
void useBigger(const string&,const string&,FuncP2);

Trả về con trỏ hàm Tương tự mảng, có thể trả về con trỏ hàm nhưng không thể trả về hàm. Ví dụ:

using F=int(int*,int); // F là kiểu hàm
using PF=int(*)(int*,int); // PF là con trỏ hàm

Khai báo hàm trả về con trỏ hàm:

PF f1(int); // Đúng
F f1(int); // Sai
F* f1(int); // Đúng

Hoặc:

int (*fl(int))(int*,int); // fl là hàm trả về con trỏ hàm

Khai báo kiểu trả về bằng auto/decltype Dùng decltype để đơn giản hóa:

decltype(sumLength)*getFcn(const string &); // Trả về con trỏ hàm

Lưu ý: decltype trả về kiểu hàm nên cần thêm * để tạo con trỏ.

Chi tiết về con trỏ hàm trong C++ Con trỏ hàm là kiểu con trỏ đặc biệt trỏ đến hàm. Dưới đây là cách định nghĩa, sử dụng và ứng dụng:

#include <iostream>
// Hàm tính tổng
int tong(int a, int b) 
{
    return a + b;
}
int main() 
{
    // Khai báo con trỏ hàm
    int (*ptr)(int, int);
    ptr = tong; // Gán hàm
    return 0;
}

Gọi hàm qua con trỏ:

#include <iostream>
int tong(int a, int b) 
{
    return a + b;
}
int main() 
{
    int (*ptr)(int, int) = tong;
    int ketqua = ptr(3,5); // Gọi hàm
    std::cout << ketqua;
    return 0;
}

Truyền con trỏ hàm làm tham số Tạo hàm linh hoạt với callback:

#include <iostream>
int tong(int a, int b) { return a + b; }
int hieu(int a, int b) { return a - b; }
int tinh(int (*p)(int, int), int x, int y) 
{
    return p(x, y);
}
int main() 
{
    std::cout << tinh(tong, 10,5) << std::endl;
    std::cout << tinh(hieu, 10,5) << std::endl;
    return 0;
}

Mảng con trỏ hàm Lưu trữ nhiều hàm trong mảng:

#include <iostream>
int tong(int a, int b) { return a + b; }
int hieu(int a, int b) { return a - b; }
int nhan(int a, int b) { return a * b; }
int main() 
{
    int (*ptr[3])(int, int) = {tong, hieu, nhan};
    for(int i=0; i<3; ++i) 
    {
        std::cout << ptr[i](10,5) << std::endl;
    }
    return 0;
}

Lưu ý quan trọng

  • Kiểu hàm phải khớp hoàn toàn với con trỏ
  • Kiểm tra null trước khi gọi: if (ptr != nullptr) { ... }

Hàm trả về con trỏ hàm Khai báo phức tạp:

int (*chonHam(int luaChon))(int, int) 
{
    if(luaChon == 1) return tong;
    else return hieu;
}

Đơn giản hóa bằng typedef Dùng typedef để dễ đọc:

typedef int (*HamToanHoc)(int, int);
HamToanHoc chonHam(int luaChon) 
{
    if(luaChon == 1) return tong;
    else return hieu;
}

Kiểm tra null Luôn kiểm tra trước khi sử dụng:

HamToanHoc ptr = chonHam(1);
if(ptr) std::cout << ptr(10,5);

Thẻ: con-trỏ-hàm callback C++ typedef decltype

Đăng vào ngày 20 tháng 6 lúc 16:40