Hàm gọi lại trong C ++

Callback Function C



Hàm gọi lại là một hàm, là một đối số, không phải là một tham số, trong một hàm khác. Chức năng còn lại có thể được gọi là chức năng chính. Vì vậy, hai chức năng có liên quan: chức năng chính và chức năng gọi lại chính nó. Trong danh sách tham số của hàm chính, có khai báo hàm gọi lại mà không có định nghĩa của nó, giống như khai báo đối tượng không có phép gán. Hàm chính được gọi với các đối số (trong hàm main ()). Một trong những đối số trong lệnh gọi hàm chính là định nghĩa hiệu quả của hàm gọi lại. Trong C ++, đối số này là một tham chiếu đến định nghĩa của hàm gọi lại; nó không phải là định nghĩa thực tế. Bản thân hàm gọi lại thực sự được gọi trong định nghĩa của hàm chính.

Hàm gọi lại cơ bản trong C ++ không đảm bảo hành vi không đồng bộ trong một chương trình. Hành vi không đồng bộ là lợi ích thực sự của lược đồ hàm gọi lại. Trong lược đồ hàm gọi lại không đồng bộ, kết quả của hàm chính phải được lấy cho chương trình trước khi nhận được kết quả của hàm gọi lại. Có thể làm điều này trong C ++; tuy nhiên, C ++ có một thư viện được gọi là tương lai để đảm bảo hành vi của lược đồ hàm gọi lại không đồng bộ.







Bài viết này giải thích lược đồ hàm gọi lại cơ bản. Rất nhiều trong số đó là với C ++ thuần túy. Liên quan đến việc gọi lại, hành vi cơ bản của thư viện tương lai cũng được giải thích. Kiến thức cơ bản về C ++ và các con trỏ của nó là cần thiết để hiểu bài viết này.



Nội dung bài viết

Lược đồ chức năng gọi lại cơ bản

Một lược đồ hàm gọi lại cần một hàm chính và chính hàm gọi lại. Khai báo của hàm gọi lại là một phần của danh sách tham số của hàm chính. Định nghĩa của hàm gọi lại được chỉ ra trong lệnh gọi hàm của hàm chính. Hàm gọi lại thực sự được gọi trong định nghĩa của hàm chính. Chương trình sau minh họa điều này:



#bao gồm

sử dụng không gian têngiờ;



NSmainFn(charch[],NS (*ptr)(NS))

{

NSid1= 1;

NSid2= 2;

NSthông thường= (*ptr)(id2);

Giá cả<<'chức năng chính:'<<id1<<''<<ch<<''<<thông thường<<' ';

trở lạiid1;

}


NScb(NSiden)

{

Giá cả<<'chức năng gọi lại'<<' ';

trở lạiiden;

}


NSchủ chốt()

{

NS (*ptr)(NS) = &cb;

charkhông[] = 'và';

mainFn(cha, cb);



trở lại 0;

}

Đầu ra là:





chức năng gọi lại

chức năng chính: 1 2

Hàm chính được xác định bởi precisionFn (). Hàm gọi lại được xác định bởi cb (). Hàm gọi lại được định nghĩa bên ngoài hàm chính nhưng thực sự được gọi bên trong hàm chính.

Lưu ý khai báo hàm gọi lại như một tham số trong danh sách tham số của khai báo hàm chính. Khai báo của hàm gọi lại là int (* ptr) (int). Lưu ý biểu thức hàm gọi lại, giống như một lệnh gọi hàm, trong định nghĩa của hàm chính; bất kỳ đối số nào cho lời gọi hàm gọi lại được chuyển vào đó. Câu lệnh cho lệnh gọi hàm này là:



NSthông thường= (*ptr)(id2);

Trong đó id2 là một đối số. ptr là một phần của tham số, một con trỏ, sẽ được liên kết với tham chiếu của hàm gọi lại trong hàm main ().

Lưu ý biểu thức:

NS (*ptr)(NS) = &cb;

Trong hàm main (), liên kết phần khai báo (không có định nghĩa) của hàm gọi lại với tên của định nghĩa của cùng một hàm gọi lại.

Hàm chính được gọi, trong hàm main (), như sau:

mainFn(cha, cb);

Trong đó cha là một chuỗi và cb là tên của hàm gọi lại mà không có bất kỳ đối số nào của nó.

Hành vi đồng bộ của chức năng gọi lại

Hãy xem xét chương trình sau:

#bao gồm

sử dụng không gian têngiờ;



vô hiệumainFn(vô hiệu (*ptr)())

