Làm thế nào để giải thích cuộc gọi lại bằng tiếng Anh? Làm thế nào khác với họ gọi một chức năng từ một chức năng khác?


342

Làm thế nào để giải thích các cuộc gọi lại bằng tiếng Anh? Chúng khác với việc gọi một chức năng từ một chức năng khác lấy một số ngữ cảnh từ chức năng gọi như thế nào? Làm thế nào sức mạnh của họ có thể được giải thích cho một lập trình viên mới làm quen?


1
Tôi tin rằng tên khoa học cho nó là phong cách tiếp tục. Bạn có thể tìm kiếm cái này trên wiki.
ming_codes

1
Có một số câu trả lời tốt để một câu hỏi giống hệt nhau trên Quora cũng
DBR


Giải thích tốt nhất về cuộc gọi lại mà tôi đã từng tìm thấy youtube.com/watch?v=xHneyv38Jro
Sameer Sinha

Câu trả lời:


114

Thông thường một ứng dụng cần thực thi các chức năng khác nhau dựa trên bối cảnh / trạng thái của nó. Đối với điều này, chúng tôi sử dụng một biến trong đó chúng tôi sẽ lưu trữ thông tin về hàm sẽ được gọi. Theo nhu cầu của nó, ứng dụng sẽ đặt biến này với thông tin về hàm sẽ được gọi và sẽ gọi hàm sử dụng cùng một biến.

Trong javascript, ví dụ dưới đây. Ở đây chúng ta sử dụng đối số phương thức như một biến trong đó chúng ta lưu trữ thông tin về hàm.

function processArray(arr, callback) {
    var resultArr = new Array(); 
    for (var i = arr.length-1; i >= 0; i--)
        resultArr[i] = callback(arr[i]);
    return resultArr;
}

var arr = [1, 2, 3, 4];
var arrReturned = processArray(arr, function(arg) {return arg * -1;});
// arrReturned would be [-1, -2, -3, -4]

18
Trong khi về mặt kỹ thuật đây là một cuộc gọi lại, lời giải thích đưa ra âm thanh không rõ ràng từ một con trỏ hàm chung. Nó sẽ giúp bao gồm một số lý do cho lý do tại sao một cuộc gọi lại có thể được sử dụng.
Eric

4
Tôi không hiểu câu trả lời này. Nó có thể là về giải thích nhiều hơn mã?
Abhishek Singh

Tại sao chúng ta không thể làm những gì chúng ta làm trong hàm gọi lại (the function(arg)) trong processArray(arr,callback)hàm
Abhi

1
@JoSmo bạn đúng một phần. Truyền một biến + hàm gọi lại dưới dạng tham số lấy kết quả được tạo với biến bằng hàm ban đầu và truyền vào hàm gọi lại để xử lý thêm. ví dụ: func1 (a, callback_func) {v = a + 1} và có chức năng gọi lại được xác định trước: callback_func (v) {return v + 1;} điều này sẽ tăng a lên 2, vì vậy nếu bạn chuyển đối số của số nguyên 4 trong Tham số "a", kết quả gọi lại sẽ trả về 6. Đọc callbackhell.com nguồn tốt nhất tôi tìm thấy.
Dung

Câu trả lời này không rõ ràng. Có thể đơn giản và rõ ràng hơn!
Arjun Kalidas

545

Tôi sẽ cố gắng để giữ cho cái chết đơn giản này. "Gọi lại" là bất kỳ chức năng nào được gọi bởi một chức năng khác lấy chức năng đầu tiên làm tham số. Rất nhiều thời gian, "gọi lại" là một chức năng được gọi khi có điều gì đó xảy ra. Rằng một cái gì đó có thể được gọi là một "sự kiện" trong lập trình viên nói.

Hãy tưởng tượng kịch bản này: bạn đang mong đợi một gói trong một vài ngày. Gói là một món quà cho hàng xóm của bạn. Do đó, một khi bạn nhận được gói, bạn muốn nó mang đến cho hàng xóm. Bạn ra khỏi thị trấn, và vì vậy bạn để lại hướng dẫn cho người phối ngẫu của bạn.

Bạn có thể bảo họ lấy gói hàng và mang đến cho hàng xóm. Nếu người phối ngẫu của bạn ngu ngốc như một chiếc máy tính, họ sẽ ngồi ở cửa và đợi gói hàng cho đến khi nó đến (KHÔNG LÀM BẤT CỨ THỨ NÀO) và sau đó một khi họ đến, họ sẽ mang nó đến hàng xóm. Nhưng có một cách tốt hơn. Nói với vợ / chồng của bạn rằng ONCE họ nhận được gói hàng, họ nên mang nó qua hàng xóm. Sau đó, họ có thể đi về cuộc sống bình thường UNTIL họ nhận được gói.

Trong ví dụ của chúng tôi, việc nhận gói hàng là "sự kiện" và việc mang nó đến hàng xóm là "gọi lại". Người phối ngẫu của bạn "chạy" hướng dẫn của bạn để chỉ mang gói hàng đến khi gói hàng đến. Tốt hơn nhiều!

Kiểu suy nghĩ này là hiển nhiên trong cuộc sống hàng ngày, nhưng máy tính không có cùng một loại suy nghĩ thông thường. Xem xét cách lập trình viên thường ghi vào một tệp:

fileObject = open(file)
# now that we have WAITED for the file to open, we can write to it
fileObject.write("We are writing to the file.")
# now we can continue doing the other, totally unrelated things our program does

Ở đây, chúng tôi chờ để mở tệp, trước khi chúng tôi viết thư cho nó. Điều này "chặn" luồng thực thi và chương trình của chúng tôi không thể thực hiện bất kỳ điều nào khác mà nó có thể cần phải làm! Điều gì xảy ra nếu chúng ta có thể làm điều này thay vào đó:

# we pass writeToFile (A CALLBACK FUNCTION!) to the open function
fileObject = open(file, writeToFile)
# execution continues flowing -- we don't wait for the file to be opened
# ONCE the file is opened we write to it, but while we wait WE CAN DO OTHER THINGS!

Hóa ra chúng tôi làm điều này với một số ngôn ngữ và khung. Nó thật tuyệt! Hãy xem Node.js để có được một số thực hành thực tế với kiểu suy nghĩ này.


6
Điều này là chính xác, nhưng không bao gồm tất cả các trường hợp sử dụng phổ biến cho các cuộc gọi lại. Thường thì bạn sử dụng các cuộc gọi lại khi bạn cần gọi một hàm với các đối số sẽ được xử lý trong quá trình của một hàm khác. Ví dụ trong PHP Array_filter () và Array_map () nhận các cuộc gọi lại được gọi trong một vòng lặp.
Haralan Dobrev

2
Là ví dụ ghi tập tin thích hợp? Nó dường như đưa ra các giả định về cách làm openviệc. Thật hợp lý khi opencó thể chặn nội bộ trong khi chờ HĐH thực hiện phép thuật đen của mình, khi đó cuộc gọi lại được thực thi. Không có sự khác biệt trong kết quả trong trường hợp như vậy.
Kenneth K.

23
Giải thích tốt đẹp, nhưng tôi bối rối một chút. Callback có đa luồng không?
Premraj

1
Ví dụ tuyệt vời! Đã tìm kiếm một tiếng Anh đơn giản ở khắp mọi nơi và đây là lần đầu tiên tôi tìm thấy cho đến nay :)
ChristoKiwi

1
Điều gì làm cho hàm mở trong ví dụ của bạn không chặn? mở vẫn có thể chặn dòng thực thi ..
Koray Tugay

82

Làm thế nào để giải thích các cuộc gọi lại bằng tiếng Anh?

Nói một cách dễ hiểu, chức năng gọi lại giống như một Công nhân "gọi lại" cho Người quản lý của mình khi anh ta đã hoàn thành Nhiệm vụ .

Chúng khác với việc gọi một chức năng từ một chức năng khác lấy một số ngữ cảnh từ chức năng gọi như thế nào?

