Làm thế nào để bạn vượt qua một chức năng như là một tham số trong C?


616

Tôi muốn tạo một hàm thực hiện một hàm được truyền bởi tham số trên một tập hợp dữ liệu. Làm thế nào để bạn vượt qua một chức năng như là một tham số trong C?


12
Nếu bạn đang sử dụng các hàm / mảng làm biến, LUÔN LUÔN sử dụng typedef.
Vịt Mooing

3
Chúng tôi gọi nó là Con trỏ hàm
AminM

Tên hàm phải là một nhà thơ cho chức năng. Hầu hết mọi người học C bao gồm qsort sớm hay muộn mà chính xác điều này?
mckenzm

5
@MooingDuck Tôi không đồng ý với đề xuất của bạn và đề xuất của bạn thiếu lý do để hỗ trợ cho kết luận. Cá nhân tôi thích không bao giờ sử dụng typedef trên các con trỏ hàm và tôi nghĩ rằng nó làm cho mã rõ ràng hơn và dễ đọc hơn.
andrewrk

2
@andrewrk: Bạn thích void funcA(void(*funcB)(int))void (*funcA())()đến typedef void funcB(); void funcA(funcB)funcB funcA()? Tôi không thấy mặt trái.
Vịt Mooing

Câu trả lời:


747

Tờ khai

Một nguyên mẫu cho một hàm có tham số hàm trông như sau:

void func ( void (*f)(int) );

Điều này nói rằng tham số fsẽ là một con trỏ tới một hàm có voidkiểu trả về và lấy một inttham số duy nhất . Hàm sau ( print) là một ví dụ về hàm có thể được truyền vào funcdưới dạng tham số vì đây là loại thích hợp:

void print ( int x ) {
  printf("%d\n", x);
}

Chức năng gọi

Khi gọi một hàm với tham số hàm, giá trị được truyền phải là con trỏ tới hàm. Sử dụng tên của hàm (không có dấu ngoặc đơn) cho việc này:

func(print);

sẽ gọi func, chuyển chức năng in cho nó.

Cơ quan chức năng

Như với bất kỳ tham số nào, funcgiờ đây có thể sử dụng tên của tham số trong thân hàm để truy cập giá trị của tham số. Hãy nói rằng funcsẽ áp dụng chức năng mà nó được truyền cho các số 0-4. Trước tiên, hãy xem xét vòng lặp sẽ trông như thế nào khi gọi in trực tiếp:

for ( int ctr = 0 ; ctr < 5 ; ctr++ ) {
  print(ctr);
}

funckhai báo tham số nói rằng đó flà tên của một con trỏ tới hàm mong muốn, trước tiên chúng ta nhớ lại rằng nếu flà một con trỏ thì đó *flà thứ ftrỏ đến (tức là hàm printtrong trường hợp này). Do đó, chỉ cần thay thế mọi lần xuất hiện của bản in trong vòng lặp ở trên bằng *f:

void func ( void (*f)(int) ) {
  for ( int ctr = 0 ; ctr < 5 ; ctr++ ) {
    (*f)(ctr);
  }
}

Nguồn


40
Trong ví dụ mã đầu tiên và cuối cùng của bạn, * không bắt buộc. Cả định nghĩa tham số hàm và lệnh fgọi hàm có thể thực hiện fgiống như không có *. Nó có thể là một ý tưởng tốt để làm điều đó như bạn làm, để làm rõ rằng tham số f là một con trỏ hàm. Nhưng nó làm tổn thương khả năng đọc khá thường xuyên.
Gauthier

5
Xem [c99, 6.9.1 §14] để biết ví dụ. Tất cả đều đúng tất nhiên, tôi chỉ muốn đề cập đến sự thay thế.
Gauthier

6
Có thật không? Câu trả lời được xếp hạng cao nhất không tạo một tham chiếu duy nhất cho việc sử dụng một typedefcon trỏ hàm? Xin lỗi, phải bỏ phiếu.
Jonathon Reinhart

3
@JonathonReinhart, điều gì sẽ là lợi thế với 'typedef' apprach? Phiên bản này trông sạch sẽ hơn nhiều và cũng thiếu các tuyên bố thêm. khởi đầu khá nhiều ở đây.
Abhinav Gauniyal

3
@JonathonReinhart phát hiện tốt; cần lưu ý một cách rõ ràng rằng con trỏ typedefs làm xáo trộn mã và do đó không nên được sử dụng.
MM

129

Câu hỏi này đã có câu trả lời cho việc xác định các con trỏ hàm, tuy nhiên chúng có thể trở nên rất lộn xộn, đặc biệt nếu bạn sẽ chuyển chúng xung quanh ứng dụng của bạn. Để tránh sự khó chịu này, tôi khuyên bạn nên gõ con trỏ hàm vào một cái gì đó dễ đọc hơn. Ví dụ.

typedef void (*functiontype)();

Khai báo một hàm trả về void và không có đối số. Để tạo một con trỏ hàm cho loại này, bây giờ bạn có thể làm:

void dosomething() { }

functiontype func = &dosomething;
func();

Đối với một hàm trả về một int và lấy một char bạn sẽ làm

typedef int (*functiontype2)(char);

và sử dụng nó

