Tính kế thừa trong C++ (C++ Inheritance) | Ironhack Việt Nam

Tóm Tắt

1. Tính kế thừa trong C++

Kế thừa là một trong những tính năng chính của lập trình hướng đối tượng trong C++. Nó cho phép chúng ta tạo một lớp mới (lớp con – derived class) từ một lớp hiện có (lớp cơ sở – base class).

ke thua trong c++Lớp con kế thừa những tính năng từ lớp cơ sở và hoàn toàn có thể có những tính năng bổ trợ của riêng nó. Ví dụ :
class Animal {

// eat() function

/ / sleep ( ) function
} ;
class Dog : public Animal {
/ / bark ( ) function
} ;
Ở đây, lớp Dog có nguồn gốc từ lớp Animal. Vì Dog có nguồn gốc từ Aminal nên những thành phần của Animal đều hoàn toàn có thể truy vấn vào Dog .
https://cdn.programiz.com/sites/tutorial2program/files/cpp-inheritance.png
Lưu ý về việc sử dụng từ khóa public trong kế thừa Dog từ Animal .
class Dog : public Animal { … } ;
Ta cũng hoàn toàn có thể sử dụng những từ khóa private và protected thay vì public .
Chúng ta sẽ khám phá về sự độc lạ giữa việc sử dụng private, public và protected sau trong bài viết này .

1.1. Quan hệ is-a

Kế thừa là một quan hệ is-a. Ta sử dụng kế thừa chỉ khi có một mối quan hệ is-a giữa hai lớp .
Dưới đây là 1 số ít ví dụ :

  • A car is a vehicle
  • Orange is a fruit
  • A surgeon is a doctor
  • A dog is an animal

Ví dụ 1: Ví dụ đơn giản về tính kế thừa trong C++

/ / C + + program to demonstrate inheritance

#include

using namespace std ;
/ / base class
class Animal {
public :
void eat ( ) {
cout < < “ I can eat ! ” < < endl ; } void sleep ( ) { cout < < “ I can sleep ! ” < < endl ; } } ; / / derived class class Dog : public Animal { public : void bark ( ) { cout < < “ I can bark ! Woof woof ! ! ” < < endl ; } } ; int main ( ) { / / Create object of the Dog class Dog dog1 ; / / Calling members of the base class dog1.eat ( ) ; dog1.sleep ( ) ; / / Calling thành viên of the derived class dog1.bark ( ) ; return 0 ; } Đầu ra I can eat ! I can sleep ! I can bark ! Woof woof ! ! Ở đây, dog1 ( đối tượng người dùng của lớp con Dog ) hoàn toàn có thể truy vấn những thành phần của lớp cơ sở Animal. Đó là vì Dog được kế thừa từ Animal . / / Calling members of the Animal class dog1.eat ( ) ; dog1.sleep ( ) ;

1.2. Protected trong C++

Công cụ sửa đổi truy cập protected đặc biệt liên quan đến tính kế thừa trong C++.

Giống như private, protected không hề truy vấn được ở bên ngoài lớp. Tuy nhiên, chúng hoàn toàn có thể được truy vấn bởi những lớp con và những lớp / hàm friend .
Chúng ta cần có protected nếu ta muốn ẩn tài liệu của một lớp nhưng vẫn muốn tài liệu đó được kế thừa bởi những lớp con của nó .

Ví dụ 2: Protected trong C++

/ / C + + program to demonstrate protected members

#include

#include

using namespace std ;
/ / base class
class Animal {
private :
string color ;
protected :
string type ;
public :
void eat ( ) {
cout < < “ I can eat ! ” < < endl ; } void sleep ( ) { cout < < “ I can sleep ! ” < < endl ; } void setColor ( string clr ) { color = clr ; } string getColor ( ) { return color ; } } ; / / derived class class Dog : public Animal { public : void setType ( string tp ) { type = tp ; } void displayInfo ( string c ) { cout < < “ I am a ” < < type < < endl ; cout < < “ My color is ” < < c < < endl ; } void bark ( ) { cout < < “ I can bark ! Woof woof ! ! ” < < endl ; } } ; int main ( ) { / / Create object of the Dog class Dog dog1 ; / / Calling members of the base class dog1.eat ( ) ; dog1.sleep ( ) ; dog1. setColor ( “ black ” ) ; / / Calling thành viên of the derived class dog1.bark ( ) ; dog1. setType ( “ mammal ” ) ; / / Using getColor ( ) of dog1 as argument / / getColor ( ) returns string data dog1. displayInfo ( dog1. getColor ( ) ) ; return 0 ; } Đầu ra I can eat ! I can sleep ! I can bark ! Woof woof ! ! I am a mammal My color is black Ở đây, kiểu biến là protected, do đó hoàn toàn có thể truy vấn được từ lớp con Dog. Ta hoàn toàn có thể thấy điều này khi khởi tạo kiểu trong lớp Dog bằng cách sử dụng hàm setType ( ) . Mặt khác, biến private color không hề được khởi tạo trong Dog . class Dog : public Animal { public : void setColor ( string clr ) { / / Error : thành viên “ Animal :: color ” is inaccessible color = clr ; } } ; Ngoài ra, vì từ khóa protected ẩn tài liệu nên ta không hề truy vấn kiểu trực tiếp từ một đối tượng người tiêu dùng của lớp Dog hoặc Animal . / / Error : thành viên “ Animal :: type ” is inaccessible dog1.type = “ mammal ” ;

1.3. Chế độ truy cập trong kế thừa C++

Trong những nội dung trước, tất cả chúng ta đã khám phá về những truy vấn như public, private và protected .
Tôi đã sử dụng từ khóa public để kế thừa một lớp từ một lớp cơ sở đã sống sót trước đó. Tuy nhiên, tất cả chúng ta cũng hoàn toàn có thể sử dụng những từ khóa private và protected để kế thừa những lớp. Ví dụ :
class Animal {
/ / code
} ;
class Dog : private Animal {
/ / code
} ;
class Cat : protected Animal {
/ / code
} ;
Cách mà tất cả chúng ta hoàn toàn có thể lấy được những lớp gọi là những chính sách truy vấn. Các chính sách truy vấn này có công dụng :

  • Public: nếu một lớp con được khai báo ở chế độ public thì các thành phần của lớp cơ sở được kế thừa bởi lớp con giống như chúng.
  • Private: trong trường hợp này, tất cả các thành phần của lớp cơ sở đều trở thành private trong lớp con.
  • Protected: các thành phần public của lớp cơ sở trở thành protected trong lớp con.

