Hàm ảo và lớp ảo trong C++

Hàm ảo giống như một hàm quá tải, nhưng điều đặc biệt ở đây là: kiểu dữ liệu, số lượng và kiểu dữ liệu của các tham số của hàm ảo ở cả hai lớp cơ sở và dẫn xuất đều như nhau ! Hàm ảo là thành phần của một lớp, được khai báo trong lớp cơ sở rồi nhưng lại được khai báo lại trong lớp dẫn xuất với từ khoá “virtual” ở đằng trước. Không phải tự dưng người ta làm điều này vô ích, hàm ảo tạo nên tính đa hình trong c++ là bởi vì có thể nói một “giao diện” cho nhiều phương thức. Có nghĩa là với một thông điệp đó thôi mà  cho nhiều kết quả khác nhau khi con trỏ của lớp cơ sở trỏ đến đối tượng của một lớp nào đó (không nói đến con trỏ của lớp dẫn xuất vì nó không thể trỏ đến đối tượng của lớp cơ sở được !). Chúng ta gọi hàm ảo này bằng con trỏ vừa được nhắc đến ở trên. Một ví dụ cho hàm ảo:

#include<iostream>

using namespace std;

class Base

{

-int x;

public:

-virtual void setvalue(int i){x=i;cout<<“\n Lop co so;”;}

—-virtual int getvalue(){return x;}

};

class Derived:public Base

{

-int y;

public:

—-void setvalue(int i){y=i;cout<<“\n Lop dan xuat”;}

—-int getvalue(){return y;}

};

void main()

{

—-Base*p, o1;

—-Derived obj, *p1;

—-p=&obj; // con trỏ lớp cơ sở trỏ đến đối tượng của lớp dẫn xuất

—-p->setvalue(5); //nhận giá trị của lớp dẫn xuất

p=&o1; //trỏ đến con trỏ trong cùng lớp cơ sở

—-p->setvalue(5); //nhận giá trị của lớp cơ sở

—-cin.get();

}

Chạy thử chương trình sẽ biết ngay điều lý thú này.

Còn một điều đáng chú ý nữa, hàm ảo này có thể không được định nghĩa trong lớp cơ sở mà chỉ được khai báo thôi! VD: virtual void setvalue(int i)=0. Thế lúc này chúng ta gọi nó là “hàm ảo thuần tuý”. Lớp cơ sở mà chứa nó thì vô tình được là lớp cơ sở ảo, và lớp ảo này không thể có đối tượng của riêng mình, tuy nhiên chúng vẫn có thể có con trỏ (vd: base*P), con trỏ này sẽ trỏ đến các đối tượng của các lớp dẫn xuất, từ đó cũng tạo nên tính đa hình. Điều nữa là lớp nào mà kế thừa lớp này thì nhất định phải định nghĩa các hàm ảo thuần tuý này, nếu không trình biên dịch sẽ báo lỗi. Trường hợp có 3 lớp Base, Derived, Derived1 mà Base là lớp cơ sở ảo, Derived thừa kế Base, Derived1 lại thừa kế Derived, lúc này lớp Derived phải định nghĩa hàm ảo thuần tuý thì không nói làm gì nhưng lớp Derived1 nếu muốn con trỏ của lớp Base trỏ đến đối tượng của mình mà cho kế quá đúng thì hiển nhiên cũng phải định nghĩa lại hàm ảo thuần tuý này (mặc dù trình biên dich không báo lỗi khi không định nghĩa lại).Viết khai báo hàm ảo thuần tuý như sau:virtual kiểu_hàm tên_hàm(danh sách đối số) = 0; VD:

#include<iostream>
using namespace std;
class Base
{
—-int x;
public:
—-virtual void setvalue(int i)=0;
—-virtual int getvalue()=0;
};
class Derived1:public Base{
—-int y;
public:
—-void setvalue(int i){y=i;cout<<“\n Lop dan xuat 1”;}
—-int getvalue(){return y;}
};
class Derived2:public Derived1{
—-int z;

public:
—-void setvalue(int i){z=i;cout<<“\n Lop dan xuat 2”;}
—-int getvalue(){return z;}
};

void main(){
—-Base*p;
//    Base o1; //sai vì lớp cơ sở ảo không có đối tượng riêng của nó
—-Derived1 obj;
—-Derived2 o2;
—-p=&obj; //trỏ đến đối tượng của lớp dẫn xuất thứ 1
—-p->setvalue(5);
—-p=&o2; //trỏ đến đối tượng của lớp dẫn xuất thứ 2
—-p->setvalue(6);//kết quả sẽ là: “lớp dẫn xuất 2” nếu ta không định nghĩa lại ở Derived1
—-cin.get();
}