{

Giá cả<<'chức năng chính'<<' ';

(*ptr)();

}


vô hiệucb()

{

Giá cả<<'chức năng gọi lại'<<' ';

}


vô hiệufn()

{

Giá cả<<'đã xem'<<' ';

}


NSchủ chốt()

{

vô hiệu (*ptr)() = &cb;

mainFn(cb);

fn();



trở lại 0;

}

Đầu ra là:

chức năng chính

chức năng gọi lại

đã xem

Có một chức năng mới ở đây. Tất cả những gì chức năng mới làm là hiển thị kết quả đầu ra. Trong hàm main (), hàm chính được gọi, sau đó hàm mới, fn () được gọi. Kết quả đầu ra cho thấy rằng mã cho hàm chính đã được thực thi, sau đó mã cho hàm gọi lại được thực thi và cuối cùng là mã cho hàm fn () đã được thực thi. Đây là hành vi đồng bộ (đơn luồng).

Nếu đó là hành vi không đồng bộ, khi ba đoạn mã được gọi theo thứ tự, đoạn mã đầu tiên có thể được thực thi, thay vào đó là việc thực thi đoạn mã thứ ba, trước khi đoạn mã thứ hai được thực thi.

Vâng, hàm, fn () có thể được gọi từ bên trong định nghĩa của hàm chính, thay vì từ bên trong hàm main (), như sau:

#bao gồm

sử dụng không gian têngiờ;



vô hiệufn()

{

Giá cả<<'đã xem'<<' ';

}


vô hiệumainFn(vô hiệu (*ptr)())

{

Giá cả<<'chức năng chính'<<' ';

fn();

(*ptr)();

}


vô hiệucb()

{

Giá cả<<'chức năng gọi lại'<<' ';

}


NSchủ chốt()

{

vô hiệu (*ptr)() = &cb;

mainFn(cb);



trở lại 0;

}

Đầu ra là:

chức năng chính

đã xem

chức năng gọi lại

Đây là sự bắt chước của hành vi không đồng bộ. Nó không phải là hành vi không đồng bộ. Nó vẫn là hành vi đồng bộ.

Ngoài ra, thứ tự thực hiện đoạn mã của hàm chính và đoạn mã của hàm gọi lại có thể được hoán đổi trong định nghĩa của hàm chính. Chương trình sau minh họa điều này:

#bao gồm

sử dụng không gian têngiờ;



vô hiệumainFn(vô hiệu (*ptr)())

{

(*ptr)();

Giá cả<<'chức năng chính'<<' ';

}


vô hiệucb()

{

Giá cả<<'chức năng gọi lại'<<' ';

}


vô hiệufn()

{

Giá cả<<'đã xem'<<' ';

}


NSchủ chốt()

{

vô hiệu (*ptr)() = &cb;

mainFn(cb);

fn();



trở lại 0;

}

Đầu ra bây giờ là,

chức năng gọi lại

chức năng chính

đã xem

Đây cũng là một sự bắt chước của hành vi không đồng bộ. Nó không phải là hành vi không đồng bộ. Nó vẫn là hành vi đồng bộ. Hành vi không đồng bộ thực sự có thể được giải thích trong phần tiếp theo hoặc với thư viện, trong tương lai.

Hành vi không đồng bộ với chức năng gọi lại

Mã giả cho lược đồ hàm gọi lại không đồng bộ cơ bản là:

loại đầu ra;

gõ cb(loại đầu ra)

{

//các câu lệnh

}


gõ gốcFn(gõ đầu vào, gõ cb(loại đầu ra))

{

//các câu lệnh

}

Lưu ý các vị trí của dữ liệu đầu vào và đầu ra ở các vị trí khác nhau của mã giả. Đầu vào của hàm gọi lại là đầu ra của nó. Các tham số của hàm chính là tham số đầu vào cho mã chung và tham số cho hàm gọi lại. Với lược đồ này, một hàm thứ ba có thể được thực thi (được gọi) trong hàm main () trước khi đầu ra của hàm gọi lại được đọc (vẫn còn trong hàm main ()). Đoạn mã sau minh họa điều này:

#bao gồm

sử dụng không gian têngiờ;

char *đầu ra;


vô hiệucb(charngoài[])

{

đầu ra=ngoài;

}



vô hiệumainFn(charđầu vào[],vô hiệu (*ptr)(char[năm mươi]))

{

(*ptr)(đầu vào);

Giá cả<<'chức năng chính'<<' ';

}


vô hiệufn()

{

Giá cả<<'đã xem'<<' ';

}