Đúng là bạn đang gọi một chức năng từ một chức năng khác, nhưng điều quan trọng là cuộc gọi lại được coi như một Đối tượng, vì vậy bạn có thể thay đổi Chức năng nào để gọi dựa trên trạng thái của hệ thống (như Mẫu thiết kế chiến lược).

Làm thế nào sức mạnh của họ có thể được giải thích cho một lập trình viên mới làm quen?

Sức mạnh của các cuộc gọi lại có thể dễ dàng được nhìn thấy trong các trang web kiểu AJAX cần lấy dữ liệu từ máy chủ. Tải xuống dữ liệu mới có thể mất một thời gian. Nếu không có cuộc gọi lại, toàn bộ Giao diện người dùng của bạn sẽ "đóng băng" trong khi tải xuống dữ liệu mới hoặc bạn sẽ cần làm mới toàn bộ trang thay vì chỉ là một phần của nó. Với một cuộc gọi lại, bạn có thể chèn một hình ảnh "bây giờ đang tải" và thay thế nó bằng dữ liệu mới sau khi nó được tải.

Một số mã không có cuộc gọi lại:

function grabAndFreeze() {
    showNowLoading(true);
    var jsondata = getData('http://yourserver.com/data/messages.json');
    /* User Interface 'freezes' while getting data */
    processData(jsondata);
    showNowLoading(false);
    do_other_stuff(); // not called until data fully downloaded
}

function processData(jsondata) { // do something with the data
   var count = jsondata.results ? jsondata.results.length : 0;
   $('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
   $('#results_messages').html(jsondata.results || '(no new messages)');
}

Với cuộc gọi lại:

Dưới đây là một ví dụ với một cuộc gọi lại, sử dụng getJSON của jQuery :

function processDataCB(jsondata) { // callback: update UI with results
   showNowLoading(false);
   var count = jsondata.results ? jsondata.results.length : 0;
   $('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
   $('#results_messages').html(jsondata.results || '(no new messages)');
}

function grabAndGo() { // and don't freeze
    showNowLoading(true);
    $('#results_messages').html(now_loading_image);
    $.getJSON("http://yourserver.com/data/messages.json", processDataCB);
    /* Call processDataCB when data is downloaded, no frozen User Interface! */
    do_other_stuff(); // called immediately
}

Có đóng cửa:

Thông thường, cuộc gọi lại cần truy cập statetừ chức năng gọi bằng cách sử dụng a closure, giống như Công nhân cần lấy thông tin từ Người quản lý trước khi anh ta có thể hoàn thành Nhiệm vụ của mình . Để tạo closure, bạn có thể nội tuyến hàm để nó nhìn thấy dữ liệu trong ngữ cảnh gọi:

/* Grab messages, chat users, etc by changing dtable. Run callback cb when done.*/
function grab(dtable, cb) { 
    if (null == dtable) { dtable = "messages"; }
    var uiElem = "_" + dtable;
    showNowLoading(true, dtable);
    $('#results' + uiElem).html(now_loading_image);
    $.getJSON("http://yourserver.com/user/"+dtable+".json", cb || function (jsondata) {
       // Using a closure: can "see" dtable argument and uiElem variables above.
       var count = jsondata.results ? jsondata.results.length : 0, 
           counterMsg = ['Fetched', count, 'new', dtable].join(' '),
           // no new chatters/messages/etc
           defaultResultsMsg = ['(no new ', dtable, ')'].join(''); 
       showNowLoading(false, dtable);
       $('#counter' + uiElem).text(counterMsg);
       $('#results'+ uiElem).html(jsondata.results || defaultResultsMsg);
    });
    /* User Interface calls cb when data is downloaded */

    do_other_stuff(); // called immediately
}

Sử dụng:

// update results_chatters when chatters.json data is downloaded:
grab("chatters"); 
// update results_messages when messages.json data is downloaded
grab("messages"); 
// call myCallback(jsondata) when "history.json" data is loaded:
grab("history", myCallback); 

Khép kín

Cuối cùng, đây là một định nghĩa closuretừ Douglas Crockford :

Các chức năng có thể được xác định bên trong các chức năng khác. Hàm bên trong có quyền truy cập vào các vars và tham số của hàm ngoài. Nếu một tham chiếu đến một chức năng bên trong tồn tại (ví dụ, như một chức năng gọi lại), các vars của chức năng bên ngoài cũng tồn tại.

Xem thêm:


12
+1. Đoạn đầu tiên là đập vào tiền . Tuy nhiên, phần còn lại của nó đi vào thuật ngữ khoa học máy tính khá nhanh.
TarkaDaal

41

Tôi choáng váng khi thấy rất nhiều người thông minh không nhấn mạnh vào thực tế rằng từ "gọi lại" đã được sử dụng theo hai cách không nhất quán.

Cả hai cách đều liên quan đến việc tùy chỉnh một chức năng bằng cách chuyển chức năng bổ sung (định nghĩa hàm, ẩn danh hoặc được đặt tên) cho một chức năng hiện có. I E.

customizableFunc(customFunctionality)

Nếu chức năng tùy chỉnh chỉ đơn giản là cắm vào khối mã, bạn đã tùy chỉnh chức năng, như vậy.

    customizableFucn(customFunctionality) {
      var data = doSomthing();
      customFunctionality(data);
      ...
    }

Mặc dù loại chức năng được tiêm này thường được gọi là "gọi lại", nhưng không có gì phụ thuộc về nó. Một ví dụ rất rõ ràng là phương thức forEach trong đó một hàm tùy chỉnh được cung cấp dưới dạng đối số được áp dụng cho từng phần tử trong một mảng để sửa đổi mảng.

Nhưng điều này về cơ bản khác với việc sử dụng các hàm "gọi lại" cho lập trình không đồng bộ , như trong AJAX hoặc node.js hoặc chỉ đơn giản là gán chức năng cho các sự kiện tương tác của người dùng (như nhấp chuột). Trong trường hợp này, toàn bộ ý tưởng là chờ đợi một sự kiện ngẫu nhiên xảy ra trước khi thực hiện chức năng tùy chỉnh. Điều này là rõ ràng trong trường hợp tương tác người dùng, nhưng cũng rất quan trọng trong các quá trình i / o (đầu vào / đầu ra) có thể mất thời gian, như đọc các tệp từ đĩa. Đây là nơi thuật ngữ "gọi lại" có ý nghĩa rõ ràng nhất. Khi quá trình i / o được bắt đầu (như yêu cầu đọc tệp từ đĩa hoặc máy chủ để trả về dữ liệu từ yêu cầu http) không đồng bộchương trình không chờ đợi nó kết thúc. Nó có thể tiếp tục với bất kỳ tác vụ nào được lên lịch tiếp theo và chỉ phản hồi với chức năng tùy chỉnh sau khi được thông báo rằng tệp đã đọc hoặc yêu cầu http đã hoàn thành (hoặc thất bại) và dữ liệu có sẵn cho chức năng tùy chỉnh. Nó giống như gọi một doanh nghiệp trên điện thoại và để lại số "gọi lại" của bạn, để họ có thể gọi cho bạn khi có người sẵn sàng liên lạc lại với bạn. Điều đó tốt hơn là treo trên đường dây cho những người biết bao lâu và không thể tham gia vào các vấn đề khác.

Việc sử dụng không đồng bộ vốn đã bao gồm một số phương tiện lắng nghe sự kiện mong muốn (ví dụ: hoàn thành quá trình i / o) để khi xảy ra (và chỉ khi nó xảy ra), chức năng "gọi lại" tùy chỉnh được thực thi. Trong ví dụ AJAX rõ ràng, khi dữ liệu thực sự đến từ máy chủ, chức năng "gọi lại" được kích hoạt để sử dụng dữ liệu đó để sửa đổi DOM và do đó vẽ lại cửa sổ trình duyệt đến mức đó.

Tóm lại. Một số người sử dụng từ "gọi lại" để chỉ bất kỳ loại chức năng tùy chỉnh nào có thể được đưa vào một chức năng hiện có làm đối số. Nhưng, ít nhất với tôi, cách sử dụng từ thích hợp nhất là sử dụng chức năng "gọi lại" được tiêm không đồng bộ - chỉ được thực hiện khi xảy ra sự kiện mà nó đang chờ để được thông báo.


Vậy khi hàm gọi lại, quá trình quay về đâu? Ví dụ, nếu có bốn dòng mã; 1.fileObject = open (tệp, writeToFile); 2. doS Something1 (); 3. doS Something2 (); 4. doS Something3 ().
MagicLAMP

Dòng 1 được thực thi, nhưng thay vì đợi tệp được mở, hãy chuyển sang dòng 2, sau đó 3. Lúc đó, tệp sẽ mở và (khi dòng 3 đã hoàn thành bất kỳ thao tác semaphore nào) hãy gọi lại bộ đếm chương trình để nói " vượt qua điều khiển để writeToFile "thực hiện công cụ và khi hoàn thành việc chuyển điều khiển trở lại điểm INT xảy ra trong dòng 3 hoặc đến dòng 4 nếu dòng 3 kết thúc.
MagicLAMP

1
Đây là một lời giải thích rất rõ ràng về một điểm quan trọng khác: ví dụ: sự khác biệt giữa hàm được truyền vào như là một đối số Array.prototype.forEach()và hàm được truyền vào như một đối số setTimeout()và chúng là những con ngựa có màu khác nhau theo cách bạn nghĩ về chương trình của mình .
mikermcneil

26

Theo thuật ngữ không phải lập trình viên, một cuộc gọi lại là phần điền vào chỗ trống trong một chương trình.

Một mục phổ biến trên nhiều mẫu giấy là "Người gọi trong trường hợp khẩn cấp". Có một dòng trống ở đó. Bạn viết tên và số điện thoại của ai đó. Nếu trường hợp khẩn cấp xảy ra, thì người đó được gọi.

  • Mọi người đều có cùng một mẫu trống, nhưng
  • Mọi người có thể viết một số liên lạc khẩn cấp khác nhau.

Đây là chìa khóa. Bạn không thay đổi hình thức (mã, thường là của người khác). Tuy nhiên, bạn có thể điền vào những phần thông tin còn thiếu ( số của bạn ).

Ví dụ 1:

Các cuộc gọi lại được sử dụng như các phương thức tùy chỉnh, có thể để thêm / thay đổi hành vi của chương trình. Ví dụ: lấy một số mã C thực hiện một chức năng, nhưng không biết cách in đầu ra. Tất cả những gì nó có thể làm là tạo ra một chuỗi. Khi nó cố gắng tìm ra những gì cần làm với chuỗi, nó sẽ thấy một dòng trống. Nhưng, lập trình viên đã cho bạn khoảng trống để viết lại cuộc gọi của bạn!

Trong ví dụ này, bạn không sử dụng bút chì để điền vào chỗ trống trên một tờ giấy, bạn sử dụng chức năng set_print_callback(the_callback).

  • Biến trống trong mô-đun / mã là dòng trống,
  • set_print_callback là bút chì,
  • the_callbacklà thông tin của bạn, bạn đang điền vào.

Bây giờ bạn đã điền vào dòng trống này trong chương trình. Bất cứ khi nào nó cần in đầu ra, nó sẽ nhìn vào dòng trống đó và làm theo hướng dẫn ở đó (tức là gọi chức năng bạn đặt ở đó.) Thực tế, điều này cho phép khả năng in ra màn hình, vào tệp nhật ký, cho máy in, qua kết nối mạng hoặc bất kỳ sự kết hợp nào của chúng. Bạn đã điền vào chỗ trống với những gì bạn muốn làm.

Ví dụ 2:

Khi bạn được thông báo rằng bạn cần gọi một số khẩn cấp, bạn đi và đọc những gì được viết trên mẫu giấy, và sau đó gọi số bạn đã đọc. Nếu dòng đó trống, sẽ không có gì được thực hiện.

Lập trình Gui hoạt động theo cùng một cách. Khi nhấn nút, chương trình cần tìm ra việc cần làm tiếp theo. Nó đi và tìm kiếm các cuộc gọi lại. Cuộc gọi lại này xảy ra ở dạng trống có nhãn "Đây là những gì bạn làm khi nhấp vào Nút 1"

Hầu hết các IDE sẽ tự động điền vào chỗ trống cho bạn (viết phương thức cơ bản) khi bạn yêu cầu nó (ví dụ button1_clicked). Tuy nhiên, trống có thể có bất kỳ phương pháp nào bạn rất vui lòng . Bạn có thể gọi phương thức run_computationshoặc butter_the_biscuitsmiễn là bạn đặt tên của cuộc gọi lại đó vào chỗ trống thích hợp. Bạn có thể đặt "555-555-1212" vào chỗ trống số khẩn cấp. Nó không có nhiều ý nghĩa, nhưng nó được cho phép.


Lưu ý cuối cùng: Dòng trống mà bạn đang điền vào cuộc gọi lại? Nó có thể được xóa và viết lại theo ý muốn. (bạn có nên hay không là một câu hỏi khác, nhưng đó là một phần sức mạnh của họ)


21

Luôn luôn tốt hơn để bắt đầu với một ví dụ :).

Giả sử bạn có hai mô-đun A và B.

Bạn muốn mô-đun A được thông báo khi xảy ra một số sự kiện / điều kiện trong mô-đun B. Tuy nhiên, mô-đun B không biết gì về mô-đun của bạn A. Tất cả những gì nó biết là một địa chỉ cho một chức năng cụ thể (của mô-đun A) thông qua một con trỏ hàm là cung cấp cho nó bởi mô-đun A.

Vì vậy, tất cả B phải làm ngay bây giờ, là "gọi lại" vào mô-đun A khi một sự kiện / điều kiện cụ thể xảy ra bằng cách sử dụng con trỏ hàm. A có thể xử lý thêm bên trong hàm gọi lại.

*) Một lợi thế rõ ràng ở đây là bạn đang trừu tượng hóa mọi thứ về mô-đun A từ mô-đun B. Mô-đun B không phải quan tâm ai / mô-đun A là gì.