Các thành phần private của lớp cơ sở luôn là private trong lớp con .

1.4. Ghi đè chức năng trong kế thừa C++

Giả sử, lớp cơ sở và lớp con có những hàm thành phần có cùng tên và những đối số .
Nếu tất cả chúng ta tạo một đối tượng người dùng của lớp con và cố gắng nỗ lực truy vấn hàm thành phần đó, thì hàm thành phần trong lớp con được gọi thay vì hàm trong lớp cơ sở .
Hàm thành phần của lớp con ghi đè hàm thành phần của lớp cơ sở .

2. Kiểm soát truy cập kế thừa trong C++

Trong kế thừa C + +, tất cả chúng ta hoàn toàn có thể dẫn xuất một lớp con từ lớp cơ sở trong những chính sách truy vấn khác nhau. Ví dụ :
class Base {
…. … … .
} ;
class Derived : public Base {
…. … … .
} ;
Chú ý từ khóa public trong code :
class Derived : public Base
Điều này có nghĩa là ta đã tạo một lớp con từ lớp cơ sở ở chính sách public. Ngoài ra, tất cả chúng ta cũng hoàn toàn có thể laays những lớp trong những chính sách protected hoặc private .
Ba từ khóa này ( public, protected và private ) được gọi là chỉ định truy vấn trong kế thừa C + + .
Kế thừa public, protected và private có những tính năng sau :

  • Kế thừa public làm cho các thành phần public của lớp cơ sở là public trong lớp con và các protected của lớp cơ sở cũng là protected trong lớp con.
  • Kế thừa protected làm cho các thành phần public và protected của lớp cơ sở là protected trong lớp con.
  • Kế thừa private làm cho các thành phần public và protected của lớp cơ sở là private trong lớp con.

Lưu ý : private của lớp cơ sở không hề truy vấn được vào lớp con .
class Base {
public :
int x ;
protected :
int y ;
private :
int z ;
} ;

class PublicDerived : public Base {
/ / x is public
/ / y is protected
/ / z is not accessible from PublicDerived
} ;
class ProtectedDerived : protected Base {
/ / x is protected
/ / y is protected
/ / z is not accessible from ProtectedDerived
} ;
class PrivateDerived : private Base {
/ / x is private
/ / y is private
/ / z is not accessible from PrivateDerived
}

Ví dụ 1: Kế thừa public trong C++

/ / C + + program to demonstrate the working of public inheritance

#include

using namespace std ;
class Base {
private :
int pvt = 1 ;
protected :
int prot = 2 ;
public :
int pub = 3 ;
/ / function to access private thành viên
int getPVT ( ) {
return pvt ;
}
} ;
class PublicDerived : public Base {
public :
/ / function to access protected thành viên from Base
int getProt ( ) {
return prot ;
}
} ;
int main ( ) {
PublicDerived object1 ;
cout < < “ Private = ” < < object1. getPVT ( ) < < endl ; cout < < “ Protected = ” < < object1. getProt ( ) < < endl ; cout < < “ Public = ” < < object1.pub < < endl ; return 0 ; } Đầu ra Private = 1 Protected = 2 Public = 3 Ở đây, tôi đã dẫn xuất PublicDerived từ Base ở chính sách public . Do đó, tròn PublicDervied :

  • prot được kế thừa như protected
  • pub và getPVT() được kế thừa như public
  • pvt không thể truy cập được vì nó là private trong lớp cơ sở (Base).

Vì private và protected không hề truy vấn được, nên tôi cần tạo những hàm public getPVT ( ) và getProt ( ) để truy vấn chúng :
/ / Error : thành viên “ Base :: pvt ” is inaccessible
cout < < “ Private = ” < < object1.pvt ; / / Error : thành viên “ Base :: prot ” is inaccessible cout < < “ Protected = ” < < object1.prot ;

Khả năng tiếp cận trong kế thừa Public

Khả năng tiếp cận Private Protected Public
Lớp cơ sở
Lớp con Không

Ví dụ 2: Kế thừa protected trong C++

/ / C + + program to demonstrate the working of protected inheritance

#include

using namespace std ;

class Base {
private :
int pvt = 1 ;

protected :
int prot = 2 ;

public :
int pub = 3 ;

/ / function to access private thành viên
int getPVT ( ) {
return pvt ;
}
} ;

class ProtectedDerived : protected Base {
public :
/ / function to access protected thành viên from Base
int getProt ( ) {
return prot ;
}

/ / function to access public thành viên from Base
int getPub ( ) {
return pub ;
}
} ;

int main ( ) {
ProtectedDerived object1 ;
cout < < “ Private cannot be accessed. ” < < endl ; cout < < “ Protected = ” < < object1. getProt ( ) < < endl ; cout < < “ Public = ” < < object1. getPub ( ) < < endl ; return 0 ; } Đầu ra Private cannot be accessed . Protected = 2 Public = 3 Ở đây, tôi đã dẫn xuất ProtectedDerived từ Base ở chính sách protected . Kết quả là, trong ProtectedDerived :

  • Prot, pub và getPVT() được kế thừa như protected
  • Pvt không thể truy cập vì nó là private trong lớp cơ sở (Base).

Như tất cả chúng ta biết, những protected không hề truy vấn trực tiếp. Do đó, tôi không hề sử dụng getPVT ( ) từ ProtectedDerived. Đó cũng là nguyên do tại sao tôi cần tạo hàm getPub ( ) trong ProtectedDerived để truy vấn biến pub .
/ / Error : thành viên “ Base :: getPVT ( ) ” is inaccessible
cout < < “ Private = ” < < object1. getPVT ( ) ; / / Error : thành viên “ Base :: pub ” is inaccessible cout < < “ Public = ” < < object1.pub ;

Khả năng tiếp cận trong kế thừa protected 

Khả năng tiếp cận Private Protected Public
Lớp cơ sở
Lớp con Không Có (kế thừa dưới dạng biến protected)

Ví dụ 3: Kế thừa private trong C++

/ / C + + program to demonstrate the working of private inheritance

