Câu trả lời:
Từ chức năng ảo của Wikipedia ...
Trong lập trình hướng đối tượng, trong các ngôn ngữ như C ++ và Object Pascal, một hàm ảo hoặc phương thức ảo là một hàm hoặc phương thức có thể thừa kế và có thể ghi đè. Khái niệm này là một phần quan trọng của phần đa hình (thời gian chạy) của lập trình hướng đối tượng (OOP). Nói tóm lại, một hàm ảo định nghĩa một hàm mục tiêu sẽ được thực thi, nhưng mục tiêu có thể không được biết đến tại thời điểm biên dịch.
Không giống như một hàm không ảo, khi một hàm ảo bị ghi đè, phiên bản có nguồn gốc nhất được sử dụng ở tất cả các cấp của hệ thống phân cấp lớp, thay vì chỉ ở mức mà nó được tạo. Do đó, nếu một phương thức của lớp cơ sở gọi một phương thức ảo, phiên bản được định nghĩa trong lớp dẫn xuất sẽ được sử dụng thay vì phiên bản được định nghĩa trong lớp cơ sở.
Điều này trái ngược với các hàm không ảo, vẫn có thể bị ghi đè trong lớp dẫn xuất, nhưng phiên bản "mới" sẽ chỉ được sử dụng bởi lớp dẫn xuất và bên dưới, nhưng sẽ không thay đổi chức năng của lớp cơ sở.
trong khi..
Hàm ảo thuần túy hoặc phương thức ảo thuần túy là một hàm ảo được yêu cầu thực hiện bởi lớp dẫn xuất nếu lớp dẫn xuất không trừu tượng.
Khi một phương thức ảo thuần túy tồn tại, lớp "trừu tượng" và không thể tự khởi tạo. Thay vào đó, một lớp dẫn xuất thực hiện (các) phương thức thuần ảo phải được sử dụng. Một ảo thuần không được định nghĩa trong lớp cơ sở, vì vậy một lớp dẫn xuất phải định nghĩa nó hoặc lớp dẫn xuất đó cũng trừu tượng và không thể khởi tạo được. Chỉ có một lớp không có phương thức trừu tượng có thể được khởi tạo.
Một ảo cung cấp một cách để ghi đè chức năng của lớp cơ sở và một ảo thuần đòi hỏi nó.
pure
từ khóa, nhưng Bell Labs sắp phát hành C ++ và người quản lý của anh ấy sẽ không cho phép nó ở giai đoạn cuối. Thêm từ khóa là một vấn đề lớn.
Tôi muốn bình luận về định nghĩa ảo của Wikipedia, như được lặp lại bởi một số ở đây. [Tại thời điểm câu trả lời này được viết,] Wikipedia đã định nghĩa một phương thức ảo là một phương thức có thể được ghi đè trong các lớp con. [May mắn thay, Wikipedia đã được chỉnh sửa từ đó và bây giờ nó giải thích điều này một cách chính xác.] Điều đó không chính xác: bất kỳ phương thức nào, không chỉ là ảo, đều có thể bị ghi đè trong các lớp con. Những gì ảo làm là cung cấp cho bạn tính đa hình, nghĩa là khả năng chọn trong thời gian chạy ghi đè có nguồn gốc nhất của một phương thức .
Hãy xem xét các mã sau đây:
#include <iostream>
using namespace std;
class Base {
public:
void NonVirtual() {
cout << "Base NonVirtual called.\n";
}
virtual void Virtual() {
cout << "Base Virtual called.\n";
}
};
class Derived : public Base {
public:
void NonVirtual() {
cout << "Derived NonVirtual called.\n";
}
void Virtual() {
cout << "Derived Virtual called.\n";
}
};
int main() {
Base* bBase = new Base();
Base* bDerived = new Derived();
bBase->NonVirtual();
bBase->Virtual();
bDerived->NonVirtual();
bDerived->Virtual();
}
Đầu ra của chương trình này là gì?
Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.
Xuất phát ghi đè mọi phương thức của Base: không chỉ ảo mà còn không ảo.
Chúng tôi thấy rằng khi bạn có Base-trỏ-to-Derogen (bDerive), gọi NonVirtual gọi thực hiện lớp Base. Điều này được giải quyết tại thời gian biên dịch: trình biên dịch thấy rằng bDerive là một Base *, NonVirtual không phải là ảo, do đó, nó thực hiện độ phân giải trên lớp Base.
Tuy nhiên, gọi Virtual gọi thực hiện lớp Derogen. Do từ khóa ảo, việc lựa chọn phương thức xảy ra vào thời gian chạy , không phải thời gian biên dịch. Điều xảy ra ở đây vào thời gian biên dịch là trình biên dịch thấy rằng đây là Base * và nó đang gọi một phương thức ảo, vì vậy nó chèn một cuộc gọi đến vtable thay vì lớp Base. Vtable này được khởi tạo tại thời gian chạy, do đó độ phân giải thời gian chạy thành ghi đè có nguồn gốc nhất.
Tôi hy vọng điều này không quá khó hiểu. Nói tóm lại, bất kỳ phương thức nào cũng có thể bị ghi đè, nhưng chỉ có các phương thức ảo cung cấp cho bạn tính đa hình, nghĩa là lựa chọn thời gian chạy của ghi đè có nguồn gốc nhất. Tuy nhiên, trên thực tế, việc ghi đè một phương thức không ảo được coi là thực tiễn xấu và hiếm khi được sử dụng, vì vậy nhiều người (bao gồm cả những người đã viết bài viết trên Wikipedia) nghĩ rằng chỉ có các phương thức ảo mới có thể bị ghi đè.
Derived*
với các cuộc gọi chức năng tương tự để lái xe về nhà điểm. Nếu không thì câu trả lời tuyệt vời
Từ khóa ảo cung cấp cho C ++ khả năng hỗ trợ đa hình. Khi bạn có một con trỏ tới một đối tượng của một số lớp, chẳng hạn như:
class Animal
{
public:
virtual int GetNumberOfLegs() = 0;
};
class Duck : public Animal
{
public:
int GetNumberOfLegs() { return 2; }
};
class Horse : public Animal
{
public:
int GetNumberOfLegs() { return 4; }
};
void SomeFunction(Animal * pAnimal)
{
cout << pAnimal->GetNumberOfLegs();
}
Trong ví dụ (ngớ ngẩn) này, hàm GetNumberOfLegs () trả về số thích hợp dựa trên lớp của đối tượng mà nó được gọi.
Bây giờ, hãy xem xét chức năng 'Một số chức năng'. Nó không quan tâm loại vật thể động vật nào được truyền cho nó, miễn là nó có nguồn gốc từ Động vật. Trình biên dịch sẽ tự động chuyển bất kỳ lớp có nguồn gốc Động vật nào sang Động vật vì nó là lớp cơ sở.
Nếu chúng ta làm điều này:
Duck d;
SomeFunction(&d);
nó sẽ xuất '2'. Nếu chúng ta làm điều này:
Horse h;
SomeFunction(&h);
nó sẽ xuất '4'. Chúng tôi không thể làm điều này:
Animal a;
SomeFunction(&a);
bởi vì nó sẽ không biên dịch do hàm ảo GetNumberOfLegs () là thuần túy, có nghĩa là nó phải được thực hiện bằng cách lấy các lớp (các lớp con).
Hàm ảo thuần túy hầu hết được sử dụng để định nghĩa:
a) các lớp trừu tượng
Đây là các lớp cơ sở nơi bạn phải xuất phát từ chúng và sau đó thực hiện các hàm ảo thuần túy.
b) giao diện
Đây là các lớp 'trống' trong đó tất cả các hàm là thuần ảo và do đó bạn phải rút ra và sau đó thực hiện tất cả các hàm.
Trong một lớp C ++, ảo là từ khóa chỉ định rằng, một phương thức có thể được ghi đè (tức là được thực hiện bởi) một lớp con. Ví dụ:
class Shape
{
public:
Shape();
virtual ~Shape();
std::string getName() // not overridable
{
return m_name;
}
void setName( const std::string& name ) // not overridable
{
m_name = name;
}
protected:
virtual void initShape() // overridable
{
setName("Generic Shape");
}
private:
std::string m_name;
};
Trong trường hợp này, một lớp con có thể ghi đè hàm initShape để thực hiện một số công việc chuyên biệt:
class Square : public Shape
{
public:
Square();
virtual ~Square();
protected:
virtual void initShape() // override the Shape::initShape function
{
setName("Square");
}
}
Thuật ngữ thuần ảo dùng để chỉ các hàm ảo cần được thực hiện bởi một lớp con và chưa được lớp cơ sở triển khai. Bạn chỉ định một phương thức là thuần ảo bằng cách sử dụng từ khóa ảo và thêm a = 0 vào cuối khai báo phương thức.
Vì vậy, nếu bạn muốn tạo Shape :: initShape thuần ảo, bạn sẽ làm như sau:
class Shape
{
...
virtual void initShape() = 0; // pure virtual method
...
};
Bằng cách thêm một phương thức ảo thuần túy vào lớp của bạn, bạn tạo cho lớp một lớp cơ sở trừu tượng rất tiện dụng để tách các giao diện khỏi việc thực hiện.
m_name
. Có m_
nghĩa là gì?
"Ảo" có nghĩa là phương thức có thể bị ghi đè trong các lớp con, nhưng có một triển khai có thể gọi trực tiếp trong lớp cơ sở. "Pure virtual" có nghĩa là nó là một phương thức ảo không có triển khai có thể gọi trực tiếp. Một phương thức như vậy phải được ghi đè ít nhất một lần trong hệ thống phân cấp thừa kế - nếu một lớp có bất kỳ phương thức ảo nào chưa được thực hiện, các đối tượng của lớp đó không thể được xây dựng và quá trình biên dịch sẽ thất bại.
@quark chỉ ra rằng các phương thức thuần ảo có thể có một triển khai, nhưng vì các phương thức thuần ảo phải bị ghi đè, nên việc thực hiện mặc định không thể được gọi trực tiếp. Dưới đây là một ví dụ về phương thức thuần ảo với mặc định:
#include <cstdio>
class A {
public:
virtual void Hello() = 0;
};
void A::Hello() {
printf("A::Hello\n");
}
class B : public A {
public:
void Hello() {
printf("B::Hello\n");
A::Hello();
}
};
int main() {
/* Prints:
B::Hello
A::Hello
*/
B b;
b.Hello();
return 0;
}
Theo ý kiến, việc biên dịch có thất bại hay không là do trình biên dịch cụ thể. Trong GCC 4.3.3 ít nhất, nó sẽ không biên dịch:
class A {
public:
virtual void Hello() = 0;
};
int main()
{
A a;
return 0;
}
Đầu ra:
$ g++ -c virt.cpp
virt.cpp: In function ‘int main()’:
virt.cpp:8: error: cannot declare variable ‘a’ to be of abstract type ‘A’
virt.cpp:1: note: because the following virtual functions are pure within ‘A’:
virt.cpp:3: note: virtual void A::Hello()
Làm thế nào để từ khóa ảo hoạt động?
Giả sử rằng Con người là một lớp cơ sở, Ấn Độ có nguồn gốc từ con người.
Class Man
{
public:
virtual void do_work()
{}
}
Class Indian : public Man
{
public:
void do_work()
{}
}
Khai báo do_work () dưới dạng ảo đơn giản có nghĩa là: mà do_work () sẽ gọi sẽ được xác định CHỈ vào thời gian chạy.
Giả sử tôi làm
Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.
Nếu ảo không được sử dụng, tương tự được xác định tĩnh hoặc bị ràng buộc tĩnh bởi trình biên dịch, tùy thuộc vào đối tượng nào đang gọi. Vì vậy, nếu một đối tượng của Man gọi do_work (), Man's do_work () được gọi là NGAY CẢ NHỮNG ĐIỂM NÀO ĐỐI VỚI MỘT ĐỐI TƯỢNG ẤN ĐỘ
Tôi tin rằng câu trả lời được bình chọn hàng đầu là sai lệch - Bất kỳ phương thức nào dù ảo hay không đều có thể có một triển khai được ghi đè trong lớp dẫn xuất. Với tham chiếu cụ thể đến C ++, sự khác biệt chính xác là thời gian chạy (khi sử dụng ảo) ràng buộc và thời gian biên dịch (khi ảo không được sử dụng nhưng một phương thức bị ghi đè và một con trỏ cơ sở được trỏ vào một đối tượng dẫn xuất) liên kết các hàm liên quan.
Dường như có một bình luận sai lệch khác nói rằng,
"Justin, 'thuần ảo' chỉ là một thuật ngữ (không phải từ khóa, xem câu trả lời của tôi bên dưới) được sử dụng có nghĩa là" chức năng này không thể được thực hiện bởi lớp cơ sở. "
CÁI NÀY SAI! Các chức năng ảo hoàn toàn cũng có thể có một cơ thể VÀ CÓ THỂ THỰC HIỆN! Sự thật là hàm ảo thuần của một lớp trừu tượng có thể được gọi là tĩnh! Hai tác giả rất giỏi là Bjarne Stroustrup và Stan Lippman .... vì họ đã viết ngôn ngữ này.
Hàm ảo là một hàm thành viên được khai báo trong lớp cơ sở và được xác định lại bởi lớp dẫn xuất. Hàm ảo được phân cấp theo thứ tự kế thừa. Khi một lớp dẫn xuất không ghi đè một hàm ảo, hàm được định nghĩa trong lớp cơ sở của nó được sử dụng.
Hàm ảo thuần túy là một hàm không chứa định nghĩa liên quan đến lớp cơ sở. Nó không có thực hiện trong lớp cơ sở. Bất kỳ lớp dẫn xuất nào cũng phải ghi đè hàm này.
Simula, C ++ và C #, sử dụng ràng buộc phương thức tĩnh theo mặc định, lập trình viên có thể chỉ định các phương thức cụ thể đó nên sử dụng liên kết động bằng cách gắn nhãn chúng là ảo. Liên kết phương thức động là trung tâm của lập trình hướng đối tượng.
Lập trình hướng đối tượng đòi hỏi ba khái niệm cơ bản: đóng gói, kế thừa và ràng buộc phương thức động.
Đóng gói cho phép các chi tiết thực hiện của một sự trừu tượng được ẩn đằng sau một giao diện đơn giản.
Kế thừa cho phép một sự trừu tượng mới được định nghĩa là một phần mở rộng hoặc sàng lọc của một số trừu tượng hiện có, có được một số hoặc tất cả các đặc tính của nó một cách tự động.
Liên kết phương thức động cho phép sự trừu tượng mới hiển thị hành vi mới của nó ngay cả khi được sử dụng trong bối cảnh mong đợi sự trừu tượng cũ.
Các phương thức ảo CÓ THỂ được ghi đè bằng cách tạo các lớp, nhưng cần một triển khai trong lớp cơ sở (một lớp sẽ bị ghi đè)
Các phương thức ảo thuần túy không có triển khai lớp cơ sở. Chúng cần được định nghĩa bởi các lớp dẫn xuất. (Vì vậy, kỹ thuật bị ghi đè không phải là thuật ngữ đúng, vì không có gì để ghi đè).
Ảo tương ứng với hành vi java mặc định, khi lớp dẫn xuất ghi đè một phương thức của lớp cơ sở.
Các phương thức Pure Virtual tương ứng với hành vi của các phương thức trừu tượng trong các lớp trừu tượng. Và một lớp chỉ chứa các phương thức và hằng ảo thuần túy sẽ là giá trị cpp cho một Giao diện.
Chức năng ảo thuần túy
thử mã này
#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{
public:
virtual void sayHellow()=0;
};
class anotherClass:aClassWithPureVirtualFunction
{
public:
void sayHellow()
{
cout<<"hellow World";
}
};
int main()
{
//aClassWithPureVirtualFunction virtualObject;
/*
This not possible to create object of a class that contain pure virtual function
*/
anotherClass object;
object.sayHellow();
}
Trong lớp AnotherClass loại bỏ hàm sayHellow và chạy mã. Bạn sẽ gặp lỗi! Bởi vì khi một lớp chứa một hàm ảo thuần túy, không có đối tượng nào có thể được tạo từ lớp đó và nó được kế thừa thì lớp dẫn xuất của nó phải thực hiện chức năng đó.
Chức năng ảo
thử mã khác
#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{
public:
virtual void sayHellow()
{
cout<<"from base\n";
}
};
class anotherClass:public aClassWithPureVirtualFunction
{
public:
void sayHellow()
{
cout<<"from derived \n";
}
};
int main()
{
aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
baseObject->sayHellow();///call base one
baseObject=new anotherClass;
baseObject->sayHellow();////call the derived one!
}
Ở đây, hàm sayHellow được đánh dấu là ảo trong lớp cơ sở. Nó nói trình biên dịch thử tìm kiếm hàm trong lớp dẫn xuất và thực hiện hàm. Nếu không tìm thấy thì thực hiện cơ sở một. Cảm ơn
"Hàm ảo hoặc phương thức ảo là một hàm hoặc phương thức có hành vi có thể bị ghi đè trong một lớp kế thừa bởi một hàm có cùng chữ ký" - wikipedia
Đây không phải là một lời giải thích tốt cho các chức năng ảo. Bởi vì, ngay cả khi một thành viên không ảo, các lớp kế thừa có thể ghi đè lên nó. Bạn có thể thử và xem nó cho mình.
Sự khác biệt thể hiện chính nó khi một hàm lấy một lớp cơ sở làm tham số. Khi bạn cung cấp một lớp kế thừa làm đầu vào, hàm đó sử dụng lớp thực hiện của lớp cơ sở của hàm overriden. Tuy nhiên, nếu hàm đó là ảo, nó sử dụng hàm được triển khai trong lớp dẫn xuất.
Các hàm ảo phải có một định nghĩa trong lớp cơ sở và cả trong lớp dẫn xuất nhưng không cần thiết, ví dụ hàm ToString () hoặc toString () là một Virtual để bạn có thể cung cấp triển khai của riêng mình bằng cách ghi đè nó trong (các) lớp do người dùng định nghĩa.
Các hàm ảo được khai báo và định nghĩa trong lớp bình thường.
Hàm ảo thuần túy phải được khai báo kết thúc bằng "= 0" và nó chỉ có thể được khai báo trong lớp trừu tượng.
Một lớp trừu tượng có (các) hàm ảo thuần túy không thể có định nghĩa về các hàm ảo thuần đó, do đó, nó ngụ ý rằng việc triển khai phải được cung cấp trong (các) lớp xuất phát từ lớp trừu tượng đó.