Vì vậy, các tham số cho hàm trong A được cung cấp trong mô-đun B, điều đó có đúng không?
Hoa Kỳ

21

Johny lập trình viên cần một cái bấm ghim, vì vậy anh ta xuống phòng cung cấp văn phòng và yêu cầu một cái, sau khi điền vào mẫu yêu cầu, anh ta có thể đứng đó và đợi nhân viên bán hàng đi tìm kho hàng cho người dập ghim (như một cuộc gọi chức năng chặn ) hoặc đi làm một cái gì đó khác trong khi đó.

vì việc này thường mất thời gian, johny đặt một ghi chú cùng với mẫu yêu cầu yêu cầu họ gọi cho anh ấy khi người bấm ghim đã sẵn sàng để nhận, vì vậy trong thời gian đó anh ấy có thể đi làm một việc khác như ngủ trưa trên bàn.


1
Điều này có vẻ hơi giống với lời hứa hơn là gọi lại: blog.jcoglan.com/2013/03/30/NH
Thomas

2
Lời hứa chỉ là cú pháp đường xung quanh cuộc gọi lại.
Deven Phillips

20

Hãy tưởng tượng bạn cần một hàm trả về 10 bình phương để bạn viết một hàm:

function tenSquared() {return 10*10;}

Sau này bạn cần 9 bình phương để bạn viết một hàm khác:

function nineSquared() {return 9*9;}

Cuối cùng, bạn sẽ thay thế tất cả những thứ này bằng một hàm chung:

function square(x) {return x*x;}

Suy nghĩ chính xác tương tự áp dụng cho các cuộc gọi lại. Bạn có một chức năng làm một cái gì đó và khi thực hiện cuộc gọi doA:

function computeA(){
    ...
    doA(result);
}

Sau đó, bạn muốn chính xác cùng chức năng gọi doB thay vì bạn có thể sao chép toàn bộ chức năng:

function computeB(){
    ...
    doB(result);
}

Hoặc bạn có thể truyền hàm gọi lại dưới dạng biến và chỉ phải có hàm một lần:

function compute(callback){
    ...
    callback(result);
}

Sau đó, bạn chỉ cần gọi compute (doA) và tính toán (doB).

Ngoài việc đơn giản hóa mã, nó cho phép mã không đồng bộ cho bạn biết nó đã hoàn thành bằng cách gọi chức năng tùy ý của bạn khi hoàn thành, tương tự như khi bạn gọi cho ai đó trên điện thoại và để lại số gọi lại.


Vì vậy, bạn đang truyền chức năng như là một tham số. Có phải tất cả các ngôn ngữ lập trình cho phép truyền chức năng như một tham số? Nếu không, thì bạn có thể đưa ra một ví dụ về cách bạn triển khai chức năng gọi lại trên các ngôn ngữ đó.
Quazi Irfan

11

Bạn cảm thấy bị bệnh nên bạn đi khám. Anh ta kiểm tra bạn và xác định bạn cần một số loại thuốc. Ông kê toa một số huy chương và gọi đơn thuốc vào nhà thuốc địa phương của bạn. Bạn về nhà. Sau đó, nhà thuốc của bạn gọi để cho bạn biết toa thuốc của bạn đã sẵn sàng. Bạn đi và nhặt nó lên.


Tương tự rất tốt. Bạn có thể vui lòng mở rộng thêm một chút, có thể với một số ví dụ liên quan đến lập trình (đơn giản) không?
a20

10

Có hai điểm để giải thích, một là cách gọi lại hoạt động (chuyển qua một hàm có thể được gọi mà không có bất kỳ kiến ​​thức nào về ngữ cảnh của nó), điểm còn lại được sử dụng cho (xử lý các sự kiện không đồng bộ).

Sự tương tự của việc chờ đợi một bưu kiện đến đã được sử dụng bởi các câu trả lời khác là một cách tốt để giải thích cả hai. Trong một chương trình máy tính, bạn sẽ nói với máy tính mong đợi một bưu kiện. Thông thường, bây giờ nó sẽ ngồi đó và chờ đợi (và không làm gì khác) cho đến khi bưu kiện đến, có thể là vô thời hạn nếu nó không bao giờ đến. Đối với con người, điều này nghe có vẻ ngớ ngẩn, nhưng không có biện pháp nào khác, điều này hoàn toàn tự nhiên đối với máy tính.

Bây giờ cuộc gọi lại sẽ là tiếng chuông ở cửa trước của bạn. Bạn cung cấp dịch vụ bưu kiện một cách để thông báo cho bạn về việc bưu kiện đến mà không cần họ phải biết bạn đang ở đâu (ngay cả khi) trong nhà, hoặc tiếng chuông hoạt động như thế nào. (Ví dụ: một số "chuông" thực sự gửi một cuộc gọi điện thoại.) Vì bạn đã cung cấp "chức năng gọi lại" có thể được "gọi" bất cứ lúc nào, ngoài ngữ cảnh, giờ đây bạn có thể dừng ngồi ở hiên trước và "xử lý sự kiện "(của bưu kiện đến) bất cứ khi nào đến lúc.


Đây là tiếng Anh đơn giản.
Jeb50

Đây dường như là lời giải thích tốt nhất!
Vishal Sharma

6

Hãy tưởng tượng một người bạn đang rời khỏi nhà bạn và bạn nói với cô ấy "Hãy gọi cho tôi khi bạn về nhà để tôi biết bạn đã đến nơi an toàn"; đó là (theo nghĩa đen) một cuộc gọi lại . Đó là chức năng gọi lại, bất kể ngôn ngữ. Bạn muốn một số thủ tục chuyển lại quyền kiểm soát cho bạn khi nó đã hoàn thành một số nhiệm vụ, vì vậy bạn cung cấp cho nó một chức năng để sử dụng để gọi lại cho bạn.

Trong Python, ví dụ,

grabDBValue( (lambda x: passValueToGUIWindow(x) ))

grabDBValuecó thể được viết để chỉ lấy một giá trị từ cơ sở dữ liệu và sau đó cho phép bạn chỉ định những gì thực sự phải làm với giá trị đó, vì vậy nó chấp nhận một hàm. Bạn không biết khi nào hoặc nếu grabDBValuesẽ trở lại, nhưng nếu / khi nó xảy ra, bạn biết bạn muốn nó làm gì. Ở đây, tôi chuyển vào một hàm ẩn danh (hoặc lambda ) gửi giá trị đến cửa sổ GUI. Tôi có thể dễ dàng thay đổi hành vi của chương trình bằng cách làm điều này:

grabDBValue( (lambda x: passToLogger(x) ))

Các cuộc gọi lại hoạt động tốt trong các ngôn ngữ nơi các hàm là các giá trị hạng nhất , giống như các số nguyên, chuỗi ký tự, booleans thông thường, v.v. Trong C, bạn có thể "truyền" một hàm xung quanh bằng cách chuyển một con trỏ tới nó và người gọi có thể sử dụng nó; trong Java, người gọi sẽ yêu cầu một lớp tĩnh của một loại nhất định với một tên phương thức nhất định vì không có hàm ("phương thức", thực sự) bên ngoài các lớp; và trong hầu hết các ngôn ngữ động khác, bạn chỉ có thể truyền một hàm với cú pháp đơn giản.

Protip:

Trong các ngôn ngữ có phạm vi từ vựng (như Scheme hoặc Perl), bạn có thể thực hiện một mẹo như thế này:

my $var = 2;
my $val = someCallerBackFunction(sub callback { return $var * 3; });
# Perlistas note: I know the sub doesn't need a name, this is for illustration

$valtrong trường hợp này sẽ là 6vì cuộc gọi lại có quyền truy cập vào các biến được khai báo trong môi trường từ vựng nơi nó được xác định. Phạm vi từ điển và các cuộc gọi lại ẩn danh là một sự kết hợp mạnh mẽ đảm bảo nghiên cứu thêm cho các lập trình viên mới làm quen.


1
+1. Tôi thực sự khá thích câu trả lời này. Giải thích về cuộc gọi lại là gì , đơn giản và ngắn gọn.
TarkaDaal

6

Bạn có một số mã bạn muốn chạy. Thông thường, khi bạn gọi nó, bạn sẽ đợi nó kết thúc trước khi tiếp tục (điều này có thể khiến ứng dụng của bạn chuyển sang màu xám / tạo thời gian quay cho con trỏ).

Một phương pháp khác là chạy mã này song song và tiếp tục với công việc của riêng bạn. Nhưng nếu mã gốc của bạn cần làm những việc khác nhau tùy thuộc vào phản hồi từ mã mà nó gọi thì sao? Chà, trong trường hợp đó bạn có thể chuyển tên / vị trí của mã mà bạn muốn gọi khi hoàn thành. Đây là một "cuộc gọi lại".

Mã thông thường: Yêu cầu thông tin-> Thông tin quy trình-> Xử lý kết quả xử lý-> Tiếp tục làm những việc khác.

Với các cuộc gọi lại: Yêu cầu thông tin-> Thông tin quy trình-> Tiếp tục làm những việc khác. Và tại một số điểm sau-> Xử lý kết quả Xử lý.


6

Không có cuộc gọi lại cũng không phải tài nguyên lập trình đặc biệt nào khác (như luồng và các tài nguyên khác), chương trình chính xác là một chuỗi các lệnh được thực hiện tuần tự lần lượt và thậm chí với một loại "hành vi động" được xác định bởi các điều kiện nhất định, tất cả các tình huống có thể xảy ra sẽ được lập trình trước đó .

Vì vậy, nếu chúng ta cần cung cấp một hành vi động thực sự cho một chương trình, chúng ta có thể sử dụng gọi lại. Với gọi lại, bạn có thể hướng dẫn theo tham số, chương trình gọi một chương trình khác cung cấp một số tham số đã xác định trước đó và có thể mong đợi một số kết quả ( đây là hợp đồng hoặc chữ ký hoạt động ), vì vậy những kết quả này có thể được tạo / xử lý bởi chương trình của bên thứ ba Trước đây được biết đến.