#include

using namespace std ;

class Base {
private :
int pvt = 1 ;

protected :
int prot = 2 ;

public :
int pub = 3 ;

/ / function to access private thành viên
int getPVT ( ) {
return pvt ;
}
} ;

class PrivateDerived : private Base {
public :
/ / function to access protected thành viên from Base
int getProt ( ) {
return prot ;
}

/ / function to access private thành viên
int getPub ( ) {
return pub ;
}
} ;

int main ( ) {
PrivateDerived object1 ;
cout < < “ Private cannot be accessed. ” < < endl ; cout < < “ Protected = ” < < object1. getProt ( ) < < endl ; cout < < “ Public = ” < < object1. getPub ( ) < < endl ; return 0 ; } Đầu ra Private cannot be accessed . Protected = 2 Public = 3 Ở đây, tôi đã dẫn xuất PrivateDerived từ Base ở chính sách private . Do đó, trong PricvateDerived :

  • Prot, pub và getPVT() được kế thừa dưới dạng private.
  • Pvt không thể truy cập được vì nó là private trong lớp cơ sở.

Như tất cả chúng ta biết. Không thể truy vấn trực tiếp từ private. Do đó, tôi không hề sử dụng getPVT ( ) từ PrivateDerived. Đó cũng là nguyên do tại sao tôi cần tạo hàm getPub ( ) trong PrivateDerived để truy vấn biến pub .
/ / Error : thành viên “ Base :: getPVT ( ) ” is inaccessible
cout < < “ Private = ” < < object1. getPVT ( ) ; / / Error : thành viên “ Base :: pub ” is inaccessible cout < < “ Public = ” < < object1.pub ;

Khả năng tiếp cận trong kế thừa private

Khả năng tiếp cận Private Protected Public
Lớp cơ sở
Lớp con Không Có (kế thừa dưới dạng biến private) Có (kế thừa dưới dạng biến private)

3. Ghi đè chức năng trong C++

Như tất cả chúng ta đã biết, kế thừa là một tính năng của OOP được cho phép tất cả chúng ta tạo những lớp con từ một lớp cơ sở. Các lớp con kế thừa những tính năng của lớp cơ sở .
Giả sử, khi một hàm cùng được định nghĩa trong cả lớp con và lớp cơ sở, nếu tôi gọi hàm này bằng cách sử dụng đối tượng người dùng của lớp con, thì hàm của lớp con sẽ được thực thi .
Đây được gọi là ghi đè hàm trong C + +. Hàm trong lớp con sẽ ghi đè hàm trong lớp cơ sở .

Ví dụ: Ghi đè hàm trong C++

/ / C + + program to demonstrate function overriding

#include

using namespace std ;

class Base {
public :
void print ( ) {
cout < < “ Base Function ” < < endl ; } } ; class Derived : public Base { public : void print ( ) { cout < < “ Derived Function ” < < endl ; } } ; int main ( ) { Derived derived1 ; derived1.print ( ) ; return 0 ; } Đầu ra Derived Function Ở đây, hàm print ( ) cùng được định nghĩa trong cả lớp cơ sở và lớp con . Vì vậy, khi tôi gọi print ( ) từ đối tượng người dùng con derived1, print ( ) từ Derived được thực thi bằng cách ghi đè hàm trong Base . https://cdn.programiz.com/sites/tutorial2program/files/cpp-function-overriding.png

Truy cập hàm ghi đè trong C++

Để truy vấn hàm ghi đè của lớp cơ sở, tất cả chúng ta sử dụng toán tử phân giải khoanh vùng phạm vi :: .
Ta hoàn toàn có thể truy vấn hàm được ghi đè bằng cách sử dụng một pointer của lớp cơ sở để trỏ đến một đối tượng người dùng của lớp con và sau đó gọi hàm từ pointer đó .

Ví dụ 2: Truy cập hàm ghi đè vào lớp cơ sở trong C++

/ / C + + program to access overridden function
/ / in main ( ) using the scope resolution operator ::

#include

using namespace std ;

class Base {
public :
void print ( ) {
cout < < “ Base Function ” < < endl ; } } ; class Derived : public Base { public : void print ( ) { cout < < “ Derived Function ” < < endl ; } } ; int main ( ) { Derived derived1, derived2 ; derived1.print ( ) ; / / access print ( ) function of the Base class derived2. Base :: print ( ) ; return 0 ; } Đầu ra Derived Function Base Function Ở đây, câu lệnh derived2. Base :: print ( ) ; Truy cập hàm print ( ) của lớp cơ sở . https://cdn.programiz.com/sites/tutorial2program/files/cpp-access-overridden-function-using-object.png

Ví dụ 3: Gọi hàm ghi đè từ lớp con trong C++

/ / C + + program to call the overridden function
/ / from a thành viên function of the derived class

#include

using namespace std ;

class Base {
public :
void print ( ) {
cout < < “ Base Function ” < < endl ; } } ; class Derived : public Base { public : void print ( ) { cout < < “ Derived Function ” < < endl ; / / call overridden function Base :: print ( ) ; } } ; int main ( ) { Derived derived1 ; derived1.print ( ) ; return 0 ; } Đầu ra Derived Function Base Function Trong chương trình này, tôi đã gọi hàm ghi đè trong lớp Derived class Derived : public Base { public : void print ( ) { cout < < “ Derived Function ” < < endl ; Base :: print ( ) ; } } ; Cần quan tâm rằng code Base :: print ( ) ; gọi hàm ghi đè bên trong lớp Derived . https://cdn.programiz.com/sites/tutorial2program/files/cpp-access-overridden-function-inside-derived-class.png

Ví dụ 4: Gọi hàm ghi đè bằng cách dùng Pointer trong C++

/ / C + + program to access overridden function using pointer
/ / of Base type that points to an object of Derived class

#include

using namespace std ;

