Tính đa hình trong lập trình hướng đối tượng

Trong 2 bài trước tất cả chúng ta đã cùng khám phá về tính kế thừa & thực hành làm bài tập về nó. Trong bài này, ta sẽ tiếp tục xem thêm 1 thuộc tính cũng trọng yếu không kém này là tính đa hình trong lập trình hướng đối tượng nhé.

Tính đa hình là gì?

Từ đa hình có nghĩa là có nhiều dạng. Nói một cách dễ dàng, tất cả chúng ta có thể khái niệm đa hình là khả năng của một thông điệp được hiển thị dưới nhiều dạng.

Mình lấy một chẳng hạn thực thế nhé:
Một người cùng một lúc có thể có đặc tính khác nhau. Giống như một người Đấng mày râu song song là một người cha, một người chồng, một nhân sự. Chính vì như vậy, cùng một người sở hữu những hành vi khác nhau trong các tình huống khác nhau. Điều này được gọi là đa hình.

Đa hình được xem là một trong những chức năng trọng yếu của Lập trình hướng đối tượng.

Phân loại đa hình

Trong ngôn từ ₵ ++, tính đa hình cốt yếu được chia thành hai loại:

  • Compile time Polymorphism.
  • Runtime Polymorphism.

Compile time Polymorphism

Tính đa hình này được sử dụng bằng cách nạp chồng hàm hoặc nạp chồng toán tử.

Các bạn có thể xem lại về nạp chồng hàm & nạp chồng toán tử: Tại Đây

Nạp chồng hàm

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

 

#include

using

namespace

std

;

 

class

OOP

{

public

:

    

// Hàm có một tham số

    

void

func

(

int

Ҳ

)

    

{

        

cout

<

<

“value of Ҳ is “

<

<

Ҳ

<

<

endl

;

    

}

 

    

// Hàm cùng tên có một tham số nhưng khác kiểu

    

void

func

(

double

Ҳ

)

    

{

        

cout

<

<

“value of Ҳ is “

<

<

Ҳ

<

<

endl

;

    

}

 

    

// Hàm cùng tên nhưng có 2 tham số

    

void

func

(

int

Ҳ

,

int

y

)

    

{

        

cout

<

<

“value of Ҳ and y is “

<

<

Ҳ

<

<

“, “

<

<

y

<

<

endl

;

    

}

}

;

 

int

main

(

)

{

 

    

OOP

obj

;

 

    

obj

.

func

(

7

)

;

    

obj

.

func

(

9.132

)

;

    

obj

.

func

(

85

,

64

)

;

    

return

;

}

 

Sau thời điểm biên dịch & chạy chương trình, ta thu được kết quả:

1

2

3

4

 

value of Ҳ is 7

value of Ҳ is 9.132

value of Ҳ and y is 85, 64

 

Trong chẳng hạn trên, ta chỉ dùng một hàm duy nhất có tên là func nhưng có thể dùng được cho 3 tình huống khác nhau. Đây là một trổ tài của tính đa hình.

Nạp chồng toán tử

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

 

#include

using

namespace

std

;

 

class

SoPhuc

{

private

:

    

int

thuc

,

ao

;

 

public

:

    

SoPhuc

(

int

thuc

=

,

int

ao

=

)

    

{

        

this

>

thuc

=

thuc

;

        

this

>

ao

=

ao

;

    

}

 

    

~

SoPhuc

(

)

    

{

        

this

>

thuc

=

;

        

this

>

ao

=

;

    

}

 

    

SoPhuc

operator

+

(

SoPhuc

const

&obj)

    {

        SoPhuc res;

        

res

.

thuc

=

thuc

+

obj

.

thuc

;

        

res

.

ao

=

ao

+

obj

.

ao

;

        

return

res

;

    

}

 

    

void

print

(

)

{

cout

<

<

this

>

thuc

<

<

” + “

<

<

this

>

ao

<

<

“i”

<

<

endl

;

}

}

;

 

int

main

(

)

{

    

SoPhuc

c1

(

10

,

5

)

,

c2

(

2

,

4

)

;

    

SoPhuc

c3

=

c1

+

c2

;

    

c3

.

print

(

)

;

 

    

return

;

}

 

Trong chẳng hạn trên, ta đã nạp chồng lại toán tử cộng.

Khái niệm của toán tử cộng chỉ dùng cho số nguyên int, nhưng sau thời điểm nạp chồng lại, ta có thể sử dụng chúng cho số phức.

Đây cũng là một trổ tài của tính đa hình.

Runtime Polymorphism

Tính đa hình được trổ tài ở cách nạp chồng toán tử trong kế thừa.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

 

#include

using

namespace

std

;

 

class

base

{

public

:

    

virtual

void

print

(

)

    

{

        

cout

<

<

“print base class”

<

<

endl

;

    

}

 

    

void

show

(

)

    

{

        

cout

<

<

“show base class”

<

<

endl

;

    

}

}

;

 

class

derived

:

public

base

{

public

:

    

void

print

(

)

    

{

        

cout

<

<

“print derived class”

<

<

endl

;

    

}

 

    

void

show

(

)

    

{

        

cout

<

<

“show derived class”

<

<

endl

;

    

}

}

;

int

main

(

)

{

    

base *

bptr

;

    

derived

{d}

;

    

bptr

=

&{d};

 

    

bptr

>

print

(

)

;

    

bptr

>

show

(

)

;

 

    

return

;

}

 