Kỹ thuật này là nền tảng của tính đa hình được áp dụng cho các chương trình, chức năng, đối tượng và tất cả các thể thống nhất khác của mã được điều hành bởi máy tính.

Thế giới con người được sử dụng làm ví dụ để gọi lại được giải thích rất hay khi bạn đang làm một công việc nào đó, giả sử bạn là một họa sĩ ( ở đây bạn là chương trình chính, đó là sơn ) và thỉnh thoảng gọi cho khách hàng của bạn để yêu cầu anh ta chấp thuận kết quả công việc của bạn , vì vậy, anh ta quyết định xem hình ảnh có tốt không ( khách hàng của bạn là chương trình của bên thứ ba ).

Trong ví dụ trên, bạn là họa sĩ và "ủy thác" cho người khác công việc phê duyệt kết quả, hình ảnh là tham số và mỗi khách hàng mới (hàm "gọi lại") thay đổi kết quả công việc của bạn quyết định anh ta muốn gì về hình ảnh ( quyết định của khách hàng là kết quả trả về từ "chức năng gọi lại" ).

Tôi hy vọng lời giải thích này có thể hữu ích.


6

Hãy giả vờ rằng bạn sẽ giao cho tôi một nhiệm vụ có khả năng lâu dài: lấy tên của năm người duy nhất đầu tiên bạn gặp. Điều này có thể mất vài ngày nếu tôi ở một khu vực dân cư thưa thớt. Bạn không thực sự thích ngồi trên tay trong khi tôi đang chạy xung quanh nên bạn nói, "Khi bạn có danh sách, hãy gọi cho tôi trên di động của tôi và đọc lại cho tôi. Đây là số."

Bạn đã cho tôi một tham chiếu gọi lại - một chức năng mà tôi phải thực hiện để xử lý tiếp.

Trong JavaScript, nó có thể trông giống như thế này:

var lottoNumbers = [];
var callback = function(theNames) {
  for (var i=0; i<theNames.length; i++) {
    lottoNumbers.push(theNames[i].length);
  }
};

db.executeQuery("SELECT name " +
                "FROM tblEveryOneInTheWholeWorld " +
                "ORDER BY proximity DESC " +
                "LIMIT 5", callback);

while (lottoNumbers.length < 5) {
  playGolf();
}
playLotto(lottoNumbers);

Điều này có thể được cải thiện theo nhiều cách. Ví dụ: bạn có thể cung cấp một cuộc gọi lại thứ hai: nếu nó kết thúc lâu hơn một giờ, hãy gọi điện thoại màu đỏ và nói với người đó rằng câu trả lời mà bạn đã hết thời gian.


6

Cuộc gọi lại được mô tả dễ dàng nhất trong điều khoản của hệ thống điện thoại. Một cuộc gọi chức năng tương tự như gọi ai đó qua điện thoại, hỏi cô ấy một câu hỏi, nhận được câu trả lời và gác máy; thêm một cuộc gọi lại thay đổi sự tương tự để sau khi hỏi cô ấy một câu hỏi, bạn cũng cho cô ấy biết tên và số của cô ấy để cô ấy có thể gọi lại cho bạn với câu trả lời.- Paul Jakubik, "Thực hiện gọi lại trong C ++"


Một lời giải thích dễ dàng hơn sẽ là: Tôi gọi cho ai đó, cô ấy đang họp, tôi để lại số điện thoại, cô ấy gọi lại.
Cô đơn

5

Gọi lại là một chức năng sẽ được gọi bởi một chức năng thứ hai. Hàm thứ hai này không biết trước nó sẽ gọi hàm nào. Vì vậy, danh tính của hàm gọi lại được lưu trữ ở đâu đó hoặc được chuyển đến hàm thứ hai dưới dạng tham số. "Danh tính" này, tùy thuộc vào ngôn ngữ lập trình, có thể là địa chỉ của cuộc gọi lại hoặc một loại con trỏ khác hoặc có thể là tên của hàm. Hiệu trưởng là như nhau, chúng tôi lưu trữ hoặc truyền một số thông tin xác định rõ ràng chức năng.

Khi thời gian đến, chức năng thứ hai có thể gọi lại, cung cấp các tham số tùy thuộc vào hoàn cảnh tại thời điểm đó. Nó thậm chí có thể chọn cuộc gọi lại từ một tập hợp các cuộc gọi lại có thể. Ngôn ngữ lập trình phải cung cấp một số loại cú pháp để cho phép hàm thứ hai gọi lại, biết "danh tính" của nó.

Cơ chế này có rất nhiều công dụng có thể. Với các cuộc gọi lại, người thiết kế một chức năng có thể cho phép nó được tùy chỉnh bằng cách gọi nó bất cứ cuộc gọi lại nào được cung cấp. Ví dụ, một hàm sắp xếp có thể lấy một cuộc gọi lại làm tham số và cuộc gọi lại này có thể là một hàm để so sánh hai yếu tố để quyết định cái nào đến trước.

Nhân tiện, tùy thuộc vào ngôn ngữ lập trình, từ "chức năng" trong cuộc thảo luận ở trên có thể được thay thế bằng "khối", "đóng cửa", "lambda", v.v.


5

Thông thường chúng tôi đã gửi các biến đến các chức năng. Giả sử bạn có nhiệm vụ trong đó biến cần được xử lý trước khi được đưa ra làm đối số - bạn có thể sử dụng gọi lại.

function1(var1, var2) là cách thông thường.

Điều gì xảy ra nếu tôi muốn var2được xử lý và sau đó gửi làm đối số? function1(var1, function2(var2))

Đây là một loại gọi lại - trong đó function2thực thi một số mã và trả về một biến trở lại hàm ban đầu.


2
Một loại gọi lại là gì?
johnny

@johnny: Có các cuộc gọi lại trình duyệt bình thường được kích hoạt khi Ajax hoàn thành, v.v.
Nishant

4

Một lời giải thích ẩn dụ:

Tôi có một bưu kiện tôi muốn giao cho một người bạn, và tôi cũng muốn biết khi nào bạn tôi nhận được nó.

Vì vậy, tôi mang bưu kiện đến bưu điện và yêu cầu họ giao nó. Nếu tôi muốn biết khi nào bạn tôi nhận được bưu kiện, tôi có hai lựa chọn:

(a) Tôi có thể đợi tại bưu điện cho đến khi nó được giao.

(b) Tôi sẽ nhận được email khi nó được gửi.

Tùy chọn (b) tương tự như một cuộc gọi lại.


4