class Base {
public :
void print ( ) {
cout < < “ Base Function ” < < endl ; } } ; class Derived : public Base { public : void print ( ) { cout < < “ Derived Function ” < < endl ; } } ; int main ( ) { Derived derived1 ; / / pointer of Base type that points to derived1 Base * ptr = và derived1 / / call function of Base class using ptr ptr -> print ( ) ;

return 0 ;
}
Đầu ra
Base Function
Trong chương trình này, tôi đã tạo một pointer loại Base tên ptr. Pointer này trỏ đến đối tượng người dùng derived1 .
/ / pointer of Base type that points to derived1
Base * ptr = và derived1
Khi tôi gọi hàm print ( ) bằng ptr, nó sẽ gọi hàm ghi đè từ Base .
/ / call function of Base class using ptr
ptr -> print ( ) ;
Mặc dù ptr trỏ đến một đối tượng người tiêu dùng con nhưng thuộc kiểu cơ sở nên nó đã gọi hàm thành viên của Base .
Để ghi đè hàm Base thay vì truy vấn nó, tất cả chúng ta cần sử dụng những hàm ảo trong lớp cơ sở .

4. Kế thừa đa cấp và đa kế thừa trong C++ (multiple và multilevel)

Kế thừa là một trong những tính năng cốt lõi của ngôn từ lập trình hướng đối tượng người dùng. Nó được cho phép những nhà tăng trưởng ứng dụng lấy ra một lớp mới từ lớp hiện có. Trong đó lớp con lại kế thừa những tính năng của lớp cơ sở ( lớp hiện có ) .

Trong lập trình C++, có nhiều mô hình tính kế thừa trong C++ khác nhau.

4.1. Kế thừa đa cấp trong C++

Trong lập trình C + +, bạn không chỉ hoàn toàn có thể dẫn xuất một lớp con từ lớp cơ sở mà bạn còn hoàn toàn có thể dẫn xuất một lớp khác từ lớp con. Hình thức kế thừa này được gọi là kế thừa đa cấp .
class A
{
… .. …
} ;
class B : public A
{
… .. …
} ;
class C : public B
{
… … …
} ;
Ở đây, lớp B có nguồn gốc từ lớp cơ sở A và lớp C được dẫn xuất từ lớp con B .

Ví dụ 1: Kế thừa đa cấp trong C++

#include

using namespace std ;

class A
{
public :
void display ( )
{
cout < < “ Base class content. ” ; } } ; class B : public A { } ; class C : public B { } ; int main ( ) { C obj ; obj.display ( ) ; return 0 ; } Đầu ra Base class content . Trong chương trình này, lớp C có nguồn gốc từ lớp B ( được dẫn xuất từ lớp cơ sở A ) . Đối tượng obj của lớp C được định nghĩa trong hàm main ( ) . Khi hàm display ( ) được gọi, hàm display ( ) trong lớp A được thực thi. Đó là vì không có hàm display ( ) trong lớp C và lớp B . Đầu tiên, trình biên dịch tìm kiếm hàm display ( ) trong lớp C. Vì hàm không sống sót ở đó nên nó sẽ tìm hàm trong lớp B ( vì C có nguồn gốc từ B ) . Hàm cũng không sống sót trong lớp B, thế cho nên trình biên dịch sẽ tìm kiếm nó trong lớp A ( vì B có nguồn gốc từ A ) . Nếu hàm display ( ) sống sót trong C, trình biên dịch sẽ ghi đè hàm display ( ) của lớp A ( công dụng ghi đè hàm thành viên )

4.2. Đa kế thừa trong C++

Trong lập trình C + +, một lớp hoàn toàn có thể được bắt nguồn từ nhiều hơn một lớp gốc. Ví dụ : một lớp Bat có nguồn gốc từ những lớp cơ sở Mammal và WingedAnimal. Nó hài hòa và hợp lý chính bới dơi là động vật hoang dã có vú và cũng là động vật hoang dã có cánh .
https://cdn.programiz.com/sites/tutorial2program/files/multiple-inheritance-example.jpg

Ví dụ: Đa kế thừa trong C++

#include

using namespace std ;

class Mammal {
public :
Mammal ( )
{
cout < < “ Mammals can give direct birth. ” < < endl ; } } ; class WingedAnimal { public : WingedAnimal ( ) { cout < < “ Winged animal can flap. ” < < endl ; } } ; class Bat : public Mammal, public WingedAnimal { } ; int main ( ) { Bat b1 ; return 0 ; } Đầu ra Mammals can give direct birth . Winged animal can flap .

Sự không rõ ràng trong đa kế thừa C++

Vấn đề rõ ràng nhất trong đa kế thừa diễn ra trong quy trình ghi đè hàm .
Giả sử, hai lớp cơ sở có cùng một tính năng không bị ghi đè trong lớp con .
Nếu bạn nỗ lực gọi hàm bằng cách sử dụng đối tượng người dùng của lớp con, trình biên dịch sẽ hiển thị lỗi. Đó là vì trình biên dịch không biết gọi hàm nào. Ví dụ :
class base1
{
public :
void someFunction ( )
{ …. … …. }
} ;
class base2
{
void someFunction ( )
{ …. … …. }
} ;
class derived : public base1, public base2
{
} ;

int main ( )

{

derived obj ;

obj. someFunction ( ) / / Error !
}
Vấn đề này hoàn toàn có thể được xử lý bằng cách sử dụng hàm phân giải khoanh vùng phạm vi để chỉ định hàm nào sẽ phân loại, là base1 hoặc base2 .
int main ( )
{
obj. base1 :: someFunction ( ) ; / / Function of base1 class is called
obj. base2 :: someFunction ( ) ; / / Function of base2 class is called .
}

5. Hàm Friend trong C++

Ẩn tài liệu là một khái niệm cơ bản của lập trình hướng đối tượng người dùng. Nó hạn chế quyền truy vấn của những thành phần private từ bên ngoài lớp .
Tương tự, những protected chỉ hoàn toàn có thể được truy vấn bởi cá lớp con và không hề truy vấn từ bên ngoài. Ví dụ :
class MyClass {
private :
int member1 ;
}

int main ( ) {
MyClass obj ;

/ / Error ! Cannot access private members from here .
obj. member1 = 5 ;
}
Tuy nhiên, có một tính năng trong C + + được gọi là hàm friend giúp phá vỡ quy tắc này và được cho phép tất cả chúng ta truy vấn những hàm thành phần từ bên ngoài lớp .
Tương tự, cũng có một lớp friend mà tất cả chúng ta sẽ khám phá ở phần sau của bài .

5.1. Hàm friend trong C++

