Upload
others
View
6
Download
0
Embed Size (px)
Citation preview
BỘ TÀI CHÍNH
HỌC VỆN TÀI CHÍNH
2018
LẬP TRÌNH HƯỚNG
ĐỐI TƯỢNG TS. VŨ BÁ ANH
Ths. HÀ VĂN SANG
A C A D E M Y O F F I N A N C E
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 2
Mục lục
MỞ ĐẦU .............................................................................................................. 5
Chương 1. GIỚI THIỆU VỀ LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG ................. 6
1. Tổng quan về các kỹ thuật lập trình ............................................................. 6
1.1. Lập trình tuyến tính ............................................................................... 6
1.2. Lập trình cấu trúc .................................................................................. 6
1.3. Lập trình mô đun hoá ............................................................................ 7
1.4. Nhược điểm của lập trình hướng thủ tục ............................................... 8
1.5. Lập trình hướng đối tượng .................................................................... 9
2. Một số khái niệm cơ bản ............................................................................ 11
2.1. Hệ thống hướng đối tượng .................................................................. 11
2.2. Đối tượng (Objects) ............................................................................. 12
2.3. Thuộc tính (Attribute) và Phương thức (method) ............................... 12
2.4. Lớp (Class) và lớp con (SubClass) ...................................................... 12
2.5. Lớp trừu tượng .................................................................................... 13
2.6. Truyền thông điệp ............................................................................... 13
2.7. Sự trừu tượng hoá (abstraction) ........................................................... 14
2.8. Sự đóng gói (encapsulation) ................................................................ 14
2.9. Tính kế thừa ......................................................................................... 14
1.10. Tính đa hình ....................................................................................... 15
3. Các bước cần thiết để thiết kế chương trình theo hướng đối tượng ........... 16
4. Các ưu điểm của lập trình hướng đối tượng ............................................... 16
5. Một số ngôn ngữ lập trình hướng đối tượng .............................................. 16
6. Một số ứng dụng của lập trình hướng đối tượng ........................................ 18
Chương 2. GIỚI THIỆU VỀ C++ .................................................................... 19
1. Lịch sử của C++ ......................................................................................... 19
2. Các mở rộng của C++................................................................................. 19
2.1. Lời chú thích ........................................................................................ 19
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 3
2.2. Từ khoá mới ........................................................................................ 20
2.3. Kiểu dữ liệu char và int ....................................................................... 20
2.4. Khai báo biến ....................................................................................... 20
2.5. Chuyển đổi và ép kiểu ......................................................................... 21
2.6. Vào ra trong C++ ................................................................................. 22
2.7. Cấp phát và giải phóng bộ nhớ ............................................................ 26
2.8. Biến tham chiếu ................................................................................... 28
2.9. Hằng tham chiếu .................................................................................. 29
2.10. Truyền tham số cho hàm theo tham chiếu ......................................... 30
2.11. Hàm với tham số có giá trị mặc định ................................................ 34
2.12. Các hàm nội tuyến ............................................................................. 35
2.13. Hàm tải bội ........................................................................................ 36
Chương 3. LỚP VÀ ĐỐI TƯỢNG .................................................................. 39
1. Xây dựng lớp và đối tượng ......................................................................... 39
1.1. Khai báo lớp ........................................................................................ 39
1.2. Khai báo đối tượng .............................................................................. 42
1.3. Truy xuất các thành phần của đối tượng ............................................. 43
2. Các phương thức ........................................................................................ 46
2.1. Hàm khởi tạo - Constructor ................................................................. 46
2.2. Hàm hủy – Destructor ......................................................................... 49
3. Đa năng hóa tóan tử .................................................................................... 51
4. Mảng và con trỏ của lớp ............................................................................. 57
5. Các hàm bạn và lớp bạn ............................................................................. 59
5.1. Hàm bạn (friend function) ................................................................... 59
5.2. Lớp bạn ................................................................................................ 61
6. Thành phần tĩnh .......................................................................................... 63
6.1. Các thành phần dữ liệu tĩnh ................................................................. 63
6.2. Các hàm thành viên tĩnh ...................................................................... 64
7. Thành phần hằng ........................................................................................ 67
7.1. Dữ liệu hằng ........................................................................................ 67
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 4
7.2. hàm thành phần (phương thức hằng) ................................................... 69
8. Thành phần là đối tượng ............................................................................. 69
Chương 4. TÍNH KẾ THỪA ........................................................................... 72
1. Khái niệm ................................................................................................... 72
2. Khai báo lớp dẫn xuất ................................................................................. 73
3. Các kiểu kế thừa ......................................................................................... 75
4. Định nghĩa lại quyền truy xuất ................................................................... 78
5. Hàm khởi tạo và hàm hủy của lớp cơ sở .................................................... 81
6. Đa kế thừa ................................................................................................... 84
7. Lớp cơ sở ảo và nhập nhằng trong đa kế thừa ............................................ 86
Chương 5. TÍNH ĐA HÌNH ............................................................................ 90
1. Phương thức ảo ........................................................................................... 90
2. Phương thức ảo thuần túy và lớp trừu tượng .............................................. 97
Chương 6. KHUÔN HÌNH MẪU .................................................................. 101
1. Template ................................................................................................... 101
a. Khuôn hình hàm ................................................................................... 101
b. Khuôn hình lớp ..................................................................................... 105
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 5
MỞ ĐẦU
Cơ sở lập trình học phần 2 (CSLT) là một trong hai môn học quan trọng nhất
của sinh viên chuyên ngành Tin học Tài chính Kế toán, ngành Hệ thống thông tin quản
ly thuộc Học viện Tài chính; môn học này giúp cho sinh viên năm băt được những ly
thuyết chung về các khái niệm, phương pháp lập trình hướng đối tượng, sau đó, giúp
sinh viên thực hành cài đặt chương trình cho các bài toán trong lĩnh vực Tài chính Kê
toán trong Hệ thống thông tin kế toán của môi đơn vị.
Môn học “Cơ sở lập trình” dành cho sinh viên chuyên ngành Tin học Tài chính
Kế toán được chia thành 3 học phần sau đây:
- CSLT HP1: Nhập môn Cơ sở lập trình học phần 1. Học phần nhập môn se
trình bày những ly thuyết chung về CSLT, đặc biệt tập trung giới thiệu kỹ thuật lập
trình hướng cấu trúc được sử dụng phổ biến nhất hiện nay.
- CSLT HP2: Lập trình hướng đối tượng với C++. Học phần này se trang bị cho
sinh viên những nội dung cần thiết để xây dựng một chương trình hướng đối tượng
trong lĩnh vực tài chính kế toán.
- CSLT HP3: Lập trình ứng dụng web. Học phần này trong bị cho sinh viên
những kiến thức cơ bản để lập trình trên môi trường web thông qua mạng máy tình;
học phần cung giúp cho sinh viên có khả năng tích hợp các hệ quản trị CSLT HP2 với
các phần mềm khác (C#, VB, NET, Crystal Report) để quản trị một ứng dụng trên
mạng máy tính.
Bài giảng gốc “CSLT HP2” được biên soạn nhằm phục vụ cho nhu cầu học tập
và nghiên cứu của sinh viên chuyên ngành Tin học Tài chính Kế toán, khoa Hệ thống
thông tin kinh tế, Học viện Tài chính.
Trong quá trình biên soạn Bài giảng gốc “CSLT học phần 2”, ở chương 1, được
Tiến sĩ Vũ Bá Anh (chủ biên), trình bày những kiến thức cơ bản để làm nền tảng cho
lập trình cấu trúc và lập trình hướng đối tượng. Trong các chương từ chương 2 đến
chương 6, Thạc sỹ Hà Văn Sang (đồng chủ biên) se trình bày những kiến thức tổng
quan về lập trình hướng đối tượng, các điểm mới của ngôn ngữ lập trình C++, các khái
niệm cơ bản về lớp, đối tượng, tính kế thừa, đa hình và khuôn hình mẫu. Trong môi
chương đó, se giới thiệu ly thuyết chung về một thành phần trong hướng đối tượng,
sau đó lấy các ví dụ minh họa cho việc xây dựng thành phần tương ứng trong quá trình
xây dựng phần mềm tài chính Kế toán.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 6
Chương 1. GIỚI THIỆU VỀ LẬP TRÌNH HƯỚNG ĐỐI
TƯỢNG
Lập trình hướng đối tượng (Object-Oriented Programming-OOP), hay còn
gọi là lập trình định hướng đối tượng, là kỹ thuật lập trình hô trợ công nghệ đối
tượng. OOP được xem là là cách tiếp cận mới, hiệu quả hơn trong việc giải quyết các
vấn đề. Nó giúp tăng năng suất, đơn giản hoá độ phức tạp khi bảo trì cung như mở
rộng phần mềm bằng cách cho phép lập trình viên tập trung vào các đối tượng phần
mềm ở bậc cao hơn. Mục đích của OOP là giúp người lập trình giảm nhẹ các thao tác
viết mã, cho phép tạo ra các ứng dụng mà các yếu tố bên ngoài có thể tương tác với
các ứng dụng đó như là tương tác với các đối tượng vật ly.
Lập trình hướng đối tượng dễ học bởi nó dựa trên một vài khái niệm đơn giản
nhưng hết sức mạnh. Trước khi tìm hiểu về kỹ thuật lập trình hướng đối tượng là gì
chúng ta hãy tìm hiểu về lịch sử của các kỹ thuật lập trình.
1. Tổng quan về các kỹ thuật lập trình
1.1. Lập trình tuyến tính
Lập trình tuyến tính thường dùng để viết các chương trình nhỏ và đơn giản
trong đó chỉ gồm một chương trình chính. Chương trình chính gồm một dãy tuần tự
các câu lệnh dùng để thay đổi dữ liệu. Các ngôn ngữ lập trình thời kỳ này được thiết
kế để giải các bài tính toán tương đối đơn giản. Phần lớn các chương trình này ngăn, ít
hơn 100 dòng. Cung theo thời gian các ngôn ngữ này không đáp ứng được việc triển
khai phần mềm do các khuyết điểm của lập trình tuyến tính:
- Các ngôn ngữ này không có khả năng sử dụng lại các đoạn mã
- Không có khả năng kiểm soát phạm vi truy xuất của dữ liệu
- Mọi dữ liệu trong chương trình đều là toàn cục (global)
Hình 1.1 Lập trình tuyến tính
1.2. Lập trình cấu trúc
Lập trình cấu trúc ra đời vào những cuối năm 1970. Các chương trình cấu trúc
thường được tổ chức theo công việc mà chúng thường thực hiện. Chương trình được
chia nhỏ thành các chương trình con riêng re, môi chương trình con thực hiện một
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 7
nhiệm vụ. Các chương trình con này thường được gọi là hàm hoặc thủ tục. Các hàm
thường độc lập với nhau, môi hàm có dữ liệu riêng. Thông tin được chuyển giao giữa
các hàm thông qua các tham số và các biến toàn cục. Các biến cục bộ trong hàm thì
không thể truy xuất bởi hàm khác. Ở lập trình cấu trúc đã băt đẫu xuất hiện khái niệm
trừu tượng hoá (Abstraction). Sự trừu tượng hoá có thể xem như khả năng quan sát các
sự vật mà không quan tâm tới chi tiết bên trong nó. Trong một chương trình có cấu
trúc ta chỉ quan tâm xem hàm hoặc thủ tục đó là được gì, cần tham số gì chứ không
quan tâm tới việc hàm đó thực hiện các lệnh gì. Các ngôn ngữ hiện nay đều hô trợ kỹ
thuật lập trình có cấu trúc bởi các chương trình có cấu trúc dễ viết, dễ bảo dưỡng hơn
so với chương trình không có cấu trúc. Một số ngôn ngữ cấu trúc điển hình là C,
Pascal, Foxpro…
Hình 1.2 Lập trình cấu trúc
1.3. Lập trình mô đun hoá
Với lập trình mô đun, các thủ tục có chung một chức năng được nhóm lại với
nhau thành các môđun riêng lẻ. Lúc này một chương trình không chỉ là một phần đơn
mà bây giờ nó được chia thành một số phần nhỏ hơn. Các phần này tương tác với nhau
thông qua việc gọi thủ tục.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 8
Hình 1.3 Lập trình mô đun hóa
Môi môđun có thể có dữ liệu của riêng nó. Điều này cho phép môi môđun quản
ly một dữ liệu trong nó, dữ liệu này được sửa đổi bởi việc gọi các thủ tục.
1.4. Nhược điểm của lập trình hướng thủ tục
Một hệ thống phần mềm theo cách tiếp cận lập trình truyền thống được xem
như là dãy các công việc cần thực hiện như đọc dữ liệu, tính toán, xử ly, lập báo cáo
và in ấn kết quả … Môi công việc đó se được thực hiện bằng một hàm hoặc thủ tục
nhất định. Đó chính là các tiếp cận lập trình hướng thủ tục. Ta có thể thấy trọng tâm
của cách tiếp cận này là các hàm chức năng. Lập trình hướng thủ tục sử dụng kỹ thuật
phân ra hàm chức năng theo cách tiếp cận từ trên xuống để tạo ra cấu trúc phân cấp.
Các ngôn ngữ lập trình bậc cao như COBOL, FORTRAN, PASCAL, C.. là những
ngôn ngữ lập trình hướng thủ tục. Những nhược điểm chính của lập trình hướng thủ
tục là:
Chương trình khó kiểm soát và khó khăn trong việc bổ sung, nâng cấp. Chương
trình được xây dựng theo cách tiếp cận hướng thủ tục thực chất là danh sách các câu
lệnh mà theo đó máy tính cần thực hiện. Danh sách các lệnh đó được tổ chức thành
từng nhóm theo đơn vị cấu trúc của ngôn ngữ lập trình và được gọi là hàm/thủ tục.
Trong chương trình có nhiều hàm/thủ tục, thường thì có nhiều thành phần dữ liệu quan
trọng se được khai báo là toàn cục (global) để các hàm có thể truy nhập, đọc và làm
thay đổi giá trị của biến toàn cục. Điều này làm cho chương trình khó kiểm soát, nhất
là đối với các chương trình lớn, phức tạp thì vấn đề càng trở nên khó khăn hơn. Khi ta
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 9
muốn thay đổi, bổ sung cấu trúc dữ liệu dùng chung cho một số hàm/thủ tục thì phải
thay đổi hầu như tất cả các hàm/thủ tục liên quan tới dữ liệu đó.
Chương trình chưa loại bỏ được các đoạn mã lặp lại.
Mô hình được xây dựng theo cách hướng thủ tục không mô tả đầy đủ, trung
thực hệ thống trong thực tế.
Phương pháp tổ chức hướng thủ tục đặt trọng tâm vào hàm là hướng tới hoạt
động se không thực sự tương ứng với các thực thể trong hệ thống của thế giới thực.
1.5. Lập trình hướng đối tượng
Tiếp cận hướng đối tượng
Trong thế giới thực xung quanh chúng ta là những đối tượng, đó là những thực
thể có mối quan hệ với nhau. Ví dụ các phòng trong một công ty kinh doanh được xem
như những đối tượng. Các phòng ở đây có thể là: phòng quản ky, phòng bán hàng,
phòng kế toán, phòng tiếp thị…. Môi phòng ngoài những cán bộ đảm nhiệm công việc
cụ thể, còn có những dữ liệu riêng về thông tin nhân viên, doanh số bán hàng, hoặc các
dữ liệu khác có liên quan tới bộ phận đó. Việc phân chia các phòng chức năng trong
công ty se tạo điều kiện dễ dàng cho việc quản ly. Môi nhân viên trong phòng se điều
khiển và xử ly dữ liệu của phòng đó. Ví dụ phòng kế toán phụ trách về lương bổng của
nhân viên trong công ty. Nếu bạn đang ở bộ phận tiếp thị và cần thông tin chi tiết về
lương của đơn vị mình thì bạn gửi yêu cầu về phòng kế toán. Với cách làm việc này
bạn đảm bảo là chỉ có nhân viên của bộ phận kế toán được quyền truy cập dữ liệu và
cung cấp thông tin cho bạn. Điều này cung cho thấy rằng, không có người nào thuộc
bộ phận khác có thể truy cập và thay đổi dữ liệu của bộ phận kế toán. Khái niệm như
thế về đối tượng hầu như có thể được mở rộng trong mọi lĩnh vực đời sống xã hội và
hơn thế nữa – đối với việc tổ chức chương trình. Mọi ứng dụng có thể được định nghĩa
như một tập các thực thể hoặc các đối tượng, sao cho quá trình tái tạo những suy nghĩ
của chúng ta là gần sát với thế giới thực.
Lập trình hướng đối tượng là phương pháp lập trình lấy đối tượng làm nền tảng
để xây dựng thuật giải, xây dựng chương trình. Đối tượng được xây dựng trên cơ sở
găn cấu trúc dữ liệu với các phương thức (hàm/thủ tục) se thể hiện được đúng cách mà
chúng ta suy nghĩ, bao quát về thế giới thực. Lập trình hướng đối tượng cho phép ta
kết hợp những tri thức bao quát về các quá trình với những khái niệm trừu tượng được
sử dụng trong máy tính.
Điểm căn bản của phương pháp lập trình hướng đối tượng là thiết kế xoay
quanh dữ liệu của hệ thống. Nghĩa là các thao tác xử ly của hệ thống được găn liền với
dữ liệu và như vậy khi có sự thay đổi cấu trúc dữ liệu thì chỉ ảnh hưởng tới một số ít
các phương thức xử ly liên quan.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 10
Lập trình hướng đối tượng không cho phép dữ liệu chuyển động tự do trong hệ
thống. Dữ liệu được găn chặt với từng phương thức thành các vùng riêng mà các
phương thức đó tác động lên và nó được bảo vệ cấm việc truy nhập tuỳ tiện từ bên
ngoài. Lập trình hướng đối tượng cho phép phân tính bài toán thành lập các thực thể
được gọi là các đối tượng và sau đó xây dựng các dữ liệu cung với các phương thức
xung quanh các đối tượng đó.
Lập trình OOP cho phép bạn tạo đối tượng, các đối tượng này được lưu trữ
trong các thư viện và được sử dụng lại trong một công việc khác. Nó tiết kiệm thời
gian cho người lập trình, nhiều công ty phần mềm xây dựng các thư viện này và bán ra
thị trường, từ đó giúp cho người lập trình dễ dàng tạo ra các ngôn ngữ lập trình hướng
đối tượng sử dụng "OPC" (Other People's Code).
OOP cho phép bạn định nghĩa đối tượng trong đó bao gồm cấu trúc dữ liệu và
các phương thức xử ly dữ liệu.
Một phương thức có thể được định nghĩa như là một chương trình có cấu trúc
nhỏ bên trong đối tượng. Các phương thức này xác định đối tượng se hoạt động ra sao.
Một lớp lại định nghĩa một đối tượng, khái niệm đóng kín dữ liệu và phương thức
trong một đối tượng được gọi là sự đóng kín hay đóng gói.
Lập trình hướng đối tượng bao gồm kỹ thuật lập trình cấu trúc và cấu trúc dữ
liệu nâng cao. Nó giúp cho người phát triển có thể kết hợp dữ liệu và các phương thức
vào một thành phần gọi là đối tượng. Nói cách khác, một đối tượng bao gồm mọi thứ
cần thiết cho việc thực thi một tập hợp các hàm, bao gồm cả dữ liệu.
Hình 1.4 Lập trình hướng đối tượng
Lập trình hướng đối tượng là kỹ thuật lập trình mới. Nó được thiết kế nhằm tiết
kiệm thời gian và tiền bạc thông qua việc cho phép người lập trình sử dụng lại các
đoạn mã chương trình. Khả năng tái sử dụng này được hoàn thiện qua tính kế thừa. Có
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 11
nghĩa là làm cho các đối tượng có thể kế thừa các đặc tính (phương thức và dữ liệu) từ
đối tượng khác.
Thông thường, OOP cho phép lập trình viên sửa đổi các thuộc tính của đối
tượng được kế thừa. Trong lập trình cấu trúc, môđun được gọi thực hiện một chức
năng và bạn phải chấp nhận chức năng này khi nó được viết, hoặc bạn có thể sao chép
mã và sửa lại theo mục đích của bạn.
Tóm lại lập trình hướng đối tượng có những đặc tính sau:
1. Tập trung vào dữ liệu thay cho các phương thức
2. Chương trình được chia thành các lớp đối tượng
3. Các cấu trúc dữ liệu được thiết kế sao cho đặc tả được các đối tượng
4. Các phương thức xác định trên các vùng dữ liệu của đối tượng được găn với
nhau trên cấu trúc dữ liệu đó.
5. Dữ liệu được bao đóng, che giấu và không cho phép các thành phần bên ngoài
truy nhập tự do.
6. Các đối tượng trao đổi với nhau thông qua các phương thức.
7. Dữ liệu và các phương thức mới có thế dễ dàng bổ sung vào đối tượng nào đó
khi cần thiết.
8. Các chương trình được thiết kế theo cách tiếp cận từ dưới lên (bottom-up)
2. Một số khái niệm cơ bản
Câu hỏi đầu tiên mà chúng ta se tìm hiểu là: Lập trình hướng đối tượng là gì?
Câu trả lời rất đơn giản: Đó là lập trình mà trong đó có sự mô tả chính xác các đối
tượng trong thế giới thực, bằng cách sử dụng các đối tượng trong mã nguồn. Lập trình
hướng đối tượng đơn giản là mô hình hoá chính xác thế giới thực trong chương trình.
Nó cho phép sử dụng lại mã nguồn. Cốt yếu của lập trình hướng đối tượng là tạo ra
một đối tượng, chứa các thuộc tính và phương thức. Ví dụ chiếc ô tô là một đối tượng,
nó có các thuộc tính như màu, số cánh cửa, nó cung có các phương thức như đi, dừng
... Bạn có thể sử dụng đối tượng bằng cách thực thi một hoặc các phương thức của nó.
Một đối tượng trong lập trình cung tương tự như vậy. Nó có các thuộc tính được mô tả
và tất nhiên thức bạn có thể sử dụng các phương của nó.
Có bốn khái niệm cơ bản trong ly thuyết lập trình hướng đối tượng. Đó là các
khái niệm: sự trừu tượng hoá, sự đóng gói, kế thừa và đa hình. Nhưng trước khi tìm
hiểu các khái niệm này chúng ta hãy tìm hiểu các khái niệm trong một hệ thống hướng
đối tượng:
2.1. Hệ thống hướng đối tượng
Hệ thống hướng đối tượng là một hệ thống có các đặc điểm sau:
- Gồm tập hợp các đối tượng (objects). Đối tượng là sự bao gói của hai loại thành
phần là dữ liệu và các thao tác trên chính dữ liệu đó.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 12
- Trong hệ thống các đối tượng có thể được thừa hưởng các thuộc tính từ các đối
tượng khác
- Sự hoạt động của hệ thống là sự tương tác giữa các đối tượng, các đối tượng tương
tác với nhau thông qua cơ chế truyền thông điệp (thông báo và gửi nhận thông báo)
2.2. Đối tượng (Objects)
Đối tượng mô tả sự vật tồn tại hoặc khái niệm trong đời sống thực tế. Ví dụ, một đối
tượng có thể mô tả một khái niệm vật ly như nhân viên, công ty, hoặc ô tô, nó cung mô
tả một cái gì đó trừu tượng như hình tròn, điểm, …Về phương diện lập trình đối tượng
được định nghĩa là một sự thể hiện của một lớp. Nó còn được định nghĩa là phiên bản
trong khi thi hành của lớp. Đối tượng chính là các thực thể cơ bản trong hệ thống
hướng đối tượng. Môi đối tượng được bao hàm bởi tên và trạng thái (dữ liệu của
nó). Các biến trong đối tượng chỉ rõ mọi thông tin về trạng thái của đối tượng (state)
còn các phương thức thì xác định nó có thể sử dụng như thế nào (behavior). Ví dụ, một
cái xe đạp có một trạng thái (số bánh xe và bánh răng), và các thao tác (phanh, tăng
tốc, giảm tốc, thay đổi bánh răng).
2.3. Thuộc tính (Attribute) và Phương thức (method)
Thuộc tính (Attribute)
Thuộc tính của một lớp bao gồm các biến, hằng hay tham số nội tại của nó.
Biến có vai trò quan trọng nhất bởi chúng có thể bị thay đổi trong suốt quá trình tồn
tại, hoạt động của đối tượng. Các thuộc tính có thể được xác định kiểu và kiểu của
chúng có thể là các kiểu dữ liệu cổ điển hay đó là một lớp đã định nghĩa từ trước. Các
thuộc tính còn được gọi là dữ liệu thành viên.
Phương thức (method)
Là các hàm nội tại của đối tượng (hay lớp).Tuỳ theo đặc tính mà người lập trình
gán cho, một phương thức có thể chỉ được gọi bên trong các hàm khác của lớp đó, có
thể cho phép các câu lệnh bên ngoài lớp gọi tới nó, hay chỉ cho phép các lớp có quan
hệ đặc biệt như là quan hệ lớp con, và quan hệ bạn bè (friend) được phép gọi tới nó.
Môi phương thức đều có thể có kiểu trả về, chúng có thể trả các kiểu dữ liệu cổ điển
hay trả về một kiểu là một lớp đã được định nghĩa từ trước. Một tên gọi khác của
phương thức của một lớp là hàm thành viên
2.4. Lớp (Class) và lớp con (SubClass)
Lớp (Class)
Một lớp là một bản thiết kế hoặc bản mẫu dùng để định nghĩa các biến (dữ liệu)
và phương thức (lệnh) cho tất cả các đối tượng cùng loại Lớp sử dụng như là một kiểu
do người dùng định nghĩa. Do đó một lớp định nghĩa kiểu dữ liệu giống như kiểu dữ
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 13
liệu sẵn có của một ngôn ngữ lập trình. Một đối tượng là một sự thể hiện cụ thể của
một lớp.
Việc truy xuất các biến và các phương thức được khai báo trong lớp phụ thuộc
vào việc khai báo chúng là riêng (private) hay chung (public). Một biến được khai báo
là public se được nhìn thấy ở mọi nơi. Còn biến khai báo là private se không xuất hiện
bên ngoài của lớp.
Tóm lại, lớp (class) là nhóm các đối tượng có cùng thuộc tính, cùng hành vi và
cùng mối liên hệ. Mối đối tượng được coi là một sự thể hiện (instant) của một lớp
Lớp con (SubClass)
Lớp con là một lớp thông thường nhưng có thêm tính chất kế thừa một phần
hoặc toàn bộ các đặc tính của một lớp khác. Lớp chia sẻ các đặc tính được gọi là lớp
cơ sở hoặc lớp cha (Base Class)
2.5. Lớp trừu tượng
Lớp trừu tượng là lớp mà nó không thể chuyển thành một lớp thực tế nào. Nó
được thiết kế nhằm tạo ra một lớp có các đặc tính tổng quát nhưng bản thân lớp đó
chưa có y nghĩa nên chưa thể tiến hành viết mã cho đối tượng. Ví dụ: Lớp
"hinh_phang" được định nghĩa không có dữ liệu nội tại và chỉ có các phương pháp
(hàm nội tại) "tinh_chu_vi", "tinh_dien_tich". Nhưng vì lớp hình_phẳng này chưa xác
định được đầy đủ các đặc tính của nó (cụ thể các biến nội tại là toạ độ các đỉnh nếu là
đa giác, là đường bán kính và toạ độ tâm nếu là hình tròn, ...) nên nó chỉ có thể được
viết thành một lớp trừu tượng. Sau đó, người lập trình có thể tạo ra các lớp con chẳng
hạn như là lớp "tam_giac", lớp "hinh_tron", lớp "tu_giac",.... Và trong các lớp con này
người viết mã se cung cấp các dữ liệu nội tại (như là biến nội tại r làm bán kính và
hằng số nội tại Pi cho lớp "hinh_tron" và sau đó viết mã cụ thể cho các phương pháp
"tinh_chu_vi" và "tinh_dien_tich")
2.6. Truyền thông điệp
Thông điệp: là phương tiện để đối tượng này chuyển tới đối tượng khác, yêu
cầu đối tượng thực hiện một trong số các phương thức. Một thông điệp bao gồm 3
phần:
- Handle của đối tượng đích (đối tượng chủ)
- Tên phương thức cần thực hiện
- Các thông tin cần thiết khác (các đối số)
Khi hệ thống yêu cầu một đối tượng nào đó thực hiện một phương thức thì hệ
thống se gửi thông báo và các tham số cần thiết cho đối tượng đó. Sau khi kiểm tra
thông báo đó là hợp lệ hàm tương ứng với phương thức đó se được gọi thực hiện.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 14
2.7. Sự trừu tượng hoá (abstraction)
Sự trừu tượng hoá đã có trong lập trình cấu trúc khi chuyển sang hướng đối
tượng nó trở thành một khái niệm rất quan trọng.
Đây là khả năng của chương trình bỏ qua hay không chú y đến một số khía
cạnh của thông tin mà nó đang trực tiếp làm việc lên, nghĩa là nó có khả năng tập trung
vào những cốt lõi cần thiết. Nói cách khác sự trừu tượng hoá được xem như việc quan
sát một sự vật hiện tượng mà không cần biết các chi tiết không quan trọng bên trong
nó (trừu tượng hoá dữ liệu) và không quan tâm tới nó làm thế nào để thực hiện công
việc (sự trừu tượng hoá chức năng – Functional Abstraction). Người ta thường phân
nhóm sự vật, hiện tượng theo thuộc tính và hành vi, chỉ quan tâm tới những đặc điểm
quan trọng và bỏ qua các chi tiết không liên quan.
2.8. Sự đóng gói (encapsulation)
Sự đóng gói (Encapsulation) bao gồm hai khái niệm là sự bao đóng và che giấu
thông tin. Nó là cơ chế ràng buộc dữ liệu và các thao tác trên dữ liệu thành một thể
thống nhất gọi là lớp. Tính chất này không cho phép người sử dụng các đối tượng thay
đổi trạng thái nội tại của một đối tượng. Chỉ có các thao tác nội tại của đối tượng cho
phép thay đổi trạng thái của nó. Việc cho phép môi trường bên ngoài tác động lên các
dữ liệu nội tại của một đối tượng theo cách nào là hoàn toàn tuỳ thuộc vào người lập
trình. Đây là tính chất đảm bảo sự toàn vẹn của đối tượng.
Sự đóng gói cho phép người lập trình quyết định cái gì se được che giấu, cái gì
se được xuất hiện. Các ưu điểm của sự đóng kín là:
Quản ly sự phức tạp
Quản ly sự thay đổi
Bảo vệ dữ liệu
Ví dụ: với số tài khoản trong ngân hàng lưu trữ thì người gửi tiền không quan
tâm tới việc lưu trữ như nào (bằng máy tính hay bằng sổ sách), anh ta chỉ quan tâm tới
việc có rút được tiền khi cần hay không?
2.9. Tính kế thừa
Lớp bao gồm các khái niệm có quan hệ chặt che với nhau. Lớp có thể được sử
dụng để tạo ra các lớp khác thông qua việc kế thừa.
Kế thừa là một kỹ thuật cho phép người lập trình có được các lớp từ các lớp sẵn
có. Lớp nhận được hay còn gọi là lớp dẫn xuất (derived class) se kế thừa các phương
thức và dữ liệu của lớp cha (lớp cơ sở - base class), sau đó lớp này có thể bố sung các
phương thức hoặc định nghĩa lại các phương thức mà nó kế thừa. Các phương thức cha
se không bị ảnh hưởng bởi những sự sửa đổi này.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 15
Ví dụ, sự khác nhau của các loại xe đạp như xe đạp leo núi, xe đua, …là chúng
kế thừa các thuộc tính cơ bản của một chiếc xe đạp nhưng có những sự thay đổi về cấu
tạo để thích ứng với mục đích sử dụng. Ví dụ, xe đạp leo núi se có thêm các bánh răng.
Hình sau thể hiện sự kế thừa
Xe đạp
Xe leo núi Xe đua Xe 2 người đạp
Hình 1.5 Lập trình hướng đối tượng
1.10. Tính đa hình
Khái niệm đa hình thường được chỉ ra thông qua từ khoá “Một giao diện nhiều
phương thức” “one interface, multiple methods”. Nó đơn giản là khả năng đưa một
phương thức có cùng tên trong các lớp con. Các phương thức riêng lẻ có thể được thực
thi các nhiệm vụ giống nhau nhưng cung có thể khác khi kiểu của các đối số hoặc số
lượng đối số đưa vào khác nhau. Hay nói cách khác đó là khả năng một thông điệp có
thể thay đổi cách thực hiện của nó theo lớp cụ thể của đối tượng nhận thông điệp
Ví dụ như việc in tất cả các thuộc tính của một chiếc xe đạp. Thì với những loại
xe khác nhau việc in các thuộc tính cung se khác. Bằng việc sử dụng tính đa hình, các
phương thức khác nhau có thể đặt cùng tên nhưng chỉ cần khác đối số.
Bản chất của tính đa hình là dựa trên sự kết nối (binding), đó chính là quá trình
găn một phương thức với một hàm cụ thể. Khi sử dụng tính đa hình cho các phương
thức thì trình biên dịch chưa thể xác định hàm nào se tương ứng với phương thức nào
se được gọi. Khi một đối tượng nhận một thông điệp cần xác định thông điệp đó tương
ứng với hàm thành phần nào se thực thi hàm thành phần đó. Có hai loại kết nối là kết
nối sớm và kết nối muộn. Kết nối muộn hay còn gọi là kết nối lúc chạy (runtime
binding) là việc kết nối khi chạy chương trình, tức là lúc chạy chương trình hàm cụ thể
mới được xác định và gọi thực hiện.
Kết gán sớm là việc kết gán một thông báo với một hàm và thời điểm kết gán là
lúc biên dịch.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 16
3. Các bước cần thiết để thiết kế chương trình theo
hướng đối tượng
Chương trình theo hướng đối tượng bao gồm một tập các đối tượng và mối
quan hệ giữa các đối tượng với nhau. Vì vậy, lập trình trong ngôn ngữ hướng đối
tượng bao gồm các bước sau:
1. Xác định các dạng đối tượng (lớp) của bài toán
2. Tìm kiếm các đặc tính chung (dữ liệu chung) trong các dạng đối tượng này,
những gì chúng cùng nhau chia sẻ.
3. Xác định lớp cơ sở dựa trên cơ sở các đặc tính chung của các dạng đối tượng.
4. Từ lớp cơ sở xây dựng các lớp dẫn xuất chứa các thành phần, những đặc tính,
những đặc tính không chung còn lại của các dạng đối tượng. Ngoài ra, ta còn
đưa ra các lớp có quan hệ với lớp cơ sở và lớp dẫn xuất.
4. Các ưu điểm của lập trình hướng đối tượng
Cách tiếp cận hướng đối tượng giải quyết được nhiều vấn đề tồn tại trong quá
trình phát triển phần mềm và tạo ra được những sản phẩm phần mềm có chất lượng
cao. Những ưu điểm chính của lập trình hướng đối tượng là:
1. Thông qua nguyên ly kế thừa, có thể loại bỏ những đoạn chương trình lặp lại
trong quá trình mô tả và mở rộng khả năng sử dụng các lớp đã được xây dựng.
2. Chương trình được xây dựng từ những đơn thể trao đổi với nhau nên việc thiết
kế và lập trình se được thực hiện theo qui trình nhất định chứ không phải dựa
vào kinh nghiệm và kỹ thuật như trước. Điều này đảm bảo rút ngăn được thời
gian xây dựng hệ thống và tăng năng suất lao động.
3. Nguyên ly che giấu thông tin giúp người lập trình tạo ra được những đoạn
chương trình an toàn không bị thay đổi những đoạn chương trình khác.
4. Có thể xây dựng được ánh xạ các đối tượng của bài toán vào đối tượng của
chương trình.
5. Cách tiếp cận thiết kế đặt trọng tâm vào đối tượng, giúp chúng ta xây dựng
được mô hình chi tiết và gần với dạng cài đặt hơn.
6. Những hệ thống hướng đối tượng dễ mở rộng, nâng cấp thành những hệ thống
lớn hơn
7. Kỹ thuật truyền thông báo trong việc trao đổi thông tin giữa các đối tượng giúp
cho việc mô tả giao diện với các hệ thống bên ngoài trở nên đơn giản hơn.
8. Có thể quản ly được độ phức tạp của những sản phẩm phần mềm.
Không phải trong hệ thống hướng đối tượng nào cung có tất cả các tính chất
trên. Khả năng có các tính chất đó còn phụ thuộc vào lĩnh vực ứng dụng của dự án tin
học và vào phương pháp thực hiện của người phát triển phần mềm.
5. Một số ngôn ngữ lập trình hướng đối tượng
Ngôn ngữ OOP đầu tiên là ngôn ngữ Smalltalk, sau đó các ngôn ngữ như
Object Pascal, Oject C, C++, Delphi, Java được ra đời.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 17
Lập trình hướng đối tượng không là đặc quyền của một ngôn ngữ nào đặc biệt.
Cung giống như lập trình có cấu trúc, những khái niệm trong lập trình hướng đối
tượng có thể cài đặt trong những ngôn ngữ lập trình như C hoặc Pascal,... Tuy nhiên,
đối với những chương trình lớn thì vấn đề lập trình se trở nên phức tạp. Những ngôn
ngữ được thiết kế đặc biệt, hô trợ cho việc mô tả, cài đặt các khái niệm của phương
pháp hướng đối tượng được gọi chung là ngôn ngữ đối tượng. Dựa vào khả năng đáp
ứng các khái niệm về hướng đối tượng, ta có chia ra làm hai loại:
1. Ngôn ngữ lập trình dựa trên hướng đối tượng
2. Ngôn ngữ lập trình hướng đối tượng
Lập trình dựa trên đối tượng là kiểu lập trình hô trợ chính cho việc bao gói, che
giấu thông tin và định danh các đối tượng. Lập trình dựa trên đối tượng có các đặc tính
sau:
Bao gói dữ liệu
Cơ chế che giấu và truy nhập dữ liệu
Tự động tạo lập và xoá bỏ đối tượng
Phép toán tải bội
Ngôn ngữ hô trợ cho kiểu lập trình trên được gọi là ngôn ngữ lập trình trên đối
tượng. Ngôn ngữ lớp này không hô trợ cho việc thực hiện kế thừa và liên kết động
chẳng hạn như Ada là ngôn ngữ dựa trên đối tượng.
Lập trình hướng đối tượng là kiểu lập trình dựa trên đối tượng và bổ sung thêm
nhiều cấu trúc để cài đặt những quan hệ về kế thừa và liên kết động. Vì vậy đặc tính
của lập trình hướng đối tượng có thể viết ngăn gọn như sau:
Các đặc tính dựa trên đối tượng + kế thừa + liên kết động.
Ngôn ngữ hô trợ cho những đặc tính trên được gọi là ngôn ngữ lập trình hướng
đối tượng, ví dụ như C++, Smalltalk, Object Pascal ....
Việc chọn ngôn ngữ để cài đặt phần mềm phụ thuộc nhiều vào các đặc tính và
yêu cầu của bài toán ứng dụng, vào khả năng sử dụng lại của những chương trình đã
có và vào tổ chức của nhóm tham gia xây dựng phần mềm.
Tóm lại, khi lựa chọn một ngôn ngữ lập trình hướng đối tượng bạn cần xem
xét các yếu tố sau:
- Dễ học
- Các lớp được định nghĩa sẵn
- Các công cụ và môi trường lập trình tích hợp
- Dễ debug
- Khả năng kiểm tra code
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 18
- Tốc độ thực thi
- Quản ly bộ nhớ
- Truy cập tới các thư viện có sẵn
- Khả năng tạo ra những ứng dụng độc lập, có khả năng phân phối.
6. Một số ứng dụng của lập trình hướng đối tượng
Lập trình hướng đối tượng đang được ứng dụng để phát triển phần mềm
trong nhiều lĩnh vực khác nhau. Trong số đó, có ứng dụng quan trọng và nổi tiếng
nhất hiện nay là hệ điều hành Windows của hãng Microsoft được phát triển dựa
trên kỹ thuật lập trình hướng đối tượng. Một số lĩnh vực ứng dụng chính của kỹ
thuật lập trình hướng đối tượng bao gồm:
- Những hệ thống làm việc theo thời gian thực
- Trong lĩnh vực mô hình hoá hoặc mô phỏng các quá trình
- Các cơ sở dữ liệu hướng đối tượng
- Những hệ siêu văn bản, multimedia
- Lĩnh vực trí tuệ nhân tạo và các hệ chuyên gia
- Lập trình song song và mạng nơron
- Những hệ tự động hoá văn phòng và trợ giúp quyết định.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 19
Chương 2. GIỚI THIỆU VỀ C++
1. Lịch sử của C++
C++ về bản chất được xây dựng trên nền của ngôn ngữ lập trình C. Do đó,
chúng ta se băt đầu lướt qua lịch sử của C. Ngôn ngữ lập trình C được ra phát minh bởi
Dennis Ritchie vào năm 1972 tại phòng thí nghiệm Bell Telephone. Nó được thiết kế
nhằm mục đích tạo ngôn ngữ để viết hệ điều hành UNIX. Lịch sử của C và Unix găn
liền với nhau. Vì ly do này mà phần lớn hệ điều hành Unix được hoàn thành với C, chỉ
có một số thành phần là được viết bằng ngôn ngữ BCPL.
Ngôn ngữ lập trình C++ được phát minh bởi Bjarne Stroustroup. C++ băt đầu
năm 1979 với phiên bản đầu tiên gọi là "C with classes" - C có lớp, cái tên này có vẻ
không hợp ly nên đã đổi thành C++. Phiên bản đầu tiên của C++ được sử dụng vào
tháng 8 năm 1983 tại AT&T. Phiên bản thương mại đầu tiên ra măt năm 1985. Ngôn
ngữ C++ chuẩn được năm giữ bởi ANSI và ISO. Đó là ly do tại sao bạn hay nghe thấy
C++ chuẩn là ANSI C++ hoặc ISO C++
C++ là gì?
Bạn có thể thấy rằng ngôn ngữ lập trình C được phát triển đầu tiên sau đó C++
mới được phát triển. Bạn có thể tự hỏi, C++ nó là cái gì? Nó có mối quan hệ thế nào
với C. Câu trả lời là: C++ cơ bản là C ở một mức độ mới. Sự khác biệt quan trọng duy
nhất là C++ hô trợ hướng đối tượng. Các đoạn mã viết bằng C được dịch và chạy tốt
với hầu hết các chương trình dịch của C++ nhưng điều ngược lại không đúng. C++ hô
trợ tất cả các lệnh của C và có mở rộng
2. Các mở rộng của C++
Như chúng ta đã biết C++ là sự mở rộng của C tức là mọi khả năng, khái niệm
trong C đều dùng được trong C++. Các chương trình viết bằng C có phần mở rộng là
*.C để chạy vẫn chương trình này với trình biên dịch của C++ ta chỉ cần đổi phần mở
rộng thành *.CPP bởi phần mở rộng mặc định của các chương trình viết bằng C++ có
đuôi là .CPP
2.1. Lời chú thích
Để chú thích trong C ta sử dụng /*…*/ khi đó trình biên dịch se bỏ qua mọi thứ
có trong đó. Trong nhiều trường hợp ta chỉ chú thích trên một dòng.
Ví dụ:
/* Day la dong chu thich */
Ta thấy việc chú thích như vậy có vẻ mất thời gian, trong C++ đã bổ sung thêm
một kiểu chú thích mới bằng cặp //
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 20
Ví dụ:
// Day la 1 dong chu thich.
Kiểu chú thích này thích hợp cho việc chú thích một dòng ngăn. Còn để chú
thích nhiều dòng ta nên dùng kiểu chú thích cu /*…*/
2.2. Từ khoá mới
Một số từ khoá đã được bổ đưa vào C++ nhằm bổ sung thêm một số tính năng
mới. Các từ khoá mới đó là:
Bảng 2.1 Từ khóa mới
asm catch class
delete friend inline
new operator private
protected public template
this throw try
virtual
Trong đó từ khoá new và delete là hai toán tử dùng để quản ly bộ nhớ động thay
cho các hàm cấp phát động malloc, free của C. Hàm inline cho phép tăng tốc độ thực
hiện chương trình. Các từ khoá khác như class, public, virtual … bạn se được tìm hiểu
ở các phần sau.
Nếu trong chương trình bạn viết bằng C có sử dụng các tên trùng với các từ
khoá trên thì bạn phải thay đổi trước khi dịch lại bằng C++ nếu không chương trình
dịch se báo lôi.
2.3. Kiểu dữ liệu char và int
Trong C một hằng kí tự được xem là nguyên nên nó có kích thước là 2 byte còn
trong C++ hằng kí tự được xem là kiểu char và có kích thước là 1 byte
Ví dụ:
sizeof(‘A’)=sizeof(int)=2
sizeof(‘A’)=sizeof(char)=1
2.4. Khai báo biến
Khi viết chương trình trong C, trước khi sử dụng biến ta phải khai báo và phải
đặt ở đầu khối hay đầu chương trình. Vị trí biến khác nhau, vị trí sử dụng khác nhau
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 21
dẫn tới việc kiểm soát chương trình khó khăn. Để khăc phục khó khăn này C++ cho
phép người lập trình khai báo biến ở bất kỳ nơi nào trước khi sử dụng. Phạm vi hoạt
động của các biến kiểu này là khối trong đó biến được khai báo.
Ví dụ:
Chương trình sau nhập một dãy số thực rồi săp xếp theo thứ tự tăng dần:
#include <stdio.h>
#include <conio.h>
#include <alloc.h>
void main()
{
int n;
printf(“\nSo phan tu cua day n=”);
scanf(“%d”,&n);
float *x=(float*)malloc((n+1)*sizeof(float));
for (int i=0;i<n;i++)
{
printf(“\nX[%d]=”,i);
scanf(“%f”,x+i);
}
for(i=0;i<n;++i)
for(int j=i+1;j<n;++j)
if(x[i]>x[j])
{
Float tg=x[i];
x[i]=x[j];
x[j]=tg;
}
printf(“\n Day sau khi sap xep\n”);
for(i=0;i<n;++i)
printf(“%0.2f”,x[i]);
getch();
}
2.5. Chuyển đổi và ép kiểu
C++ cho phép người dùng đổi kiểu một cách rộng rãi, trình biên dịch tự động
thực hiện việc chuyển kiểu này:
- Khi gán một giá trị kiểu số vào một biến kiểu khác
- Các kiểu số khác nhau có thể có mặt trong cùng một biểu thức, và đặc biệt là
khi truyền đối số cho hàm.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 22
Khi C++ tự động đổi từ các đối tượng nhỏ thành các đối tượng lớn thì không có
vấn đề gì
Ví dụ chuyển từ kiểu short(16 bits) long(32 bits)
Nhưng khi chuyển ngược lại thì có thể có vấn đề:
Ví dụ: long (32bít) short(16bit): có thể mất dữ liệu
Trong trường hợp này trình biên dịch của C++ se phát sinh thông báo đối với
các chuyển đổi tự động gây mất dữ liệu
Nếu trong trường hợp không thể tự động chuyển kiểu người lập trình có thể ép
kiểu một cách tường minh như sau:
Ép kiểu kiểu cu (C): myInt = (int) myFloat
Ép kiểu kiểu mới (C++): myInt = int(myFloat)
Phép chuyển kiểu này có dạng như một hàm số chuyển kiểu đang được gọi.
Cách chuyển kiểu này thường được sử dụng trong thực tế.
Ví dụ:
Chương trình sau tính tổng S
Với n là một số nguyên dương nhập từ bàn phím.
#include <stdio.h>
#include <conio.h>
void main() {
int n;
printf("\n So phan tu cua day N=");
scanf("%d",&n);
float s=0.0;
for (int i=1;i<=n;++i)
s+= float(1)/float(i); //chuyen kieu
theo C++
printf("S=%0.2f",s); getch();
}
2.6. Vào ra trong C++
Để xuất dữ liệu ra màn hình và nhập dữ liệu từ bàn phím, trong C++ vẫn có thể
dùng hàm printf() và scanf(), ngoài ra trong C++ ta có thể dùng dòng nhập xuất chuẩn
để nhập/xuất dữ liệu thông qua hai biến đối tượng của dòng (stream object) là cout và
cin.
- Xuất dữ liệu
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 23
Cú pháp:
cout<<biểu thức 1<<..<<biểu thức n;
Trong đó cout được định nghĩa trước như một đối tượng biểu diễn cho thiết bị
xuất chuẩn cho C++ là màn hình, cout được sử dụng kết hợp với toán tử chèn << để
hiển thị giá trị các biểu thức 1,2,.., N ra màn hình.
- Nhập dữ liệu
Cú pháp:
cin>>biến1>>…>>biến n;
Toán tử cin được định nghĩa trước như một đối tượng biểu diễn cho thiết bị vào
chuẩn cảu C++ là bàn phím, cin được sử dụng kết hợp với toán tử trích>> để nhập dữ
liệu từ bàn phím cho các biến 1,2,…,n
Chú ý:
Để nhập một chuôi không quá n ky tự và lưu vào mảng một chiều a (kiểu char)
có thể dùng hàm cin.get như sau: cin.get(a,n);
Toán tử nhập cin>> se để lại ky tự chuyển dòng’\n’ trong bộ đệm. Ky tự này có
thể làm trôi phương thước cin.get. Để khăc phục tình trạng trên cần dùng phương thức
cin.ignore(1) để bỏ qua một kí tự chuyển dòng.
Để sử dụng các loại toán tử và phương thức nói trên ta cần khai báo tập tin dẫn
hướng iostream.h
- Đinh dạng khi in ra màn hình
Để qui định số thực được hiển thị ra màn hình với p chữ số sau dấu chấm thập
phân, ta sử dụng đồng thời các hàm:
setiosflags(ios::showpoint);//bật cờ hiệu
setprecision(p)
Các hàm này cần đặt trong toán tử xuất như sau:
cout<<setiosflags(io::showpoint)<<setprecision(p);
Câu lệnh trên se có hiệu lực với tất cả các toán tử xuất tiếp theo cho tới khi gặp
một câu lệnh đinh dạng mới.
Để qui định độ rộng tối thiểu để hiện thị là k vị trí cho giá trị ( nguyên, thực,
chuôi) ta dùng hàm setw(k)
Hàm này cần đặt trong toán tử xuất và nó chỉ có hiệu lực cho một giá trị được in
gấn nhất. Các giá trị in tiếp theo se có độ rộng tối thiểu mặc định là 0, như vậy câu
lệnh:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 24
cout<<setw(6)<<”Khoa”<<”HTTT”
se in ra chuôi “ KhoaHTTT”
Ví dụ:
Chương trình sau cho phép nhập một danh sách không quá 100 thí sinh. Dữ liệu
môi thí sinh gồm họ tên, các điểm thi môn 1, môn 2, môn 3. Sau đó in danh sách thí
sinh theo thứ tự giảm dần của tổng điểm.
#include <iostream.h>
#include <conio.h>
#include <iomanip.h>
void main()
{
struct {
char ht[25];
float d1,d2,d3,td;
}ts[100],tg;
int n,i,j;
clrscr();
cout << "So thi sinh:";
cin >> n;
for (i=0;i<n;++i)
{
cout << "\n Thi sinh:"<<i;
cout << "\n Ho ten:";
cin.ignore(1);
cin.get(ts[i].ht,25);
cout << "Diem cac mon thi :";
cin>>ts[i].d1>>ts[i].d2>>ts[i].d3;
ts[i].td=ts[i].d1+ts[i].d2+ts[i].d3;
}
for (i=0;i<n-1;++i)
for(j=i+1;j<n;++j)
if(ts[i].td<ts[j].td)
{
tg=ts[i];
ts[i]=ts[j];
ts[j]=tg;
}
cout<< "\ Danh sach thi sinh sau khi sap xep :";
for (i=0;i<n;++i)
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 25
{
cout<< "\n" <<
setw(25)<<ts[i].ht<<setw(5)<<ts[i].td;
}
getch();
}
Ví dụ Chương trình sau đây cho phép nhập một mảng thực cấp 50x50. Sau đó
in ma trận dưới dạng bảng và tìm một phần tử lớn nhất.
#include <iostream.h>
#include <iomanip.h>
#include <conio.h>
void main()
{
float a[50][50],smax; int m,n,i,j,imax,jmax;
clrscr();
cout<< "Nhap so hang va cot:";
cin>>m>>n;
for (i=0;i<m;++i)
for (j=0;j<n;++j)
{
cout<< "a["<<i<<","<<j<<"]=";
cin>> a[i][j];
}
smax= a[0][0];
imax=0;
jmax=0;
for (i=0;i<m;++i)
for (j=0;j<n;++j)
if (smax<a[i][j])
{
smax=a[i][j];
imax=i;
jmax=j;
}
cout << "\n\n Mang da nhap";
cout << setiosflags(ios::showpoint)<<setprecision(1);
for (i=0;i<m;++i)
for (j=0;j<n;++j)
{
if (j==0) cout<<"\n";
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 26
cout << setw(6)<<a[i][j];
}
cout << "\n\n"<< "Phan tu max:"<< "\n";
cout << "co gia tri ="<<setw(6)<<smax;
cout<<"\nTai hang"<<imax<< " cot "<<jmax;
getch();
}
2.7. Cấp phát và giải phóng bộ nhớ
Trong C có thể sử dụng các hàm cấp phát bộ nhớ như malloc(), calloc() và hàm
free() để giải phóng bộ nhớ được cấp phát. C++ đưa thêm một cách thức mới để thực
hiện việc cấp phát bộ nhớ và giải phóng bộ nhớ bằng cách dùng hai toán tử new và
delete.
- Toán tử new để cấp phát bộ nhớ
Toán tử new thay cho hàm malloc() và calloc() của C có cú pháp như sau:
new Tên kiểu ;
hoặc new (Tên kiểu);
Trong đó Tên kiểu là kiểu dữ liệu của biến con trỏ, nó có thể là: các kiểu dữ
liệu chuẩn như int, float, double, char,... hoặc các kiểu do người lập trình định nghĩa
như mảng, cấu trúc, lớp,... Chú y: Để cấp phát bộ nhớ cho mảng một chiều, dùng cú
pháp như sau:
Biến con trỏ = new kiểu[n];
Trong đó n là số nguyên dương xác định số phần tử của mảng.
Ví dụ: float *p = new float; //cấp phát bộ nhớ cho biến con trỏ p có kiểu
float
int *a = new int[100]; //cấp phát bộ nhớ để lưu trữ mảng một chiều a
// gồm 100 phần tử
Khi sử dụng toán tử new để cấp phát bộ nhớ, nếu không đủ bộ nhớ để cấp phát,
new se trả lại giá trị NULL cho con trỏ. Đoạn chương trình sau minh họa cách kiểm tra
lôi cấp phát bộ nhớ:
double *p;
int n;
cout<< “\n So phan tu : ”;
cin>>n;
p = new double[n]
if (p == NULL)
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 27
{
cout << “Loi cap phat bo nho”;
exit(0);
}
Toán tử delete
Toán tử delete thay cho hàm free() của C, nó có cú pháp như sau:
delete con trỏ ;
Ví dụ 2.6 Chương trình sau minh hoạ cách dùng new để cấp phát bộ nhớ chứa n
thí sinh. Môi thí sinh là một cấu trúc gồm các trường ht(họ tên), sobd(số báo danh), và
td(tổng điểm). Chương trình se nhập n, cấp phát bộ nhớ chứa n thí sinh, kiểm tra lôi
cấp phát bộ nhớ, nhập n thí sinh, săp xếp thí sinh theo thứ tự giảm của tổng điểm, in
danh sách thí sinh sau khi săp xếp, giải phóng bộ nhớ đã cấp phát.
#include <iomanip.h>
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
struct TS {
char ht[20];
long sobd;
float td;
};
void main(void)
{
TS *ts;
int n;
cout<<"\nSo thi sinh n = ";
cin>>n;
ts = new TS[n+1];
if (ts == NULL)
{
cout << "\n Loi cap phat vung
nho";
getch();
exit(0);
}
for (int i=0;i<n;++i)
{
cout << "\n Thi sinh thu "<<i;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 28
cout<< "\n Ho ten";
cin.ignore(1);
cin.get (ts[i].ht,20);
cout << "so bao danh";
cin>> ts[i].sobd;
cout<< "tong diem:";
cin>>ts[i].td;
}
for (i=0;i<n-1;++i)
for (int j=i+1;j<n;++j)
if (ts[i].td<ts[j].td)
{
TS tg=ts[i];
ts[i]=ts[j];
ts[j]=tg;
}
cout <<
setiosflags(ios::showpoint)<<setprecision(1);
for (i=0;i<n;++i)
cout << "\n" <<
setw(20)<<ts[i].ht<<setw(6)<<ts[i].td;
delete ts;
getch();
}
2.8. Biến tham chiếu
Trong C có 2 loại biến là: Biến giá trị dùng để chứa dữ liệu (nguyên, thực, ky
tự,...) và biến con trỏ dùng để chứa địa chỉ. Các biến này đều được cung cấp bộ nhớ và
có địa chỉ. C++ cho phép sử dụng loại biến thứ ba là biến tham chiếu. C++ cho phép
khai báo một biến tham chiếu dùng tham chiếu tới một biến tồn tại trong bộ nhớ. Khi
biến tham chiếu này được thay đổi thì lập tức biến được tham chiếu cung thay đổi.
Đây là một mở rộng rất tiện bởi nó cho phép hàm thao tác trực tiếp trên biến được
truyền. Biến tham chiếu là một tên khác (bí danh) cho biến đã định nghĩa trước đó. Cú
pháp khai báo biến tham chiếu như sau:
Kiểu &Biến tham chiếu = Biến;
Biến tham chiếu có đặc điểm là nó được dùng làm bí danh cho một biến (kiểu
giá trị) nào đó và sử dụng vùng nhớ của biến này.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 29
Ví dụ: Với câu lệnh: int a, &tong=a; thì tong là bí danh của biến a và biến
tong dùng chung vùng nhớ của biến a. Lúc này, trong mọi câu lệnh, viết a hay viết
tong đều có y nghĩa như nhau, vì đều truy nhập đến cùng một vùng nhớ. Mọi sự thay
đổi đối với biến tong đều ảnh hưởng đối với biến a và ngược lại.
Ví dụ: int a, &tong = a;
tong =1; //a=1
cout<< tong; //in ra số 1
tong++; //a=2
++a; //a=3
cout<<tong; //in ra số 3
Chú ý:
Trong khai báo biến tham chiếu phải chỉ rõ tham chiếu đến biến nào
Biến tham chiếu có thể tham chiếu đến một phần tử mảng, nhưng không cho
phép khai báo mảng tham chiếu
Biếu tham chiếu có thể tham gia chiếu đến một hằng. Khi đó nó sử dụng vùng
nhớ của hằng và có thể làm thay đổi giá trị chứa trong vùng nhớ này.
Biến tham chiếu thường được sử dụng làm đối của hàm để cho phép hàm truy
nhập đến các tham biến trong lời gọi hàm.
2.9. Hằng tham chiếu
Cú pháp khai báo hằng tham chiếu như sau:
const kiểu dữ liệu &biến = biến/hằng
ví dụ:
int n=10;
const int &m=n;
const int &p=123;
hằng tham chiếu có thể tham chiếu đến một biến hoặc một hằng.
Chú ý:
Biến tham chiếu và hằng tham chiếu khác nhau ở chô: không cho phép dùng
hằng tham chiếu để là thay đổi giá trị của vung nhớ mà nó tham chiếu
Ví dụ:
Int u=12,z
const int &p=y; //hằng tham chiếu p tham chiếu tới biến y
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 30
p=p+1; //sai biên dịch se báo lôi
Hằng tham chiếu cho phép sử dụng giá trị chứa trong một vùng nhớ, nhưng
không cho phép thay đổi giá trị này.
Hằng tham chiếu thường được sử dụng là tham số của hàm để cho phép sử dụng
giá trị của các tham số trong lời gọi hàm, nhưng tránh làm thay đổi giá trị tham số.
2.10. Truyền tham số cho hàm theo tham chiếu
Trong C chỉ có một cách truyền dữ liệu cho hàm là truyền theo theo giá trị.
Chương trình se tạo ra các bản sao của các tham số thực sự trong lời gọi hàm và se
thao tác trên các bản sao này chứ không xử ly trực tiếp với các tham số thực sự. Cơ
chế này rất tốt nếu khi thực hiện hàm trong chương trình không cần làm thay đổi giá trị
của biến gốc. Tuy nhiên, nhiều khi ta lại muốn những tham số đó thay đổi khi thực
hiện hàm trong chương trình. C++ cung cấp thêm cách truyền dữ liệu cho hàm theo
tham chiếu bằng cách dùng đối là tham chiếu. Cách làm này có ưu diểm là không cần
tạo ra các bản sao của các tham số, do dó tiết kiệm bộ nhớ và thời gian chạy máy. Mặt
khác, hàm này se thao tác trực tiếp trên vùng nhớ của các tham số, do đó dễ dàng thay
đổi giá trị các tham số khi cần.
Ví dụ:
Nhập dãy số thực và săp xếp dãy theo thứ tự tăng dần.
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void nhapds(double *a,int n)
{
for(int i=0;i<n;++i)
{
cout<<"\n Phan tu thu "<<i<<":";
cin>>a[i];
}
}
void hv(double &x,double &y)
{
double tam=x;x=y;y=tam;
}
void sapxep(double *a,int n)
{
for(int i=0;i<n-1;++i)
for(int j=i+1;j<n;++j)
if(a[i]>a[j])
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 31
hv(a[i],a[j]);
}
void main()
{
double x[100];
int i,n;
clrscr();
cout<<"\n nhap so phan tu N = ";
cin>>n;
nhapds(x,n);
sapxep(x,n);
cout<<"\nCac phan tu mang sau khi sap xep :";
for(i=0;i<n;++i)
printf("\n%6.2f",x[i]);
getch();
}
Ví dụ nhập danh sách thí sinh bao gồm họ tên, điểm các môn 1, môn 2, môn 3 và in
danh sách thí sinh:
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include <iomanip.h>
struct TS
{
char ht[20];
float d1,d2,d3,td;
};
void ints(const TS &ts)
{
cout<<setiosflags(ios::showpoint)<<setprecision(1);
cout<<"\n ho
ten"<<setw(20)<<ts.ht<<setw(6)<<ts.td;
}
void nhapsl(TS *ts,int n)
{
for(int i=0;i<n;++i)
{
cout<<"\n Thi sinh"<<i;
cout<<"\n ho ten ";
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 32
cin.ignore(1);
cin.get(ts[i].ht,25);
cout<<"Nhap diem cac mon thi : ";
cin>>ts[i].d1>>ts[i].d2>>ts[i].d3;
ts[i].td=ts[i].d1+ts[i].d2+ts[i].d3;
}
}
void hvts(TS &ts1,TS &ts2)
{
TS tg=ts1;
ts1=ts2;
ts2=tg;
}
void sapxep(TS *ts,int n)
{
for(int i=0;i<n-1;++i)
for(int j=i+1;j<n;++j)
if(ts[i].td<ts[j].td)
hvts(ts[i],ts[j]) ;
}
void main()
{
TS ts[100];
int n,i;
clrscr();
cout<<"So thi sinh : ";
cin>>n;
nhapsl(ts,n);
sapxep(ts,n);
float dc;
cout<<"\n\nDanh sach thi sinh \n";
for(i=0;i<n;++i)
if(ts[i].td>=dc)
ints(ts[i]);
else
break;
getch();
}
Hàm trả về giá trị tham chiếu
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 33
C++ cho phép hàm trả về giá trị là một tham chiếu, lúc này định nghĩa của hàm có
dạng như sau:
Kiểu &tênhàm()
{
//thân hàm
return <biến phạm vi toàn cục>;
}
Trong truờng hợp này biểu thức được trả lại trong câu lệng return phải là tên của một
biến xác định từ bên ngoài hàm, bởi vì khi đó mới có thể sử dụng được giá trị của hàm.
Khi ta trả về một tham chiều đến một biến cục bộ khai báo bên trong hàm, biến này se
bị mất đi khi kết thúc hàm. Do vậy tham chiếu của hàm se không còn y nghĩa gì nữa.
Khi giá trị trả về của hàm là tham chiếu, ta có thể gặp các câu lệnh gán hơi khác
thường, trong đó vế trái là một lời gọi hàm chứ không phải là tên của một biến. Điều
này hoàn toàn hợp ly, bời vì bản thân hàm đó có giá trị trả về là một tham chiếu. Nói
cách khác, vế trái của lệnh gán có thể là lời gọi đến của một hàm có giá trị trả về là
một tham chiếu
Ví dụ:
#include <iostream.h>
#include <conio.h>
int z;
int &f()// ham tra ve mot bi danh cua bien toan bo z
{
return z;
}
void main()
{
f()=50;//z=50
cout<<"\nz="<<z;
getch();
}
Ví dụ
#include <iostreams.h>
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 34
#include <stdio.h>
#include <string.h>
#include <conio.h>
int & max(int& a, int& b);
void main()
{
clrscr();
int b =10, a= 7, c= 20;
cout << "Max a,b : "<<max(b,a) << endl;
max(b,a)++;
cout << "Gia tri b va a :"<< b <<" "<<a <<endl;
max(b,c)=5;
cout << "Gia tri b va a va c :"<<b<<" "<<a<<"
"<<c<< endl;
}
int &max(int &a, int &b)
{
return a>b ? a:b;
}
Kết quả trên màn hình se là : Max a,b : 10
Gia tri cua b va a : 11 7
Gia tri cua b va a va c : 11 7 5
2.11. Hàm với tham số có giá trị mặc định
C++ cho phép xây dựng hàm với các tham số được khởi gán giá trị mặc định. Qui tăc
xây dựng hàm với tham số mặc định như sau:
Các đối có giá trị mặc định cần là các tham số cuối cùng tính từ trái qua phải
Nếu chương trình sử dụng khái báo nguyên mẫu hàm thì các hàm số mặc định cần
được khởi gán trong nguyên mẫu hàm, không được khởi gán lại cho các đối mặc định
trong dòng đầu của địng nghĩa hàm.
Void f(int a, float x, char *st=”trung tam”,int b=1,double y=1.234);
Void f(int a,float x, char *st=”trung tam”,int b=1, double y=1.234)
{
//Các câu lệnh
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 35
Khi xây dựng hàm nếu không khai báo nguyên mẫu, thì các đối mặc định được khởi
gán trong dòng đầu cảu định nghĩa hàm, ví dụ:
void f(int a, float x, char *st=”TRUNG TAM”, int b=1, double y = 1.234)
{
//Các câu lệnh
}
Chú ý:
Đối với các hàm có tham số mặc định thì lời gọi hàm cần viết theo quy định: Các
tham số văng mặt trong lời gọi hàm tương ứng với các tham số mặc định cuối cùng
(tính từ trái sang phải), ví dụ với hàm:
void f(int a, float x, char *st=”TRUNG TAM”, int b=1, double y = 1.234);
thì các lời gọi hàm đúng:
f(3,3.4,”TIN HOC”,10,1.0);//Đầy đủ tham số
f(3,3.4,”ABC”); //Thiếu 2 tham số cuối
f(3,3.4); //Thiếu 3 tham số cuối
Các lời gọi hàm sai:
f(3);
f(3,3.4, ,10);
2.12. Các hàm nội tuyến
Việc tổ chức chương trình thành các hàm có ưu điểm chương trình được chia thành
các đơn vị độc lập, điều này giảm được kích thước chương trình, vì môi đoạn chương
trình thực hiện nhiệm vụ của hàm được thay bằng lời gọi hàm. Tuy nhiên hàm cung có
nhược điểm là làm là chậm tốc độ thực hiện chương trình vì phải thực hiện một số thao
tác có tính thủ tục môi khi gọi hàm như: cấp phát vùng nhớ cho các tham số và biến
cục bộ, truyền dữ liệu của các tham số cho các đối, giải phóng vùng nhớ trước khi
thoát khỏi hàm.
C++ cho khả năng khăc phục được nhược điểm nói trên bằng cách dùng hàm nội
tuyến. Để biến một hàm thành hàm nội tuyến ta viết thêm từ khóa inline vào trước khai
báo nguyên mẫu hàm.
Chú y: Trong mọi trường hợp, từ khóa inline phải xuất hiện trước các lời gọi hàm thì
trình biên dịch mới biết cần xử ly hàm theo kiểu inline. Ví dụ hàm f() trong chương
trình sau se không phải là hàm nội tuyến vì inline viết sau lời gọi hàm.
Ví dụ:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 36
#include <iostream.h>
#include <conio.h>
void main()
{
int s ;
s=f(5,6);
cout<<s;
getch();
}
inline int f(int a,int b)
{
return a*b;
}
Chú ý:
Chương trình dịch các hàm inline như tương tự như các macro, nghĩa là nó se thay
đổi lời gọi hàm bằng một đoạn chương trình thực hiện nhiệm vụ hàm. Cách làm này se
tăng tốc độ chương trình do không phải thực hiện các thao tác có tính thủ tục khi gọi
hàm nhưng lại làm tăng khối lượng bộ nhớ chương trình (nhất là đối với các hàm nội
tuyến có nhiều câu lệnh). Vì vậy chỉ nên dùng hàm inline đối với các hàm có nội dung
đơn giản.
Không phải khi gặp từ khoá inline là chương trình dịch nhất thiết phải xử ly
hàm theo kiểu nội tuyến. Từ khoá inline chỉ là một từ khoá gợi y cho chương
trình dịch chứ không phải là một mệnh lệnh băt buộc.
2.13. Hàm tải bội
Các hàm tải bội là các hàm có cùng một tên và có tập đối khác nhau (về số lượng các
đối hoặc kiểu). Khi gặp lời gọi các hàm tải bội thì trình biên dịch se căn cứ vào số
lượng và kiểu các tham số để gọi hàm có đúng tên và đúng các tham số tương ứng.
Ví dụ:
Chương trình tìm max của một dãy số nguyên và max của một dẫy số thực. Trong
chương trình có 6 hàm: hai hàm dùng để nhập dãy số nguyên và dãy số thực có tên
chung là nhapds, bốn hàm: tính max 2 số nguyên, tính max 2 số thực, tính max của
dẫy số nguyên, tính max của dẫy số thực được đặt chung một tên là max.
#include <iostream.h>
#include <conio.h>
#include <iomanip.h>
void nhapds(int *x,int n);
void nhapds(double *x,int n);
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 37
int max(int x,int y);
double max(double x,double y);
void nhapds(int *x,int n)
{
for(int i=0;i<n;++i)
{
cout<<"Phan tu "<<i<<" = ";
cin>>x[i];
}
}
void nhapds(double *x,int n)
{
for (int i=0;i<n;i++)
{
cout<<"Phan tu "<<i<<" = ";
cin>>x[i];
}
}
}
double max(double *x,int n)
{
double s=x[0];
for(int i=1;i<n;++i)
s=max(s,x[i]);
return s;
}
void main()
{
int a[20],n,ni,nd,maxi;
double x[20],maxd;
clrscr();
cout<<"\n So phan tu nguyen n: ";
cin>>ni;
cout<<"\n Nhap day so nguyen: ";
nhapds(a,ni);
cout<<"\n So phan tu so thuc: ";
cin>>nd;
cout<<"\n Nhap day so thuc: ";
nhapds(x,nd);
maxi=max(a,ni);
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 38
maxd=max(x,nd);
cout<<"\n Max day so nguyen ="<<maxi;
cout<<"\n Max day so thuc="<<maxd;
getch();
}
Chú y: Nếu hai hàm trùng tên và trùng đối thì trình biên dịch không thể phân biệt
được. Ngay cả khi hai hàm này có cùng kiểu khác nhau thì trình biên dịch vẫn báo lôi.
Ví dụ sau xây dựng hai hàm cùng có tên là f và cùng một đối nguyên a, nhưng kiểu
hàm khác nhau. Hàm thứ nhất có kiểu nguyên( trả về a*a), hàm thứ hai có kiểu void.
Chương trình sau se bị thông báo lôi khi biên dịch.
Ví dụ:
#include <iostream.h>
#include <conio.h>
int f(int a);
void f(int a);
int f(int a)
{
return a*a;
}
void f(int a)
{
cout<<"\n"<<a;
}
void main()
{
int b = f(5);
f(b);
getch();
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 39
Chương 3. LỚP VÀ ĐỐI TƯỢNG
1. Xây dựng lớp và đối tượng
Như chúng ta đã biết lớp là một mô tả trừu tượng của nhóm các đối tượng cùng
bản chất, cùng loại, ngược lại môi đối tượng lại là một thể hiện cụ thể của một lớp. Xét
về phương diện cấu trúc dữ liệu ta có thể xem lớp như là một kiểu dữ liệu mới do
người dùng định nghĩa, kiểu dữ liệu này tiên tiến hơn kiểu dữ liệu cấu trúc ở chô nó
không chỉ có dữ liệu mà còn bao gói cả các thao tác trên dữ liệu đó. Lúc này đối tượng
chính là các biến thể hiện cho kiểu dữ liệu.
Trong C++ ta khai báo lớp với cú pháp như sau:
1.1. Khai báo lớp
class <tên lớp>
{
[quyền truy xuất:]
<khai báo các thành phần riêng của lớp>
[quyền truy xuất:]
<khai báo các thành phần dùng chung>
};
Trong đó:
Tên lớp: do người lập trình đặt và phải tuân theo các qui tăc về tên
Các thành phần của lớp có thể là các dữ liệu, thuộc tính và phương thức
Quyền truy xuất: là khả năng truy xuất thành phần dữ liệu của các hàm thành phần:
o private: chỉ quyền truy xuất trong phạm vi lớp đó (bởi các hàm thành phần
trong lớp)
o public: được truy xuất ở mọi nơi nếu đối tượng đó tồn tại
o protected: chỉ được truy xuất trong phạm vi lớp đó và các lớp con kế thừa từ
nó.
Phạm vi tác động của 1 từ khóa quyền truy xuất kà từ lúc nó xuất hiện cho đến
hết khai báo lớp hoặc gặp từ khóa xác định quyền truy xuất khác.
Nếu không xác định quyền truy xuất thì ngầm định là private. Ở kiểu dữ liệu
cấu trúc, các thành phần dữ liệu đều có quyền truy xuất là public, nghĩa là các dữ liệu
này có quyền truy nhập ở mọi nơi. Đó là yếu điểm của kiểu dữ liệu cấu trúc, lớp đã
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 40
khăc phục nhược điểm này bằng cách cho quyền truy xuất mặc định các thành phần dữ
liệu là private, chỉ có các hàm trong lớp mới có quyền truy xuất các thành phần dữ liệu
này.
Khai báo các thành phần:
- Dữ liệu: tương tự khai báo biến
<kiểu dữ liệu> <tên thành phần>
Chú y: khi khai báo thành phần dữ liệu không gán giá trị ban đầu, không được
khai báo từ khóa extern, register
- Các hàm thành phần:
Có hai cách để khai báo hàm thành phần:
+ Chỉ khai báo nguyên mẫu trong lớp và định nghĩa ngoài lớp
+ Định nghĩa hàm thành phần ngay trong khai báo lớp
Khai báo:
<kiểu hàm> <tên hàm>([khai báo tham số])
Khi khai báo hàm thành phần ngoài khai báo lớp ta dùng cú pháp sau:
<kiểu hàm> <tên lớp>::<tên hàm>([khai báo tham số])
{
<thân hàm>
}
Nhận xét: Ta thấy rằng khai báo lớp cung gần giống như khai báo cấu trúc, chỉ khác là
trong khai báo lớp có thêm các hàm thành viên và các nhãn phạm vi truy xuất private,
public. Sự khác nhau giữa lớp và cấu trúc là kiểu truy xuất mặc định của struct là
public còn kiểu truy xuất mặc định của lớp là private.
Ví dụ:
Xây dựng cấu trúc dữ liệu mô tả các sinh viên
- Thuộc tính: họ tên, ngày sinh, giới tính, lớp, dtb
- Các hàm: nhập, in danh sách
Lớp sinh viên
class SV
{
private:
char ht[30];
char ns[8];
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 41
char lop[10];
float dtb;
public:
void nhap(){
cout<<”\n Nhap ho ten: “;
gets(ht);
cout<<”\n Nhap ngay sinh: “;
gets(ns);
cout<”\n Nhap diem trung binh:”;
cin>>dtb;
}
void in();
};
void SV::in(){
cout<<”\n Ho ten: “<<ht<<endl;
cout<<”\n Ngay sinh: “<<ns<<endl;
cout<”\n Diem trung binh:”<<dtb<<endl;
…
Ở ví dụ trên ta thấy các thành phần dữ liệu như họ tênm ngày sinh, lớp, điểm trung
bình có quyền truy xuất là private, nghĩa là chỉ có các hàm thành viên của lớp mới có
quyền truy xuất các thành phần dữ liệu này. Ngược lại, hai hàm thành viên nhập và in
một sinh viên có quyền truy xuất là public. Các lệnh hay người sử dụng bên ngoài
muốn truy nhập tới các thành phần dữ liệu thì phải thông qua việc sử dụng các hàm
thành viên này.
Ví dụ:
Xây dựng cấu trúc dữ liệu mô tả các hóa đơn mua bán vật tư:
- Thuộc tính: mã vật tư, tên vật tư, loại phiếu, khối lượng, đơn giá, thành tiền,….
- Hàm: nhập danh sách, in danh sách, xác định loại phiếu, tính tổng …
Lớp các hóa đơn
class hoadon
{
char mavt[5]
char tenvt[20]
byte lp;
int kl,dg,tt;
public:
void nhap();
void in()
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 42
int loai();
int tinhtong();
};
Ví dụ này cho ta thấy rằng khi không khai báo quyền truy xuất thì quyền ngầm định
quyền truy xuất là private, tức là các dữ liệu thành viên như mã vật tư, tên vật tư, loại
phiếu, khối lượng, đơn giá, thành tiền có quyền truy xuất là private.
Ví dụ: xây dựng cấu trúc dữ liệu mô tả các phân số
- Dữ liệu: tử số, mẫu số
- Phương thức: nhập, in, tối giản
Lớp phân số
class PS
{
protected:
int tso, mso;
public:
void nhap();
void in();
protected:
void toigian();
};
void PS::nhap()
{
cout<<”Nhap tu so:<<”\n”;
cin>>tso;
do
cout<<”nhap mau so:”<<”\n”;
cin>>ms;
while (ms==0);
}
Ở ví dụ này, hai dữ liệu thành phần là tử số và mẫu số có quyền truy xuất là
protected. Hai hàm thành viên nhập và in có quyền truy xuất là public nhưng sau đó
hàm thành viên tối giản lại có quyền truy xuất là protected. Có nghĩa là dữ liệu và hàm
tối chỉ chỉ có thể được sử dụng bởi các thành phần trong lớp phân số và lớp con kế
thừa lớp phân số.
1.2. Khai báo đối tượng
Khi một lớp được định nghĩa, tên của nó có thể được sử dụng để khai báo đối
tượng theo cú pháp sau:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 43
<tên lớp> <tên đối tượng>;
Ví dụ:
Khai báo đối tượng sinh viên:
SV sv1,vs2;
sv1,sv2 là các đối tượng sinh viên
Khai báo đối tượng hóa đơn:
hoadon hd;
hd là một đối tượng hóa đơn
Chú y: một lớp có thể phát sinh ra nhiều đối tượng, môi đối tượng gọi là các thể
hiện (instant) của lớp đó, môi đối tượng có bản sao riêng về thành phần dữ liệu gọi là
các biến thể hiện. Thành phần dữ liệu của các đối tượng là độc lập nhau.
Các đối tượng dùng chung các phương thức của lớp
Kích thước một lớp bằng tổng kích thước các thành phần dữ liệu ( không tính
các hàm)
1.3. Truy xuất các thành phần của đối tượng
- Thành phần dữ liệu:
<tên đối tượng>.<tên tp dữ liệu>
Cần chú y rằng dữ liệu thành phần riêng chỉ có thể truy nhập bởi các hàm thành
phần của cùng một lớp, đối tượng của lớp cung không thể truy nhập.
Ví dụ:
sv1.ht
sv2.ns
cout<<”Chao ban “<<sv1.ht; sai do sv1.ht có quyền là private
sv2.nhap(); đúng
hoặc
<tên con trỏ><tên tp dữ liệu>;
- Thành phần hàm
<tên đối tượng>.<tên hàm>([ds đối số])
Ví dụ:
hoadon hd1;
hd1.nhap();
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 44
hd1.in();
hoặc
<tên con trỏ><tên hàm>([ds đối số]);
Chú ý:
- Khi thực hiện một phương thức đối với một đối tượng nào đó thì hệ thống se ngầm
truyền vào trong thân phương thức đó một con trỏ có tên là this. Con trỏ này trỏ tới
chính đối tượng thực hiện phương thức.
Ví dụ:
a.nhap(); thì a là đối tượng ẩn
- Trong thân một phương thức khi truy xuất một thành phần mà không viết rõ
tên đối tượng thì se được hiểu thành phần đó của đối tượng ẩn. Viết tường minh se như
sau
kq.pt = pt + a.pt;
kq.pt = thispt + a.pt;
- Một truy xuất được xem là hợp lệ khi chỉ khi đúng cú pháp và đảm bảo đúng
quyền truy xuất
Ví dụ:
Xây dựng lớp phân số { tử số, mẫu số, nhập, in phân số, cộng hai phân số, tích
hai phân số}
Viết hàm main nhập 2 phân số a,b tính và in ra a+b, a*b
#include "iostream.h"
#include "math.h"
#include "conio.h"
class PS{
private:
int ts,ms;
public:
void nhap();
void in();
void toigian();
PS tong(PS);
PS tich(PS);
};
void PS::nhap(){
cout<<"Nhap tu so:";cin>>ts;
cout<<"\n Nhap mau so:";
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 45
do
{
cin>>ms;
if(ms==0)cout<<"\nMau so phai khac 0\n";
}
while(ms==0);
toigian();
}
void PS::in()
{
if(ts==0)cout<<0;
else if(ms==-1) cout<<-ts;
else if(ms==1) cout<<ts;
else if(ts*ms<0) cout<<-
abs(ts)<<"/"<<abs(ms);
else cout<<abs(ts)<<"/"<<abs(ms)<<endl;
}
void PS::toigian()
{
int a=abs(ts);
int b=abs(ms);
if(a!=0)
{
while(a!=b)
{
if(a>b) a=a-b;
else b-=a;
}
ts/=a;
ms/=a;
}
}
PS PS::tong(PS a){
PS kq;
kq.ts=ts*a.ms+ms*a.ts;
kq.ms=ms*a.ms;
kq.toigian();
return kq;
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 46
PS PS::tich(PS a){
PS kq;
kq.ts=ts*a.ts;
kq.ms=ms*a.ms;
kq.toigian();
return kq;
}
void main(){
PS a,b;
a.nhap();b.nhap();
a.cong(b).in();
a.tich(b).in();
getch();
}
2. Các phương thức
Một đối tượng thường có 4 kiểu hành vi cơ bản sau: tạo, truy vấn, cập nhập và
hủy. Để cài đặt cho các hành vi trên ta có thể sử dụng các phương thức sau:
- Các phương thức tạo – Constructor
Dùng để khởi tạo một thể hiện của lớp
- Các phương thức truy vấn – Queries
Dùng để hỏi về dữ liệu của lớp
- Các phương thức cập nhập – Updates
Dùng để cập nhập, thay đổi trạng thái của đối tượng
- Các phương thức hủy – Destructor
Dùng để dọn dẹp, thu hồi bộ nhớ khi hủy một đối tượng
2.1. Hàm khởi tạo - Constructor
Khi người lập trình khai báo biến thì các giá trị chứa trong biến đó thường là
chưa xác định, trong rất nhiều trường hợp ta phải đảm bảo là các biến phải được khởi
tạo giá trị nào đó trước khi dùng. Tương tự với các lớp trong C++ các thành viên dữ
liệu cần được khởi tạo trước khi sử dụng các phương thức tác động lên, nhưng như ta
đã biết trong C++ không cho phép khởi tạo thành phần dữ liệu ngay khi khai báo trong
lớp. Để khởi tạo các thành phần dữ liệu ta sử dụng hàm khởi tạo Constructor
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 47
Hàm khởi tạo là một hàm thành phần đặc biệt của lớp cho phép khởi tạo đối
tượng; nó được gọi thực hiện một cách tự động khi đối tượng phát sinh.
Khai báo hàm khởi tạo
Cú pháp:
Khai báo trong lớp:
<tên lóp>([ds tham số])
Định nghĩa:
<tên lớp>::<tên lớp>([ds tham số])
{
//thân hàm
}
Ví dụ:
class PS
{
int tso, mso;
public:
PS()
{
tso=0;
mso=1;
}
PS(int ts, int ms)
{
tso=ts;
mso=ms;
}
void nhap();
void in();
void toigian();
};
PS a,b; //Gọi hàm khởi tạo thứ nhất cho a,b
PS c(1,5); //Gọi hàm khởi tạo thứ 2 cho c
PS *d; //khai báo con trỏ chưa phát sinh đối tượng
Như vậy:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 48
- Hàm khởi tạo có với mọi lớp
- Tên hàm giống như tên lớp
- Hàm tạo không có kiểu nên không cần khai báo
- Hàm tạo không có giá trị trả về
- Nếu lập trình viên không xây dựng hàm khởi tạo thì chương trình se tự động
sinh ra hàm khởi tạo mặc định
- Không được phép gọi các hàm khởi tạo một cách tường minh bởi chúng được
gọi tự động khi khai báo các thể hiện của lớp.
Một số hàm khởi tạo:
- Hàm khởi tạo mặc định (default constructor): là hàm khởi tạo được gọi khi đối
tượng được khai báo mà không có đối số
- Hàm khởi tạo sao chép (copy destructor)là hàm khởi tạo đặc biệt khi ta gọi đối
tượng mới là bản sao của đối tượng đã có sẵn
Khai báo:
<tên lớp>([const] <tên lớp> & <tên tham số>);
ví dụ:
class PS
{
int tso, mso;
public:
PS(){int ts=0;int ms=1;};
PS(const PS &);
void nhap();
void in();
void toigian();
}
PS::PS(const PS & a)
{
tso=a.tso;
mso=a.mso;
}
void main()
{
PS p;
PS q(p); //Tạo q theo p
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 49
2.2. Hàm hủy – Destructor
Hàm hủy là một hàm thành phần của lớp với chức năng hủy bỏ, giải phóng các
đối tượng khi nó hết phạm vi tồn tại.
Khai báo:
tên: ~<tên lớp>
Hàm hủy không có đối, không có giá trị trả về và không thể định nghĩa lại
Hàm hủy trùng tên với lớp và có dấu ~ ở trước.
Tác động: hàm destructor được gọi khi đối tượng bị hủy bỏ tức là khi sự thực
hiện chương trình rời khỏi phạm vi mà trong đó đối tượng của lớp được khởi tạo. Thực
chất hàm destructor không hủy bỏ đối tượng mà nó chỉ thực hiện một số công việc
trước khi hệ thống giải phóng bộ nhớ.
Mọi lớp đều có hàm hủy do đó nếu người lập trình không định nghĩa tường
minh hàm hủy thì chương trình dịch se tự phát sinh một hàm hủy mặc định.
Ví dụ:
class VT
{
int spt;
int *a;
public:
VT2(int n);
VT2(const VT2 &);
~VT2()
{
delete []a;
}
};
Nếu
p:con trỏ p = new int delete p;
q: mảng q= new int[n] delete []q;
Tóm lại, hai hàm constructor và destructor là hai hàm thành phần đặc biệt cho
phép khởi tạo và hủy bỏ đối tượng. Hai hàm này được gọi một cách tự động. Thứ tự
các hàm này được gọi phụ thuộc vào thứ tự trong đó sự thực hiện vào và rời khỏi
phạm vi mà các đối tượng được khởi tạo. Một cách tổng quát, các destructor được gọi
theo thứ tự ngược với thứ tự của các constructor được gọi. Các constructor được gọi
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 50
của các đối tượng khai báo trong phạm vi toàn cục trước bất kỳ hàm nào (bao gồm
hàm main()) trong file mà băt đầu thực hiện. Các destructor tương ứng được gọi khi
hàm main() kết thúc hoặc hàm exit() được gọi. Hoặc khi ta dùng con trỏ kiểu đối
tượng, khi ta khai báo và xin cấp phát bộ nhớ cho con trỏ, hàm constructor se được
gọi, còn khi ta giải phóng vùng nhớ mà con trỏ đang chiếm giữ bằng lệnh delete , thì
hàm destructor se được gọi.
Ví dụ:
class A{
public:
A()
{
cout<<” \nHam khoi tao!”;
}
A(A &a)
{
cout<<”\nHam khoi tao sao chep!”;
}
~A()
{
cout<<”\nHam huy!”;
}
};
void main()
{
A a;
{
A b;
A *c;
}
cout<<”\nKet thuc ham main”;
}
Kết quả thực hiện se như sau:
Ham khoi tao! //a
Ham khoi tao! //b
Ham huy! //b
Ket thuc ham main
Ham huy! //a
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 51
Ta xét trường hợp cấp phát động như sau:
void main()
{
A a;
{
A b;
A *c;
c= new A;
}
}
Khi đó kết quả thực hiện se như sau:
Ham khoi tao! //a
Ham khoi tao! //b
Ham khoi tao! //c
Ham huy! //b
Ket thuc ham main
Ham huy! //a
3. Đa năng hóa tóan tử
Với các kiểu dữ liệu chuẩn thì C++ cung cấp cho chúng ta các toán tử +, -, *, /
… để thực hiện các phép toán. Ví dụ
int a, b, c;
a=b + c;
Mã lệnh như trên là hiển nhiên đúng trong C++, kể từ khi các kiểu dữ liệu mới
do người dùng định nghĩa được thêm vào thì việc chùng ta thực hiện các phép tóan
trên kiểu dữ liệu đó là không còn hiển nhiên đúng. Ví dụ:
struct {
string san_pham;
float gia;
} a, b, c;
a=b+c;
Trong thực tế, khi dịch chương trình dịch se báo lôi. Để giải quyết vấn đề này
trong khi khai báo lớp chúng ta thêm vào đó các phương thức thỏa mãn.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 52
Ví dụ
class Vat_tu{
string san_pham;
float gia;
public:
void nhap();
void in();
san_pham cong();
};
san_pham a,b,c;
a = b.cong(c);
Tuy nhiên cách viết như vậy không chuẩn cho lăm, chúng ta luôn muốn viết
theo kiểu
a=b+c;
Trong C++ đã đáp ứng yêu cầu này bằng cách cho phép ta nạp chồng toán tử.
Nghĩa là các toán tử cơ bản chỉ dùng được cho các kiểu cơ bản nay đã áp dụng được
cho các kiểu do người dùng định nghĩa.
Sau đây là danh sách các toán tử có thể nạp chồng.
+ - * / = < > += -= *= /= << >>
<<= >>= == != <= >= ++ -- % & ^ ! |
~ &= ^= |= && || %= [] () , ->* -> new
delete new[] delete[]
Để nạp chồng toán tử ta sử dùng từ khóa operator theo cú pháp sau:
<kiểu trả vê> operator <tên tóan tử>([danh sách tham số])
{
/*…*/
}
Ví dụ: về việc nạp chồng tóan tử operator(+) để cộng hai vec tơ
// vectors: overloading operators example
#include <iostream>
#include <conio.h>;
class Vector {
public:
int x,y;
Vector () {};
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 53
Vector (int,int);
Vector operator + (Vector);
};
Vector::Vector (int a, int b) {
x = a;
y = b;
}
Vector Vector::operator+ (Vector p) {
Vector temp;
temp.x = x + p.x;
temp.y = y + p.y;
return (temp);
}
int main () {
Vector a (3,1);
Vector b (1,2);
Vector c;
c = a + b;
cout << c.x << "," << c.y;
return 0;
}
Chú ý:
- Chỉ định nghĩa lại được các toán tử được chấp nhận bởi C++ ( các toán tử ở bảng
trên)
- Không thể làm thay đổi độ ưu tiên của các toán tử
- Đối với các toán tử 2 ngôi thì tóan hạng bên trái là đối tượng ẩn còn đối tượng bên
phải là đối số. Do đó số tham số của hàm toán tử bằng số toán hạng - 1
- Đối với các toán tử một ngôi, không có đối số thì toán hạng (!) chính là đối tượng
ẩn
Cách gọi hàm toán tử
Có hai cách dùng các hàm toán tử:
- Dùng như cú pháp thông thường của phép toán
Ví dụ
PS a, b,c;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 54
c = a+b;
- Dùng như hàm thành phần của đối tượng
Ví dụ:
PS a, b, c;
c = a.operator+(b);
Xây dựng lớp phân số có hai toán tử + và *
class PS
{
public:
int tso, mso;
public:
void nhap();
void in();
void toigian();
PS operator + (PS);
PS operator *(PS);
};
PS PS::operator + (PS a)
{
PS kq;
kq.tso = tso*a.mso + mso*a.tso;
kq.mso = mso*a.mso;
kq.toigian();
return kq;
}
PS PS::operator * (PS a)
{
PS kq;
kq.tso = tso*a.tso;
kq.mso = mso*a.mso;
kq.toigian();
return kq;
}
void main()
{
PS a,b,c;
//nhap a,b
c= a+b;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 55
cout<<”a+b=”; c.in();
c= a.operator*(b);
cout<<”a*b=”; c.in();
}
Đa năng hóa các toán tử chèn dòng (<<) và trích dòng (>>)
Chúng ta có thể đa năng hóa các toán tử chèn dòng << và trích dòng >> . Hàm
toán tử của toán tử << được đa năng hóa được khai báo như sau:
ostream & operator << (ostream & stream, ClassName Object);
Hàm toán tử << trả về tham chiếu chỉ đến dòng xuất ostream. Tham số thứ nhất
của hàm toán tử << là một tham chiếu chỉ đến dòng xuất ostream, tham số thứ hai là
đối tượng được chèn vào dòng. Khi sử dụng, dòng trao cho toán tử << (tham số thứ
nhất) là toán hạng bên trái và đối tượng được đưa vào dòng (tham số thứ hai) là toán
hạng bên phải. Để bảo đảm cách dùng toán tử << luôn nhất quán, chúng ta không thể
định nghĩa hàm toán tử << như là hàm thành viên của lớp đang xét, thông thường nó
chính là hàm friend.
- Hàm toán tử của toán tử >> được đa năng hóa được khai báo như sau:
istream & operator >> (istream & stream, ClassName Object);
Hàm toán tử >> trả về tham chiếu chỉ đến dòng nhập istream. Tham số thứ nhất
của hàm toán tử này là một tham chiếu chỉ đến dòng nhập istream, tham số thứ hai là
đối tượng của lớp đang xét mà chúng ta muốn tạo dựng nhờ vào dữ liệu lấy từ dòng
nhập. Khi sử dụng, dòng nhập đóng vai toán hạng bên trái, đối tượng nhận dữ liệu
đóng vai toán hạng bên phải. Cung như trường hợp toán tử <<, hàm toán tử >> không
là hàm thành viên của lớp, thông thường nó chính là hàm friend.
VD:Xây dựng lớp phân số và các toán tử >>,<<,+,-
#include<iostream.h>
class PS
{
privave:
int tso, int mso;
public:
PS ( );
PS (int,int );
PS operator + (PS);
PS operator( );
friend istream & operator>>(istream & is,PS & ps);
friend ostream & operator>>(ostream & os,PS & ps);
};
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 56
istream & operator>>(istream & is,PS & ps)
{
cout<<”Nhap tu so:”;
is>>ps.tso;
cout<<”Nhap mau so:”;
is>>ps.mso;
return is;
};
ostream & operator>>(ostream & os,PS & ps)
{
os<<ps.tso<<”/”<<ps.mso;
return os;
};
PS::PS()
{
tso=0;
mso=1;
};
PS::PS(int TS,int MS)
{
tso=TS;
mso=MS;
};
PS PS::operator + (PS ps)
{
PS Tong;
Tong.tso=tso*ps.mso+mso*ps.tso;
Tong.mso=mso*ps.mso;
return Tong;
}
PS PS::operator-()
{
tso = - tso;
return *this;
};
int main()
{
PS a(3,4),b,c;
cout<<”Nhap phan so b:”<<endl;
cin>>;
cout<<”a=”<<a<<endl;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 57
cout<<”b=”<<b<<endl;
c=a+b;
cout<<”c=”<<c<<endl;
-c;
cout<<”-c=”<<c<<endl;
return 0;
}
4. Mảng và con trỏ của lớp
Một điều quan trọng mà bạn đã nhận ra, khi bạn tạo ra một lớp, nó thực sự là
một kiểu dữ liệu mới, cung giống như các kiểu dữ liệu đã có (int, float, char, …). Rõ
ràng lớp phức tạp hơn kiểu int và float, nhưng nó vẫn chỉ là một kiểu dữ liệu và bất kỳ
những gì bạn có thể làm với kiểu dữ liệu chuẩn thì cung có thể làm với một lớp. Có
nghĩa là bạn có thể tạo mảng các lớp và con trỏ tới các lớp. Ví dụ sau đây mô tả một
mảng của lớp. Ví dụ này cung sử dụng một con trỏ trỏ tới mảng của lớp, để nhập dữ
liệu vào một lớp.
#include <iostream>
class sinhvien
{
public:
char hodem[30];
char ten[7];
float dtb;
void hienthi();
};
void sinhvien::hienthi()
{
cout << "*****Thong tin Sinhvien*****\n";
cout << "Ho Dem: " << hodem << endl;
cout << "Ten:" << ten << endl;
cout << "Diem trung binh: " << dtb << endl;
}
int main()
{
sinhvien sv[5];
sinhvien *ptr = sv;
int i;
for (i = 0;i<5;i++)
{
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 58
cout << "Nhap ho dem cua sinh vien: \n";
cin>>ptr->hodem;
cout << "Nhap ten sinh vien: \n";
cin >> ptr->ten;
cout << "Nhap diem trung binh: \n";
cin >> ptr->dtb;
ptr++;
}// het vong lap.
for(i=0;i<5;i++)
{
sv[i].hienthi();
}
return 0;
}
Một mảng của lớp không khác gì mảng các số nguyên integer. Tất cả các quy
tăc áp dụng cho mảng vẫn được áp dụng. Điều tương tự cung đúng cho con trỏ. Khi
tạo một lớp, nó se chiếm một phần bộ nhớ. Khi bạn tạo con trỏ trỏ tới một lớp, nó trỏ
tới byte đầu tiên của vùng nhớ mà lớp chiếm.
Chú y cuối cùng là từ khóa: this. Trong hầu hết các ngôn ngữ lập trình hướng
đối tượng, this thay thế cho thể hiện hiện tại của một lớp bạn đang dùng. Khi một hàm
thành viên tham chiếu thành viên khác của lớp cho đối tượng cụ thể của lớp đó, làm
thế nào C++ bảo đảm rằng đối tượng thích hợp được tham chiếu? Câu trả lời là môi
đối tượng duy trì một con trỏ trỏ tới chính nó – gọi là con trỏ this – Đó là một tham số
ẩn trong tất cả các tham chiếu tới các thành viên bên trong đối tượng đó. Con trỏ this
cung có thể được sử dụng tường minh. Môi đối tượng có thể xác định địa chỉ của chính
mình bằng cách sử dụng từ khóa this. Con trỏ this được sử dụng để tham chiếu cả các
thành viên dữ liệu và hàm thành viên của một đối tượng. Kiểu của con trỏ this phụ
thuộc vào kiểu của đối tượng.
VD: chương trình sau minh họa sử dụng tường minh của con trỏ this để cho
phép một hàm thành viên của lớp Test in dữ liệu X của một đối tượng Test.
#include <iostream.h>
class Test
{
int x;
public:
Test(int i= 0);
void Print();
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 59
};
Test::Test(int i) {
x = i;
}
void Test::Print() {
cout << " x = " << x << endl;
cout<< " this->x = " << this->x << endl;
cout<< "(*this).x = " << (*this).x << endl;
void main()
{
Test t(12);
t.Print();
};
Kết quả chương trình:
x = 12;
this->x = 12;
(*this).x = 12;
5. Các hàm bạn và lớp bạn
Như chúng ta đã biết, các thành viên được khai báo là private hay protected
không thể được truy xuất từ bên ngoài mà chỉ các hàm thành viên của lớp đó mới được
truy xuất. Tuy nhiên, luật này se không có hiệu lực nếu ta sử dụng thêm từ khóa friend.
Friends là các hàm hoặc các lớp được khai báo với từ khóa friend ở trước.
5.1. Hàm bạn (friend function)
Khái niệm
Một hàm friend của một lớp được khai báo bên trong lớp và được định nghĩa
bên ngoài phạm vi của lớp đó, có quyền truy cập đến các thành viên private hoặc
protected của một lớp. Tuy nhiên nó không phải là hàm thành viên của lớp.
Khai báo
Một hàm se trở thành hàm bạn của một lớp nếu trong lớp đó có khai báo
friend <kiểu trả về> <tên hàm>([danh sách tham số]);
Ví dụ:
class SP
{
float pt,pa;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 60
public:
void nhap();
void in();
friend float getpa(SP);
};
float getpa(SP x)
{
return x.pa;
}
void main()
{
SP x;
x.nhap();
cout<<”Phan ao cua x la:”<<getpa(x);
}
Ta thấy hàm getpa là hàm bạn của lớp SP, nó trả về phần ảo của một đối tượng
bất kỳ.
Ví dụ 2 :
Khai báo một hàm bạn tongdientich là bạn của 2 lớp hinhchunhat va hinhtron,
có chức năng tính tổng diện tích của hình chữ nhật và hình tròn.
class hinhtron;
class hinhchunhat {
int cd,cr;
public:
hinhchunhat(int cd1, int cr1);
int tinhdientich( );
friend float tongdientich(hinhchunhat hcn, hinhtron
ht);
};
class hinhtron {
int r;
public:
hinhtron(int r1);
float tinhdientich( );
friend float tongdientich(hinhchunhat hcn, hinhtron ht);
};
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 61
hinhchunhat :: hinhchunhat(int cd1, int cr1)
{
cd = cd1;
cr = cr1;
};
int hinhchunhat :: tinhdientich( )
{
return cd*cr;
};
hinhtron :: hinhtron (int r1)
{
r = r1;
};
float hinhtron :: tinhdientich( )
{
return 3.14*r*r;
};
float tongdientich(hinhchunhat hcn, hinhtron ht)
{
float t;
t = hcn.tinhdientich() + ht.tinhdientich() ;
return t ;
};
Chú ý :
- Không hạn chế số hàm bạn
- Hàm bạn không phải hàm thành viên nên không bị ảnh hưởng bởi từ khóa truy xuất
5.2. Lớp bạn
Lớp A là bạn của lớp B nếu trong B chứa khai báo :
friend class A ;
Nếu A là bạn của B thì mọi hàm thành phần trong A đều trở thành bạn của B
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 62
Như vậy nếu vế trái là đối tượng thuộc lớp đang xét (A) thì một hàm f nào đó
có thể là thành phần có thể là bạn
Nếu vế trái không là đối tượng thuộc lớp đang xét (A) thì hàm f phải là hàm
bạn.
Ví dụ :
Xây dựng lớp số phức với các tóan tử >> , <<
class SP
{
float pt,pa ;
public :
friend istream& operator>>(istream &, SP &)
friend ostream& operator<<(ostream & , const
SP);
};
istream &operator>>(istream &is, SP &a)
{
cout<<”Nhap phan thuc:”;
is>>a.pt;
cout<<”\n Nhap phan ao:”;
is>>a.pa;
return is;
}
ostream &operator<<(ostream &os, SP a)
{
os<<a.pt<<”+”<<a.pa<<”i”;
return os;
}
void main()
{
SP a;
cout<<”Nhap a:\n”;
cin>>a;
cout<<”So phuc vua nhap:”<<a;
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 63
6. Thành phần tĩnh
6.1. Các thành phần dữ liệu tĩnh
Khái niệm:
Thành phần dữ liệu tĩnh là một thành phần dữ liệu của lớp nhưng lại không
“găn” cụ thể với một đối tượng nào mà được dùng chung cho toàn bộ lớp. Nói cách
khác thành phần dữ liệu tĩnh chỉ có một biến thể hiện trên toàn bộ lớp (các đối tượng
của lớp)
Khai báo:
Để khai báo một thành phần dữ liệu là thành phần dữ liệu tĩnh ta sử dụng từ
khóa static như sau:
static <kiểu dữ liệu> <tên thành phần>;
Ví dụ:
Xây dựng lớp thí sinh ngoài các thuộc tính cá nhân như SBD, họ tên, điểm ..
cần thêm một thành phần để ghi số lượng đối tượng thí sinh trong danh sách.
Nhận xét: Số lượng là một thành phần nhưng chỉ có một thể hiện chung cho tất
cả các đối tượng trong lớp thí sinh. Vậy số lượng là thành phần dữ liệu tĩnh.
class TS
{
int sbd;
char ht[30];
float d1,d2,d3;
static int sl;
public:
friend istream& operator>>(istream &is, TS &a);
friend ostream& operator<<(ostream &os, TS &a);
};
Để truy xuất các thành phần dữ liệu tĩnh:
<tên đối tượng>.<tên thành phần >
<tên lớp>::<tên thành phần >
Chú ý:
- Vì thành phần dữ liệu tĩnh không găn với một đối tượng cụ thể nên nó tồn tại
ngay khi chưa có đối tượng nào.
- Thành phần dữ liệu tĩnh phải được gán giá trị ban đầu (khởi tạo) trước khi đối
tượng của lớp được phát sinh và phải khởi tạo ngoài mọi hàm theo cú pháp sau:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 64
<kiểu dữ liệu><tên lớp>::<tên thành phần dữ liệu tĩnh>=<giá trị>;
int TS::sl=0;
Xây dựng lớp Hóa đơn có một thành phần dữ liệu tĩnh để kiểm soát số đối
tượng HĐ được cấp phát.
class hoadon
{
char mavt[5]
char tenvt[20]
byte lp;
int kl,dg,tt;
public:
static int sl;
hoadon(int k=0, int d=0, int t=0)
{
kl=k ;
d=dg ;
t=tt ;
sl++ ;
}
void nhap();
void in()
int loai();
int tinhtong();
~hoadon()
{
sl--;
}
};
int hoadon::sl=0;
void main()
{
hoadon a,b,c;
cout<<”So hoa don hien co la:”<<a.sl;
}
6.2. Các hàm thành viên tĩnh
Xét trường hợp sau:
Xây dựng lớp SV có các phương thức:
- nhập 1 sinh viên
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 65
- in một sinh viên
- Nhập một danh sách sinh viên
- In một danh sách sinh viên.
Ta thấy rằng hai phương thức đầu được dùng cho một đối tượng cụ thể, còn hai
phương thức sau là thao tác chung trên lớp không găn với một đối tượng cụ thể nào.
Để làm được như vậy C++ cung cấp cho chúng ta khái niệm phương thức tĩnh.
Khái niệm:
Phương thức tĩnh là hàm thành phần của lớp nhưng không găn với một đối
tượng cụ thể nào, nó dùng để thao tác chung cho lớp, trong thân hàm không có đối
tượng ẩn.
Khai báo
static <kiểu hàm> <tên hàm>([danh sách tham số]);
Ví dụ:
Quản ly danh sách sinh viên bằng danh sách liên kết với yêu cầu:
SV( ht, lop, dtb)
- con trỏ next, head (tĩnh)
- số lượng các SV (tĩnh)
Phương thức:
- Nhập, in 1 sinh viên
- Nhập, in danh sách sinh viên
- In số lượng sinh viên
class SV
{
char ht[30], lop[10];
float dtb;
SV *next;
static SV *head;
static int sl;
public:
void nhap();
void in();
static void nhapds();
static void inds();
static int getsl()
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 66
{
return sl;)
}
};
int SV::sl=0;
SV *SV::head=NULL;
void SV::nhapds()
{
int i,tl;
SV *p;
i=1;
do
{
cout <<”Nhap sinh vien thu <<i<<”:”;
p=new SV;
pnhap();
pnext=head;
head=p;
sl++;
cout<<”Nhap nua khong?”;
cin>>tl;
}
while (tl=1);
}
void SV::inds()
{
cout<<”\n Danh sacg sinh vien:\n”;
SV *p=head;
while(p)
{
pin();
p=pnext;
cout<<endl;
}
}
void main()
{
cout<<”Danh sach co:”<<SV::getsl();
cout<<”\n Nhap danh sach:\n”;
SV::nhapds();
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 67
cout<<”\n Danh sach vua nhap la:\n”;
SV::inds();
cout<<”Danh sach co: “<<SV::getsl();
}
7. Thành phần hằng
7.1. Dữ liệu hằng
Khái niệm:
Là một thành phần dữ liệu của một lớp mà không thể thay đổi giá trị trong thời
gian tồn tại của đối tượng
Ví dụ:
Với lớp SV thì mã sinh viên là thành phần dữ liệu hằng
Với lớp nhân sự thì số CMT là thành phần dữ liệu hằng
Khai báo:
const <kiểu dữ liệu > <tên thành phần >
Ví dụ:
class SV
{
char ht[30];
const char masv[5];
float dtb;
char lop[10];
public:
…
};
-Khởi tạo giá trị hằng:
Thành phần hằng của một đối tượng chỉ được gán giá trị tại thời điểm hàm khởi
tạo của nó được gọi . Hay nói cách khác nó phải được khởi tạo tại dòng khởi tạo đối
tượng (sau tên hàm khởi tạo, trước khi vào thân của hàm khởi tạo).
Cú pháp:
<tên lớp>::<tên lớp>([danh sách tham số]):<tên thành phần hằng>([đối
số]),…
{
<thân hàm khởi tạo>
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 68
}
Ví dụ với lớp SV
SV::SV(char *msv):masv(msv){…}
Ví dụ:
class SV
{
const int masv;
char ht[30];
char lop[10]
float dtb;
public:
SV(int m=0);
friend istream& operator>>(istream &is, SV &a);
friend ostream & operator<<(ostream &os, SV &a);
};
SV::SV(int m):masv(m)
{
ht[0]=’\0’;
lop[0]=’\0’;
dtb=0;
}
istream & operatorr>>(istream & is, SV &a)
{
cout<<”\Nhap thong tin cho sinh vien co
ma:”<<a.masv;
cout<<”\n Ho ten:”; gets(a.ht);
cout<<”\n Lop:”;gets(a.lop);
cout<<”\nDiem tb:”;is>>a.dbt;
return is;
}
ostream& operator<<(ostream& os, SV &a)
{
...
}
void main()
{
int m;
cout<<”Nhap ma sinh vien”; cin>>m;
SV a(m) ;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 69
cin>>a ;
cout<<" Sinh vien vua nhap :" ; cout>>a ;
}
7.2. hàm thành phần (phương thức hằng)
Phương thức hằng là các hàm thành phần không có khả năng thay đổi thành
phần dữ liệu trong đối tượng.
Ví dụ :
Lớp PS hàm in() là phương thức hằng
class PS
{
int tso, mso ;
public:
void nhap();
void in() const;
void toigian();
};
void PS::in()const
{
cout<<tso<<”/”<<mso;
}
8. Thành phần là đối tượng
Đối tượng thành phần là các thành phần dữ liệu có kiểu là một lớp khác. Khai
báo các thành phần như sau:
<tên lớp> <tên thành phần>;
Ví dụ:
- thành phần ngày sinh của lớp SV là đối tượng của lớp date
- Thành phần cha mẹ của đối tượng SV là thành phần của lớp nhân sự
class date
{
int ngay;
int thang;
int nam;
};
class nguoi
{
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 70
char ht[30];
char qq[50];
date ns;
public:
nguoi(char *hten=” “, char *q=” “);
..
} ;
class SV
{
const int masv ;
char ht[30] ;
char lop[10] ;
float dtb ;
nguoi cha, me ;
public :
SV(char *h, int m, char *tencha, char *tenme) ;
} ;
Chú ý :
- Việc nhập các thành phần như nhập các đối tượng thông thường
- Khi phát sinh một đối tượng của lớp bao phải đảm bảo mọi thành phần của nó đều
được phát sinh
- Nếu lớp thành phần có hàm khởi tạo không đối số hoặc có đối nhưng đối số có giá
trị ngầm định thì hàm khởi tạo của lớp bao có thể gọi tự động đến hàm khởi tạo của
lớp thành phần để phát sinh đối tượng thành phần đó.
- Ngược lại, hàm khởi tạo của lớp thành phần có tham số thì hàm khởi tạo của lớp
bao phải gọi tường minh tới hàm khởi tạo của lớp thành phần.
Ví dụ :
class A
{
int x ;
public:
A(int k)
{
x=k;
…
}
…
};
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 71
class B
{
int y;
A t;
public:
B(int t1, int t2)
{
…
}
…
}
B b1; // Lôi do không có đối số
B b2(4,5); //Lôi do không khởi tạo được t
Nếu viết
B(int t1, int t2):t(t2)
{
...
}
thì khai báo B b2(4,5) mới thỏa mãn
Như vậy thành phần đối tượng được khởi tạo tại dòng khởi tạo tương tự như các
thành phần là hằng. Thứ tự thực hiện khởi tạo theo thứ tự khai báo thành phần trong
lớp.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 72
Chương 4. TÍNH KẾ THỪA
Chương trước giới thiệu về hướng đối tượng và tìm hiểu về các khái niệm sự
trừu tượng và đóng gói. Tuy nhiên, bạn vẫn chưa thấy được sức mạnh của lập trình
hướng đối tượng, sức mạnh đó thể hiện qua sự kế thừa. Sự kế thừa là một quá trình
cho phép một lớp kế thừa hoặc nhận các thành phần public và protected từ một lớp
khác. Thông quá kế thừa bạn có thể tạo ra các lớp tương đương mà không phải viết lại
code mà những code này được sử dụng thường xuyên. Bạn se thấy, trong chương này,
C++ mang lại sức mạnh tuyệt đối trong kế thừa. Điều đó rất quan trọng bởi chỉ một số
ngôn ngữ lập trình hô trợ kế thừa một cách hoàn chỉnh.
Như chúng ta đã biết việc sử dụng lại phần mềm cho phép người lập trình tiết
kiệm thời gian trong quá trình phát triển phần mềm. Khả năng kế thừa giúp cho người
lập trình thực hiện việc này. Có nhiều loại kế thừa sau đây ta se đi vào tìm hiểu hại loại
kế thừa chính là đơn kế thừa và đa kế thừa
1. Khái niệm
Kế thừa là khả năng cho phép xây dựng một lớp mới (lớp con, lớp dẫn xuất)
được thừa hưởng các thành phần từ một hay nhiều lớp đã có (lớp cha, lớp cơ sở).
Trong lớp dẫn xuất ta có thể bổ sung thêm các thành phần mới hoặc định nghĩa lại các
thành phần từ lớp cơ sở.
Ví dụ :
-Xây dựng lớp Phân số PS1:{ tử số, mẫu số, nhập, in, tối giản phân số}
-Xây dựng lớp phân số PS2: {tử số, mẫu số, nhập, in, tối giản, cộng, trừ, nhân,
chia phân số}
Xây dựng lớp người
Xây dựng lớp SV: lớp SV kế thừa từ lớp người
Xây dựng lớp GIÁO VIÊN: lớp GV kế thừa từ lớp người
Kế thừa tạo ra một mô hình phân cấp :
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 73
Đơn kế thừa: là sự kế thừa trong đó một lớp dẫn xuất có một lớp cơ sở
Đa kế thừa: là sự kế thừa trong đó một lớp dẫn xuất có nhiều lớp cơ sở
2. Khai báo lớp dẫn xuất
Cú pháp:
class <lớp dẫn xuất>:[kiểu dẫn xuất] <lớp cơ sở 1 >,
[kiểu dẫn xuất] <lớp cơ sở 2>,…
{
//Khai báo lớp dẫn xuất
};
Trong đó:
- Kiểu dẫn xuất (kiểu kế thừa): gồm có:
-public
-private
C
A B
PS1
PS2
NGƯỜI
SV GV
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 74
-protected
Ví dụ:
class NGUOI
{
char ht[30];
char noisinh[40];
unsigned namsinh;
public:
void nhap();
void in()
{
cout<<ht<<’\t’<<namsinh<<’\t’<<noisinh;
}
};
void NGUOI::nhap()
{
cout<<”Nhap ho ten”;
cin.getline(ht,30);
cout<<”Nhap noi sinh:”;
cin.getline(noisinh,40);
cout<<”Nhap nam sinh:”;
cin>>namsinh;
}
class SV:public NGUOI
{
char lop[10];
float dtb;
public:
void nhap();
void in()
{
NGUOI::in();
cout<<lop<<’\t’<<dtb<<endl;
}
};
void SV::nhap()
{
NGUOI::nhap();
cout<<”Nhap lop:”;
cin.getline(lop,10);
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 75
cout<<”Nhap diem trung binh:”;
cin>>dtb;
}
void main()
{
SV a;
cout<<”Nhap sinh vien\n”;
a.nhap();
cout<<”Thong tin ve sinh vien:\n”;
a.in();
}
Như ta thấy, kích thước của lớp dẫn xuất se bằng tổng kích thước của lớp cở sở
+ thành phần dữ liệu mới.
3. Các kiểu kế thừa
Ở ví dụ trên lớp SV kế thừa lớp người bằng từ khóa public. Đó là hai kiểu kế
thừa chính: private và public ngoài ra còn kiểu kế thừa là protected. Mặc định các lớp
dẫn xuất se kế thừa các lớp cơ sở theo kiểu private, muốn kế thừa theo kiểu public ta
phải khai báo tường minh như ở trên để trình biên dịch biết. Kiểu kế thừa chi phối
quyền truy xuất tới các phần tử của các lớp cơ sở khác nhau. Khi sử dụng kế thừa
public, mọi thành phần được khai báo ở lớp cơ sở là private se trở thành private ở lớp
con. Tương tự, các thành phần khai báo là public ở lớp cha thì ở lớp con cung se là
public.
Khi sử dụng kế thừa kiểu private thì lại khác
Mọi thành phần trong lớp cha khi sang lớp con se trở thành private. Bạn cung
có thể xem thêm kiểu kế thừa protected thông qua hình trên.
Ví dụ:
Nhập đoạn code sau và lưu dưới tên bankacc.h
class bankaccount
{
protected:
float balance;
public:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 76
float withdraw(float);
float deposit(float);
void displaybalance();
bankaccount();
};
bankaccount::bankaccount()
{
balance = 1100;
}
float bankaccount::withdraw(float amount)
{
balance -= amount;
return balance;
}
float bankaccount::deposit(float amount)
{
balance += amount;
return balance;
}
void bankaccount::displaybalance()
{
cout << "Your balance is " << balance << endl;
}
class checking:public bankaccount
{
};
class savings:public bankaccount
{
};
Sau đó nhập đoạn code sau và lưu dưới tên bankacc-1.cpp
#include "bankacc.h"
#include <iostream>
// function prototypes
void menu();
// declare an instance of the checking class
checking mychecking;
int main()
{
menu();
return 0;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 77
}
void menu()
{
// declare other variables
int menuchoice;
float amount, balance;
// display choices
cout << "****** Bank Checking Account*******"<<
endl;
cout << endl;
cout << "1. Check Balance "<< endl;
cout << "2. Make Deposit "<< endl;
cout << "3. Make withdrawal " << endl;
cout << "4. Exit " << endl;
cout << "Please enter your choice " << endl;
// input selection
cin >> menuchoice;
// do a switch based on selection
switch(menuchoice)
{
case 1:
mychecking.displaybalance();
cout << endl;
menu();
break;
case 2:
cout << "Please enter the amount to deposit \n";
cin>> amount;
balance = mychecking.deposit(amount);
cout << "New balance is " << balance << endl;
cout << endl;
menu();
break;
case 3:
cout << "Please enter the amount to withdraw
\n";
cin >> amount;
balance = mychecking.withdraw(amount);
cout << "New balance is " << balance << endl;
cout << endl;
menu();
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 78
break;
case 4:
return;
break;
default:
cout << "Your choice was invalid \n";
menu(); // re display the menu
}// end of switch
}
Bây giờ bạn có thể bạn có thế kế thừa phương thức và thuộc tính từ lớp cha như
thế nào. Ví dụ này cung có giá trị thực tế vì nó cung cấp cơ sở cho việc tạo một
chương trình ngân hàng thực tế. Vấn đề chú y trong ví dụ này là thực tế bạn có thể
thấy phương thức public được kế thừa bởi lớp con tồn tại như thế nào trong lớp con,
nó như là một phần của lớp. Đó là toàn bộ mục đính cho việc sử dụng kế thừa. Định
nghĩa của kế thừa đề cập tới việc một lóp nhập bản sao của một lớp khác. Bạn có thể
băn khoăn đó có nghĩa gì. Public và protected đều là quyền truy xuất, Chúng xác định,
thành phần bên ngoài lớp có thể được truy xuất như thế nào các phương thức hoặc
thuộc tính trong lớp.
4. Định nghĩa lại quyền truy xuất
Như chúng ta đã biết quyền truy xuất một thành phần của lớp con phụ thuộc
vào quyền truy xuất đó ở lớp cha và kiểu dẫn xuất. Trong nhiều trường hợp ta muốn
thay đổi quyền truy xuất của một thành phần lớp cha với lớp con. Xét ví dụ sau:
class A{
private: f1,f2;
protect: f3,f4;
public: f5,f6;
};
class B:A
{
public:
}
Kết quả f1f6: private
Ta muốn f6 có quyền truy xuất là public thì cần phải làm thế nào.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 79
Để xác định lại quyền truy xuất một thành phần của lớp cha trong lớp con thì
chỉ cần liệt kê thành phần đó sau từ khoá quyền truy xuất tương ứng.
<quyền truy xuất> :
<tên lớp cơ sở>::<tên thành phần>;
Ví dụ:
Class B:A
{
public:
A::f6;
}
Khi đó f6 là public
Ta lại xét trường hợp sau:
class A
{
int x;
protected:
int y,z;
public:
void f1();
void f1(int);
void f2();
};
class B:A
{
protected:
int t;
A::y;
public:
A::f1();
void g();
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 80
};
Ở đây ta có có một số hàm cùng tên vậy khi định nghĩa lại quyền truy xuất 1
thành phần thì các thành phần cùng tên khác có ảnh hưởng không? Câu trả lời là có, vì
khi định nghĩa lại quyền truy xuất của một thành phần thì tất các thành phần có cùng
tên đều bị tác động. Nhưng nếu các thành phần cùng tên mà khác quyền truy xuất thì
ta không thể định nghĩa lại quyền truy xuất. Chúng ta cung chỉ có thể định lại quyền
truy xuất xuất theo đúng quyền của thành phần đó trong lớp cơ sở. Trong trường hợp
lớp con có một thành phần trùng tên với lớp cha thì thành phần của lớp con se che phủ
lớp cha, tức là khi truy xuất thành phần này thì mặc định se là thành phần của lớp con.
Do đó muốn truy xuất thành phần của lớp cha thì phải truy xuất tường minh.
<tên đối tượng>.<tên lớp cơ sở>::<tên thành phần>
Ví dụ: xây dựng lớp TS gồm: sbd, họ tên, ngày sinh, khu vực, phương thức
nhập, in
Xây dựng lớp TSA kế thừa từ lớp TS: bổ sung thêm điểm toán, ly, hoá
Xây dựng lớp TSC kế thừa lớp TS: bổ sung điểm văn, sử địa
Hàm main:
- Nhập một sanh sách các TS
- In danh sách theo từng khối
- In danh sách trúng tuyển theo từng khối
class TS
{
int sbd, kv;
char hoten[30];
date ns;
public:
void nhap();
void in();
};
class TSA: public TS
{
float dt, dl, dh;
public:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 81
void nhap();
void in();
};
class TSC:public TS
{
float dvan,dsu,ddia;
public:
void nhap();
void in();
};
5. Hàm khởi tạo và hàm hủy của lớp cơ sở
a. Hàm khởi tạo
Các thành phần của lớp cơ sở đều được lớp dẫn xuất kế thừa, tuy nhiên hàm
khởi tạo của lớp cơ sở se không được kế thừa. Việc gọi hàm khởi tạo của lớp dẫn xuất
se kéo theo gọi hàm khởi tạo của lớp cơ sở, bởi có thế xem một đối tượng của lớp dẫn
xuất là một đối tượng của lớp cơ sở. Thứ tự thực hiện se là: hàm khởi tạo của lớp cơ sở
trước, sau đó tới hàm khởi tạo của lớp dẫn xuất. Ví dụ:
class A
{
public:
A(){cout<<”Ham khoi tao cua A\n”;}
};
class B: public A
{
public:
B(){ cout<<”Ham khoi tao cua B\n”;}
};
void main()
{ B b;
}
Khi đó kết quả thực hiện se là:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 82
Ham khoi tao cua A
Ham khoi tao cua B
Tuy nhiên, đó là trong trường hợp hàm khởi tạo mặc định. Nếu ta xây dựng lại
hàm khởi tạo của lớp dẫn xuất thì trong định nghĩa của nó ta phải gọi hàm khởi tạo của
lớp cơ sở một cách tường minh như sau:
Cú pháp:
<tên lớp dẫn xuất>([ds tham số]):<tên lớp cơ sở>([ds đối số])
{
//thân hàm khởi tạo
}
b hàm hủy
Không viết lệnh gọi hàm hủy của lớp cơ sở một cách tường minh mà hàm hủy
của lớp cơ sở được gọi tự động khi hàm hủy của lớp dẫn xuất thực hiện xong.
Trình tự thực hiện:
class B:A1, A2 { }
B b; A1() hủy ~B()
A2() ~A2()
B() ~A1()
Theo nguyên tăc: hàm khởi tạo của lớp cơ sở thực hiện trước rồi mới đến lớp
dẫn xuất
thứ tự của các hàm khởi tạo của lớp cơ sở theo thứ tự kế thừa
Thứ tự của hàm hủy ngược với thứ tự hàm khởi tạo.
Ví dụ:
Xây dựng lớp NGUOI, SV, GV theo sơ đồ kế thừa
Hàm main: nhập 1 danh sách SV,GIÁO VIÊN vào một mảng
NGƯỜI
SV GV
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 83
class NGUOI
{
char ht[30];
char noisinh[40];
unsigned namsinh;
public:
void nhap();
void in();
};
void NGUOI::nhap()
{
cout<<”Nhap ho ten”;
cin.getline(ht,30);
cout<<”Nhap noi sinh:”;
cin.getline(noisinh,40);
cout<<”Nhap nam sinh:”;
cin>>namsinh;
}
class SV:public NGUOI
{
char lop[10];
float dtb;
public:
void nhap();
void in()
{
NGUOI::in();
cout<<lop<<’\t’<<dtb<<endl;
}
};
void SV::nhap()
{
NGUOI::nhap();
cout<<”Nhap lop:”;
cin.getline(lop,10);
cout<<”Nhap diem trung binh:”;
cin>>dtb;
}
class GV:public NGUOI
{
char donvi[10];
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 84
float hsl;
public:
void nhap();
void in()
{
NGUOI::in();
cout<<donvi<<’\t’<<hsl<<endl;
}
};
void GV::nhap()
{
NGUOI::nhap();
cout<<”Nhap don vi:”;
cin.getline(donvi,10);
cout<<”Nhap he so luong:”;
cin>>hsl;
}
void main()
{
NGUOI *ds[10];
int n,i,c;
cout<<”Nhap so doi tuong trong danh sach\n”;
cin>>n;
cout<<”Thong tin ve sinh vien:\n”;
a.in();
}
6. Đa kế thừa
C++ hô trợ đa kế thừa một cách đầy đủ nhất so với các ngôn ngữ lập trình
hướng đối tượng cùng loại. Đa kế thừa (Multiple Inheritance) là khả năng cho phép
một lớp kế thừa từ nhiều hơn một lớp cơ sở. Bạn có thể dễ dàng dẫn xuất từ nhiều hơn
một lớp thông qua việc viết các lớp cơ sở trong danh sách ngăn cách nhau bởi dấu
phẩy.
class <lớp dẫn xuất>:public lớp cs1,public lớp cs2, public lớp cs3
{
};
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 85
Ví dụ:
Ví dụ sau mô tả đa kế thừa, nhập đoạn code sau và lưu vớ tên tax.h
class baseclassl
{
public:
float computetax(float);
} ;
float baseclass1::computetax(float amount)
{
return amount * .075f;
}
class baseclass2
{
public:
float computededuction(float);
};
float baseclass2::computededuction(float amount)
{
return amount - (amount *.10f);
}
class derivedclass:public baseclassl,public
baseclass2
{
} ;
Nhập đoạn code sau và lưu với tên tax-1.cpp
#include "tax.h"
#include <iostream>
int main()
{
derivedclass myclass;
float answer, amount;
cout <<"Enter the amount to tax \n";
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 86
cin>> amount;
answer = myclass.computetax(amount);
cout << "The tax is " << answer << endl;
cout << "Enter the amount to reduce \n";
cin >> amount;
answer = myclass.computededuction(amount);
cout << "The amount after deduction is "<< answer <<
endl;
return 0;
}
Như bạn thấy trong ví dụ trên, bạn có thể sử dụng đa thừa kế để nhận các hàm
mà bạn cần kế thừa từ nhiều hơn một lớp cơ sở. Mục đích của lập trình hướng đối
tượng là có thể sử dụng lại các đoạn mã. Điều này chỉ có thể thực hiện thông qua việc
sử dụng đơn kế thừa và đa kế thừa.
Đa kế thừa có thể là tính năng rất mạnh tuy nhiên đôi khi nó lại gây ra một số
vấn đề. Cho rằng bạn đang kế thừa từ một lớp cớ sở A và lớp cơ sở B, hai lớp này đều
có một hàm có tên là funca(). Khi lớp dẫn xuất gọi hàm funca(), thì hàm nào se được
gọi? Câu trả lời cho vấn đề này đơn giản là nạp chồng bất cứ hàm nào mà cả các lớp
cơ sở phổ biến. Về cơ bản bạn sử dụng kỹ thuật đa năng hóa hàm ở chương trước.
7. Lớp cơ sở ảo và nhập nhằng trong đa kế thừa
Xét trường hợp sau:
Giả sử trong lớp A có thành phần x, lớp B cung có thành phần x
Xây dựng lớp D kế thừa từ A và B
Theo nguyên ly kế thừa ta thấy trong lớp D se có hai thành phần x. Khi truy
xuất thành phần x trong D trình biên dịch se không biết thành phần x đó là x của lớp A
hay lớp B. Tức là có sự nhập nhằng trong kế thừa.
Để giải quyết sự nhập nhằng này ta có thể xác định phạm vi tường minh. Ví dụ
D d;
d.A::x
hoặc d.B::x
Trường hợp 2:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 87
Trong lớp A có thành phần x
Xây dựng lớp B,C kế thừa từ A và lớp D kế thừa từ B và C
Như ta thấy trong D se có hai thể hiện của x, và tất nhiên khi truy xuất x trong
D se dẫn tới sự nhập nhằng. Để khăc phục ta lại xác định tường minh việc truy xuất:
d.B::x
d.C::x
Một cách khác để giải quyết vấn đề nhập nhằng này là việc khai báo lớp cơ sở
là lớp cơ sở ảo như sau:
class B: virtual public A {….}
class C: virtual public A{…..}
class D: public B, public C {….}
Bằng cách này trong D chỉ có một sự thể hiện của A
Cú pháp
class <tên lớp>: virtual [<kiểu kế thừa>] <tên lớp cơ sở>
{
//
A
x
B
x
C
x
D
x
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 88
};
Ví dụ:
Giả sử có lớp động vật như sau:
class Animal{
virtual void eat();
};
//Lớp động vật có vú kế thừa từ lớp động vật
class Mammal{
public:
virtual Color getHairColor();
};
//Lớp động vật có cánh kế thừa lớp động vật
class WingedAnimal{
public:
virtual void Flap();
};
//Loài dơi là động vật có vú biết bay nên nó kế thừa cả 2 lớp động vật có vú và
động vật có cánh
class Bat: public Mammal, public: WingedAnimal {};
Vấn đề đặt ra là loài dơi ăn như thế nào tức là sử dụng phương thức eat() nào?
Nếu theo như khai báo ở trên thì việc gọi Bat.eat() là nhập nhằng không rõ ràng. Khi
đó có thể gọi Bat.WingedAnimal::eat(); hoặc Bat.Mammal::eat();
Sử dụng kế thừa ảo se giải quyết được vấn đề này. Chúng ta se khai báo lại lớp
như sau:
…
class Mammal : puclic virtual Animal{
public:
virtual Color GetHairColor();
};
class WingedAnimal : public virtual Animal{
public:
virtual void Flap();
};
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 89
//Loài dơi vẫn là động vật có vú
class Bat: public Mammal, public WingedAnimal
{
};
Bây giờ phần Animal trong Bat::WingedAnimal cung tương tự như trong
Bat::Mammal, có thể nói rằng loài dơi chỉ là một loại vật và ta có thể gọi phương thức
Bat::eat() không nhập nhằng.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 90
Chương 5. TÍNH ĐA HÌNH
Chương trước đã giới thiệu cho bạn về sự thừa kế, đó là quá trình mà một lớp
tiếp nhận bản sao các phương thức public và private của một lớp khác. Trong 2
chương trước, bạn đã thấy ba trong bốn nguyên ly cơ bản của ly thuyết hướng đối
tượng. Bạn đã thấy sự trừu tượng, đóng gói và thừa kế. Chương này se cho bạn thấy
điều nguyên ly cơ bản thứ tư được sử dụng như thế nào, đó là đa hình. Đa hình theo
nghĩa đen là “nhiều dạng-many forms”. Nó đơn giản là khi bạn đã kế thừa một hàm,
bạn có thể ghi đè lên hàm đó và thay đổi nó.
Trong OOP, khi một lớp dẫn xuất kế thừa một lớp cơ sở, đối tượng của lớp dẫn
xuất có thể được tham chiếu tới cả lớp cơ sở và lớp dẫn xuất. Nếu trong lớp dẫn xuất
có hàm chồng lên hàm của lớp cơ sở se nảy sinh vấn đề là đối tượng được dẫn xuất
tham chiếu như là đối tượng của lớp cơ sở. Khi một đối tượng dẫn xuất được tham
chiếu như là đối tượng của lớp cơ sở thì các hàm dẫn xuất rơi vào trình trạng nhập
nhằng.
Trước khi đi vào phương thức ảo và tính đa hình ta nhăc lại hai khái niệm kết
gán sớm (tĩnh) và kết gán muộn (động). Khi một đối tượng nhận thông báo để thực
hiện một phương thức, hệ thống se thực hiện các công việc sau:
Kiểm tra cú pháp của thông báo
Găn thông báo đó với 1 định nghĩa hàm cụ thể ( kết gán)
Việc kết gán này có thể thực hiện ở hai thời điểm:
Lúc biên dịch chương trình Kết gán sớm
Lúc chạy chương trình Kết gán muộn
Kết gán sớm làm cho chương trình chạy nhanh hơn, còn kết gán muộn se làm
chương trình chạy chậm do lúc đó chương trình mới biết kiểu của đối tượng.
Để kết gán muộn C++ cho phép dùng phương thức ảo thực hiện
1. Phương thức ảo
Khai báo:
virtual <kiểu giá trị trả về> <tên phương thức>([danh sách tham số])
Ví dụ:
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 91
class NGUOI
{
char ht[30];
char noisinh[40];
unsigned namsinh;
public:
virtual void nhap();
virtual void in();
};
class GV: public NGUOI
{
float hsl;
char donvi[20]
public:
void nhap();
void in();
void f();
};
void main()
{
NGUOI *p;
p = new NGUOI;
pnhap();
pin();
p=new GV;
pnhap();
pin();
pf();// sai vì kiểm tra không thấy p là con trỏ lớp
NGUOI
}
Cơ chế kết gán muộn:
Khi một lớp có phương thức ảo hoặc kế thừa từ lớp có phương thức ảo thì
chương trình dịch se phát sinh thêm một con trỏ gọi là con trỏ ảo(virtual pointer). Con
trỏ này trỏ tới bảng ảo (virtual table), trong bảng ảo ghi địa chỉ của các phương thức ảo
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 92
Khi kết gán chương trình se kiểm tra xem bảng ảo của lớp GV có phương thức
nhập và in chưa nếu đã có thì se ghi đè lên NGUOI::nhap và NGUOI:in thành
GV::nhap và Gv::in
Ví dụ:
Xây dựng lớp GV, SV kế thừa từ lớp NGUOI, in danh sách GV, SV
NGUOI
{
- ht,namsinh
- nhap, in
- loai(){returrn 0};
}
SV:NGUOI
{
-lop,dtb
-nhap, in
-loai(){ return 1;}
}
GV:NGUOI
{
NGUOI
ht
noisinh
nguoi::in
nguoi::nhap
GV
VP
ht
noisinh
hsl
donvi
VP
GV::in
nguoi::nhap
GV::nhap
NGUOI::in
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 93
-hsl, dv
-nhap,in
-loai(){return 2;}
}
class NGUOI
{
char ht[30];
unsigned namsinh;
public:
virtual void nhap();
virtual void in()
{
cout<<”\n”<<ht<<’\t’<<namsinh<<’\t’;
}
virtual void loai()
{
return 0;
}
};
void NGUOI::nhap()
{
cout<<”Ho va ten:”; gets(ht);
cout<<"\nNam sinh :" ; cin>>namsinh ;
}
class GV:public NGUOI
{
float hsl;
char donvi[20];
public:
void nhap();
void in();
int loai()
{
return 1;
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 94
};
void GV::nhap()
{
cout<<”\nHe so luong:”; cin>>hsl;
cout<<”\nDon vi:”;gets(donvi);
}
void GV ::in::()
{
NGUOI ::in() ;
cout<<hsl<<’\t’<<donvi<<’\t’ ;
}
class SV : public NGUOI
{
char lop[10] ;
float dtb;
public :
void nhap() ;
void in() ;
int loai()
{
return 2;
}
};
void SV::nhap()
{
NGUOI::nhap();
cout<<”Lop:”; gets(lop);
cout<<”\nDiem TB:”; cin>>dtb;
}
void SV ::in()
{
NGUOI ::in() ;
cout<<lop<<’\t’<<dtb ;
}
void main()
{
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 95
NGUOI *ds[20] ;
int n,c,i;
do
{
cout<<”\n1. Nhap danh sach”;
cout<<”\n2. In danh sach sinh vien”;
cout<<”\n3. In danh sach giao vien”;
cout<<”\n0. Ket thuc!”;
c=getchar();
switch(c)
{
case ‘1’: cout<<”So doi tuong trong danh sach?”;
cin>>n;
for(i=0;i<n;i++)
{
cout<<”\n1. Nhap sinh vien”;
cout<<”\n2. Nhap giao vien”;
int k ;
k=getchar() ;
if(k==’1’) ds[i]=new SV ;
else ds[i]=new GV;
ds[i]nhap();
}
break;
case ‘2’: cout<<”\n Danh sach sinh vien:”;
for(i=0;i<n;i++)
if(ds[i]loai()==1) ds[i]in();
getchar();
break;
case ‘3’: cout<<”\n Danh sach giao vien:”;
for(i=0;i<n;i++)
if(ds[i]loai()==2) ds[i]in();
getchar();
break;
}
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 96
while(c!=’0’);
}
Nhận xét: Ở class NGUOI nếu như hai phương thức nhap() và in() không khai
báo virtual thì khi gọi phương thức nhập và in của GV và SV đều se gọi phương thức
nhập và in của lớp cơ sở là lớp NGUOI. Như vậy là không theo y muốn của chúng ta,
để gọi đúng phương thức GV::nhap() hay SV::nhap() thì ở lớp NGUOI ta phải khai
báo virtual ở hai phương thức nhập và in.
Chú ý:
Cơ chế kết gán phương thức ảo chỉ có thể thực hiện được qua phép gán con trỏ
hoặc tham chiếu.
Ví dụ:
NGUOI a,*p;
GV b;
a=b;
a.nhap(); //kết gán sớm
p=&b;
pnhap(); //kết gán muộn
Ví dụ 2:
NGUOI &q=b;
qnhap(); //kết gán muộn
void f(NGUOI x)
{
x.nhap(); //kết gán sớm
}
void f(NGUOI &x)
{
x.nhap(); //kết gán muộn
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 97
2. Phương thức ảo thuần túy và lớp trừu tượng
Trong quá trình kế thừa se phát sinh ra các lớp con, các lớp con này lại có các
lớp con riêng. Chúng ta phải đoán trước xem các lớp con này có những thành phần nào
để xây dựng lớp cơ sở trên cùng. Tuy nhiên nếu xây dựng toàn bộ các thành phần cho
các đối tượng thì se dẫn tới tình trạng lãng phí bộ nhớ. Để giải quyết vấn đề này C++
cung cấp cho chúng ta khái niệm phương thức ảo thuần túy.
Đối với lớp cơ sở cần cung cấp một phương thức thống nhất làm giao diện cho
các lớp con của nó mà các phương thức này không được định nghĩa trong lớp hoặc
không làm gì thì phương thức đó được gọi là phương thức ảo thuần túy.
Cú pháp:
virtual <kiểu giá trị trả về> <tên phương thức>([danh sách tham số])=0;
Một số đặc điểm của phương thức ảo thuần túy:
- Phương thức ảo thuần túy không băt buộc định nghĩa trong lớp cơ sở
- Phương thức ảo thuần túy không thể phát sinh các đối tượng tức là không thể khai
báo đối tượng thuộc lớp có phương thức ảo thuần túy
- Lớp có phương thức ảo thuần túy chỉ làm lớp cơ sở cho các lớp khác và được gọi là
lớp cơ sở trừu tượng.
- Trong lớp dẫn xuất kế thừa lớp cơ sở trừu tượng nếu không định nghĩa lại phương
thức ảo thuần túy thì lớp dẫn xuất đó cung trở thành lớp cơ sở trừu tượng.
Ví dụ:
Xây dựng lớp hình, hình vuông, hình tam giác kế thừa từ lớp hình với các thành
phần cần thiết để thực hiện yêu cầu:
- Nhập danh sách các hình
- Săp xếp các hình theo thứ tự giảm của diện tích
- In các hình
class hinh
{
public:
virtual void nhap()=0;
virtual void in()=0;
virtual float dt()=0;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 98
int operator>(hinh x)
{
return dt()>x.dt();
}
};
class HV:public hinh
{
float a;
public:
void nhap();
void in()
{
cout<<”Kich thuoc:”<<a;
cout<<”Dien tich:”<<đối tượng();
}
float dt()
{
return a*a;
}
};
class HTG: public hinh
{
float a,b,c
public:
void nhap();
void in():
int ktra()
{
if((a+b>c)&&(b+c>a)&&(c+a>b)&&(a>0)&&(b>0)&&(c>0
))
return 1;
else return 0;
}
float dt()
{
float s,p;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 99
p=(a+b+c)/2
s=sqrt(p*(p-a)*(p-b)*(p-c));
return s;
}
};
void HV::nhap()
{
cout<<”Nhap do dai canh :”;cin>>a;
}
void HTG::in()
{
cout<<”Cac canh tam giac:”<<a<<”,”<<b<<”,”<<c;
cout<<”Dien tich:”<<dt;
}
void HTG::nhap()
{
do
{
cout<<”Nhap cac canh:”;
cin>>a>>b>>c;
}while ktra()=0;
}
void main()
{
hinh *d[10];
int n,i,c;
cout<<”Nhap so hinh:”;cin>>n;
for(i=0;i<n;i++)
{
cout<<”Hinh thu “<<i<<”(1. HV, 2. TG)”;
cin>>c;
if(c==1) d[i]=new HV;
else d[i]= new HTG;
d[i]nhap();
}
int k,j
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 100
for(i=0;i<n-1;i++)
for(k=i,j=i+1;j<n;j++)
if d[j]>d[k] k=j;
if(k!=i)
{
hinh *tmp;
tmp=d[i];
d[i]=d[k];
d[k]=tmp;
}
cout<<”Danh sach cac hinh sau khi sap:”;
for(i=0;i<n;i++)
{
d[i]in();
cout<<endl;
}
}
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 101
Chương 6. KHUÔN HÌNH MẪU
1. Template
a. Khuôn hình hàm
Trước khi đi vào khái niệm khuôn hình hàm ta xét một số ví dụ sau:
Xây dựng hàm để tìm giá trị nhỏ nhất của 2, 3, hay 4 số thực.
Bài toán này se được giải quyết đơn giản bằng cách nạp chồng hàm
float min(float, float);
float min(float, float, float);
float min(float,float,float,float);
Ta lại xét trường hợp sau:
Xây dựng hàm để tìm giá trị lớn nhất của 2 số bất kỳ. Nếu nạp chồng hàm ta se
có hai hàm như sau:
int max(int a, int b)
{
if(a>b) return a;
else return b;
}
float max(float a, float b)
{
if(a>b) return a;
else return b;
}
Ta thấy rằng hàm max thứ nhất se trả về kiểu int cùng kiểu với kiểu của tham
số, hàm max thứ hai cung tương tự. Vậy tại sao chúng ta không xây dựng một mẫu
hàm sao cho đối số truyền vào là kiểu gì thì hàm trả về kiểu đó. Trong thực tế ta gặp
rất nhiều trường hợp như vậy.
Khái niệm
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 102
Khuôn hình hàm là một mẫu của hàm có các tham số là kiểu của các đối số; với
môi giá trị hợp lệ của tham số đó (tham số kiểu) se phát sinh cho chúng ta một hàm cụ
thể gọi là hàm thể hiện.
Một khuôn hình hàm là đặc trưng cho một lớp các hàm có cùng tham số và
cùng thuật giải
Khai báo
template <class T1, class T2....> <kiểu giá trị trả về> <tên khuôn hình
hàm>([ds tham số])
{
//thân khuôn hình hàm
}
ví dụ:
template <class T> T max(T a, T b)
{
if(a>b) return a ;
else return b;
}
Ví dụ 2:
Xây dựng khuôn hình tổng của 3 số bất kỳ
template <class T1, class T2, class T3> T1 tong(T1 x,
T2 y, T3 z)
{
return x+y+z ;
}
Gọi hàm từ khuôn hình hàm
Cú pháp
<tên hàm>(đối số)
Tên hàm trùng tên khuôn hình hàm
Ví dụ:
Với khuôn hình hàm max
int a,b ;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 103
float x,y ;
max(a,b) ;
Khi gọi hàm max(a,b) như trên thì chương trình dịch se xác định :
- Kiểu của a,b là int nên kiểu của T se là int
- Phát sinh một hàm cụ thể từ khuôn hình hàm max với T là int
Hàm max lúc này se như sau
int max(int a, int b)
{
if(a>b) return a ;
else return b;
}
Tương tự với max(x,y)
Nhận xét: khi gặp một lời gọi hàm, chương trình dịch căn cứ vào kiểu của đối
số để xác định giá trị của tham số kiểu và se phát sinh một hàm thể hiện tương ứng với
giá trị của tham số kiểu đó. Sau đó biên dịch và chạy hàm
Chú ý:
- Mọi tên tham số kiểu phải xuất hiện trong khai báo tham số ít nhất một lần
- Tham số kiểu được dùng trong khuôn hình như tên 1 kiểu thông thường
Ví dụ: max(x, a) se báo lôi
- Giá trị của tham số kiểu phải so sánh chính xác (không ép kiểu)
Cụ thể hóa một khuôn hình hàm
Ta xét trường hợp sau:
Có một khuôn hình hàm f có thể sử dụng với hầu hết các kiểu dữ liệu trừ :
- có kiểu T không đáp ứng các thao tác bên trong của f
- có kiểu U có các phép tóan đáp ứng được các phép tóan bên trong của f nhưng lại
không phù hợp với yêu cầu đặt ra
Để giải quyết, ta làm như sau :
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 104
Th1 : bổ sung vào T các thao tác cần thiết ( nếu T là kiểu của người lập trình tạo
ra lớp)
Th2 : định nghĩa một hàm cụ thể đối với kiểu U ( cụ thể hoad 1 khuôn hình
hàm)
Ví dụ :
char *max(char *s1, char *s2)
{
if(strcmp(s1,s2)>0)
return s1;
else return s2;
}
float a,b;
char *s1, *s2;
max(a,b) sử dụng khuôn hình hàm rồi phát sinh hàm tương ứng với T là float
max(s1,s2) sử dụng hàm cụ thể
Qui tăc chọn hàm
Khi gặp một lời gọi hàm, chương trình dịch chọn hàm theo các bước sau:
B1: Chọn trong các hàm cụ thể một hàm phù hợp (không ép kiểu)
- Nếu có đúng 1 hàm phù hợp thì thể hiện hàm đó
- Nếu có nhiều hơn 1 hàm phù hợp thì báo lôi nhập nhằng
- Nếu không có hàm phù hợp thì chuyển sang bước 2
B2: Chọn trong khuôn hình hàm
- Nếu có đúng 1 khuôn hình hàm phù hợp thì phát sinh hàm thể hiện và thể hiện hàm
đó
- Nếu có hơn 1 khuôn hình phù hợp thì báo lôi
- Nếu không có khuôn hình phù hợp se sang bước 3
B3: Chọn lại trong các hàm cụ thể 1 hàm phù hợp (có ép kiểu)
- Nếu có đúng 1 hàm phù hợp thì thực hiện
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 105
- Ngược lại báo lôi
Ví dụ
Viết khuôn hình hàm min để tìm giá trị nhỏ nhất trong 2 số, 3 số, 1 mảng có n
phần tử
template <class T> T min(T a, T b)
{
if(a>b) return b ;
else return a;
}
template <class T> T min(T a, T b, T c)
{
return min(min(a,b),c) ;
}
template<class T> T min (T *d, int n)
{
T x ;
x=d[0] ;
for(i=0 ;i<n ;i++)
if(x>d[i]) x=d[i] ;
return x ;
}
b. Khuôn hình lớp
Khái niệm
Khuôn hình lớp là một mẫu của lớp có các tham số là các kiểu dữ liệu (tham số
kiểu) và với môi giá trị của tham số kiểu se phát sinh ra một thể hiện là một lớp cụ thể
(lớp khuôn hình)
Khai báo khuôn hình lớp
Cú pháp :
template <class T1, class T2...> class <tên khuôn hình lớp>
{
<khai báo các thành phần>
};
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 106
Định nghĩa các phương thức
- Có thể khai báo trong lớp
- Khai báo ngoài lớp
Ví dụ:
Xây dựng khuôn hình lớp vectơ có kiểu chưa xác định
template <class T> class VT{
int spt;
T *d;
public:
void nhap() ;
void in() ;
VT operator + (VT&) ;
T operator *(VT &);
};
Khai báo phương thức:
template <class T> void VT<T>::nhap(){
cout<<"So phan tu :" ; cin>>spt ;
d= new T[spt] ;
for(int i=0 ; i<spt ;i++)
{
cout<<”Phan tu thu “<<i<<”:”;
cin>>d[i];
}
}
Cú pháp:
template <class T1, class T2 …> <kiểu trả về> <tên lớp><T1, T2>::<tên
tphần>([tham số])
{
<thân phương thức>
}
Phát sinh các lớp và các đối tượng
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 107
Khi phát sinh lớp từ khuôn hình lớp ta phải truyền các giá trị cụ thể của tham số
kiểu
Ví dụ với khuôn hình lớp VT
VT <int>: lớp vec tơ với các phần tử kiểu int: T= int
VT <float>: lớp vec tơ với các phần tử kiểu float: T= float
VT <PS>: lớp vec tơ với các phần tử kiểu PS: T= PS
VT <SP>: lớp vec tơ với các phần tử kiểu SP: T=SP
Khai báo đối tượng
VT<int> a,b;
VT<float> x;
Cú pháp
<tên khuôn hình lớp><kiểu 1, kiểu 2…>
Các thành phần đặc biệt
- Phương thức bạn:
Giả sử có khuôn hình lớp A, nếu trong A có hàm bạn f thì f là bạn của mọi lớp
phát sinh từ A
- Thành phần tĩnh:
Giả sử có khuôn hình lớp A, nếu trong A có thành phần tĩnh k thì môi lớp phát
sinh từ A đều có một thành phần tĩnh k
Ví dụ:
xây dựng khuôn hình lớp MT2 có các thành phần :
- dữ liệu: số dòng, số cột, mảng các phần tử có kiểu xác định
- Phương thức: hàm tạo, hàm hủy, phép gán, +, *, nhập, in
- Hàm bạn:
Hàm bạn của một khuôn hình lớp là bạn của mọi lớp thể hiện
-Lớp bạn:
Lớp bạn của một khuôn hình lớp là lớp bạn của mọi lớp thể hiện
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 108
-Khuôn hình hàm: là một bạn của khuôn hình lớp thì mọi hàm thể hiện của
khuôn hình hàm là bạn của mọi lớp thể hiện của khuôn hình lớp
Tham số giá trị của khuôn hình lớp
Xét ví dụ:
template <class T> class VT
{
int spt;
T v[10];
VT(int n);
};
Lớp thể hiện VT<int>, VT<float>
x(10), y(3)
Ta thấy rằng cần có một tham số là số phần tử của VT, tham số đó được gọi là
giá trị của khuôn hình lớp.
Tham số giá trị là tham số của khuôn hình lớp, ứng với môi giá trị của tham số
cho ta một lớp.
Ví dụ :
template <class T, int n> class VT
{
T v[n];
public:
void nhap();
void in();
};
Để phát sinh một lớp từ khuôn hình lớp cần:
- Cấp giá trị của tham số kiểu (tên một kiểu)
- Cấp giá trị của tham số giá trị (giá trị cụ thể)
Ví dụ :
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 109
Xây dựng khuôn hình lớp mảng 2 chiều với số dòng, số cột và kiểu các phần tử
là các tham số
template <class T, int sd, int sc> class MT2
{
T d[sd][sc];
public:
void nhap() ;
void in() ;
MT2 operator+(MT2) ;
} ;
template <class T, int sd, int sc>
void MT2<T, sd,sc>::nhap()
{
int I,j;
for(i=0;i<sd;i++)
for(j=;j<sc;j++)
{
cout<<” Pha tu hang “<<i<<” cot “<<j;
cin>>d[i][j];
}
}
template <class T, int sd, int sc>
void MT2<T, sd,sc>::in()
{
for(int i=0;i<sd;i++)
for(int j=0;j<sc;j++)
cout<<d[i][j];
}
template <class T, int sd, int sc) MT2<T,sd,sc>
MT2<t,sd,sc>::operator+(MT2 a)
{
MT2<T,sd,sc> kq;
for(int i=0;i<sd;i++)
for(int j=0;j<sc;j++)
kq.d[i][j]=d[i][j]+a.d[i][j];
return kq;
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 110
}
void main()
{
MT2<int,2,2> a,b,c;
cout<<”\nNhap a:”;a.nhap();
cout<<”\nNhap b:”;b.nhap();
c=a+b;
cout<<”\n a+b=:”;c.in();
}
Cụ thể hóa một lớp thể hiện
1. Xử ly lôi phát sinh
Phụ lục: Các dòng nhập/xuất
Phụ lục: Thiết kế hướng đối tượng
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 111
MỞ ĐẦU .............................................................................................................. 5
Chương 1. GIỚI THIỆU VỀ LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG ................. 6
1. Tổng quan về các kỹ thuật lập trình ............................................................. 6
1.1. Lập trình tuyến tính ............................................................................... 6
1.2. Lập trình cấu trúc .................................................................................. 6
1.3. Lập trình mô đun hoá ............................................................................ 7
1.4. Nhược điểm của lập trình hướng thủ tục ............................................... 8
1.5. Lập trình hướng đối tượng .................................................................... 9
2. Một số khái niệm cơ bản ............................................................................ 11
2.1. Hệ thống hướng đối tượng .................................................................. 11
2.2. Đối tượng (Objects) ............................................................................. 12
2.3. Thuộc tính (Attribute) và Phương thức (method) ............................... 12
2.4. Lớp (Class) và lớp con (SubClass) ...................................................... 12
2.5. Lớp trừu tượng .................................................................................... 13
2.6. Truyền thông điệp ............................................................................... 13
2.7. Sự trừu tượng hoá (abstraction) ........................................................... 14
2.8. Sự đóng gói (encapsulation) ................................................................ 14
2.9. Tính kế thừa ......................................................................................... 14
1.10. Tính đa hình ....................................................................................... 15
3. Các bước cần thiết để thiết kế chương trình theo hướng đối tượng ........... 16
4. Các ưu điểm của lập trình hướng đối tượng ............................................... 16
5. Một số ngôn ngữ lập trình hướng đối tượng .............................................. 16
6. Một số ứng dụng của lập trình hướng đối tượng ........................................ 18
Chương 2. GIỚI THIỆU VỀ C++ .................................................................... 19
1. Lịch sử của C++ ......................................................................................... 19
2. Các mở rộng của C++................................................................................. 19
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 112
2.1. Lời chú thích ........................................................................................ 19
2.2. Từ khoá mới ........................................................................................ 20
2.3. Kiểu dữ liệu char và int ....................................................................... 20
2.4. Khai báo biến ....................................................................................... 20
2.5. Chuyển đổi và ép kiểu ......................................................................... 21
2.6. Vào ra trong C++ ................................................................................. 22
2.7. Cấp phát và giải phóng bộ nhớ ............................................................ 26
2.8. Biến tham chiếu ................................................................................... 28
2.9. Hằng tham chiếu .................................................................................. 29
2.10. Truyền tham số cho hàm theo tham chiếu ......................................... 30
2.11. Hàm với tham số có giá trị mặc định ................................................ 34
2.12. Các hàm nội tuyến ............................................................................. 35
2.13. Hàm tải bội ........................................................................................ 36
Chương 3. LỚP VÀ ĐỐI TƯỢNG .................................................................. 39
1. Xây dựng lớp và đối tượng ......................................................................... 39
1.1. Khai báo lớp ........................................................................................ 39
1.2. Khai báo đối tượng .............................................................................. 42
1.3. Truy xuất các thành phần của đối tượng ............................................. 43
2. Các phương thức ........................................................................................ 46
2.1. Hàm khởi tạo - Constructor ................................................................. 46
2.2. Hàm hủy – Destructor ......................................................................... 49
3. Đa năng hóa tóan tử .................................................................................... 51
4. Mảng và con trỏ của lớp ............................................................................. 57
5. Các hàm bạn và lớp bạn ............................................................................. 59
5.1. Hàm bạn (friend function) ................................................................... 59
5.2. Lớp bạn ................................................................................................ 61
6. Thành phần tĩnh .......................................................................................... 63
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
Trang 113
6.1. Các thành phần dữ liệu tĩnh ................................................................. 63
6.2. Các hàm thành viên tĩnh ...................................................................... 64
7. Thành phần hằng ........................................................................................ 67
7.1. Dữ liệu hằng ........................................................................................ 67
7.2. hàm thành phần (phương thức hằng) ................................................... 69
8. Thành phần là đối tượng ............................................................................. 69
Chương 4. TÍNH KẾ THỪA ........................................................................... 72
1. Khái niệm ................................................................................................... 72
2. Khai báo lớp dẫn xuất ................................................................................. 73
3. Các kiểu kế thừa ......................................................................................... 75
4. Định nghĩa lại quyền truy xuất ................................................................... 78
5. Hàm khởi tạo và hàm hủy của lớp cơ sở .................................................... 81
6. Đa kế thừa ................................................................................................... 84
7. Lớp cơ sở ảo và nhập nhằng trong đa kế thừa ............................................ 86
Chương 5. TÍNH ĐA HÌNH ............................................................................ 90
1. Phương thức ảo ........................................................................................... 90
2. Phương thức ảo thuần túy và lớp trừu tượng .............................................. 97
Chương 6. KHUÔN HÌNH MẪU .................................................................. 101
1. Template ................................................................................................... 101
a. Khuôn hình hàm ................................................................................... 101
b. Khuôn hình lớp ..................................................................................... 105