24 thoughts on “Hàm ảo và lớp ảo trong C++

  1. em mới học C++ nên đến phần này hơi khó hiểu , đọc của anh thì cũng hiểu , nhưng em vẫn không thấy cái cần thiết khi viết cái hàm ảo , rõ ràng ta có thể làm một phương thức của hàm ảo trong class mà. đằng nào mỗi lần gọi nó ta lại khai báo mà..anh có thể giải thích rõ hơn được không!!

  2. hehe. Gió quên kiến thức C++ từ ngày nhảy sang làm Java. Cho nên giờ ai hỏi gì lại lục cục xem lại. Hic, Kiến thức rồi sẽ theo thời gian trôi đi mãi, chỉ còn tấm bằng ở lại. :))
    NHưng thầy đã dạy, văn hóa là cái khi rũ bỏ hết mọi thứ nó vẫ còn ở lại. Lấy điều này lằm động viên vậy. Xin lỗi vì không trả lời bạn, bạn chịu khó đọc lại bài này nha.

  3. ban oi co the cho minh hoi ve mot bai tap ve ham ao duoc khong nhung minh lai sap thi roi, sang mai minh thi roi, mong ban se nhan duoc va lien he minh qua so dt: 01693227497
    tai vi minh lam i chan ban chi nhung co dieu no cu bao loi minh khong the kiem soat duoc nhung loi ay mong ban cao tay chi cho minh nha! cam on ban nhiu lam

  4. Đọc xong cái bài này thấy ớn, ông muốn giải thích cho người ta hieeurm hay muốn chứng tỏ ông alf giáo sư ? Nếu muốn chứng tỏ ông là giáo sư thì next sớm hộ cái, nếu đã post bài thì làm sao cho nguwoif ngu nhất cũng phải hiểu, cái phần này có cái địt gì mà ông cứ làm nó phức tạp nên thế !

  5. Hi bạn!
    Sao phải nặng lời vậy chứ. Đây là một vấn đề khó trong lập trình C++ mà không phải ai cũng hiểu được.
    Mình viết những gì mình đã làm để chia sẻ cho mọi người nếu có gặp vấn đề thì cùng xem và có thể bài viết của mình có tác dụng với con đường học tập của họ thôi chứ cũng ko kỳ vọng là ai đọc cũng sẽ hiểu được.

  6. Trường hợp có 3 lớp Base, Derived, Derived1 mà Base là lớp cơ sở ảo, Derived thừa kế Base, Derived1 lại thừa kế Derived, lúc này lớp Derived phải định nghĩa hàm ảo thuần tuý thì không nói làm gì nhưng lớp Derived1 nếu muốn con trỏ của lớp Base trỏ đến đối tượng của mình mà cho kết quả đúng thì hiển nhiên cũng phải định nghĩa lại hàm ảo thuần tuý này (mặc dù trình biên dich không báo lỗi khi không định nghĩa lại)
    Em thay doan vi du van chua dinh nghia lai ham ao thuan tuy phai ko a?
    neu co thi cac lop dan xuat 1 va 2 phai co virtual o trc cac ham chu a?

  7. cảm ơn bạn nhiều nhé. Nhờ có bài viết này của bạn mà mình tiếp cận rất nhanh, giải đáp được thắc mắc của mình chỉ trong vài phút. Cảm ơn bạn thật nhiều vì đã giành thời gian quý báu của mình cho mọi người.

  8. bạn Tung comment that ra minh cung thay rat buc xuc cho tac gia, thế nên mình cũng xin đưa ra ý kiến của mình. Nếu bạn cảm thấy k hợp để mình đọc thì chắc nhiều trang khác và giáo trình khác viết cho bạn dễ hiểu hơn, ban nên đi tìm thêm, sao lại comment thiếu tính xây dựng quá.

  9. Nên viết tên hàm vs lấy ví dụ hàm tiếng việt gần gũi thôi cho dễ hiểu bạn ạ. Với newbie mơi đọc code rất loạn giữa tên hàm để tiếng anh và câu lệnh🙂

  10. Trường hợp có 3 lớp Base, Derived, Derived1 mà Base là lớp cơ sở ảo, Derived thừa kế Base, Derived1 lại thừa kế Derived, lúc này lớp Derived phải định nghĩa hàm ảo thuần tuý thì không nói làm gì nhưng lớp Derived1 nếu muốn con trỏ của lớp Base trỏ đến đối tượng của mình mà cho kết quả đúng thì hiển nhiên cũng phải định nghĩa lại hàm ảo thuần tuý này (mặc dù trình biên dich không báo lỗi khi không định nghĩa lại)

    Chỉ cần thêm virtual ở class Derived1 là hợp lý rồi,tại lớp Derived2 không có lớp dẫn xuất.

  11. Hay lắm,, tớ mới học phương thức ảo , tính đa hình thấy khó, nhưng đọc xong bài viết trên giúp tớ hiểu ra vấn đề!@@/ thank bạn

  12. Em còn giữ được đam mê này thật là quý và hiếm (trong sách đỏ có tên em rồi đấy).
    Những khái niệm công nghệ khó hiểu mấy cũng đều xuất phát từ cuộc sống tự nhiên và quay lại điều khiển tự nhiên. Mỗi khi gặp vấn đề như vậy, các bạn hãy cố tìm hiểu nó bắt nguồn từ đâu, nó mô phỏng lại điều gì trong tự nhiên.
    Quay lại khái niệm “ảo” trong lập trình, các bạn phải đặt nó trong bối cảnh chung về “Lập trình hướng đối tượng – OOP”.
    Tại sao cần khái niệm “lớp-class”? Câu trả lời là các đối tượng chúng ta đang quản lý và điều khiển dù chúng có hàng tỷ tỷ thuộc tính vật lý và hành vi khác nhau (ví dụ đối tượng Sinh Viên ???). Trong phạm vi mỗi bài toán chúng ta luôn chỉ quan tâm tới một tập hữu hạn các thuộc tính và hành vi mà thôi. Chúng ta làm điều này trong lập trình OOP bằng cách mô tả một lớp-class có các thuộc tính và hành vi chung. Mỗi khi cần một đối tượng xuất hiện trong thế giới, chúng ta chỉ việc khai báo nó qua các constructor với giá trị các thuộc tính chỉ đặc điểm riêng – các đối tượng trong cùng lớp phân biệt với nhau bởi giá trị các thuộc tính.
    Tại sao phải có “kế thừa”? Câu trả lời là thuyết tiến hóa các bạn ạ, từ đơn giản đến phức tạp, từ khái quát đến chi tiết cụ thể, từ cái chung đến cái riêng. Đặc điểm của thế giới này là biến đổi liên tục, cái mới ra đời nhưng vẫn có kế thừa cái cũ. Hơn nữa sự biến dạng không bao giờ theo một hướng (một ông bố có nhiều con ấy mà) và không chỉ là sao chép – các con thường chỉ có một vài nét giống bố thôi. Các lớp đối tượng cũng vậy. Tưởng tượng khi chương trình chạy, chúng ta có một quần thể các đối tượng nằm trong các chi-họ-lớp khác nhau, tương tác với nhau qua các hành vi. Hành vi của mỗi đối tượng làm thay đổi các giá trị thuộc tính- tức trạng thái của chính nó (chứ không phải hành vi của đối tượng thuộc lớp khác). Đối tượng lớp A muốn thay đổi trạng thái của một đối tượng thuộc lớp B thì phải thông qua các phương thức của lớp B hoặc lớp B cho phép thay đổi đối với các thuộc tính public của nó. Hiểu được điều này làm cho thế giới đối tượng trở nên mạch lạc hơn, dễ kiểm soát hơn.

    Ví dụ vui: Có hai lớp đối tượng A và B cùng có 2 phương thức ĐẤM() và ĐAU().
    ở lớp A ta định nghĩa:
    void ĐAU()
    {
    Mặt = “Sưng”.
    Kêu(“Ối giời ơi tôi đau quá! Cứu tôi”) // phương thức Kêu.
    }
    void ĐẤM(object BiDam)
    {
    BiDam.ĐAU();
    }

    ở lớp B:
    void ĐAU()
    {
    Kêu(“Hehe, đánh tẩm quất hả”)
    }
    Khi A gặp B đang chở người yêu của mình đi chơi, A thấy a cay liền gọi hành vi:
    A.ĐẤM(B);

    Anh A nghĩ B sẽ đau như mình nhưng không phải vậy đúng không các bạn.

    Khi làm ứng dụng theo phương pháp OOP, các bạn sẽ nhanh chóng tìm ra nguyên nhân và fix lỗi vì bạn biết rất rõ ai gây ra hành vi dẫn tới các thuộc tính bị thay đổi không theo ý muốn.

    Tại sao lại có “đa hình”? Câu trả lời là trong quá trình kế thừa, cùng một hành vi nhưng biều hiện trên các hậu duệ sẽ khác nhau. Bởi vậy chúng ta cần một lớp “Cơ sở – Template – Base” để mà khai báo những gì có thể bị biến dạng ở các lớp kế thừa, một cách thức để cài đặt tính đa dạng của tự nhiên vào môi trường kỹ thuật cứng nhắc thôi mà. Khái niệm “ảo” ra đời chỉ những lớp đối tượng hoặc hành vi (phương thức- method) không thể xuất hiện trực tiếp, chúng sẽ được các lớp kế thừa khai báo lại và định nghĩa cụ thể nội dung của hành vi.
    Nhờ khả năng “ép kiểu – nhập hồn” của một đối tượng lớp cơ sở, chúng “nhập” vào lớp dẫn xuất nào thì tác động của hành vi theo lớp đó đã định nghĩa. Ví dụ: Giả sử có một linh hồn có một hành vi “Chào”, khi nhập vào người Việt thì nói “Chào bạn”, nhập vào người Anh nói “Hello”. Liệu được không các bạn??? hay trừ khi hồn ma đó đã được học tiếng Anh và Tiếng Việt ???. Câu trả lời là linh hồn KHÔNG cần học một ngôn ngữ nào. Khi gặp người quen, linh hồn sẽ biết nó đang nhập vào ai và gọi phương thức “Chào” của người đó là xong, phát ra từ thế nào chính là nội dung phương thức “Chào” được cài đặt vào mỗi lớp người (Anh, Việt,…). Làm sao để kết nối linh hồn (lớp cơ sở) với các lớp người (lớp dẫn xuất) – đây là công việc của ngôn ngữ lập trình và trình biên dịch.

    Khi đã thông suốt các khái niệm này, vấn đề còn lại của các bạn là tuân thủ cú pháp mà ngôn ngữ quy định để nói ý tưởng của mình với máy tính.

    Chúc các bạn thành công.

  13. Nhờ khả năng “ép kiểu – nhập hồn” của một đối tượng lớp cơ sở, chúng “nhập” vào lớp dẫn xuất nào thì tác động của hành vi theo lớp đó đã định nghĩa.

    Chỉ cần câu này thôi là mình hiểu ý nghĩa của virtual rồi. Cám ơn Biensau

  14. //bai thuc hanh C++ da hinh ham ao
    //Nguyen Dinh Duy
    //B12DCPT015
    #include
    #include
    #include
    using namespace std;
    class truong;
    class ngaythang;
    class truong{
    private :
    char tentruong[25];
    public:
    void hienthi();
    char *gettentruong(){
    return tentruong;
    }
    void nhap1(){
    cout<<"\nNhap ten truong:";fflush(stdin);cin.getline(tentruong,25);
    }
    };
    void truong::hienthi(){
    cout<<"\nTen Truong:"<<tentruong;
    }
    class ngaythang{
    private :
    int ngay,thang,nam;
    };
    class lop:public truong{
    private: char tenlop[25];
    public:
    char *gettenlop(){
    return tenlop;
    }
    void hienthi();
    void nhap2(){
    nhap1();
    cout<<"\nNhap ten lop:";fflush(stdin);cin.getline(tenlop,25);
    }
    };
    void lop::hienthi(){
    cout<<"\nTen truong:"<<gettentruong();
    cout<<"\nTen lop:"<<tenlop;
    }
    class sinhvien:public lop,public ngaythang {
    private: char tensv[25],diachi[25];
    public:
    char *getdiachi(){
    return diachi;
    }
    void hienthi(){
    cout<<"\nTen truong:"<<gettentruong();
    cout<<"\nLop hoc:"<<gettenlop();
    cout<<"\nDia chi:"<<diachi;
    }
    void nhap3(){
    nhap2();
    cout<<"\nNhap dia chi:";fflush(stdin);cin.getline(diachi,25);
    }
    };
    int main(){
    sinhvien a;
    a.nhap3();
    a.hienthi();
    return 0;
    }

    hàm hienthi() đang là chồng hàm . Câu 2: cô giáo yêu cầu xây dựng hàm hiển thị thành hàm ảo . và viết chưởng trình nhập và hiển thị 5 sinh viên thể hiện tính đa hình . Em ko hiểu phải làm thế nào ạ mong anh chỉ giúp em

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s