Hàm friend hoàn toàn có thể truy vấn tài liệu private và protected của một lớp. Ta hoàn toàn có thể khai báo hàm friend bằng cách sử dụng từ khóa friend bên trong phần thân của lớp .
class className {
… .. …
friend returnType functionName ( arguments ) ;
… .. …
}

Ví dụ 1: Hoạt động của hàm friend

/ / C + + program to demonstrate the working of friend function

#include

using namespace std ;

class Distance {
private :
int meter ;
/ / friend function
friend int addFive ( Distance ) ;

public :
Distance ( ) : meter ( 0 ) { }
} ;

/ / friend function definition
int addFive ( Distance d ) {

/ / accessing private members from the friend function
d.meter + = 5 ;
return d.meter ;
}

int main ( ) {
Distance D ;
cout < < “ Distance : ” < < addFive ( D ) ; return 0 ; } Đầu ra Distance : 5 Ở đây, addFive ( ) là một hàm friend hoàn toàn có thể truy vấn cả tài liệu private và public . Mặc dù ví dụ này giúp tất cả chúng ta hiểu về khái niệm hàm friend nhưng không cho ta thấy cách sử dụng có ý nghĩa nào của hàm . Hàm friend sẽ có ý nghĩa hơn khi hoạt động giải trí trên những đối tượng người dùng của hai lớp khác nhau .

Ví dụ 2: Thêm thành phần của hai lớp khác nhau

/ / Add members of two different classes using friend functions

#include

using namespace std ;

/ / forward declaration
class ClassB ;

class ClassA {
public :
/ / constructor to initialize numA to 12
ClassA ( ) : numA ( 12 ) { }
private :
int numA ;
/ / friend function declaration
friend int add ( ClassA, ClassB ) ;
} ;

class ClassB {

public :
/ / constructor to initialize numB to 1
ClassB ( ) : numB ( 1 ) { }
private :
int numB ;
/ / friend function declaration
friend int add ( ClassA, ClassB ) ;
} ;

/ / access members of both classes
int add ( ClassA objectA, ClassB objectB ) {
return ( objectA. numA + objectB. numB ) ;
}

int main ( ) {
ClassA objectA ;
ClassB objectB ;
cout < < “ Sum : ” < < add ( objectA, objectB ) ; return 0 ; } Đầu ra Sum : 13 Trong chương trình này, ClassA và ClassB đã khai báo add ( ) như một hàm friend. Do đó, hàm này hoàn toàn có thể truy vấn tài liệu private của cả hai lớp . Một điều cần chú ý quan tâm ở đây là hàm friend bên trong ClassA đang sử dụng ClassB. Tuy nhiên, tôi chưa xác lập ClassB ở thời gian này . / / inside classA friend int add ( ClassA, ClassB ) ; Để thao tác này hoạt động giải trí, tôi cần khai báo ClassB trước trong chương trình . / / forward declaration class ClassB ;

5.2. Lớp friend trong C++

Ta cũng hoàn toàn có thể dùng lớp friend trong C + + bằng cách dùng từ khóa friend. Ví dụ :
class ClassB ;

class ClassA {
/ / ClassB is a friend class of ClassA
friend class ClassB ;
… .. …
}

class ClassB {
… .. …
}
Khi một lớp friend được khai báo, toàn bộ những hàm thành phần của lớp friend đều trở thành hàm friend .
Vì ClassB là một lớp friend nên tôi hoàn toàn có thể truy vấn tổng thể những thành phần của classA từ bên trong classB .
Tuy nhiên, tôi không hề truy vấn những thành phần của classB từ bên trong classA. Đó là vì quan hệ friend trong C + + chỉ được cấp, không được lấy .

Ví dụ 3: Lớp friend trong C++

/ / C + + program to demonstrate the working of friend class

#include

using namespace std ;

/ / forward declaration
class ClassB ;

class ClassA {
private :
int numA ;

/ / friend class declaration
friend class ClassB ;

public :
/ / constructor to initialize numA to 12
ClassA ( ) : numA ( 12 ) { }
} ;

class ClassB {
private :
int numB ;

public :
/ / constructor to initialize numB to 1
ClassB ( ) : numB ( 1 ) { }
/ / thành viên function to add numA
/ / from ClassA and numB from ClassB
int add ( ) {
ClassA objectA ;
return objectA. numA + numB ;
}
} ;

int main ( ) {
ClassB objectB ;
cout < < “ Sum : ” < < objectB. add ( ) ; return 0 ; } Đầu ra Sum : 13 Ở đây, ClassB là một lớp friend của ClassA. Vì vậy, ClassB có quyền truy vấn vào classA . Trong ClassB, tôi đã tạo một hàm add ( ) trả về tổng của numA và numB. Vì classB là một lớp friend nên tôi hoàn toàn có thể tạo những đối tượng người tiêu dùng của classA bên trong classB .

6. Hàm ảo (virtual) trong C++

Hàm ảo là một hàm thành viên trong lớp cơ sở mà ta muốn xác lập lại trong những lớp con .
Về cơ bản, một hàm ảo được sử dụng trong lớp cơ sở để bảo vệ rằng hàm được ghi đè. Điều này đặc biệt quan trọng vận dụng cho những trường hợp một pointer của lớp cơ sở trỏ đến một đối tượng người dùng của lớp con .
Ví dụ :
class Base {
public :
void print ( ) {
/ / code
}
} ;

class Derived : public Base {
public :
void print ( ) {
/ / code
}
} ;
Sau đó, nếu tôi tạo một pointer kiểu Base để trỏ đến một đối tượng người tiêu dùng của lớp con và gọi làm print ( ), nó sẽ gọi hàm print ( ) của lớp cơ sở .
Nói cách khác, hàm thành viên của Base không bị ghi đè .
int main ( ) {
Derived derived1 ;
Base * base1 = và derived1

/ / calls function of Base class
base1 -> print ( ) ;

return 0 ;
}
Để tránh điều này, ta cần khai báo hàm print ( ) của lớp cơ sở là hàm ảo bằng cách sử dụng từ khóa virtual .
class Base {
public :
virtual void print ( ) {
/ / code
}
} ;
Hàm ảo là một phần không hề thiếu của tính đa hình trong C + + .

Ví dụ 1: Hàm ảo trong C++

#include

using namespace std ;