Để dạy gọi lại, bạn phải dạy con trỏ trước. Một khi các sinh viên hiểu ý tưởng của con trỏ đến một biến, ý tưởng về các cuộc gọi lại sẽ trở nên dễ dàng hơn. Giả sử bạn đang sử dụng C / C ++, các bước này có thể được thực hiện theo.

  • Đầu tiên chỉ cho học sinh của bạn cách sử dụng và thao tác các biến bằng cách sử dụng các con trỏ cùng với việc sử dụng các định danh biến thông thường.
  • Sau đó dạy cho họ có những điều chỉ có thể được thực hiện với con trỏ (như truyền một biến bằng tham chiếu).
  • Sau đó cho họ biết mã hoặc các hàm thực thi giống như một số dữ liệu (hoặc biến) khác trong bộ nhớ. Vì vậy, các chức năng cũng có địa chỉ hoặc con trỏ.
  • Sau đó chỉ cho họ cách các hàm có thể được gọi bằng các con trỏ hàm và nói với chúng được gọi là các hàm gọi lại.
  • Bây giờ, câu hỏi là, tại sao tất cả những rắc rối để gọi một số chức năng? Lợi ích là gì? Giống như con trỏ dữ liệu, con trỏ hàm hay còn gọi là hàm gọi lại có một số lợi thế so với việc sử dụng các định danh thông thường.
  • Đầu tiên là, định danh hàm hoặc tên hàm không thể được sử dụng như dữ liệu bình thường. Ý tôi là, bạn không thể tạo cấu trúc dữ liệu với các hàm (như một mảng hoặc danh sách các hàm được liên kết). Nhưng với các cuộc gọi lại, bạn có thể tạo một mảng, một danh sách được liên kết hoặc sử dụng chúng với các dữ liệu khác như trong từ điển của các cặp hoặc giá trị khóa hoặc bất kỳ thứ gì khác. Đây là một lợi ích mạnh mẽ. Và những lợi ích khác thực sự là con của cái này.
  • Việc sử dụng gọi lại phổ biến nhất được thấy trong lập trình trình điều khiển sự kiện. Trong đó một hoặc nhiều chức năng được thực thi dựa trên một số tín hiệu đến. Với các cuộc gọi lại, một từ điển có thể được duy trì để ánh xạ tín hiệu với các cuộc gọi lại. Sau đó, độ phân giải tín hiệu đầu vào và thực thi mã tương ứng trở nên dễ dàng hơn nhiều.
  • Việc sử dụng thứ hai của các cuộc gọi lại trong tâm trí tôi là các hàm bậc cao hơn. Các hàm lấy các hàm khác làm đối số đầu vào. Và để gửi các hàm làm đối số, chúng ta cần gọi lại. Một ví dụ có thể là một hàm lấy một mảng và gọi lại. Sau đó, nó thực hiện gọi lại trên từng mục của mảng và trả về kết quả trong một mảng khác. Nếu chúng ta truyền vào hàm một cuộc gọi lại nhân đôi, chúng ta sẽ nhận được một mảng có giá trị gấp đôi. Nếu chúng ta vượt qua một cuộc gọi lại bình phương, chúng ta sẽ có được hình vuông. Đối với căn bậc hai, chỉ cần gửi gọi lại thích hợp. Điều này không thể được thực hiện với các chức năng bình thường.

Có thể có nhiều thứ nữa. Thu hút các sinh viên và họ sẽ khám phá. Hi vọng điêu nay co ich.


1
Một câu trả lời khác của tôi liên quan đến chủ đề này trong các lập trình viên. Các lập trình
viên.stackexchange.com/a/75449/963

3

Trong tiếng Anh đơn giản, một cuộc gọi lại là một lời hứa. Joe, Jane, David và Samantha cùng đi chung xe. Joe đang lái xe ngày hôm nay. Jane, David và Samantha có một vài lựa chọn:

  1. Kiểm tra cửa sổ cứ sau 5 phút để xem Joe có ra ngoài không
  2. Tiếp tục làm việc của họ cho đến khi Joe bấm chuông cửa.

Tùy chọn 1: Đây giống như một ví dụ bỏ phiếu trong đó Jane sẽ bị mắc kẹt trong "vòng lặp" kiểm tra xem Joe có ở bên ngoài không. Jane không thể làm bất cứ điều gì khác trong thời gian đó.

Tùy chọn 2: Đây là ví dụ gọi lại. Jane bảo Joe bấm chuông cửa khi anh ở ngoài. Cô cho anh "chức năng" bấm chuông cửa. Joe không cần biết chuông cửa hoạt động như thế nào hay nó ở đâu, anh ta chỉ cần gọi chức năng đó tức là rung chuông cửa khi anh ta ở đó.

Cuộc gọi lại được thúc đẩy bởi "sự kiện". Trong ví dụ này, "sự kiện" là sự xuất hiện của Joe. Trong Ajax, ví dụ các sự kiện có thể là "thành công" hoặc "thất bại" của yêu cầu không đồng bộ và mỗi yêu cầu có thể có các cuộc gọi lại giống nhau hoặc khác nhau.

Về mặt ứng dụng JavaScript và cuộc gọi lại. Chúng ta cũng cần hiểu "đóng cửa" và bối cảnh ứng dụng. "Điều này" đề cập đến có thể dễ dàng gây nhầm lẫn cho các nhà phát triển JavaScript. Trong ví dụ này trong phương thức / gọi lại "ring_the_door_bell ()" của mỗi người, có thể có một số phương thức khác mà mỗi người cần thực hiện dựa trên thói quen buổi sáng của họ. "Tắt TV đi()". Chúng tôi muốn "cái này" đề cập đến đối tượng "Jane" hoặc đối tượng "David" để mỗi người có thể thiết lập bất cứ điều gì khác họ cần làm trước khi Joe chọn chúng. Đây là nơi thiết lập cuộc gọi lại với Joe yêu cầu nhại lại phương thức sao cho "cái này" chỉ đúng đối tượng.

Mong rằng sẽ giúp!


3

Một cuộc gọi lại là một phong bì tự đóng dấu. Khi bạn gọi một chức năng, điều đó giống như gửi thư. Nếu bạn muốn chức năng đó gọi một chức năng khác, bạn cung cấp thông tin đó dưới dạng tham chiếu hoặc địa chỉ.


3

Tôi nghĩ đó là một nhiệm vụ khá dễ dàng để giải thích.

Lúc đầu gọi lại chỉ là các chức năng thông thường.
Và hơn nữa là, chúng ta gọi hàm này (hãy gọi nó là A) từ bên trong một hàm khác (hãy gọi nó là B).

Sự kỳ diệu về việc này là tôi quyết định, trong đó chức năng nên được gọi bởi hàm từ bên ngoài B.

Tại thời điểm tôi viết hàm BI không biết nên gọi hàm gọi lại nào. Lúc đó tôi gọi hàm BI cũng bảo hàm này gọi hàm A. Thế thôi.


3

Chức năng gọi lại là gì?

Câu trả lời đơn giản cho câu hỏi đầu tiên này là hàm gọi lại là một hàm được gọi thông qua một con trỏ hàm. Nếu bạn truyền con trỏ (địa chỉ) của hàm làm đối số cho đối số khác, khi con trỏ đó được sử dụng để gọi hàm, nó trỏ đến nó sẽ nói rằng một cuộc gọi lại được thực hiện.

Chức năng gọi lại rất khó để theo dõi, nhưng đôi khi nó rất hữu ích. Đặc biệt là khi bạn đang thiết kế thư viện. Hàm gọi lại giống như yêu cầu người dùng cung cấp cho bạn một tên hàm và bạn sẽ gọi hàm đó trong một số điều kiện nhất định.

Ví dụ, bạn viết một bộ đếm thời gian gọi lại. Nó cho phép bạn chỉ định thời lượng và chức năng nào cần gọi, và chức năng sẽ được gọi lại tương ứng. Hãy chạy myfeft () cứ sau 10 giây cho 5 lần

Hoặc bạn có thể tạo một thư mục hàm, chuyển danh sách tên hàm và yêu cầu thư viện gọi lại cho phù hợp. Gọi lại thành công () nếu thành công, gọi lại thất bại () nếu thất bại.

Hãy xem xét một ví dụ con trỏ hàm đơn giản

void cbfunc()
{
     printf("called");
}

 int main ()
 {
                   /* function pointer */ 
      void (*callback)(void); 
                   /* point to your callback function */ 
      callback=(void *)cbfunc; 
                   /* perform callback */
      callback();
      return 0; 
}

Làm thế nào để truyền đối số cho hàm gọi lại?

Quan sát thấy rằng con trỏ hàm để thực hiện gọi lại mất trong void *, điều này cho biết rằng nó có thể nhận bất kỳ loại biến nào bao gồm cả cấu trúc. Do đó, bạn có thể vượt qua trong nhiều đối số theo cấu trúc.

typedef struct myst
{
     int a;
     char b[10];
}myst;

void cbfunc(myst *mt) 
{
     fprintf(stdout,"called %d %s.",mt->a,mt->b); 
}

int main() 
{
       /* func pointer */
    void (*callback)(void *);       //param
     myst m;
     m.a=10;
     strcpy(m.b,"123");       
     callback = (void*)cbfunc;    /* point to callback function */
     callback(&m);                /* perform callback and pass in the param */
     return 0;   
}

2

Gọi lại là một phương thức được lên lịch để thực hiện khi một điều kiện được đáp ứng.

