1. Trang chủ
  2. » Công Nghệ Thông Tin

Bài giảng môn học Lập trình hướng đối tượng - Chương 4: Sự kế thừa

74 12 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 74
Dung lượng 457,89 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Sự kế thừa là một đặc điểm của ngôn ngữ dùng để biểu diễn mối quan hệ đặc biệt giữa các lớp. Chương này sẽ trang bị cho người học một số kiến thức về sự kế thừa như: Kế thừa đơn, phạm vi truy xuất, phương thức thiết lập và huỷ bỏ, con trỏ và kế thừa. Mời các bạn cùng tham khảo để nắm bắt các nội dung chi tiết.

Trang 1

Chöông 4

Trang 2

4 Phương thức thiết lập và huỷ bỏ

5 Con trỏ và kế thừa

Trang 3

4.1 Mở đầu

- Sự kế thừa là một đặc điểm của ngôn ngữ dùng để biểudiễn mối quan hệ đặc biệt giữa các lớp Các lớp được trừutượng hóa và tổ chức thành một sơ đồ phân cấp lớp

- Kế thừa là một cơ chế trừu tượng hóa Thủ tục và hàm là cơchế trừu tượng hóa cho giải thuật, record và struct là trừutượng hóa cho dữ liệu Khái niệm lớp trong C++, kết hợpdữ liệu và thủ tục để được kiểu dữ liệu trừu tượng với giaodiện độc lập với cài đặt và cho người sử dụng cảm giácthoải mái như kiểu dữ liệu có sẵn

- Sự kế thừa là một mức cao hơn của trừu tượng hóa cungcấp một cơ chế gom chung các lớp có liên quan với nhauthành một mức khái quát hóa đặc trưng cho toàn bộ các lớpnói trên Các lớp với các đặc điểm tương tự nhau có thể

Trang 4

Mở đầu

 Quan hệ là 1: Kế thừa được sử dụng thông dụng nhất đểbiểu diễn quan hệ là một

• Một sinh viên là một người

• Một hình tròn là một hình ellipse

• Một tam giác là một đa giác

 Kế thừa tạo khả năng xây dựng lớp mới từ lớp đã có, trongđó hàm thành phần được thừa hưởng từ lớp cha Trong

C++, kế thừa còn định nghĩa sự tương thích, nhờ đó ta có

cơ chế chuyển kiểu tự động

 Kế thừa vừa có khả năng tạo cơ chế khái quát hoá vừa cókhả năng chuyên biệt hoá

 Kế thừa cho phép tổ chức các lớp chia sẻ mã chương trìnhchung nhờ vậy có thể dễ dàng sửa chữa, nâng cấp hệ

thống

Trang 5

Mở đầu

 Kế thừa thường được dùng theo hai cách:

• Để phản ánh mối quan hệ giữa các lớp Là công cụ đểtổ chức và phân cấp lớp dựa vào sự chuyên biệt hóa, trong đó một vài hàm thành phần của lớp con là phiênbản hoàn thiện hoặc đặc biệt hoá của phiên bản ở lớpcha Trong C++ mối quan hệ này thường được cài đặtsử dụng:

 Kế thừa public.

 Hàm thành phần là phương thức ảo

• Để phản ánh sự chia sẻ mã chương trình giữa các lớpkhông có quan hệ về mặt ngữ nghĩa nhưng có thể có tổchức dữ liệu và mã chương trình tương tự nhau Trong

Trang 6

4.2 Kế thừa đơn

 Kế thừa có thể được thực hiện để thể hiện mối quan hệ 'làmột'

 Xét hai khái niệm người và sinh viên với mối quan hệ tựnhiên: một 'sinh viên' là một 'người' Trong C++, ta có thểbiểu diễn khái niệm trên, một sinh viên là một người cóthêm một số thông tin và một số thao tác (riêng biệt củasinh viên)

 Ta tổ chức lớp sinh viên kế thừa từ lớp người Lớp ngườiđược gọi là lớp cha (superclass) hay lớp cơ sở (base class) Lớp sinh viên được gọi là lớp con (subclass) hay lớp dẫnxuất (derived class)

Trang 7

Kế thừa đơn

~Nguoi() {delete [] HoTen;}