Sau thời điểm biên dịch & chạy chương trình ta có kết quả

1

2

3

 

print derived class

show base class

 

Vì sao lại có sự độc đáo ấy? Vì sao cùng là nạp chồng toán tử trong lớp kế thừa nhưng kết quả lại khác nhau?

Trong chẳng hạn trên mình đã thêm keyword virtual vào hàm print() trong lớp nền tảng base.
Keyword virtual này dùng để khai báo một hàm là hàm ảo.

Khi khai báo hàm ảo với keyword virtual nghĩa là hàm này sẽ được gọi theo loại đối tượng được trỏ (hoặc tham chiếu), chứ không phải theo loại của con trỏ (hoặc tham chiếu). & điều này kéo theo kết quả khác nhau:

  • Nếu như không khai báo hàm ảo virtual trình biên dịch sẽ gọi hàm tại lớp cở sở base
  • Nếu dùng hàm ảo virtual trình biên dịch sẽ gọi hàm tại lớp dẫn xuất derived
Mục đích của hàm ảo là gì?

Các hàm ảo sẽ cho phép tất cả chúng ta tạo một mục lục các con trỏ lớp nền tảng & các cách thức của bất kỳ lớp dẫn xuất nào mà không cần biết loại đối tượng của lớp dẫn xuất.

Mình lấy một chẳng hạn rõ ràng nhé:

Ta sẽ khởi đầu với một software làm chủ nhân sự.

Trước tiên, ta sẽ xây dựng một lớp Nhanvien sau đó xây dựng các hàm ảo tangluong(), chuyenphong(), …
Từ lớp Nhanvien này ta sẽ cho kế thừa tới các lớp Baove, NhanvienphongA, NhanvienphongB, … & hiển nhiên các lớp này có thể triển khai tách biệt các hàm ảo có tại lớp nền tảng Nhanvien.

Trình biên dịch sẽ thực hiện Runtime Polymorphism như vậy nào?

Trình biên dịch sẽ duy trì:

  • vtable: Đây là một bảng các con trỏ hàm được duy trì cho mỗi lớp
  • vptr: Đây là một con trỏ tới vtable & được duy trì cho mỗi một đối tượng.
    Bạn có thể xem một chẳng hạn dễ dàng: Tại đây

Runtime Polymorphism

Trình biên dịch sẽ thêm code bổ sung tại 2 chỗ là:

Code trong mỗi hàm khởi tạo. Nó sẽ khởi tạo vptr của đối tượng được tạo & đăt vptr trỏ đến vtable của lớp.

Code với lệnh gọi hàm ảo. Tại bất kể nơi đâu tính đa hình được thực hiện, trình biên dịch sẽ chèn code để tìm vptr trước bằng cách dùng con trỏ hoặc tham chiếu lớp nền tảng. Khi vptr được nạp, vptable của lớp dẫn xuất có thể được truy cập. Sử dụng vtable, địa chỉ của hàm ảo tại lớp dẫn xuất sẽ được truy cập & gọi.

Vậy thì đây có phải là một cách thực hiện Runtime polymorphism chuẩn hay không?

Trên thực tiễn thì ₵++ không bắt buộc một Runtime polymorphism chạy đúng đắn như vậy này. Nhưng trình biên dịch thường sử dụng các mô hình với biến thể nhỏ, dựa vào mô hình căn bản bên trên.

Hàm Pure Virtual trong ₵++

Với Pure Virtual nghĩa là bạn chỉ dùng hàm ảo tại lớp nền tảng để khai báo, chứ không có bất kể câu lệnh nào bên trong hàm đó.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

 

#include

using

namespace

std

;

 

class

base

{

public

:

    

virtual

void

print

(

)

;

// Pure Virtual

    

void

show

(

)

    

{

        

cout

<

<

“show base class”

<

<

endl

;

    

}

}

;

 

class

derived

:

public

base

{

public

:

    

void

print

(

)

    

{

        

cout

<

<

“print derived class”

<

<

endl

;

    

}

 

    

void

show

(

)

    

{

        

cout

<

<

“show derived class”

<

<

endl

;

    

}

}

;

 

int

main

(

)

{

    

base *

bptr

;

    

derived

{d}

;

    

bptr

=

&{d};

 

    

bptr

>

print

(

)

;

    

bptr

>

show

(

)

;

 

    

return

;

}

 

Trong đoạn code trên, mình đã sửa hàm print() thành một Pure Virtual & hiển nhiên kết quả vẫn chẳng hề biến đổi.

Nội dung của mình đến đây là hết rồi, mình rất mong thu được những quan điểm của chúng ta để nội dung của mình ngày một tốt hơn. Chính vì như thế đừng ngần ngại bình luận bất kể khúc mắc, hay đóng góp nào tại phần comment ngay phía dưới nhé. Cảm ơn mọi người rất là nhiều. Hẹn hội ngộ các bạn trong nội dung kế tiếp.

Ebook xem qua


[Hướng đối tượng C++] Đa hình là gì ?(Phần 1) – Bí quyết ảo


Source code: http://123link.pw/7Nrv

Tham khảo thêm nội dung thuộc chuyên đề: Thủ thuật máy tính
Xem Thêm  [NEW] Luyện Nói Tiếng Anh Chủ Đề Thời Tiết Để Thoải Mái Tán Gẫu | hỏi về thời tiết - Pickpeup

Viết một bình luận