Một ví dụ "thế giới thực" là một cửa hàng trò chơi video địa phương. Bạn đang chờ Half-Life 3. Thay vì đến cửa hàng mỗi ngày để xem nó có ở trong không, bạn đăng ký email của bạn trên một danh sách để được thông báo khi trò chơi có sẵn. Email trở thành "cuộc gọi lại" của bạn và điều kiện cần đáp ứng là tính khả dụng của trò chơi.

Ví dụ "lập trình viên" là một trang web nơi bạn muốn thực hiện một hành động khi nhấp vào nút. Bạn đăng ký một phương thức gọi lại cho một nút và tiếp tục thực hiện các nhiệm vụ khác. Khi / nếu người dùng chọn nút, trình duyệt sẽ xem danh sách các cuộc gọi lại cho sự kiện đó và gọi phương thức của bạn.

Gọi lại là một cách để xử lý các sự kiện không đồng bộ. Bạn không bao giờ có thể biết khi nào cuộc gọi lại sẽ được thực hiện, hoặc nếu nó sẽ được thực hiện ở tất cả. Ưu điểm là nó giải phóng các chu trình chương trình và CPU của bạn để thực hiện các tác vụ khác trong khi chờ trả lời.


Nói rằng đó là "theo lịch trình" có thể gây nhầm lẫn ở đây. Các cuộc gọi lại thường được sử dụng trong các hệ thống không đồng bộ và sẽ không có "lịch biểu" mà thay vào đó là một "sự kiện" kích hoạt cuộc gọi lại được thực hiện.
Deven Phillips

2

Đơn giản và đơn giản: Gọi lại là một chức năng mà bạn cung cấp cho một chức năng khác, để nó có thể gọi nó.

Thông thường nó được gọi khi một số hoạt động được hoàn thành. Vì bạn tạo cuộc gọi lại trước khi đưa nó cho chức năng khác, bạn có thể khởi tạo nó với thông tin ngữ cảnh từ trang web cuộc gọi. Đó là lý do tại sao nó được đặt tên là một cuộc gọi * trở lại * - chức năng đầu tiên gọi lại vào bối cảnh từ nơi nó được gọi.


2

Trong lập trình máy tính, một cuộc gọi lại là một tham chiếu đến mã thực thi, hoặc một đoạn mã thực thi, được truyền dưới dạng đối số cho mã khác. Điều này cho phép lớp phần mềm cấp thấp hơn gọi một chương trình con (hoặc hàm) được xác định trong lớp cấp cao hơn. - Wikipedia

Gọi lại trong C bằng cách sử dụng con trỏ hàm

Trong C, gọi lại được thực hiện bằng cách sử dụng Hàm con trỏ. Con trỏ hàm - như tên cho thấy, là một con trỏ tới hàm.

Ví dụ: int (* ptrFunc) ();

Ở đây, ptrFunc là một con trỏ tới một hàm không có đối số và trả về một số nguyên. KHÔNG quên đặt trong ngoặc đơn, nếu không trình biên dịch sẽ cho rằng ptrFunc là tên hàm bình thường, không mất gì và trả về một con trỏ tới một số nguyên.

Đây là một số mã để chứng minh con trỏ hàm.

#include<stdio.h>
int func(int, int);
int main(void)
{
    int result1,result2;
    /* declaring a pointer to a function which takes
       two int arguments and returns an integer as result */
    int (*ptrFunc)(int,int);

    /* assigning ptrFunc to func's address */                    
    ptrFunc=func;

    /* calling func() through explicit dereference */
    result1 = (*ptrFunc)(10,20);

    /* calling func() through implicit dereference */        
    result2 = ptrFunc(10,20);            
    printf("result1 = %d result2 = %d\n",result1,result2);
    return 0;
}

int func(int x, int y)
{
    return x+y;
}

Bây giờ chúng ta hãy cố gắng hiểu khái niệm Callback trong C bằng cách sử dụng con trỏ hàm.

Chương trình hoàn chỉnh có ba tệp: callback.c, reg_callback.h và reg_callback.c.

/* callback.c */
#include<stdio.h>
#include"reg_callback.h"

/* callback function definition goes here */
void my_callback(void)
{
    printf("inside my_callback\n");
}

int main(void)
{
    /* initialize function pointer to
    my_callback */
    callback ptr_my_callback=my_callback;                        
    printf("This is a program demonstrating function callback\n");
    /* register our callback function */
    register_callback(ptr_my_callback);                          
    printf("back inside main program\n");
    return 0;
}

/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);


/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"

/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
    printf("inside register_callback\n");
    /* calling our callback function my_callback */
    (*ptr_reg_callback)();                               
}

Nếu chúng tôi chạy chương trình này, đầu ra sẽ là

Đây là một chương trình thể hiện chức năng gọi lại bên trong register_callback bên trong my_callback trở lại bên trong chương trình chính

Hàm lớp cao hơn gọi hàm lớp thấp hơn như một cuộc gọi bình thường và cơ chế gọi lại cho phép hàm lớp thấp hơn gọi hàm lớp cao hơn thông qua một con trỏ đến hàm gọi lại.

Gọi lại trong Java bằng giao diện

Java không có khái niệm về con trỏ hàm. Nó thực hiện cơ chế gọi lại thông qua cơ chế Giao diện của nó Ở đây thay vì một con trỏ hàm, chúng ta khai báo một Giao diện có một phương thức sẽ được gọi khi callee hoàn thành nhiệm vụ của nó

Hãy để tôi chứng minh điều đó qua một ví dụ:

Giao diện gọi lại

public interface Callback
{
    public void notify(Result result);
}

Người gọi hoặc Lớp cấp cao hơn

public Class Caller implements Callback
{
Callee ce = new Callee(this); //pass self to the callee

//Other functionality
//Call the Asynctask
ce.doAsynctask();

public void notify(Result result){
//Got the result after the callee has finished the task
//Can do whatever i want with the result
}
}

Hàm Callee hoặc lớp dưới

public Class Callee {
Callback cb;
Callee(Callback cb){
this.cb = cb;
}

doAsynctask(){
//do the long running task
//get the result
cb.notify(result);//after the task is completed, notify the caller
}
}

Gọi lại bằng cách sử dụng mẫu EventListener

  • Danh sách mục

Mẫu này được sử dụng để thông báo 0 đến n số lượng Người quan sát / Người nghe rằng một nhiệm vụ cụ thể đã kết thúc

  • Danh sách mục

Sự khác biệt giữa cơ chế gọi lại và cơ chế EventListener / Observer là trong cuộc gọi lại, callee thông báo cho người gọi duy nhất, trong khi trong Eventlisener / Observer, callee có thể thông báo cho bất kỳ ai quan tâm đến sự kiện đó (thông báo có thể đi đến một số phần khác của ứng dụng chưa kích hoạt tác vụ)

Hãy để tôi giải thích nó thông qua một ví dụ.

Giao diện sự kiện

public interface Events {

public void clickEvent();
public void longClickEvent();
}

Lớp học Widget

package com.som_itsolutions.training.java.exampleeventlistener;

import java.util.ArrayList;
import java.util.Iterator;

public class Widget implements Events{

    ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>(); 
    ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();

    @Override
    public void clickEvent() {
        // TODO Auto-generated method stub
        Iterator<OnClickEventListener> it = mClickEventListener.iterator();
                while(it.hasNext()){
                    OnClickEventListener li = it.next();
                    li.onClick(this);
                }   
    }
    @Override
    public void longClickEvent() {
        // TODO Auto-generated method stub
        Iterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();
        while(it.hasNext()){
            OnLongClickEventListener li = it.next();
            li.onLongClick(this);
        }

    }

    public interface OnClickEventListener
    {
        public void onClick (Widget source);
    }

    public interface OnLongClickEventListener
    {
        public void onLongClick (Widget source);
    }

    public void setOnClickEventListner(OnClickEventListener li){
        mClickEventListener.add(li);
    }
    public void setOnLongClickEventListner(OnLongClickEventListener li){
        mLongClickEventListener.add(li);
    }
}

Nút lớp

public class Button extends Widget{
private String mButtonText;
public Button (){
} 
public String getButtonText() {
return mButtonText;
}
public void setButtonText(String buttonText) {
this.mButtonText = buttonText;
}
}