void An() const { cout << HoTen << " an 3 chen com";}

void Ngu() const { cout << HoTen << " ngu ngay

8 tieng";}

void Xuat() const;

friend ostream& operator << (ostream &os,

Trang 8

Kế thừa đơn

class SinhVien : public Nguoi

{

char *MaSo;

public:

SinhVien(char *ht, char *ms, int ns) :

Nguoi(ht,ns) { MaSo = strdup(ms);}

~SinhVien() {delete [] MaSo;}

void Xuat() const;

Trang 9

Kế thừa đơn

void Nguoi::Xuat() const

Trang 10

Kế thừa đơn

void main()

{

Nguoi p1("Le Van Nhan",1980);

SinhVien s1("Vo Vien Sinh", "200002541",1984);

Trang 11

Tự động kế thừa các đặc tính của lớp cha

Cho biết lớp sinh viên kế thừa từ lớp người Khi đó sinh

viên được thừa hưởng các đặc tính của lớp người

 Về mặt dữ liệu: Mỗi đối tượng sinh viên tự động có thànhphần dữ liệu họ tên và năm sinh của người

 Về mặt thao tác: Lớp sinh viên được tự động kế thừa cácthao tác của lớp cha Đây chính là khả năng sử dụng lại mãchương trình

Trang 12

Tự động kế thừa các đặc tính của lớp cha

Nguoi p1("Le Van Nhan",1980);

SinhVien s1("Vo Vien Sinh", "200002541",1984);

 Khả năng thừa hưởng các thao tác của lớp cơ sở có thể

được truyền qua vô hạn mức

Trang 13

Định nghĩa lại thao tác ở lớp con

 Ta có thể định nghĩa lại các đặc tính ở lớp con đã có ở lớpcha, việc định nghĩa chủ yếu là thao tác, bằng cách khaibáo giống hệt như ở lớp cha

class SinhVien : public Nguoi

Trang 14

 Việc định nghĩa lại thao tác ở lớp con được thực hiện khithao tác ở lớp con khác thao tác ở lớp cha Thông thường làcác thao tác xuất, nhập

 Ta cũng có thể định nghĩa lại thao tác ở lớp con trong

trường hợp giải thuật ở lớp con đơn giản hơn (tô màu đa

giác, tính modun của số ảo )

class DaGiac

{

//

void Ve() const;

void ToMau() const;

Trang 15

Định nghĩa lại thao tác ở lớp con

 Hoặc ở lớp con, thao tác không có tác dụng

Trang 16

4.3 Ràng buộc ngữ nghĩa ở lớp con

 Kế thừa có thể được áp dụng cho quan hệ kế thừa mang ýnghĩa ràng buộc, đối tượng ở lớp con là đối tượng ở lớp cha nhưng có dữ liệu bị ràng buộc

• Hình tròn là Ellipse ràng buộc bán kính ngang dọc bằngnhau

• Số ảo là số phức ràng buộc phần thực bằng 0

• Hình vuông là hình chữ nhật ràng buộc hai cạnh ngangvà dọc bằng nhau…

 Trong trường hợp này, các hàm thành phần phải bảo đảmsự ràng buộc dữ liệu được tôn trọng Lớp số ảo sau đây làmột ví dụ minh hoạ

Trang 17

Ràng buộc ngữ nghĩa ở lớp con

Complex operator +(Complex b);

Complex operator -(Complex b);

Complex operator *(Complex b);

Complex operator /(Complex b);

double Norm() const {return sqrt(re*re +

im*im);}

};

Trang 18

class Imag: public Complex

{

public:

Imag(double i = 0):Complex(0, i){}

Imag(const Complex &c) : Complex(0, c.im){} Imag& operator = (const Complex &c)

{re = 0; im = c.im; return *this;}

double Norm() const {return fabs(im);}

Trang 19

Ràng buộc ngữ nghĩa ở lớp con

 Trong ví dụ trên lớp số ảo (Imag) kế thừa hầu hết các thaotác của lớp số phức (Complex) Tuy nhiên ta muốn ràngbuộc mọi đối tượng thuộc lớp số ảo đều phải có phần thựcbằng 0 Vì vậy phải định nghĩa lại các hàm thành phần cóthể vi phạm điều này Ví dụ phép toán gán phải được địnhnghĩa lại để bảo đảm ràng buộc này

class Imag: public Complex

{

public:

//

Imag(const Complex &c) : Complex(0, c.im){}

Imag& operator = (const Complex &c)

{re = 0; im = c.im; return *this;}

Trang 20

Ràng buộc ngữ nghĩa ở lớp con

 Ví dụ sau minh hoạ thêm ràng buộc ngữ nghĩa ở lớp con

class HCN:public Hinh

{

Diem TrenTrai;

double rong, cao;

public:

HCN(Diem tt, double r, double c);

HCN(double ttx, double tty, double r, double c); HCN():TrenTrai(4,6), rong(7), cao(4){}

virtual double DienTich() const {return rong*cao;} virtual void Nhap();

virtual void Xuat();

virtual void PhongTo(double tiLe);

virtual void GianNgang(double tiLe);

virtual void GianDoc(double tiLe);

};

Trang 21

Ràng buộc ngữ nghĩa ở lớp con

void HCN::PhongTo(double tiLe)

Trang 22

void GianNgang(double tiLe);

void GianDoc(double tiLe);

};

Trang 23

Ràng buộc ngữ nghĩa ở lớp con

void HV::GianNgang(double tiLe)

Trang 24

4.4 Phạm vi truy xuất

 Khi thiết lập quan hệ kế thừa, ta vẫn phải quan tâm đếntính đóng gói và che dấu thông tin Điều này dẫn đến vấnđề xác định ảnh hưởng của kế thừa đến phạm vi truy xuấtcác thành phần của lớp Hai vấn đề được đặt ra là:

 Truy xuất theo chiều dọc: Hàm thành phần của lớp con cóquyền truy xuất các thành phần riêng tư của lớp cha hay không ? Vì chiều truy xuất là từ lớp con, cháu lên lớp cha nên ta gọi là truy xuất theo chiều dọc

 Truy xuất theo chiều ngang: Các thành phần của lớp cha, sau khi kế thừa xuống lớp con, thì thế giới bên ngoài cóquyền truy xuất thông qua đối tượng của lớp con hay

không ? Trong trường hợp này, ta gọi là truy xuất theo

chiều ngang

Trang 25

4.4.1 Truy xuất theo chiều dọc

 Lớp con có quyền truy xuất các thành phần của lớp cha hay không, hay tổng quát hơn, nơi nào có quyền truy xuấtcác thành phần của lớp cha, hoàn toàn do lớp cha quyếtđịnh Điều đó được xác định bằng thuộc tính truy xuất

 Trong trường hợp lớp sinh viên kế thừa từ lớp người, truyxuất theo chiều dọc có nghĩa liệu lớp sinh viên có quyềntruy xuất các thành phần họ tên, năm sinh của lớp ngườihay không Chính xác hơn một đối tượng sinh viên có

quyền truy xuất họ tên của chính mình nhưng được khaibáo ở lớp người hay không?

 Thuộc tính truy xuất là đặc tính của một thành phần củalớp cho biết những nơi nào có quyền truy xuất thành phần

Trang 26

Thuộc tính truy xuất

 Thuộc tính public: Thành phần nào có thuộc tính public thìcó thể được truy xuất từ bất cứ nơi nào (từ sau khai báolớp)

 Thuộc tính private: Thành phần nào có thuộc tính private thì nó là riêng tư của lớp đó Chỉ có các hàm thành phầncủa lớp và ngoại lệ là các hàm bạn được phép truy xuất, ngay cả các lớp con cũng không có quyền truy xuất

Trang 27

Thuộc tính truy xuất

Trang 28

Thuộc tính private

 Trong ví dụ trên, không có hàm thành phần nào của lớpSinhVien có thể truy xuất các thành phần private HoTen, NamSinh của lớp Nguoi Nói cách khác, lớp con không cóquyền vi phạm tính đóng gói của lớp cha Đoạn chươngtrình sau gây ra lỗi lúc biên dịch

void SinhVien::Xuat() const

{

cout << "Sinh vien, ma so: " << MaSo << ", ho ten: " << HoTen;

}

 Ta có thể khắc phục được lỗi trên nhờ khai báo lớp

SinhVien là bạn của lớp Nguoi, như trong ví dụ ở đầu

chương:

Trang 29

void Xuat() const { cout << "Sinh vien, ma so: "

<< MaSo << ", ho ten: " << HoTen; }

Trang 30

 Cách làm trên giải quyết được nhu cầu của người sử dụngkhi muốn tạo lớp con có quyền truy xuất các thành phần dữliệu private của lớp cha Tuy nhiên nó đòi hỏi phải sửa đổilại lớp cha và tất cả các lớp ở cấp cao hơn mỗi khi một lớpcon mới ra đời.

Trang 31

Thuộc tính private

class Nguoi

{

friend class SinhVien;

friend class NuSinh;

Trang 32

Nguoi p1("Le Van Nhan",1980);

SinhVien s1("Vo Vien Sinh", "200002541",1984); NuSinh ns("Le Thi Ha Dong", "200002544",1984); p1.An(); cout << "\n";

s1.An();cout << "\n";

ns.An();cout << "\n";

}

Trang 33

Thuộc tính protected

 Trong ví dụ trên, khi lớp NuSinh ra đời ta phải thay đổi lớpcha SinhVien và cả lớp cơ sở Nguoi ở mức cao hơn

 Thuộc tính protected: cho phép qui định một vài thành

phần nào đó của lớp là bảo mật, theo nghĩa thế giới bên

ngoài không được phép truy xuất, nhưng tất cả các lớp con, cháu… đều được phép truy xuất

Trang 34

~SinhVien() {delete [] MaSo;}

void Xuat() const; // Co the truy xuat

// Nguoi::HoTen va Nguoi::NamSinh };

Trang 37

Thuộc tính protected

 Thuộc tính protected là phương tiện để tránh phải sửa đổilớp cơ sở khi có lớp con mới ra đời Nhờ đó nó bảo được

tính đóng của một lớp Khai báo một thành phần nào có

thuộc tính protected tương đương với qui định trước tất cảcác lớp con, cháu sau này đều là bạn của thành phần đó

 Thông thường ta dùng thuộc tính protected cho các thànhphần dữ liệu và thuộc tính public cho hàm thành phần

 Các thuộc tính public, private, protected và khai báo friend cho những nơi nào có quyền truy xuất đến các thành phầncủa lớp Cho hay không cho ai truy xuất đến (thành phầncủa) lớp hoàn toàn do lớp quyết định

Trang 38

4.4.2 Truy xuất theo chiều ngang

 Thành phần protected và public của lớp khi đã kế thừa

xuống lớp con thì thế giới bên ngoài có quyền truy xuất

thông qua đối tượng thuộc lớp con hay không? Điều này

hoàn toàn do lớp con quyết định bằng thuộc tính kế thừa Có hai thuộc tính kế thừa là kế thừa public và kế thừa

private

 Kế thừa public: Lớp con kế thừa public từ lớp cha thì cácthành phần protected của lớp cha trở thành protected củalớp con, các thành phần public của lớp cha trở thành public của lớp con Nói cách khác mọi thao tác của lớp cha đượckế thừa xuống lớp con Vì vậy ta có thể sử dụng thao táccủa lớp cha cho đối tượng thuộc lớp con

 Ta qui định kế thừa public bằng từ khoá public theo sau

dấu hai chấm khi thiết lập quan hệ kế thừa

Trang 39

Kế thừa public

class SinhVien : public Nguoi

{

char *MaSo;

public:

SinhVien(char *ht, char *ms, int ns) :

Nguoi(ht,ns) { MaSo = strdup(ms);}

~SinhVien() {delete [] MaSo;}

void Xuat() const;

Trang 40

Kế thừa public

 Do được thừa hưởng các đặc tính của lớp cha nên ta dùngkế thừa public khi và chỉ khi có quan hệ là một từ lớp con đến lớp cha

 Hầu hết các trường hợp kế thừa là kế thừa public, nó chophép tận dụng lại mã chương trình, đồng thời tạo khả năngthu gom các đặc điểm chung của các lớp vào một lớp cơsở, nhờ đó dễ dàng nâng cấp và sửa chữa (bảo trì)

Trang 41

Kế thừa private

 Có những trường hợp các lớp không có quan hệ với nhauvề mặt ngữ nghĩa nhưng chia sẻ chung chi tiết cài đặt, nếudùng kế thừa public thì sai khái niệm vì lớp con sẽ thừa

hưởng các thao tác nó không có từ lớp cha

 Kế thừa private: Lớp con kế thừa private từ lớp cha thì cácthành phần protected và public của lớp cha trở thành

private của lớp con Nói cách khác mọi thao tác của lớp

cha đều bị lớp con che dấu Vì vậy trên quan điểm của thếgiới bên ngoài lớp con không có các thao tác mà lớp cha có

 Sử dụng kế thừa private Ta có thể chia sẻ mã chương trìnhgiữa các lớp có cấu trúc dữ liệu tương tự nhau nhưng vẫn

Trang 42

Kế thừa private

 Ví dụ sau minh hoạ kế thừa private: Lớp List biểu diễn

khái niệm danh sách liên kết, lớp Stack biểu diễn , lớp Set biểu diễn khái niệm tập hợp Nếu tổ chức Stack, Set nhưdanh sách liên kết, có thể dùng kế thừa private để tận

dụng mã chương trình chung

typedef int Item;

typedef int bool;

const bool true = 1, false = 0;

class Link

{

friend class List;

friend class ListIterator;

Link *next;

Item e;

Link(Item a, Link *p):e(a) {next = p;}

};

Trang 43

Kế thừa private

void GetFirst(Item *px); // return and remove head void CleanUp();

bool Empty() const {return last == NULL;}

bool IsMember(Item x) const;

int Count() const;

void View() const;

Trang 45

Kế thừa private

class Stack:private List

Trang 46

Kế thừa private

class Set:private List

bool Empty() const {return List::Empty();}

bool IsMember(Item x) const {return

List::IsMember(x);}

int Count() const {return List::Count();}

void View() const {List::View();}

};

 Các lớp Stack và Set tận dụng được cấu trúc dữ liệu và chi tiết cài đặt của lớp List, nhưng không bị trở thành List vì sửdụng kế thừa private Các thao tác của List không bị kế

thừa xuống lớp con Stack và Set

Trang 47

Kế thừa private

 Một số hàm thành phần của lớp cơ sở List có thể cần thiết

ở lớp con như hàm Empty trong lớp Stack, hàm Empty, IsMember, Count trong lớp Set… Ta định nghĩa lại nhữnghàm này bằng cách gọi lại phiên bản trong lớp List

 Một cách thay thế việc viết lại hàm như trên là khai báolại các danh hiệu này trong phần public của lớp con

class Stack:private List

Trang 48

Kế thừa private

 Ta có thể làm tương tự cho lớp Set

class Set:private List

Trang 49

Kế thừa private

friend Diem operator + (Diem d);

friend Diem operator - (Diem d);

Trang 50

MangDiem operator + (Diem d) const;

MangDiem& operator += (Diem d);

MangDiem operator + (const MangDiem md) const; MangDiem& operator += (MangDiem md) const;

MangDiem& operator << (Diem d);

Diem &operator [] (int i);

Diem operator [] (int i) const;

//

};

Trang 51

Kế thừa private

Diem dd[] = {Diem(100,100), Diem(200,200),

DaGiac& operator = (const DaGiac &d);

void Ve(int color) const;

void Quay(Diem Tam, double goc);

void TinhTien(double dx, double dy) const;

Diem TrongTam() const;

//

};

Trang 52

Không dùng kế thừa private

 Ta luôn luôn có thể tận dụng mã chương trình chung bằngkế thừa private như trên, nhưng cũng có thể không cầndùng kế thừa

class Link

{

friend class List;

friend class ListIterator;

Link *next;

Item e;

Link(Item a, Link *p):e(a) {next = p;}

};

Trang 53

Không dùng kế thừa private

List() {last = NULL;}

List(Item a) { last = new Link(a, NULL);

last->next = last; }

~List() { CleanUp(); }

Trang 55

Không dùng kế thừa private

bool Empty() const {return l.Empty();}

bool IsMember(Item x) const {return

l.IsMember(x);}

int Count() const {return l.Count();}

void View() const {l.View();}

};

Ngày đăng: 10/05/2021, 13:59

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w