class Base {
public :
virtual void print ( ) {
cout < < “ Base Function ” < < endl ; } } ; class Derived : public Base { public : void print ( ) { cout < < “ Derived Function ” < < endl ; } } ; int main ( ) { Derived derived1 ; / / pointer of Base type that points to derived1 Base * base1 = và derived1 / / calls thành viên function of Derived class base1 -> print ( ) ;

return 0 ;
}
Đầu ra
Derived Function
Ở đây, tôi đã khai báo hàm print ( ) của Base là ảo .
Vì vậy, hàm này bị ghi đè ngay cả khi tôi sử dụng pointer kiểu cơ sở trỏ đến đến đối tượng người dùng con derived1 .
https://cdn.programiz.com/sites/tutorial2program/files/cpp-virtual-function.png

6.1. Ghi đè mã định danh trong C++

C + + 11 đã phân phối cho người dùng công dụng mới rất hữu dụng là ghi đè mã định danh để tránh lỗi khi sử dụng những hàm ảo .
Định danh này chỉ định những hàm thành viên của những lớp con ghi đè hàm thành viên của lớp cơ sở .
Ví dụ :
class Base {
public :
virtual void print ( ) {
/ / code
}
} ;

class Derived : public Base {
public :
void print ( ) override {
/ / code
}
} ;
Nếu tất cả chúng ta sử dụng một nguyên mẫu hàm trong lớp con và xác lập hàm đó bên ngoài lớp thì ta dùng đoạn code sau :
class Derived : public Base {
public :
/ / function prototype
void print ( ) override ;
} ;

/ / function definition
void Derived :: print ( ) {
/ / code
}

Sử dụng ghi đè trong C++

Khi sử dụng những hàm ảo, ta hoàn toàn có thể gặp lỗi khi khai báo những hàm thành viên của những lớp con .
Việc sử dụng mã định danh ghi đè sẽ nhắc trình biên dịch hiển thị thông tin lỗi khi những lỗi này xảy ra .
Nếu không, chương trình sẽ biên dịch đơn thuần nhưng hàm ảo sẽ không bị ghi đè .
Một số lỗi hoàn toàn có thể xảy ra gồm có :

  • Hàm sai tên: ví dụ, nếu hàm ảo trong lớp cơ sở được đặt tên là print(), nhưng chúng ta vô tình đặt tên cho hàm ghi đè trong lớp con là pint().
  • Hàm có kiểu trả về khác nhau: nếu hàm ảo là kiểu void nhưng hàm trong lớp con là kiểu int.
  • Hàm với các tham số khác nhau: nếu các tham số của hàm ảo và các hàm trong lớp con không khớp.
  • Không có hàm ảo nào được khai báo trong lớp cơ sở.

6.2. Sử dụng hàm ảo trong C++

Giả sử ta có một lớp cơ sở Animal và lớp con là Dog và Cat, mỗi lớp có tài liệu thành viên tên là kiểu và những biến này được khởi tạo trải qua những hàm khởi tạo tương ứng .
class Animal {
private :
string type ;
… .. …
public :
Animal ( ) : type ( “ Animal ” ) { }
… .. …
} ;

class Dog : public Animal {
private :
string type ;
… .. …
public :
Animal ( ) : type ( “ Dog ” ) { }
… .. …
} ;

class Cat : public Animal {
private :
string type ;
… .. …
public :
Animal ( ) : type ( “ Cat ” ) { }
… .. …
} ;
Bây giờ, tôi giả sử rằng chương trình nhu yếu ta tạo hai hàm public cho mỗi lớp :

  • getType() để trả về giá trị của kiểu.
  • print() để xuất giá trị của kiểu

Tôi hoàn toàn có thể tạo cả hai hàm này trong mỗi lớp riêng không liên quan gì đến nhau và ghi đè chúng nhưng sẽ rất lâu và nhàm chán .
Hoặc tôi hoàn toàn có thể làm cho getType ( ) ảo trong lớp Animal, sau đó tạo một hàm print ( ) riêng không liên quan gì đến nhau đồng ý một pointer kiểu Animal làm đối số của nó. Sau đó, tôi hoàn toàn có thể sử dụng hàm đơn này để ghi đè hàm ảo .
class Animal {
… .. …
public :
… .. …
virtual string getType { … }
} ;

… .. …
… .. …

void print ( Animal * ani ) {
cout < < “ Animal : ” < < ani -> getType ( ) < < endl ; } Điều này sẽ làm cho code ngắn hơn, rõ ràng hơn và ít lặp lại hơn .

Ví dụ 2: Hàm ảo C++

/ / C + + program to demonstrate the use of virtual function

#include

#include

using namespace std ;

class Animal {
private :
string type ;

public :
/ / constructor to initialize type
Animal ( ) : type ( “ Animal ” ) { }

/ / declare virtual function
virtual string getType ( ) {
return type ;
}
} ;

class Dog : public Animal {
private :
string type ;

public :
/ / constructor to initialize type
Dog ( ) : type ( “ Dog ” ) { }

string getType ( ) override {
return type ;
}
} ;

class Cat : public Animal {
private :
string type ;

public :
/ / constructor to initialize type
Cat ( ) : type ( “ Cat ” ) { }

string getType ( ) override {
return type ;
}
} ;

void print ( Animal * ani ) {
cout < < “ Animal : ” < < ani -> getType ( ) < < endl ; } int main ( ) { Animal * animal1 = new Animal ( ) ; Animal * dog1 = new Dog ( ) ; Animal * cat1 = new Cat ( ) ; print ( animal1 ) ; print ( dog1 ) ; print ( cat1 ) ; return 0 ; } Đầu ra Animal : Animal Animal : Dog Animal : Cat Ở đây, tôi đã sử dụng hàm ảo getType ( ) và một pointer Animal là ani để tránh lặp lại hàm print ( ) trong mỗi lớp . void print ( Animal * ani ) { cout < < “ Animal : ” < < ani -> getType ( ) < < endl ; } Trong main ( ), tôi đã tạo 3 pointer Animal để tạo những đối tượng người dùng của những lớp Animal, Dog và Cat . / / dynamically create objects using Animal pointers Animal * animal1 = new Animal ( ) ; Animal * dog1 = new Dog ( ) ; Animal * cat1 = new Cat ( ) ; Sau đó, tôi gọi hàm print ( ) bằng cách sử dụng những pointer sau :

  • Khi print(animal1) được gọi, pointer trỏ đến một đối tượng Animal. Vì vậy, hàm ảo trong lớp Animal được thực thi bên trong print().
  • Khi print(dog1) được gọi, pointer trỏ đến một đối tượng Dog. Vì vậy, hàm ảo bị ghi đè và hàm của Dog được thực thi bên trong print().
  • Khi print(cat1) được gọi, pointer trỏ đến một đối tượng Cat. Vì vậy, hàm ảo bị ghi đè và hàm của Cat được được thi bên trong print().

