Biến cục bộ còn được gọi là biến động, từ khoá auto được sử dụng để khai báo
chúng. Chúng chỉ được tham chiếu đến bởi các lệnh bên trong của khối lệnh mà biến
được khai báo. Để rõ hơn, một biến cục bộ được tạo ra trong lúc vào một khối và bị
huỷ trong lúc đi ra khỏi khối đó. Khối lệnh thông thường nhất mà trong đó một biến
cục bộ được khai báo chính là hàm.
358 trang |
Chia sẻ: huyhoang44 | Lượt xem: 770 | Lượt tải: 0
Bạn đang xem trước 20 trang tài liệu Bài giảng kĩ thuật lập trình, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
dựng
phần mềm. Trong kiểu mới này, các đối tượng (object) và các lớp (class) là những
khối xây dựng trong khi các phương thức (method), thông điệp (message), và sự thừa
kế (inheritance) cung cấp các cơ chế chủ yếu.
Lập trình hướng đối tượng (OOP- Object-Oriented Programming) là một cách tư duy
mới, tiếp cận hướng đối tượng để giải quyết vấn đề bằng máy tính. Thuật ngữ OOP
ngày càng trở nên thông dụng trong lĩnh vực công nghệ thông tin.
Khái niệm 18.1
Lập trình hướng đối tượng (OOP) là một phương pháp thiết kế và phát triển phần
mềm dựa trên kiến trúc lớp và đối tượng.
Nếu bạn chưa bao giờ sử dụng một ngôn ngữ OOP thì trước tiên bạn nên nắm
vững các khái niệm của OOP hơn là viết các chương trình. Bạn cần hiểu được đối
tượng (object) là gì, lớp (class) là gì, chúng có quan hệ với nhau như thế nào, và làm
thế nào để các đối tượng trao đổi thông điệp (message) với nhau.
OOP là tập hợp các kỹ thuật quan trọng mà có thể dùng để làm cho việc triển khai
chương trình hiệu quả hơn. Quá trình tiến hóa của OOP như sau:
Lập trình tuyến tính
Lập trình có cấu trúc
Sự trừu tượng hóa dữ liệu
Lập trình hướng đối tượng
322
20.2. Trừu tượng hóa (Abstraction)
Trừu tượng hóa là một kỹ thuật chỉ trình bày những các đặc điểm cần thiết của
vấn đề mà không trình bày những chi tiết cụ thể hay những lời giải thích phức tạp của
vấn đề đó. Hay nói khác hơn nó là một kỹ thuật tập trung vào thứ cần thiết và phớt lờ
đi những thứ không cần thiết.
Ví dụ những thông tin sau đây là các đặc tính gắn kết với con người:
Tên
Tuổi
Địa chỉ
Chiều cao
Màu tóc
Giả sử ta cần phát triển ứng dụng khách hàng mua sắm hàng hóa thì những chi
tiết thiết yếu là tên, địa chỉ còn những chi tiết khác (tuổi, chiều cao, màu tóc, ..) là
không quan trọng đối với ứng dụng. Tuy nhiên, nếu chúng ta phát triển một ứng dụng
hỗ trợ cho việc điều tra tội phạm thì những thông tin như chiều cao và màu tóc là thiết
yếu.
Sự trừu tượng hóa đã không ngừng phát triển trong các ngôn ngữ lập trình,
nhưng chỉ ở mức dữ liệu và thủ tục. Trong OOP, việc này được nâng lên ở mức cao
hơn – mức đối tượng. Sự trừu tượng hóa được phân thành sự trừu tượng hóa dữ liệu và
trừu tượng hóa chương trình.
Khái niệm 18.2
Trừu tượng hóa dữ liệu (data abstraction) là tiến trình xác định và nhóm các
thuộc tính và các hành động liên quan đến một thực thể đặc thù trong ứng dụng đang
phát triển.
Trừu tượng hóa chương trình (program abstraction) là một sự trừu tượng hóa dữ
liệu mà làm cho các dịch vụ thay đổi theo dữ liệu.
20.3. Đối tượng (object)
Các đối tượng là chìa khóa để hiểu được kỹ thuật hướng đối tượng. Bạn có thể
nhìn xung quanh và thấy được nhiều đối tượng trong thế giới thực như: con chó, cái
bàn, quyển vở, cây viết, tivi, xe hơi ...Trong một hệ thống hướng đối tượng, mọi thứ
đều là đối tượng. Một bảng tính, một ô trong bảng tính, một biểu đồ, một bảng báo
323
cáo, một con số hay một số điện thoại, một tập tin, một thư mục, một máy in, một câu
hoặc một từ, thậm chí một ký tự, tất cả chúng là những ví dụ của một đối tượng. Rõ
ràng chúng ta viết một chương trình hướng đối tượng cũng có nghĩa là chúng ta đang
xây dựng một mô hìnhcủa một vài bộ phận trong thế giới thực. Tuy nhiên các đối
tượng này có thể được biểu diễn hay mô hình trên máy tính.
Một đối tượng thế giới thực là một thực thể cụ thể mà thông thường bạn có thể
sờ, nhìn thấy hay cảm nhận được. Tất cả các đối tượng trong thế giới thực đều có trạng
thái (state) và hành động (behaviour). Ví dụ:
Các đối tượng phần mềm (software object) có thể được dùng để biểu diễn các đối
tượng thế giới thực. Chúng được mô hình sau khi các đối tượng thế giới thực có cả
trạng thái và hành động. Giống như các đối tượng thế giới thực, các đối tượng phần
mềm cũng có thể có trạng thái và hành động. Một đối tượng phần mềm có biến
(variable) hay trạng thái (state) mà thường được gọi là thuộc tính (attribute; property)
để duy trì trạng thái của nó và phương thức (method) để thực hiện các hành động của
nó. Thuộc tính là một hạng mục dữ liệu được đặt tên bởi một định danh (identifier)
trong khi phương thức là một chức năng được kết hợp với đối tượng chứa nó.
OOP thường sử dụng hai thuật ngữ mà sau này Java cũng sử dụng là thuộc tính
(attribute) và phương thức (method) để đặc tả tương ứng cho trạng thái (state) hay biến
(variable) và hành động (behavior). Tuy nhiên C++ lại sử dụng hai thuật ngữ dữ liệu
thành viên (member data) và hàm thành viên (member function) thay cho các thuật
ngữ này.
Xét một cách đặc biệt, chỉ một đối tượng riêng rẽ thì chính nó không hữu dụng.
Một chương trình hướng đối tượng thường gồm có hai hay nhiều hơn các đối tượng
324
phần mềm tương tác lẫn nhau như là sự tương tác của các đối tượng trong trong thế
giới thực.
Khái niệm 20.3
Đối tượng (object) là một thực thể phần mềm bao bọc các thuộc tính và các
phương thức liên quan.
Kể từ đây, trong giáo trình này chúng ta sử dụng thuật ngữ đối tượng (object) để
chỉ một đối tượng phần mềm. Hình 20.1 là một minh họa của một đối tượng phần
mềm:
Hình 20.1. Một đối tượng phần mềm
Mọi thứ mà đối tượng phần mềm biết (trạng thái) và có thể làm (hành động)
được thể hiện qua các thuộc tính và các phương thức. Một đối tượng phần mềm mô
phỏng cho chiếc xe đạp sẽ có các thuộc tính để xác định các trạng thái của chiếc xe
đạp như: tốc độ của nó là 10 km trên giờ, nhịp bàn đạp là 90 vòng trên phút, và bánh
răng hiện tại là bánh răng thứ 5. Các thuộc tính này thông thường được xem như thuộc
tính thể hiện (instance attribute) bởi vì chúng chứa đựng các trạng thái cho một đối
tượng xe đạp cụ thể. Trong kỹ thuật hướng đối tượng thì một đối tượng cụ thể được
gọi là một thể hiện (instance).
Khái niệm 20.4
Một đối tượng cụ thể được gọi là một thể hiện (instance).
Hình 20.2 minh họa một xe đạp được mô hình như một đối tượng phần mềm:
325
Hình 20.2. Một đối tượng phần mềm xe đạp
Đối tượng xe đạp phần mềm cũng có các phương thức để thắng lại, tăng nhịp đạp
hay là chuyển đổi bánh răng. Nó không có phương thức để thay đổi tốc độ vì tốc độ
của xe đạp có thể tình ra từ hai yếu tố số vòng quay và bánh răng hiện tại. Những
phương thức này thông thường được biết như là các phương thước thể hiện (instance
method) bởi vì chúng tác động hay thay đổi trạng thái của một đối tượng cụ thể.
20.4. Lớp (Class)
Trong thế giới thực thông thường có nhiều loại đối tượng cùng loại. Chẳng hạn
chiếc xe đạp của bạn chỉ là một trong hàng tỉ chiếc xe đạp trên thế giới. Tương tự,
trong một chương trình hướng đối tượng có thể có nhiều đối tượng cùng loại và chia sẻ
những đặc điểm chung. Sử dụng thuật ngữ hướng đối tượng, chúng ta có thể nói rằng
chiếc xe đạp của bạn là một thể hiện của lớp xe đạp. Các xe đạp có một vài trạng thái
chung (bánh răng hiện tại, số vòng quay hiện tại, hai bánh xe) và các hành động
(chuyển bánh răng, giảm tốc). Tuy nhiên, trạng thái của mỗi xe đạp là độc lập và có
thể khác với các trạng thái của các xe đạp khác. Trước khi tạo ra các xe đạp, các nhà
sản xuất thường thiết lập một bảng thiết kế (blueprint) mô tả các đặc điểm và các yếu
tố cơ bản của xe đạp. Sau đó hàng loạt xe đạp sẽ được tạo ra từ bản thiết kế này.
Không hiệu quả nếu như tạo ra một bản thiết kế mới cho mỗi xe đạp được sản xuất.
Trong phần mềm hướng đối tượng cũng có thể có nhiều đối tượng cùng loại chia
sẻ những đặc điểm chung như là: các hình chữ nhật, các mẫu tin nhân viên, các đoạn
phim, Giống như là các nhà sản xuất xe đạp, bạn có thể tạo ra một bảng thiết kế cho
các đối tượng này. Một bảng thiết kế phần mềm cho các đối tượng được gọi là lớp
(class).
Khái niệm 20.5
326
Lớp (class) là một thiết kế (blueprint) hay một mẫu ban đầu (prototype) định nghĩa
các thuộc tính và các phương thức chung cho tất cả các đối tượng của cùng một
loại nào đó.
Một đối tượng là một thể hiện cụ thể của một lớp.
Khái niệm 20.6
Thuộc tính lớp (class attribute) là một hạng mục dữ liệu liên kết với một lớp cụ
thể mà không liên kết với các thể hiện của lớp. Nó được định nghĩa bên trong định
nghĩa lớp và được chia sẻ bởi tất cả các thể hiện của lớp.
Phương thức lớp (class method) là một phương thức được triệu gọi mà không
tham khảo tới bất kỳ một đối tượng nào. Tất cả các phương thức lớp ảnh hưởng đến
toàn bộ lớp chứ không ảnh hưởng đến một lớp riêng rẽ nào.
20.5. Thuộc tính (Attribute)
Các thuộc tính trình bày trạng thái của đối tượng. Các thuộc tính nắm giữ các giá
trị dữ liệu trong một đối tượng, chúng định nghĩa một đối tượng đặc thù.
Khái niệm 20.7
Thuộc tính (attribute) là dữ liệu trình bày các đặc điểm về một đối tượng. Một
thuộc tính có thể được gán một giá trị chỉ sau khi một đối tượng dựa trên lớp ấy được
tạo ra. Một khi các thuộc tính được gán giá trị chúng mô tả một đối tượng. Mọi đối
tượng của một lớp phải có cùng các thuộc tính nhưng giá trị của các thuộc tính thì có
thể khác nhau. Một thuộc tính của đối tượng có thể nhận các giá trị khác nhau tại
những thời điểm khác nhau.
20.6. Phương thức (Method)
Các phương thức thực thi các hoạt động của đối tượng. Các phương thức là nhân
tố làm thay đổi các thuộc tính của đối tượng.
Khái niệm 6.8
Phương thức (method) có liên quan tới những thứ mà đối tượng có thể làm. Một
phương thức đáp ứng một chức năng tác động lên dữ liệu của đối tượng (thuộc tính).
Các phương thức xác định cách thức hoạt động của một đối tượng và được thực thi
khi đối tượng cụ thể được tạo ra.Ví dụ, các hoạt động chung của một đối tượng thuộc
lớp Chó là sủa, vẫy tai, chạy, và ăn. Tuy nhiên, chỉ khi một đối tượng cụ thể thuộc lớp
Chó được tạo ra thì các phương thức sủa, vẫy tai, chạy, và ăn mới được thực thi.
327
Các phương thức mang lại một cách nhìn khác về đối tượng. Khi bạn nhìn vào
đối tượng Cửa ra vào bên trong môi trường của bạn (môi trường thế giới thực), một
cách đơn giản bạn có thể thấy nó là một đối tượng bất động không có khả năng suy
nghỉ. Trong tiếp cận hướng đối tượng cho phát triển hệ thống, Cửa ra vào có thể được
liên kết tới phương thức được giả sử là có thể được thực hiện. Ví dụ, Cửa ra vào có thể
mở, nó có thể đóng, nó có thể khóa, hoặc nó có thể mở khóa. Tất cả các phương thức
này gắn kết với đối tượng Cửa ra vào và được thực hiện bởi Cửa ra vào chứ không
phải một đối tượng nào khác.
20.7. Thông điệp (Message)
Một chương trình hay ứng dụng lớn thường chứa nhiều đối tượng khác nhau. Các
đối tượng phần mềm tương tác và giao tiếp với nhau bằng cách gởi các thông điệp
(message). Khi đối tượng A muốn đối tượng B thực hiện các phương thức của đối
tượng B thì đối tượng A gởi một thông điệp tới đối tượng B.
Ví dụ đối tượng người đi xe đạp muốn đối tượng xe đạp thực hiện phương
thức chuyển đổi bánh răng của nó thì đối tượng người đi xe đạp cần phải gởi một
thông điệp tới đối tượng xe đạp.
Đôi khi đối tượng nhận cần thông tin nhiều hơn để biết chính xác thực hiện công
việc gì. Ví dụ khi bạn chuyển bánh răng trên chiếc xe đạp của bạn thì bạn phải chỉ rõ
bánh răng nào mà bạn muốn chuyển. Các thông tin này được truyền kèm theo thông
điệp và được gọi là các tham số (parameter).
Một thông điệp gồm có:
Đối tượng nhận thông điệp
Tên của phương thức thực hiện
Các tham số mà phương thức cần
Khái niệm 6.9
Một thông điệp (message) là một lời yêu cầu một hoạt động. Một thông điệp được
truyền khi một đối tượng triệu gọi một hay nhiều phương thức của đối tượng khác để
yêu cầu thông tin.
Khi một đối tượng nhận được một thông điệp, nó thực hiện một phương thức
tương ứng. Ví dụ đối tượng xe đạp nhận được thông điệp là chuyển đổi bánh răng nó
328
sẽ thực hiện việc tìm kiếm phương thức chuyển đổi bánh răng tương ứng và thực hiện
theo yêu cầu của thông điệp mà nó nhận được.
20.8. Tính bao gói (Encapsulation)
Trong đối tượng xe đạp, giá trị của các thuộc tính được chuyển đổi bởi các
phương thức. Phương thức changeGear() chuyển đổi giá trị của thuộc tính
currentGear. Thuộc tính speed được chuyển đổi bởi phương thức changeGear() hoặc
changRpm().
Trong OOP thì các thuộc tính là trung tâm, là hạt nhân của đối tượng. Các
phương thức bao quanh và che giấu đi hạt nhân của đối tượng từ các đối tượng khác
trong chương trình.Việc bao gói các thuộc tính của một đối tượng bên trong sự che chở
của các phương thức của nó được gọi là sự đóng gói (encapsulation) hay là đóng gói
dữ liệu.
Đặc tính đóng gói dữ liệu là ý tưởng của các nhà thiết các hệ thống hướng đối
tượng. Tuy nhiên, việc áp dụng trong thực tế thì có thể không hoàn toàn như thế. Vì
những lý do thực tế mà các đối tượng đôi khi cần phải phơi bày ra một vài thuộc tính
này và che giấu đi một vài phương thức kia. Tùy thuộc vào các ngôn ngữ lập trình
hướng đối tượng khác nhau, chúng ta có các điều khiển các truy xuất dữ liệu khác
nhau.
Khái niệm 20.10
Đóng gói (encapsulation) là tiến trình che giấu việc thực thi chi tiết của một
đối tượng.
Một đối tượng có một giao diện chung cho các đối tượng khác sử dụng để giao
tiếp với nó. Do đặc tính đóng gói mà các chi tiết như: các trạng thái được lưu trữ như
thế nào hay các hành động được thi công ra sao có thể được che giấu đi từ các đối
tượng khác. Điều này có nghĩa là các chi tiết riêng của đối tượng có thể được chuyển
đổi mà hoàn toàn không ảnh hưởng tới các đối tượng khác có liên hệ với nó. Ví dụ,
một người đi xe đạp không cần biết chính xác cơ chế chuyển bánh răng trên xe đạp
thực sự làm việc như thế nào nhưng vẫn có thể sử dụng nó. Điều này được gọi là che
giấu thông tin.
Khái niệm 20.11
329
Che giấu thông tin (information hiding) là việc ẩn đi các chi tiết của thiết kế
hay thi công từ các đối tượng khác.
20.9. Tính thừa kế (Inheritance)
Hệ thống hướng đối tượng cho phép các lớp được định nghĩa kế thừa từ các lớp
khác. Ví dụ, lớp xe đạp leo núi và xe đạp đua là những lớp con (subclass) của lớp xe
đạp. Như vậy ta có thể nói lớp xe đạp là lớp cha (superclass) của lớp xe đạp leo núi và
xe đạp đua.
Khái niệm 20.12
Thừa kế (inheritance) nghĩa là các hành động (phương thức) và các thuộc tính được
định nghĩa trong một lớp có thể được thừa kế hoặc được sử dụng lại bởi lớp khác.
Khái niệm 20.13
Lớp cha (superclass) là lớp có các thuộc tính hay hành động được thừa hưởng bởi một
hay nhiều lớp khác.
Lớp con (subclass) là lớp thừa hưởng một vài đặc tính chung của lớp cha và thêm
vào những đặc tính riêng khác.
Các lớp con thừa kế thuộc tính và hành động từ lớp cha của chúng. Ví dụ, một xe
đạp leo núi không những có bánh răng, số vòng quay trên phút và tốc độ giống như
mọi xe đạp khác mà còn có thêm một vài loại bánh răng vì thế mà nó cần thêm một
thuộc tính là gearRange (loại bánh răng).
Các lớp con có thể định nghĩa lại các phương thức được thừa kế để cung cấp các
thi công riêng biệt cho các phương thức này. Ví dụ, một xe đạp leo núi sẽ cần một
phương thức đặc biệt để chuyển đổi bánh răng.
Các lớp con cung cấp các phiên bản đặc biệt của các lớp cha mà không cần phải
định nghĩa lại các lớp mới hoàn toàn. Ở đây, mã lớp cha có thể được sử dụng lại nhiều
lần.
20.10.Tính đa hình (Polymorphism)
Một khái niệm quan trọng khác có liên quan mật thiết với truyền thông điệp là đa
hình (polymorphism). Với đa hình, nếu cùng một hành động (phương thức) ứng dụng
cho các đối tượng thuộc các lớp khác nhau thì có thể đưa đến những kết quả khác
nhau.
Khái niệm 20.14
330
Đa hình (polymorphism) nghĩa là “nhiều hình thức”, hành động cùng tên có thể
được thực hiện khác nhau đối với các đối tượng/các lớp khác nhau.
Chúng ta hãy xem xét các đối tượng Cửa Sổ và Cửa Cái. Cả hai đối tượng có
một hành động chung có thể thực hiện là đóng. Nhưng một đối tượng Cửa Cái thực
hiện hành động đó có thể khác với cách mà một đối tượng Cửa Sổ thực hiện hành
động đó. Cửa Cái khép cánh cửa lại trong khi Cửa Sổ hạ các thanh cửa xuống. Thật
vậy, hành động đóng có thể thực hiện một trong hai hình thức khác nhau. Một ví dụ
khác là hành động hiển thị. Tùy thuộc vào đối tượng tác động, hành động ấy có thể
hiển thị một chuỗi, hoặc vẽ một đường thẳng, hoặc là hiển thị một hình.
Đa hình có sự liên quan tới việc truyền thông điệp. Đối tượng yêu cầu cần biết
hành động nào để yêu cầu và yêu cầu từ đối tượng nào. Tuy nhiên đối tượng yêu cầu
không cần lo lắng về một hành động được hoàn thành như thế nào.
Bài tập cuối bài 20
20.1 Trình bày các định nghĩa của các thuật ngữ:
Lập trình hướng đối tượng
Trừu tượng hóa
Đối tượng
Lớp
Thuộc tính
Phương thức
Thông điệp
20.2 Phân biệt sự khác nhau giữa lớp và đối tượng, giữa thuộc tính và giá trị, giữa
thông điệp và truyền thông điệp.
20.3 Trình bày các đặc điểm của OOP.
20.4 Những lợi ích có được thông qua thừa kế và bao gói.
20.5 Những thuộc tính và phương thức cơ bản của một cái máy giặt.
20.6 Những thuộc tính và phương thức cơ bản của một chiếc xe hơi.
20.7 Những thuộc tính và phương thức cơ bản của một hình tròn.
20.8 Chỉ ra các đối tượng trong hệ thống rút tiền tự động ATM.
20.9 Chỉ ra các lớp có thể kế thừa từ lớp điện thoại, xe hơi, và động vật.
331
Bài 21. Lập trình cơ bản với C++
Trong bài học này sẽ thực hành làm quen với ngôn ngữ C/C++
21.1. Chương trình lập trình cơ bản với C++
Có lẽ một trong những cách tốt nhất để bắt đầu học một ngôn ngữ lập trình là
bằng một chương trình. Vậy đây là chương trình đầu tiên của chúng ta :
// my first program in C++
#include
int main ()
{
cout << "Hello World!";
return 0;
}
Hello World!
Chương trình trên đây là chương trình đầu tiên mà hầu hết những người học nghề
lập trình viết đầu tiên và kết quả của nó là viết câu "Hello, World" lên màn hình. Đây
là một trong những chương trình đơn giản nhất có thể viết bằng C++ nhưng nó đã bao
gồm những phần cơ bản mà mọi chương trình C++ có. Hãy cùng xem xét từng dòng
một :
// my first program in C++
Đây là dòng chú thích. Tất cả các dòng bắt đầu bằng hai dấu sổ (//) được coi là chút
thích mà chúng không có bất kì một ảnh hưởng nào đến hoạt động của chương trình.
Chúng có thể được các lập trình viên dùng để giải thích hay bình phẩm bên trong mã
nguồn của chương trình. Trong trường hợp này, dòng chú thích là một giải thích ngắn
gọn những gì mà chương trình chúng ta làm.
#include
Các câu bắt đầu bằng dấu (#) được dùng cho preprocessor (ai dịch hộ tôi từ này với).
Chúng không phải là những dòng mã thực hiện nhưng được dùng để báo hiệu cho trình
dịch. Ở đây câu lệnh #include báo cho trình dịch biết cần phải "include"
332
thư viện iostream. Đây là một thư viện vào ra cơ bản trong C++ và nó phải được
"include" vì nó sẽ được dùng trong chương trình. Đây là cách cổ điển để sử dụng thư
viện iostream
int main ()
Dòng này tương ứng với phần bắt đầu khai báo hàm main. Hàm main là điểm mà tất
cả các chương trình C++ bắt đầu thực hiện. Nó không phụ thuộc vào vị trí của hàm
này (ở đầu, cuối hay ở giữa của mã nguồn) mà nội dung của nó luôn được thực hiện
đầu tiên khi chương trình bắt đầu. Thêm vào đó, do nguyên nhân nói trên, mọi chương
trình C++ đều phải tồn tại một hàm main.
Theo sau main là một cặp ngoặc đơn bởi vì nó là một hàm. Trong C++, tất cả
các hàm mà sau đó là một cặp ngoặc đơn () thì có nghĩa là nó có thể có hoặc không có
tham số (không bắt buộc). Nội dung của hàm main tiếp ngay sau phần khai báo chính
thức được bao trong các ngoặc nhọn ( { } ) như trong ví dụ của chúng ta
cout << "Hello World";
Dòng lệnh này làm việc quan trọng nhất của chương trình. cout là một dòng (stream)
output chuẩn trong C++ được định nghĩa trong thư viện iostream và những gì mà
dòng lệnh này làm là gửi chuỗi kí tự "Hello World" ra màn hình.
Chú ý rằng dòng này kết thúc bằng dấu chấm phẩy ( ; ). Kí tự này được dùng để
kết thúc một lệnh và bắt buộc phải có sau mỗi lệnh trong chương trình C++ của bạn
(một trong những lỗi phổ biến nhất của những lập trình viên C++ là quên mất dấu
chấm phẩy).
Return 0;
Lệnh return kết thúc hàm main và trả về mã đi sau nó, trong trường hợp này là
0. Đây là một kết thúc bình thường của một chương trình không có một lỗi nào trong
quá trình thực hiện. Như bạn sẽ thấy trong các ví dụ tiếp theo, đây là một cách phổ
biến nhất để kết thúc một chương trình C++.
Chương trình được cấu trúc thành những dòng khác nhau để nó trở nên dễ đọc
hơn nhưng hoàn toàn không phải bắt buộc phải làm vậy. Ví dụ, thay vì viết
int main ()
333
{
cout << " Hello World ";
return 0;
}
ta có thể viết
int main () { cout << " Hello World "; return 0; }
cũng cho một kết quả chính xác như nhau.
Trong C++, các dòng lệnh được phân cách bằng dấu chấm phẩy ( ;). Việc chia chương
trình thành các dòng chỉ nhằm để cho nó dễ đọc hơn mà thôi.
Các chú thích.
Các chú thích được các lập trình viên sử dụng để ghi chú hay mô tả trong các phần của
chương trình. Trong C++ có hai cách để chú thích
// Chú thích theo dòng
/* Chú thích theo khối */
Chú thích theo dòng bắt đầu từ cặp dấu xổ (//) cho đến cuối dòng. Chú thích theo khối
bắt đầu bằng /* và kết thúc bằng */ và có thể bao gồm nhiều dòng. Chúng ta sẽ thêm
các chú thích cho chương trình :
/* my second program in C++
with more comments */
#include
int main ()
{
cout << "Hello World! "; //
says Hello World!
cout << "I'm a C++ program"; //
says I'm a C++ program
return 0;
}
Hello World! I'm a C++ program
334
Nếu bạn viết các chú thích trong chương trình mà không sử dụng các dấu //, /* hay */,
trình dịch sẽ coi chúng như là các lệnh C++ và sẽ hiển thị các lỗi.
21.2. Câu lệnh vào \ ra trong C++
* Nhập dữ liệu từ bàn phím: cin>>biến1<<biến2<<...;
* In dữ liệu ra màn hình: cout<<"xâu hằng"<<hoặc biến<<...
Ví dụ1 : Nhập vào 2 cạnh a,b của hình chữ nhật. Tính chu vi diện tích của HCN.
#include
#include
void main(){
// khai bao bien float a,b;
// nhap a, b cout>a; cout>b;
// tinh chu vi float c=(a+b)*2;
// tinh dien tich float s=a*b;
// in ket qua cout<<"\n Chu vi: "<<c<<"\n Dien tich: "<<s;
getch();
}
//-------------------------------------------------------
Ví dụ 2: Nhập vào Điểm toán, Điểm lý, Điểm hóa. Tính điểm tổng
#include
#include
void main(){
// khai bao bien
335
float dToan,dLy,dHoa;
// nhap diem
cout>dToan;
cout>dLy;
// Tinh diem
float dTong=dToan+dLy+dHoa;
// in ket qua
cout<<"\n Diem tong : "<<dTong;
getch();
}
//----------------------------------------------------------------------
Vi dụ 3:
Nhập vào bán kính đường tròn, tính chu vi, diện tích đường tròn. In kết quả
#include
#include
#define PI 3.14 // khai bao hang PI
void main(){
// khai bao bien
float r;
// nhap r
cout>r;
// tinh chu vi, dien tich
float c=2*PI*r; float s=PI*r*r;
// in ket qua
cout<<"\n Chu vi hinh tron: "<<c<<"\n Dien tich hinh tron: "<<s;
getch();
336
}
//--------------------------------------------------------
Vi dụ 4: Nhập vào 1 số nguyên gồm 4 chữ số, tách số đó thành 4 số (đơn vị, chục,
trăm, nghìn) ; in ra màn hình.
#include
#include
void main(){
// khai bao bien int a;
// so nguyen 4 chu so
// nhap a
cout>a;
// tach so int dv, ch,tr,ng;
dv=a%10;
ch=(a/10)%10;
tr=(a/100)%10;
ng=(a/100)%10;
// in ket qua
cout<<"\n Phan don vi: "<<dv<<"\n Phan chuc: "<<ch<<"\n Phan tram: "<<tr<<"\n
Phan ngan: "<<ng
getch();
}
337
Bài 22. Kỹ thuật lập trình cơ bản với lớp
Chương bài học này giới thiệu cấu trúc lớp C++ để định nghĩa các kiểu dữ liệu
mới. Một kiểu dữ liệu mới gồm hai thành phần như sau:
• Đặc tả cụ thể cho các đối tượng của kiểu.
• Tập các thao tác để thực thi các đối tượng.
Ngoài các thao tác đã được chỉ định thì không có thao tác nào khác có thể điều
khiển đối tượng. Về mặt này chúng ta thường nói rằng các thao tác mô tả kiểu, nghĩa
là chúng quyết định cái gì có thể và cái gì không thể xảy ra trên các đối tượng. Cũng
với cùng lý do này, các kiểu dữ liệu thích hợp như thế được gọi là kiểu dữ liệu trừu
tượng (abstract data type) - trừu tượng bởi vì sự đặc tả bên trong của đối tượng được
ẩn đi từ các thao tác mà không thuộc kiểu. Một định nghĩa lớp gồm hai phần: phần
đầu và phần thân. Phần đầu lớp chỉ định tên lớp và các lớp cơ sở(base class). (Lớp cơ
sở có liên quan đến lớp dẫn xuất và được thảo luận trong chương 8). Phần thân lớp
định nghĩa các thành viên lớp. Hai loại thành viên được hỗ trợ:
• Dữ liệu thành viên (member data) có cú pháp của định nghĩa biến và chỉ định các đại
diện cho các đối tượng của lớp.
• Hàm thành viên (member function) có cú pháp của khai báo hàm và chỉ định các
thao tác của lớp (cũng được gọi là các giao diện của lớp).
C++ sử dụng thuật ngữ dữ liệu thành viên và hàm thành viên thay cho thuộc tính và
phương thức nên kể từ đây chúng ta sử dụng dụng hai thuật ngữ này để đặc tả các lớp
và các đối tượng. Các thành viên lớp được liệt kê vào một trong ba loại quyền truy
xuất khác nhau:
• Các thành viên chung (public) có thể được truy xuất bởi tất cả các thành phần sử
dụng lớp.
• Các thành viên riêng (private) chỉ có thể được truy xuất bởi các thành viên lớp.
• Các thành viên được bảo vệ (protected) chỉ có thể được truy xuất bởi các thành viên
lớp và các thành viên của một lớp dẫn xuất.
Kiểu dữ liệu được định nghĩa bởi một lớp được sử dụng như kiểu có sẵn.
338
22.1. Lớp đơn giản
Danh sách 22.1 trình bày định nghĩa của một lớp đơn giản để đại diện cho các
điểm trong không gian hai chiều.
Danh sách 22.1
1 class Point {
2 int xVal, yVal;
3 public:
4 void SetPt (int, int);
5 void OffsetPt (int, int);
6 };
Chú giải
1 . Hàng này chứa phần đầu của lớp và đặt tên cho lớp là Point. Một định nghĩa lớp
luôn bắt đầu với từ khóa class và theo sau đó là tên lớp. Một dấu { (ngoặc mở) đánh
dấu điểm bắt đầu của thân lớp.
2 . Hàng này định nghĩa hai dữ liệu thành viên xVal và yVal, cả hai thuộc kiểu int.
Quyền truy xuất mặc định cho một thành viên của lớp là riêng (private). Vì thế cả hai
xVal và yVal là riêng.
3 .Từ khóa này chỉ định rằng từ điểm này trở đi các thành viên của lớp là chung
(public).
4-5 Hai hàng này là các hàm thành viên. Cả hai có hai tham số nguyên và một kiểu trả
về void.
6 Dấu } (ngoặc đóng) này đánh dấu kết thúc phần thân lớp. Thứ tự trình bày các dữ
liệu thành viên và hàm thành viên của một lớp là không quan trọng lắm. Ví dụ lớp trên
có thể được viết tương đương như thế này:
class Point {
public:
void SetPt (int, int);
void OffsetPt (int, int);
private:
int xVal, yVal;
};
339
22.2. Các hàm thành viên nội tuyến
Việc định nghĩa những hàm thành viên là nội tuyến cải thiện tốc độ đáng kể. Một
hàm thành viên được định nghĩa là nội tuyến bằng cách chèn từ khóa inline trước định
nghĩa của nó.
inline void Point::SetPt (int x,int y)
{
xVal = x;
yVal = y;
}
Một cách dễ hơn để định nghĩa các hàm thành viên là nội tuyến là chèn định
nghĩa của các hàm này vào bên trong lớp.
class Point {
int xVal, yVal;
public:
void SetPt (int x,int y) { xVal = x; yVal = y; }
void OffsetPt (int x,int y) { xVal += x; yVal += y; }
};
Chú ý rằng bởi vì thân hàm được chèn vào nên không cần dấu chấm phẩy sau
khai báo hàm. Hơn nữa, các tham số của hàm phải được đặt tên.
22.3. Ví dụ: Lớp Set
Tập hợp (Set) là một tập các đối tượng không kể thứ tự và không lặp. Ví dụ này
thể hiện rằng một tập hợp có thể được định nghĩa bởi một lớp như thế nào. Để đơn
giản chúng ta giới hạn trên hợp các số nguyên với số lượng các phần tử là hữu hạn.
Danh sách 7.3 trình bày định nghĩa lớp Set.
1 #include
2 const maxCard = 100;
3 enum Bool {false, true};
4 class Set {
5 public:
6 void EmptySet (void){ card = 0; }
7 Bool Member (const int);
340
8 void AddElem (const int);
9 void RmvElem (const int);
10 void Copy (Set&);
11 Bool Equal (Set&);
12 void Intersect (Set&, Set&);
13 void Union (Set&, Set&);
14 void Print (void);
15 private:
16 int elems[maxCard]; // cac phan tu cua tap hop
17 int card; // so phan tu cua tap hop
18 };
Chú giải
2 maxCard biểu thị số lượng phần tử tối đa trong tập hợp.
6 EmptySet xóa nội dung tập hợp bằng cách đặt số phần tử tập hợp về 0.
7 Member kiểm tra một số cho trước có thuộc tập hợp hay không.
8 AddElem thêm một phần tử mới vào tập hợp. Nếu phần tử đã có trong tập
hợp rồi thì không làm gì cả. Ngược lại thì thêm nó vào tập hợp. Trường
hợp mà tập hợp đã tràn thì phần tử không được xen vào.
9 RmvElem xóa một phần tử trong tập hợp.
10 Copy sao chép tập hợp tới một tập hợp khác. Tham số cho hàm này là
một tham chiếu tới tập hợp đích.
11 Equal kiểm tra hai tập hợp có bằng nhau hay không. Hai tập hợp là bằng
nhau nếu chúng chứa đựng chính xác cùng số phần tử (thứ tự của chúng
là không quan trọng).
12 Intersect so sánh hai tập hợp để cho ra tập hợp thứ ba chứa các phần tử là
giao của hai tập hợp. Ví dụ, giao của {2,5,3} và {7,5,2} là {2,5}.
13 Union so sánh hai tập hợp để cho ra tập hợp thứ ba chứa các phần tử là
hội của hai tập hợp. Ví dụ, hợp của {2,5,3} và {7,5,2} là {2,5,3,7}.
14 Print in một tập hợp sử dụng ký hiệu toán học theo qui ước. Ví dụ, một
tập hợp gồm các số 5, 2, và 10 được in là {5,2,10}.
16 Các phần tử của tập hợp được biểu diễn bằng mảng elems.
341
17 Số phần tử của tập hợp được biểu thị bởi card. Chỉ có các đầu vào bản số
đầu tiên trong elems được xem xét là các phần tử hợp lệ.
Việc định nghĩa tách biệt các hàm thành viên của một lớp đôi khi được biết tới như
là sự cài đặt (implementation) của một lớp. Sự thi công lớp Set là như sau.
Bool Set::Member (const int elem)
{
for (register i = 0; i < card; ++i)
if (elems[i] == elem)
return true;
return false;
}
void Set::AddElem (const int elem)
{
if (Member(elem))
return;
if (card < maxCard)
elems[card++] = elem;
else
cout << "Set overflow\n";
}
void Set::RmvElem (const int elem)
{
for (register i = 0; i < card; ++i)
if (elems[i] == elem) {
for (; i < card-1; ++i) // dich cac phan tu sang trai
elems[i] = elems[i+1];
--card;
}
}
void Set::Copy (Set &set)
{
342
for (register i = 0; i < card; ++i)
set.elems[i] = elems[i];
set.card = card;
}
Bool Set::Equal (Set &set)
{
if (card != set.card)
return false;
for (register i = 0; i < card; ++i)
if (!set.Member(elems[i]))
return false;
return true;
}
void Set::Intersect (Set &set, Set &res)
{
res.card = 0;
for (register i = 0; i < card; ++i)
if (set.Member(elems[i]))
res.elems[res.card++] = elems[i];
}
void Set::Union (Set &set, Set &res)
{
set.Copy(res);
for (register i = 0; i < card; ++i)
res.AddElem(elems[i]);
}
void Set::Print (void)
{
cout << "{";
for (int i = 0; i < card-1; ++i)
343
cout << elems[i] << ",";
if (card > 0) // khong co dau , sau phan tu cuoi cung
cout << elems[card-1];
cout << "}\n";
}
Hàm main sau đây tạo ra ba tập đối tượng Set và thực thi một vài hàm thành viên
của nó.
int main (void)
{
Set s1, s2, s3;
s1.EmptySet(); s2.EmptySet(); s3.EmptySet();
s1.AddElem(10); s1.AddElem(20); s1.AddElem(30); s1.AddElem(40);
s2.AddElem(30); s2.AddElem(50); s2.AddElem(10); s2.AddElem(60);
cout << "s1 = "; s1.Print();
cout << "s2 = "; s2.Print();
s2.RmvElem(50);
cout << "s2 - {50} = ";
s2.Print();
if (s1.Member(20))
cout << "20 is in s1\n";
s1.Intersect(s2,s3);
cout << "s1 intsec s2 = ";
s3.Print();
s1.Union(s2,s3);
cout << "s1 union s2 = ";
s3.Print();
if (!s1.Equal(s2))
cout s2\n";
return 0;
}
Khi chạy chương trình sẽ cho kết quả như sau:
344
s1 = {10,20,30,40}
s2 = {30,50,10,60}
s2 - {50} = {30,10,60}
20 is in s1
s1 intsec s2 = {10,30}
s1 union s2 = {30,10,60,20,40}
s1 s2
345
Bài 23. Hàm xây dựng (Constructor) và Hàm hủy (Destructor)
23.1. Hàm xây dựng (Constructor)
Hoàn toàn có thể định nghĩa và khởi tạo các đối tượng của một lớp ở cùng một
thời điểm. Điều này được hỗ trợ bởi các hàm đặc biệt gọi là hàm xây dựng
(constructor). Một hàm xây dựng luôn có cùng tên với tên lớp của nó. Nó không bao
giờ có một kiểu trả về rõ ràng. Ví dụ,
class Point {
int xVal, yVal;
public:
Point (int x,int y) {xVal = x; yVal = y;} // constructor
void OffsetPt (int,int);
};
là một định nghĩa có thể của lớp Point, trong đó SetPt đã được thay thế bởi một hàm
xây dựng được định nghĩa nội tuyến.
Bây giờ chúng ta có thể định nghĩa các đối tượng kiểu Point và khởi tạo chúng
một lượt. Điều này quả thật là ép buộc đối với những lớp chứa các hàm xây dựng đòi
hỏi các đối số:
Point pt1 = Point(10,20);
Point pt2; // trái luật
Hàng thứ nhất có thể được đặc tả trong một hình thức ngắn gọn.
Point pt1(10,20);
Một lớp có thể có nhiều hơn một hàm xây dựng. Tuy nhiên, để tránh mơ hồ thì
mỗi hàm xây dựng phải có một dấu hiệu duy nhất. Ví dụ,
class Point {
int xVal, yVal;
public:
Point (int x, int y) { xVal = x; yVal = y; }
Point (float, float); // các tọa độ cực
Point (void) { xVal = yVal = 0; } // gốc
void OffsetPt (int, int);
346
};
Point::Point (float len, float angle) // các tọa độ cực
{
xVal = (int) (len * cos(angle));
yVal = (int) (len * sin(angle));
}
có ba hàm xây dựng khác nhau. Một đối tượng có kiểu Point có thể được định nghĩa sử
dụng bất kỳ hàm nào trong các hàm này:
Point pt1(10,20); // tọa độ Đê-cát-tơ
Point pt2(60.3,3.14); // tọa độ cực
Point pt3; // gốc
Lớp Set có thể được cải tiến bằng cách sử dụng một hàm xây dựng thay vì EmptySet:
class Set {
public:
Set (void) { card = 0; }
//...
};
Điều này tạo thuận lợi cho các lập trình viên không cần phải nhớ gọi EmptySet
nữa. Hàm xây dựng đảm bảo rằng mọi tập hợp là rỗng vào lúc ban đầu.
Lớp Set có thể được cải tiến hơn nữa bằng cách cho phép người dùng điều khiển
kích thước tối đa của tập hợp. Để làm điều này chúng ta định nghĩa elems như một
con trỏ số nguyên hơn là mảng số nguyên. Hàm xây dựng sau đó có thể được cung cấp
một đối số đặc tả kích thước tối đa mong muốn.
Nghĩa là maxCard sẽ không còn là hằng được dùng cho tất cả các đối tượng Set
nữa mà chính nó trở thành một thành viên dữ liệu:
class Set {
public:
Set (const int size);
//...
private:
347
int *elems; // cac phan tu tap hop
int maxCard; // so phan tu toi da
int card; // so phan tu
};
Hàm xây dựng dễ dàng cấp phát một mảng động với kích thước mong muốn và khởi
tạo giá trị phù hợp cho maxCard và card:
Set::Set (const int size)
{
elems = new int[size];
maxCard = size;
card = 0;
}
Bây giờ có thể định nghĩa các tập hợp có các kích thước tối đa khác nhau:
Set ages(10), heights(20), primes(100);
Chúng ta cần lưu ý rằng một hàm xây dựng của đối tượng được ứng dụng khi đối
tượng được tạo ra. Điều này phụ thuộc vào phạm vi của đối tượng. Ví dụ, một đối
tượng toàn cục được tạo ra ngay khi sự thực thi chương trình bắt đầu; một đối tượng tự
động được tạo ra khi phạm vi của nó được đăng ký; và một đối tượng động được tạo ra
khi toán tử new được áp dụng tới nó.
23.2. Hàm hủy (Destructor)
Như là một hàm xây dựng được dùng để khởi tạo một đối tượng khi nó được tạo
ra, một hàm hủy được dùng để dọn dẹp một đối tượng ngay trước khi nó được thu hồi.
Hàm hủy luôn luôn có cùng tên với chính tên lớp của nó nhưng được đi đầu với ký tự
~. Không giống các hàm xây dựng, mỗi lớp chỉ có nhiều nhất một hàm hủy. Hàm hủy
không nhận bất kỳ đối số nào và không có một kiểu trả về rõ ràng.
Thông thường các hàm hủy thường hữu ích và cần thiết cho các lớp chứa dữ liệu
thành viên con trỏ. Các dữ liệu thành viên con trỏ trỏ tới các khối bộ nhớ được cấp
phát từ lớp. Trong các trường hợp như thế thì việc giải phóng bộ nhớ đã được cấp
phát cho các con trỏ thành viên là cực kỳ quan trọng trước khi đối tượng được thu hồi.
Hàm hủy có thể làm công việc như thế.
348
Ví dụ, phiên bản sửa lại của lớp Set sử dụng một mảng được cấp phát động cho
các thành viên elems. Vùng nhớ này nên được giải phóng bởi một hàm hủy:
class Set {
public:
Set (const int size);
~Set (void) {delete elems;} // destructor
//...
private:
int *elems; // cac phan tu tap hop
int maxCard; // so phan tu toi da
int card; // so phan tu cua tap hop
};
Bây giờ hãy xem xét cái gì xảy ra khi một Set được định nghĩa và sử dụng trong
hàm:
void Foo (void)
{
Set s(10);
//...
}
Khi hàm Foo được gọi, hàm xây dựng cho s được triệu tập, cấp phát lưu trữ cho
s.elems và khởi tạo các thành viên dữ liệu của nó. Kế tiếp, phần còn lại của thân hàm
Foo được thực thi. Cuối cùng, trước khi Foo trả về, hàm hủy cho cho s được triệu tập,
xóa đi vùng lưu trữ bị chiếm bởi s.elems. Kể từ đây cho đến khi cấp phát lưu trữ được
kể đến thì s ứng xử giống như là biến tự động của một kiểu có sẳn được tạo ra khi
phạm vi của nó được biết đến và được hủy đi khi phạm vi của nó được rời khỏi.
Nói chung, hàm xây dựng của đối tượng được áp dụng trước khi đối tượng
được thu hồi. Điều này phụ thuộc vào phạm vi của đối tượng. Ví dụ, một đối tượng
toàn cục được thu hồi khi sự thực hiện của chương trình hoàn tất; một đối tượng tự
động được thu hồi khi toán tử delete được áp dụng tới nó.
349
Bài 24. Kỹ thuật lập trình Thừa kế
Trong thực tế hầu hết các lớp có thể kế thừa từ các lớp có trước mà không cần
định nghĩa lại mới hoàn toàn. Ví dụ xem xét một lớp được đặt tên là RecFile đại diện
cho một tập tin gồm nhiều mẫu tin và một lớp khác được đặt tên là SortedRecFile đại
diện cho một tập tin gồm nhiều mẫu tin được sắp xếp. Hai lớp này có thể có nhiều
điểm chung. Ví dụ, chúng có thể có các thành viên hàm giống nhau như là Insert,
Delete, và Find, cũng như là thành viên dữ liệu giống nhau. SortedRecFile là một
phiên bản đặc biệt của RecFile với thuộc tính các mẫu tin của nó được tổ chức theo thứ
tự được thêm vào. Vì thế hầu hết các hàm thành viên trong cả hai lớp là giống nhau
trong khi một vài hàm mà phụ thuộc vào yếu tố tập tin được sắp xếp thì có thể khác
nhau. Ví dụ, hàm Find có thể là khác trong lớp SortedRecFile bởi vì nó có thể nhờ vào
yếu tố thuận lợi là tập tin được sắp để thực hiện tìm kiếm nhị phân thay vì tìm tuyến
tính như hàm Find của lớp RecFile.
Với các thuộc tính được chia sẻ của hai lớp này thì việc định nghĩa chúng một
cách độc lập là rất dài dòng. Rõ ràng điều này dẫn tới việc phải sao chép lại mã đáng
kể. Mã không chỉ mất thời gian lâu hơn để viết nó mà còn khó có thể được bảo trì hơn:
một thay đổi tới bất kỳ thuộc tính chia sẻ nào có thể phải được sửa đổi tới cả hai lớp.
Lập trình hướng đối tượng cung cấp một kỹ thuật thuận lợi gọi là thừa kế để giải
quyết vấn đề này. Với thừa kế thì một lớp có thể thừa kế những thuộc tính của một lớp
đã có trước. Chúng ta có thể sử dụng thừa kế để định nghĩa những thay đổi của một
lớp mà không cần định nghĩa lại lớp mới từ đầu. Các thuộc tính chia sẻ chỉ được định
nghĩa một lần và được sử dụng lại khi cần.
Trong C++ thừa kế được hỗ trợ bởi các lớp dẫn xuất (derived class). Lớp dẫn
xuất thì giống như lớp gốc ngoại trừ định nghĩa của nó dựa trên một hay nhiều lớp có
sẵn được gọi là lớp cơ sở (base class). Lớp dẫn xuất có thể chia sẻ những thuộc tính đã
chọn (các thành viên hàm hay các thành viên dữ liệu) của các lớp cơ sở của nó nhưng
không làm chuyển đổi định nghĩa của bất kỳ lớp cơ sở nào. Lớp dẫn xuất chính nó có
thể là lớp cơ sở của một lớp dẫn xuất khác. Quan hệ thừa kế giữa các lớp của một
chương trình được gọi là quan hệ cấp bậc lớp (class hierarchy).
350
Lớp dẫn xuất cũng được gọi là lớp con (subclass) bởi vì nó trở thành cấp thấp
hơn của lớp cơ sở trong quan hệ cấp bậc. Tương tự một lớp cơ sở có thể được gọi là
lớp cha (superclass) bởi vì từ nó có nhiều lớp khác có thể được dẫn xuất.
24.1. Ví dụ minh họa
Chúng ta sẽ định nghĩa hai lớp nhằm mục đích minh họa một số khái niệm lập
trình trong các phần sau của chương này. Hai lớp được định nghĩa trong Danh sách
22.1 và hỗ trợ việc tạo ra một thư mục các đối tác cá nhân.
1 #include
2 #include
3 class Contact {
4 public:
5 Contact(const char *name, const char *address, const char *tel);
6 ~Contact (void);
7 const char* Name (void) const {return name;}
8 const char* Address(void) const {return address;}
9 const char* Tel(void) const {return tel;}
10 friend ostream& operator << (ostream&, Contact&);
11 private:
12 char *name; // ten doi tac
13 char *address; // dia chi doi tac
14 char *tel; // so dien thoai
15 };
16//-------------------------------------------------------------------
17 class ContactDir {
18 public:
19 ContactDir(const int maxSize);
10 ~ContactDir(void);
21 void Insert(const Contact&);
22 void Delete(const char *name);
23 Contact* Find(const char *name);
24 friend ostream& operator <<(ostream&, ContactDir&);
351
25 private:
26 int Lookup(const char *name);
27 Contact **contacts; // danh sach cac doi tac
int dirSize; // kich thuoc thu muc hien tai
28 int maxSize; // kich thuoc thu muc toi da
29 };
Chú giải
3 Lớp Contact lưu giữ các chi tiết của một đối tác (nghĩa là, tên, địa chỉ, và số điện
thoại).
18 Lớp ContactDir cho phép chúng ta thêm, xóa, và tìm kiếm một danh sách các đối
tác.
22 Hàm Insert xen một đối tác mới vào thư mục. Điều này sẽ viết chồng lên một đối
tác tồn tại (nếu có) với tên giống nhau.
23 Hàm Delete xóa một đối tác (nếu có) mà tên của đối tác trùng với tên đã cho.
24 Hàm Find trả về một con trỏ tới một đối tác (nếu có) mà tên của đối tác khớp với
tên đã cho.
27 Hàm Lookup trả về chỉ số vị trí của một đối tác mà tên của đối tác khớp với tên đã
cho. Nếu không tồn tại thì sau đó hàm Lookup trả về chỉ số của vị trí mà tại đó mà một
đầu vào như thế sẽ được thêm vào. Hàm Lookup được định nghĩa như là riêng
(private) bởi vì nó là một hàm phụ được sử dụng bởi các hàm Insert, Delete, và Find.
Cài đặt của hàm thành viên và hàm bạn như sau:
Contact::Contact (const char *name,
const char *address, const char *tel)
{
Contact ::name = new char[strlen(name) + 1];
Contact::address = new char[strlen(address) + 1];
Contact :: tel = new char[strlen(tel) + 1];
strcpy(Contact::name, name);
strcpy(Contact::address, address);
strcpy(Contact: :tel, tel);
}
352
Contact ::~Contact (void)
{
delete name;
delete address;
delete tel ;
}
ostream& operator << (ostream &os, Contact &c)
{
os << "(" << c.name << " , "
<< c.address << " , " << c.tel << ")";
return os;
}
ContactDir::ContactDir (const int max)
{
typedef Contact *ContactPtr;
dirSize = 0;
maxSize = max;
contacts = new ContactPtr[maxSize];
};
ContactDir::~ContactDir (void)
{
for (register i = 0; i < dirSize; ++i)
delete contacts[i];
delete [] contacts;
}
void ContactDir::Insert (const Contact& c)
{
if (dirSize < maxSize) {
int idx = Lookup(c.Name());
if (idx > 0 &&
strcmp(c.Name(), contacts[idx]->Name()) == 0) {
353
delete contacts[idx];
} else {
for (register i = dirSize; i > idx; --i) // dich phai
contacts[i] = contacts[i-1];
++dirSize;
}
contacts[idx] = new Contact(c.Name(), c.Address(), c.Tel());
}
}
void ContactDir::Delete (const char *name)
{
int idx = Lookup(name);
if (idx < dirSize) {
delete contacts[idx];
--dirSize;
for (register i = idx; i < dirSize; ++i) // dich trai
contacts[i] = contacts[i+1];
}
}
Contact *ContactDir: :Find (const char *name)
{
int idx = Lookup(name);
return (idx < dirSize &&
strcmp(contacts[idx]->Name(), name) == 0)
? contacts[idx]
: 0;
}
int ContactDir::Lookup (const char *name)
{
for (register i = 0; i < dirSize; ++i)
if (strcmp(contacts[i]->Name(), name) == 0)
354
return i;
return dirSize;
}
ostream &operator << (ostream &os, ContactDir &c)
{
for (register i = 0; i < c.dirSize; ++i)
os << *(c.contacts[i]) << '\n' ;
return os;
}
Hàm main sau thực thi lớp ContactDir bằng cách tạo ra một thư mục nhỏ và gọi
các hàm thành viên:
int main (void)
{
ContactDir dir(10);
dir.Insert(Contact("Mary", "11 South Rd", "282 1324"));
dir.Insert(Contact("Peter", "9 Port Rd", "678 9862"));
dir.Insert(Contact("Jane", "321 Yara Ln", "982 6252"));
dir.Insert(Contact("Jack", "42 Wayne St", "663 2989"));
dir.Insert(Contact("Fred", "2 High St", "458 2324"));
cout << dir;
cout << "Find Jane: " << *dir.Find("Jane") << '\n' ;
dir.Delete("Jack");
cout << "Deleted Jack\n";
cout << dir;
return 0;
};
Khi chạy nó sẽ cho kết quả sau:
(Mary , 11 South Rd , 282 1324)
(Peter , 9 Port Rd , 678 9862)
(Jane , 321 Yara Ln , 982 6252)
(Jack , 42 Wayne St , 663 2989)
355
(Fred , 2 High St , 458 2324)
Find Jane: (Jane , 321 Yara Ln , 982 6252)
Deleted Jack
(Mary , 11 South Rd , 282 1324)
(Peter , 9 Port Rd , 678 9862)
(Jane , 321 Yara Ln , 982 6252)
(Fred , 2 High St , 458 2324)
24.2. Lớp dẫn xuất đơn giản
Chúng ta muốn định nghĩa một lớp gọi là SmartDir ứng xử giống như là lớp
ContactDir và theo dõi tên của đối tác mới vừa được tìm kiếm gần nhất. Lớp SmartDir
được định nghĩa tốt nhất như là một dẫn xuất của lớp ContactDir như được minh họa
bởi Danh sách 22.2.
1 class SmartDir : public ContactDir {
2 public:
3 SmartDir(const int max) : ContactDir(max) {recent = 0;}
4 Contact* Recent (void);
5 Contact* Find (const char *name);
6 private:
7 char * recent; // ten duoc tim gan nhat
8 };
Chú giải
1 Phần đầu của lớp dẫn xuất chèn vào các lớp cơ sở mà nó thừa kế. Một dấu hai chấm
(:) phân biệt giữa hai phần. Ở đây, lớp ContactDir được đặc tả là lớp cơ sở mà lớp
SmartDir được dẫn xuất. Từ khóa public phía trước lớp ContactDir chỉ định rằng lớp
ContactDir được sử dụng như một lớp cơ sở chung.
3 Lớp SmartDir có hàm xây dựng của nó, hàm xây dựng này triệu gọi hàm xây dựng
của lớp cơ sở trong danh sách khởi tạo thành viên của nó.
4 Hàm Recent trả về một con trỏ tới đối tác được tìm kiếm sau cùng (hoặc 0 nếu không
có).
5 Hàm Find được định nghĩa lại sao cho nó có thể ghi nhận đầu vào được tìm kiếm sau
cùng.
356
7 Con trỏ recent được đặt tới tên của đầu vào đã được tìm sau cùng.
Các hàm thành viên được định nghĩa như sau:
Contact* SmartDir::Recent (void)
{
return recent == 0 ? 0 : ContactDir::Find(recent);
}
Contact* SmartDir::Find (const char *name)
{
Contact *c = ContactDir::Find(name);
if (c != 0)
recent = (char*) c->Name();
return c;
}
Bởi vì lớp ContactDir là một lớp cơ sở chung của lớp SmartDir nên tất cả thành viên
chung của lớp ContactDir trở thành các thành viên chung của lớp martDir. Điều này
nghĩa là chúng ta có thể triệu gọi một hàm thành viên như là Insert trên một đối tượng
SmartDir và đây là một lời gọi tới ContactDir::Insert. Tương tự, tất cả các thành viên
riêng của lớp ContactDir trở thành các thành viên riêng của lớp SmartDir.
Phù hợp với các nguyên lý ẩn thông tin, các thành viên riêng của lớp ContactDir
sẽ không thể được truy xuất bởi SmartDir. Vì thế, lớp SmartDir sẽ không thể truy xuất
tới bất kỳ thành viên dữ liệu nào của lớp ContactDir cũng như là hàm thành viên riêng
Lookup.
Lớp SmartDir định nghĩa lại hàm thành viên Find. Điều này không nên nhầm
lẫn với tái định nghĩa. Có hai định nghĩa phân biệt của hàm này: ContactDir::Find và
SmartDir::Find (cả hai định nghĩa có cùng dấu hiệu dẫu cho chúng có thể có các dấu
hiệu khác nhau nếu được yêu cầu). Triệu gọi hàm Find trên đối tượng SmartDir thứ hai
sẽ được gọi. Như được minh họa bởi định nghĩa của hàm Find trong lớp SmartDir,hàm
thứ nhất có thể vẫn còn được triệu gọi bằng cách sử dụng tên đầy đủ của nó.
Đoạn mã sau minh họa lớp SmartDir cư xử như là lớp ContactDir nhưng cũng
theo dõi đầu vào được tìm kiếm được gần nhất:
SmartDir dir(10);
357
dir.Insert(Contact("Mary", "11 South Rd", "282 1324"));
dir.Insert(Contact("Peter", "9 Port Rd", "678 9862"));
dir.Insert(Contact("Jane", "321 Yara Ln", "982 6252"));
dir.Insert(Contact("Fred", "2 High St", "458 2324"));
dir.Find("Jane");
dir.Find("Peter");
cout << "Recent: " << *dir.Recent() << '\n';
Điều này sẽ cho ra kết quả sau:
Recent: (Peter , 9 Port Rd , 678 9862)
Một đối tượng kiểu SmartDir chứa đựng tất cả dữ liệu thành viên của
ContactDir cũng như là bất kỳ dữ liệu thành viên thêm vào được giới thiệu bởi
SmartDir. Hình 22.1 minh họa việc tạo ra một đối tượng ContactDir và một đối tượng
SmartDir.
Hình 22.1. Các đối tượng lớp cơ sở và lớp dẫn xuất.
24.3. Ký hiệu thứ bậc lớp
Thứ bậc lớp thường được minh họa bằng cách sử dụng ký hiệu đồ họa đơn giản.
Hình 9.2 minh họa ký hiệu của ngôn ngữ UML mà chúng ta sẽ đang sử dụng trong
giáo trình này. Mỗi lớp được biểu diễn bằng một hộp được gán nhãn là tên lớp. Thừa
kế giữa hai lớp được minh họa bằng một mũi tên có hướng vẽ từ lớp dẫn xuất đến lớp
cơ sở. Một đường thẳng với hình kim cương ở một đầu miêu tả composition (tạm dịch
là quan hệ bộ phận, nghĩa là một đối tượng của lớp được bao gồm một hay nhiều đối
tượng của lớp khác). Số đối tượng chứa bởi đối tượng khác được miêu tả bởi một nhãn
(ví dụ, n).
358
Hình trên được thông dịch như sau. Contact, ContactDir, và SmartDir là các lớp.
Lớp ContactDir gồm có không hay nhiều đối tượng Contact. Lớp SmartDir được dẫn
xuất từ lớp ContactDir.
Các file đính kèm theo tài liệu này:
- bai_giang_ky_thuat_lap_trinh_1468.pdf