Hộp kiểm tra lớp

public class CheckBox extends Widget{
private boolean checked;
public CheckBox() {
checked = false;
}
public boolean isChecked(){
return (checked == true);
}
public void setCheck(boolean checked){
this.checked = checked;
}
}

Lớp hoạt động

gói com.som_itsolutions.training.java.exampleeventlistener;

public class Activity implements Widget.OnClickEventListener
{
    public Button mButton;
    public CheckBox mCheckBox;
    private static Activity mActivityHandler;
    public static Activity getActivityHandle(){
        return mActivityHandler;
    }
    public Activity ()
    {
        mActivityHandler = this;
        mButton = new Button();
        mButton.setOnClickEventListner(this);
        mCheckBox = new CheckBox();
        mCheckBox.setOnClickEventListner(this);
        } 
    public void onClick (Widget source)
    {
        if(source == mButton){
            mButton.setButtonText("Thank you for clicking me...");
            System.out.println(((Button) mButton).getButtonText());
        }
        if(source == mCheckBox){
            if(mCheckBox.isChecked()==false){
                mCheckBox.setCheck(true);
                System.out.println("The checkbox is checked...");
            }
            else{
                mCheckBox.setCheck(false);
                System.out.println("The checkbox is not checked...");
            }       
        }
    }
    public void doSomeWork(Widget source){
        source.clickEvent();
    }   
}

Lớp khác

public class OtherClass implements Widget.OnClickEventListener{
Button mButton;
public OtherClass(){
mButton = Activity.getActivityHandle().mButton;
mButton.setOnClickEventListner(this);//interested in the click event                        //of the button
}
@Override
public void onClick(Widget source) {
if(source == mButton){
System.out.println("Other Class has also received the event notification...");
}
}

Lớp chính

public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Activity a = new Activity();
OtherClass o = new OtherClass();
a.doSomeWork(a.mButton);
a.doSomeWork(a.mCheckBox);
}
}

Như bạn có thể thấy từ đoạn mã trên, chúng ta có một giao diện gọi là các sự kiện về cơ bản liệt kê tất cả các sự kiện có thể xảy ra cho ứng dụng của chúng ta. Lớp Widget là lớp cơ sở cho tất cả các thành phần UI như Nút, Hộp kiểm. Các thành phần UI này là các đối tượng thực sự nhận các sự kiện từ mã khung. Lớp widget thực hiện giao diện Sự kiện và cũng có hai giao diện lồng nhau là OnClickEventListener & OnLongClickEventListener

Hai giao diện này chịu trách nhiệm lắng nghe các sự kiện có thể xảy ra trên các thành phần UI có nguồn gốc Widget như Nút hoặc Hộp kiểm. Vì vậy, nếu chúng ta so sánh ví dụ này với ví dụ Gọi lại trước đó bằng Giao diện Java, hai giao diện này hoạt động như giao diện Gọi lại. Vì vậy, mã cấp cao hơn (Hoạt động ở đây) thực hiện hai giao diện này. Và bất cứ khi nào một sự kiện xảy ra với một widget, mã cấp cao hơn (hoặc phương thức của các giao diện này được triển khai ở mã cấp cao hơn, ở đây là Activity) sẽ được gọi.

Bây giờ hãy để tôi thảo luận về sự khác biệt cơ bản giữa mẫu Callback và Eventlistener. Như chúng tôi đã đề cập rằng sử dụng Callback, Callee chỉ có thể thông báo cho một Người gọi duy nhất. Nhưng trong trường hợp mẫu EventListener, bất kỳ phần hoặc lớp nào khác của Ứng dụng đều có thể đăng ký các sự kiện có thể xảy ra trên Nút hoặc Hộp kiểm. Ví dụ về loại lớp này là OtherClass. Nếu bạn thấy mã của OtherClass, bạn sẽ thấy rằng nó đã tự đăng ký làm người nghe cho ClickEvent có thể xảy ra trong Nút được xác định trong Hoạt động. Điều thú vị là, ngoài Hoạt động (Người gọi), OtherClass này cũng sẽ được thông báo mỗi khi sự kiện nhấp xảy ra trên Nút.


1

Callbacks cho phép bạn chèn mã của riêng bạn vào một khối mã khác để được thực thi vào một thời điểm khác, điều chỉnh hoặc thêm vào hành vi của khối mã khác đó cho phù hợp với nhu cầu của bạn. Bạn có được sự linh hoạt và khả năng tùy biến trong khi có thể có nhiều mã có thể bảo trì hơn.

Ít mã hóa hơn = dễ bảo trì và thay đổi = ít thời gian hơn = giá trị doanh nghiệp nhiều hơn = sự tuyệt vời.

Ví dụ: trong javascript, sử dụng Underscore.js, bạn có thể tìm thấy tất cả các phần tử chẵn trong một mảng như thế này:

var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]

Ví dụ lịch sự của Underscore.js: http://documentcloud.github.com/underscore/#filter


1

[sửa] khi chúng ta có hai chức năng nói functionA và functionB , nếu functionA phụ thuộc vào functionB .

sau đó chúng ta gọi là functionB như một hàm callback .Đây sử dụng rộng rãi trong khuôn khổ mùa xuân.

chức năng gọi lại ví dụ wikipedia


1

Hãy nghĩ về một phương pháp như giao một nhiệm vụ cho đồng nghiệp. Một nhiệm vụ đơn giản có thể là như sau:

Solve these equations:
x + 2 = y
2 * x = 3 * y

Đồng nghiệp của bạn siêng năng làm toán và cho bạn kết quả như sau:

x = -6
y = -4

Nhưng đồng nghiệp của bạn có một vấn đề, anh ta không phải lúc nào cũng hiểu các ký hiệu, chẳng hạn như ^, nhưng anh ta hiểu chúng theo mô tả của họ. Chẳng hạn như exponent. Mỗi khi anh ta tìm thấy một trong những thứ này bạn sẽ nhận lại được những điều sau:

I don't understand "^"

Điều này đòi hỏi bạn phải viết lại toàn bộ tập lệnh của mình một lần nữa sau khi giải thích ý nghĩa của nhân vật đối với đồng nghiệp của bạn và anh ta không luôn nhớ giữa các câu hỏi. Và anh ấy cũng gặp khó khăn trong việc ghi nhớ lời khuyên của bạn, chẳng hạn như chỉ cần hỏi tôi. Anh ấy luôn luôn làm theo chỉ dẫn bằng văn bản của bạn tốt nhất có thể.

Bạn nghĩ đến một giải pháp, bạn chỉ cần thêm những điều sau vào tất cả các hướng dẫn của bạn:

If you have any questions about symbols, call me at extension 1234 and I will tell you its name.

Bây giờ bất cứ khi nào anh ta gặp vấn đề, anh ta gọi cho bạn và hỏi, thay vì trả lời không tốt và làm cho quá trình khởi động lại.


0

Điều này về mặt tải xuống một trang web:

Chương trình của bạn chạy trên điện thoại di động và đang yêu cầu trang web http://www.google.com . Nếu bạn viết chương trình của mình một cách đồng bộ, chức năng bạn viết để tải xuống dữ liệu sẽ chạy liên tục cho đến khi tất cả dữ liệu được tải xuống. Điều này có nghĩa là giao diện người dùng của bạn sẽ không làm mới và về cơ bản sẽ xuất hiện đóng băng. Nếu bạn viết chương trình của mình bằng các cuộc gọi lại, bạn yêu cầu dữ liệu và nói "thực hiện chức năng này khi bạn hoàn thành." Điều này cho phép UI vẫn cho phép người dùng tương tác trong khi tệp đang tải xuống. Khi trang web đã tải xuống xong, chức năng kết quả của bạn (gọi lại) được gọi và bạn có thể xử lý dữ liệu.

Về cơ bản, nó cho phép bạn yêu cầu một cái gì đó và tiếp tục thực hiện trong khi chờ kết quả. Khi kết quả quay lại với bạn thông qua chức năng gọi lại, bạn có thể chọn thao tác nơi nó dừng lại.

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.