7. Templates trong C++

Templates là tính năng quyền lực tối cao trong C + + cho phép bạn viết những chương trình chung. Nói một cách dễ hiểu, bạn hoàn toàn có thể tạo một hàm hoặc môt lớp để thao tác với những kiểu tài liệu khác nhau bằng cách sử dụng templates .
Templates thường được sử dụng trong cơ sở mã lớn nhằm mục đích mục tiêu tái sử dụng mã và sự linh động của chương trình .
Khái niệm về templates hoàn toàn có thể được chia theo hai cách :

  • Function templates – Khuôn mẫu hàm
  • Class templates – Khuôn mẫu lớp

7.1. Function templates – Khuôn mẫu hàm

Một khuôn mẫu hàm hoạt động giải trí tương tự như như một hàm thông thường nhưng có một độc lạ chính .
Một khuôn mẫu hàm duy nhất hoàn toàn có thể hoạt động giải trí với những kiểu tài liệu khác nhau cùng một lúc, nhưng một hàm thông thường chỉ hoàn toàn có thể hoạt động giải trí với một nhóm kiểu tài liệu .
Thông thường, nếu bạn cần triển khai những thao tác giống hệt nhau trên hai hoặc nhiều kiểu tài liệu, bạn sử dụng tính năng nạp chồng hàm để tạo hai hàm với khai báo hàm được nhu yếu .
Tuy nhiên, bạn hoàn toàn có thể sử dụng những khuôn mẫu hàm vì nó hoàn toàn có thể triển khai cùng một tác vụ với code ít hơn và hoàn toàn có thể duy trì .

Cách khai báo khuôn mẫu hàm

Một khuôn mẫu hàm khởi đầu với từ khóa template, theo sau là tham số / s bên trong ngoặc nhọn < >, và sau đó là khai báo hàm .

template

T someFunction ( T arg )
{
… .. …
}
Trong đoạn mã trên, T là một đối số khuôn mẫu gật đầu những kiểu tài liệu khác nhau ( int, float ) và class là một từ khóa .
Bạn cũng hoàn toàn có thể sử dụng từ khóa typename thay vì class trong ví dụ trên .
Khi một đối số của một kiểu tài liệu được chuyển đến someFunction ( ), trình biên dịch sẽ tạo một phiên bản mới của someFunction ( ) cho kiểu tài liệu đã cho .

Ví dụ 1: Khuôn mẫu hàm tìm kiếm số lớn nhất.

Chương trình hiển thị số lớn nhất trong hai số bằng cách sử dụng khuôn mẫu hàm
/ / If two characters are passed to function template, character with larger ASCII value is displayed .

#include

using namespace std ;
/ / template function

template

T Large ( T n1, T n2 )
{
return ( n1 > n2 ) ? n1 : n2 ;
}
int main ( )
{
int i1, i2 ;
float f1, f2 ;
char c1, c2 ;

cout < < “ Enter two integers : \ n ” ; cin >> i1 >> i2 ;
cout < < Large ( i1, i2 ) < < ” is larger. ” < < endl ; cout < < “ \ nEnter two floating-point numbers : \ n ” ; cin >> f1 >> f2 ;
cout < < Large ( f1, f2 ) < < ” is larger. ” < < endl ; cout < < “ \ nEnter two characters : \ n ” ; cin >> c1 >> c2 ;
cout < < Large ( c1, c2 ) < < ” has larger ASCII value. ” ; return 0 ; } Đầu ra Enter two integers : 5 10 10 is larger . Enter two floating-point numbers : 12.4 10.2 12.4 is larger . Enter two characters : z Z z has larger ASCII value . Trong chương trình trên, một khuôn mẫu hàm Large ( ) được định nghĩa, đồng ý hai đối số n1 và n2 của kiểu tài liệu T. T biểu lộ rằng đối số hoàn toàn có thể thuộc bất kể kiểu tài liệu nào . Hàm Large ( ) trả về giá trị lớn nhất trong số hai đối số bằng cách sử dụng một phép toán có điều kiện kèm theo đơn thuần . Bên trong hàm main ( ), những biến của ba kiểu tài liệu khác nhau : int, float và char được khai báo. Các biến sau đó được chuyển đến khuôn mẫu hàm Large ( ) như những hàm thông thường . Trong thời hạn chạy, khi một số ít nguyên được chuyển đến khuôn mẫu hàm, trình biên dịch biết nó phải tạo ra một hàm Large ( ) để nhận những đối số int nên nó đã làm như vậy . Tương tự, khi tài liệu dấu chấm động và tài liệu char được truyền, nó sẽ biết những kiểu tài liệu đối số và tạo ra hàm Large ( ) cho tương thích . Bằng cách này, chỉ sử dụng một khuôn mẫu hàm duy nhất đã thay thế sửa chữa ba hàm thông thường giống hệt nhau và làm cho mã của bạn được duy trì .

Ví dụ 2: Hoán đổi dữ liệu bằng khuôn mẫu hàm

Chương trình hoán đổi tài liệu sử dụng khuôn mẫu hàm

#include

using namespace std ;

template

void Swap ( T và n1, T và n2 )
{
T temp ;
temp = n1 ;
n1 = n2 ;
n2 = temp ;
}

