Tôi đã có câu trả lời của mình dưới dạng một cuộc trò chuyện để được đọc tốt hơn:
Tại sao chúng ta cần các chức năng ảo?
Vì đa hình.
Đa hình là gì?
Thực tế là một con trỏ cơ sở cũng có thể trỏ đến các đối tượng loại dẫn xuất.
Làm thế nào để định nghĩa về đa hình này dẫn đến sự cần thiết của các chức năng ảo?
Vâng, thông qua ràng buộc sớm .
Liên kết sớm là gì?
Liên kết sớm (liên kết thời gian biên dịch) trong C ++ có nghĩa là một lệnh gọi hàm được cố định trước khi chương trình được thực thi.
Vì thế...?
Vì vậy, nếu bạn sử dụng một loại cơ sở làm tham số của hàm, trình biên dịch sẽ chỉ nhận ra giao diện cơ sở và nếu bạn gọi hàm đó với bất kỳ đối số nào từ các lớp dẫn xuất, nó sẽ bị cắt, đó không phải là điều bạn muốn xảy ra.
Nếu đó không phải là những gì chúng ta muốn xảy ra, tại sao điều này được cho phép?
Bởi vì chúng ta cần đa hình!
Lợi ích của đa hình là gì?
Bạn có thể sử dụng một con trỏ kiểu cơ sở làm tham số của một hàm duy nhất và sau đó trong thời gian chạy chương trình của bạn, bạn có thể truy cập từng giao diện loại dẫn xuất (ví dụ: các hàm thành viên của chúng) mà không gặp vấn đề gì, sử dụng chức năng hủy bỏ hội nghị của đơn đó con trỏ cơ sở.
Tôi vẫn không biết chức năng ảo nào tốt cho ...! Và đây là câu hỏi đầu tiên của tôi!
tốt, điều này là do bạn đã hỏi câu hỏi của bạn quá sớm!
Tại sao chúng ta cần các chức năng ảo?
Giả sử rằng bạn đã gọi một hàm với một con trỏ cơ sở, có địa chỉ của một đối tượng từ một trong các lớp dẫn xuất của nó. Như chúng ta đã nói về nó ở trên, trong thời gian chạy, con trỏ này bị hủy đăng ký, tuy nhiên, rất tốt, chúng tôi hy vọng một phương thức (== một hàm thành viên) "từ lớp dẫn xuất của chúng ta" sẽ được thực thi! Tuy nhiên, một phương thức tương tự (một phương thức có cùng tiêu đề) đã được xác định trong lớp cơ sở, vậy tại sao chương trình của bạn phải bận tâm chọn phương thức khác? Nói cách khác, ý tôi là, làm thế nào bạn có thể nói ra kịch bản này từ những gì chúng ta thường thấy trước đây thường xảy ra?
Câu trả lời ngắn gọn là "một hàm thành viên ảo trong cơ sở" và câu trả lời dài hơn một chút là, "ở bước này, nếu chương trình thấy một hàm ảo trong lớp cơ sở, nó biết (nhận ra) rằng bạn đang cố gắng sử dụng Đa hình "và do đó đi đến các lớp dẫn xuất (sử dụng bảng v , một dạng liên kết muộn) để tìm ra một phương thức khác có cùng tiêu đề, nhưng với một cách bất ngờ - một cách thực hiện khác.
Tại sao thực hiện khác nhau?
Bạn gõ đầu! Đi đọc một cuốn sách hay !
OK, chờ đợi, chờ đợi, tại sao người ta lại bận tâm sử dụng các con trỏ cơ sở, khi anh ta / cô ta chỉ có thể sử dụng các con trỏ loại dẫn xuất? Bạn là thẩm phán, tất cả đau đầu này có đáng không? Nhìn vào hai đoạn này:
// 1:
Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();
// 2:
Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();
OK, mặc dù tôi nghĩ rằng 1 vẫn tốt hơn 2 , bạn cũng có thể viết 1 như thế này:
// 1:
Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();
và hơn nữa, bạn nên lưu ý rằng đây vẫn chỉ là một cách sử dụng giả tạo cho tất cả những điều tôi đã giải thích cho bạn cho đến nay. Thay vì điều này, giả sử ví dụ một tình huống trong đó bạn có một hàm trong chương trình đã sử dụng các phương thức từ mỗi lớp dẫn xuất tương ứng (getMonthBenefit ()):
double totalMonthBenefit = 0;
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
totalMonthBenefit += x -> getMonthBenefit();
}
Bây giờ, hãy cố gắng viết lại này, mà không có bất kỳ đau đầu!
double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();
Và trên thực tế, đây cũng có thể là một ví dụ giả định!