NSchủ chốt()

{

charđầu vào[] = 'chức năng gọi lại';

vô hiệu (*ptr)(char[]) = &cb;

mainFn(đầu vào, cb);

fn();

Giá cả<<đầu ra<<' ';



trở lại 0;

}

Đầu ra của chương trình là:

chức năng chính

đã xem

chức năng gọi lại

Trong mã cụ thể này, dữ liệu đầu ra và đầu vào giống nhau. Kết quả của lệnh gọi hàm thứ ba trong hàm main () đã được hiển thị trước kết quả của hàm gọi lại. Hàm gọi lại được thực thi, kết thúc và gán kết quả (giá trị) của nó cho biến, đầu ra, cho phép chương trình tiếp tục mà không có sự can thiệp của nó. Trong hàm main (), đầu ra của hàm gọi lại đã được sử dụng (đọc và hiển thị) khi nó cần thiết, dẫn đến hành vi không đồng bộ cho toàn bộ lược đồ.

Đây là cách đơn luồng để có được hành vi không đồng bộ của hàm gọi lại với C ++ thuần túy.

Sử dụng cơ bản của Thư viện tương lai

Ý tưởng của lược đồ hàm gọi lại không đồng bộ là hàm chính trả về trước khi hàm gọi lại trả về. Điều này đã được thực hiện gián tiếp, hiệu quả, trong đoạn mã trên.

Lưu ý từ đoạn mã trên rằng hàm gọi lại nhận đầu vào chính cho mã và tạo ra đầu ra chính cho mã. Thư viện C ++, tương lai, có một hàm được gọi là sync (). Đối số đầu tiên của hàm này là tham chiếu hàm gọi lại; đối số thứ hai là đầu vào cho hàm gọi lại. Hàm sync () trả về mà không cần đợi quá trình thực thi hàm gọi lại hoàn tất nhưng cho phép hàm gọi lại hoàn tất. Điều này cung cấp hành vi không đồng bộ. Trong khi hàm gọi lại tiếp tục thực thi, vì hàm sync () đã trả về nên các câu lệnh bên dưới nó tiếp tục thực thi. Điều này giống như hành vi không đồng bộ lý tưởng.

Chương trình trên đã được viết lại dưới đây, có tính đến thư viện trong tương lai và hàm sync () của nó:

#bao gồm

#bao gồm

#bao gồm

sử dụng không gian têngiờ;

Tương lai<dây>đầu ra;

chuỗi cb(chuỗi sọc)

{

trở lạiStri;

}



vô hiệumainFn(đầu vào chuỗi)

{

đầu ra=không đồng bộ(cb, đầu vào);

Giá cả<<'chức năng chính'<<' ';

}


vô hiệufn()

{

Giá cả<<'đã xem'<<' ';

}


NSchủ chốt()

{

đầu vào chuỗi=dây('chức năng gọi lại');

mainFn(đầu vào);

fn();

chuỗi ret=đầu ra.hiểu được(); // đợi callback trả về nếu cần

Giá cả<<đúng<<' ';



trở lại 0;

}

Hàm sync () cuối cùng lưu trữ đầu ra của hàm gọi lại vào đối tượng tương lai. Đầu ra mong đợi có thể nhận được trong hàm main (), sử dụng hàm thành viên get () của đối tượng tương lai.

Phần kết luận

Hàm gọi lại là một hàm, là một đối số, không phải là một tham số, trong một hàm khác. Một lược đồ hàm gọi lại cần một hàm chính và chính hàm gọi lại. Khai báo của hàm gọi lại là một phần của danh sách tham số của hàm chính. Định nghĩa của hàm gọi lại được chỉ ra trong lệnh gọi hàm của hàm chính (trong hàm main ()). Hàm gọi lại thực sự được gọi trong định nghĩa của hàm chính.

Một lược đồ hàm gọi lại không nhất thiết là không đồng bộ. Để chắc chắn rằng lược đồ hàm gọi lại là không đồng bộ, hãy tạo đầu vào chính cho mã, đầu vào cho hàm gọi lại; tạo đầu ra chính của mã, đầu ra của hàm gọi lại; lưu trữ đầu ra của hàm gọi lại trong một biến hoặc cấu trúc dữ liệu. Trong hàm main (), sau khi gọi hàm chính, hãy thực thi các câu lệnh khác của ứng dụng. Khi đầu ra của hàm gọi lại là cần thiết, trong hàm main (), hãy sử dụng (đọc và hiển thị) nó ở đó và sau đó.