2012-01-23 48 views
14

thể trùng lặp:
What is the point of function pointers?whats việc sử dụng các con trỏ hàm?

Tôi cố gắng để hiểu nơi trong các tình huống thực tế con trỏ chức năng được sử dụng. và cũng có thể bất cứ ai xin vui lòng cho tôi một ví dụ thực tế, nơi chúng tôi phải vượt qua chức năng chính nó như là một đối số cho một chức năng khác.

+0

Đây là C#, nhưng nó cũng đi vào C/C++ và tính thực tiễn của con trỏ hàm: http://stackoverflow.com/questions/667410/the-benefits-of-using-function-pointers – Algorhythm

Trả lời

18

Con trỏ chức năng có thể hữu ích khi bạn muốn tạo callback mechanism và cần chuyển địa chỉ của hàm sang hàm khác.

Chúng cũng có thể hữu ích khi bạn muốn lưu trữ một mảng các hàm, để gọi động chẳng hạn.

8

Một cách sử dụng phổ biến là triển khai callback function.

Cố gắng sắp xếp thứ gì đó bằng cách sử dụng chức năng thư viện qsort. Đó là tham số cuối cùng là một con trỏ đến hàm so sánh được viết bởi bạn.

5

Điều đầu tiên đến với tâm trí của tôi như là một ứng dụng rất hữu ích là một nút. Hãy lấy đoạn mã sau:

int buttonID = CreateButton ("Click Me!", 100, 100, 200, 100, onClick); 

Điều này sẽ tạo một nút tại (100.100) với chiều rộng 200 và chiều cao 100. Mỗi khi bạn nhấp vào, nhấp vào được gọi.

Tôi sử dụng tính năng tương tự trong trình bao bọc API Windows cá nhân. Nó làm cho việc tạo các nút vv dễ dàng hơn rất nhiều.

3

Vâng, câu trả lời cổ phiếu số 1 là: qsort. Các thói quen so sánh rằng qsort sẽ sử dụng được thông qua như là một con trỏ hàm. Rất nhiều hàm “thuật toán chung” khác sẽ so sánh theo những cách tương tự; ví dụ. có lẽ một triển khai hashtable có thể chấp nhận hàm băm của bạn.

Bộ công cụ giao diện ngôn ngữ C và khung ứng dụng (ví dụ: Gnome/Gtk +/Glib) thường chấp nhận con trỏ chức năng dưới dạng "cuộc gọi lại" cho các sự kiện hẹn giờ hoặc giao diện người dùng. (EG: “gọi chức năng này bất cứ khi nào nút này được nhấp” hoặc “… bất cứ khi nào bộ đếm thời gian này hết hạn”)

Thực tế, hầu hết mã “OOP giống” hoặc “theo sự kiện” trong C sẽ chấp nhận con trỏ hàm cho một lý do tương tự.

4

Có hai mục đích sử dụng chủ yếu cho con trỏ hàm:

  • callbacks - được sử dụng để xử lý sự kiện, phân tích cú pháp chuyên môn, chức năng so sánh qua ...
  • plugin và phần mở rộng - các con trỏ chức năng được cung cấp bởi các plugin hoặc tiện ích mở rộng thư viện được thu thập theo functio tiêu chuẩn GetProcAddress, dlsym hoặc tương tự, lấy mã định danh hàm làm tên và trả về một con trỏ hàm. Hoàn toàn quan trọng đối với các API như OpenGL.
1

Bạn có thể sử dụng nó để chuyển một cuộc gọi lại đến một hàm. Ví dụ: bạn có thể muốn sắp xếp một mảng bằng cách sử dụng qsort().Chức năng này có một chức năng so sánh là một trong những đối số của nó, có nghĩa là bạn có thể sử dụng lệnh phân loại của riêng bạn:

// All odd numbers are before even numbers 
int cmpoddeven(const void *xp, const void *yp) { 
    int x = *((int*) xp); 
    int y = *((int*) yp); 
    if(x == y) 
    return 0; 
    if(x % 2 == y % 2) { 
    return (x < y ? -1 : 1); 
    if(x % 2 == 1) 
    return -1; 
    return 1; 
} 

int main() { 
    int array[] = {1, 2, 3, 4, 5}; 
    // calling qsort with cmpoddeven as the comparison function 
    qsort(array, 5, sizeof(int), &cmpoddeven); 
    // array == {1, 3, 5, 2, 4}; 
} 
1

Trong trường MOST, đó là về cơ bản cách C làm dependency inversion. Bài viết wiki nêu rõ:

A. Mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp. Cả hai nên phụ thuộc vào trừu tượng. B. Tóm tắt không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào trừu tượng.

Ví dụ cổ điển về qsort thực hiện điều này theo nghĩa là chức năng sắp xếp cấp cao hơn không phụ thuộc vào loại, kích thước hoặc phương pháp so sánh của dữ liệu cần sắp xếp. Vì vậy, nếu bạn qsort() một mảng int, chi tiết là sizeof(int) và việc so sánh triển khai của bạn. Sự trừu tượng là một mảng các phần tử có kích thước tùy ý và một hàm so sánh các phần tử của kiểu đó.

Xem thêm: Inversion of Control.

Tôi ngạc nhiên không ai đề cập đến pthread_create() làm ví dụ.

Cách sử dụng phổ biến nhất mà tôi có thể nghĩ là không thể khái quát hóa như đảo ngược phụ thuộc đang thực hiện kiểm soát luồng chuyển đổi trên các kiểu dữ liệu không thể chuyển đổi. Ví dụ, nếu bạn đã từng muốn bật một chuỗi, hãy tạo các mảng ánh xạ các chuỗi ký tự được sắp xếp tới các con trỏ hàm và thực hiện tìm kiếm nhị phân. Nó không phải là O (1) giống như một công tắc, nhưng tốt hơn là làm một cách mù quáng khi thực hiện strcmp() nếu bạn tìm thấy một trận đấu. Nhưng có lẽ không tốt hơn là tokenize chuỗi và sử dụng một switch thực tế.

4

Các thói quen gọi lại dường như là kịch bản phổ biến nhất được đưa ra từ trước đến nay. Tuy nhiên, có nhiều người khác ...

Máy hữu hạn của tiểu bang nơi các phần tử của mảng (đa chiều) cho biết quy trình xử lý/xử lý trạng thái tiếp theo. Điều này giữ định nghĩa của FSM ở một nơi (mảng).

Bật tính năng và tắt tính năng có thể được thực hiện bằng cách sử dụng con trỏ hàm. Bạn có thể có các tính năng mà bạn muốn bật hoặc tắt tính năng tương tự nhưng những điều khác biệt. Thay vì populating và cluttering code của bạn với if-else xây dựng các biến kiểm tra, bạn có thể mã nó để nó sử dụng một con trỏ hàm, và sau đó bạn có thể bật/tắt các tính năng bằng cách thay đổi/gán con trỏ hàm. Nếu bạn thêm các biến thể mới, bạn không phải theo dõi tất cả các trường hợp if-else hoặc switch (và rủi ro bị thiếu); thay vào đó bạn chỉ cần cập nhật con trỏ hàm của mình để bật tính năng mới hoặc tắt tính năng cũ.

Giảm sự lộn xộn mã Tôi đã chạm vào điều này trong ví dụ trước. Ví dụ như ...

switch (a) { 
case 0: 
    func0(); 
    break; 
case 1: 
    func1(); 
    break; 
case 2: 
    func2(); 
    break; 
case 3: 
    func3(); 
    break; 
default: 
    funcX(); 
    break; 
} 

có thể được đơn giản hóa để ...

/* This declaration may be off a little, but I am after the essence of the idea */ 
void (*funcArray)(void)[] = {func0, func1, func2, func3, funcX}; 
... appropriate bounds checking on 'a' ... 
funcArray[a](); 

Có nhiều hơn nữa. Hi vọng điêu nay co ich.

+1

FWIW, khai báo nên là 'void (* funcArray []) (void) = {func0, func1, func2 ...}; '(' funcArray' là một mảng các con trỏ tới hàm ...). Nhớ khai báo bắt chước sử dụng; nếu biểu thức trong mã là 'funcArray [a]()', thì khai báo sẽ được cấu trúc theo cùng một cách. –

+1

@JohnBode - Được đánh giá cao. Mặc dù tôi đã làm việc với C trong hơn 20 năm, tôi vẫn bị trộn lẫn với cú pháp con trỏ hàm. Thông thường tôi lựa chọn không cho con đường dễ dàng (cho tôi) của typedef'ing con trỏ hàm đầu tiên và sau đó tuyên bố một mảng của biến typedef'ed. – Sparky

+0

@Sparky Đồng ý về typedef. Nó làm cho các tình huống như mảng của con trỏ hàm hoặc con trỏ trỏ đến hàm con trỏ dễ đọc hơn nhiều. –

Các vấn đề liên quan