int dosomethingwithchar(char a) { return 1; }

functiontype2 func2 = &dosomethingwithchar
int result = func2('a');

Có những thư viện có thể giúp biến con trỏ hàm thành các kiểu dễ đọc. Các chức năng thúc đẩy thư viện là rất tốt và rất đáng nỗ lực!

boost::function<int (char a)> functiontype2;

đẹp hơn rất nhiều so với ở trên.


4
Nếu bạn muốn "biến một con trỏ hàm thành một loại", bạn không cần thư viện boost. Chỉ cần sử dụng typedef; nó đơn giản hơn và không yêu cầu bất kỳ thư viện bổ sung.
wizzwizz4

72

Vì C ++ 11, bạn có thể sử dụng thư viện chức năng để thực hiện việc này một cách ngắn gọn và chung chung. Cú pháp là, vd

std::function<bool (int)>

trong đó boolkiểu trả về ở đây của hàm một đối số có đối số đầu tiên là kiểu int.

Tôi đã bao gồm một chương trình ví dụ dưới đây:

// g++ test.cpp --std=c++11
#include <functional>

double Combiner(double a, double b, std::function<double (double,double)> func){
  return func(a,b);
}

double Add(double a, double b){
  return a+b;
}

double Mult(double a, double b){
  return a*b;
}

int main(){
  Combiner(12,13,Add);
  Combiner(12,13,Mult);
}

Tuy nhiên, đôi khi, sử dụng chức năng mẫu sẽ thuận tiện hơn:

// g++ test.cpp --std=c++11

template<class T>
double Combiner(double a, double b, T func){
  return func(a,b);
}

double Add(double a, double b){
  return a+b;
}

double Mult(double a, double b){
  return a*b;
}

int main(){
  Combiner(12,13,Add);
  Combiner(12,13,Mult);
}

43
Câu hỏi là về C; C ++ không áp dụng ở đây.
Siêu mèo

16
Câu hỏi cho C ++ ( stackoverflow.com/questions/6339970/ - ) được đề cập ở đây, vì vậy tôi nghĩ rằng câu trả lời này được đưa ra.
mr_T

8
Có lẽ câu hỏi cho C ++ không nên được đề cập ở đây vì câu hỏi này là C.
Michael Fulton

5
Câu hỏi này xuất hiện đầu tiên khi tôi tìm kiếm "hàm gọi c ++ làm tham số", vì vậy câu trả lời này đang phục vụ tốt mục đích của nó ở đây bất kể.
ufoxDan

25

Truyền địa chỉ của hàm làm tham số cho hàm khác như hiển thị bên dưới

#include <stdio.h>

void print();
void execute(void());

int main()
{
    execute(print); // sends address of print
    return 0;
}

void print()
{
    printf("Hello!");
}

void execute(void f()) // receive address of print
{
    f();
}

Ngoài ra chúng ta có thể truyền hàm dưới dạng tham số bằng cách sử dụng con trỏ hàm

#include <stdio.h>

void print();
void execute(void (*f)());

int main()
{
    execute(&print); // sends address of print
    return 0;
}

void print()
{
    printf("Hello!");
}

void execute(void (*f)()) // receive address of print
{
    f();
}

1
Câu trả lời rất hay, với toàn bộ khối mã (thay vì cắt mọi thứ thành một mớ hỗn độn khó hiểu). Bạn có thể giải thích về sự khác biệt của cả hai kỹ thuật?
Rafael Eyng

5

Các hàm có thể được "chuyển" làm con trỏ hàm, theo ISO C11 6.7.6.3p8: " Một khai báo của một tham số là '' kiểu trả về hàm '' sẽ được điều chỉnh thành '' con trỏ tới kiểu trả về của hàm '' , như trong 6.3 .2.1. ". Ví dụ: cái này:

void foo(int bar(int, int));

tương đương với điều này:

void foo(int (*bar)(int, int));

Tài liệu bạn đang trích dẫn từ đâu? Bất kỳ liên kết đến nó?
Rafael Eyng

1
Tôi đang trích dẫn từ tiêu chuẩn ISO C11.
doppelheathen


-2

Nó không thực sự là một chức năng, nhưng nó là một đoạn mã được bản địa hóa. Tất nhiên nó không vượt qua mã chỉ là kết quả. Nó sẽ không hoạt động nếu được chuyển đến một bộ điều phối sự kiện để được chạy sau đó (vì kết quả được tính ngay bây giờ và không phải khi sự kiện xảy ra). Nhưng nó sẽ bản địa hóa mã của bạn vào một nơi nếu đó là tất cả những gì bạn đang cố gắng làm.

#include <stdio.h>

int IncMultInt(int a, int b)
{
    a++;
    return a * b;
}

int main(int argc, char *argv[])

{
    int a = 5;
    int b = 7;

    printf("%d * %d = %d\n", a, b, IncMultInt(a, b));

    b = 9;

    // Create some local code with it's own local variable
    printf("%d * %d = %d\n", a, b,  ( { int _a = a+1; _a * b; } ) );

    return 0;
}

Bạn chỉ đang gọi một chức năng. Làm thế nào bạn sẽ vượt qua một số chức năng khác thay IncMultIntthế?
Tejas Pendse
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.