int main ( )
{
int i1 = 1, i2 = 2 ;
float f1 = 1.1, f2 = 2.2 ;
char c1 = ‘ a ’, c2 = ‘ b ’ ;

cout < < “ Before passing data to function template. \ n ” ; cout < < “ i1 = ” < < i1 < < “ \ ni2 = ” < < i2 ; cout < < “ \ nf1 = ” < < f1 < < “ \ nf2 = ” < < f2 ; cout < < “ \ nc1 = ” < < c1 < < “ \ nc2 = ” < < c2 ; Swap ( i1, i2 ) ; Swap ( f1, f2 ) ; Swap ( c1, c2 ) ; cout < < “ \ n \ nAfter passing data to function template. \ n ” ; cout < < “ i1 = ” < < i1 < < “ \ ni2 = ” < < i2 ; cout < < “ \ nf1 = ” < < f1 < < “ \ nf2 = ” < < f2 ; cout < < “ \ nc1 = ” < < c1 < < “ \ nc2 = ” < < c2 ; return 0 ; } Đầu ra Before passing data to function template . i1 = 1 i2 = 2 f1 = 1.1 f2 = 2.2 c1 = a c2 = b After passing data to function template . i1 = 2 i2 = 1 f1 = 2.2 f2 = 1.1 c1 = b c2 = a Trong chương trình này, thay vì gọi một hàm bằng cách truyền một giá trị, một lệnh gọi bằng tham chiếu đã được thực thi . Khuôn mẫu hàm swap ( ) đã nhận hai đối số và hoán đổi chúng bằng cách tham chiếu .

7.2. Class templates – Khuôn mẫu lớp

Giống với khuôn mẫu hàm, bạn cũng hoàn toàn có thể tạo những khuôn mẫu lớp cho những hoạt động giải trí chung của lớp .
Đôi khi, bạn cần tiến hành lớp giống nhau cho toàn bộ những lớp, chỉ có kiểu tài liệu được sử dụng là khác nhau .
Thông thường, bạn sẽ cần tạo một lớp khác nhau cho từng kiểu tài liệu, hoặc tạo những biến và những hàm thành viên khác nhau trong một lớp duy nhất .
Việc này sẽ khiến cho code của bạn phức tạp một cách không thiết yếu và khó duy trì, vì việc đổi khác một lớp / hàm nên được thực thi trên toàn bộ những lớp / hàm .
Tuy nhiên, những khuôn mẫu lớp sẽ giúp bạn thuận tiện sử dụng lại cùng một code cho toàn bộ những kiểu tài liệu .

Cách khai báo khuôn mẫu lớp

template

class className
{
… .. …
public :
T var ;
T someOperation ( T arg ) ;
… .. …
} ;
Trong phần khai báo trên, T là đối số khuôn mẫu, là phần giữ chỗ cho kiểu tài liệu được sử dụng .
Bên trong phần thân của lớp, một biến var và một biến someOperation ( ) đều thuộc kiểu T .

Cách tạo một đối tượng khuôn mẫu lớp

Để tạo một đối tượng người tiêu dùng khuôn mẫu lớp, bạn cần xác lập kiểu tài liệu bên trong một dấu ngoặc nhọn < > khi tạo .

className classObject;

Ví dụ :

className classObject;

className classObject;

className classObject;

Ví dụ 3: Phép tính đơn giản sử dụng khuôn mẫu lớp

Chương trình cộng, trừ, nhân, chia hai số sử dụng khuôn mẫu lớp .

#include

using namespace std ;

template

class Calculator
{
private :
T num1, num2 ;
public :
Calculator ( T n1, T n2 )
{
num1 = n1 ;
num2 = n2 ;
}
void displayResult ( )
{
cout < < “ Numbers are : ” < < num1 < < ” and ” < < num2 < < “. ” < < endl ; cout < < “ Addition is : ” < < add ( ) < < endl ; cout < < “ Subtraction is : ” < < subtract ( ) < < endl ; cout < < “ Product is : ” < < multiply ( ) < < endl ; cout < < “ Division is : ” < < divide ( ) < < endl ; } T add ( ) { return num1 + num2 ; } T subtract ( ) { return num1 – num2 ; } T multiply ( ) { return num1 * num2 ; } T divide ( ) { return num1 / num2 ; } } ; int main ( ) {

Calculator intCalc(2, 1);

Calculator floatCalc(2.4, 1.2);

cout < < “ Int results : ” < < endl ; intCalc. displayResult ( ) ; cout < < endl < < “ Float results : ” < < endl ; floatCalc. displayResult ( ) ; return 0 ; } Đầu ra Int results : Numbers are : 2 and 1 . Addition is : 3 Subtraction is : 1 Product is : 2 Division is : 2 Float results : Numbers are : 2.4 and 1.2 . Addition is : 3.6 Subtraction is : 1.2 Product is : 2.88 Division is : 2 Trong chương trình trên, một khuôn mẫu lớp Calculator được khai báo . Lớp chứa hai thành phần private của kiểu T là num1 và num2, và một hàm khởi tạo để tạo những thành viên . Nó cũng chứa những hàm public để tính cộng, trừ, nhân, chia những số trả về giá trị của kiểu tài liệu do người dùng xác lập. Tương tự vậy, hàm displayResult ( ) để hiển thị hiệu quả sau cuối ra màn hình hiển thị . Trong hàm main ( ), hai đối tượng người dùng Calculator khác nhau là intCalc và floatCalc được tạo cho kiểu tài liệu int và float tương ứng. Các giá trị được khởi tạo bằng cách sử dụng hàm khởi tạo .

Lưu ý rằng tôi sử dụng trong khi tạo các đối tượng. Chúng cho trình biên dịch biết kiểu dữ liệu được sử dụng để tạo lớp.

Việc này tạo ra định nghĩa lớp cho từng int và float và được sử dụng cho tương thích. Sau đó, displayResult ( ) của cả hai đối tượng người dùng được gọi để triển khai những hoạt động giải trí đo lường và thống kê và hiển thị hiệu quả đầu ra .

8. Lời kết

Trên đây là tất cả những kiến thức tổng quan về tính kế thừa trong C++ cùng các ví dụ bài tập kế thừa trong C++ cụ thể. Hy vọng các kiến thức bổ ích này giúp bạn có thể hiểu rõ hơn về kế thừa C++ trong lập trình.

Tham khảo thêm những bài viết về kiến thức và kỹ năng lập trình của Ironhack Nước Ta để cập nhập thêm mindset cho bản thân. Hoặc nếu bạn muốn trở thành một lập trình viên chuyên nghiệp, đừng ngần ngại tìm hiểu thêm những khóa học của Ironhack Nước Ta. Chúc bạn thành công xuất sắc .